@hominis/fireforge 0.51.0 → 0.53.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 (254) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/build-info.json +4 -4
  3. package/dist/src/commands/bootstrap.js +2 -3
  4. package/dist/src/commands/build-prelock.d.ts +3 -3
  5. package/dist/src/commands/build-prelock.js +3 -3
  6. package/dist/src/commands/build.js +4 -0
  7. package/dist/src/commands/declarations-report.d.ts +9 -3
  8. package/dist/src/commands/declarations-report.js +18 -11
  9. package/dist/src/commands/discard.js +3 -3
  10. package/dist/src/commands/doctor-furnace-config-sync.js +4 -4
  11. package/dist/src/commands/doctor-furnace.js +5 -5
  12. package/dist/src/commands/export-adopt-check.d.ts +5 -6
  13. package/dist/src/commands/export-adopt-check.js +5 -6
  14. package/dist/src/commands/export-flow.js +6 -9
  15. package/dist/src/commands/export-staged-registration.d.ts +4 -4
  16. package/dist/src/commands/export-staged-registration.js +4 -4
  17. package/dist/src/commands/forward-import-remedy.d.ts +2 -3
  18. package/dist/src/commands/forward-import-remedy.js +2 -3
  19. package/dist/src/commands/furnace/create-dry-run.d.ts +2 -2
  20. package/dist/src/commands/furnace/deploy-fragment-includers.d.ts +2 -2
  21. package/dist/src/commands/furnace/deploy-fragment-includers.js +2 -2
  22. package/dist/src/commands/furnace/index.js +2 -2
  23. package/dist/src/commands/lint.js +4 -4
  24. package/dist/src/commands/patch/compact.js +1 -1
  25. package/dist/src/commands/patch/lint-ignore-from.d.ts +3 -4
  26. package/dist/src/commands/patch/lint-ignore-from.js +3 -4
  27. package/dist/src/commands/patch/move-files-adopt.d.ts +33 -0
  28. package/dist/src/commands/patch/move-files-adopt.js +65 -0
  29. package/dist/src/commands/patch/move-files-create.js +41 -20
  30. package/dist/src/commands/patch/move-files-into.d.ts +18 -0
  31. package/dist/src/commands/patch/move-files-into.js +125 -60
  32. package/dist/src/commands/patch/move-files-retire.d.ts +39 -0
  33. package/dist/src/commands/patch/move-files-retire.js +91 -0
  34. package/dist/src/commands/patch/move-files.d.ts +3 -0
  35. package/dist/src/commands/patch/move-files.js +42 -12
  36. package/dist/src/commands/patch/move-size-tier.d.ts +27 -0
  37. package/dist/src/commands/patch/move-size-tier.js +66 -0
  38. package/dist/src/commands/patch/rename.js +1 -1
  39. package/dist/src/commands/patch/reorder-projection.d.ts +17 -0
  40. package/dist/src/commands/patch/reorder-projection.js +49 -0
  41. package/dist/src/commands/patch/reorder-shift.d.ts +30 -0
  42. package/dist/src/commands/patch/reorder-shift.js +307 -0
  43. package/dist/src/commands/patch/reorder.d.ts +9 -15
  44. package/dist/src/commands/patch/reorder.js +52 -50
  45. package/dist/src/commands/patch/split-plan.d.ts +31 -3
  46. package/dist/src/commands/patch/split-plan.js +36 -12
  47. package/dist/src/commands/patch/split-staged.d.ts +2 -2
  48. package/dist/src/commands/patch/split-staged.js +2 -2
  49. package/dist/src/commands/patch/split.js +18 -5
  50. package/dist/src/commands/patch/staged-dependency-entries.d.ts +9 -1
  51. package/dist/src/commands/patch/staged-dependency-entries.js +32 -4
  52. package/dist/src/commands/patch/staged-dependency-from.js +15 -9
  53. package/dist/src/commands/patch/staged-dependency.js +13 -9
  54. package/dist/src/commands/patch/tier.js +2 -2
  55. package/dist/src/commands/patch-rename-map.d.ts +14 -3
  56. package/dist/src/commands/patch-rename-map.js +21 -3
  57. package/dist/src/commands/re-export-adjacent.d.ts +4 -5
  58. package/dist/src/commands/re-export-lint.d.ts +4 -3
  59. package/dist/src/commands/re-export-lint.js +4 -3
  60. package/dist/src/commands/re-export-preflight.js +1 -2
  61. package/dist/src/commands/re-export-purity-scope.d.ts +1 -1
  62. package/dist/src/commands/re-export-purity-scope.js +13 -7
  63. package/dist/src/commands/re-export-retired-declarations.d.ts +22 -0
  64. package/dist/src/commands/re-export-retired-declarations.js +113 -0
  65. package/dist/src/commands/re-export-single.js +2 -2
  66. package/dist/src/commands/re-export-stale-registrations.d.ts +27 -6
  67. package/dist/src/commands/re-export-stale-registrations.js +65 -21
  68. package/dist/src/commands/re-export.js +4 -0
  69. package/dist/src/commands/rebase/continue.js +3 -3
  70. package/dist/src/commands/register.js +5 -4
  71. package/dist/src/commands/status-output.js +5 -1
  72. package/dist/src/commands/status.js +1 -1
  73. package/dist/src/commands/test-build-furnace.d.ts +1 -2
  74. package/dist/src/commands/test-build-furnace.js +1 -2
  75. package/dist/src/commands/test-build-phase.d.ts +10 -0
  76. package/dist/src/commands/test-build-phase.js +51 -16
  77. package/dist/src/commands/test-control-restore.d.ts +40 -0
  78. package/dist/src/commands/test-control-restore.js +86 -0
  79. package/dist/src/commands/test-dispatch.js +83 -13
  80. package/dist/src/commands/test-multi-verdict.js +1 -0
  81. package/dist/src/commands/test-orchestrate.d.ts +11 -0
  82. package/dist/src/commands/test-orchestrate.js +33 -2
  83. package/dist/src/commands/test-perf-scope.d.ts +1 -1
  84. package/dist/src/commands/test-perf-scope.js +1 -1
  85. package/dist/src/commands/test-prelock.d.ts +7 -8
  86. package/dist/src/commands/test-prelock.js +15 -12
  87. package/dist/src/commands/test-register.js +2 -2
  88. package/dist/src/commands/test-results.d.ts +10 -1
  89. package/dist/src/commands/test-results.js +20 -3
  90. package/dist/src/commands/test-run.d.ts +6 -0
  91. package/dist/src/commands/test-run.js +23 -4
  92. package/dist/src/commands/test-shards.d.ts +7 -3
  93. package/dist/src/commands/test-shards.js +24 -7
  94. package/dist/src/commands/test-stale-gate.d.ts +6 -1
  95. package/dist/src/commands/test-stale-gate.js +47 -2
  96. package/dist/src/commands/test-verdict.d.ts +5 -3
  97. package/dist/src/commands/test.js +14 -1
  98. package/dist/src/commands/token.js +2 -2
  99. package/dist/src/commands/verify.js +1 -1
  100. package/dist/src/commands/watch.js +1 -1
  101. package/dist/src/commands/wire.js +1 -1
  102. package/dist/src/core/build-audit-resolve.d.ts +4 -5
  103. package/dist/src/core/build-audit-resolve.js +4 -5
  104. package/dist/src/core/build-audit-skip.d.ts +2 -2
  105. package/dist/src/core/build-audit.d.ts +3 -4
  106. package/dist/src/core/build-baseline-types.d.ts +37 -3
  107. package/dist/src/core/build-baseline-validate.d.ts +30 -1
  108. package/dist/src/core/build-baseline-validate.js +134 -33
  109. package/dist/src/core/build-baseline.d.ts +8 -2
  110. package/dist/src/core/build-baseline.js +32 -16
  111. package/dist/src/core/build-jar-escalation.d.ts +4 -4
  112. package/dist/src/core/build-jar-escalation.js +4 -4
  113. package/dist/src/core/build-prepare.js +4 -4
  114. package/dist/src/core/config-validate.js +4 -4
  115. package/dist/src/core/coverage-extend.d.ts +5 -3
  116. package/dist/src/core/coverage-extend.js +8 -3
  117. package/dist/src/core/coverage-records.d.ts +4 -5
  118. package/dist/src/core/coverage-records.js +4 -5
  119. package/dist/src/core/destructive.d.ts +2 -2
  120. package/dist/src/core/destructive.js +2 -2
  121. package/dist/src/core/discard-baseline.d.ts +2 -2
  122. package/dist/src/core/discard-baseline.js +3 -3
  123. package/dist/src/core/engine-session-lock.d.ts +1 -1
  124. package/dist/src/core/engine-session-lock.js +1 -1
  125. package/dist/src/core/file-lock-owner.d.ts +5 -5
  126. package/dist/src/core/file-lock-owner.js +5 -5
  127. package/dist/src/core/file-lock-queue.js +6 -8
  128. package/dist/src/core/file-lock-shared.d.ts +4 -4
  129. package/dist/src/core/file-lock-shared.js +4 -4
  130. package/dist/src/core/file-lock.js +7 -10
  131. package/dist/src/core/firefox.js +2 -2
  132. package/dist/src/core/furnace-apply-overwrite-warn.d.ts +1 -1
  133. package/dist/src/core/furnace-apply-overwrite-warn.js +1 -1
  134. package/dist/src/core/furnace-apply.js +3 -3
  135. package/dist/src/core/furnace-config-validate-exceptions.d.ts +4 -3
  136. package/dist/src/core/furnace-config-validate-exceptions.js +26 -6
  137. package/dist/src/core/furnace-operation.js +5 -5
  138. package/dist/src/core/furnace-registration-jar-inplace.js +5 -6
  139. package/dist/src/core/furnace-registration-rename.d.ts +5 -5
  140. package/dist/src/core/furnace-registration-rename.js +5 -5
  141. package/dist/src/core/furnace-registration-validate.d.ts +2 -1
  142. package/dist/src/core/furnace-registration-validate.js +2 -1
  143. package/dist/src/core/furnace-registration.js +2 -2
  144. package/dist/src/core/furnace-validate-accessibility.d.ts +1 -1
  145. package/dist/src/core/furnace-validate-accessibility.js +22 -11
  146. package/dist/src/core/furnace-validate-exceptions.d.ts +2 -1
  147. package/dist/src/core/furnace-validate-exceptions.js +31 -13
  148. package/dist/src/core/git-base.js +4 -3
  149. package/dist/src/core/git-diff.js +1 -1
  150. package/dist/src/core/git.js +1 -1
  151. package/dist/src/core/harness-orphans.d.ts +6 -6
  152. package/dist/src/core/harness-orphans.js +6 -6
  153. package/dist/src/core/harness-port-reservation.d.ts +60 -0
  154. package/dist/src/core/harness-port-reservation.js +122 -0
  155. package/dist/src/core/history-log-file.d.ts +9 -9
  156. package/dist/src/core/history-log-file.js +15 -15
  157. package/dist/src/core/history-log.d.ts +3 -3
  158. package/dist/src/core/history-log.js +3 -3
  159. package/dist/src/core/launchable-build.d.ts +39 -0
  160. package/dist/src/core/launchable-build.js +85 -0
  161. package/dist/src/core/marionette-port.js +1 -3
  162. package/dist/src/core/moz-manifest-rules.js +3 -2
  163. package/dist/src/core/negative-control-compiled.d.ts +22 -3
  164. package/dist/src/core/negative-control-compiled.js +41 -5
  165. package/dist/src/core/parser-fallback.d.ts +5 -5
  166. package/dist/src/core/parser-fallback.js +5 -5
  167. package/dist/src/core/patch-identifier-suggest.d.ts +6 -0
  168. package/dist/src/core/patch-identifier-suggest.js +11 -4
  169. package/dist/src/core/patch-lint-checkjs.d.ts +1 -1
  170. package/dist/src/core/patch-lint-checkjs.js +1 -1
  171. package/dist/src/core/patch-lint-css.js +2 -2
  172. package/dist/src/core/patch-lint-file-size.d.ts +5 -7
  173. package/dist/src/core/patch-lint-file-size.js +5 -7
  174. package/dist/src/core/patch-lint-ignore.d.ts +3 -3
  175. package/dist/src/core/patch-lint-ignore.js +3 -3
  176. package/dist/src/core/patch-lint-prettier.d.ts +4 -6
  177. package/dist/src/core/patch-lint-prettier.js +4 -6
  178. package/dist/src/core/patch-lint-projection.d.ts +1 -1
  179. package/dist/src/core/patch-lint-projection.js +1 -1
  180. package/dist/src/core/patch-lint-queue-types.d.ts +2 -2
  181. package/dist/src/core/patch-lint-queue-types.js +2 -2
  182. package/dist/src/core/patch-manifest-consistency.d.ts +2 -2
  183. package/dist/src/core/patch-manifest-consistency.js +2 -2
  184. package/dist/src/core/patch-manifest-io.d.ts +5 -1
  185. package/dist/src/core/patch-manifest-io.js +12 -10
  186. package/dist/src/core/patch-manifest-key-order.d.ts +3 -4
  187. package/dist/src/core/patch-manifest-key-order.js +3 -4
  188. package/dist/src/core/patch-parse.d.ts +3 -3
  189. package/dist/src/core/patch-parse.js +3 -3
  190. package/dist/src/core/patch-policy.d.ts +1 -1
  191. package/dist/src/core/patch-policy.js +1 -1
  192. package/dist/src/core/patch-source-metadata.d.ts +1 -1
  193. package/dist/src/core/patch-source-metadata.js +1 -1
  194. package/dist/src/core/patch-subset.d.ts +5 -3
  195. package/dist/src/core/patch-subset.js +8 -7
  196. package/dist/src/core/patch-transform.d.ts +2 -2
  197. package/dist/src/core/patch-transform.js +2 -2
  198. package/dist/src/core/rebase-session.js +4 -6
  199. package/dist/src/core/register-xpcshell-test.d.ts +28 -7
  200. package/dist/src/core/register-xpcshell-test.js +76 -27
  201. package/dist/src/core/run-log.d.ts +8 -8
  202. package/dist/src/core/run-log.js +8 -8
  203. package/dist/src/core/status-classify.d.ts +1 -2
  204. package/dist/src/core/status-classify.js +1 -2
  205. package/dist/src/core/test-harness-crash.d.ts +5 -3
  206. package/dist/src/core/test-harness-crash.js +13 -14
  207. package/dist/src/core/test-harness-infra.d.ts +32 -0
  208. package/dist/src/core/test-harness-infra.js +69 -0
  209. package/dist/src/core/test-harness-signature.d.ts +6 -0
  210. package/dist/src/core/test-run-completeness.d.ts +12 -5
  211. package/dist/src/core/test-run-completeness.js +15 -8
  212. package/dist/src/core/test-stale-check.d.ts +48 -1
  213. package/dist/src/core/test-stale-check.js +97 -2
  214. package/dist/src/core/test-stall-triage.js +5 -7
  215. package/dist/src/core/token-category.js +2 -2
  216. package/dist/src/core/token-inventory.js +3 -3
  217. package/dist/src/core/token-manager.d.ts +2 -2
  218. package/dist/src/core/token-manager.js +5 -7
  219. package/dist/src/core/token-variant.d.ts +6 -6
  220. package/dist/src/core/token-variant.js +7 -7
  221. package/dist/src/core/typecheck-shim.d.ts +2 -1
  222. package/dist/src/core/typecheck-shim.js +29 -5
  223. package/dist/src/core/wire-destroy.d.ts +1 -1
  224. package/dist/src/core/wire-destroy.js +1 -1
  225. package/dist/src/core/wire-dom-fragment.js +1 -1
  226. package/dist/src/core/wire-init.d.ts +1 -1
  227. package/dist/src/core/wire-init.js +1 -1
  228. package/dist/src/core/wire-subscript.d.ts +1 -1
  229. package/dist/src/core/wire-subscript.js +1 -1
  230. package/dist/src/core/wire-utils.d.ts +2 -2
  231. package/dist/src/core/wire-utils.js +3 -3
  232. package/dist/src/errors/base.d.ts +1 -1
  233. package/dist/src/errors/base.js +1 -1
  234. package/dist/src/errors/rebase.d.ts +1 -1
  235. package/dist/src/errors/rebase.js +1 -1
  236. package/dist/src/types/commands/options.d.ts +26 -3
  237. package/dist/src/types/config.d.ts +5 -7
  238. package/dist/src/types/furnace.d.ts +9 -0
  239. package/dist/src/utils/options.js +2 -2
  240. package/dist/src/utils/process.js +2 -2
  241. package/dist/src/utils/regex.d.ts +2 -2
  242. package/dist/src/utils/regex.js +3 -3
  243. package/dist/src/utils/sleep.d.ts +5 -6
  244. package/dist/src/utils/sleep.js +5 -6
  245. package/dist/src/utils/validation.js +2 -2
  246. package/docs/configuration.md +40 -26
  247. package/docs/exit-codes.md +8 -11
  248. package/docs/furnace.md +21 -3
  249. package/docs/lifecycle-invariants.md +36 -45
  250. package/docs/machine-output.md +43 -22
  251. package/docs/patch-workflow.md +129 -24
  252. package/docs/run-logs.md +8 -9
  253. package/docs/testing.md +118 -44
  254. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,51 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.53.0
4
+
5
+ ### Test harness
6
+
7
+ - `test --jobs` gives each harness instance its own `MOZ_UPLOAD_DIR` (`<MOZ_UPLOAD_DIR>/job-<n>`, created up front) when the caller sets one, so instances no longer overwrite each other's fixed-name artifacts such as the `modifiedPrefs.json` pref census. The verdict names the directories as `upload-dirs=`.
8
+ - `test --jobs` holds the mochitest HTTP, ssltunnel and WebSocket ports (8888, 4443, 9988) for the whole parallel run, so each instance's harness falls back to server ports of its own instead of racing for 8888. Each `FIREFORGE-SHARD:` line names the ports its instance took as `ports=http:N,ssl:N,ws:N`.
9
+ - A harness whose mochitest server never starts (`could not start server on port`, `Timed out while waiting for server startup`) is an infrastructure failure, not `no-tests`: it is retried within `--harness-retries` and reports `reason=server-startup` once the budget is spent. A `--results` record for a harness-level result (`automation.py`) carries `shardFiles`, plus `file` when its shard has one file.
10
+ - `test` refuses with `note=stale-compiled` when C++, Rust, IDL, WebIDL, IPDL or YAML sources changed after the binary under test was linked (the newer of the binary and its `libxul`). `--allow-stale-build` turns the refusal into a warning, and `test --build` escalates to a full `mach build`. Every run that reaches dispatch stamps `build-id=<BuildID>` from `application.ini` on its verdict.
11
+ - A negative control on a header (`.h`, `.hh`, `.hpp`, `.ipdlh`) counts as compiled even when an `EXPORTS` manifest installs it, so it is applied before a full pre-test build instead of reporting `control=missed`.
12
+ - After a compiled negative control is restored, `test` rebuilds the binary under the same lock hold and adds `control-restore-build=done|failed` to the verdict. A binary that still carries a control (the run was killed, or the restore build failed) is refused with `note=control-built` until `fireforge build`, and `test --build` escalates to a full build.
13
+ - A scoped `test --build <dir>/` no longer leaves a baseline the next plain `test` refuses: coverage paths are recorded normalized, and non-ASCII dirty paths are fingerprinted unquoted. An invalid baseline's refusal names the failing field and its value, and offers `--build` when the field belongs to the coverage claim.
14
+
15
+ ### Patch queue
16
+
17
+ - Every `patches.json` write puts every row and the manifest's own keys in the one canonical order, not only the rows it changed. A re-export no longer leaves an older writer's rows (`sourceProduct` after `filesAffected`) in the working copy while `patch stage` stages them canonical, so commits stop flipping key order on hundreds of rows. The `re-export --dry-run` purity guard hashes rows in canonical order.
18
+ - `patch reorder --shift <n> --from <order> [--through <order>]` renumbers a run of patches up by `n` in one transaction, to open room for inserts. Retired and reserved slots inside the run move with it, a shift onto an occupied order past the run is refused, and under `patchPolicy.allowGaps: false` the opened orders are recorded as reservations, which the inserts that follow fill. `--map-out <file>` on `reorder` and `split` writes the old-to-new filename map as JSON.
19
+ - `patch move-files --scan-file <path>` adopts new, unowned engine files into the destination in the same transaction, and both sides are linted as the transaction leaves them. `--retire-empty` lets a move empty the source: the declarations it owns re-point to the destination and its slot is retired as by `patch retire`. Without the flag, the refusal names it.
20
+ - The projected lint of `patch move-files`, `patch split` and a placed `export` uses the queue's whole lint configuration. With `forwardRegistration: "extended"`, a shared test manifest's suite entries (`["browser_*.js"]`) are projected and written to the `--declarations-out` report instead of surfacing only after the move.
21
+ - A refused `move-files --create` or `split` report names the patch the rerun creates as `owner`. The declarations `move-files` and `split` write, and every report entry, carry a `reason` naming the move, so the printed `--add --from` call needs no `--reason`.
22
+ - A per-patch size refusal from `move-files` or `split` names a tier change behind it, for example "the source becomes test tier (it was general tier): 9634 lines against 6000".
23
+ - `patch staged-dependency --add`, single or `--from`, on a declaration already present with a different `reason` replaces the reason in place, keeping the entry's position. The summary reports updated reasons apart from additions.
24
+ - `re-export --expect-removed` drops every staged registration and forward import, in any patch, whose `creates` path it retires, names each one (`would drop` on a dry run) and records `re-export-staged-prune`. Before, the stale declaration waited for the next `lint --per-patch`.
25
+ - A re-export keeps a staged registration whose `creates` file exists in `engine/` or is an `--expect-new` path of the run, and says so ("kept: file exists but no patch owns it yet"), instead of pruning the declaration the next scan needs.
26
+ - `fireforge register` inserts an xpcshell section ahead of the `#` comments that introduce the next entry, never between a comment and its entry. `--after <entry>` now works for `xpcshell.toml` and places the section after that entry's whole block.
27
+ - `--patches` on `lint --per-patch`, `status`, `verify` and `check` splits a list on whitespace as well as commas, so a list in one zsh word works. An unmatched name is refused with its nearest matches and the queue size instead of every patch in the queue.
28
+
29
+ ## 0.52.0
30
+
31
+ ### Test harness
32
+
33
+ - `test --jobs` gives each harness instance its own mochitest test list (`--testrun-manifest-file`). On the shared default `tests.json` the second instance ran the first shard's file and the verdict passed, or it found no file at all. A `--jobs` run now always reads each dispatch's mozlog raw log, and a dispatch whose raw log starts none of its requested files fails with `started=0/<m>` instead of passing on another shard's results.
34
+ - A negative control on compiled source escalates its pre-test build to a full `mach build`, with a notice that names the control's files. `mach build faster` compiles nothing, so under `--build` such a control still tested the previous binary and reported `control=missed`.
35
+ - A `coverage-replaced` refusal names who wrote the coverage record: its invocation, when, and the writing process's pid (marked `still running` while it is), `FIREFORGE_SESSION_ID` and invocation id. Every build baseline now records that `recorder`, and `status --test-coverage` shows it.
36
+
37
+ ### Furnace
38
+
39
+ - A `validationExceptions` entry can name a `file` of the component, as one can name a `tag`. `hardcoded-text` judges each module on its own and lists the modules in `files`, so a vendored bundle whose bytes are pinned can be accepted while the authored modules beside it keep the check. A `furnace-ignore: hardcoded-text` comment now covers only the file it is in.
40
+
41
+ ### Typecheck
42
+
43
+ - `typecheck.litTypes` declares the global type `MozLitElementWith<Extra>`. A widget casts its `MozLitElement` base to it to type query getters and members declared only in `static properties`. Without `litTypes`, and under per-patch checkJs, the type is `any`, so the same source checks in both.
44
+
45
+ ### Other
46
+
47
+ - CI: `github/codeql-action` 4.39.0.
48
+
3
49
  ## 0.51.0
4
50
 
5
51
  ### Test harness
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "version": "0.51.0",
4
- "commit": "bd93338b037832fdcd69259a70a3a4b134b98906",
5
- "shortCommit": "bd93338b0378",
3
+ "version": "0.53.0",
4
+ "commit": "2f2367bfe9c2b8a834cbf09f9b19b02d8dbd6b66",
5
+ "shortCommit": "2f2367bfe9c2",
6
6
  "dirty": false,
7
7
  "dirtyHash": null,
8
- "buildTime": "2026-10-19T12:33:48.474Z"
8
+ "buildTime": "2026-10-22T20:59:15.472Z"
9
9
  }
@@ -79,9 +79,8 @@ export async function bootstrapCommand(projectRoot) {
79
79
  if (hasErrors) {
80
80
  outro('Build dependencies installed with errors');
81
81
  // Propagate the failure (mirroring doctor's exit-code handling)
82
- // instead of exiting 0. CI gating on bootstrap used to proceed to a
83
- // build that could not succeed because error-severity check results
84
- // were discarded here.
82
+ // instead of exiting 0, so CI gating on bootstrap does not proceed to
83
+ // a build that cannot succeed.
85
84
  return checksExitCode;
86
85
  }
87
86
  outro('Build dependencies installed with warnings');
@@ -3,9 +3,9 @@
3
3
  * session lock, not while holding it.
4
4
  *
5
5
  * A build queued behind a running objdir browser (a demo batch, an
6
- * end-to-end harness) used to wait inside `withEngineSessionLock`, so every
7
- * peer's build, test and re-export queued behind a run that was doing
8
- * nothing. The wait now happens first, and the in-lock objdir preflight
6
+ * end-to-end harness) that waited inside `withEngineSessionLock` would queue
7
+ * every peer's build, test and re-export behind a run that was doing
8
+ * nothing. The wait happens first, and the in-lock objdir preflight
9
9
  * re-checks once (the spent budget is stripped from the options it gets).
10
10
  */
11
11
  import type { BuildOptions } from '../types/commands/index.js';
@@ -4,9 +4,9 @@
4
4
  * session lock, not while holding it.
5
5
  *
6
6
  * A build queued behind a running objdir browser (a demo batch, an
7
- * end-to-end harness) used to wait inside `withEngineSessionLock`, so every
8
- * peer's build, test and re-export queued behind a run that was doing
9
- * nothing. The wait now happens first, and the in-lock objdir preflight
7
+ * end-to-end harness) that waited inside `withEngineSessionLock` would queue
8
+ * every peer's build, test and re-export behind a run that was doing
9
+ * nothing. The wait happens first, and the in-lock objdir preflight
10
10
  * re-checks once (the spent budget is stripped from the options it gets).
11
11
  */
12
12
  import { join } from 'node:path';
@@ -392,6 +392,10 @@ async function recordFullBuildBaseline(projectRoot, engineDir, binaryName, ui, p
392
392
  recordedBy: ui ? 'fireforge build --ui' : 'fireforge build',
393
393
  staticComponentsHandling: 'auto',
394
394
  buildKind: ui ? 'faster' : 'full',
395
+ // A full build recompiles a negative control's restored source, so
396
+ // it drops the control-built stamp; `--ui` compiles nothing and
397
+ // keeps it.
398
+ controlBuild: ui ? previousBaseline?.controlBuild : undefined,
395
399
  });
396
400
  }
397
401
  catch (baselineError) {
@@ -10,11 +10,14 @@ export interface DeclarationsReport {
10
10
  commands: string[];
11
11
  }
12
12
  /**
13
- * A declaration as the report records it. `owner` is dropped when it names
14
- * a patch the refused change would have created, which does not exist yet.
13
+ * A declaration as the report records it. `owner` is optional for renamers
14
+ * that cannot name one; `reason` is the rationale `staged-dependency --add
15
+ * --from` records with the declaration (an entry's own reason wins over the
16
+ * call's `--reason`).
15
17
  */
16
18
  export type ReportedDeclaration = Omit<PatchLintStagedDeclaration, 'owner'> & {
17
19
  owner?: string;
20
+ reason?: string;
18
21
  };
19
22
  /** Rewrites a projected declaration into the names the current queue uses. */
20
23
  export type DeclarationRenamer = (declaration: PatchLintStagedDeclaration) => ReportedDeclaration;
@@ -26,8 +29,10 @@ export type DeclarationRenamer = (declaration: PatchLintStagedDeclaration) => Re
26
29
  * @param issues - The refusal's findings
27
30
  * @param out - `--declarations-out`, relative to the working directory
28
31
  * @param rename - Maps projected patch names back to current ones
32
+ * @param reason - Rationale stamped on every declaration that carries none
33
+ * (`patch move-files` and `patch split` name the move that needs it)
29
34
  */
30
- export declare function writeDeclarationsReport(projectRoot: string, issues: readonly PatchLintIssue[], out?: string, rename?: DeclarationRenamer): Promise<DeclarationsReport | undefined>;
35
+ export declare function writeDeclarationsReport(projectRoot: string, issues: readonly PatchLintIssue[], out?: string, rename?: DeclarationRenamer, reason?: string): Promise<DeclarationsReport | undefined>;
31
36
  /** The refusal lines that point at a written report. */
32
37
  export declare function formatDeclarationsReport(report: DeclarationsReport): string[];
33
38
  /**
@@ -40,4 +45,5 @@ export declare function withDeclarationsReport(conflicts: ConflictReport | null,
40
45
  regressions: readonly PatchLintIssue[];
41
46
  out?: string | undefined;
42
47
  rename?: DeclarationRenamer;
48
+ reason?: string;
43
49
  }): Promise<ConflictReport | null>;
@@ -2,12 +2,11 @@
2
2
  /**
3
3
  * One `--from`-ready declaration report for a refused queue change.
4
4
  *
5
- * Moving a test out of a patch that owns a shared manifest refused with one
6
- * `forward-registration` error per manifest line, and each line needed its
7
- * own `patch staged-dependency --add` call: a slice that added seven suites
8
- * wrote eighteen declarations by hand. `patch move-files`, `patch split`
9
- * and `re-export --scan` now write every declaration their refusal proposes
10
- * into one lint-report-shaped JSON (`queueIssues[].stagedDeclarations`) and
5
+ * Moving a test out of a patch that owns a shared manifest refuses with one
6
+ * `forward-registration` error per manifest line, and each line would need
7
+ * its own `patch staged-dependency --add` call. `patch move-files`,
8
+ * `patch split` and `re-export --scan` write every declaration their refusal
9
+ * proposes into one lint-report-shaped JSON (`queueIssues[].stagedDeclarations`) and
11
10
  * name the single `patch staged-dependency <holder> --add --from <file>`
12
11
  * call per holding patch that discharges them.
13
12
  */
@@ -26,15 +25,20 @@ const DECLARABLE_CHECKS = new Set(['forward-import', 'forward-registration']);
26
25
  * @param issues - The refusal's findings
27
26
  * @param out - `--declarations-out`, relative to the working directory
28
27
  * @param rename - Maps projected patch names back to current ones
28
+ * @param reason - Rationale stamped on every declaration that carries none
29
+ * (`patch move-files` and `patch split` name the move that needs it)
29
30
  */
30
- export async function writeDeclarationsReport(projectRoot, issues, out, rename = (declaration) => declaration) {
31
+ export async function writeDeclarationsReport(projectRoot, issues, out, rename = (declaration) => declaration, reason) {
32
+ const withReason = (declaration) => reason === undefined || declaration.reason !== undefined
33
+ ? declaration
34
+ : { ...declaration, reason };
31
35
  const queueIssues = issues
32
36
  .filter((issue) => DECLARABLE_CHECKS.has(issue.check))
33
37
  .map((issue) => ({
34
38
  check: issue.check,
35
39
  file: issue.file,
36
40
  message: issue.message,
37
- stagedDeclarations: (issue.stagedDeclarations ?? []).map(rename),
41
+ stagedDeclarations: (issue.stagedDeclarations ?? []).map((declaration) => withReason(rename(declaration))),
38
42
  }))
39
43
  .filter((issue) => issue.stagedDeclarations.length > 0);
40
44
  const declarations = queueIssues.flatMap((issue) => issue.stagedDeclarations);
@@ -46,11 +50,14 @@ export async function writeDeclarationsReport(projectRoot, issues, out, rename =
46
50
  await ensureDir(resolve(path, '..'));
47
51
  await writeJson(path, { queueIssues });
48
52
  const holders = [...new Set(declarations.map((declaration) => declaration.patch))].sort();
53
+ // A report whose every entry carries its reason needs no `--reason`.
54
+ const reasonFlag = declarations.every((declaration) => declaration.reason !== undefined)
55
+ ? ''
56
+ : ` ${STAGED_DEPENDENCY_REASON_PLACEHOLDER}`;
49
57
  return {
50
58
  path,
51
59
  count: declarations.length,
52
- commands: holders.map((holder) => `fireforge patch staged-dependency ${holder} --add --from ${path} ` +
53
- STAGED_DEPENDENCY_REASON_PLACEHOLDER),
60
+ commands: holders.map((holder) => `fireforge patch staged-dependency ${holder} --add --from ${path}${reasonFlag}`),
54
61
  };
55
62
  }
56
63
  /** The refusal lines that point at a written report. */
@@ -69,7 +76,7 @@ export function formatDeclarationsReport(report) {
69
76
  export async function withDeclarationsReport(conflicts, args) {
70
77
  if (conflicts === null)
71
78
  return null;
72
- const report = await writeDeclarationsReport(args.projectRoot, args.regressions, args.out, args.rename);
79
+ const report = await writeDeclarationsReport(args.projectRoot, args.regressions, args.out, args.rename, args.reason);
73
80
  if (report === undefined)
74
81
  return conflicts;
75
82
  return { ...conflicts, details: [...conflicts.details, ...formatDeclarationsReport(report)] };
@@ -33,7 +33,7 @@ async function confirmDiscard(message, hint, options) {
33
33
  /**
34
34
  * Plans the restore baseline for `entries`: the patch-applied baseline for
35
35
  * patch-claimed paths, or pristine HEAD when `--to-upstream`
36
- * explicitly requests the legacy semantics.
36
+ * explicitly requests it.
37
37
  */
38
38
  async function planDiscards(patchesDir, engineDir, entries, options) {
39
39
  if (options.toUpstream)
@@ -160,8 +160,8 @@ export async function discardCommand(projectRoot, file, options = {}) {
160
160
  }
161
161
  if (options.dryRun) {
162
162
  // Show the rename pair regardless of which side the operator passed.
163
- // Passing the new path of a rename used to hide that discarding also
164
- // resurrects the old path, exactly when the full picture matters most.
163
+ // Discarding the new path of a rename also resurrects the old path, so
164
+ // the preview names both, exactly when the full picture matters most.
165
165
  info(`Would discard changes to: ${dryRunLabel(plan)}`);
166
166
  outro('Dry run complete — no changes made');
167
167
  return;
@@ -138,10 +138,10 @@ function emptyOutcome() {
138
138
  * under `custom` would leave it in both maps, a state nothing validates.
139
139
  * Every orphan is therefore re-checked against `freshConfig` inside the
140
140
  * lock and skipped when it is no longer orphaned.
141
- * 2. Delete custom directories outside the lock. `repairCustomOrphans` used to
142
- * run after the lock was released, so a `furnace create` that had
143
- * registered a name and not yet populated its directory had that directory
144
- * deleted underneath it.
141
+ * 2. Delete custom directories outside the lock. `repairCustomOrphans` runs
142
+ * after the lock is released, so it must not delete the directory of a
143
+ * name a concurrent `furnace create` has registered but not yet
144
+ * populated.
145
145
  */
146
146
  async function repairOrphans(projectRoot, orphans) {
147
147
  const outcome = emptyOutcome();
@@ -443,8 +443,8 @@ async function readFurnaceLockPid(lockPath) {
443
443
  *
444
444
  * This is the recovery path when the signal-handler sweep
445
445
  * (`forceReleaseFurnaceLocksForActiveOperations` in `bin/fireforge.ts`)
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
446
+ * misses: a SIGKILL'd process that never ran the handler, or a lock
447
+ * directory without a PID file. SIGINT'ing `furnace
448
448
  * preview` leaves the lock behind, and the next `fireforge test --build`
449
449
  * then times out waiting for it. `doctor --repair-furnace` clears it so the
450
450
  * next command runs immediately.
@@ -464,9 +464,9 @@ const furnaceStaleLockCheck = {
464
464
  const ageSuffix = ageMs !== undefined ? ` (age: ${Math.round(ageMs / 1000)}s)` : '';
465
465
  // Two signals mark a lock as stale:
466
466
  // 1. PID file says owner is dead → unambiguous, remove immediately.
467
- // 2. PID file absent and lock is older than 60s → older FireForge
468
- // releases (or an externally-created lock directory) fall into this
469
- // bucket. The age gate avoids false positives on a lock just
467
+ // 2. PID file absent and lock is older than 60s → a lock directory
468
+ // written without a PID file (for example created externally)
469
+ // falls into this bucket. The age gate avoids false positives on a lock just
470
470
  // acquired by a concurrent process that has not written its PID yet.
471
471
  //
472
472
  // `isProcessAlive` treats EPERM as alive. Returning false for EPERM
@@ -4,12 +4,11 @@
4
4
  * adopt-then-split for a real moved-code block, but only after checking
5
5
  * that the adopt itself would pass.
6
6
  *
7
- * The advice used to be offered on the moved-code heuristic alone. On one
8
- * queue it told the operator to adopt six new modules into an existing
9
- * patch when the only error was the missing registration; the adopt then
10
- * refused one module for importing later patches, and without it breached
11
- * the source patch's hard size limit. Split out of export-flow.ts for the
12
- * line budget.
7
+ * The moved-code heuristic alone is not enough: it can advise adopting
8
+ * several new modules into an existing patch when the only error is the
9
+ * missing registration, and the adopt then refuses a module for importing
10
+ * later patches, or without it breaches the source patch's hard size limit.
11
+ * Split out of export-flow.ts for the line budget.
13
12
  */
14
13
  import { type PatchQueueContext } from '../core/patch-lint.js';
15
14
  import type { PatchLintIssue } from '../types/commands/index.js';
@@ -5,12 +5,11 @@
5
5
  * adopt-then-split for a real moved-code block, but only after checking
6
6
  * that the adopt itself would pass.
7
7
  *
8
- * The advice used to be offered on the moved-code heuristic alone. On one
9
- * queue it told the operator to adopt six new modules into an existing
10
- * patch when the only error was the missing registration; the adopt then
11
- * refused one module for importing later patches, and without it breached
12
- * the source patch's hard size limit. Split out of export-flow.ts for the
13
- * line budget.
8
+ * The moved-code heuristic alone is not enough: it can advise adopting
9
+ * several new modules into an existing patch when the only error is the
10
+ * missing registration, and the adopt then refuses a module for importing
11
+ * later patches, or without it breaches the source patch's hard size limit.
12
+ * Split out of export-flow.ts for the line budget.
14
13
  */
15
14
  import { detectMovedCodeOverlaps, formatMovedCodeAdvice } from '../core/export-moved-code-shape.js';
16
15
  import { countNonBinaryDiffLines, getPatchSizeThresholds, lintPatchQueue, resolvePatchSizeTier, } from '../core/patch-lint.js';
@@ -180,7 +180,10 @@ export async function projectPlacementForLint(patchesDir, plan, diff, config) {
180
180
  ...pendingBody,
181
181
  });
182
182
  projectedEntries.sort((a, b) => a.order - b.order || a.filename.localeCompare(b.filename));
183
- const policyRest = baseCtx.patchPolicy ? { patchPolicy: baseCtx.patchPolicy } : {};
183
+ // The projection keeps every queue-wide lint setting of the baseline
184
+ // (patch policy, forward-registration scope, token scales), so a
185
+ // test-manifest suite entry is checked exactly as the committed gate
186
+ // checks it.
184
187
  // A new module whose moz.build line is declared (staged registration)
185
188
  // and present in the working tree, but not yet exported by its owner, is
186
189
  // accepted with a "re-export the owner next" note instead of refused.
@@ -189,10 +192,7 @@ export async function projectPlacementForLint(patchesDir, plan, diff, config) {
189
192
  baseCtx,
190
193
  newFilename: plan.newFilename,
191
194
  pendingNewFiles: new Set(pendingBody.newFiles.keys()),
192
- projectedIssues: lintPatchQueue({
193
- entries: projectedEntries,
194
- ...policyRest,
195
- }).filter((i) => i.severity === 'error'),
195
+ projectedIssues: lintPatchQueue({ ...baseCtx, entries: projectedEntries }).filter((i) => i.severity === 'error'),
196
196
  });
197
197
  for (const note of registration.accepted)
198
198
  warn(note);
@@ -203,10 +203,7 @@ export async function projectPlacementForLint(patchesDir, plan, diff, config) {
203
203
  // pre-existing breakage rather than a consequence of this placement, and
204
204
  // calling it one would misattribute. Entries are already in memory,
205
205
  // so this second pass costs no additional IO.
206
- const baselineIssues = lintPatchQueue({
207
- entries: baseCtx.entries,
208
- ...policyRest,
209
- }).filter((i) => i.severity === 'error');
206
+ const baselineIssues = lintPatchQueue(baseCtx).filter((i) => i.severity === 'error');
210
207
  // Register-first and new-file + moved-code advice: name the working
211
208
  // sequence instead of leaving the operator to derive it from two
212
209
  // individually-correct guards that together dead-end, but only offer
@@ -2,10 +2,10 @@
2
2
  * Lets `export --order` land a new system module whose moz.build line is
3
3
  * still unexported working-tree drift in the patch that registers it.
4
4
  *
5
- * The two refusals used to deadlock: the module's export failed
6
- * `unregistered-system-module` because no patch body carried its moz.build
7
- * line yet, and the registering patch could not be re-exported first
8
- * because its line named a file no patch created. The way through is
5
+ * Without this the two refusals deadlock: the module's export fails
6
+ * `unregistered-system-module` because no patch body carries its moz.build
7
+ * line yet, and the registering patch cannot be re-exported first because
8
+ * its line names a file no patch creates. The way through is
9
9
  * declared intent plus evidence: when the registering patch carries a
10
10
  * staged registration for the module and the working tree's moz.build has
11
11
  * exactly that line, the export is accepted and the operator is told to
@@ -3,10 +3,10 @@
3
3
  * Lets `export --order` land a new system module whose moz.build line is
4
4
  * still unexported working-tree drift in the patch that registers it.
5
5
  *
6
- * The two refusals used to deadlock: the module's export failed
7
- * `unregistered-system-module` because no patch body carried its moz.build
8
- * line yet, and the registering patch could not be re-exported first
9
- * because its line named a file no patch created. The way through is
6
+ * Without this the two refusals deadlock: the module's export fails
7
+ * `unregistered-system-module` because no patch body carries its moz.build
8
+ * line yet, and the registering patch cannot be re-exported first because
9
+ * its line names a file no patch creates. The way through is
10
10
  * declared intent plus evidence: when the registering patch carries a
11
11
  * staged registration for the module and the working tree's moz.build has
12
12
  * exactly that line, the export is accepted and the operator is told to
@@ -4,9 +4,8 @@
4
4
  * can take as their own later patch, and the exact `patch staged-dependency
5
5
  * --add` commands that declare the edges instead.
6
6
  *
7
- * The refusal used to end with "export them with --order <n> or run
8
- * patch staged-dependency --add", which left the operator to work out both
9
- * the number and every flag of the declaration from the finding text.
7
+ * Both the order number and every flag of the declaration are spelled out,
8
+ * so the operator does not have to work them out from the finding text.
10
9
  */
11
10
  import type { PatchesManifest, PatchLintIssue } from '../types/commands/index.js';
12
11
  import type { FireForgeConfig } from '../types/config.js';
@@ -5,9 +5,8 @@
5
5
  * can take as their own later patch, and the exact `patch staged-dependency
6
6
  * --add` commands that declare the edges instead.
7
7
  *
8
- * The refusal used to end with "export them with --order <n> or run
9
- * patch staged-dependency --add", which left the operator to work out both
10
- * the number and every flag of the declaration from the finding text.
8
+ * Both the order number and every flag of the declaration are spelled out,
9
+ * so the operator does not have to work them out from the finding text.
11
10
  */
12
11
  import { STAGED_DEPENDENCY_REASON_PLACEHOLDER } from '../core/patch-lint-staged-registration.js';
13
12
  import { earliestLegalOrderAfter } from '../core/patch-order-legal.js';
@@ -19,8 +19,8 @@ export interface DryRunPlanInput {
19
19
  * Resolved `--test-dir` override (engine-relative, already validated by
20
20
  * `resolveValidatedTestDir`). Omit for the default `<binaryName>`-derived
21
21
  * scaffold directory. The formatters must receive the same value the
22
- * scaffolders get. The plan used to recompute the default from
23
- * `binaryName` and named a directory the real run never wrote to.
22
+ * scaffolders get; recomputing the default from `binaryName` would name a
23
+ * directory the real run never writes to.
24
24
  */
25
25
  testDir?: string;
26
26
  }
@@ -4,8 +4,8 @@
4
4
  * When `<tag>` includes a shared CSS fragment that changed, every other
5
5
  * deployed includer of that fragment still carries the old expansion after
6
6
  * the targeted deploy. Deploy-all refreshes all of them as a side effect of
7
- * deploying everything; the targeted form used to refresh exactly the named
8
- * component and say nothing about the rest. This module refreshes the other
7
+ * deploying everything; the targeted form refreshes only the named
8
+ * component. This module refreshes the other
9
9
  * stale includers through the same pipeline and reports them, so a stale
10
10
  * expansion is never left behind silently.
11
11
  */
@@ -5,8 +5,8 @@
5
5
  * When `<tag>` includes a shared CSS fragment that changed, every other
6
6
  * deployed includer of that fragment still carries the old expansion after
7
7
  * the targeted deploy. Deploy-all refreshes all of them as a side effect of
8
- * deploying everything; the targeted form used to refresh exactly the named
9
- * component and say nothing about the rest. This module refreshes the other
8
+ * deploying everything; the targeted form refreshes only the named
9
+ * component. This module refreshes the other
10
10
  * stale includers through the same pipeline and reports them, so a stale
11
11
  * expansion is never left behind silently.
12
12
  */
@@ -24,8 +24,8 @@ import { parseMaxWarnings } from './validate-report.js';
24
24
  * Splits the raw `--wait-lock` value off a furnace leaf's parsed options and
25
25
  * resolves it into `waitLockSeconds` (flag, else `FIREFORGE_WAIT_LOCK`, else
26
26
  * undefined) for the furnace lock `runFurnaceMutation` takes. Used by the
27
- * leaves that take only Furnace's lock, where the flag used to be accepted
28
- * and ignored.
27
+ * leaves that take only Furnace's lock, so the flag is honoured there rather
28
+ * than accepted and ignored.
29
29
  */
30
30
  function withFurnaceLockBudget(options) {
31
31
  const { waitLock, ...rest } = options;
@@ -377,7 +377,7 @@ async function reportLintOutcome(engineDir, issues, options) {
377
377
  // with warnings" when only notices fired contradicts the preceding
378
378
  // `0 warning(s)` summary line and reads as a regression. Errors suppressed
379
379
  // by --only-introduced still warrant the "with warnings" outro: they print
380
- // as ERROR rows but no longer fail the run, which is the same contract the
380
+ // as ERROR rows but do not fail the run, which is the same contract the
381
381
  // operator gets from a real warning.
382
382
  const suppressedErrors = options.onlyIntroduced && errors.length > 0;
383
383
  if (warnings.length > 0 || suppressedErrors) {
@@ -480,7 +480,7 @@ export function registerLint(program, { getProjectRoot, withErrorHandling }) {
480
480
  .option('--since <git-rev>', 'Tag issues as [introduced] or [cumulative] based on whether the file changed since <git-rev> (e.g. HEAD, a branch, a SHA)')
481
481
  .option('--only-introduced', 'Fail only on issues tagged [introduced] (requires --since). Cumulative errors still print but do not set a non-zero exit.')
482
482
  .option('--per-patch', "Lint each patch in the queue as its own isolated diff. Rescopes patch-size rules so they fire against individual patches rather than the aggregate. Honours each patch's `lintIgnore` entries.")
483
- .option('--patches <names...>', 'With --per-patch, lint only the named patches. Accepts repeated flags, comma lists, full filenames/stems, bare order numbers, manifest names, category-prefixed slugs, or bare slugs. Positional arguments after --per-patch are treated the same way.')
483
+ .option('--patches <names...>', 'With --per-patch, lint only the named patches. Accepts repeated flags, comma or whitespace lists, full filenames/stems, bare order numbers, manifest names, category-prefixed slugs, or bare slugs. Positional arguments after --per-patch are treated the same way.')
484
484
  .option('--max-warnings <n>', 'Fail when lint reports more than <n> warning(s); use 0 for warning-clean release gates.')
485
485
  .option('--no-cache', 'Bypass per-patch lint result cache reads and writes.')
486
486
  .option('--report <path>', 'With --per-patch, write a machine-readable JSON report (per-patch size metrics, tier, thresholds, issues, and lintIgnore-suppressed issues) to <path>.')
@@ -527,8 +527,8 @@ export function registerLint(program, { getProjectRoot, withErrorHandling }) {
527
527
  .description('Manage the per-patch lint result cache')
528
528
  // Commander routes `fireforge lint cache` here even when the operator
529
529
  // meant to lint an engine directory literally named `cache`. A bare
530
- // `lint cache` (no subcommand) used to silently do nothing, so make
531
- // the ambiguity explicit instead of doing the wrong operation quietly.
530
+ // `lint cache` (no subcommand) would otherwise silently do nothing, so
531
+ // make the ambiguity explicit instead of doing the wrong operation quietly.
532
532
  .action(withErrorHandling(() => {
533
533
  info('Nothing to do: "lint cache" manages the lint result cache (try "lint cache clear"). ' +
534
534
  'To lint a directory named cache/, disambiguate with a trailing slash: "fireforge lint cache/".');
@@ -22,7 +22,7 @@ import { addWaitLockOption, pickDefined, resolveWaitLockSeconds } from '../../ut
22
22
  import { proceedAfterDecision } from '../destructive-decision.js';
23
23
  import { renameMapsEqual } from '../patch-rename-map.js';
24
24
  import { requirePatchQueue } from './patch-context.js';
25
- import { rebuildFilenameForOrder } from './reorder.js';
25
+ import { rebuildFilenameForOrder } from './reorder-projection.js';
26
26
  /** True when `order` falls inside a configured reserved range. */
27
27
  function isReservedOrder(policyCfg, order) {
28
28
  return (policyCfg.reservedRanges ?? []).some((r) => order >= r.from && order <= r.to);
@@ -2,10 +2,9 @@
2
2
  * Batch and per-entry input for `patch lint-ignore --add`. Split out of
3
3
  * `lint-ignore.ts` for the per-file line budget.
4
4
  *
5
- * A rebaseline that sets a ceiling per rule used to take one call per rule,
6
- * each taking the patch lock and rewriting `patches.json` (138 calls on one
7
- * consumer's queue), because `--max` applied to every `--add` of a call.
8
- * Two forms lift that:
5
+ * `--max` applies to every `--add` of a call, so a rebaseline that sets a
6
+ * ceiling per rule would take one call per rule, each taking the patch lock
7
+ * and rewriting `patches.json`. Two forms avoid that:
9
8
  *
10
9
  * - `--add <rule>=<max>` gives each entry its own ceiling in one call.
11
10
  * - `--from <json-file>` reads every entry from a file, under one lock hold
@@ -3,10 +3,9 @@
3
3
  * Batch and per-entry input for `patch lint-ignore --add`. Split out of
4
4
  * `lint-ignore.ts` for the per-file line budget.
5
5
  *
6
- * A rebaseline that sets a ceiling per rule used to take one call per rule,
7
- * each taking the patch lock and rewriting `patches.json` (138 calls on one
8
- * consumer's queue), because `--max` applied to every `--add` of a call.
9
- * Two forms lift that:
6
+ * `--max` applies to every `--add` of a call, so a rebaseline that sets a
7
+ * ceiling per rule would take one call per rule, each taking the patch lock
8
+ * and rewriting `patches.json`. Two forms avoid that:
10
9
  *
11
10
  * - `--add <rule>=<max>` gives each entry its own ceiling in one call.
12
11
  * - `--from <json-file>` reads every entry from a file, under one lock hold
@@ -0,0 +1,33 @@
1
+ /**
2
+ * `patch move-files --scan-file <path>`: adopt new engine files into the
3
+ * move's destination in the same transaction.
4
+ *
5
+ * Splitting a widget across patches moved existing files and adopted the
6
+ * seam modules that appeared beside them. Adopting first (`re-export
7
+ * --scan --scan-file`) grew the source past its own `large-patch-lines`
8
+ * ceiling, which refused although the move that followed would have
9
+ * shrunk it again. Adopting straight into the destination of the move
10
+ * means no patch ever carries the intermediate body: both sides are linted
11
+ * on what the transaction leaves behind.
12
+ */
13
+ import type { PatchMetadata } from '../../types/commands/index.js';
14
+ /**
15
+ * Validates `--scan-file` paths: each must be engine-relative, exist in
16
+ * `engine/`, be owned by no patch and not be one of the moved files.
17
+ * Returns the sorted, de-duplicated list.
18
+ *
19
+ * @param rawFiles - The `--scan-file` values
20
+ * @param patches - Manifest rows
21
+ * @param enginePath - Engine directory
22
+ * @param movedFiles - The `--file` paths of the same move
23
+ */
24
+ export declare function resolveAdoptedFiles(rawFiles: readonly string[] | undefined, patches: readonly PatchMetadata[], enginePath: string, movedFiles: readonly string[]): Promise<string[]>;
25
+ /**
26
+ * Re-checks under the patch-directory lock that no patch claimed an
27
+ * adopted file while the move waited for confirmation.
28
+ *
29
+ * @param patches - Manifest rows read under the lock
30
+ * @param adoptedFiles - Files the move adopts
31
+ * @param command - Command name for the error
32
+ */
33
+ export declare function assertAdoptedStillUnowned(patches: readonly PatchMetadata[], adoptedFiles: readonly string[] | undefined, command: string): void;