@hominis/fireforge 0.49.2 → 0.50.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 (334) hide show
  1. package/CHANGELOG.md +40 -1
  2. package/README.md +1 -1
  3. package/dist/bin/fireforge.js +6 -0
  4. package/dist/build-info.json +4 -4
  5. package/dist/src/commands/build-prelock.d.ts +20 -0
  6. package/dist/src/commands/build-prelock.js +50 -0
  7. package/dist/src/commands/build.d.ts +3 -1
  8. package/dist/src/commands/build.js +13 -6
  9. package/dist/src/commands/check-report.d.ts +28 -0
  10. package/dist/src/commands/check-report.js +78 -0
  11. package/dist/src/commands/check-run.d.ts +28 -0
  12. package/dist/src/commands/check-run.js +104 -0
  13. package/dist/src/commands/check-steps.d.ts +67 -0
  14. package/dist/src/commands/check-steps.js +175 -0
  15. package/dist/src/commands/check.d.ts +32 -0
  16. package/dist/src/commands/check.js +160 -0
  17. package/dist/src/commands/config.js +5 -6
  18. package/dist/src/commands/doctor-engine-eslint.js +6 -1
  19. package/dist/src/commands/doctor-source-pin.js +4 -1
  20. package/dist/src/commands/export-adopt-check.d.ts +38 -0
  21. package/dist/src/commands/export-adopt-check.js +98 -0
  22. package/dist/src/commands/export-flow.js +29 -14
  23. package/dist/src/commands/export-placement-gate.d.ts +6 -2
  24. package/dist/src/commands/export-shared.d.ts +2 -1
  25. package/dist/src/commands/export-staged-registration.d.ts +46 -0
  26. package/dist/src/commands/export-staged-registration.js +91 -0
  27. package/dist/src/commands/forward-import-remedy.d.ts +32 -0
  28. package/dist/src/commands/forward-import-remedy.js +63 -0
  29. package/dist/src/commands/furnace/apply.js +3 -3
  30. package/dist/src/commands/furnace/chrome-doc-remove.d.ts +2 -1
  31. package/dist/src/commands/furnace/chrome-doc-remove.js +2 -2
  32. package/dist/src/commands/furnace/chrome-doc.d.ts +2 -1
  33. package/dist/src/commands/furnace/chrome-doc.js +2 -2
  34. package/dist/src/commands/furnace/create.js +2 -2
  35. package/dist/src/commands/furnace/deploy-named.d.ts +48 -0
  36. package/dist/src/commands/furnace/deploy-named.js +107 -0
  37. package/dist/src/commands/furnace/deploy.d.ts +3 -2
  38. package/dist/src/commands/furnace/deploy.js +50 -86
  39. package/dist/src/commands/furnace/diff.js +26 -3
  40. package/dist/src/commands/furnace/index.js +66 -41
  41. package/dist/src/commands/furnace/init.js +13 -6
  42. package/dist/src/commands/furnace/override.js +4 -2
  43. package/dist/src/commands/furnace/preview.js +4 -3
  44. package/dist/src/commands/furnace/refresh.js +2 -2
  45. package/dist/src/commands/furnace/remove.js +4 -3
  46. package/dist/src/commands/furnace/rename-dry-run.d.ts +29 -0
  47. package/dist/src/commands/furnace/rename-dry-run.js +127 -0
  48. package/dist/src/commands/furnace/rename-helpers.d.ts +7 -0
  49. package/dist/src/commands/furnace/rename-helpers.js +15 -1
  50. package/dist/src/commands/furnace/rename-registrations.d.ts +41 -0
  51. package/dist/src/commands/furnace/rename-registrations.js +93 -0
  52. package/dist/src/commands/furnace/rename.d.ts +2 -1
  53. package/dist/src/commands/furnace/rename.js +28 -66
  54. package/dist/src/commands/furnace/scan.d.ts +9 -4
  55. package/dist/src/commands/furnace/scan.js +11 -9
  56. package/dist/src/commands/furnace/sync.js +3 -2
  57. package/dist/src/commands/furnace/validate-report.d.ts +45 -0
  58. package/dist/src/commands/furnace/validate-report.js +91 -0
  59. package/dist/src/commands/furnace/validate.d.ts +1 -1
  60. package/dist/src/commands/furnace/validate.js +127 -114
  61. package/dist/src/commands/furnace/validation-output.js +14 -3
  62. package/dist/src/commands/lint-per-patch.js +31 -60
  63. package/dist/src/commands/lint-report.d.ts +8 -4
  64. package/dist/src/commands/lint-report.js +15 -5
  65. package/dist/src/commands/lint.js +15 -34
  66. package/dist/src/commands/manifest.js +2 -0
  67. package/dist/src/commands/patch/delete-gap-notice.js +3 -1
  68. package/dist/src/commands/patch/delete.d.ts +1 -1
  69. package/dist/src/commands/patch/delete.js +13 -98
  70. package/dist/src/commands/patch/index.d.ts +1 -1
  71. package/dist/src/commands/patch/index.js +4 -2
  72. package/dist/src/commands/patch/lint-ignore.d.ts +5 -1
  73. package/dist/src/commands/patch/lint-ignore.js +65 -28
  74. package/dist/src/commands/patch/move-files-create.js +26 -17
  75. package/dist/src/commands/patch/move-files-into.js +5 -3
  76. package/dist/src/commands/patch/move-files.js +3 -1
  77. package/dist/src/commands/patch/removal-dependents.d.ts +63 -0
  78. package/dist/src/commands/patch/removal-dependents.js +133 -0
  79. package/dist/src/commands/patch/rename.js +7 -0
  80. package/dist/src/commands/patch/retire.d.ts +34 -0
  81. package/dist/src/commands/patch/retire.js +128 -0
  82. package/dist/src/commands/patch/split-plan.d.ts +7 -7
  83. package/dist/src/commands/patch/split-plan.js +45 -28
  84. package/dist/src/commands/patch/split-staged.d.ts +108 -0
  85. package/dist/src/commands/patch/split-staged.js +202 -0
  86. package/dist/src/commands/patch/split.d.ts +5 -0
  87. package/dist/src/commands/patch/split.js +51 -15
  88. package/dist/src/commands/patch/staged-dependency-batch.d.ts +48 -0
  89. package/dist/src/commands/patch/staged-dependency-batch.js +176 -0
  90. package/dist/src/commands/patch/staged-dependency-entries.d.ts +36 -0
  91. package/dist/src/commands/patch/staged-dependency-entries.js +52 -0
  92. package/dist/src/commands/patch/staged-dependency-from.d.ts +14 -0
  93. package/dist/src/commands/patch/staged-dependency-from.js +108 -0
  94. package/dist/src/commands/patch/staged-dependency.js +14 -41
  95. package/dist/src/commands/re-export-adjacent-registrations.d.ts +20 -0
  96. package/dist/src/commands/re-export-adjacent-registrations.js +75 -0
  97. package/dist/src/commands/re-export-adjacent.d.ts +45 -2
  98. package/dist/src/commands/re-export-adjacent.js +156 -37
  99. package/dist/src/commands/re-export-drift.d.ts +36 -8
  100. package/dist/src/commands/re-export-drift.js +73 -49
  101. package/dist/src/commands/re-export-files.js +11 -8
  102. package/dist/src/commands/re-export-lint.d.ts +2 -1
  103. package/dist/src/commands/re-export-lint.js +23 -10
  104. package/dist/src/commands/re-export-options.js +45 -4
  105. package/dist/src/commands/re-export-preflight.d.ts +23 -0
  106. package/dist/src/commands/re-export-preflight.js +160 -0
  107. package/dist/src/commands/re-export-register.js +42 -17
  108. package/dist/src/commands/re-export-removed.d.ts +12 -1
  109. package/dist/src/commands/re-export-removed.js +41 -11
  110. package/dist/src/commands/re-export-scan-owner.d.ts +44 -0
  111. package/dist/src/commands/re-export-scan-owner.js +116 -0
  112. package/dist/src/commands/re-export-scan.d.ts +17 -9
  113. package/dist/src/commands/re-export-scan.js +56 -23
  114. package/dist/src/commands/re-export-single.js +22 -6
  115. package/dist/src/commands/re-export-summary.d.ts +28 -0
  116. package/dist/src/commands/re-export-summary.js +36 -0
  117. package/dist/src/commands/re-export.js +37 -24
  118. package/dist/src/commands/rebase/index.js +17 -17
  119. package/dist/src/commands/run.js +16 -2
  120. package/dist/src/commands/status-check.d.ts +55 -16
  121. package/dist/src/commands/status-check.js +92 -37
  122. package/dist/src/commands/status-json.d.ts +22 -9
  123. package/dist/src/commands/status-json.js +47 -32
  124. package/dist/src/commands/status-output.js +3 -1
  125. package/dist/src/commands/status-scope.d.ts +37 -0
  126. package/dist/src/commands/status-scope.js +54 -0
  127. package/dist/src/commands/status.js +66 -23
  128. package/dist/src/commands/test-browser-preflight.js +19 -3
  129. package/dist/src/commands/test-build-furnace.d.ts +21 -0
  130. package/dist/src/commands/test-build-furnace.js +48 -0
  131. package/dist/src/commands/test-build-phase.js +47 -41
  132. package/dist/src/commands/test-checkjs.js +7 -0
  133. package/dist/src/commands/test-diagnose.js +7 -0
  134. package/dist/src/commands/test-modes.d.ts +16 -1
  135. package/dist/src/commands/test-modes.js +24 -1
  136. package/dist/src/commands/test-multi-verdict.d.ts +61 -0
  137. package/dist/src/commands/test-multi-verdict.js +111 -0
  138. package/dist/src/commands/test-orchestrate.d.ts +43 -0
  139. package/dist/src/commands/test-orchestrate.js +222 -0
  140. package/dist/src/commands/test-perf-scope.d.ts +29 -0
  141. package/dist/src/commands/test-perf-scope.js +83 -0
  142. package/dist/src/commands/test-prelock.d.ts +51 -0
  143. package/dist/src/commands/test-prelock.js +177 -0
  144. package/dist/src/commands/test-register.js +54 -10
  145. package/dist/src/commands/test-run.js +4 -1
  146. package/dist/src/commands/test-stale-gate.js +57 -27
  147. package/dist/src/commands/test-verdict.d.ts +10 -1
  148. package/dist/src/commands/test-verdict.js +28 -1
  149. package/dist/src/commands/test.d.ts +6 -1
  150. package/dist/src/commands/test.js +66 -60
  151. package/dist/src/commands/token-lint.d.ts +14 -0
  152. package/dist/src/commands/token-lint.js +111 -0
  153. package/dist/src/commands/token.js +10 -0
  154. package/dist/src/commands/verify-scope.d.ts +28 -0
  155. package/dist/src/commands/verify-scope.js +67 -0
  156. package/dist/src/commands/verify.d.ts +10 -2
  157. package/dist/src/commands/verify.js +59 -35
  158. package/dist/src/core/build-baseline-types.d.ts +46 -0
  159. package/dist/src/core/build-baseline-validate.js +19 -0
  160. package/dist/src/core/build-baseline.d.ts +5 -1
  161. package/dist/src/core/build-baseline.js +3 -1
  162. package/dist/src/core/build-overwrite-guard.d.ts +50 -0
  163. package/dist/src/core/build-overwrite-guard.js +84 -4
  164. package/dist/src/core/build-prepare.d.ts +7 -0
  165. package/dist/src/core/build-prepare.js +14 -8
  166. package/dist/src/core/build-run-record.d.ts +74 -0
  167. package/dist/src/core/build-run-record.js +136 -0
  168. package/dist/src/core/checkjs-support-heads.d.ts +41 -0
  169. package/dist/src/core/checkjs-support-heads.js +104 -0
  170. package/dist/src/core/config-paths.d.ts +2 -2
  171. package/dist/src/core/config-paths.js +8 -0
  172. package/dist/src/core/config-validate-check.d.ts +8 -0
  173. package/dist/src/core/config-validate-check.js +143 -0
  174. package/dist/src/core/config-validate-file-size.d.ts +9 -6
  175. package/dist/src/core/config-validate-file-size.js +91 -2
  176. package/dist/src/core/config-validate-history.d.ts +11 -0
  177. package/dist/src/core/config-validate-history.js +38 -0
  178. package/dist/src/core/config-validate-patch-policy.js +100 -0
  179. package/dist/src/core/config-validate-test-toolchains.js +7 -0
  180. package/dist/src/core/config-validate-token-scales.d.ts +6 -0
  181. package/dist/src/core/config-validate-token-scales.js +50 -0
  182. package/dist/src/core/config-validate.js +14 -5
  183. package/dist/src/core/config.d.ts +5 -0
  184. package/dist/src/core/config.js +16 -0
  185. package/dist/src/core/coverage-dispatch.d.ts +24 -0
  186. package/dist/src/core/coverage-dispatch.js +44 -0
  187. package/dist/src/core/coverage-extend.d.ts +70 -45
  188. package/dist/src/core/coverage-extend.js +199 -144
  189. package/dist/src/core/coverage-records.d.ts +97 -0
  190. package/dist/src/core/coverage-records.js +350 -0
  191. package/dist/src/core/destructive.d.ts +7 -4
  192. package/dist/src/core/destructive.js +10 -10
  193. package/dist/src/core/engine-session-lock.d.ts +10 -1
  194. package/dist/src/core/engine-session-lock.js +28 -8
  195. package/dist/src/core/export-moved-code-shape.d.ts +49 -11
  196. package/dist/src/core/export-moved-code-shape.js +135 -44
  197. package/dist/src/core/file-lock-queue.d.ts +67 -0
  198. package/dist/src/core/file-lock-queue.js +221 -0
  199. package/dist/src/core/file-lock.d.ts +4 -25
  200. package/dist/src/core/file-lock.js +105 -138
  201. package/dist/src/core/furnace-apply-output.d.ts +10 -0
  202. package/dist/src/core/furnace-apply-output.js +31 -1
  203. package/dist/src/core/furnace-apply.js +12 -2
  204. package/dist/src/core/furnace-composed-source.d.ts +76 -0
  205. package/dist/src/core/furnace-composed-source.js +167 -0
  206. package/dist/src/core/furnace-config-validate-exceptions.d.ts +9 -0
  207. package/dist/src/core/furnace-config-validate-exceptions.js +71 -0
  208. package/dist/src/core/furnace-config-validate.js +8 -0
  209. package/dist/src/core/furnace-css-fragments.d.ts +10 -0
  210. package/dist/src/core/furnace-css-fragments.js +26 -0
  211. package/dist/src/core/furnace-operation.d.ts +3 -3
  212. package/dist/src/core/furnace-registration-remove.d.ts +22 -1
  213. package/dist/src/core/furnace-registration-remove.js +53 -17
  214. package/dist/src/core/furnace-registration-rename.d.ts +26 -0
  215. package/dist/src/core/furnace-registration-rename.js +77 -0
  216. package/dist/src/core/furnace-registration.d.ts +14 -4
  217. package/dist/src/core/furnace-registration.js +44 -10
  218. package/dist/src/core/furnace-staleness.d.ts +7 -0
  219. package/dist/src/core/furnace-staleness.js +34 -24
  220. package/dist/src/core/furnace-validate-accessibility.d.ts +4 -2
  221. package/dist/src/core/furnace-validate-accessibility.js +10 -1
  222. package/dist/src/core/furnace-validate-exceptions.d.ts +40 -0
  223. package/dist/src/core/furnace-validate-exceptions.js +43 -0
  224. package/dist/src/core/furnace-validate-icon-button.d.ts +7 -0
  225. package/dist/src/core/furnace-validate-icon-button.js +49 -0
  226. package/dist/src/core/furnace-validate.js +1 -1
  227. package/dist/src/core/harness-port-fallback.d.ts +60 -0
  228. package/dist/src/core/harness-port-fallback.js +96 -0
  229. package/dist/src/core/history-log-file.d.ts +61 -0
  230. package/dist/src/core/history-log-file.js +157 -0
  231. package/dist/src/core/history-log.d.ts +2 -1
  232. package/dist/src/core/history-log.js +2 -1
  233. package/dist/src/core/json-preserving-edit.d.ts +22 -0
  234. package/dist/src/core/json-preserving-edit.js +199 -0
  235. package/dist/src/core/mach.js +8 -1
  236. package/dist/src/core/negative-control-diff.d.ts +39 -0
  237. package/dist/src/core/negative-control-diff.js +136 -0
  238. package/dist/src/core/negative-control.d.ts +138 -0
  239. package/dist/src/core/negative-control.js +356 -0
  240. package/dist/src/core/objdir-install-manifests.d.ts +73 -0
  241. package/dist/src/core/objdir-install-manifests.js +226 -0
  242. package/dist/src/core/patch-export-update.d.ts +2 -0
  243. package/dist/src/core/patch-export-update.js +1 -0
  244. package/dist/src/core/patch-export.js +3 -4
  245. package/dist/src/core/patch-lint-checkjs-any-import.d.ts +37 -0
  246. package/dist/src/core/patch-lint-checkjs-any-import.js +61 -0
  247. package/dist/src/core/patch-lint-checkjs.js +8 -11
  248. package/dist/src/core/patch-lint-code-lines.d.ts +59 -0
  249. package/dist/src/core/patch-lint-code-lines.js +105 -0
  250. package/dist/src/core/patch-lint-cross.js +12 -4
  251. package/dist/src/core/patch-lint-css.d.ts +8 -2
  252. package/dist/src/core/patch-lint-css.js +10 -4
  253. package/dist/src/core/patch-lint-file-size.d.ts +12 -5
  254. package/dist/src/core/patch-lint-file-size.js +22 -11
  255. package/dist/src/core/patch-lint-forward-registration-extended.d.ts +18 -0
  256. package/dist/src/core/patch-lint-forward-registration-extended.js +35 -10
  257. package/dist/src/core/patch-lint-forward-registration.js +10 -0
  258. package/dist/src/core/patch-lint-ignore.d.ts +152 -0
  259. package/dist/src/core/patch-lint-ignore.js +299 -0
  260. package/dist/src/core/patch-lint-size-thresholds.d.ts +52 -0
  261. package/dist/src/core/patch-lint-size-thresholds.js +110 -0
  262. package/dist/src/core/patch-lint-size.d.ts +117 -0
  263. package/dist/src/core/patch-lint-size.js +323 -0
  264. package/dist/src/core/patch-lint-staged-registration.d.ts +18 -1
  265. package/dist/src/core/patch-lint-staged-registration.js +20 -0
  266. package/dist/src/core/patch-lint-token-scale.d.ts +39 -0
  267. package/dist/src/core/patch-lint-token-scale.js +109 -0
  268. package/dist/src/core/patch-lint.d.ts +11 -83
  269. package/dist/src/core/patch-lint.js +64 -286
  270. package/dist/src/core/patch-lock.js +7 -6
  271. package/dist/src/core/patch-manifest-consistency.js +7 -6
  272. package/dist/src/core/patch-manifest-io.d.ts +6 -3
  273. package/dist/src/core/patch-manifest-io.js +19 -8
  274. package/dist/src/core/patch-manifest-validate.js +34 -2
  275. package/dist/src/core/patch-order-legal.d.ts +25 -0
  276. package/dist/src/core/patch-order-legal.js +83 -0
  277. package/dist/src/core/patch-policy-content.d.ts +40 -0
  278. package/dist/src/core/patch-policy-content.js +216 -0
  279. package/dist/src/core/patch-policy.d.ts +16 -3
  280. package/dist/src/core/patch-policy.js +16 -9
  281. package/dist/src/core/patch-retired.d.ts +35 -0
  282. package/dist/src/core/patch-retired.js +54 -0
  283. package/dist/src/core/patch-subset.d.ts +16 -0
  284. package/dist/src/core/patch-subset.js +66 -0
  285. package/dist/src/core/preflight-wait.d.ts +20 -0
  286. package/dist/src/core/preflight-wait.js +38 -0
  287. package/dist/src/core/rebase-from-version.d.ts +46 -0
  288. package/dist/src/core/rebase-from-version.js +71 -0
  289. package/dist/src/core/run-log.d.ts +20 -0
  290. package/dist/src/core/run-log.js +89 -3
  291. package/dist/src/core/run-profile.d.ts +7 -0
  292. package/dist/src/core/run-profile.js +7 -1
  293. package/dist/src/core/smoke-patterns.d.ts +5 -0
  294. package/dist/src/core/smoke-patterns.js +13 -0
  295. package/dist/src/core/test-harness-crash.d.ts +18 -1
  296. package/dist/src/core/test-harness-crash.js +6 -4
  297. package/dist/src/core/test-harness-signature.d.ts +17 -0
  298. package/dist/src/core/test-harness-signature.js +4 -0
  299. package/dist/src/core/test-run-completeness.d.ts +57 -0
  300. package/dist/src/core/test-run-completeness.js +144 -0
  301. package/dist/src/core/test-stale-check.d.ts +27 -1
  302. package/dist/src/core/test-stale-check.js +39 -49
  303. package/dist/src/core/test-stale-exempt.d.ts +40 -0
  304. package/dist/src/core/test-stale-exempt.js +70 -0
  305. package/dist/src/core/token-lint.d.ts +50 -0
  306. package/dist/src/core/token-lint.js +126 -0
  307. package/dist/src/core/tree-guard.js +3 -0
  308. package/dist/src/core/typecheck-shim.d.ts +1 -1
  309. package/dist/src/core/typecheck-shim.js +90 -2
  310. package/dist/src/index.d.ts +8 -3
  311. package/dist/src/index.js +3 -1
  312. package/dist/src/types/commands/index.d.ts +2 -2
  313. package/dist/src/types/commands/options.d.ts +148 -21
  314. package/dist/src/types/commands/patches.d.ts +75 -1
  315. package/dist/src/types/config.d.ts +192 -0
  316. package/dist/src/types/config.js +9 -0
  317. package/dist/src/types/furnace.d.ts +43 -1
  318. package/dist/src/types/furnace.js +7 -0
  319. package/dist/src/types/index.d.ts +1 -1
  320. package/dist/src/utils/options.d.ts +34 -6
  321. package/dist/src/utils/options.js +86 -5
  322. package/dist/src/utils/path-glob.d.ts +15 -0
  323. package/dist/src/utils/path-glob.js +47 -0
  324. package/dist/src/utils/regex.d.ts +15 -3
  325. package/dist/src/utils/regex.js +37 -16
  326. package/docs/configuration.md +282 -9
  327. package/docs/exit-codes.md +18 -1
  328. package/docs/furnace.md +97 -3
  329. package/docs/lifecycle-invariants.md +52 -19
  330. package/docs/machine-output.md +74 -12
  331. package/docs/patch-workflow.md +297 -22
  332. package/docs/run-logs.md +20 -1
  333. package/docs/testing.md +276 -20
  334. package/package.json +1 -1
@@ -0,0 +1,47 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Minimal path globs for policy config: `*` matches within one path segment,
4
+ * `?` one character of a segment, and `**` any number of whole segments
5
+ * (`a/**` is everything under `a/`, `**\/x` is `x` in any directory). A
6
+ * pattern is anchored at both ends against an engine-relative POSIX path;
7
+ * there is no basename-anywhere matching, so `*.toml` matches only top-level
8
+ * files and `**\/*.toml` matches them everywhere.
9
+ *
10
+ * Hand-rolled because the policy surface needs exactly this much and the
11
+ * package keeps its dependency list short.
12
+ */
13
+ import { escapeRegex } from './regex.js';
14
+ /** Compiles one glob to an anchored regular expression. */
15
+ export function globToRegExp(glob) {
16
+ let source = '';
17
+ for (let i = 0; i < glob.length; i++) {
18
+ const char = glob[i] ?? '';
19
+ if (char === '*' && glob[i + 1] === '*') {
20
+ const atSegmentStart = i === 0 || glob[i - 1] === '/';
21
+ const followedBySlash = glob[i + 2] === '/';
22
+ if (atSegmentStart && followedBySlash) {
23
+ // `**/` : zero or more leading directories.
24
+ source += '(?:[^/]+/)*';
25
+ i += 2;
26
+ }
27
+ else {
28
+ source += '.*';
29
+ i += 1;
30
+ }
31
+ }
32
+ else if (char === '*') {
33
+ source += '[^/]*';
34
+ }
35
+ else if (char === '?') {
36
+ source += '[^/]';
37
+ }
38
+ else {
39
+ source += escapeRegex(char);
40
+ }
41
+ }
42
+ return new RegExp(`^${source}$`);
43
+ }
44
+ /** True when `path` matches at least one of `globs`. */
45
+ export function matchesAnyGlob(path, globs) {
46
+ return globs.some((glob) => globToRegExp(glob).test(path));
47
+ }
@@ -16,11 +16,23 @@ export declare function hasRawCssColors(content: string): boolean;
16
16
  * implementation does not model them, so two adjacent slashes inside a regex
17
17
  * (the tail of `/https?:\/\//`) read as a `//` line comment and the rest of
18
18
  * the line is blanked, corrupting whatever lint pass consumes the stripped
19
- * text. Firefox chrome sources contain preprocessor directives (`#ifdef`)
20
- * and other constructs acorn cannot tokenize. Those fall back to the legacy
21
- * regex strip, which handles strings but not regex literals.
19
+ * text. The source is tokenized as a module, then as a script. Firefox
20
+ * chrome sources can contain constructs acorn tokenizes under neither goal.
21
+ * Those fall back to the legacy regex strip, which handles strings but not
22
+ * regex literals.
22
23
  */
23
24
  export declare function stripJsComments(source: string): string;
25
+ /**
26
+ * The tokenizer half of {@link stripJsComments}: the stripped source, or
27
+ * `null` when acorn can tokenize it neither as a module nor as a script.
28
+ *
29
+ * Module first, then script, because chrome subscripts are scripts: a
30
+ * legacy octal escape is a tokenizer error only under the module goal.
31
+ * Callers that must not under-count when tokenizing fails (the code-line
32
+ * size metric) read the `null` and pick their own fallback rather than the
33
+ * legacy regex strip, which can blank code.
34
+ */
35
+ export declare function stripJsCommentsParsed(source: string): string | null;
24
36
  /**
25
37
  * Counts the total number of raw CSS color values (hex, rgb, hsl) in content.
26
38
  */
@@ -32,16 +32,47 @@ export function hasRawCssColors(content) {
32
32
  * implementation does not model them, so two adjacent slashes inside a regex
33
33
  * (the tail of `/https?:\/\//`) read as a `//` line comment and the rest of
34
34
  * the line is blanked, corrupting whatever lint pass consumes the stripped
35
- * text. Firefox chrome sources contain preprocessor directives (`#ifdef`)
36
- * and other constructs acorn cannot tokenize. Those fall back to the legacy
37
- * regex strip, which handles strings but not regex literals.
35
+ * text. The source is tokenized as a module, then as a script. Firefox
36
+ * chrome sources can contain constructs acorn tokenizes under neither goal.
37
+ * Those fall back to the legacy regex strip, which handles strings but not
38
+ * regex literals.
38
39
  */
39
40
  export function stripJsComments(source) {
41
+ return stripJsCommentsParsed(source) ?? stripJsCommentsLegacy(source);
42
+ }
43
+ /**
44
+ * The tokenizer half of {@link stripJsComments}: the stripped source, or
45
+ * `null` when acorn can tokenize it neither as a module nor as a script.
46
+ *
47
+ * Module first, then script, because chrome subscripts are scripts: a
48
+ * legacy octal escape is a tokenizer error only under the module goal.
49
+ * Callers that must not under-count when tokenizing fails (the code-line
50
+ * size metric) read the `null` and pick their own fallback rather than the
51
+ * legacy regex strip, which can blank code.
52
+ */
53
+ export function stripJsCommentsParsed(source) {
54
+ const comments = collectJsComments(source, 'module') ?? collectJsComments(source, 'script');
55
+ if (comments === null)
56
+ return null;
57
+ if (comments.length === 0)
58
+ return source;
59
+ const chars = source.split('');
60
+ for (const { start, end } of comments) {
61
+ for (let i = start; i < end; i++) {
62
+ if (chars[i] !== '\n' && chars[i] !== '\r') {
63
+ chars[i] = ' ';
64
+ }
65
+ }
66
+ }
67
+ return chars.join('');
68
+ }
69
+ /** Comment ranges of `source` under one parse goal, or `null` on a tokenizer error. */
70
+ function collectJsComments(source, sourceType) {
40
71
  const comments = [];
41
72
  try {
42
73
  const t = acornTokenizer(source, {
43
74
  ecmaVersion: 'latest',
44
- sourceType: 'module',
75
+ sourceType,
45
76
  allowHashBang: true,
46
77
  onComment: (_isBlock, _text, start, end) => {
47
78
  comments.push({ start, end });
@@ -55,19 +86,9 @@ export function stripJsComments(source) {
55
86
  }
56
87
  }
57
88
  catch {
58
- return stripJsCommentsLegacy(source);
89
+ return null;
59
90
  }
60
- if (comments.length === 0)
61
- return source;
62
- const chars = source.split('');
63
- for (const { start, end } of comments) {
64
- for (let i = start; i < end; i++) {
65
- if (chars[i] !== '\n' && chars[i] !== '\r') {
66
- chars[i] = ' ';
67
- }
68
- }
69
- }
70
- return chars.join('');
91
+ return comments;
71
92
  }
72
93
  /**
73
94
  * Legacy pure-regex comment strip, used only when acorn cannot tokenize
@@ -20,6 +20,19 @@ which is worth knowing when you revert that file. Running
20
20
  uncommitted pin, and the tree is then building a different version than the
21
21
  config claims.
22
22
 
23
+ `fireforge source set`, `fireforge config <key> <value>` and `furnace init`
24
+ edit `fireforge.json` in place (since 0.50.0). Only the values that change
25
+ are rewritten. Indentation, key order, and objects or arrays kept on one
26
+ line stay as they are, so a pin move is a diff of the `firefox` block alone.
27
+ New keys follow the layout of the object they land in. Before 0.50.0 every
28
+ write re-serialized the whole file and expanded every compact section.
29
+
30
+ `fireforge doctor`'s "Source pin matches engine" check compares the pin with
31
+ the engine's committed `browser/config/version.txt`. That file never carries
32
+ the `esr` suffix, so a trailing `esr` is dropped from the pin for this
33
+ comparison (a `153.4.0esr` pin matches `153.4.0`). The comparison with the
34
+ version the last download recorded keeps the full pin.
35
+
23
36
  ### Archive integrity: `firefox.sha256` and `firefox.allowUnverifiedDownload`
24
37
 
25
38
  Every fresh download is checked against Mozilla's published `SHA256SUMS` for
@@ -76,6 +89,12 @@ been bootstrapped.
76
89
 
77
90
  Per-patch lint type-checks plain Firefox JS against a built-in
78
91
  Firefox-globals shim that tracks upstream WebIDL additions per release.
92
+ Most globals in it are `any`. `Cu` is typed as `CuShim`, the members of
93
+ `nsIXPCComponents_Utils` (`js/xpconnect/idl/xpccomponents.idl`) with loose
94
+ parameter and result types, so a call to a member `Cu` does not have (such
95
+ as `Cu.now()`, which is `ChromeUtils.now()`) fails `typecheck` and per-patch
96
+ lint instead of throwing at runtime (since 0.50.0). The same shim backs
97
+ `fireforge typecheck`.
79
98
 
80
99
  - `patchLint.checkJsExtraShim` adds members to the structured shim globals
81
100
  via TypeScript interface merging, for example
@@ -113,7 +132,11 @@ Firefox-globals shim that tracks upstream WebIDL additions per release.
113
132
  alone: a `%` or `@AB_CD@` substitution, `**`, `!`, a path that leaves the
114
133
  tree, or a chrome URL no queue jar.mn line packages. Each finding prints
115
134
  the same paste-and-run `patch staged-dependency --add --kind registration`
116
- command as the `support-files` arm. The setting is opt-in because a queue
135
+ command as the `support-files` arm, ending in a `--reason '<why …>'`
136
+ placeholder to fill in. Each finding also carries the declaration as
137
+ structured `stagedDeclarations`, so a `lint --per-patch --report` file
138
+ can be applied with one `patch staged-dependency <patch> --add --from
139
+ <report>` per patch instead of one call per edge. The setting is opt-in because a queue
117
140
  that predates it can carry many such edges, and a new error would red a
118
141
  `--max-warnings 0` gate on install. The same census is on the public API as
119
142
  `findForwardRegistrations(patchesDir, { scope })`, which defaults to
@@ -138,7 +161,102 @@ Firefox-globals shim that tracks upstream WebIDL additions per release.
138
161
  `notice <= warning <= error`, which is validated at config load. Under
139
162
  the recommended gate posture `--max-warnings 0`, the `warning` band
140
163
  is a hard failure rather than a soft limit, which is what these thresholds
141
- exist to let you move.
164
+ exist to let you move. A notice is the design target: advisory, and it
165
+ never fails `--max-warnings 0`. Only the error band suggests splitting.
166
+
167
+ - `patchLint.fileSizeMetric` (`'raw'` or `'code'`, default `'raw'`) picks
168
+ what `file-too-large` counts. `'raw'` counts every line, the way `wc -l`
169
+ does. `'code'` blanks comments with acorn's tokenizer (module first, then
170
+ script, since chrome subscripts are scripts) and counts the lines left
171
+ non-blank, so JSDoc and blank lines stop counting. A file acorn cannot
172
+ tokenize is counted in raw lines, the upper bound, and a
173
+ `file-size-unparsed` notice names it. Every message says which unit it
174
+ counted ("lines" or "code lines"). The counter is on the public API as
175
+ `countCodeLines(content)`, next to `resolveFileSizeThresholds` and
176
+ `isTestFile`.
177
+
178
+ - `patchLint.patchSizeThresholds` tunes `large-patch-lines` and
179
+ `large-patch-files` per tier (`general`, `test`, `branding`), each with a
180
+ `lines` and a `files` triple:
181
+
182
+ ```json
183
+ {
184
+ "patchLint": {
185
+ "patchSizeThresholds": {
186
+ "general": {
187
+ "lines": { "notice": 1000, "warning": 2000, "error": 4000 },
188
+ "files": { "notice": 8, "warning": 12, "error": 24 }
189
+ },
190
+ "branding": { "files": { "error": 90 } }
191
+ }
192
+ }
193
+ }
194
+ ```
195
+
196
+ Every field is optional and merges over the defaults, and each merged
197
+ triple has to satisfy `notice <= warning <= error`. The line defaults are
198
+ general `800/1500/3000`, test `1500/3000/6000` and branding
199
+ `8000/18000/30000`. Files warn above 5 (general, test) or 60 (branding).
200
+ The files triple has no notice or error band unless you set one: an unset
201
+ `files.notice` follows `files.warning`, and an unset `files.error` never
202
+ fires. The `branding` tier applies to a patch whose files all sit under
203
+ `browser/branding/` (plus `browser/moz.configure` and
204
+ `browser/confvars.sh`), or one marked `"tier": "branding"` in
205
+ `patches.json` (`re-export --tier branding`). A patch whose files are all
206
+ tests takes the `test` tier, and wins over branding.
207
+ `getPatchSizeThresholds(tier, config)` returns the configured values.
208
+
209
+ - `patchLint.patchSizeMetric` (`'diff'` or `'addedCode'`, default `'diff'`)
210
+ picks what `large-patch-lines` counts. `'diff'` counts every non-binary
211
+ line of the unified diff, headers and context included. `'addedCode'`
212
+ counts only added lines that are neither blank nor comment, judged on
213
+ each file's post-image in `engine/` (a hunk can start inside a block
214
+ comment). Comments are recognised per file kind: JS through acorn, CSS
215
+ block comments, `<!-- -->` in markup, and whole-line `#` comments in
216
+ manifests, `moz.build`, Fluent and similar. In CSS it also skips lines
217
+ inside a Furnace `@fireforge-include` … `@fireforge-end-include`
218
+ expansion: they are a copy of a shared fragment. Under `'addedCode'`, a
219
+ general-tier patch that carries tests counts per class: lines in test
220
+ files against the test tier's `lines`, everything else against the
221
+ general tier's, each with its own finding.
222
+
223
+ - `patchLint.patchSizeRegistrationFiles` lists globs whose files count
224
+ toward neither `large-patch-lines` nor `large-patch-files`, for example
225
+ `["jar.mn", "moz.build", "components.conf", "*.toml", "jsconfig*.json"]`.
226
+ A glob without a `/` matches the basename, one with a `/` the
227
+ engine-relative path; `*` stays within a path segment and `**` crosses
228
+ segments. Default: none.
229
+
230
+ - `patchLint.tokenScales` (default unset) puts property families on a token
231
+ scale. In every CSS file a patch creates, a declaration of a governed
232
+ property must resolve to the scale (`token-scale`, an error):
233
+
234
+ ```json
235
+ {
236
+ "patchLint": {
237
+ "tokenScales": [
238
+ {
239
+ "properties": ["gap", "row-gap", "column-gap", "padding*", "margin*"],
240
+ "prefix": "--mybrowser-space-",
241
+ "allowLiterals": ["0", "auto", "*%", "1px"],
242
+ "allow": ["--mybrowser-page-padding"]
243
+ },
244
+ { "properties": ["z-index"], "prefix": "--mybrowser-z-" }
245
+ ]
246
+ }
247
+ }
248
+ ```
249
+
250
+ `properties` takes exact names or a `*`-suffixed prefix. Each value atom
251
+ must be a `var()` of a `prefix` token or of an `allow` name (exact, or
252
+ `*`-suffixed such as `--moz-*`), a literal matching `allowLiterals` (`*`
253
+ is a wildcard), or a CSS-wide keyword (`inherit`, `initial`, `unset`,
254
+ `revert`, `revert-layer`). A `var()` fallback is held to the same scale,
255
+ so `var(--mybrowser-gap, 7px)` fails on `7px`. `calc()`, `min()`, `max()`
256
+ and `clamp()` are transparent, so a unitless multiplier inside one needs
257
+ an `allowLiterals` entry. Files the patch modifies rather than creates are
258
+ not checked, and a line carrying `fireforge-ignore: token-scale` is
259
+ skipped.
142
260
 
143
261
  ## `typecheck`
144
262
 
@@ -167,6 +285,59 @@ checking; they do not turn constructors or instances into `any`.
167
285
  `patchPolicy.ranges` makes `patch compact` range-aware, so closing ordinal
168
286
  gaps does not move a patch out of the band its category owns.
169
287
 
288
+ With `patchPolicy.allowGaps: false`, orders are contiguous within a range:
289
+ a new patch must fill an existing gap or take the number right after the
290
+ range's last patch. A patch that has to leave the queue without opening a
291
+ gap is removed with `patch retire`, which keeps its order as a retired slot
292
+ (see [`patch-workflow.md`](patch-workflow.md#queue-maintenance)).
293
+
294
+ ### Registration-only rules
295
+
296
+ Four opt-in rules read the patch bodies rather than `patches.json` alone.
297
+ `verify` and `lint --per-patch` report them (as `patch-policy/<code>` in
298
+ lint), at the severity `mutationMode` implies. Mutating commands do not
299
+ evaluate them, because those commands check a projected manifest before
300
+ any body exists.
301
+
302
+ ```json
303
+ {
304
+ "patchPolicy": {
305
+ "ranges": [{ "from": 100, "to": 899, "category": "ui" }],
306
+ "addOnlyMarker": "hominis",
307
+ "upstreamAllowlist": [
308
+ "browser/moz.configure",
309
+ { "path": "**/jar.mn", "addOnly": true },
310
+ { "path": "**/moz.build", "addOnly": true }
311
+ ],
312
+ "scopeRules": [
313
+ { "filenamePattern": "-tests\\.patch$", "files": ["**/test/**", "**/tests/**", "**/*.toml"] }
314
+ ],
315
+ "verifyDocumentPaths": true
316
+ }
317
+ }
318
+ ```
319
+
320
+ - `upstreamAllowlist` names, as path globs, the upstream files a
321
+ non-reserved patch may modify (`upstream-allowlist`). A file is upstream
322
+ when no patch in the queue creates it, so fork-owned files never need an
323
+ entry. A patch in a `reservedRanges` band is exempt: its allowlist entry's
324
+ `files` already governs it. Globs are anchored engine-relative paths: `*`
325
+ stays within a segment, `**` spans segments, so `*.toml` matches only a
326
+ top-level file and `**/*.toml` matches one anywhere.
327
+ - An object entry with `addOnly: true` puts matching files in
328
+ registration-only mode (`add-only`): the patch may only add lines, every
329
+ non-blank added line must contain the marker (the entry's `marker`, else
330
+ `addOnlyMarker`, matched case-insensitively), and deleting the file or
331
+ changing it as binary is refused. When several entries match a file, any
332
+ add-only one makes it add-only.
333
+ - `scopeRules` limits a class of patch to the files its globs name
334
+ (`scope`). A rule selects patches by `category`, `filenamePattern` (a
335
+ regular expression over the patch filename), or both. The example keeps a
336
+ `-tests` patch to test directories and test manifests.
337
+ - `verifyDocumentPaths: true` requires each reserved allowlist entry's `adr`
338
+ and `documentation` path to exist under the project root
339
+ (`reserved-document-missing`).
340
+
170
341
  ## `test`
171
342
 
172
343
  `test.canaryPath` and `test.canaryTimeoutSeconds` supply the defaults for
@@ -178,6 +349,30 @@ objdir. `"reap"` terminates it and stamps `orphans-reaped=<n>` on the verdict
178
349
  line; `--reap-orphans` does the same for one invocation. The objdir anchor
179
350
  rule is the same under both. See [`testing.md`](testing.md).
180
351
 
352
+ `test.mochitestPorts` is `"fixed"` (default) or `"auto"`. `"auto"` lets a
353
+ browser run proceed while the mochitest server or Marionette port is held by
354
+ something that is not this checkout's own, relying on the engine
355
+ harness's own busy-port fallback, so sibling checkouts can run browser suites
356
+ at once. `--auto-ports` does the same for one invocation. See
357
+ [`testing.md`](testing.md#shared-browsers-ports-and-host-state).
358
+
359
+ ## `history`
360
+
361
+ `history.path` is where FireForge appends its patch history log, relative to
362
+ the project root (default `.fireforge/history.jsonl`). The log records
363
+ structural patch operations (staged dependencies, splits, renames, export
364
+ orders, re-exports that change a patch's file list) as one JSON object per
365
+ line. FireForge never reads it back; it is an audit trail only. Each record
366
+ carries `invocationId` (shared by every record one command writes), `pid`,
367
+ `ppid`, and `sessionId` when the environment sets `FIREFORGE_SESSION_ID`, so
368
+ a session sharing the checkout can select its own lines.
369
+
370
+ Before 0.50.0 the log was `patches/.fireforge-history.jsonl`, inside the
371
+ tracked queue. The first append after upgrading moves it to `history.path`;
372
+ when both files exist, the old records are appended to the new file rather
373
+ than either being overwritten. Setting `history.path` to
374
+ `patches/.fireforge-history.jsonl` keeps the old location.
375
+
181
376
  ## `buildAudit`
182
377
 
183
378
  The post-build packaging audit is warn-only. Most of what it would otherwise
@@ -224,6 +419,45 @@ The `Packaged:` summary line names each non-zero skip class with its count,
224
419
  so a run that dismissed four unselected-branding files reads differently from
225
420
  one that dismissed four unregistered sources.
226
421
 
422
+ ## `check`
423
+
424
+ `fireforge check` is the composed static gate. It runs six built-in steps,
425
+ `status`, `verify`, `lint`, `typecheck`, `furnace-validate` and
426
+ `token-coverage`, and then the project's own commands. Every field of the
427
+ `check` block is optional:
428
+
429
+ ```json
430
+ {
431
+ "check": {
432
+ "skip": ["token-coverage"],
433
+ "status": {
434
+ "failOn": ["unmanaged", "patch-owned-drift", "conflict", "ownership-conflict"],
435
+ "allow": ["patch-backed", "branding", "furnace", "binary-unsupported"],
436
+ "failOnUnknown": true
437
+ },
438
+ "maxWarnings": { "lint": 0, "furnace": 0 },
439
+ "extraSteps": [
440
+ { "name": "policy-audit", "command": "npm run audit:policy", "timeout": 600 },
441
+ { "name": "invariants", "command": "node check-invariants.mjs", "cwd": "tools" }
442
+ ]
443
+ }
444
+ }
445
+ ```
446
+
447
+ - `skip` lists the built-in steps this project never runs. `typecheck` is
448
+ also skipped when there is no `typecheck` block, and `furnace-validate`
449
+ when there is no `furnace.json`.
450
+ - `status` is the status step's `--check` policy: `--fail-on`, `--allow` and
451
+ `--fail-on-unknown` (see [`patch-workflow.md`](patch-workflow.md#ci-enforcement)).
452
+ An invalid policy is refused as a config error before any step runs.
453
+ - `maxWarnings` passes `--max-warnings` to `lint --per-patch` and
454
+ `furnace validate`.
455
+ - `extraSteps[]` adds project commands. `name` is lowercase letters, digits
456
+ and dashes and must not repeat a built-in name. `command` runs with
457
+ `/bin/sh -c` (`cmd.exe /c` on Windows) from the project root, or from the
458
+ project-relative `cwd`. `timeout` is in seconds: past it the step's whole
459
+ process tree is killed and the step fails. A step passes when it exits 0.
460
+
227
461
  ## Design tokens
228
462
 
229
463
  Tokens are managed with `fireforge token add`. `fireforge token list` and
@@ -273,6 +507,22 @@ hand-parsing a `= Category =` banner out of the file.
273
507
  Only the base `:root` block owns a token, so `token list` reports it once.
274
508
  The dark and variant blocks mirror it and appear in `token show`.
275
509
 
510
+ ### Checking the token file
511
+
512
+ `fireforge token lint [--json]` exits non-zero on either of two findings:
513
+
514
+ - `token-lockstep`: a themed override block (the dark `@media` mirror, a
515
+ `:root[variant]` block) declares a token with the project's token prefix
516
+ that the base `:root` does not declare. Every other theme would resolve
517
+ it to nothing. Overrides of non-prefixed properties (a `--moz-*` platform
518
+ variable) are left alone.
519
+ - `token-undeclared`: a `var(--<prefix>…)` in the tokens CSS, or in any CSS
520
+ file a patch in the queue touches, names a token no block of the tokens
521
+ CSS declares. A property declared in the same file, and the `furnace.json`
522
+ `tokenAllowlist` and `runtimeVariables` names, are exempt.
523
+
524
+ The prefix is `furnace.json` `tokenPrefix`, else `--<binaryName>-`.
525
+
276
526
  ## Smoke runs
277
527
 
278
528
  For unattended smoke checks, `fireforge run --smoke-exit <s> --headless`
@@ -297,10 +547,24 @@ up after itself, so the next `run` removes any temporary profile whose
297
547
  owning process is gone.
298
548
 
299
549
  - `--profile <path>` launches with a named profile directory and leaves it
300
- in place. Under `--smoke-exit` it replaces the fresh default.
550
+ in place. Under `--smoke-exit` it replaces the fresh default. Firefox also
551
+ gets `-purgecaches` (since 0.50.0), which discards the profile's startup
552
+ cache of compiled chrome scripts. That cache is keyed on the build id, and
553
+ a `build --ui` does not change it, so without the purge a kept profile runs
554
+ the chrome scripts an earlier build cached. `--no-purge-caches` keeps the
555
+ cache. mach's `profile-default` has its `startupCache/` removed before
556
+ every run, and a temporary profile starts without one.
301
557
  - `--temp-profile` gives a plain `run` the same fresh, removed-afterwards
302
558
  profile.
303
559
 
560
+ A smoke run whose browser exits with a clean status before the window
561
+ elapses, without printing any console output, fails with exit 13 (since
562
+ 0.50.0). Lines from mach's own log (the timestamped command echo) do not
563
+ count as output. A launch that exits silently never reached a window: a
564
+ stale `.parentlock`, or a profile whose run of SIGTERM-ended smoke runs
565
+ counts as a startup-crash streak (`toolkit.startup.recent_crashes`). Before
566
+ 0.50.0 such a run passed as a clean exit.
567
+
304
568
  A plain `run` without either flag keeps mach's `profile-default`. `mach run`'s
305
569
  desktop parser has no `--profile` option (its `--profile/-P` belongs to the
306
570
  Android parser), so the profile is always passed as a program parameter.
@@ -324,18 +588,25 @@ after about a second by default.
324
588
  `--wait-lock [seconds]` (bare flag means 60, valid range 1 to 3600) replaces
325
589
  that with a bounded wait, and the budget covers every lock the command takes.
326
590
  For `furnace apply`, `deploy` and `sync` that means the furnace lock as well
327
- as the engine-session lock. While waiting, a progress line roughly every 5
328
- seconds identifies the holder by PID, command and start time.
591
+ as the engine-session lock. The furnace leaves that take only the furnace lock
592
+ (`create`, `remove`, `rename`, `refresh`, `scan`, `override`, `preview`, and
593
+ `chrome-doc create`/`remove`) apply the flag to that lock. While waiting, a
594
+ progress line roughly every 5 seconds identifies the holder by PID, command
595
+ and start time.
329
596
 
330
597
  Commands that take no lock accept `--wait-lock` and ignore it, so a scripted
331
598
  sequence can append the flag everywhere without a usage error killing the
332
599
  run.
333
600
 
334
601
  `FIREFORGE_WAIT_LOCK=<seconds>` applies the same budget to every command in a
335
- session, using the same bounds as the flag. It is the only way to give a
336
- budget to the furnace mutators that declare no flag (`create`, `remove`,
337
- `rename`, `refresh`, `scan`, `override`, `chrome-doc`, and the rest). An
338
- explicit `--wait-lock` always wins.
602
+ session, using the same bounds as the flag. It is the fallback whenever the
603
+ flag is absent, including for the furnace lock a build's pre-build apply and
604
+ `doctor --repair-furnace` take. An explicit `--wait-lock` always wins.
605
+
606
+ Waiters are granted a lock in arrival order: the progress line's "You are
607
+ next" or "N ahead of you" is the order the lock will actually be handed out.
608
+ A waiter that crashed, was killed, or is stopped drops out of the queue
609
+ instead of blocking it.
339
610
 
340
611
  `fireforge status --lock` reports the current holder and queue depth.
341
612
 
@@ -344,6 +615,8 @@ explicit `--wait-lock` always wins.
344
615
  | Variable | Effect |
345
616
  | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
346
617
  | `FIREFORGE_WAIT_LOCK` | Session-wide lock wait budget in seconds (1 to 3600). See above. |
618
+ | `FIREFORGE_SESSION_ID` | Recorded as `sessionId` on every patch history log record this process writes. See `history` above. |
619
+ | `FIREFORGE_LOG_KEEP` | Also write every `test`/`build` run log here, outside retention (a directory collects one file per run). See `run-logs.md`. |
347
620
  | `FIREFORGE_MAX_UNTRACKED_FILES` | Per-directory cap when expanding collapsed untracked directories in `status` (default 5000). A non-positive value warns and is ignored. |
348
621
  | `FIREFORGE_GIT_ADD_TIMEOUT_MS` | Timeout for the monolithic `git add -A` baseline pass (default 10 min). For slow or loaded filesystems. |
349
622
  | `FIREFORGE_GIT_ADD_CHUNK_TIMEOUT_MS` | Timeout for the chunked `git add -- <dir>` fallback (default 30 min). Paired with the above. |
@@ -22,7 +22,7 @@ which error class produces each one.
22
22
  | 10 | `RESOLUTION_ERROR` | Conflict resolution failed. | `ResolutionError` |
23
23
  | 11 | `INTERNAL_ERROR` | A FireForge invariant did not hold. This is a bug in FireForge, not something the operator can fix. | `InternalInvariantError` |
24
24
  | 12 | `SMOKE_EXIT_FAILURE` | `run --smoke-exit` saw console errors that were not allowed in the smoke window, or an allowlisted shape fired past its `# max-hits` ceiling. | `SmokeRunError` |
25
- | 13 | `SMOKE_LAUNCH_FAILURE` | `run --smoke-exit` saw the browser exit uncleanly before the window elapsed. | `SmokeRunError` |
25
+ | 13 | `SMOKE_LAUNCH_FAILURE` | `run --smoke-exit` saw the browser exit uncleanly before the window elapsed, or exit cleanly before it without any console output. | `SmokeRunError` |
26
26
  | 14 | `INCONCLUSIVE` | A test run's verdict was thrown away because `engine/` moved while the harness ran. This is not red. Re-run it. | `InconclusiveVerdictError` |
27
27
  | 15 | `LOCK_TIMEOUT` | A lock wait expired, so the run never started. Re-queue it with a larger `--wait-lock`. | `LockContentionError` |
28
28
  | 130 | `USER_CANCELLED` | The operator interrupted a prompt (Esc or Ctrl+C). 128 + SIGINT. | `CancellationError` |
@@ -75,6 +75,23 @@ not tell them apart. They are now distinct: an interrupt raises
75
75
  normally and exits 0, because the run did what it was asked and the answer
76
76
  was no.
77
77
 
78
+ ## `fireforge check`
79
+
80
+ The composed gate maps its verdict onto the existing codes rather than
81
+ adding one:
82
+
83
+ | Exit | When |
84
+ | ---: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
85
+ | 0 | Every step passed or was skipped. |
86
+ | 1 | At least one step failed (`reason=step-failures`). Each step's own exit code is in the `--json` document's `steps[].exitCode`. Also a `--patches` name that matches no patch, before any step runs. |
87
+ | 15 | Every failed step exited 15 (`reason=lock-timeout`): nothing was judged red, the steps never got their locks. Re-queue it. |
88
+ | 2 | `fireforge.json` is invalid, including a `check.status` policy `status` would refuse. Raised before any step runs. |
89
+ | 8 | An unknown `--only`/`--skip` step, or a bad `--jobs`. Raised before any step runs. |
90
+
91
+ `furnace validate` exits 9 (`FURNACE_ERROR`) both for errors and for
92
+ warnings past `--max-warnings`, so a gate that wants the distinction reads
93
+ `summary.errors` from its `--json` document.
94
+
78
95
  ## Adding a code
79
96
 
80
97
  New codes go in `src/errors/codes.ts` with a comment saying what a consumer