@hominis/fireforge 0.49.2 → 0.51.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 (400) hide show
  1. package/CHANGELOG.md +87 -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 +41 -0
  10. package/dist/src/commands/check-report.js +91 -0
  11. package/dist/src/commands/check-run.d.ts +33 -0
  12. package/dist/src/commands/check-run.js +112 -0
  13. package/dist/src/commands/check-steps.d.ts +75 -0
  14. package/dist/src/commands/check-steps.js +200 -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/declarations-report.d.ts +43 -0
  19. package/dist/src/commands/declarations-report.js +76 -0
  20. package/dist/src/commands/doctor-engine-eslint.js +6 -1
  21. package/dist/src/commands/doctor-source-pin.js +4 -1
  22. package/dist/src/commands/export-adopt-check.d.ts +38 -0
  23. package/dist/src/commands/export-adopt-check.js +98 -0
  24. package/dist/src/commands/export-flow.js +29 -14
  25. package/dist/src/commands/export-placement-gate.d.ts +6 -2
  26. package/dist/src/commands/export-placement-gate.js +8 -0
  27. package/dist/src/commands/export-shared.d.ts +2 -1
  28. package/dist/src/commands/export-staged-registration.d.ts +46 -0
  29. package/dist/src/commands/export-staged-registration.js +91 -0
  30. package/dist/src/commands/forward-import-remedy.d.ts +32 -0
  31. package/dist/src/commands/forward-import-remedy.js +63 -0
  32. package/dist/src/commands/furnace/apply.js +3 -3
  33. package/dist/src/commands/furnace/chrome-doc-remove.d.ts +2 -1
  34. package/dist/src/commands/furnace/chrome-doc-remove.js +2 -2
  35. package/dist/src/commands/furnace/chrome-doc.d.ts +2 -1
  36. package/dist/src/commands/furnace/chrome-doc.js +2 -2
  37. package/dist/src/commands/furnace/create.js +2 -2
  38. package/dist/src/commands/furnace/deploy-named.d.ts +48 -0
  39. package/dist/src/commands/furnace/deploy-named.js +107 -0
  40. package/dist/src/commands/furnace/deploy.d.ts +3 -2
  41. package/dist/src/commands/furnace/deploy.js +50 -86
  42. package/dist/src/commands/furnace/diff.js +26 -3
  43. package/dist/src/commands/furnace/index.js +66 -41
  44. package/dist/src/commands/furnace/init.js +13 -6
  45. package/dist/src/commands/furnace/override.js +4 -2
  46. package/dist/src/commands/furnace/preview.js +4 -3
  47. package/dist/src/commands/furnace/refresh.js +2 -2
  48. package/dist/src/commands/furnace/remove.js +4 -3
  49. package/dist/src/commands/furnace/rename-dry-run.d.ts +29 -0
  50. package/dist/src/commands/furnace/rename-dry-run.js +127 -0
  51. package/dist/src/commands/furnace/rename-helpers.d.ts +7 -0
  52. package/dist/src/commands/furnace/rename-helpers.js +15 -1
  53. package/dist/src/commands/furnace/rename-registrations.d.ts +41 -0
  54. package/dist/src/commands/furnace/rename-registrations.js +98 -0
  55. package/dist/src/commands/furnace/rename.d.ts +2 -1
  56. package/dist/src/commands/furnace/rename.js +28 -66
  57. package/dist/src/commands/furnace/scan.d.ts +9 -4
  58. package/dist/src/commands/furnace/scan.js +11 -9
  59. package/dist/src/commands/furnace/sync.js +3 -2
  60. package/dist/src/commands/furnace/validate-report.d.ts +45 -0
  61. package/dist/src/commands/furnace/validate-report.js +91 -0
  62. package/dist/src/commands/furnace/validate.d.ts +1 -1
  63. package/dist/src/commands/furnace/validate.js +151 -113
  64. package/dist/src/commands/furnace/validation-output.js +14 -3
  65. package/dist/src/commands/lint-per-patch.js +36 -61
  66. package/dist/src/commands/lint-per-run-checkjs.js +2 -2
  67. package/dist/src/commands/lint-report.d.ts +8 -4
  68. package/dist/src/commands/lint-report.js +20 -5
  69. package/dist/src/commands/lint.js +15 -34
  70. package/dist/src/commands/manifest.js +4 -0
  71. package/dist/src/commands/park.d.ts +22 -0
  72. package/dist/src/commands/park.js +232 -0
  73. package/dist/src/commands/patch/delete-gap-notice.js +3 -1
  74. package/dist/src/commands/patch/delete.d.ts +1 -1
  75. package/dist/src/commands/patch/delete.js +24 -104
  76. package/dist/src/commands/patch/index.d.ts +3 -3
  77. package/dist/src/commands/patch/index.js +10 -4
  78. package/dist/src/commands/patch/lint-ignore-from.d.ts +44 -0
  79. package/dist/src/commands/patch/lint-ignore-from.js +142 -0
  80. package/dist/src/commands/patch/lint-ignore.d.ts +5 -1
  81. package/dist/src/commands/patch/lint-ignore.js +78 -31
  82. package/dist/src/commands/patch/move-files-create.js +34 -18
  83. package/dist/src/commands/patch/move-files-into.js +15 -7
  84. package/dist/src/commands/patch/move-files.js +6 -2
  85. package/dist/src/commands/patch/removal-created-files.d.ts +48 -0
  86. package/dist/src/commands/patch/removal-created-files.js +83 -0
  87. package/dist/src/commands/patch/removal-dependents.d.ts +63 -0
  88. package/dist/src/commands/patch/removal-dependents.js +133 -0
  89. package/dist/src/commands/patch/rename.js +20 -1
  90. package/dist/src/commands/patch/reserve.d.ts +33 -0
  91. package/dist/src/commands/patch/reserve.js +152 -0
  92. package/dist/src/commands/patch/retire.d.ts +34 -0
  93. package/dist/src/commands/patch/retire.js +133 -0
  94. package/dist/src/commands/patch/split-plan.d.ts +17 -8
  95. package/dist/src/commands/patch/split-plan.js +60 -28
  96. package/dist/src/commands/patch/split-staged.d.ts +108 -0
  97. package/dist/src/commands/patch/split-staged.js +202 -0
  98. package/dist/src/commands/patch/split.d.ts +5 -0
  99. package/dist/src/commands/patch/split.js +62 -17
  100. package/dist/src/commands/patch/stage.d.ts +34 -0
  101. package/dist/src/commands/patch/stage.js +153 -0
  102. package/dist/src/commands/patch/staged-dependency-batch.d.ts +48 -0
  103. package/dist/src/commands/patch/staged-dependency-batch.js +176 -0
  104. package/dist/src/commands/patch/staged-dependency-entries.d.ts +36 -0
  105. package/dist/src/commands/patch/staged-dependency-entries.js +52 -0
  106. package/dist/src/commands/patch/staged-dependency-from.d.ts +14 -0
  107. package/dist/src/commands/patch/staged-dependency-from.js +108 -0
  108. package/dist/src/commands/patch/staged-dependency-remove.d.ts +31 -0
  109. package/dist/src/commands/patch/staged-dependency-remove.js +80 -0
  110. package/dist/src/commands/patch/staged-dependency.js +54 -139
  111. package/dist/src/commands/re-export-adjacent-registrations.d.ts +20 -0
  112. package/dist/src/commands/re-export-adjacent-registrations.js +75 -0
  113. package/dist/src/commands/re-export-adjacent.d.ts +50 -2
  114. package/dist/src/commands/re-export-adjacent.js +160 -40
  115. package/dist/src/commands/re-export-bulk-scan.d.ts +2 -1
  116. package/dist/src/commands/re-export-bulk-scan.js +22 -7
  117. package/dist/src/commands/re-export-drift.d.ts +36 -8
  118. package/dist/src/commands/re-export-drift.js +73 -49
  119. package/dist/src/commands/re-export-files.js +11 -8
  120. package/dist/src/commands/re-export-hunks.d.ts +35 -0
  121. package/dist/src/commands/re-export-hunks.js +220 -0
  122. package/dist/src/commands/re-export-lint.d.ts +2 -1
  123. package/dist/src/commands/re-export-lint.js +23 -10
  124. package/dist/src/commands/re-export-options.js +74 -6
  125. package/dist/src/commands/re-export-preflight.d.ts +23 -0
  126. package/dist/src/commands/re-export-preflight.js +160 -0
  127. package/dist/src/commands/re-export-purity-scope.d.ts +18 -0
  128. package/dist/src/commands/re-export-purity-scope.js +131 -0
  129. package/dist/src/commands/re-export-register.js +56 -17
  130. package/dist/src/commands/re-export-removed.d.ts +13 -2
  131. package/dist/src/commands/re-export-removed.js +57 -12
  132. package/dist/src/commands/re-export-scan-owner.d.ts +44 -0
  133. package/dist/src/commands/re-export-scan-owner.js +116 -0
  134. package/dist/src/commands/re-export-scan.d.ts +19 -9
  135. package/dist/src/commands/re-export-scan.js +60 -24
  136. package/dist/src/commands/re-export-single.js +122 -46
  137. package/dist/src/commands/re-export-stale-registrations.d.ts +18 -0
  138. package/dist/src/commands/re-export-stale-registrations.js +62 -0
  139. package/dist/src/commands/re-export-summary.d.ts +28 -0
  140. package/dist/src/commands/re-export-summary.js +36 -0
  141. package/dist/src/commands/re-export.js +71 -57
  142. package/dist/src/commands/rebase/index.js +17 -17
  143. package/dist/src/commands/run.js +16 -2
  144. package/dist/src/commands/status-check.d.ts +55 -16
  145. package/dist/src/commands/status-check.js +92 -37
  146. package/dist/src/commands/status-json.d.ts +22 -9
  147. package/dist/src/commands/status-json.js +47 -32
  148. package/dist/src/commands/status-output.js +3 -1
  149. package/dist/src/commands/status-scope.d.ts +37 -0
  150. package/dist/src/commands/status-scope.js +54 -0
  151. package/dist/src/commands/status.js +66 -23
  152. package/dist/src/commands/test-browser-preflight.js +19 -3
  153. package/dist/src/commands/test-build-furnace.d.ts +22 -0
  154. package/dist/src/commands/test-build-furnace.js +54 -0
  155. package/dist/src/commands/test-build-phase.js +64 -41
  156. package/dist/src/commands/test-checkjs.js +10 -2
  157. package/dist/src/commands/test-diagnose.js +7 -0
  158. package/dist/src/commands/test-dispatch.d.ts +39 -0
  159. package/dist/src/commands/test-dispatch.js +211 -0
  160. package/dist/src/commands/test-furnace-reach.d.ts +37 -0
  161. package/dist/src/commands/test-furnace-reach.js +132 -0
  162. package/dist/src/commands/test-modes.d.ts +16 -1
  163. package/dist/src/commands/test-modes.js +24 -1
  164. package/dist/src/commands/test-multi-verdict.d.ts +61 -0
  165. package/dist/src/commands/test-multi-verdict.js +111 -0
  166. package/dist/src/commands/test-orchestrate.d.ts +52 -0
  167. package/dist/src/commands/test-orchestrate.js +240 -0
  168. package/dist/src/commands/test-perf-scope.d.ts +50 -0
  169. package/dist/src/commands/test-perf-scope.js +138 -0
  170. package/dist/src/commands/test-prelock.d.ts +51 -0
  171. package/dist/src/commands/test-prelock.js +273 -0
  172. package/dist/src/commands/test-register.js +69 -10
  173. package/dist/src/commands/test-results.d.ts +62 -0
  174. package/dist/src/commands/test-results.js +62 -0
  175. package/dist/src/commands/test-run.d.ts +13 -55
  176. package/dist/src/commands/test-run.js +74 -99
  177. package/dist/src/commands/test-shards.d.ts +110 -0
  178. package/dist/src/commands/test-shards.js +255 -0
  179. package/dist/src/commands/test-stale-gate.d.ts +5 -1
  180. package/dist/src/commands/test-stale-gate.js +60 -28
  181. package/dist/src/commands/test-verdict.d.ts +11 -1
  182. package/dist/src/commands/test-verdict.js +28 -1
  183. package/dist/src/commands/test.d.ts +6 -1
  184. package/dist/src/commands/test.js +120 -124
  185. package/dist/src/commands/token-lint.d.ts +14 -0
  186. package/dist/src/commands/token-lint.js +151 -0
  187. package/dist/src/commands/token.js +10 -0
  188. package/dist/src/commands/verify-scope.d.ts +28 -0
  189. package/dist/src/commands/verify-scope.js +67 -0
  190. package/dist/src/commands/verify.d.ts +10 -2
  191. package/dist/src/commands/verify.js +67 -36
  192. package/dist/src/core/build-baseline-types.d.ts +46 -0
  193. package/dist/src/core/build-baseline-validate.js +19 -0
  194. package/dist/src/core/build-baseline.d.ts +5 -1
  195. package/dist/src/core/build-baseline.js +3 -1
  196. package/dist/src/core/build-overwrite-guard.d.ts +50 -0
  197. package/dist/src/core/build-overwrite-guard.js +84 -4
  198. package/dist/src/core/build-prepare.d.ts +13 -0
  199. package/dist/src/core/build-prepare.js +42 -10
  200. package/dist/src/core/build-run-record.d.ts +74 -0
  201. package/dist/src/core/build-run-record.js +136 -0
  202. package/dist/src/core/checkjs-support-heads.d.ts +41 -0
  203. package/dist/src/core/checkjs-support-heads.js +104 -0
  204. package/dist/src/core/config-paths.d.ts +2 -2
  205. package/dist/src/core/config-paths.js +10 -0
  206. package/dist/src/core/config-validate-check.d.ts +8 -0
  207. package/dist/src/core/config-validate-check.js +219 -0
  208. package/dist/src/core/config-validate-file-size.d.ts +9 -6
  209. package/dist/src/core/config-validate-file-size.js +91 -2
  210. package/dist/src/core/config-validate-history.d.ts +11 -0
  211. package/dist/src/core/config-validate-history.js +38 -0
  212. package/dist/src/core/config-validate-patch-policy.js +100 -0
  213. package/dist/src/core/config-validate-test-toolchains.js +21 -0
  214. package/dist/src/core/config-validate-token-scales.d.ts +9 -0
  215. package/dist/src/core/config-validate-token-scales.js +92 -0
  216. package/dist/src/core/config-validate.js +20 -5
  217. package/dist/src/core/config.d.ts +5 -0
  218. package/dist/src/core/config.js +16 -0
  219. package/dist/src/core/coverage-dispatch.d.ts +24 -0
  220. package/dist/src/core/coverage-dispatch.js +44 -0
  221. package/dist/src/core/coverage-extend.d.ts +70 -45
  222. package/dist/src/core/coverage-extend.js +199 -144
  223. package/dist/src/core/coverage-records.d.ts +108 -0
  224. package/dist/src/core/coverage-records.js +384 -0
  225. package/dist/src/core/destructive.d.ts +7 -4
  226. package/dist/src/core/destructive.js +10 -10
  227. package/dist/src/core/engine-session-lock.d.ts +16 -1
  228. package/dist/src/core/engine-session-lock.js +38 -10
  229. package/dist/src/core/export-moved-code-shape.d.ts +49 -11
  230. package/dist/src/core/export-moved-code-shape.js +135 -44
  231. package/dist/src/core/file-lock-queue.d.ts +67 -0
  232. package/dist/src/core/file-lock-queue.js +221 -0
  233. package/dist/src/core/file-lock-shared.d.ts +57 -0
  234. package/dist/src/core/file-lock-shared.js +117 -0
  235. package/dist/src/core/file-lock.d.ts +13 -25
  236. package/dist/src/core/file-lock.js +163 -140
  237. package/dist/src/core/furnace-apply-output.d.ts +10 -0
  238. package/dist/src/core/furnace-apply-output.js +31 -1
  239. package/dist/src/core/furnace-apply.js +22 -12
  240. package/dist/src/core/furnace-composed-source.d.ts +76 -0
  241. package/dist/src/core/furnace-composed-source.js +167 -0
  242. package/dist/src/core/furnace-config-validate-exceptions.d.ts +16 -0
  243. package/dist/src/core/furnace-config-validate-exceptions.js +102 -0
  244. package/dist/src/core/furnace-config-validate.js +14 -0
  245. package/dist/src/core/furnace-css-fragments.d.ts +10 -0
  246. package/dist/src/core/furnace-css-fragments.js +26 -0
  247. package/dist/src/core/furnace-operation.d.ts +3 -3
  248. package/dist/src/core/furnace-registration-jar-inplace.d.ts +12 -0
  249. package/dist/src/core/furnace-registration-jar-inplace.js +80 -0
  250. package/dist/src/core/furnace-registration-remove.d.ts +22 -1
  251. package/dist/src/core/furnace-registration-remove.js +53 -17
  252. package/dist/src/core/furnace-registration-rename.d.ts +26 -0
  253. package/dist/src/core/furnace-registration-rename.js +77 -0
  254. package/dist/src/core/furnace-registration.d.ts +14 -4
  255. package/dist/src/core/furnace-registration.js +44 -10
  256. package/dist/src/core/furnace-staleness.d.ts +7 -0
  257. package/dist/src/core/furnace-staleness.js +34 -24
  258. package/dist/src/core/furnace-validate-accessibility.d.ts +8 -4
  259. package/dist/src/core/furnace-validate-accessibility.js +71 -28
  260. package/dist/src/core/furnace-validate-exceptions.d.ts +48 -0
  261. package/dist/src/core/furnace-validate-exceptions.js +80 -0
  262. package/dist/src/core/furnace-validate-icon-button.d.ts +7 -0
  263. package/dist/src/core/furnace-validate-icon-button.js +78 -0
  264. package/dist/src/core/furnace-validate.js +1 -1
  265. package/dist/src/core/harness-port-fallback.d.ts +60 -0
  266. package/dist/src/core/harness-port-fallback.js +96 -0
  267. package/dist/src/core/history-log-file.d.ts +61 -0
  268. package/dist/src/core/history-log-file.js +164 -0
  269. package/dist/src/core/history-log.d.ts +2 -1
  270. package/dist/src/core/history-log.js +2 -1
  271. package/dist/src/core/json-preserving-edit.d.ts +22 -0
  272. package/dist/src/core/json-preserving-edit.js +199 -0
  273. package/dist/src/core/line-prefixer.d.ts +18 -0
  274. package/dist/src/core/line-prefixer.js +34 -0
  275. package/dist/src/core/mach.d.ts +7 -0
  276. package/dist/src/core/mach.js +32 -10
  277. package/dist/src/core/module-resolution-preflight.d.ts +19 -3
  278. package/dist/src/core/module-resolution-preflight.js +24 -6
  279. package/dist/src/core/negative-control-compiled.d.ts +27 -0
  280. package/dist/src/core/negative-control-compiled.js +73 -0
  281. package/dist/src/core/negative-control-diff.d.ts +51 -0
  282. package/dist/src/core/negative-control-diff.js +179 -0
  283. package/dist/src/core/negative-control.d.ts +150 -0
  284. package/dist/src/core/negative-control.js +399 -0
  285. package/dist/src/core/objdir-install-manifests.d.ts +73 -0
  286. package/dist/src/core/objdir-install-manifests.js +226 -0
  287. package/dist/src/core/park-store.d.ts +63 -0
  288. package/dist/src/core/park-store.js +216 -0
  289. package/dist/src/core/patch-export-update.d.ts +6 -0
  290. package/dist/src/core/patch-export-update.js +13 -3
  291. package/dist/src/core/patch-export.d.ts +3 -2
  292. package/dist/src/core/patch-export.js +22 -16
  293. package/dist/src/core/patch-files.js +26 -8
  294. package/dist/src/core/patch-lint-checkjs-any-import.d.ts +39 -0
  295. package/dist/src/core/patch-lint-checkjs-any-import.js +63 -0
  296. package/dist/src/core/patch-lint-checkjs-diagnostics.d.ts +17 -0
  297. package/dist/src/core/patch-lint-checkjs-diagnostics.js +32 -0
  298. package/dist/src/core/patch-lint-checkjs.d.ts +10 -1
  299. package/dist/src/core/patch-lint-checkjs.js +20 -13
  300. package/dist/src/core/patch-lint-code-lines.d.ts +59 -0
  301. package/dist/src/core/patch-lint-code-lines.js +105 -0
  302. package/dist/src/core/patch-lint-cross.d.ts +3 -1
  303. package/dist/src/core/patch-lint-cross.js +17 -4
  304. package/dist/src/core/patch-lint-css.d.ts +8 -2
  305. package/dist/src/core/patch-lint-css.js +24 -14
  306. package/dist/src/core/patch-lint-file-size.d.ts +12 -5
  307. package/dist/src/core/patch-lint-file-size.js +22 -11
  308. package/dist/src/core/patch-lint-forward-registration-extended.d.ts +23 -0
  309. package/dist/src/core/patch-lint-forward-registration-extended.js +36 -11
  310. package/dist/src/core/patch-lint-forward-registration.js +10 -0
  311. package/dist/src/core/patch-lint-ignore.d.ts +196 -0
  312. package/dist/src/core/patch-lint-ignore.js +429 -0
  313. package/dist/src/core/patch-lint-ownership.d.ts +10 -0
  314. package/dist/src/core/patch-lint-ownership.js +26 -0
  315. package/dist/src/core/patch-lint-size-thresholds.d.ts +52 -0
  316. package/dist/src/core/patch-lint-size-thresholds.js +110 -0
  317. package/dist/src/core/patch-lint-size.d.ts +117 -0
  318. package/dist/src/core/patch-lint-size.js +331 -0
  319. package/dist/src/core/patch-lint-staged-registration.d.ts +18 -1
  320. package/dist/src/core/patch-lint-staged-registration.js +20 -0
  321. package/dist/src/core/patch-lint-token-scale.d.ts +57 -0
  322. package/dist/src/core/patch-lint-token-scale.js +286 -0
  323. package/dist/src/core/patch-lint.d.ts +11 -83
  324. package/dist/src/core/patch-lint.js +66 -288
  325. package/dist/src/core/patch-lock.js +7 -6
  326. package/dist/src/core/patch-manifest-consistency.js +7 -6
  327. package/dist/src/core/patch-manifest-io.d.ts +6 -3
  328. package/dist/src/core/patch-manifest-io.js +27 -9
  329. package/dist/src/core/patch-manifest-key-order.d.ts +24 -0
  330. package/dist/src/core/patch-manifest-key-order.js +61 -0
  331. package/dist/src/core/patch-manifest-stage.d.ts +46 -0
  332. package/dist/src/core/patch-manifest-stage.js +96 -0
  333. package/dist/src/core/patch-manifest-validate.js +61 -3
  334. package/dist/src/core/patch-order-legal.d.ts +26 -0
  335. package/dist/src/core/patch-order-legal.js +88 -0
  336. package/dist/src/core/patch-policy-content.d.ts +40 -0
  337. package/dist/src/core/patch-policy-content.js +216 -0
  338. package/dist/src/core/patch-policy.d.ts +22 -5
  339. package/dist/src/core/patch-policy.js +26 -12
  340. package/dist/src/core/patch-retired.d.ts +57 -0
  341. package/dist/src/core/patch-retired.js +92 -0
  342. package/dist/src/core/patch-subset.d.ts +16 -0
  343. package/dist/src/core/patch-subset.js +66 -0
  344. package/dist/src/core/preflight-wait.d.ts +20 -0
  345. package/dist/src/core/preflight-wait.js +38 -0
  346. package/dist/src/core/rebase-from-version.d.ts +46 -0
  347. package/dist/src/core/rebase-from-version.js +71 -0
  348. package/dist/src/core/run-log.d.ts +26 -0
  349. package/dist/src/core/run-log.js +106 -4
  350. package/dist/src/core/run-profile.d.ts +7 -0
  351. package/dist/src/core/run-profile.js +7 -1
  352. package/dist/src/core/smoke-patterns.d.ts +5 -0
  353. package/dist/src/core/smoke-patterns.js +13 -0
  354. package/dist/src/core/test-harness-crash.d.ts +19 -1
  355. package/dist/src/core/test-harness-crash.js +8 -9
  356. package/dist/src/core/test-harness-signature.d.ts +33 -0
  357. package/dist/src/core/test-harness-signature.js +23 -1
  358. package/dist/src/core/test-raw-log.d.ts +66 -0
  359. package/dist/src/core/test-raw-log.js +122 -0
  360. package/dist/src/core/test-run-completeness.d.ts +90 -0
  361. package/dist/src/core/test-run-completeness.js +184 -0
  362. package/dist/src/core/test-stale-check.d.ts +27 -1
  363. package/dist/src/core/test-stale-check.js +39 -49
  364. package/dist/src/core/test-stale-exempt.d.ts +40 -0
  365. package/dist/src/core/test-stale-exempt.js +70 -0
  366. package/dist/src/core/test-xpcshell-retry.d.ts +2 -0
  367. package/dist/src/core/test-xpcshell-retry.js +2 -1
  368. package/dist/src/core/token-lint.d.ts +53 -0
  369. package/dist/src/core/token-lint.js +131 -0
  370. package/dist/src/core/token-sibling-sheets.d.ts +46 -0
  371. package/dist/src/core/token-sibling-sheets.js +119 -0
  372. package/dist/src/core/tree-guard.js +5 -0
  373. package/dist/src/core/typecheck-shim.d.ts +6 -2
  374. package/dist/src/core/typecheck-shim.js +205 -4
  375. package/dist/src/core/typecheck.js +3 -1
  376. package/dist/src/index.d.ts +14 -3
  377. package/dist/src/index.js +6 -1
  378. package/dist/src/types/commands/index.d.ts +2 -2
  379. package/dist/src/types/commands/options.d.ts +212 -24
  380. package/dist/src/types/commands/patches.d.ts +118 -1
  381. package/dist/src/types/config.d.ts +264 -0
  382. package/dist/src/types/config.js +9 -0
  383. package/dist/src/types/furnace.d.ts +63 -1
  384. package/dist/src/types/furnace.js +7 -0
  385. package/dist/src/types/index.d.ts +1 -1
  386. package/dist/src/utils/options.d.ts +36 -7
  387. package/dist/src/utils/options.js +94 -8
  388. package/dist/src/utils/path-glob.d.ts +15 -0
  389. package/dist/src/utils/path-glob.js +47 -0
  390. package/dist/src/utils/regex.d.ts +15 -3
  391. package/dist/src/utils/regex.js +37 -16
  392. package/docs/configuration.md +369 -11
  393. package/docs/exit-codes.md +18 -1
  394. package/docs/furnace.md +139 -3
  395. package/docs/lifecycle-invariants.md +65 -30
  396. package/docs/machine-output.md +120 -12
  397. package/docs/patch-workflow.md +458 -22
  398. package/docs/run-logs.md +20 -1
  399. package/docs/testing.md +346 -21
  400. package/package.json +3 -3
@@ -5,16 +5,18 @@ import { stripJsComments } from '../utils/regex.js';
5
5
  import { containsUpstreamLicenseText, getLicenseHeader, hasAnyLicenseHeader, hasAnyLicenseHeaderAnyStyle, hasUpstreamMplBlockHeader, } from './license-headers.js';
6
6
  import { invokePatchLintCheckJs, runCheckJsTestFilesGrouped } from './patch-lint-checkjs.js';
7
7
  import { lintChromeScriptJsDocForFile } from './patch-lint-chrome-jsdoc.js';
8
+ import { countCodeLines, countRawLines } from './patch-lint-code-lines.js';
8
9
  import { lintPatchedCss } from './patch-lint-css.js';
9
10
  import { detectNewFilesInDiff, extractAddedLinesPerFile } from './patch-lint-diff.js';
10
- import { AGGREGATE_PATCH_FILE } from './patch-lint-diff-tag.js';
11
11
  import { formatFileTooLargeMessage, resolveFileSizeThresholds } from './patch-lint-file-size.js';
12
+ import { partitionLintIgnoredIssues } from './patch-lint-ignore.js';
12
13
  import { hasRelativeImport } from './patch-lint-imports.js';
13
14
  import { validateExportJsDoc } from './patch-lint-jsdoc.js';
14
15
  import { lintMozBuildSortedLists } from './patch-lint-mozbuild.js';
15
16
  import { lintObserverTopics } from './patch-lint-observer.js';
16
- import { resolvePatchOwnedChromeScripts, resolvePatchOwnedSysMjs, resolvePatchOwnedTestScripts, } from './patch-lint-ownership.js';
17
+ import { resolvableModulesForTests, resolvePatchOwnedChromeScripts, resolvePatchOwnedSysMjs, resolvePatchOwnedTestScripts, } from './patch-lint-ownership.js';
17
18
  import { invokePatchLintPrettier } from './patch-lint-prettier.js';
19
+ import { isTestFile, lintPatchSize, measurePatchSize, } from './patch-lint-size.js';
18
20
  // ---------------------------------------------------------------------------
19
21
  // Cross-patch lint re-exports
20
22
  // ---------------------------------------------------------------------------
@@ -30,23 +32,12 @@ export { buildModifiedFileAdditionsFromDiff, detectNewFilesInDiff } from './patc
30
32
  // split as the other rule families). Re-export the imported binding so
31
33
  // callers and tests keep importing `lintPatchedCss` from this module.
32
34
  export { lintPatchedCss };
35
+ // Same split for the patch-size rules and their measurement.
36
+ export { countNonBinaryDiffLines, getPatchSizeThresholds, isTestFile, lintPatchSize, measurePatchSize, resolvePatchSizeTier, } from './patch-lint-size.js';
33
37
  // ---------------------------------------------------------------------------
34
38
  // Helpers
35
39
  // ---------------------------------------------------------------------------
36
40
  const JS_EXTENSIONS = ['.js', '.mjs', '.jsm'];
37
- /**
38
- * Counts the lines of `content` the way `wc -l` reports them: a trailing
39
- * newline terminates the last line rather than starting an empty extra one.
40
- * A naive `split('\n').length` over-counts by one for any content ending in
41
- * `\n` (the common case), making the size rules fire one line early against
42
- * the operator's own `wc -l` measurement.
43
- */
44
- function countContentLines(content) {
45
- const lines = content.split('\n');
46
- if (lines[lines.length - 1] === '')
47
- lines.pop();
48
- return lines.length;
49
- }
50
41
  /**
51
42
  * Canonical `[check] file: message` rendering of a patch-lint issue. It is
52
43
  * the one string shape every reporter and refusal message uses, so operators
@@ -55,133 +46,6 @@ function countContentLines(content) {
55
46
  export function formatPatchLintIssue(issue) {
56
47
  return `[${issue.check}] ${issue.file}: ${issue.message}`;
57
48
  }
58
- /**
59
- * Counts the total lines in a unified diff and the number of non-binary
60
- * text lines, so binary hunks do not inflate patch size checks.
61
- *
62
- * Line counting agrees with `wc -l`: a trailing newline does not add a
63
- * phantom empty line to either `total` or the binary-hunk accounting.
64
- *
65
- * @param diffContent - Raw unified diff string
66
- * @returns Object with `total` line count and `textLines` (total minus binary hunk lines)
67
- */
68
- export function countNonBinaryDiffLines(diffContent) {
69
- const lines = diffContent.split('\n');
70
- if (lines[lines.length - 1] === '')
71
- lines.pop();
72
- const total = lines.length;
73
- let binaryLines = 0;
74
- let inBinaryHunk = false;
75
- for (const line of lines) {
76
- if (line === 'GIT binary patch' || line.startsWith('GIT binary patch')) {
77
- inBinaryHunk = true;
78
- }
79
- else if (line.startsWith('diff --git ')) {
80
- inBinaryHunk = false;
81
- }
82
- if (inBinaryHunk) {
83
- binaryLines++;
84
- }
85
- }
86
- return { total, textLines: total - binaryLines };
87
- }
88
- const PATCH_LINE_THRESHOLDS = {
89
- general: { notice: 800, warning: 1500, error: 3000 },
90
- test: { notice: 1500, warning: 3000, error: 6000 },
91
- /**
92
- * Branding patches have a legitimate reason to be large: they include
93
- * every locale's `brand.ftl`, copied upstream CSS/PNG assets, and the
94
- * fork-specific `configure.sh` / `brand.properties` under a single
95
- * `browser/branding/<name>/` subtree.
96
- *
97
- * Calibrated against a fresh-fork branding export, which lands around
98
- * 15-16k lines (localized brand.ftl across many locales + SVG path data +
99
- * copied upstream CSS). That is the minimum branding diff, so it must
100
- * surface as a soft `notice` rather than a `warning`: the warning band
101
- * sits above it with ~13% headroom, and the error band at roughly 2× the
102
- * baseline, where non-branding work is probably bundled in.
103
- *
104
- * Permissive thresholds are safe because the *gate* into this tier is
105
- * narrow: auto-detect requires every file under `browser/branding/` plus a
106
- * tight registration allowlist, or an explicit `PatchMetadata.tier:
107
- * "branding"` opt-in. A non-branding patch cannot accidentally land here.
108
- */
109
- branding: { notice: 8000, warning: 18000, error: 30000 },
110
- };
111
- /**
112
- * File-count thresholds for the `large-patch-files` rule, mirroring the tier
113
- * shape of {@link PATCH_LINE_THRESHOLDS}. A single warning-only threshold
114
- * per tier is intentional: file count expresses scope, not blast radius,
115
- * and there is no error band that would block export.
116
- *
117
- * The branding tier sits well above the typical floor because branding
118
- * patches inherently span many files: PNG/ICO icon assets in 7+ sizes, MSIX
119
- * manifests, channel-specific configs, locale `.ftl` files, Windows/macOS
120
- * launcher resources. A fresh-fork branding bundle is around 56 files, so 60
121
- * leaves headroom for additional channels/locales while still firing on a
122
- * genuinely bloated patch.
123
- *
124
- * Test tier matches general because a test-only patch rarely touches many
125
- * files (a single regression test usually adds 1-3 fixtures). The elevation
126
- * in {@link PATCH_LINE_THRESHOLDS.test} addresses big table-driven test
127
- * bodies, not file fan-out.
128
- */
129
- const PATCH_FILES_THRESHOLDS = {
130
- general: 5,
131
- test: 5,
132
- branding: 60,
133
- };
134
- /**
135
- * Shared remedy sentence for the over-budget findings. Both places an
136
- * operator stands when a patch outgrows its budget must name the sanctioned
137
- * one-transaction way out, or they reach for the wrong tools first. One
138
- * constant, four emission sites, so the wording cannot drift.
139
- */
140
- const SPLIT_HINT = 'Consider splitting into smaller, focused patches — ' +
141
- '"fireforge patch move-files <from> <to> --file <path> --create --order <n>" ' +
142
- 'moves files into a new patch in one transaction.';
143
- /**
144
- * Fixed allowlist of non-branding sibling paths that real-world Firefox
145
- * branding patches legitimately need to touch to register the new branding
146
- * flavor with the top-level configure. A predicate requiring every file to
147
- * live under `browser/branding/` drops a branding patch that also touches
148
- * `browser/moz.configure` (the canonical registration point) into the
149
- * general lint tier.
150
- *
151
- * Narrow on purpose: add entries only when a genuine branding patch
152
- * cannot be expressed without a specific registration sibling. Pinned
153
- * against ESR 140.x conventions at time of writing.
154
- */
155
- const BRANDING_REGISTRATION_FILES = new Set([
156
- 'browser/moz.configure',
157
- 'browser/confvars.sh',
158
- ]);
159
- /**
160
- * Returns true when a patch qualifies for the branding threshold tier:
161
- * every file lives either under `browser/branding/` or in the narrow
162
- * registration allowlist, and the patch contains at least one file
163
- * under `browser/branding/` (guard against a config-only patch
164
- * accidentally qualifying as branding).
165
- *
166
- * Used by `lintPatchSize` to pick the branding threshold tier. The
167
- * explicit `tier: "branding"` field on `PatchMetadata` bypasses this
168
- * heuristic and forces the branding tier directly.
169
- */
170
- function isBrandingOnlyPatch(files) {
171
- if (files.length === 0)
172
- return false;
173
- let hasBrandingFile = false;
174
- for (const file of files) {
175
- if (file.startsWith('browser/branding/')) {
176
- hasBrandingFile = true;
177
- continue;
178
- }
179
- if (BRANDING_REGISTRATION_FILES.has(file))
180
- continue;
181
- return false;
182
- }
183
- return hasBrandingFile;
184
- }
185
49
  /**
186
50
  * Returns true if the filename looks like a JS/MJS/JSM file.
187
51
  * Handles `.sys.mjs` as well.
@@ -207,15 +71,57 @@ export function filesMeasuredByFileTooLarge(patchText) {
207
71
  return measuredByFileTooLarge(detectNewFilesInDiff(patchText));
208
72
  }
209
73
  /**
210
- * Returns true if the file path looks like a test file.
211
- * Matches paths containing `/test/` or filenames starting with
212
- * `browser_`, `test_`, or `xpcshell_` (all `.js`).
74
+ * The `file-too-large` rule for one measured file, in the configured
75
+ * `patchLint.fileSizeMetric`. Under `"code"`, a file acorn cannot tokenize
76
+ * is measured in raw lines (the upper bound) and a `file-size-unparsed`
77
+ * notice names it, so the fallback is visible instead of silently lenient.
213
78
  */
214
- export function isTestFile(file) {
215
- if (file.includes('/test/'))
216
- return true;
217
- const basename = file.split('/').pop() ?? '';
218
- return /^(?:browser_|test_|xpcshell_).*\.js$/.test(basename);
79
+ function lintFileSize(file, content, config) {
80
+ const issues = [];
81
+ let lineCount = countRawLines(content);
82
+ let unit = 'lines';
83
+ if (config.patchLint?.fileSizeMetric === 'code') {
84
+ const counted = countCodeLines(content);
85
+ if (counted.parsed) {
86
+ lineCount = counted.lines;
87
+ unit = 'code lines';
88
+ }
89
+ else {
90
+ issues.push({
91
+ file,
92
+ check: 'file-size-unparsed',
93
+ message: `Could not parse the file as a module or a script, so file-too-large counted its ` +
94
+ `${lineCount} raw lines (the upper bound) instead of code lines.`,
95
+ severity: 'notice',
96
+ });
97
+ }
98
+ }
99
+ const isTest = isTestFile(file);
100
+ const thresholds = resolveFileSizeThresholds(config.patchLint?.fileSizeThresholds, isTest);
101
+ const band = lineCount > thresholds.error
102
+ ? 'error'
103
+ : lineCount > thresholds.warning
104
+ ? 'warning'
105
+ : lineCount > thresholds.notice
106
+ ? 'notice'
107
+ : undefined;
108
+ if (band !== undefined) {
109
+ issues.push({
110
+ file,
111
+ check: 'file-too-large',
112
+ message: formatFileTooLargeMessage({
113
+ label: isTest ? 'Test file' : 'New file',
114
+ lineCount,
115
+ unit,
116
+ thresholds,
117
+ band,
118
+ verb: isTest ? 'splitting' : 'decomposing',
119
+ }),
120
+ severity: band,
121
+ measurement: lineCount,
122
+ });
123
+ }
124
+ return issues;
219
125
  }
220
126
  /**
221
127
  * Narrower scope than `isTestFile`: only browser-chrome test files
@@ -424,26 +330,7 @@ export async function lintPatchedJs(repoDir, affectedFiles, newFiles, config, pa
424
330
  }
425
331
  // 2. File size check (new JS files only; see filesMeasuredByFileTooLarge)
426
332
  if (sizeMeasured.has(file)) {
427
- const lineCount = countContentLines(content);
428
- const isTest = isTestFile(file);
429
- const thresholds = resolveFileSizeThresholds(config.patchLint?.fileSizeThresholds, isTest);
430
- const label = isTest ? 'Test file' : 'New file';
431
- const verb = isTest ? 'splitting' : 'decomposing';
432
- const band = lineCount > thresholds.error
433
- ? 'error'
434
- : lineCount > thresholds.warning
435
- ? 'warning'
436
- : lineCount > thresholds.notice
437
- ? 'notice'
438
- : undefined;
439
- if (band !== undefined) {
440
- issues.push({
441
- file,
442
- check: 'file-too-large',
443
- message: formatFileTooLargeMessage({ label, lineCount, thresholds, band, verb }),
444
- severity: band,
445
- });
446
- }
333
+ issues.push(...lintFileSize(file, content, config));
447
334
  }
448
335
  // 3. JSDoc on exports (patch-owned .sys.mjs files)
449
336
  const isOwned = patchOwnedFiles ? patchOwnedFiles.has(file) : isNew;
@@ -522,114 +409,6 @@ export function lintModificationComments(diffContent, config) {
522
409
  }
523
410
  return issues;
524
411
  }
525
- /**
526
- * Decides which `large-patch-lines` threshold tier applies to a patch.
527
- * Exported so `runPatchLint` and the per-patch `lint` command can
528
- * surface the tier choice to the operator *without* depending on
529
- * `lintPatchSize`'s internal return shape. The rule itself stays a
530
- * pure issues-array API, and the decision is computed separately for
531
- * the sole purpose of reporting.
532
- *
533
- * Precedence: test > branding (explicit) > branding (auto) > general.
534
- * The test tier beats branding because a table-driven regression test
535
- * is legitimately large independent of whether the patch also claims
536
- * branding shape, and the test-tier thresholds are already more
537
- * permissive than branding, so "tests beat branding" is the
538
- * defensive-for-tests choice.
539
- */
540
- export function resolvePatchSizeTier(filesAffected, patchTier) {
541
- const allTests = filesAffected.length > 0 && filesAffected.every(isTestFile);
542
- if (allTests)
543
- return { tier: 'test' };
544
- if (patchTier === 'branding')
545
- return { tier: 'branding', source: 'explicit' };
546
- if (isBrandingOnlyPatch(filesAffected))
547
- return { tier: 'branding', source: 'auto' };
548
- return { tier: 'general' };
549
- }
550
- /**
551
- * Read-only view of the size thresholds a tier enforces.
552
- * Public-API companion to {@link resolvePatchSizeTier} /
553
- * {@link countNonBinaryDiffLines} so consumers can build waiver-freshness
554
- * checks against the same numbers `large-patch-lines` /
555
- * `large-patch-files` fire on instead of mirroring them.
556
- */
557
- export function getPatchSizeThresholds(tier) {
558
- return {
559
- lines: { ...PATCH_LINE_THRESHOLDS[tier] },
560
- maxFiles: PATCH_FILES_THRESHOLDS[tier],
561
- };
562
- }
563
- /**
564
- * Checks patch size and emits advisory warnings.
565
- *
566
- * @param filesAffected - Files touched by the patch
567
- * @param lineCount - Non-binary line count of the unified diff
568
- * @param patchTier - Optional explicit tier override declared on
569
- * `PatchMetadata.tier`. When `"branding"`, forces the branding
570
- * thresholds regardless of `filesAffected`. Tests still win over
571
- * branding (precedence `test > branding > general`) because the
572
- * test-tier thresholds are already more permissive and an all-tests
573
- * patch that is also branding-shaped is vanishingly rare.
574
- */
575
- export function lintPatchSize(filesAffected, lineCount, patchTier) {
576
- const issues = [];
577
- // Tier selection: test > branding > general. Tests keep their elevated
578
- // thresholds because a big regression test is legitimate (table-driven
579
- // harnesses run into the thousands of lines). Branding patches get their
580
- // own tier so a first export of setup-generated branding does not fire the
581
- // general hard limit. See `PATCH_LINE_THRESHOLDS.branding` and
582
- // `PATCH_FILES_THRESHOLDS.branding` above. An explicit `patchTier` opt-in
583
- // forces branding even when `isBrandingOnlyPatch` cannot reach the patch's
584
- // actual shape (a branding patch that also touches a non-allowlisted
585
- // sibling like a vendor-specific icon resource). Both checks read off the
586
- // same decision so the file-count and line-count rules cannot disagree
587
- // about which tier applies.
588
- const decision = resolvePatchSizeTier(filesAffected, patchTier);
589
- const fileThreshold = decision.tier === 'test'
590
- ? PATCH_FILES_THRESHOLDS.test
591
- : decision.tier === 'branding'
592
- ? PATCH_FILES_THRESHOLDS.branding
593
- : PATCH_FILES_THRESHOLDS.general;
594
- const lineThresholds = decision.tier === 'test'
595
- ? PATCH_LINE_THRESHOLDS.test
596
- : decision.tier === 'branding'
597
- ? PATCH_LINE_THRESHOLDS.branding
598
- : PATCH_LINE_THRESHOLDS.general;
599
- if (filesAffected.length > fileThreshold) {
600
- issues.push({
601
- file: AGGREGATE_PATCH_FILE,
602
- check: 'large-patch-files',
603
- message: `Patch affects ${filesAffected.length} files (recommended: ≤${fileThreshold}). ${SPLIT_HINT}`,
604
- severity: 'warning',
605
- });
606
- }
607
- if (lineCount > lineThresholds.error) {
608
- issues.push({
609
- file: AGGREGATE_PATCH_FILE,
610
- check: 'large-patch-lines',
611
- message: `Patch is ${lineCount} lines (hard limit: ${lineThresholds.error}). ${SPLIT_HINT}`,
612
- severity: 'error',
613
- });
614
- }
615
- else if (lineCount > lineThresholds.warning) {
616
- issues.push({
617
- file: AGGREGATE_PATCH_FILE,
618
- check: 'large-patch-lines',
619
- message: `Patch is ${lineCount} lines (soft limit: ${lineThresholds.warning}, hard limit: ${lineThresholds.error}). ${SPLIT_HINT}`,
620
- severity: 'warning',
621
- });
622
- }
623
- else if (lineCount > lineThresholds.notice) {
624
- issues.push({
625
- file: AGGREGATE_PATCH_FILE,
626
- check: 'large-patch-lines',
627
- message: `Patch is ${lineCount} lines (soft limit: ${lineThresholds.warning}, hard limit: ${lineThresholds.error}). ${SPLIT_HINT}`,
628
- severity: 'notice',
629
- });
630
- }
631
- return issues;
632
- }
633
412
  // ---------------------------------------------------------------------------
634
413
  // Modified file header lint
635
414
  // ---------------------------------------------------------------------------
@@ -683,7 +462,6 @@ export async function lintModifiedFileHeaders(repoDir, affectedFiles, newFiles)
683
462
  export async function lintExportedPatch(repoDir, affectedFiles, diffContent, config, options) {
684
463
  const { patchQueueCtx, ignoreChecks, patchTier } = options ?? {};
685
464
  const newFiles = detectNewFilesInDiff(diffContent);
686
- const { textLines: lineCount } = countNonBinaryDiffLines(diffContent);
687
465
  const patchOwnedFiles = resolvePatchOwnedSysMjs(newFiles, patchQueueCtx);
688
466
  const patchOwnedChromeScripts = resolvePatchOwnedChromeScripts(newFiles, patchQueueCtx);
689
467
  const [cssIssues, headerIssues, jsIssues, modifiedHeaderIssues, mozBuildIssues] = await Promise.all([
@@ -696,7 +474,8 @@ export async function lintExportedPatch(repoDir, affectedFiles, diffContent, con
696
474
  const modCommentIssues = lintModificationComments(diffContent, config);
697
475
  const sizeIssues = options?.skipPatchSize
698
476
  ? []
699
- : lintPatchSize(affectedFiles, lineCount, patchTier);
477
+ : lintPatchSize(affectedFiles, options?.patchSizeMeasurement ??
478
+ (await measurePatchSize(repoDir, diffContent, affectedFiles, config)), patchTier, config);
700
479
  const issues = [
701
480
  ...sizeIssues,
702
481
  ...cssIssues,
@@ -716,7 +495,7 @@ export async function lintExportedPatch(repoDir, affectedFiles, diffContent, con
716
495
  // Export-time twin of per-patch lint's test-file pass, so
717
496
  // export/re-export and `lint --per-patch` agree on the checked surface.
718
497
  if (config.patchLint.checkJsTestFiles === true) {
719
- const testGrouped = await runCheckJsTestFilesGrouped(repoDir, resolvePatchOwnedTestScripts(newFiles, patchQueueCtx), config.patchLint, dirname(repoDir));
498
+ const testGrouped = await runCheckJsTestFilesGrouped(repoDir, resolvePatchOwnedTestScripts(newFiles, patchQueueCtx), config.patchLint, dirname(repoDir), undefined, patchQueueCtx ? resolvableModulesForTests(patchQueueCtx) : undefined);
720
499
  issues.push(...testGrouped.global);
721
500
  for (const [rel, list] of testGrouped.byFile) {
722
501
  if (options?.checkJsReportScope && !options.checkJsReportScope.has(rel))
@@ -740,12 +519,11 @@ export async function lintExportedPatch(repoDir, affectedFiles, diffContent, con
740
519
  // reclassify severities: an ignored error simply drops, mirroring how
741
520
  // inline `fireforge-ignore: <check>` markers work in the CSS and
742
521
  // forward-import rules.
743
- if (ignoreChecks && ignoreChecks.size > 0) {
744
- const suppressed = issues.filter((issue) => ignoreChecks.has(issue.check));
745
- if (suppressed.length > 0) {
746
- options?.onSuppressed?.(suppressed);
747
- }
748
- return issues.filter((issue) => !ignoreChecks.has(issue.check));
522
+ // A ceiling entry (see `patch-lint-ignore.ts`) drops a size finding only
523
+ // while its measurement stays at or below the ceiling.
524
+ const { kept, suppressed } = partitionLintIgnoredIssues(issues, ignoreChecks);
525
+ if (suppressed.length > 0) {
526
+ options?.onSuppressed?.(suppressed);
749
527
  }
750
- return issues;
528
+ return kept;
751
529
  }
@@ -8,20 +8,21 @@ import { PatchError } from '../errors/patch.js';
8
8
  import { assert } from '../utils/assert.js';
9
9
  import { toError } from '../utils/errors.js';
10
10
  import { info } from '../utils/logger.js';
11
- import { readLockStatus, withFileLock } from './file-lock.js';
11
+ import { formatLockQueuePosition, readLockStatus, withFileLock } from './file-lock.js';
12
12
  const PATCH_DIRECTORY_LOCK = '.fireforge-patches.lock';
13
13
  /**
14
14
  * Formats the periodic operator-facing waiting line printed while
15
15
  * `--wait-lock` polls a contended patch directory lock. Mirrors the engine
16
16
  * session lock's progress line so operators read one shape for both locks.
17
17
  */
18
- function formatWaitProgressLine(waitedMs, timeoutMs, holder) {
18
+ function formatWaitProgressLine(waitedMs, timeoutMs, holder, queue) {
19
19
  const progress = `${Math.round(waitedMs / 1000)}s of up to ${Math.round(timeoutMs / 1000)}s`;
20
+ const position = formatLockQueuePosition(queue);
20
21
  if (holder === undefined) {
21
- return `Waiting for the FireForge patch directory lock — ${progress}.`;
22
+ return `Waiting for the FireForge patch directory lock — ${progress}.${position}`;
22
23
  }
23
24
  const details = holder.metadata.length > 0 ? ` (${holder.metadata.join(', ')})` : '';
24
- return `Waiting for the FireForge patch directory lock held by PID ${holder.pid}${details} — ${progress}.`;
25
+ return `Waiting for the FireForge patch directory lock held by PID ${holder.pid}${details} — ${progress}.${position}`;
25
26
  }
26
27
  /**
27
28
  * Asserts that this process is inside {@link withPatchDirectoryLock} for the
@@ -80,8 +81,8 @@ export async function withPatchDirectoryLock(patchesDir, operation, options = {}
80
81
  pollMs: 100,
81
82
  pollMaxMs: 2000,
82
83
  waitProgressMs: 5000,
83
- onWaitProgress: ({ waitedMs, timeoutMs, holder }) => {
84
- info(formatWaitProgressLine(waitedMs, timeoutMs, holder));
84
+ onWaitProgress: ({ waitedMs, timeoutMs, holder, queue }) => {
85
+ info(formatWaitProgressLine(waitedMs, timeoutMs, holder, queue));
85
86
  },
86
87
  }
87
88
  : {}),
@@ -8,6 +8,7 @@ import { discoverPatches, getAllTargetFilesFromPatch } from './patch-files.js';
8
8
  import { withPatchDirectoryLock } from './patch-lock.js';
9
9
  import { loadPatchesManifestState, PATCHES_MANIFEST, savePatchesManifest, } from './patch-manifest-io.js';
10
10
  import { inferPatchMetadataFromFilename } from './patch-manifest-validate.js';
11
+ import { manifestWithRetiredSlots } from './patch-retired.js';
11
12
  /**
12
13
  * Validates that patches.json and the patch directory describe the same patch set.
13
14
  * @param patchesDir - Path to the patches directory
@@ -216,16 +217,16 @@ async function rebuildPatchesManifestUnderLock(patchesDir, fallbackSourceEsrVers
216
217
  };
217
218
  // Copy the arrays rather than aliasing the loaded manifest's, so a caller
218
219
  // mutating the rebuilt entry cannot reach back into `manifestState`.
219
- if (existing?.lintIgnore !== undefined)
220
- rebuilt.lintIgnore = [...existing.lintIgnore];
220
+ if (existing?.lintIgnore !== undefined) {
221
+ rebuilt.lintIgnore = existing.lintIgnore.map((entry) => typeof entry === 'string' ? entry : { ...entry });
222
+ }
221
223
  rebuiltPatches.push(rebuilt);
222
224
  existingEntries.delete(patch.filename);
223
225
  }
224
226
  rebuiltPatches.sort((left, right) => left.order - right.order || left.filename.localeCompare(right.filename));
225
- const rebuiltManifest = {
226
- version: 1,
227
- patches: rebuiltPatches,
228
- };
227
+ // Retired slots have no patch file to recover them from, so they ride
228
+ // along from the loaded manifest like the per-row fields above.
229
+ const rebuiltManifest = manifestWithRetiredSlots(manifestState.manifest, rebuiltPatches);
229
230
  // Whatever is left in the map had a manifest row but no patch file, so the
230
231
  // rebuild drops it. That is the correct outcome, since the file is the
231
232
  // source of truth, but dropping rows without naming them is
@@ -7,7 +7,7 @@
7
7
  * inconsistent manifests.
8
8
  */
9
9
  import { FireForgeError } from '../errors/base.js';
10
- import type { PatchesManifest, PatchMetadata } from '../types/commands/index.js';
10
+ import type { PatchesManifest, PatchMetadata, PatchRetiredSlot } from '../types/commands/index.js';
11
11
  import type { JsonObject } from '../types/json.js';
12
12
  /** Filename for the patches manifest */
13
13
  export declare const PATCHES_MANIFEST = "patches.json";
@@ -89,10 +89,11 @@ export declare function addPatchToManifest(patchesDir: string, metadata: PatchMe
89
89
  *
90
90
  * @param patchesDir - Path to the patches directory
91
91
  * @param filename - Filename of the patch to remove from the manifest
92
+ * @param retiredSlot - Optional placeholder to record for the removed order
92
93
  * @returns True when the manifest was written (i.e. an entry was removed),
93
94
  * false when no matching entry existed
94
95
  */
95
- export declare function removePatchFromManifest(patchesDir: string, filename: string): Promise<boolean>;
96
+ export declare function removePatchFromManifest(patchesDir: string, filename: string, retiredSlot?: PatchRetiredSlot): Promise<boolean>;
96
97
  /**
97
98
  * A single rename step in a {@link renumberPatchesInManifest} plan.
98
99
  */
@@ -188,5 +189,7 @@ export declare class PatchDeleteRollbackError extends FireForgeError {
188
189
  *
189
190
  * @param patchesDir - Path to the patches directory
190
191
  * @param filename - Patch filename to delete
192
+ * @param retiredSlot - When set (`patch retire`), the placeholder written in
193
+ * the same manifest update that removes the row
191
194
  */
192
- export declare function removePatchFileAndManifest(patchesDir: string, filename: string): Promise<void>;
195
+ export declare function removePatchFileAndManifest(patchesDir: string, filename: string, retiredSlot?: PatchRetiredSlot): Promise<void>;
@@ -19,7 +19,9 @@ import { pathExistsStrict, readJson, removeFile, writeJson } from '../utils/fs.j
19
19
  import { warn } from '../utils/logger.js';
20
20
  import { isArray, isObject } from '../utils/validation.js';
21
21
  import { assertPatchDirectoryLockHeld } from './patch-lock.js';
22
+ import { canonicalizeManifest, canonicalizePatchRow } from './patch-manifest-key-order.js';
22
23
  import { validatePatchesManifest } from './patch-manifest-validate.js';
24
+ import { manifestWithRetiredSlots } from './patch-retired.js';
23
25
  /** Filename for the patches manifest */
24
26
  export const PATCHES_MANIFEST = 'patches.json';
25
27
  /**
@@ -85,7 +87,7 @@ export async function loadPatchesManifestForWrite(patchesDir) {
85
87
  */
86
88
  export async function savePatchesManifest(patchesDir, manifest) {
87
89
  const manifestPath = join(patchesDir, PATCHES_MANIFEST);
88
- await writeJson(manifestPath, manifest);
90
+ await writeJson(manifestPath, canonicalizeManifest(manifest));
89
91
  }
90
92
  /**
91
93
  * Mutates selected manifest rows while preserving the raw JSON shape of every
@@ -138,6 +140,12 @@ export async function mutatePatchRowsInManifest(patchesDir, filenames, mutator)
138
140
  }
139
141
  if (changedIndexes.length === 0)
140
142
  return [];
143
+ // Only the rows this call changed are put in canonical order, so a key
144
+ // it sets lands where every other writer puts it; untouched rows keep
145
+ // their raw shape.
146
+ for (const index of changedIndexes) {
147
+ rawPatches[index] = canonicalizePatchRow(rawPatches[index]);
148
+ }
141
149
  const nextRawManifest = {
142
150
  ...rawManifest,
143
151
  patches: rawPatches,
@@ -171,7 +179,8 @@ export async function addPatchToManifest(patchesDir, metadata, removeFilenames)
171
179
  // Add new entry and sort by order
172
180
  manifest.patches.push(metadata);
173
181
  manifest.patches.sort((a, b) => a.order - b.order);
174
- await savePatchesManifest(patchesDir, manifest);
182
+ // A patch landing on a retired order fills that slot.
183
+ await savePatchesManifest(patchesDir, manifestWithRetiredSlots(manifest, manifest.patches));
175
184
  }
176
185
  /**
177
186
  * Removes a single patch entry from the manifest by filename. Leaves the
@@ -184,10 +193,11 @@ export async function addPatchToManifest(patchesDir, metadata, removeFilenames)
184
193
  *
185
194
  * @param patchesDir - Path to the patches directory
186
195
  * @param filename - Filename of the patch to remove from the manifest
196
+ * @param retiredSlot - Optional placeholder to record for the removed order
187
197
  * @returns True when the manifest was written (i.e. an entry was removed),
188
198
  * false when no matching entry existed
189
199
  */
190
- export async function removePatchFromManifest(patchesDir, filename) {
200
+ export async function removePatchFromManifest(patchesDir, filename, retiredSlot) {
191
201
  const manifest = await loadPatchesManifestForWrite(patchesDir);
192
202
  if (!manifest)
193
203
  return false;
@@ -196,6 +206,14 @@ export async function removePatchFromManifest(patchesDir, filename) {
196
206
  if (manifest.patches.length === originalLength) {
197
207
  return false;
198
208
  }
209
+ if (retiredSlot !== undefined) {
210
+ // `patch retire`: the row leaves and its order stays spent, in the
211
+ // same write, so no reader ever sees the gap in between.
212
+ manifest.retired = [
213
+ ...(manifest.retired ?? []).filter((slot) => slot.order !== retiredSlot.order),
214
+ retiredSlot,
215
+ ].sort((a, b) => a.order - b.order);
216
+ }
199
217
  await savePatchesManifest(patchesDir, manifest);
200
218
  return true;
201
219
  }
@@ -436,10 +454,8 @@ export async function renumberPatchesInManifest(patchesDir, renameMap) {
436
454
  // try, so a violation unwinds through the Phase 3 rollback that puts the
437
455
  // filenames back.
438
456
  assertRenumberedOrderIsWellFormed(updatedPatches);
439
- await savePatchesManifest(patchesDir, {
440
- ...manifest,
441
- patches: updatedPatches,
442
- });
457
+ // A patch renumbered onto a retired order fills that slot.
458
+ await savePatchesManifest(patchesDir, manifestWithRetiredSlots(manifest, updatedPatches));
443
459
  }
444
460
  catch (error) {
445
461
  // Phase 3 rollback: reverse every completed rename. The manifest
@@ -504,14 +520,16 @@ export class PatchDeleteRollbackError extends FireForgeError {
504
520
  *
505
521
  * @param patchesDir - Path to the patches directory
506
522
  * @param filename - Patch filename to delete
523
+ * @param retiredSlot - When set (`patch retire`), the placeholder written in
524
+ * the same manifest update that removes the row
507
525
  */
508
- export async function removePatchFileAndManifest(patchesDir, filename) {
526
+ export async function removePatchFileAndManifest(patchesDir, filename, retiredSlot) {
509
527
  await assertPatchDirectoryLockHeld(patchesDir, 'deleting a patch and its manifest row');
510
528
  const patchPath = join(patchesDir, filename);
511
529
  // ForWrite: deleting the patch file while a corrupt manifest still
512
530
  // references it would strand the queue. Abort on corruption instead.
513
531
  const originalManifest = await loadPatchesManifestForWrite(patchesDir);
514
- const removedFromManifest = await removePatchFromManifest(patchesDir, filename);
532
+ const removedFromManifest = await removePatchFromManifest(patchesDir, filename, retiredSlot);
515
533
  try {
516
534
  if (await pathExistsStrict(patchPath)) {
517
535
  await removeFile(patchPath);
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The one key order every `patches.json` writer uses for a patch row and
3
+ * for the manifest itself. Writers used to disagree: `export` spread
4
+ * `sourceProduct` before `sourceVersion` while a full-manifest save put it
5
+ * after `filesAffected`, so a no-op rewrite left a key-order-only hunk in
6
+ * the manifest.
7
+ */
8
+ /**
9
+ * Returns a copy of a patch row with its keys in canonical order.
10
+ *
11
+ * @param row - A `PatchMetadata` row, typed or raw
12
+ * @returns The same fields in canonical key order
13
+ */
14
+ export declare function canonicalizePatchRow<T extends object>(row: T): T;
15
+ /**
16
+ * Returns a copy of a manifest with its top-level keys and every row in
17
+ * canonical order.
18
+ *
19
+ * @param manifest - A `PatchesManifest`, typed or raw
20
+ * @returns The manifest in canonical key order
21
+ */
22
+ export declare function canonicalizeManifest<T extends {
23
+ patches: readonly object[];
24
+ }>(manifest: T): T;