@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
@@ -3,6 +3,7 @@
3
3
  * Manifest consistency checks and rebuild/recovery operations.
4
4
  */
5
5
  import { stat } from 'node:fs/promises';
6
+ import { GeneralError } from '../errors/base.js';
6
7
  import { discoverPatches, getAllTargetFilesFromPatch } from './patch-files.js';
7
8
  import { withPatchDirectoryLock } from './patch-lock.js';
8
9
  import { loadPatchesManifestState, PATCHES_MANIFEST, savePatchesManifest, } from './patch-manifest-io.js';
@@ -59,8 +60,8 @@ export async function validatePatchesManifestConsistency(patchesDir) {
59
60
  });
60
61
  continue;
61
62
  }
62
- const declaredFiles = normalizeFiles(metadata.filesAffected);
63
- const actualFiles = normalizeFiles(await getAllTargetFilesFromPatch(patch.path));
63
+ const declaredFiles = normalizeAffectedFiles(metadata.filesAffected);
64
+ const actualFiles = normalizeAffectedFiles(await getAllTargetFilesFromPatch(patch.path));
64
65
  if (!sameStringArray(declaredFiles, actualFiles)) {
65
66
  issues.push({
66
67
  code: 'files-affected-mismatch',
@@ -80,26 +81,78 @@ export async function validatePatchesManifestConsistency(patchesDir) {
80
81
  }
81
82
  return issues;
82
83
  }
84
+ /**
85
+ * Recovery hint for an unrepaired consistency failure.
86
+ *
87
+ * Drift that is only in `filesAffected` is a derived value disagreeing with
88
+ * the diff it describes, and the narrow repair fixes exactly that. Naming the
89
+ * whole-manifest rebuild for it — as this hint used to, unconditionally — puts
90
+ * an operator one keystroke from rewriting every row in the manifest to
91
+ * correct one list.
92
+ */
93
+ export function recommendManifestRepair(issues) {
94
+ const filesAffectedOnly = issues.length > 0 && issues.every((issue) => issue.code === 'files-affected-mismatch');
95
+ return filesAffectedOnly
96
+ ? 'Run "fireforge doctor --repair-files-affected" to recompute filesAffected from the patch ' +
97
+ 'bodies (add --dry-run to preview). It rewrites only the drifted lists and leaves every ' +
98
+ 'other manifest field untouched.'
99
+ : 'Run "fireforge doctor --repair-patches-manifest" to rebuild patches.json from the patch ' +
100
+ 'files (add --dry-run to preview). Only filesAffected and order are recomputed; every ' +
101
+ 'other field on an existing entry is preserved.';
102
+ }
83
103
  /**
84
104
  * Rebuilds patches.json from the patch files currently present on disk.
85
- * Existing metadata is preserved when possible; missing entries are recovered
86
- * from filename structure, patch contents, and file mtimes.
105
+ *
106
+ * This is a MERGE, not a from-scratch rebuild: only `filesAffected` and
107
+ * `order` are recomputed from the patch files, and every other field on an
108
+ * existing entry is carried forward untouched. Entries with no pre-existing
109
+ * metadata are recovered from filename structure, patch contents, and file
110
+ * mtimes.
111
+ *
112
+ * Refuses when patches.json exists but cannot be parsed, because that is the
113
+ * one case where nothing can be carried forward and the "merge" silently
114
+ * becomes a whole-manifest reinvention. Pass `allowMetadataLoss` to accept it.
115
+ *
87
116
  * @param patchesDir - Path to the patches directory
88
117
  * @param fallbackSourceEsrVersion - source version to use for recovered legacy entries
89
- * @returns {@link RebuildPatchesManifestResult} — the persisted manifest
90
- * plus the filenames that were reconstructed from generic defaults.
118
+ * @param options - Lock options plus `allowMetadataLoss` / `dryRun`
119
+ * @returns {@link RebuildPatchesManifestResult} — the rebuilt manifest, the
120
+ * filenames that were reconstructed from generic defaults, and the rows
121
+ * dropped because their patch file is gone.
91
122
  */
92
- export async function rebuildPatchesManifest(patchesDir, fallbackSourceEsrVersion) {
123
+ export async function rebuildPatchesManifest(patchesDir, fallbackSourceEsrVersion, options = {}) {
93
124
  // The whole read → discover → rebuild → save cycle runs under the shared
94
125
  // patch-directory lock (invariant 2, docs/lifecycle-invariants.md):
95
- // manifest writes serialize on this lock, and a repair racing a
96
- // concurrent export/reorder used to be able to clobber the other
97
- // writer's manifest. Not reentrant — callers must not already hold it.
98
- return withPatchDirectoryLock(patchesDir, () => rebuildPatchesManifestUnderLock(patchesDir, fallbackSourceEsrVersion));
126
+ // manifest writes serialize on this lock, so without it a repair racing a
127
+ // concurrent export/reorder can clobber the other writer's manifest. Not
128
+ // reentrant — callers must not already hold it.
129
+ //
130
+ // A dry run takes the lock too: it reads the same manifest and patch bodies
131
+ // a real run would, and a projection computed against a queue being
132
+ // rewritten underneath is worth less than no projection at all.
133
+ return withPatchDirectoryLock(patchesDir, () => rebuildPatchesManifestUnderLock(patchesDir, fallbackSourceEsrVersion, options), options);
99
134
  }
100
- async function rebuildPatchesManifestUnderLock(patchesDir, fallbackSourceEsrVersion) {
135
+ async function rebuildPatchesManifestUnderLock(patchesDir, fallbackSourceEsrVersion, options) {
101
136
  const manifestState = await loadPatchesManifestState(patchesDir);
102
137
  const existingEntries = new Map();
138
+ // An unparseable manifest yields `manifest: null`, which the merge below
139
+ // cannot distinguish from "no manifest at all" — every entry would be
140
+ // treated as new and every descriptive field reinvented. `filesAffected`
141
+ // is the only field a .patch body can yield; `stagedDependencies`,
142
+ // `lintIgnore`, `tier`, `description`, `category`, `createdAt` and the
143
+ // source version cannot be reconstructed from a diff at all. Refuse rather
144
+ // than write that, since the write is what makes it unrecoverable on a
145
+ // queue that is not under version control.
146
+ if (manifestState.exists && manifestState.parseError && options.allowMetadataLoss !== true) {
147
+ throw new GeneralError(`Refusing to rebuild ${PATCHES_MANIFEST}: it exists but could not be parsed ` +
148
+ `(${manifestState.parseError.message}). None of its metadata can be carried ` +
149
+ 'forward, so every entry would be rebuilt with an invented description, an ' +
150
+ 'mtime-based createdAt and an inferred category, and any stagedDependencies, ' +
151
+ 'lintIgnore or tier declarations would be lost — a patch body carries none of ' +
152
+ 'them.\n' +
153
+ `Fix the JSON in ${PATCHES_MANIFEST} (restore it from version control if you ` +
154
+ 'can) and re-run, or pass --allow-metadata-loss to accept the reinvention.');
155
+ }
103
156
  if (manifestState.manifest) {
104
157
  for (const entry of manifestState.manifest.patches) {
105
158
  existingEntries.set(entry.filename, entry);
@@ -113,13 +166,12 @@ async function rebuildPatchesManifestUnderLock(patchesDir, fallbackSourceEsrVers
113
166
  }, 0);
114
167
  let nextRecoveredOrder = highestFiniteOrder + 1;
115
168
  // Prefetch the two independent per-patch reads concurrently. This runs
116
- // while the patch-directory lock is held, and it was two serialised awaits
117
- // per patch across the whole queue — 58 round-trips on a 29-patch queue.
118
- // The fold below stays sequential because `nextRecoveredOrder` is
119
- // loop-carried: parallelising the assignment would scramble the recovered
120
- // ordinals.
169
+ // while the patch-directory lock is held, and as two serialised awaits per
170
+ // patch it costs 58 round-trips on a 29-patch queue. The fold below stays
171
+ // sequential because `nextRecoveredOrder` is loop-carried: parallelising
172
+ // the assignment would scramble the recovered ordinals.
121
173
  const prefetched = await Promise.all(patches.map(async (patch) => ({
122
- filesAffected: normalizeFiles(await getAllTargetFilesFromPatch(patch.path)),
174
+ filesAffected: normalizeAffectedFiles(await getAllTargetFilesFromPatch(patch.path)),
123
175
  patchStats: await stat(patch.path),
124
176
  })));
125
177
  for (const [index, patch] of patches.entries()) {
@@ -131,26 +183,27 @@ async function rebuildPatchesManifestUnderLock(patchesDir, fallbackSourceEsrVers
131
183
  const inferred = inferPatchMetadataFromFilename(patch.filename);
132
184
  const recoveredOrder = Number.isFinite(patch.order) ? patch.order : nextRecoveredOrder++;
133
185
  if (!existing) {
134
- // Track every filename that had no pre-existing manifest entry
135
- // so callers can warn the operator per-patch. A missing entry
136
- // means every descriptive field (`description`, `createdAt`,
137
- // `category`) was invented rather than preserved. FireForge
138
- // patch files carry no header metadata that could carry a
139
- // human description forward, so full fidelity is impossible —
140
- // visibility is the best we can offer. 2026-04-21 eval
141
- // (Finding #17) tripped over silent overwrites of useful
142
- // human-written descriptions during a recovery run.
186
+ // Track every filename that had no pre-existing manifest entry so
187
+ // callers can warn the operator per-patch. A missing entry means every
188
+ // descriptive field (`description`, `createdAt`, `category`) was
189
+ // invented rather than preserved, and FireForge patch files carry no
190
+ // header metadata that could carry a human description forward — so
191
+ // visibility is the best available, and silent overwrites of
192
+ // human-written descriptions during a recovery run are the failure to
193
+ // avoid.
143
194
  recoveredFilenames.push(patch.filename);
144
195
  }
145
- // Preserve optional fields the operator declared on the existing
146
- // entry — `lintIgnore` (per-patch lint suppression) and `tier`
147
- // (explicit branding-threshold override). Without this, a
148
- // `doctor --repair-patches-manifest` run silently strips both
149
- // fields from every entry that had them, and the next `lint`
150
- // or `re-export` pass fires rules the operator had intentionally
151
- // quieted. Mirrors how other descriptive fields fall back to
152
- // existing values when the entry is known.
196
+ // The rebuild OWNS exactly two fields — `filesAffected` (recomputed from
197
+ // the patch body) and `order` (from the filename ordinal). Everything
198
+ // else on an existing entry is carried forward by the spread, so a field
199
+ // a .patch cannot express — `stagedDependencies`, `lintIgnore`, `tier`,
200
+ // and whatever is added to PatchMetadata next — survives a repair by
201
+ // construction rather than by someone remembering to list it here. It was
202
+ // an enumerated literal until `stagedDependencies` was dropped from every
203
+ // entry that had one, which is the failure this shape prevents from
204
+ // recurring.
153
205
  const rebuilt = {
206
+ ...(existing ?? {}),
154
207
  filename: patch.filename,
155
208
  order: recoveredOrder,
156
209
  category: existing?.category ?? inferred.category,
@@ -160,24 +213,42 @@ async function rebuildPatchesManifestUnderLock(patchesDir, fallbackSourceEsrVers
160
213
  createdAt: existing?.createdAt ?? new Date(patchStats.mtimeMs).toISOString(),
161
214
  sourceEsrVersion: existing?.sourceEsrVersion ?? fallbackSourceEsrVersion,
162
215
  sourceVersion: existing?.sourceVersion ?? existing?.sourceEsrVersion ?? fallbackSourceEsrVersion,
163
- ...(existing?.sourceProduct !== undefined ? { sourceProduct: existing.sourceProduct } : {}),
164
216
  filesAffected,
165
217
  };
218
+ // Copy the arrays rather than aliasing the loaded manifest's, so a caller
219
+ // mutating the rebuilt entry cannot reach back into `manifestState`.
166
220
  if (existing?.lintIgnore !== undefined)
167
221
  rebuilt.lintIgnore = [...existing.lintIgnore];
168
- if (existing?.tier !== undefined)
169
- rebuilt.tier = existing.tier;
170
222
  rebuiltPatches.push(rebuilt);
223
+ existingEntries.delete(patch.filename);
171
224
  }
172
225
  rebuiltPatches.sort((left, right) => left.order - right.order || left.filename.localeCompare(right.filename));
173
226
  const rebuiltManifest = {
174
227
  version: 1,
175
228
  patches: rebuiltPatches,
176
229
  };
230
+ // Whatever is left in the map had a manifest row but no patch file, so the
231
+ // rebuild drops it. That is the correct outcome — the file is the source of
232
+ // truth — but dropping rows without naming them is indistinguishable from
233
+ // never having had them.
234
+ const droppedFilenames = [...existingEntries.keys()].sort((left, right) => left.localeCompare(right));
235
+ if (options.dryRun === true) {
236
+ return {
237
+ manifest: rebuiltManifest,
238
+ recoveredFilenames,
239
+ droppedFilenames,
240
+ written: false,
241
+ };
242
+ }
177
243
  await savePatchesManifest(patchesDir, rebuiltManifest);
178
- return { manifest: rebuiltManifest, recoveredFilenames };
244
+ return { manifest: rebuiltManifest, recoveredFilenames, droppedFilenames, written: true };
179
245
  }
180
- function normalizeFiles(files) {
246
+ /**
247
+ * Canonical form of a `filesAffected` list: deduplicated and sorted, so a
248
+ * comparison between a declared list and one extracted from a patch body is
249
+ * about membership rather than ordering.
250
+ */
251
+ export function normalizeAffectedFiles(files) {
181
252
  return Array.from(new Set(files)).sort((left, right) => left.localeCompare(right));
182
253
  }
183
254
  function sameStringArray(left, right) {
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Narrow repair for `filesAffected` — the one manifest field a `.patch` body
3
+ * can actually yield.
4
+ *
5
+ * A `files-affected-mismatch` is pure derived-value drift: the manifest's list
6
+ * disagrees with the diff it describes, and the diff is right by definition.
7
+ * Rebuilding the whole manifest to fix it is a much larger operation than the
8
+ * failure calls for, so this repair recomputes one field on the drifted rows
9
+ * and leaves every other row byte-identical.
10
+ */
11
+ import type { PatchDirectoryLockOptions } from './patch-lock.js';
12
+ /** One row whose `filesAffected` list disagreed with its patch body. */
13
+ export interface FilesAffectedRepair {
14
+ /** Patch filename. */
15
+ filename: string;
16
+ /** The list the manifest declared, normalized for comparison. */
17
+ before: string[];
18
+ /** The list the patch body actually targets. */
19
+ after: string[];
20
+ }
21
+ /** Summary of a {@link repairPatchesFilesAffected} run. */
22
+ export interface RepairFilesAffectedResult {
23
+ /** Rows that drifted and were (or would be) corrected. */
24
+ repairs: FilesAffectedRepair[];
25
+ /** Requested filenames with no manifest row, or no patch file on disk. */
26
+ skippedFilenames: string[];
27
+ /** False when the run was a dry run and patches.json was left untouched. */
28
+ written: boolean;
29
+ }
30
+ /** Options for {@link repairPatchesFilesAffected}. */
31
+ export interface RepairFilesAffectedOptions extends PatchDirectoryLockOptions {
32
+ /** Compute the corrections without writing them. */
33
+ dryRun?: boolean;
34
+ }
35
+ /**
36
+ * Recomputes `filesAffected` from the patch body for the named patches.
37
+ *
38
+ * @param patchesDir - Path to the patches directory
39
+ * @param filenames - Patch filenames to repair
40
+ * @param options - Lock options plus `dryRun`
41
+ * @returns The corrections applied (or projected) and the filenames skipped
42
+ */
43
+ export declare function repairPatchesFilesAffected(patchesDir: string, filenames: readonly string[], options?: RepairFilesAffectedOptions): Promise<RepairFilesAffectedResult>;
@@ -0,0 +1,77 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Narrow repair for `filesAffected` — the one manifest field a `.patch` body
4
+ * can actually yield.
5
+ *
6
+ * A `files-affected-mismatch` is pure derived-value drift: the manifest's list
7
+ * disagrees with the diff it describes, and the diff is right by definition.
8
+ * Rebuilding the whole manifest to fix it is a much larger operation than the
9
+ * failure calls for, so this repair recomputes one field on the drifted rows
10
+ * and leaves every other row byte-identical.
11
+ */
12
+ import { GeneralError } from '../errors/base.js';
13
+ import { discoverPatches, getAllTargetFilesFromPatch } from './patch-files.js';
14
+ import { withPatchDirectoryLock } from './patch-lock.js';
15
+ import { normalizeAffectedFiles } from './patch-manifest-consistency.js';
16
+ import { loadPatchesManifestForWrite, mutatePatchRowsInManifest, PATCHES_MANIFEST, } from './patch-manifest-io.js';
17
+ /**
18
+ * Recomputes `filesAffected` from the patch body for the named patches.
19
+ *
20
+ * @param patchesDir - Path to the patches directory
21
+ * @param filenames - Patch filenames to repair
22
+ * @param options - Lock options plus `dryRun`
23
+ * @returns The corrections applied (or projected) and the filenames skipped
24
+ */
25
+ export async function repairPatchesFilesAffected(patchesDir, filenames, options = {}) {
26
+ // Same lock contract as the full rebuild (invariant 2,
27
+ // docs/lifecycle-invariants.md): a manifest write serializes on the
28
+ // patch-directory lock, and a dry run reads under it so its projection
29
+ // cannot be computed against a queue being rewritten underneath.
30
+ return withPatchDirectoryLock(patchesDir, () => repairPatchesFilesAffectedUnderLock(patchesDir, filenames, options), options);
31
+ }
32
+ async function repairPatchesFilesAffectedUnderLock(patchesDir, filenames, options) {
33
+ // ForWrite: a corrupt manifest must abort here rather than be treated as
34
+ // empty. This repair exists precisely so that a one-field drift never
35
+ // reaches a whole-manifest rewrite, and silently writing over unparseable
36
+ // metadata would be a larger version of what it avoids.
37
+ const manifest = await loadPatchesManifestForWrite(patchesDir);
38
+ if (!manifest) {
39
+ throw new GeneralError(`Cannot repair filesAffected: ${PATCHES_MANIFEST} does not exist in ${patchesDir}.`);
40
+ }
41
+ const patchPathByFilename = new Map((await discoverPatches(patchesDir)).map((patch) => [patch.filename, patch.path]));
42
+ const rowByFilename = new Map(manifest.patches.map((entry) => [entry.filename, entry]));
43
+ const repairs = [];
44
+ const skippedFilenames = [];
45
+ for (const filename of new Set(filenames)) {
46
+ const patchPath = patchPathByFilename.get(filename);
47
+ const row = rowByFilename.get(filename);
48
+ if (patchPath === undefined || !row) {
49
+ // A missing patch file or a missing row is a different consistency
50
+ // issue with a different remedy; this repair reports it rather than
51
+ // inventing a row or deleting one.
52
+ skippedFilenames.push(filename);
53
+ continue;
54
+ }
55
+ const before = normalizeAffectedFiles(row.filesAffected);
56
+ const after = normalizeAffectedFiles(await getAllTargetFilesFromPatch(patchPath));
57
+ if (sameFiles(before, after))
58
+ continue;
59
+ repairs.push({ filename, before, after });
60
+ }
61
+ skippedFilenames.sort((left, right) => left.localeCompare(right));
62
+ if (repairs.length === 0 || options.dryRun === true) {
63
+ return { repairs, skippedFilenames, written: false };
64
+ }
65
+ const correctedByFilename = new Map(repairs.map((repair) => [repair.filename, repair.after]));
66
+ // Row-scoped mutation, so every untouched entry keeps its exact JSON shape —
67
+ // no re-sorted `filesAffected` lists and no reader-only fallback fields
68
+ // serialized into rows this repair has no business touching.
69
+ await mutatePatchRowsInManifest(patchesDir, [...correctedByFilename.keys()], (existing) => {
70
+ const corrected = correctedByFilename.get(existing.filename);
71
+ return corrected ? { set: { filesAffected: corrected } } : null;
72
+ });
73
+ return { repairs, skippedFilenames, written: true };
74
+ }
75
+ function sameFiles(left, right) {
76
+ return left.length === right.length && left.every((value, index) => value === right[index]);
77
+ }
@@ -12,7 +12,7 @@ import { rename } from 'node:fs/promises';
12
12
  import { join } from 'node:path';
13
13
  import { FireForgeError } from '../errors/base.js';
14
14
  import { ExitCode } from '../errors/codes.js';
15
- import { PatchManifestCorruptError } from '../errors/patch.js';
15
+ import { PatchError, PatchManifestCorruptError } from '../errors/patch.js';
16
16
  import { assert } from '../utils/assert.js';
17
17
  import { toError } from '../utils/errors.js';
18
18
  import { pathExistsStrict, readJson, removeFile, writeJson } from '../utils/fs.js';
@@ -315,7 +315,7 @@ export async function renumberPatchesInManifest(patchesDir, renameMap) {
315
315
  await assertPatchDirectoryLockHeld(patchesDir, 'renumbering the patch manifest');
316
316
  const manifest = await loadPatchesManifestForWrite(patchesDir);
317
317
  if (!manifest) {
318
- throw new Error('Cannot renumber patches: patches.json is missing.');
318
+ throw new PatchError('Cannot renumber patches: patches.json is missing.');
319
319
  }
320
320
  // Phase 1: rename each old filename to a unique temp staging name so the
321
321
  // later rename to the final target cannot collide with another entry
@@ -326,7 +326,7 @@ export async function renumberPatchesInManifest(patchesDir, renameMap) {
326
326
  for (const [oldFilename, entry] of renameMap) {
327
327
  const oldPath = join(patchesDir, oldFilename);
328
328
  if (!(await pathExistsStrict(oldPath))) {
329
- throw new Error(`Cannot renumber: patch file is missing on disk: ${oldFilename}`);
329
+ throw new PatchError(`Cannot renumber: patch file is missing on disk: ${oldFilename}`, oldFilename);
330
330
  }
331
331
  const stagedName = `.fireforge-renumber-${stagingId}-${oldFilename}`;
332
332
  const stagedPath = join(patchesDir, stagedName);
@@ -354,18 +354,16 @@ export async function renumberPatchesInManifest(patchesDir, renameMap) {
354
354
  const { staged, toEntry } = stagedEntry;
355
355
  const targetPath = join(patchesDir, toEntry.newFilename);
356
356
  if (await pathExistsStrict(targetPath)) {
357
- throw new Error(`Cannot renumber: target patch filename already exists on disk: ${toEntry.newFilename}`);
357
+ throw new PatchError(`Cannot renumber: target patch filename already exists on disk: ${toEntry.newFilename}`, toEntry.newFilename);
358
358
  }
359
359
  await rename(join(patchesDir, staged), targetPath);
360
- // Postcondition assert: confirm the target actually exists on
361
- // disk before we mark the rename complete. A silent rename
362
- // failure would leave the manifest and the filesystem
363
- // disagreeing — exactly what the eval 1 Finding #7 report
364
- // described: manifest rewrote to new filenames while the old
365
- // files stayed on disk. If the assert ever fires, the Phase 2
366
- // rollback will undo prior moves before re-throwing.
360
+ // Postcondition assert: confirm the target actually exists on disk
361
+ // before marking the rename complete. A silent rename failure leaves
362
+ // the manifest and the filesystem disagreeing — manifest rewritten to
363
+ // new filenames while the old files stay on disk. If the assert fires,
364
+ // the Phase 2 rollback undoes prior moves before re-throwing.
367
365
  if (!(await pathExistsStrict(targetPath))) {
368
- throw new Error(`Rename postcondition failed: expected ${toEntry.newFilename} to exist after rename, but it was not found on disk.`);
366
+ throw new PatchError(`Rename postcondition failed: expected ${toEntry.newFilename} to exist after rename, but it was not found on disk.`, toEntry.newFilename);
369
367
  }
370
368
  completedFinalRenames.push(stagedEntry);
371
369
  }
@@ -525,12 +523,10 @@ export async function removePatchFileAndManifest(patchesDir, filename) {
525
523
  await savePatchesManifest(patchesDir, originalManifest);
526
524
  }
527
525
  catch (rollbackError) {
528
- // Compound failure: both the delete and the rollback failed,
529
- // so the directory is in a known-inconsistent state. Throw a
530
- // dedicated error type that carries both causes so the
531
- // operator's log shows the complete picture instead of the
532
- // original delete error with a warning about the rollback
533
- // buried in stderr.
526
+ // Compound failure: both the delete and the rollback failed, so the
527
+ // directory is in a known-inconsistent state. A dedicated error type
528
+ // carrying both causes shows the complete picture instead of the
529
+ // original delete error with a rollback warning buried in stderr.
534
530
  throw new PatchDeleteRollbackError(filename, deleteError, toError(rollbackError));
535
531
  }
536
532
  }
@@ -7,18 +7,15 @@ import type { PatchMetadata } from '../types/commands/index.js';
7
7
  * matches `PatchMetadata.filename`.
8
8
  * 3. A filename without the `.patch` suffix — the command appends it
9
9
  * before matching (e.g. `002-ui-foo`).
10
- * 4. The manifest `name` field (e.g. `eval-furnace-token-override`) —
11
- * matches `PatchMetadata.name`. This is the short logical handle
12
- * the export workflow stamps onto the patch and the natural
13
- * identifier an operator keeps in their notes. 2026-04-21 eval
14
- * (Finding #6): `patch reorder`/`delete` rejected the `name`
15
- * even though the CLI help said `<name>`, forcing the operator
16
- * to copy the full filename from `patches.json` before every
17
- * queue mutation.
10
+ * 4. The manifest `name` field (e.g. `furnace-token-override`) — the short
11
+ * logical handle the export workflow stamps onto the patch and the
12
+ * natural identifier an operator keeps in their notes. The CLI help
13
+ * says `<name>`, so rejecting it forces the operator to copy the full
14
+ * filename out of `patches.json` before every queue mutation.
18
15
  *
19
- * Resolution order is strict: numeric ordinals first, then filename
20
- * lookup (with + without `.patch` suffix), then name-field lookup.
21
- * The filename lookup beats the name lookup when the two happen to
22
- * collide so legacy scripts that pass filenames keep working.
16
+ * Resolution order is strict: numeric ordinals first, then filename lookup
17
+ * (with and without the `.patch` suffix), then name-field lookup. The
18
+ * filename lookup beats the name lookup when the two collide, so scripts
19
+ * that pass filenames keep working.
23
20
  */
24
21
  export declare function resolvePatchIdentifier(identifier: string, patches: PatchMetadata[]): PatchMetadata | null;
@@ -6,19 +6,16 @@
6
6
  * matches `PatchMetadata.filename`.
7
7
  * 3. A filename without the `.patch` suffix — the command appends it
8
8
  * before matching (e.g. `002-ui-foo`).
9
- * 4. The manifest `name` field (e.g. `eval-furnace-token-override`) —
10
- * matches `PatchMetadata.name`. This is the short logical handle
11
- * the export workflow stamps onto the patch and the natural
12
- * identifier an operator keeps in their notes. 2026-04-21 eval
13
- * (Finding #6): `patch reorder`/`delete` rejected the `name`
14
- * even though the CLI help said `<name>`, forcing the operator
15
- * to copy the full filename from `patches.json` before every
16
- * queue mutation.
9
+ * 4. The manifest `name` field (e.g. `furnace-token-override`) — the short
10
+ * logical handle the export workflow stamps onto the patch and the
11
+ * natural identifier an operator keeps in their notes. The CLI help
12
+ * says `<name>`, so rejecting it forces the operator to copy the full
13
+ * filename out of `patches.json` before every queue mutation.
17
14
  *
18
- * Resolution order is strict: numeric ordinals first, then filename
19
- * lookup (with + without `.patch` suffix), then name-field lookup.
20
- * The filename lookup beats the name lookup when the two happen to
21
- * collide so legacy scripts that pass filenames keep working.
15
+ * Resolution order is strict: numeric ordinals first, then filename lookup
16
+ * (with and without the `.patch` suffix), then name-field lookup. The
17
+ * filename lookup beats the name lookup when the two collide, so scripts
18
+ * that pass filenames keep working.
22
19
  */
23
20
  export function resolvePatchIdentifier(identifier, patches) {
24
21
  if (/^\d+$/.test(identifier)) {
@@ -2,7 +2,12 @@
2
2
  * Schema validation for patches.json manifest data.
3
3
  */
4
4
  import type { PatchCategory, PatchesManifest } from '../types/commands/index.js';
5
- /** Validates raw patches.json data and returns the typed manifest shape. */
5
+ /**
6
+ * Validates raw patches.json data and returns the typed manifest shape.
7
+ *
8
+ * @param data - Parsed patches.json content
9
+ * @returns The validated manifest
10
+ */
6
11
  export declare function validatePatchesManifest(data: unknown): PatchesManifest;
7
12
  /**
8
13
  * Infers patch metadata from a filename pattern.
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { parseObject } from '../utils/parse.js';
6
6
  import { isArray, isObject, isValidFirefoxProduct, isValidFirefoxVersion, PATCH_CATEGORIES, } from '../utils/validation.js';
7
+ import { checkDocumentVersion, describeNewerDocument } from './state-file.js';
7
8
  function parseForwardImports(data, label) {
8
9
  if (!isArray(data)) {
9
10
  throw new Error(`${label} must be an array`);
@@ -91,16 +92,13 @@ function validatePatchMetadata(data, index) {
91
92
  throw new Error(`patches[${index}].sourceEsrVersion must be a valid Firefox version string`);
92
93
  }
93
94
  const filesAffected = rec.stringArray('filesAffected');
94
- // Optional fields. These were silently stripped before the 0.17.0
95
- // branding-tier work reached in and audited the loader — the 0.16.0
96
- // `lintIgnore` escape hatch demonstrably round-tripped only through
97
- // test fixtures that mocked `loadPatchesManifest` directly. Real
98
- // operator edits to `patches.json` were dropped on every subsequent
99
- // load, so any patch that relied on `lintIgnore` to suppress a
100
- // specific lint rule was quietly re-tripped the next time the
101
- // manifest validated. Preserve both the pre-existing `lintIgnore`
102
- // and the new `tier` field here so future-added optional fields
103
- // have a ready template to follow.
95
+ // Optional fields. These are easy to strip silently — an escape hatch
96
+ // that round-trips only through test fixtures mocking
97
+ // `loadPatchesManifest` looks fine while real operator edits to
98
+ // `patches.json` are dropped on every load, so a patch relying on
99
+ // `lintIgnore` to suppress a rule is quietly re-tripped the next time the
100
+ // manifest validates. Preserve both `lintIgnore` and `tier` here; future
101
+ // optional fields follow the same template.
104
102
  const lintIgnore = rec.optionalStringArray('lintIgnore');
105
103
  const rawTier = rec.raw('tier');
106
104
  let tier;
@@ -133,13 +131,27 @@ function validatePatchMetadata(data, index) {
133
131
  }
134
132
  return result;
135
133
  }
136
- /** Validates raw patches.json data and returns the typed manifest shape. */
134
+ /** Manifest schema version this build writes and understands. */
135
+ const PATCHES_MANIFEST_VERSION = 1;
136
+ /**
137
+ * Validates raw patches.json data and returns the typed manifest shape.
138
+ *
139
+ * @param data - Parsed patches.json content
140
+ * @returns The validated manifest
141
+ */
137
142
  export function validatePatchesManifest(data) {
138
143
  if (!isObject(data)) {
139
144
  throw new Error('patches.json must be a JSON object');
140
145
  }
141
- if (data['version'] !== 1) {
142
- throw new Error('patches.json version must be 1');
146
+ // A manifest from a NEWER FireForge is not corrupt. The bare "version must
147
+ // be 1" this replaced sent the operator looking for damage in a file that
148
+ // only needed a newer build to read it.
149
+ const version = checkDocumentVersion(data, 'version', PATCHES_MANIFEST_VERSION);
150
+ if (version.kind === 'newer') {
151
+ throw new Error(describeNewerDocument('patches manifest', version.found, PATCHES_MANIFEST_VERSION));
152
+ }
153
+ if (version.kind !== 'current') {
154
+ throw new Error(`patches.json version must be ${PATCHES_MANIFEST_VERSION}`);
143
155
  }
144
156
  if (!isArray(data['patches'])) {
145
157
  throw new Error('patches.json field "patches" must be an array');
@@ -4,7 +4,8 @@
4
4
  * Callers should continue to import from this module; the internal split
5
5
  * is an implementation detail.
6
6
  */
7
- export { rebuildPatchesManifest, validatePatchesManifestConsistency, } from './patch-manifest-consistency.js';
7
+ export { type PatchManifestConsistencyIssue, rebuildPatchesManifest, recommendManifestRepair, validatePatchesManifestConsistency, } from './patch-manifest-consistency.js';
8
+ export { type FilesAffectedRepair, repairPatchesFilesAffected, } from './patch-manifest-files-affected.js';
8
9
  export { addPatchToManifest, loadPatchesManifest, loadPatchesManifestForWrite, mutatePatchRowsInManifest, PATCHES_MANIFEST, type PatchRenameEntry, removePatchFileAndManifest, renumberPatchesInManifest, rewriteStagedDependencyOwners, savePatchesManifest, } from './patch-manifest-io.js';
9
10
  export { checkVersionCompatibility, findPatchesAffectingFile, getClaimedFiles, stampPatchVersions, validatePatchIntegrity, } from './patch-manifest-query.js';
10
11
  export { resolvePatchIdentifier } from './patch-manifest-resolve.js';
@@ -5,7 +5,8 @@
5
5
  * Callers should continue to import from this module; the internal split
6
6
  * is an implementation detail.
7
7
  */
8
- export { rebuildPatchesManifest, validatePatchesManifestConsistency, } from './patch-manifest-consistency.js';
8
+ export { rebuildPatchesManifest, recommendManifestRepair, validatePatchesManifestConsistency, } from './patch-manifest-consistency.js';
9
+ export { repairPatchesFilesAffected, } from './patch-manifest-files-affected.js';
9
10
  export { addPatchToManifest, loadPatchesManifest, loadPatchesManifestForWrite, mutatePatchRowsInManifest, PATCHES_MANIFEST, removePatchFileAndManifest, renumberPatchesInManifest, rewriteStagedDependencyOwners, savePatchesManifest, } from './patch-manifest-io.js';
10
11
  export { checkVersionCompatibility, findPatchesAffectingFile, getClaimedFiles, stampPatchVersions, validatePatchIntegrity, } from './patch-manifest-query.js';
11
12
  export { resolvePatchIdentifier } from './patch-manifest-resolve.js';