@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
@@ -6,11 +6,13 @@ import { getFurnacePaths, loadFurnaceConfig } from '../../core/furnace-config.js
6
6
  import { assertFurnaceReady } from '../../core/furnace-precondition.js';
7
7
  import { addCustomElementRegistration, addJarMnEntries, pruneStaleJarMnEntries, } from '../../core/furnace-registration.js';
8
8
  import { validateAllComponents, validateComponent } from '../../core/furnace-validate.js';
9
+ import { applyValidationExceptions, } from '../../core/furnace-validate-exceptions.js';
10
+ import { createIssue } from '../../core/furnace-validate-helpers.js';
9
11
  import { FurnaceError } from '../../errors/furnace.js';
10
12
  import { toError } from '../../utils/errors.js';
11
13
  import { pathExists } from '../../utils/fs.js';
12
- import { info, intro, note, outro, success, warn } from '../../utils/logger.js';
13
- import { displayValidationIssues } from './validation-output.js';
14
+ import { info, intro, isMachineOutputMode, note, outro, setMachineOutputMode, warn, } from '../../utils/logger.js';
15
+ import { judgeValidateReport, renderValidateHuman, renderValidateJson } from './validate-report.js';
14
16
  /** Checks that auto-fix can correct. */
15
17
  const FIXABLE_CHECKS = new Set([
16
18
  'missing-jar-mn-mjs',
@@ -114,133 +116,176 @@ async function autoFixIssues(projectRoot, issues) {
114
116
  }
115
117
  return fixed;
116
118
  }
119
+ /**
120
+ * Resolves the named component's type and directory.
121
+ * @returns undefined for a stock component (nothing local to validate)
122
+ */
123
+ function resolveNamedComponent(name, config, furnacePaths) {
124
+ if (name in config.overrides) {
125
+ return { type: 'override', componentDir: join(furnacePaths.overridesDir, name) };
126
+ }
127
+ if (name in config.custom) {
128
+ return { type: 'custom', componentDir: join(furnacePaths.customDir, name) };
129
+ }
130
+ if (config.stock.includes(name))
131
+ return undefined;
132
+ throw new FurnaceError(`Component "${name}" not found in furnace.json.`, name);
133
+ }
134
+ /**
135
+ * Runs the first validation pass: one named component, or every override
136
+ * and custom component. Returns undefined when there is nothing local to
137
+ * validate (a stock component, or no components at all), after saying so.
138
+ */
139
+ async function runValidationPass(projectRoot, name, config, furnacePaths) {
140
+ if (name) {
141
+ const target = resolveNamedComponent(name, config, furnacePaths);
142
+ if (target === undefined) {
143
+ info(`"${name}" is a stock component. Stock components are not validated locally.`);
144
+ return undefined;
145
+ }
146
+ if (!(await pathExists(target.componentDir))) {
147
+ throw new FurnaceError(`Component directory not found for "${name}".`, name);
148
+ }
149
+ const issues = await validateComponent(target.componentDir, name, target.type, config, projectRoot);
150
+ return { results: new Map([[name, issues]]), judged: (component) => component === name };
151
+ }
152
+ if (Object.keys(config.overrides).length === 0 && Object.keys(config.custom).length === 0) {
153
+ info('No components to validate.');
154
+ return undefined;
155
+ }
156
+ if (config.stock.length > 0) {
157
+ info(`Skipping ${config.stock.length} stock component(s) (no local files to validate).`);
158
+ }
159
+ return { results: await validateAllComponents(projectRoot), judged: () => true };
160
+ }
161
+ /**
162
+ * Runs `--fix` over the fixable findings, then re-validates the affected
163
+ * components and swaps their findings in, so the summary and the JSON
164
+ * report what auto-fix actually accomplished. The counter `autoFixIssues`
165
+ * returns only counts calls that did not throw, so a write that succeeded
166
+ * but did not resolve the finding would otherwise still be reported fixed.
167
+ */
168
+ async function applyAutoFix(projectRoot, config, furnacePaths, run, countedIssues) {
169
+ const fixableIssues = countedIssues.filter((issue) => FIXABLE_CHECKS.has(issue.check));
170
+ if (fixableIssues.length === 0) {
171
+ info('\nNo auto-fixable issues found. Remaining issues require manual resolution.');
172
+ return;
173
+ }
174
+ await autoFixIssues(projectRoot, fixableIssues);
175
+ const reValidated = await reValidateComponents(projectRoot, config, furnacePaths, new Set(fixableIssues.map((issue) => issue.component)));
176
+ for (const [component, issues] of reValidated)
177
+ run.results.set(component, issues);
178
+ const fixableAfter = [...reValidated.values()]
179
+ .flat()
180
+ .filter((issue) => FIXABLE_CHECKS.has(issue.check)).length;
181
+ const actuallyFixed = Math.max(0, fixableIssues.length - fixableAfter);
182
+ if (actuallyFixed > 0) {
183
+ info(`\nAuto-fixed ${actuallyFixed} issue(s).`);
184
+ }
185
+ if (fixableAfter > 0) {
186
+ warn(`${fixableAfter} fixable issue(s) remain after auto-fix — investigate manually.`);
187
+ }
188
+ }
189
+ /** Applies `validationExceptions` to every finding of the run. */
190
+ function exceptRun(run, config) {
191
+ return applyValidationExceptions([...run.results.values()].flat(), config.validationExceptions, run.judged);
192
+ }
193
+ /**
194
+ * The `furnace.json#minComponents` floor of a full run: an error when it
195
+ * judged fewer components, so a run over an emptied or moved components
196
+ * tree cannot pass by validating nothing. A run naming one component is
197
+ * not judged against it.
198
+ */
199
+ function componentFloorIssue(config, name, judged) {
200
+ if (name !== undefined || config.minComponents === undefined)
201
+ return undefined;
202
+ if (judged >= config.minComponents)
203
+ return undefined;
204
+ return createIssue('furnace.json', 'error', 'component-floor', `Validated ${judged} component(s), under furnace.json minComponents ${config.minComponents}. ` +
205
+ 'Check that the components tree is where furnace.json says, or lower the floor.');
206
+ }
117
207
  /**
118
208
  * Runs the furnace validate command to perform static analysis on components.
119
209
  * @param projectRoot - Root directory of the project
120
210
  * @param name - Optional component name to validate (validates all if omitted)
121
- * @param options - Optional command options (e.g. --fix)
211
+ * @param options - Optional command options (`--fix`, `--json`, `--max-warnings`)
122
212
  */
123
213
  export async function furnaceValidateCommand(projectRoot, name, options = {}) {
214
+ const previousMachineOutputMode = isMachineOutputMode();
215
+ // `--json` owns stdout: every diagnostic routes to stderr from here on.
216
+ // On a throw the mode stays engaged and withErrorHandling resets it, so
217
+ // the refusal cannot land on stdout after the payload.
218
+ if (options.json === true)
219
+ setMachineOutputMode(true);
220
+ await runFurnaceValidate(projectRoot, name, options);
221
+ if (isMachineOutputMode() !== previousMachineOutputMode) {
222
+ setMachineOutputMode(previousMachineOutputMode);
223
+ }
224
+ }
225
+ async function runFurnaceValidate(projectRoot, name, options) {
124
226
  intro('Furnace Validate');
125
227
  // Gains the engine rung here: this was the one site in the family that
126
228
  // checked furnace.json alone, so a validate against a missing engine
127
229
  // produced downstream noise instead of the shared precondition refusal.
128
230
  const { config } = await assertFurnaceReady(projectRoot);
129
231
  const furnacePaths = getFurnacePaths(projectRoot);
130
- let totalErrors = 0;
131
- let totalWarnings = 0;
132
- let componentCount;
133
- let allIssues = [];
134
- if (name) {
135
- // --- Single component validation ---
136
- let type;
137
- let componentDir;
138
- if (name in config.overrides) {
139
- type = 'override';
140
- componentDir = join(furnacePaths.overridesDir, name);
141
- }
142
- else if (name in config.custom) {
143
- type = 'custom';
144
- componentDir = join(furnacePaths.customDir, name);
145
- }
146
- else if (config.stock.includes(name)) {
147
- info(`"${name}" is a stock component. Stock components are not validated locally.`);
148
- outro('Validation complete');
149
- return;
150
- }
151
- else {
152
- throw new FurnaceError(`Component "${name}" not found in furnace.json.`, name);
153
- }
154
- if (!(await pathExists(componentDir))) {
155
- throw new FurnaceError(`Component directory not found for "${name}".`, name);
156
- }
157
- const issues = await validateComponent(componentDir, name, type, config, projectRoot);
158
- componentCount = 1;
159
- allIssues = issues;
160
- if (issues.length === 0) {
161
- success(`${name} — all checks passed`);
162
- }
163
- else {
164
- const [e, w] = displayValidationIssues(issues);
165
- totalErrors += e;
166
- totalWarnings += w;
167
- }
168
- }
169
- else {
170
- // --- Validate all components ---
171
- const overrideCount = Object.keys(config.overrides).length;
172
- const customCount = Object.keys(config.custom).length;
173
- if (overrideCount === 0 && customCount === 0) {
174
- info('No components to validate.');
175
- outro('Done');
176
- return;
177
- }
178
- if (config.stock.length > 0) {
179
- info(`Skipping ${config.stock.length} stock component(s) (no local files to validate).`);
232
+ const run = await runValidationPass(projectRoot, name, config, furnacePaths);
233
+ if (run === undefined) {
234
+ const floor = componentFloorIssue(config, name, 0);
235
+ if (options.json === true) {
236
+ renderValidateJson({
237
+ components: [],
238
+ outcome: { issues: floor ? [floor] : [], excepted: [], stale: [] },
239
+ maxWarnings: options.maxWarnings,
240
+ });
180
241
  }
181
- const results = await validateAllComponents(projectRoot);
182
- componentCount = results.size;
183
- for (const [componentName, issues] of results) {
184
- allIssues.push(...issues);
185
- if (issues.length === 0) {
186
- success(`${componentName} — all checks passed`);
187
- }
188
- else {
189
- const [e, w] = displayValidationIssues(issues);
190
- totalErrors += e;
191
- totalWarnings += w;
192
- }
242
+ if (floor !== undefined) {
243
+ renderValidateHuman([], [floor]);
244
+ throw new FurnaceError('Validation failed with 1 error(s).');
193
245
  }
246
+ outro(name ? 'Validation complete' : 'Done');
247
+ return;
194
248
  }
195
- // Auto-fix fixable issues when --fix is passed. The counter returned by
196
- // `autoFixIssues` only counts function calls that did not throw, so a
197
- // write that succeeded but did not actually resolve the issue
198
- // (addJarMnEntries appending to a file mach later ignores) would still
199
- // bump it. Re-validate the affected components and compute the real drop
200
- // in fixable issues so the reported number is honest.
201
- if (options.fix && allIssues.length > 0) {
202
- const fixableIssues = allIssues.filter((issue) => FIXABLE_CHECKS.has(issue.check));
203
- if (fixableIssues.length > 0) {
204
- await autoFixIssues(projectRoot, fixableIssues);
205
- const reValidated = await reValidateComponents(projectRoot, config, furnacePaths, new Set(fixableIssues.map((issue) => issue.component)));
206
- const fixableBefore = fixableIssues.length;
207
- const fixableAfter = reValidated.issues.filter((issue) => FIXABLE_CHECKS.has(issue.check)).length;
208
- const actuallyFixed = Math.max(0, fixableBefore - fixableAfter);
209
- // Replace the pre-fix issue totals with the post-fix view so the
210
- // summary reflects current reality. Issues that auto-fix could not
211
- // address still count toward totalErrors / totalWarnings.
212
- totalErrors = reValidated.totalErrors;
213
- totalWarnings = reValidated.totalWarnings;
214
- if (actuallyFixed > 0) {
215
- info(`\nAuto-fixed ${actuallyFixed} issue(s).`);
216
- }
217
- if (fixableAfter > 0) {
218
- warn(`${fixableAfter} fixable issue(s) remain after auto-fix — investigate manually.`);
219
- }
220
- }
221
- else {
222
- info('\nNo auto-fixable issues found. Remaining issues require manual resolution.');
223
- }
249
+ const floor = componentFloorIssue(config, name, run.results.size);
250
+ const firstPass = exceptRun(run, config);
251
+ if (floor !== undefined)
252
+ firstPass.issues.push(floor);
253
+ renderValidateHuman([...run.results.keys()], firstPass.issues);
254
+ let outcome = firstPass;
255
+ if (options.fix && firstPass.issues.length > 0) {
256
+ await applyAutoFix(projectRoot, config, furnacePaths, run, firstPass.issues);
257
+ outcome = exceptRun(run, config);
258
+ if (floor !== undefined)
259
+ outcome.issues.push(floor);
224
260
  }
225
- // Summary
226
- note(`${totalErrors} error(s), ${totalWarnings} warning(s) across ${componentCount} component(s)`, 'Validation Summary');
227
- if (totalErrors > 0) {
261
+ const report = {
262
+ components: [...run.results.keys()],
263
+ outcome,
264
+ maxWarnings: options.maxWarnings,
265
+ };
266
+ const verdict = judgeValidateReport(report);
267
+ const excepted = outcome.excepted.length > 0 ? `, ${outcome.excepted.length} excepted` : '';
268
+ note(`${verdict.errors} error(s), ${verdict.warnings} warning(s)${excepted} across ${report.components.length} component(s)`, 'Validation Summary');
269
+ if (options.json === true)
270
+ renderValidateJson(report);
271
+ if (verdict.errors > 0) {
228
272
  const fixHint = options.fix ? '' : ' Use --fix to auto-correct registration issues.';
229
273
  info(`Fix the errors above and run "fireforge furnace validate" again.${fixHint}`);
230
- throw new FurnaceError(`Validation failed with ${totalErrors} error(s).`);
274
+ throw new FurnaceError(`Validation failed with ${verdict.errors} error(s).`);
275
+ }
276
+ if (verdict.warningBudgetExceeded) {
277
+ throw new FurnaceError(`Validation failed: ${verdict.warnings} warning(s) exceed --max-warnings ${String(options.maxWarnings)}. ` +
278
+ 'Fix them, or record a reviewed finding in furnace.json validationExceptions.');
231
279
  }
232
280
  outro('Validation passed');
233
281
  }
234
282
  /**
235
283
  * Re-validates a specific set of components after an auto-fix pass and
236
- * returns the post-fix issue list with the recomputed error / warning
237
- * totals. Used by the `--fix` path to honestly report what auto-fix
238
- * actually accomplished.
284
+ * returns each one's post-fix findings. Used by the `--fix` path to
285
+ * honestly report what auto-fix actually accomplished.
239
286
  */
240
287
  async function reValidateComponents(projectRoot, config, furnacePaths, componentNames) {
241
- const issues = [];
242
- let totalErrors = 0;
243
- let totalWarnings = 0;
288
+ const results = new Map();
244
289
  for (const componentName of componentNames) {
245
290
  let type;
246
291
  let componentDir;
@@ -258,14 +303,7 @@ async function reValidateComponents(projectRoot, config, furnacePaths, component
258
303
  }
259
304
  if (!(await pathExists(componentDir)))
260
305
  continue;
261
- const componentIssues = await validateComponent(componentDir, componentName, type, config, projectRoot);
262
- issues.push(...componentIssues);
263
- for (const issue of componentIssues) {
264
- if (issue.severity === 'error')
265
- totalErrors += 1;
266
- else
267
- totalWarnings += 1;
268
- }
306
+ results.set(componentName, await validateComponent(componentDir, componentName, type, config, projectRoot));
269
307
  }
270
- return { issues, totalErrors, totalWarnings };
308
+ return results;
271
309
  }
@@ -1,6 +1,7 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { join } from 'node:path';
3
3
  import { validateAllComponents, validateComponent } from '../../core/furnace-validate.js';
4
+ import { applyValidationExceptions } from '../../core/furnace-validate-exceptions.js';
4
5
  import { FurnaceError } from '../../errors/furnace.js';
5
6
  import { pathExists } from '../../utils/fs.js';
6
7
  import { error, info, outro, success, warn } from '../../utils/logger.js';
@@ -36,6 +37,14 @@ function filterProjectedDryRunIssues(issues, actions) {
36
37
  return true;
37
38
  });
38
39
  }
40
+ /**
41
+ * Drops the findings `furnace.json#validationExceptions` records as
42
+ * reviewed, so a deploy does not fail on a finding `furnace validate`
43
+ * accepts. Staleness is `furnace validate`'s report, not a deploy's.
44
+ */
45
+ function withoutExceptedIssues(issues, config) {
46
+ return applyValidationExceptions(issues, config.validationExceptions, () => false).issues;
47
+ }
39
48
  function resolveNamedValidationTarget(name, config, furnacePaths) {
40
49
  if (name in config.overrides) {
41
50
  return {
@@ -67,7 +76,8 @@ async function validateNamedTarget(args) {
67
76
  throw new FurnaceError(`Component directory not found for "${name}".`, name);
68
77
  }
69
78
  const rawIssues = await validateComponent(target.componentDir, name, target.type, config, projectRoot);
70
- const issues = isDryRun ? filterProjectedDryRunIssues(rawIssues, dryRunActions) : rawIssues;
79
+ const counted = withoutExceptedIssues(rawIssues, config);
80
+ const issues = isDryRun ? filterProjectedDryRunIssues(counted, dryRunActions) : counted;
71
81
  if (issues.length === 0) {
72
82
  success(`${name} — all checks passed`);
73
83
  return [0, 0];
@@ -130,9 +140,10 @@ export async function runDeployValidation(options) {
130
140
  continue;
131
141
  }
132
142
  componentCount++;
143
+ const counted = withoutExceptedIssues(issues, config);
133
144
  const projectedIssues = isDryRun
134
- ? filterProjectedDryRunIssues(issues, dryRunActions)
135
- : issues;
145
+ ? filterProjectedDryRunIssues(counted, dryRunActions)
146
+ : counted;
136
147
  if (projectedIssues.length === 0) {
137
148
  success(`${componentName} — all checks passed`);
138
149
  }
@@ -3,10 +3,13 @@ import { join } from 'node:path';
3
3
  import { loadConfig } from '../core/config.js';
4
4
  import { getDiffForFilesAgainstHead } from '../core/git-diff.js';
5
5
  import { withPrivateGitIndex } from '../core/git-readonly-index.js';
6
- import { buildPatchQueueContext, countNonBinaryDiffLines, formatPatchLintIssue, lintExportedPatch, lintPatchQueue, resolvePatchSizeTier, } from '../core/patch-lint.js';
6
+ import { buildPatchQueueContext, formatPatchLintIssue, lintExportedPatch, lintPatchQueue, measurePatchSize, resolvePatchSizeTier, } from '../core/patch-lint.js';
7
7
  import { buildPerPatchLintCacheKey, getCachedPerPatchLintIssues, getPerPatchLintCacheHeadSha, loadPerPatchLintCache, savePerPatchLintCache, setCachedPerPatchLintIssues, } from '../core/patch-lint-cache.js';
8
+ import { buildLintIgnoreChecks, evaluateLintIgnoreCeilings, formatLintIgnoreEntry, isLintIgnoreCeiling, lintIgnoreCacheTokens, removableCeilingIssues, } from '../core/patch-lint-ignore.js';
8
9
  import { loadPatchesManifest } from '../core/patch-manifest.js';
9
10
  import { evaluatePatchPolicy } from '../core/patch-policy.js';
11
+ import { evaluatePatchPolicyContent } from '../core/patch-policy-content.js';
12
+ import { selectPatchSubset } from '../core/patch-subset.js';
10
13
  import { GeneralError } from '../errors/base.js';
11
14
  import { mapWithConcurrency } from '../utils/concurrency.js';
12
15
  import { pathExists } from '../utils/fs.js';
@@ -47,7 +50,7 @@ async function lintQueuedPatch(patch, lintCtx) {
47
50
  usedCheckJs: false,
48
51
  };
49
52
  }
50
- const ignore = patch.lintIgnore?.length ? new Set(patch.lintIgnore) : undefined;
53
+ const ignore = buildLintIgnoreChecks(patch.lintIgnore);
51
54
  let cacheKey;
52
55
  if (cache) {
53
56
  cacheKey = await buildPerPatchLintCacheKey({
@@ -60,7 +63,7 @@ async function lintQueuedPatch(patch, lintCtx) {
60
63
  queueContext: ctx,
61
64
  ...(engineHeadSha === undefined ? {} : { engineHeadSha }),
62
65
  });
63
- const cached = getCachedPerPatchLintIssues(cache, patch.filename, cacheKey, ignore);
66
+ const cached = getCachedPerPatchLintIssues(cache, patch.filename, cacheKey, lintIgnoreCacheTokens(ignore));
64
67
  if (cached) {
65
68
  // Returning before the empty-diff probe below is safe: the cache key
66
69
  // hashes every affected engine file's content plus engineHeadSha, so
@@ -106,8 +109,13 @@ async function lintQueuedPatch(patch, lintCtx) {
106
109
  }
107
110
  lintOptions.precomputedCheckJs = precomputedCheckJs;
108
111
  }
112
+ // Measured from the working tree, like every other rule here: the files
113
+ // on disk are the would-be export, so a size tier fires mid-edit rather
114
+ // than first at the re-export.
115
+ const patchSize = await measurePatchSize(paths.engine, diff, existing, config);
109
116
  const patchIssues = await lintExportedPatch(paths.engine, existing, diff, config, {
110
117
  ...lintOptions,
118
+ patchSizeMeasurement: patchSize,
111
119
  patchQueueCtx: ctx,
112
120
  ...(ignore ? { ignoreChecks: ignore } : {}),
113
121
  ...(patch.tier ? { patchTier: patch.tier } : {}),
@@ -117,11 +125,11 @@ async function lintQueuedPatch(patch, lintCtx) {
117
125
  existingFiles: existing,
118
126
  rawIssues: patchIssues,
119
127
  suppressedIssues,
120
- lineCount: countNonBinaryDiffLines(diff).textLines,
128
+ lineCount: patchSize.lines,
121
129
  usedCheckJs,
122
130
  };
123
131
  if (cache && cacheKey) {
124
- result.cacheWrite = { key: cacheKey, lintIgnore: [...(ignore ?? [])] };
132
+ result.cacheWrite = { key: cacheKey, lintIgnore: lintIgnoreCacheTokens(ignore) };
125
133
  }
126
134
  return result;
127
135
  }
@@ -183,6 +191,9 @@ async function applyPerPatchResults(subset, results, issues, checkJs, cache, hel
183
191
  for (const issue of result.rawIssues) {
184
192
  issues.push({ ...issue, file: `${patch.filename} :: ${issue.file}` });
185
193
  }
194
+ // A ceiling the patch no longer needs is reported as a notice naming
195
+ // the command that drops it.
196
+ issues.push(...removableCeilingIssues(patch.filename, evaluateLintIgnoreCeilings(patch.lintIgnore, [...result.rawIssues, ...result.suppressedIssues], { lines: result.lineCount, files: patch.filesAffected.length })));
186
197
  // A waived size finding still reports its measurement: the finding stays
187
198
  // suppressed (no exit-code / --max-warnings effect) but the current count
188
199
  // is readable from the tool that enforces it, so a waiver's cited size
@@ -194,7 +205,10 @@ async function applyPerPatchResults(subset, results, issues, checkJs, cache, hel
194
205
  heldNotices.push({ check: suppressedIssue.check, patch: patch.filename, suppressed: true });
195
206
  continue;
196
207
  }
197
- info(`NOTICE [${suppressedIssue.check}] ${patch.filename}: suppressed by lintIgnore — ${suppressedIssue.message}`);
208
+ const ceilings = (patch.lintIgnore ?? []).filter((entry) => isLintIgnoreCeiling(entry) && entry.rule === suppressedIssue.check);
209
+ const ceiling = ceilings.find((entry) => isLintIgnoreCeiling(entry) && entry.share === suppressedIssue.share) ?? ceilings[0];
210
+ const waiver = ceiling === undefined ? 'lintIgnore' : `lintIgnore (${formatLintIgnoreEntry(ceiling)})`;
211
+ info(`NOTICE [${suppressedIssue.check}] ${patch.filename}: suppressed by ${waiver} — ${suppressedIssue.message}`);
198
212
  }
199
213
  totals.suppressed += result.suppressedIssues.length;
200
214
  if (result.status === 'cached')
@@ -309,57 +323,9 @@ function reportNoticeSummary(heldNotices, issues) {
309
323
  info(`NOTICE [${check}]: ${entry.count} across ${entry.patches.size} patch(es)${suppressed} (see --report)`);
310
324
  }
311
325
  }
312
- /**
313
- * Resolves the `--patches <name…>` subset filter against the manifest,
314
- * matching each requested name tolerantly (exact filename, filename ±
315
- * `.patch`, the bare order number, or the manifest `name` field). Throws listing the available
316
- * patches when a requested name matches none, so a typo fails loud rather
317
- * than silently linting nothing.
318
- */
319
- function selectPatchSubset(manifest, requested) {
320
- const normalizedRequests = requested
321
- .flatMap((entry) => entry.split(','))
322
- .map((entry) => entry.trim())
323
- .filter((entry) => entry.length > 0);
324
- const patchAliases = (p) => {
325
- const aliases = new Set([p.filename, p.filename.replace(/\.patch$/, ''), p.name]);
326
- // The bare order number, both as the manifest records it and as the
327
- // filename spells it (`2` and `002`). The refusal message offers stems,
328
- // and an operator reading `102-ui-canvas-tiles.patch` in a lint finding
329
- // reaches for `102` first.
330
- aliases.add(String(p.order));
331
- const match = /^(\d+)-([a-z]+)-(.+)\.patch$/.exec(p.filename);
332
- if (match?.[1])
333
- aliases.add(match[1]);
334
- if (match?.[2] && match[3]) {
335
- aliases.add(`${match[2]}-${match[3]}`);
336
- aliases.add(match[3]);
337
- }
338
- return aliases;
339
- };
340
- const matches = (p, name) => {
341
- const stem = name.replace(/\.patch$/, '');
342
- return (patchAliases(p).has(name) || patchAliases(p).has(stem) || patchAliases(p).has(`${stem}.patch`));
343
- };
344
- const selected = [];
345
- const seen = new Set();
346
- for (const name of normalizedRequests) {
347
- const found = manifest.patches.filter((p) => matches(p, name));
348
- if (found.length === 0) {
349
- const available = manifest.patches.map((p) => p.filename).join(', ');
350
- throw new GeneralError(`No patch in the queue matches "${name}". In --per-patch mode, positional arguments ` +
351
- `and --patches select patches (filename, stem, order number, manifest name, or slug) — not engine ` +
352
- `files; drop --per-patch to lint engine paths. Available patches: ${available}`);
353
- }
354
- for (const p of found) {
355
- if (!seen.has(p.filename)) {
356
- seen.add(p.filename);
357
- selected.push(p);
358
- }
359
- }
360
- }
361
- return selected;
362
- }
326
+ /** The refusal sentence `selectPatchSubset` adds for a `lint --per-patch` miss. */
327
+ const PER_PATCH_SUBSET_HINT = 'In --per-patch mode, positional arguments and --patches select patches (filename, stem, ' +
328
+ 'order number, manifest name, or slug) — not engine files; drop --per-patch to lint engine paths.';
363
329
  /**
364
330
  * Lints each patch in the queue as its own isolated diff, honouring
365
331
  * per-patch `lintIgnore` entries. Cross-patch rules still run once over
@@ -382,7 +348,7 @@ async function lintPerPatchInner(projectRoot, paths, options = {}) {
382
348
  return;
383
349
  }
384
350
  const subset = options.patches && options.patches.length > 0
385
- ? selectPatchSubset(manifest, options.patches)
351
+ ? selectPatchSubset(manifest, options.patches, PER_PATCH_SUBSET_HINT)
386
352
  : manifest.patches;
387
353
  const subsetNames = new Set(subset.map((p) => p.filename));
388
354
  const isSubset = subset.length !== manifest.patches.length;
@@ -405,7 +371,11 @@ async function lintPerPatchInner(projectRoot, paths, options = {}) {
405
371
  const cache = options.noCache === true ? undefined : await loadPerPatchLintCache(projectRoot);
406
372
  const engineHeadSha = cache ? await getPerPatchLintCacheHeadSha(paths.engine) : undefined;
407
373
  const issues = [];
408
- for (const issue of evaluatePatchPolicy(config, manifest)) {
374
+ const policyIssues = [
375
+ ...evaluatePatchPolicy(config, manifest),
376
+ ...(await evaluatePatchPolicyContent(config, projectRoot, manifest, ctx.entries)),
377
+ ];
378
+ for (const issue of policyIssues) {
409
379
  if (isSubset && !subsetNames.has(issue.filename))
410
380
  continue;
411
381
  issues.push({
@@ -433,10 +403,15 @@ async function lintPerPatchInner(projectRoot, paths, options = {}) {
433
403
  });
434
404
  const heldNotices = options.notices === 'summary' ? [] : undefined;
435
405
  const { linted, skipped, cacheDirty, reusedCacheEntries, suppressed } = await applyPerPatchResults(subset, results, issues, checkJs, cache, heldNotices);
406
+ const queueIssues = [];
436
407
  for (const issue of lintPatchQueue(ctx)) {
437
- if (isSubset && !subsetTouchedFiles.has(issue.file))
408
+ // A finding that names a subset patch stays in scope even when its file
409
+ // is one the patch no longer touches (a stale staged declaration).
410
+ const namesSubset = issue.patches?.some((name) => subsetNames.has(name)) === true;
411
+ if (isSubset && !namesSubset && !subsetTouchedFiles.has(issue.file))
438
412
  continue;
439
413
  issues.push(issue);
414
+ queueIssues.push(issue);
440
415
  }
441
416
  if (cache && cacheDirty)
442
417
  await savePerPatchLintCache(projectRoot, cache);
@@ -447,7 +422,7 @@ async function lintPerPatchInner(projectRoot, paths, options = {}) {
447
422
  info(`Suppressed ${suppressed} issue(s) via per-patch lintIgnore.`);
448
423
  }
449
424
  if (options.report !== undefined) {
450
- await writePerPatchLintReport(options.report, subset, results);
425
+ await writePerPatchLintReport(options.report, subset, results, config, queueIssues);
451
426
  }
452
427
  reportPerPatchOutcome(issues, linted, skipped, options, heldNotices);
453
428
  }
@@ -21,7 +21,7 @@
21
21
  * cold" via `probeCheckJsGlobalIssues` instead of building anything.
22
22
  */
23
23
  import { invokePatchLintCheckJsGrouped, probeCheckJsGlobalIssues, runCheckJsTestFilesGrouped, } from '../core/patch-lint-checkjs.js';
24
- import { isTestScriptFile, resolvePatchOwnedSysMjs, resolvePatchOwnedTestScripts, } from '../core/patch-lint-ownership.js';
24
+ import { isTestScriptFile, resolvableModulesForTests, resolvePatchOwnedSysMjs, resolvePatchOwnedTestScripts, } from '../core/patch-lint-ownership.js';
25
25
  /**
26
26
  * Builds the per-run checkJs program controller when `patchLint.checkJs` is
27
27
  * enabled, or returns undefined. With `rootScopePatches` set (the
@@ -69,7 +69,7 @@ export function buildPerRunCheckJs(projectRoot, paths, config, ctx, rootScopePat
69
69
  const sys = await invokePatchLintCheckJsGrouped(paths.engine, resolvePatchOwnedSysMjs(new Set(), ctx), patchLint, projectRoot, scopedRoots);
70
70
  if (!testFilesEnabled)
71
71
  return sys;
72
- const tests = await runCheckJsTestFilesGrouped(paths.engine, resolvePatchOwnedTestScripts(new Set(), ctx), patchLint, projectRoot, scopedRoots);
72
+ const tests = await runCheckJsTestFilesGrouped(paths.engine, resolvePatchOwnedTestScripts(new Set(), ctx), patchLint, projectRoot, scopedRoots, resolvableModulesForTests(ctx));
73
73
  const byFile = new Map(sys.byFile);
74
74
  for (const [rel, list] of tests.byFile) {
75
75
  byFile.set(rel, [...(byFile.get(rel) ?? []), ...list]);
@@ -1,4 +1,5 @@
1
1
  import type { PatchLintIssue, PatchMetadata } from '../types/commands/index.js';
2
+ import type { FireForgeConfig } from '../types/config.js';
2
3
  /**
3
4
  * Structural view of one per-patch lint outcome. Mirrors the relevant
4
5
  * fields of lint-per-patch's QueuedPatchResult without importing it:
@@ -14,8 +15,11 @@ export interface PerPatchReportResult {
14
15
  }
15
16
  /**
16
17
  * Writes the per-patch lint report JSON. Called after the per-patch
17
- * results are assembled in patch order. Queue-level and policy findings
18
- * are patch-attributed by their `file`/`filename` fields upstream and
19
- * are not duplicated here. The report is the per-patch view.
18
+ * results are assembled in patch order. Policy findings are
19
+ * patch-attributed by their `filename` field upstream and are not
20
+ * duplicated here. Cross-patch findings (`forward-import`,
21
+ * `forward-registration`, …) go in `queueIssues`, with the
22
+ * `stagedDeclarations` that discharge them, so the report can be handed to
23
+ * `patch staged-dependency --from` as is.
20
24
  */
21
- export declare function writePerPatchLintReport(reportPath: string, subset: readonly PatchMetadata[], results: readonly PerPatchReportResult[]): Promise<void>;
25
+ export declare function writePerPatchLintReport(reportPath: string, subset: readonly PatchMetadata[], results: readonly PerPatchReportResult[], config: Pick<FireForgeConfig, 'patchLint'>, queueIssues?: readonly PatchLintIssue[]): Promise<void>;
@@ -10,16 +10,21 @@
10
10
  * none is wanted.
11
11
  */
12
12
  import { getPatchSizeThresholds, resolvePatchSizeTier, } from '../core/patch-lint.js';
13
+ import { evaluateLintIgnoreCeilings, evaluateLintIgnoreWaivers, } from '../core/patch-lint-ignore.js';
13
14
  import { writeJson } from '../utils/fs.js';
14
15
  import { info } from '../utils/logger.js';
15
16
  const LINT_REPORT_SCHEMA_VERSION = 1;
16
17
  /**
17
18
  * Writes the per-patch lint report JSON. Called after the per-patch
18
- * results are assembled in patch order. Queue-level and policy findings
19
- * are patch-attributed by their `file`/`filename` fields upstream and
20
- * are not duplicated here. The report is the per-patch view.
19
+ * results are assembled in patch order. Policy findings are
20
+ * patch-attributed by their `filename` field upstream and are not
21
+ * duplicated here. Cross-patch findings (`forward-import`,
22
+ * `forward-registration`, …) go in `queueIssues`, with the
23
+ * `stagedDeclarations` that discharge them, so the report can be handed to
24
+ * `patch staged-dependency --from` as is.
21
25
  */
22
- export async function writePerPatchLintReport(reportPath, subset, results) {
26
+ export async function writePerPatchLintReport(reportPath, subset, results, config, queueIssues = []) {
27
+ const metric = config.patchLint?.patchSizeMetric ?? 'diff';
23
28
  const patches = [];
24
29
  let errors = 0;
25
30
  let warnings = 0;
@@ -32,16 +37,25 @@ export async function writePerPatchLintReport(reportPath, subset, results) {
32
37
  if (!patch || !result)
33
38
  continue;
34
39
  const tier = resolvePatchSizeTier(result.existingFiles, patch.tier);
40
+ const findings = [...result.rawIssues, ...result.suppressedIssues];
41
+ const measurements = { lines: result.lineCount, files: patch.filesAffected.length };
35
42
  patches.push({
36
43
  filename: patch.filename,
37
44
  name: patch.name,
38
45
  status: result.status,
46
+ metric,
39
47
  lineCount: result.lineCount,
40
48
  filesAffected: patch.filesAffected.length,
41
49
  tier,
42
- thresholds: getPatchSizeThresholds(tier.tier),
50
+ thresholds: getPatchSizeThresholds(tier.tier, config),
43
51
  issues: result.rawIssues,
44
52
  suppressedIssues: result.suppressedIssues,
53
+ lintIgnoreCeilings: result.status === 'skipped'
54
+ ? []
55
+ : evaluateLintIgnoreCeilings(patch.lintIgnore, findings, measurements),
56
+ lintIgnoreWaivers: result.status === 'skipped'
57
+ ? []
58
+ : evaluateLintIgnoreWaivers(patch.lintIgnore, findings, measurements),
45
59
  });
46
60
  if (result.status === 'skipped')
47
61
  skipped++;
@@ -55,6 +69,7 @@ export async function writePerPatchLintReport(reportPath, subset, results) {
55
69
  schemaVersion: LINT_REPORT_SCHEMA_VERSION,
56
70
  generatedAt: new Date().toISOString(),
57
71
  patches,
72
+ queueIssues,
58
73
  totals: { linted, skipped, errors, warnings, suppressed },
59
74
  });
60
75
  info(`Per-patch lint report written to ${reportPath}.`);