@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
@@ -2,29 +2,19 @@ import type { PatchLintIssue } from '../types/commands/index.js';
2
2
  import type { FireForgeConfig, ProjectLicense } from '../types/config.js';
3
3
  import { type CommentStyle } from './license-headers.js';
4
4
  import { lintPatchedCss } from './patch-lint-css.js';
5
+ import { type LintIgnoreChecks } from './patch-lint-ignore.js';
6
+ import { type PatchSizeMeasurement } from './patch-lint-size.js';
5
7
  export * from './patch-lint-cross.js';
6
8
  export { buildModifiedFileAdditionsFromDiff, detectNewFilesInDiff } from './patch-lint-diff.js';
7
9
  export { lintPatchedCss };
10
+ export { countNonBinaryDiffLines, getPatchSizeThresholds, isTestFile, lintPatchSize, measurePatchSize, type PatchSizeMeasurement, type PatchSizeTierDecision, resolvePatchSizeTier, } from './patch-lint-size.js';
11
+ export type { PatchSizeThresholds } from './patch-lint-size-thresholds.js';
8
12
  /**
9
13
  * Canonical `[check] file: message` rendering of a patch-lint issue. It is
10
14
  * the one string shape every reporter and refusal message uses, so operators
11
15
  * can grep one format across lint output and refusal details.
12
16
  */
13
17
  export declare function formatPatchLintIssue(issue: Pick<PatchLintIssue, 'check' | 'file' | 'message'>): string;
14
- /**
15
- * Counts the total lines in a unified diff and the number of non-binary
16
- * text lines, so binary hunks do not inflate patch size checks.
17
- *
18
- * Line counting agrees with `wc -l`: a trailing newline does not add a
19
- * phantom empty line to either `total` or the binary-hunk accounting.
20
- *
21
- * @param diffContent - Raw unified diff string
22
- * @returns Object with `total` line count and `textLines` (total minus binary hunk lines)
23
- */
24
- export declare function countNonBinaryDiffLines(diffContent: string): {
25
- total: number;
26
- textLines: number;
27
- };
28
18
  /**
29
19
  * The files the `file-too-large` rule measures for a patch: the JS files
30
20
  * (`.js`, `.mjs`, `.sys.mjs`, …) the patch creates. A modified file is
@@ -36,12 +26,6 @@ export declare function countNonBinaryDiffLines(diffContent: string): {
36
26
  * @returns Engine-relative paths the rule would size-check
37
27
  */
38
28
  export declare function filesMeasuredByFileTooLarge(patchText: string): Set<string>;
39
- /**
40
- * Returns true if the file path looks like a test file.
41
- * Matches paths containing `/test/` or filenames starting with
42
- * `browser_`, `test_`, or `xpcshell_` (all `.js`).
43
- */
44
- export declare function isTestFile(file: string): boolean;
45
29
  /** True when `strippedContent` contains at least one assertion call. */
46
30
  export declare function hasBrowserChromeAssertion(strippedContent: string): boolean;
47
31
  /**
@@ -115,68 +99,6 @@ export declare function lintPatchedJs(repoDir: string, affectedFiles: string[],
115
99
  * @returns Array of lint issues
116
100
  */
117
101
  export declare function lintModificationComments(diffContent: string, config: FireForgeConfig): PatchLintIssue[];
118
- /**
119
- * Describes which tier `resolvePatchSizeTier` selected and why.
120
- * Consumers that want to surface the tier choice to the operator
121
- * (e.g. a one-line `info()` when branding thresholds kick in) read
122
- * this alongside the issues array from `lintPatchSize`.
123
- */
124
- export type PatchSizeTierDecision = {
125
- tier: 'general';
126
- } | {
127
- tier: 'test';
128
- } | {
129
- tier: 'branding';
130
- source: 'auto' | 'explicit';
131
- };
132
- /**
133
- * Decides which `large-patch-lines` threshold tier applies to a patch.
134
- * Exported so `runPatchLint` and the per-patch `lint` command can
135
- * surface the tier choice to the operator *without* depending on
136
- * `lintPatchSize`'s internal return shape. The rule itself stays a
137
- * pure issues-array API, and the decision is computed separately for
138
- * the sole purpose of reporting.
139
- *
140
- * Precedence: test > branding (explicit) > branding (auto) > general.
141
- * The test tier beats branding because a table-driven regression test
142
- * is legitimately large independent of whether the patch also claims
143
- * branding shape, and the test-tier thresholds are already more
144
- * permissive than branding, so "tests beat branding" is the
145
- * defensive-for-tests choice.
146
- */
147
- export declare function resolvePatchSizeTier(filesAffected: ReadonlyArray<string>, patchTier?: 'branding'): PatchSizeTierDecision;
148
- /** Line and file-count thresholds governing one patch-size tier. */
149
- export interface PatchSizeThresholds {
150
- /** Added-line counts at which notice/warning/error issues fire. */
151
- lines: {
152
- notice: number;
153
- warning: number;
154
- error: number;
155
- };
156
- /** Maximum number of files a patch may touch before an issue fires. */
157
- maxFiles: number;
158
- }
159
- /**
160
- * Read-only view of the size thresholds a tier enforces.
161
- * Public-API companion to {@link resolvePatchSizeTier} /
162
- * {@link countNonBinaryDiffLines} so consumers can build waiver-freshness
163
- * checks against the same numbers `large-patch-lines` /
164
- * `large-patch-files` fire on instead of mirroring them.
165
- */
166
- export declare function getPatchSizeThresholds(tier: 'general' | 'test' | 'branding'): PatchSizeThresholds;
167
- /**
168
- * Checks patch size and emits advisory warnings.
169
- *
170
- * @param filesAffected - Files touched by the patch
171
- * @param lineCount - Non-binary line count of the unified diff
172
- * @param patchTier - Optional explicit tier override declared on
173
- * `PatchMetadata.tier`. When `"branding"`, forces the branding
174
- * thresholds regardless of `filesAffected`. Tests still win over
175
- * branding (precedence `test > branding > general`) because the
176
- * test-tier thresholds are already more permissive and an all-tests
177
- * patch that is also branding-shaped is vanishingly rare.
178
- */
179
- export declare function lintPatchSize(filesAffected: string[], lineCount: number, patchTier?: 'branding'): PatchLintIssue[];
180
102
  /**
181
103
  * Checks that modified (non-new) files with a supported extension still
182
104
  * start with a recognized license header.
@@ -204,7 +126,7 @@ export interface LintExportedPatchOptions {
204
126
  * of a specific rule without reaching for the blunt `--skip-lint` hammer.
205
127
  * Not mutated.
206
128
  */
207
- ignoreChecks?: ReadonlySet<string>;
129
+ ignoreChecks?: LintIgnoreChecks;
208
130
  /**
209
131
  * Explicit tier override, threaded from `PatchMetadata.tier`. When
210
132
  * `"branding"` forces the branding thresholds on the
@@ -246,6 +168,12 @@ export interface LintExportedPatchOptions {
246
168
  * and the `--report` JSON) instead of losing it entirely.
247
169
  */
248
170
  onSuppressed?: (suppressed: PatchLintIssue[]) => void;
171
+ /**
172
+ * The patch's {@link measurePatchSize} result, when the caller already
173
+ * measured it for its own report or cache row. Absent, the size rules
174
+ * measure the diff themselves.
175
+ */
176
+ patchSizeMeasurement?: PatchSizeMeasurement;
249
177
  }
250
178
  /**
251
179
  * Runs all patch lint checks and returns combined issues.
@@ -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
17
  import { 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,
@@ -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>;