@hominis/fireforge 0.50.0 → 0.52.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 (212) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/dist/build-info.json +4 -4
  3. package/dist/src/commands/check-report.d.ts +17 -4
  4. package/dist/src/commands/check-report.js +19 -6
  5. package/dist/src/commands/check-run.d.ts +5 -0
  6. package/dist/src/commands/check-run.js +9 -1
  7. package/dist/src/commands/check-steps.d.ts +8 -0
  8. package/dist/src/commands/check-steps.js +31 -6
  9. package/dist/src/commands/check.js +1 -1
  10. package/dist/src/commands/declarations-report.d.ts +43 -0
  11. package/dist/src/commands/declarations-report.js +76 -0
  12. package/dist/src/commands/export-placement-gate.js +8 -0
  13. package/dist/src/commands/furnace/rename-registrations.js +7 -2
  14. package/dist/src/commands/furnace/validate.js +26 -1
  15. package/dist/src/commands/lint-per-patch.js +6 -2
  16. package/dist/src/commands/lint-per-run-checkjs.js +2 -2
  17. package/dist/src/commands/lint-report.js +7 -2
  18. package/dist/src/commands/manifest.js +2 -0
  19. package/dist/src/commands/park.d.ts +22 -0
  20. package/dist/src/commands/park.js +232 -0
  21. package/dist/src/commands/patch/delete.js +11 -6
  22. package/dist/src/commands/patch/index.d.ts +3 -3
  23. package/dist/src/commands/patch/index.js +8 -4
  24. package/dist/src/commands/patch/lint-ignore-from.d.ts +44 -0
  25. package/dist/src/commands/patch/lint-ignore-from.js +142 -0
  26. package/dist/src/commands/patch/lint-ignore.js +39 -29
  27. package/dist/src/commands/patch/move-files-create.js +11 -4
  28. package/dist/src/commands/patch/move-files-into.js +10 -4
  29. package/dist/src/commands/patch/move-files.js +3 -1
  30. package/dist/src/commands/patch/removal-created-files.d.ts +48 -0
  31. package/dist/src/commands/patch/removal-created-files.js +83 -0
  32. package/dist/src/commands/patch/rename.js +15 -3
  33. package/dist/src/commands/patch/reserve.d.ts +33 -0
  34. package/dist/src/commands/patch/reserve.js +152 -0
  35. package/dist/src/commands/patch/retire.js +11 -6
  36. package/dist/src/commands/patch/split-plan.d.ts +10 -1
  37. package/dist/src/commands/patch/split-plan.js +16 -1
  38. package/dist/src/commands/patch/split.js +14 -5
  39. package/dist/src/commands/patch/stage.d.ts +34 -0
  40. package/dist/src/commands/patch/stage.js +153 -0
  41. package/dist/src/commands/patch/staged-dependency-remove.d.ts +31 -0
  42. package/dist/src/commands/patch/staged-dependency-remove.js +80 -0
  43. package/dist/src/commands/patch/staged-dependency.js +40 -98
  44. package/dist/src/commands/re-export-adjacent.d.ts +5 -0
  45. package/dist/src/commands/re-export-adjacent.js +4 -3
  46. package/dist/src/commands/re-export-bulk-scan.d.ts +2 -1
  47. package/dist/src/commands/re-export-bulk-scan.js +22 -7
  48. package/dist/src/commands/re-export-hunks.d.ts +35 -0
  49. package/dist/src/commands/re-export-hunks.js +220 -0
  50. package/dist/src/commands/re-export-options.js +29 -2
  51. package/dist/src/commands/re-export-purity-scope.d.ts +18 -0
  52. package/dist/src/commands/re-export-purity-scope.js +131 -0
  53. package/dist/src/commands/re-export-register.js +15 -1
  54. package/dist/src/commands/re-export-removed.d.ts +1 -1
  55. package/dist/src/commands/re-export-removed.js +17 -2
  56. package/dist/src/commands/re-export-scan.d.ts +2 -0
  57. package/dist/src/commands/re-export-scan.js +4 -1
  58. package/dist/src/commands/re-export-single.js +102 -42
  59. package/dist/src/commands/re-export-stale-registrations.d.ts +18 -0
  60. package/dist/src/commands/re-export-stale-registrations.js +62 -0
  61. package/dist/src/commands/re-export.js +35 -34
  62. package/dist/src/commands/status-output.js +5 -1
  63. package/dist/src/commands/test-build-furnace.d.ts +3 -2
  64. package/dist/src/commands/test-build-furnace.js +8 -2
  65. package/dist/src/commands/test-build-phase.d.ts +2 -0
  66. package/dist/src/commands/test-build-phase.js +34 -17
  67. package/dist/src/commands/test-checkjs.js +3 -2
  68. package/dist/src/commands/test-dispatch.d.ts +39 -0
  69. package/dist/src/commands/test-dispatch.js +220 -0
  70. package/dist/src/commands/test-furnace-reach.d.ts +37 -0
  71. package/dist/src/commands/test-furnace-reach.js +132 -0
  72. package/dist/src/commands/test-orchestrate.d.ts +14 -0
  73. package/dist/src/commands/test-orchestrate.js +25 -4
  74. package/dist/src/commands/test-perf-scope.d.ts +21 -0
  75. package/dist/src/commands/test-perf-scope.js +55 -0
  76. package/dist/src/commands/test-prelock.js +100 -4
  77. package/dist/src/commands/test-register.js +18 -3
  78. package/dist/src/commands/test-results.d.ts +62 -0
  79. package/dist/src/commands/test-results.js +62 -0
  80. package/dist/src/commands/test-run.d.ts +13 -55
  81. package/dist/src/commands/test-run.js +73 -101
  82. package/dist/src/commands/test-shards.d.ts +110 -0
  83. package/dist/src/commands/test-shards.js +255 -0
  84. package/dist/src/commands/test-stale-gate.d.ts +5 -1
  85. package/dist/src/commands/test-stale-gate.js +11 -2
  86. package/dist/src/commands/test-verdict.d.ts +2 -1
  87. package/dist/src/commands/test.d.ts +1 -1
  88. package/dist/src/commands/test.js +68 -75
  89. package/dist/src/commands/token-lint.js +48 -8
  90. package/dist/src/commands/verify.js +9 -2
  91. package/dist/src/core/build-baseline-types.d.ts +13 -0
  92. package/dist/src/core/build-baseline-validate.js +9 -0
  93. package/dist/src/core/build-baseline.js +7 -0
  94. package/dist/src/core/build-prepare.d.ts +6 -0
  95. package/dist/src/core/build-prepare.js +29 -3
  96. package/dist/src/core/config-paths.d.ts +1 -1
  97. package/dist/src/core/config-paths.js +2 -0
  98. package/dist/src/core/config-validate-check.js +83 -7
  99. package/dist/src/core/config-validate-test-toolchains.js +14 -0
  100. package/dist/src/core/config-validate-token-scales.d.ts +3 -0
  101. package/dist/src/core/config-validate-token-scales.js +47 -5
  102. package/dist/src/core/config-validate.js +6 -0
  103. package/dist/src/core/coverage-extend.js +2 -2
  104. package/dist/src/core/coverage-records.d.ts +11 -0
  105. package/dist/src/core/coverage-records.js +34 -0
  106. package/dist/src/core/engine-session-lock.d.ts +6 -0
  107. package/dist/src/core/engine-session-lock.js +10 -2
  108. package/dist/src/core/file-lock-shared.d.ts +57 -0
  109. package/dist/src/core/file-lock-shared.js +117 -0
  110. package/dist/src/core/file-lock.d.ts +9 -0
  111. package/dist/src/core/file-lock.js +58 -2
  112. package/dist/src/core/furnace-apply.js +10 -10
  113. package/dist/src/core/furnace-config-validate-exceptions.d.ts +11 -3
  114. package/dist/src/core/furnace-config-validate-exceptions.js +59 -8
  115. package/dist/src/core/furnace-config-validate.js +7 -1
  116. package/dist/src/core/furnace-registration-jar-inplace.d.ts +12 -0
  117. package/dist/src/core/furnace-registration-jar-inplace.js +80 -0
  118. package/dist/src/core/furnace-validate-accessibility.d.ts +5 -3
  119. package/dist/src/core/furnace-validate-accessibility.js +75 -30
  120. package/dist/src/core/furnace-validate-exceptions.d.ts +13 -4
  121. package/dist/src/core/furnace-validate-exceptions.js +59 -4
  122. package/dist/src/core/furnace-validate-icon-button.js +30 -1
  123. package/dist/src/core/history-log-file.js +8 -1
  124. package/dist/src/core/line-prefixer.d.ts +18 -0
  125. package/dist/src/core/line-prefixer.js +34 -0
  126. package/dist/src/core/mach.d.ts +7 -0
  127. package/dist/src/core/mach.js +24 -9
  128. package/dist/src/core/module-resolution-preflight.d.ts +19 -3
  129. package/dist/src/core/module-resolution-preflight.js +24 -6
  130. package/dist/src/core/negative-control-compiled.d.ts +36 -0
  131. package/dist/src/core/negative-control-compiled.js +87 -0
  132. package/dist/src/core/negative-control-diff.d.ts +14 -2
  133. package/dist/src/core/negative-control-diff.js +49 -6
  134. package/dist/src/core/negative-control.d.ts +12 -0
  135. package/dist/src/core/negative-control.js +48 -5
  136. package/dist/src/core/park-store.d.ts +63 -0
  137. package/dist/src/core/park-store.js +216 -0
  138. package/dist/src/core/patch-export-update.d.ts +4 -0
  139. package/dist/src/core/patch-export-update.js +12 -3
  140. package/dist/src/core/patch-export.d.ts +3 -2
  141. package/dist/src/core/patch-export.js +21 -14
  142. package/dist/src/core/patch-files.js +26 -8
  143. package/dist/src/core/patch-lint-checkjs-any-import.d.ts +6 -4
  144. package/dist/src/core/patch-lint-checkjs-any-import.js +9 -7
  145. package/dist/src/core/patch-lint-checkjs-diagnostics.d.ts +17 -0
  146. package/dist/src/core/patch-lint-checkjs-diagnostics.js +32 -0
  147. package/dist/src/core/patch-lint-checkjs.d.ts +10 -1
  148. package/dist/src/core/patch-lint-checkjs.js +13 -3
  149. package/dist/src/core/patch-lint-cross.d.ts +3 -1
  150. package/dist/src/core/patch-lint-cross.js +5 -0
  151. package/dist/src/core/patch-lint-css.js +14 -10
  152. package/dist/src/core/patch-lint-forward-registration-extended.d.ts +5 -0
  153. package/dist/src/core/patch-lint-forward-registration-extended.js +1 -1
  154. package/dist/src/core/patch-lint-ignore.d.ts +56 -12
  155. package/dist/src/core/patch-lint-ignore.js +173 -43
  156. package/dist/src/core/patch-lint-ownership.d.ts +10 -0
  157. package/dist/src/core/patch-lint-ownership.js +26 -0
  158. package/dist/src/core/patch-lint-size.js +9 -1
  159. package/dist/src/core/patch-lint-token-scale.d.ts +26 -8
  160. package/dist/src/core/patch-lint-token-scale.js +206 -29
  161. package/dist/src/core/patch-lint.js +2 -2
  162. package/dist/src/core/patch-manifest-io.js +8 -1
  163. package/dist/src/core/patch-manifest-key-order.d.ts +24 -0
  164. package/dist/src/core/patch-manifest-key-order.js +61 -0
  165. package/dist/src/core/patch-manifest-stage.d.ts +46 -0
  166. package/dist/src/core/patch-manifest-stage.js +96 -0
  167. package/dist/src/core/patch-manifest-validate.js +27 -1
  168. package/dist/src/core/patch-order-legal.d.ts +2 -1
  169. package/dist/src/core/patch-order-legal.js +7 -2
  170. package/dist/src/core/patch-policy.d.ts +6 -2
  171. package/dist/src/core/patch-policy.js +14 -7
  172. package/dist/src/core/patch-retired.d.ts +26 -4
  173. package/dist/src/core/patch-retired.js +40 -2
  174. package/dist/src/core/run-log.d.ts +6 -0
  175. package/dist/src/core/run-log.js +17 -1
  176. package/dist/src/core/test-harness-crash.d.ts +2 -1
  177. package/dist/src/core/test-harness-crash.js +5 -8
  178. package/dist/src/core/test-harness-signature.d.ts +16 -0
  179. package/dist/src/core/test-harness-signature.js +19 -1
  180. package/dist/src/core/test-raw-log.d.ts +66 -0
  181. package/dist/src/core/test-raw-log.js +122 -0
  182. package/dist/src/core/test-run-completeness.d.ts +47 -7
  183. package/dist/src/core/test-run-completeness.js +70 -23
  184. package/dist/src/core/test-stale-check.d.ts +10 -1
  185. package/dist/src/core/test-stale-check.js +15 -1
  186. package/dist/src/core/test-xpcshell-retry.d.ts +2 -0
  187. package/dist/src/core/test-xpcshell-retry.js +2 -1
  188. package/dist/src/core/token-lint.d.ts +5 -2
  189. package/dist/src/core/token-lint.js +8 -3
  190. package/dist/src/core/token-sibling-sheets.d.ts +46 -0
  191. package/dist/src/core/token-sibling-sheets.js +119 -0
  192. package/dist/src/core/tree-guard.js +2 -0
  193. package/dist/src/core/typecheck-shim.d.ts +6 -1
  194. package/dist/src/core/typecheck-shim.js +139 -2
  195. package/dist/src/core/typecheck.js +3 -1
  196. package/dist/src/index.d.ts +9 -3
  197. package/dist/src/index.js +4 -1
  198. package/dist/src/types/commands/index.d.ts +2 -2
  199. package/dist/src/types/commands/options.d.ts +68 -7
  200. package/dist/src/types/commands/patches.d.ts +43 -0
  201. package/dist/src/types/config.d.ts +74 -2
  202. package/dist/src/types/furnace.d.ts +29 -0
  203. package/dist/src/types/index.d.ts +1 -1
  204. package/dist/src/utils/options.d.ts +3 -2
  205. package/dist/src/utils/options.js +9 -4
  206. package/docs/configuration.md +110 -9
  207. package/docs/furnace.md +61 -1
  208. package/docs/lifecycle-invariants.md +13 -11
  209. package/docs/machine-output.md +68 -18
  210. package/docs/patch-workflow.md +174 -13
  211. package/docs/testing.md +89 -5
  212. package/package.json +3 -3
package/docs/furnace.md CHANGED
@@ -47,7 +47,10 @@ in `toolkit/content/jar.mn`, including extra modules beside the main one. Add
47
47
  the file to the directory and deploy instead of writing the jar line by hand.
48
48
  A line that already registers the file anywhere in the jar (same target, or
49
49
  same `(widgets/<tag>/<file>)` source) is kept and never duplicated, so mach
50
- does not fail with "Item already in manifest".
50
+ does not fail with "Item already in manifest". Removing a file from the
51
+ directory drops only its line, and `furnace rename` rewrites the widget's
52
+ lines where they stand, so the rest of the block keeps its order and the
53
+ owning patch carries no reorder hunks.
51
54
 
52
55
  The apply and deploy summaries name why each component was skipped, for
53
56
  example `Applied 0, skipped 1 (1 already current)`. "Already current" means
@@ -112,6 +115,63 @@ warning asking for the entry to be deleted. A run over one component judges
112
115
  only that component's entries. `deploy` honours the same entries when it
113
116
  validates, but leaves the staleness report to `validate`.
114
117
 
118
+ An entry never suppresses an error (since 0.51.0): an error blocks apply,
119
+ and a review record cannot make a broken component deployable. The error
120
+ stays counted, and the entry is reported as a
121
+ `validation-exception-on-error` error until the finding is fixed and the
122
+ entry deleted.
123
+
124
+ An optional `tag` narrows an entry to findings about one element tag, so a
125
+ reviewed `<panel-list>` menu does not mute `no-keyboard-handler` for the
126
+ rest of the component. A finding that names several tags (`no-keyboard-handler`
127
+ names every synthetic element with an `@click`) is suppressed only when
128
+ entries cover all of them:
129
+
130
+ ```json
131
+ {
132
+ "component": "moz-vault",
133
+ "check": "no-keyboard-handler",
134
+ "tag": "li",
135
+ "reason": "Rows are reached through the list's roving focus."
136
+ }
137
+ ```
138
+
139
+ An optional `file` (since 0.52.0) narrows an entry the same way to findings
140
+ in one module of the component, by file name. `hardcoded-text` names the
141
+ modules it found text in, so a vendored bundle whose bytes are pinned (and
142
+ so cannot carry a `furnace-ignore` comment) can be accepted while the
143
+ authored modules beside it keep the check. An entry names a `tag` or a
144
+ `file`, not both:
145
+
146
+ ```json
147
+ {
148
+ "component": "moz-editor",
149
+ "check": "hardcoded-text",
150
+ "file": "moz-editor-codemirror.mjs",
151
+ "reason": "The vendored bundle's width probe is never rendered."
152
+ }
153
+ ```
154
+
155
+ Accessibility checks judge the composed component: the host
156
+ `<tag>.mjs` and its companion modules (`<tag>-*.mjs`, tests and stories
157
+ left out) read as one source. Moving a template between the component's
158
+ modules therefore no longer changes its findings: a keyboard handler in a
159
+ companion covers an `@click` in the host, and an `@click` in a companion
160
+ is judged too. `hardcoded-text` judges each module on its own, so a
161
+ `// furnace-ignore: hardcoded-text` comment covers the file it is in, not
162
+ its siblings. Toolkit's `panel-item` counts as natively
163
+ keyboard-activated, beside `button`, form controls and the `moz-*` buttons.
164
+
165
+ `minComponents` sets the fewest components a full `furnace validate` must
166
+ judge. A run under it, an empty tree included, fails with a
167
+ `component-floor` error on `furnace.json`, so a moved or emptied
168
+ components tree cannot pass by validating nothing. A run naming one
169
+ component is not held to it.
170
+
171
+ ```json
172
+ { "minComponents": 40 }
173
+ ```
174
+
115
175
  `optInChecks` turns on checks that are off by default:
116
176
 
117
177
  ```json
@@ -21,10 +21,11 @@ command, read the decision table at the bottom first.
21
21
  take the `.fireforge/engine-session.lock`, then subsystem locks such as
22
22
  build, furnace, or patch-directory locks. Furnace mutations serialize on a
23
23
  per-root lock, and patch directory mutations (filename allocation plus
24
- manifest writes) on a per-directory lock. Dry runs hold the engine-session
25
- lock too, because a `re-export --dry-run` over an untracked binary legally
26
- touches the git index, and holding the lock serializes even "read-only"
27
- previews against real writers.
24
+ manifest writes) on a per-directory lock. `re-export --dry-run` is the
25
+ exception: it writes nothing, runs its git plumbing on a private index
26
+ (so even the `--intent-to-add` staging an untracked binary needs never
27
+ reaches the shared `.git/index`), and serializes only against other dry
28
+ runs, so a preview no longer queues behind a peer's build or test run.
28
29
 
29
30
  3. **A furnace mutation that does not finish restores the engine or says
30
31
  so.** Both a signal (SIGINT or SIGTERM) and a thrown error trigger a
@@ -348,13 +349,14 @@ real-procfs `withFileLock` reap of a live-PID stale record.
348
349
  The project-wide engine-session lock under `.fireforge/`, stamped with PID,
349
350
  command, and start time metadata. CLI `build`, `test`, `export`, `re-export`,
350
351
  `furnace apply`, `furnace deploy` and `furnace sync` take it before subsystem
351
- locks, unconditionally, including dry runs: a `re-export --dry-run` over an
352
- untracked binary legally touches the git index (temporary `--intent-to-add`
353
- staging), so holding the lock serializes even "read-only" previews against
354
- real writers. Dry-run purity is separately enforced at runtime by
355
- `withDryRunPurityGuard` (`re-export-bulk-scan.ts`), which fingerprints the
356
- engine tree and every patch artifact around the dry run and fails hard on
357
- divergence.
352
+ locks. `re-export --dry-run` does not: it runs inside `withPrivateGitIndex`,
353
+ so its temporary `--intent-to-add` staging lands on a throwaway index, and it
354
+ serializes only against other dry runs. Dry-run purity is enforced at runtime
355
+ by `withDryRunPurityGuard` (`re-export-bulk-scan.ts`). For `re-export` it
356
+ fingerprints what the dry run could touch (the engine HEAD and index, the
357
+ selected patches' bodies, their `patches.json` rows and the engine files they
358
+ own) and fails hard on divergence, naming a foreign writer when the history
359
+ log or the engine-session lock shows one in the window (`re-export-purity-scope.ts`).
358
360
 
359
361
  Contention fails fast after about a second by default, with the fixed message
360
362
  "Another FireForge engine-mutating command is already running. Wait for it to
@@ -53,21 +53,21 @@ if (options.json === true) {
53
53
 
54
54
  ## Current surfaces
55
55
 
56
- | Surface | Shape | Notes |
57
- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
58
- | `status --json` | `{ schemaVersion: 1, … }` | Error envelope on three engine preconditions |
59
- | `status --json` under `--check` | adds `check: { enabled, failOn, allow?, failOnUnknown?, failed, offenders }` | Since 0.50.0 on the full payload too, not only `--summary`. `allow` and `failOnUnknown` appear only when set. An offender failing only through `--fail-on-unknown` carries `unnamed: true`. `--patches` adds `scope: { patches }` |
60
- | `furnace validate --json` | `{ schemaVersion: 1, passed, summary, maxWarnings?, components, issues, excepted, staleExceptions }` | `issues` counts after `validationExceptions`, stale-exception warnings included. `excepted` carries each suppressed finding with its `reason` |
61
- | `check --json` | `{ schemaVersion: 1, verdict, reason?, summary, steps }` | One entry per step: `name`, `kind`, `command`, `status` (`pass`/`fail`/`skipped`), `exitCode`, `durationMs`, `detail`, and on failure `outputTail` (`timedOut` when killed at its budget). No verdict line under `--json` |
62
- | `check` verdict line | `FIREFORGE-VERDICT: PASS\|FAIL [reason=step-failures\|lock-timeout failed=<steps>] steps=<n> skipped=<n>` | Not JSON. The last stdout line, after the step table, sealed like `test`'s. Its `reason=` set is its own, not `test`'s |
63
- | `tree list --json` | `{ schemaVersion: 1, trees }` | Error envelope added in 0.44.0 |
64
- | `token list --json` | `{ schemaVersion: 1, tokensCssPath, categories }` | Categories in file order, each with its tokens (`name`, `line`, `value`) |
65
- | `token show --json` | `{ schemaVersion: 1, name, category, declarations }` | One entry per declaring block, with its selector trail and line |
66
- | `token lint --json` | `{ schemaVersion: 1, tokensCssPath, tokenPrefix, filesScanned, findings }` | Each finding has `check`, `file`, `line`, `token`, `message`. The document is written even when findings exist; the exit is then non-zero, as with `status --check --json` |
67
- | `test` verdict line | `FIREFORGE-VERDICT: PASS\|FAIL reason=… [note=<class>] [started=<n>/<m> first-unstarted=<path>] [runs=<p>/<n>] [control=<outcome>] [shuffle=<seed>] [furnace-applied=<names>\|furnace-apply=skipped] [log=<path>]` | Not JSON. A single final stdout line, sealed by `setStdoutSealed` so nothing can displace it. `log=` names the run's own complete output under `.fireforge/logs/`, so a piped or truncated run still leaves a re-readable artifact |
68
- | `lint --per-patch --report <path>` | `{ schemaVersion: 1, patches, queueIssues, totals, … }` | Written to a file. `queueIssues` holds the cross-patch findings; `patch staged-dependency --add --from` reads back their `stagedDeclarations` |
69
- | `status --raw` | `git status --porcelain` lines | Not JSON, but still a machine surface: it engages the same stdout discipline, so rules 3 and 4 apply and rules 1 and 2 do not. A clean tree prints nothing |
70
- | `build` log announcement | `Full build output: <path>` | Not JSON. `build` prints no verdict line, so it names its run log directly on the way out |
56
+ | Surface | Shape | Notes |
57
+ | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
58
+ | `status --json` | `{ schemaVersion: 1, … }` | Error envelope on three engine preconditions |
59
+ | `status --json` under `--check` | adds `check: { enabled, failOn, allow?, failOnUnknown?, failed, offenders }` | Since 0.50.0 on the full payload too, not only `--summary`. `allow` and `failOnUnknown` appear only when set. An offender failing only through `--fail-on-unknown` carries `unnamed: true`. `--patches` adds `scope: { patches }` |
60
+ | `furnace validate --json` | `{ schemaVersion: 1, passed, summary, maxWarnings?, components, issues, excepted, staleExceptions }` | `issues` counts after `validationExceptions`, stale-exception warnings included. `excepted` carries each suppressed finding with its `reason` |
61
+ | `check --json` | `{ schemaVersion: 1, verdict, reason?, summary, steps }` | One entry per step: `name`, `kind`, `command`, `status` (`pass`/`fail`/`skipped`), `exitCode`, `durationMs`, `detail`, and on failure `outputTail` (`timedOut` when killed at its budget). No verdict line under `--json` |
62
+ | `check` verdict line | `FIREFORGE-VERDICT: PASS\|FAIL [reason=step-failures\|lock-timeout\|skipped-steps failed=<steps>] steps=<n> skipped=<n> [skipped-steps=<steps>]` | Not JSON. The last stdout line, after the step table, sealed like `test`'s. Its `reason=` set is its own, not `test`'s |
63
+ | `tree list --json` | `{ schemaVersion: 1, trees }` | Error envelope added in 0.44.0 |
64
+ | `token list --json` | `{ schemaVersion: 1, tokensCssPath, categories }` | Categories in file order, each with its tokens (`name`, `line`, `value`) |
65
+ | `token show --json` | `{ schemaVersion: 1, name, category, declarations }` | One entry per declaring block, with its selector trail and line |
66
+ | `token lint --json` | `{ schemaVersion: 1, tokensCssPath, tokenPrefix, filesScanned, findings }` | Each finding has `check`, `file`, `line`, `token`, `message`. The document is written even when findings exist; the exit is then non-zero, as with `status --check --json` |
67
+ | `test` verdict line | `FIREFORGE-VERDICT: PASS\|FAIL reason=… [note=<class>] [started=<n>/<m> first-unstarted=<path> started-source=<src>] [shards=<p>/<t> [failed-shards=<labels>]] [runs=<p>/<n>] [control=<outcome>] [shuffle=<seed>] [furnace-applied=<names>\|furnace-apply=skipped] [log=<path>]` | Not JSON. A single final stdout line, sealed by `setStdoutSealed` so nothing can displace it. `log=` names the run's own complete output under `.fireforge/logs/`, so a piped or truncated run still leaves a re-readable artifact |
68
+ | `lint --per-patch --report <path>` | `{ schemaVersion: 1, patches, queueIssues, totals, … }` | Written to a file. `queueIssues` holds the cross-patch findings; `patch staged-dependency --add --from` reads back their `stagedDeclarations` |
69
+ | `status --raw` | `git status --porcelain` lines | Not JSON, but still a machine surface: it engages the same stdout discipline, so rules 3 and 4 apply and rules 1 and 2 do not. A clean tree prints nothing |
70
+ | `build` log announcement | `Full build output: <path>` | Not JSON. `build` prints no verdict line, so it names its run log directly on the way out |
71
71
 
72
72
  `--raw` is why `cli.ts` reads `process.argv` for `--json` or `--raw` before
73
73
  dispatching. Rule 3 is about who owns stdout, and that question is settled
@@ -80,6 +80,21 @@ it. Follow the rules above rather than inventing a shape.
80
80
 
81
81
  The run logs behind `log=` are specified in [`run-logs.md`](run-logs.md).
82
82
 
83
+ Additive keys since 0.51.0 outside `test`:
84
+
85
+ - `check --json`: a skipped step carries `skipKind`, `config` when
86
+ `check.skip` names it and `unavailable` when the project has nothing for
87
+ it to run. The verdict line adds `skipped-steps=<steps>` whenever a step
88
+ is skipped, and `reason=skipped-steps` when `check.failOnSkipped` fails a
89
+ run on an `unavailable` skip alone.
90
+ - `furnace validate --json`: a finding about particular elements carries
91
+ `tags` (`no-keyboard-handler` names each synthetic element with an
92
+ `@click`). An exception that matched an error adds a
93
+ `validation-exception-on-error` error, and a full run under
94
+ `furnace.json#minComponents` a `component-floor` error on `furnace.json`.
95
+ - `furnace validate --json` (since 0.52.0): a `hardcoded-text` finding
96
+ carries `files`, the component's module file names it was found in.
97
+
83
98
  ## Versioning
84
99
 
85
100
  `schemaVersion` is `MACHINE_OUTPUT_SCHEMA_VERSION` in
@@ -159,6 +174,37 @@ that has none. A run refused only for a non-zero `unexpected=` count or a
159
174
  mid-test shutdown carries neither. Absent on every other line. See
160
175
  [`testing.md`](testing.md#the-verdict-line).
161
176
 
177
+ `started-source=raw|stream|capture` follows them and names the evidence the
178
+ count came from: the mozlog structured log (a run with `--results` or
179
+ `--shard-size`), the complete streamed output (every other run), or, only
180
+ when nothing was streamed, the last 2 MB of the capture.
181
+
182
+ ### Shards: `FIREFORGE-SHARD:` and `failed-shards=`
183
+
184
+ A sharded run prints one `FIREFORGE-SHARD:` line per finished shard, before
185
+ the verdict line:
186
+
187
+ ```
188
+ FIREFORGE-SHARD: PASS|FAIL [reason=…] [checks=<n>] [unexpected=<n>] shard=<i>/<n> path=<label> [failed-files=<path>,…]
189
+ ```
190
+
191
+ `path=` is the shard's path argument (whitespace replaced by `_`), or a
192
+ `--shard-size`/`--jobs` chunk's first file plus `+<k>`. `failed-files=`
193
+ appears under `--shard-size` and names the chunk's files that failed. The
194
+ aggregate verdict appends `failed-shards=<label>,…` after `shards=` when any
195
+ shard did not pass; under `--shard-size` `shards=` counts files and
196
+ `failed-shards=` names files. Both are absent on a green run.
197
+
198
+ ### Additive keys of parallel and per-file runs
199
+
200
+ `jobs=<n>` marks a `--jobs` run, `lock=shared` a run that held the engine
201
+ session lock shared, and `results=<path>` names the `--results` document. A
202
+ run whose engine changed mid-run only in files the running browser cannot
203
+ see keeps its verdict and appends `drift-tolerated=<n>`. A negative control
204
+ on compiled source appends `control-build=compiled`. Since 0.52.0 a raw log
205
+ that starts none of a dispatch's requested files counts as
206
+ `started=0/<m> ... started-source=raw` instead of leaving the run uncounted.
207
+
162
208
  ### The additive `shuffle=` key
163
209
 
164
210
  A `fireforge test --shuffle [seed]` run records the seed used to permute
@@ -210,8 +256,10 @@ the verdict `reason=inconclusive` only when HEAD moved or a changed path is
210
256
  packaged by the running build: a source an objdir install manifest names, a
211
257
  directory one installs from, or a test file. Edits that no manifest installs
212
258
  (compiled sources, build files, docs) leave the sample standing with a
213
- warning naming them. Other runs keep the strict rule: any change is
214
- inconclusive.
259
+ warning naming them. Other runs are inconclusive only when a changed path is
260
+ one the running browser can see: a file the objdir links to its source, one
261
+ staged at dispatch, or a test file or staging input of the run; anything
262
+ else leaves the verdict standing with `drift-tolerated=<n>`.
215
263
 
216
264
  ### Additive Furnace keys
217
265
 
@@ -219,4 +267,6 @@ A `fireforge test --build` or `--build-only` run whose pre-test Furnace apply
219
267
  deployed components appends `furnace-applied=<name>,<name>…`: the components
220
268
  whose `components/` source changed since their last deploy, which the
221
269
  runtime under test therefore includes. `--no-furnace-apply` skips that apply
222
- and appends `furnace-apply=skipped`. Both are absent otherwise.
270
+ and appends `furnace-apply=skipped`. `--furnace-apply reached` appends
271
+ `furnace-skipped=<names>` for components with undeployed edits that no
272
+ requested test reaches. All are absent otherwise.
@@ -37,6 +37,14 @@ directories. The manifest is JSON:
37
37
  The command can be dry-run, refuses ambiguous ownership, and reports each
38
38
  file-to-patch assignment before refreshing the patch.
39
39
 
40
+ `re-export --dry-run` takes no engine-session lock: it writes nothing, so it
41
+ does not queue behind a peer's build or test run, and its git plumbing runs on
42
+ a private index. Its purity guard covers what the dry run could touch (the
43
+ selected patches, their `patches.json` rows, the engine files they own, and
44
+ the engine HEAD and index). A peer writing any of those during the preview is
45
+ named from the history log or the engine-session lock, and the preview is
46
+ simply rerun.
47
+
40
48
  To adopt files into several patches in one call, name each file's owner
41
49
  with `--scan-file <patch>:<path>`. A call with more than one target patch
42
50
  requires the prefix on every `--scan-file`. Selected patches that get no
@@ -68,6 +76,29 @@ module a later patch creates. They also refuse a forward registration
68
76
  module), in the same refusal and with the `patch staged-dependency` command
69
77
  that declares it, so one declaration pass covers both.
70
78
 
79
+ ### Exporting by hunk
80
+
81
+ Re-export captures whole files, so on a shared engine a peer's unexported
82
+ line in a shared manifest or chrome document lands in the exporting patch.
83
+ Two flags export a file by hunk instead. Each takes one explicit patch and
84
+ cannot combine with `--scan`, `--files` or `--all`:
85
+
86
+ - `--hunks-from <diff>` exports each file the diff names as the patch's
87
+ current body for it plus only the diff's hunks. Files the diff does not
88
+ name keep their old section.
89
+ - `--exclude-hunks <diff>` exports each named file as the working tree minus
90
+ the diff's hunks. The other files export as usual.
91
+
92
+ Hunks are placed by their context, so a diff taken before a peer's edit
93
+ above it still applies, and a hunk whose context is gone refuses. The
94
+ section is diffed by git against HEAD on a throwaway index, and the engine
95
+ is not touched. A spine patch can then export one change's lines at a time:
96
+
97
+ ```sh
98
+ git -C engine diff -- browser/base/content/hominis.js > my-change.diff # trim to your hunks
99
+ fireforge re-export 200 --hunks-from my-change.diff
100
+ ```
101
+
71
102
  ### Re-export belts and arguments
72
103
 
73
104
  `--refuse-adjacent-unmanaged` refuses a patch when an unmanaged file is
@@ -82,6 +113,10 @@ session's unexported test in `test/hominis-tiles/` no longer refuses a
82
113
  patch that owns `test/hominis/browser.toml`. The refusal names every file
83
114
  with its anchor (`beside engine/<dir>` or `registered by engine/<carrier>`).
84
115
 
116
+ Beside `--scan` and `--scan-file` the belt judges the adjacent files the
117
+ scan did not adopt, so one call can adopt a new widget and its test and
118
+ still refuse a peer's neighbour. It cannot combine with `--files`.
119
+
85
120
  Three carve-outs keep the belt armed for everything they do not name. Each
86
121
  requires `--refuse-adjacent-unmanaged`, and each admitted file is still
87
122
  listed:
@@ -142,7 +177,10 @@ deleted whose engine copy is back at HEAD:
142
177
  fireforge re-export 202 --refuse-adjacent-unmanaged --refuse-foreign-drift --expect browser/themes/shared/hominis/widgets.css --expect-removed browser/themes/shared/hominis/stop.svg
143
178
  ```
144
179
 
145
- The removal must have exactly one selected owner. A patch-created file must
180
+ Each removal must have exactly one selected owner, and one call may retire
181
+ paths from several selected patches (`re-export 102 202 --expect-removed a
182
+ --expect-removed b`). A path whose owner was left out of the selection is
183
+ refused with that owner named. A patch-created file must
146
184
  be absent from disk and engine HEAD, and have a `new file mode` section in
147
185
  that owner's patch. An upstream file must be tracked in HEAD, match it on
148
186
  disk, and have a section in that owner's patch. Without the retirement, a
@@ -161,7 +199,18 @@ later patch creates names the earliest legal order for exporting those
161
199
  files as their own patch (free, inside the category range, and not opening
162
200
  a gap under `allowGaps: false`), and prints the exact `patch
163
201
  staged-dependency --add` command for each edge, with a `--reason`
164
- placeholder.
202
+ placeholder. A refused `--scan` adoption, `patch move-files` or `patch split`
203
+ also writes every declaration it proposes into one lint-report-shaped JSON
204
+ (`--declarations-out <path>`, by default under `.fireforge/declarations/`)
205
+ and names the single `patch staged-dependency <holder> --add --from <file>`
206
+ call per holding patch that discharges them all. Moving tests out of a patch
207
+ that owns a shared manifest no longer needs one declaration typed per
208
+ manifest line.
209
+
210
+ A re-export also drops a staged registration whose `creates` path no patch
211
+ in the queue owns any more, for example after a merge deleted the file it
212
+ registered. The dry run names the drop, and the write records it in the
213
+ history log (`re-export-staged-prune`).
165
214
 
166
215
  An `export --order` (or `--before`/`--after`) refused on a new system
167
216
  module that no patch registers in moz.build says "register first". When the
@@ -214,6 +263,40 @@ Under `fireforge patch`:
214
263
  occupied. A patch that later takes the order (`export --order <n>`, a
215
264
  rename or a renumber) fills the slot, which then disappears. `patch
216
265
  compact` renumbers through retired slots like any other gap.
266
+ - `patch delete` and `patch retire` name each file the patch creates that is
267
+ still on disk, in the dry run and at the prompt: once the patch is gone
268
+ nothing owns it, so `status` would report it unmanaged. Delete those files
269
+ first, or pass `--delete-created` to remove them as part of the confirmed
270
+ operation (under the engine lock as well). Files the patch only modifies
271
+ keep their edits and become unmanaged, as before.
272
+ - `patch reserve <order> --category <c> --owner <label> [--reason <text>]`
273
+ holds an order for a patch a session has agreed to export but has not
274
+ exported yet. The reservation lives in `patches.json#reserved`. Like a
275
+ retired slot, the gap check counts it as occupied, so `export --order 571`
276
+ is legal while a peer holds 569 and 570. Unlike a retired slot, the
277
+ earliest-legal-order search and a default `export` skip it, because it is
278
+ held for its owner. The export that lands on the order fills it, with a
279
+ notice naming the owner. `patch reserve <order> --release` drops a
280
+ reservation whose patch will not come. An order a patch, a retired slot or
281
+ another reservation holds, or one outside the category's range, is
282
+ refused.
283
+ - `patch stage <name>...` stages the named patches' `.patch` files and only
284
+ their rows of `patches/patches.json` in the project repository's git index.
285
+ Every other row stays as at HEAD. The staged manifest is built from HEAD's
286
+ rows plus the named working rows (with their retired slots; a reservation
287
+ a staged row fills is dropped), validated, and written with `git
288
+ hash-object` and `git update-index`. A collision (two rows at one order) or
289
+ an invalid result is refused before the index is touched. The working copy
290
+ is never changed and nothing is committed. Name a patch the working copy
291
+ no longer has by its HEAD filename to stage its removal, and name both
292
+ filenames of a renamed patch.
293
+ - `patch staged-dependency <name> --remove` names the entries to remove by
294
+ any of `--file`, `--specifier`/`--line`, `--creates` and `--owner`. It needs
295
+ `--creates`, or `--file` with the matcher. `--creates` alone removes every
296
+ declaration for that created path, which is the cleanup after a merge
297
+ deletes it. Registration lines compare whitespace-insensitively, as the
298
+ lint does. A selection that matches nothing is refused with exit 8 (it used
299
+ to print "no staged registration matched" and exit 0), on a dry run too.
217
300
  - `patch staged-dependency <name> --add --from <json-file>` adds many
218
301
  declarations in one call under one lock hold. The file is a `lint
219
302
  --per-patch --report` document (its findings' `stagedDeclarations` for
@@ -223,6 +306,24 @@ reason?, patch? }` entries; entries naming another patch are skipped, and
223
306
  only its delta (`added` / `removed`) in the history; records written
224
307
  before 0.50.0 carry the whole `before` / `after` lists instead.
225
308
 
309
+ ### Parking a session's edits
310
+
311
+ Several sessions edit the same files, and a peer's `re-export
312
+ --refuse-foreign-drift` refuses while another session's hunks sit in its
313
+ patch's files. `fireforge park <patch>` sets them aside: every drifted file of
314
+ the patch goes back to its queue-applied baseline (or, with `--hunks <diff>`,
315
+ only the named hunks come out of the working tree), and both sides of each
316
+ file are kept under `.fireforge/park/<label>/`. After the owner commits,
317
+ `fireforge park --restore <label>` three-way merges the parked edits back on
318
+ top of the commit. A conflict leaves markers, keeps the park and exits
319
+ non-zero. `park --list` and `park --drop <label>` manage parks, `--dry-run`
320
+ previews, and park takes the engine lock.
321
+
322
+ Before parking it warns when a hunk it takes out removes a Fluent message id,
323
+ or a whole file, that files outside the patch still reference. A parked
324
+ string still used by markup another patch owns makes `document.l10n.ready`
325
+ reject in every new window, so move it somewhere you own first.
326
+
226
327
  Every queue-mutating patch command accepts `--wait-lock [seconds]`. See
227
328
  [`configuration.md`](configuration.md#lock-waits) for how the budget
228
329
  resolves.
@@ -260,7 +361,15 @@ programmatic API, with the file-size helpers `isTestFile`,
260
361
  `file-too-large` measures in a patch: the JS files it creates, and never a
261
362
  modified file or a new file of another kind. The rule selects its files
262
363
  through that same function, so an audit that uses it cannot drift from the
263
- rule.
364
+ rule. Since 0.51.0 the API also exports the measurement the size rules
365
+ themselves take, `measurePatchSize(engineDir, diff, filesAffected, config)`
366
+ (lines, test-file lines, files and registration files under the configured
367
+ metric), `isPatchSizeRegistrationFile(file, globs)`, the per-line
368
+ `codeLineMask(file, content)` behind `countCodeLines`, and
369
+ `evaluatePatchPolicyContentForQueue(config, projectRoot, patchesDir,
370
+ manifest)`, the body-dependent `patchPolicy` rules `verify` runs. A waiver
371
+ audit or a policy probe can then measure a patch exactly as the lint does,
372
+ without importing `dist/` files by path.
264
373
 
265
374
  `lint --per-patch` measures sizes on the working tree, the way export and
266
375
  re-export do: each patch's diff is taken against the engine baseline from
@@ -272,10 +381,13 @@ thresholds.
272
381
 
273
382
  Per-patch lint type-checks plain Firefox JS against a built-in
274
383
  Firefox-globals shim that tracks upstream WebIDL additions per release. A
275
- `resource://` or `chrome://` import the per-patch program cannot resolve (a
276
- module another patch creates, outside the file's program) is typed `any`;
277
- an implicit-`any` finding in a file with such an import names it, since the
278
- whole-tree `fireforge typecheck` sees the real type. See
384
+ `resource://` or `chrome://` import resolves to the module the queue
385
+ creates under that name; a test script's own program sees the modules
386
+ created up to the test's patch. An import the program cannot resolve (a
387
+ module a later patch creates, an upstream module, or a basename two
388
+ patches create) is typed `any`; an implicit-`any` finding in a file with
389
+ such an import names it, since the whole-tree `fireforge typecheck` sees
390
+ the real type. See
279
391
  [`configuration.md`](configuration.md#patchlint) for the project hooks
280
392
  (`checkJsExtraShim`, `checkJsTestFiles`, `checkJsTestShim`).
281
393
 
@@ -328,11 +440,49 @@ longer reaches its own warning band, `lint --per-patch` prints a
328
440
  `lint-ignore-removable` notice naming the `patch lint-ignore --remove`
329
441
  command, and the `--report` JSON carries each ceiling under
330
442
  `lintIgnoreCeilings` with its current `measurement` and a `state` of
331
- `within`, `exceeded` or `removable`. An `--add` for a rule that already has
332
- an entry replaces it, so the same command moves a ceiling or widens it back
333
- to a bare waiver. `export` and `re-export --lint-ignore <id>` only append
334
- bare IDs, and never widen an existing ceiling. The manifest refuses a
335
- ceiling on any other rule, and a second entry for the same rule.
443
+ `within`, `exceeded` or `removable`. A notice never exceeds a ceiling: it
444
+ gates nothing, so a notice-band measurement past `max` surfaces as the plain
445
+ notice it is. The report's `lintIgnoreWaivers` lists every entry, bare IDs
446
+ included (`kind: "bare"`, `max: null`), with the same `measurement` and
447
+ `state`, so an audit does not have to re-measure bare waivers from
448
+ `suppressedIssues`. For a bare waiver the state is informational:
449
+ `removable` also covers a rule this lint does not run. An `--add` for a rule
450
+ that already has an entry replaces it, so the same command moves a ceiling
451
+ or widens it back to a bare waiver. `export` and `re-export --lint-ignore
452
+ <id>` only append bare IDs, and never widen an existing ceiling. The manifest
453
+ refuses a ceiling on any other rule, and a second entry for the same rule.
454
+
455
+ Under `patchSizeMetric: "addedCode"` a general-tier patch's
456
+ `large-patch-lines` findings carry `share: "product"` (non-test files) or
457
+ `share: "test"` (test files, held to the test tier). A ceiling can name the
458
+ share it bounds, and `large-patch-lines` may then carry one ceiling per
459
+ share instead of a single one:
460
+
461
+ ```json
462
+ "lintIgnore": [
463
+ { "rule": "large-patch-lines", "share": "product", "max": 900, "reason": "…" },
464
+ { "rule": "large-patch-lines", "share": "test", "max": 4000, "reason": "…",
465
+ "meta": { "kind": "structural", "owner": "tiles" } }
466
+ ]
467
+ ```
468
+
469
+ A ceiling's optional `meta` is a free-form JSON object FireForge keeps
470
+ verbatim and never reads, for a project's own waiver bookkeeping (kind,
471
+ owner, exit condition). The report echoes it.
472
+
473
+ Several ceilings go in one call, under one lock hold and one manifest write:
474
+ `--add <rule>=<max>` gives each entry its own ceiling (`--reason` applies to
475
+ all of them, `--share` scopes a `large-patch-lines` one), and `--from
476
+ <json-file>` adds every entry of a JSON array (check IDs, or `{ rule, max,
477
+ reason?, share?, meta?, patch? }` objects; entries naming another patch are
478
+ skipped, and `--reason` fills objects without one). Given a `lint --per-patch
479
+ --report` document instead, `--from` rebaselines each of the patch's
480
+ ceilings to its current `measurement`.
481
+
482
+ ```sh
483
+ fireforge patch lint-ignore 310-ui-sidebar --add file-too-large=1200 --add large-patch-files=40 --reason "Rebaseline 2026-10"
484
+ fireforge patch lint-ignore 310-ui-sidebar --from lint-report.json
485
+ ```
336
486
 
337
487
  ## CI enforcement
338
488
 
@@ -369,9 +519,20 @@ them (filename, stem, order number, manifest name or slug). A peer's drifted
369
519
  patch then no longer turns this session's gate red. The limit is the
370
520
  point: an unmanaged file belongs to no patch, so a scoped run never judges
371
521
  unexported files, this session's included. Run the unscoped gate before a
372
- commit that should cover them. `verify --patches` keeps findings about the
522
+ commit that should cover them. `lint --per-patch --patches` keeps every
523
+ queue finding that names a scoped patch, even on a file the patch no longer
524
+ touches (a stale `staged-dependency-unused` declaration, since 0.51.0). `verify --patches` keeps findings about the
373
525
  queue as a whole (a `patches.json` that does not parse) whatever the scope.
374
526
 
527
+ `verify` resolves every `resource:///modules/…` and `resource://gre/modules/…`
528
+ `.sys.mjs` specifier the queue's files import, and fails on a fork module that
529
+ is missing or that no `moz.build` at or above its directory registers. Such
530
+ an import otherwise crashes every browser start of the objdir, or kills
531
+ xpcshell with no output. An unscoped run also scans the engine worktree:
532
+ changed files are read for imports, and untracked files count as fork
533
+ modules. That covers a chrome script's lazy getter for a module that neither
534
+ file has exported yet.
535
+
375
536
  `fireforge check` composes the static gates into one command: `status`
376
537
  (as `--json --summary --check`), `verify`, `lint --per-patch`,
377
538
  `typecheck`, `furnace validate` and `token coverage`, plus the project's