@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
@@ -8,9 +8,12 @@
8
8
  * the orchestrator would go the wrong way.
9
9
  */
10
10
  import { Command, Option } from 'commander';
11
+ import { getProjectPaths } from '../core/config.js';
11
12
  import { withEngineSessionLock } from '../core/engine-session-lock.js';
13
+ import { withPrivateGitIndex } from '../core/git-readonly-index.js';
12
14
  import { addWaitLockOption, pickDefined, resolveWaitLockSeconds, stringListOption, } from '../utils/options.js';
13
15
  import { reExportCommand } from './re-export.js';
16
+ import { preflightReExportArguments, readPathListFile } from './re-export-preflight.js';
14
17
  /** Registers the re-export command on the CLI program. */
15
18
  export function registerReExport(program, { getProjectRoot, withErrorHandling }) {
16
19
  const reExport = program
@@ -20,41 +23,77 @@ export function registerReExport(program, { getProjectRoot, withErrorHandling })
20
23
  'rebase for source metadata stamping.')
21
24
  .option('-a, --all', 'Re-export all patches')
22
25
  .option('-s, --scan', 'Scan directories for new/removed files and update filesAffected')
23
- .option('--scan-file <path>', 'With --scan, add this explicit engine-relative file to one target patch without collecting adjacent files. Repeatable.', ...stringListOption())
26
+ .option('--scan-file <path>', 'With --scan, add this explicit engine-relative file without collecting adjacent files. Prefix the owning patch (<patch>:<path>, required with several target patches) to adopt into one of them; selected patches without a --scan-file refresh scan-less. Repeatable.', ...stringListOption())
24
27
  .option('--scan-files <manifest>', 'With --scan, bulk-assign generated files from a JSON manifest: {"assignments":[{"patch":"002-name.patch","files":["path"]}]}. Selects patches from the manifest.')
25
28
  .option('--files <paths>', 'Restrict the re-exported filesAffected to this comma-separated list (single target patch only)', (value) => value
26
29
  .split(',')
27
30
  .map((v) => v.trim())
28
31
  .filter((v) => v.length > 0))
29
- .option('--refuse-adjacent-unmanaged', 'Refuse a scan-less re-export (non-zero exit, patch not written) when unmanaged files exist adjacent to the patch ownership, instead of warning. Mutually exclusive with --scan and --files.')
30
- .option('--refuse-foreign-drift', "Refuse a scan-less re-export (non-zero exit, patch not written) when the refreshed body would absorb engine lines not present in the old patch body — protects multi-session checkouts from silently capturing another session's uncommitted edits. Mutually exclusive with --scan and --files.")
32
+ .option('--refuse-adjacent-unmanaged', 'Refuse a re-export (non-zero exit, patch not written) when unmanaged files exist adjacent to the patch ownership, instead of warning. Adjacent means the same directory as an owned file (not a sibling or nested directory), or named by a registration line the patch adds. Beside --scan it judges the files the scan did not adopt. Mutually exclusive with --files.')
33
+ .option('--refuse-foreign-drift', "Refuse a re-export (non-zero exit, patch not written) when the refreshed body would add or drop lines, in a file the patch already owns, that the old patch body did not carry — protects multi-session checkouts from silently capturing another session's uncommitted edits. Beside --scan it guards each patch's existing files (adopted files count as expected). Mutually exclusive with --files.")
31
34
  .option('--expect <path>', "With --refuse-foreign-drift, name an engine-relative file whose drift is this session's intended edit: drift confined to --expect files proceeds, drift anywhere else still refuses. Repeatable, and the whitelist applies across EVERY patch in this invocation, not per patch — so pass all the patches and all their --expect paths in one call rather than looping one patch at a time.", ...stringListOption())
32
35
  .option('--expect-unmanaged <path>', 'With --refuse-adjacent-unmanaged, name an engine-relative file that is a REVIEWED, recorded exception to the adjacency refusal: an approved unmanaged path is still listed, but no longer refuses. Repeatable, and the carve-out applies across EVERY patch in this invocation, not per patch. Use it to keep the belt armed for everything else instead of dropping --refuse-adjacent-unmanaged entirely.', ...stringListOption())
33
- .option('--expect-removed <path>', 'Retire an absent file created by the selected patch while keeping scan-less refusals armed. Repeatable.', ...stringListOption())
36
+ .option('--expect-from <file>', 'Read --expect paths from a file, one engine-relative path per line (blank lines and # comments ignored). Adds to any --expect flags.')
37
+ .option('--expect-new <path>', 'With --refuse-adjacent-unmanaged, name a file this session created and will adopt with its next export (or --scan-file): it passes the belt and is reported, while every other adjacent file still refuses. Must be changed in engine/ and owned by no patch. Repeatable.', ...stringListOption())
38
+ .option('--adjacent-ignore <path|glob>', 'With --refuse-adjacent-unmanaged, a reviewed ignore-list entry: an engine-relative path, or a glob whose * stays within one path segment (** is refused). Matches are reported, never refused. Repeatable.', ...stringListOption())
39
+ .option('--expect-removed <path>', 'Retire a file from the selected patch while keeping scan-less refusals armed: an absent file the patch created, or a patch-modified upstream file whose engine copy is back at HEAD. Repeatable.', ...stringListOption())
34
40
  .option('--dry-run', 'Show what would change without writing')
35
41
  .option('--skip-lint', 'Skip patch lint checks (downgrade errors to warnings)')
36
42
  .option('--no-cache', 'Bypass per-patch lint result cache reads and writes for this re-export')
37
43
  .option('--allow-stale-furnace', 'Export the deployed engine copy even when the components/ source changed since the last furnace apply')
38
44
  .option('--allow-shrink', 'Allow --files to remove paths currently owned by the patch. Required before --yes can bypass the shrink confirmation.')
45
+ .option('--declarations-out <path>', 'Where a refused --scan adoption writes its one --from-ready staged-declaration report (default: .fireforge/declarations/<timestamp>.json).')
46
+ .option('--hunks-from <diff>', "Export each file the diff names as the patch's current body plus only the diff's hunks, so another session's edits in the same file stay out. One explicit patch; not with --scan, --files or --all.")
47
+ .option('--exclude-hunks <diff>', "Export each file the diff names as the working tree minus the diff's hunks. One explicit patch; not with --scan, --files or --all.")
39
48
  .option('-y, --yes', 'Skip confirmation prompts (required for non-TTY destructive writes)')
40
49
  .option('--force-unsafe', 'Bypass cross-patch lint refusal when --files shrinks a patch')
41
50
  .option('--stamp', "After every selected patch refreshes cleanly, stamp each re-exported patch's sourceVersion/sourceProduct in patches.json to firefox.version/firefox.product from fireforge.json. No effect on a partial run.")
42
51
  .addOption(new Option('--tier <tier>', 'Force a tier override on the selected patch (only "branding" recognised). Mutually exclusive with --all.').choices(['branding']))
43
52
  .option('--lint-ignore <check-id>', 'Append a lint check ID to the patch\'s PatchMetadata.lintIgnore (union, de-duped, repeatable). Mutually exclusive with --all. Use "fireforge patch lint-ignore" for --remove / --clear.', ...stringListOption());
44
53
  addWaitLockOption(reExport).action(withErrorHandling(async (patches, options) => {
45
- const { tier, lintIgnore, scanFile, scanFiles, cache, expect, expectUnmanaged, ...rest } = options;
46
54
  const projectRoot = getProjectRoot();
47
- await withEngineSessionLock(projectRoot, 're-export', () => reExportCommand(projectRoot, patches, {
48
- ...pickDefined(rest),
49
- ...(scanFile !== undefined && scanFile.length > 0 ? { scanFiles: scanFile } : {}),
50
- ...(scanFiles !== undefined ? { scanFilesManifest: scanFiles } : {}),
51
- ...(tier !== undefined ? { tier: tier } : {}),
52
- ...(lintIgnore !== undefined && lintIgnore.length > 0 ? { lintIgnore } : {}),
53
- ...(expect !== undefined && expect.length > 0 ? { expect } : {}),
54
- ...(expectUnmanaged !== undefined && expectUnmanaged.length > 0
55
- ? { expectUnmanaged }
56
- : {}),
57
- ...(cache === false ? { noCache: true } : {}),
58
- }), { waitLockSeconds: resolveWaitLockSeconds(options.waitLock) });
55
+ const reExportOptions = await toReExportOptions(options);
56
+ // Refuse what the locked run would refuse before queueing for the
57
+ // lock, which can be a long wait under contention.
58
+ await preflightReExportArguments(projectRoot, patches, reExportOptions);
59
+ // A dry run writes nothing, so it does not queue behind peers' builds
60
+ // and test runs for the engine lock. Its git plumbing runs on a
61
+ // private index, so not even git's stat-cache refresh reaches the
62
+ // shared `.git/index`; dry runs still serialize among themselves.
63
+ if (reExportOptions.dryRun === true) {
64
+ const engineDir = getProjectPaths(projectRoot).engine;
65
+ await withPrivateGitIndex(engineDir, () => reExportCommand(projectRoot, patches, reExportOptions));
66
+ return;
67
+ }
68
+ await withEngineSessionLock(projectRoot, 're-export', () => reExportCommand(projectRoot, patches, reExportOptions), { waitLockSeconds: resolveWaitLockSeconds(options.waitLock) });
59
69
  }));
60
70
  }
71
+ /**
72
+ * Maps commander's option names onto {@link ReExportOptions}, reading the
73
+ * `--expect-from` list into `expect` and dropping empty repeatables.
74
+ */
75
+ async function toReExportOptions(options) {
76
+ const { tier, lintIgnore, scanFile, scanFiles, cache, expect: expectFlags, expectFrom, expectUnmanaged, expectNew, adjacentIgnore, ...rest } = options;
77
+ const expect = [
78
+ ...(expectFlags ?? []),
79
+ ...(expectFrom !== undefined ? await readPathListFile(expectFrom, '--expect-from') : []),
80
+ ];
81
+ return {
82
+ ...pickDefined({
83
+ ...rest,
84
+ scanFiles: nonEmpty(scanFile),
85
+ scanFilesManifest: scanFiles,
86
+ tier: tier,
87
+ lintIgnore: nonEmpty(lintIgnore),
88
+ expect: nonEmpty(expect),
89
+ expectUnmanaged: nonEmpty(expectUnmanaged),
90
+ expectNew: nonEmpty(expectNew),
91
+ adjacentIgnore: nonEmpty(adjacentIgnore),
92
+ }),
93
+ ...(cache === false ? { noCache: true } : {}),
94
+ };
95
+ }
96
+ /** The list when it has entries, else undefined (which `pickDefined` drops). */
97
+ function nonEmpty(values) {
98
+ return values !== undefined && values.length > 0 ? values : undefined;
99
+ }
@@ -1,4 +1,15 @@
1
1
  import type { PatchMetadata, ReExportOptions } from '../types/commands/index.js';
2
2
  import type { ProjectPaths } from '../types/config.js';
3
- /** Validates every retirement before any patch is written; typos never silently grant a carve-out. */
4
- export declare function validateExpectedRemovals(paths: ProjectPaths, patches: PatchMetadata[], options: ReExportOptions): Promise<string[]>;
3
+ /**
4
+ * Validates every retirement before any patch is written; typos never
5
+ * silently grant a carve-out. Two shapes retire a file from its one
6
+ * selected owner:
7
+ *
8
+ * - a file the patch created (`new file mode` section), now absent from
9
+ * disk and untracked in engine HEAD;
10
+ * - an upstream file the patch modified or deleted, whose engine copy is
11
+ * back at HEAD (present, tracked, no diff). A scan-less re-export keeps
12
+ * `filesAffected` as it is, so without this the stale entry would stay
13
+ * in the manifest and in the reserved-range policy check.
14
+ */
15
+ export declare function validateExpectedRemovals(paths: ProjectPaths, patches: PatchMetadata[], options: ReExportOptions, queue?: readonly PatchMetadata[]): Promise<string[]>;
@@ -2,32 +2,45 @@
2
2
  import { lstat } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
4
  import { listTrackedInHead } from '../core/git-file-ops.js';
5
+ import { getDirtyFiles } from '../core/git-status.js';
5
6
  import { parseDiffSections } from '../core/patch-parse.js';
6
7
  import { InvalidArgumentError } from '../errors/base.js';
7
8
  import { getNodeErrorCode } from '../utils/errors.js';
8
9
  import { readText } from '../utils/fs.js';
9
10
  import { normalizeEngineRelativeInput } from './re-export-scan.js';
10
- /** Validates every retirement before any patch is written; typos never silently grant a carve-out. */
11
- export async function validateExpectedRemovals(paths, patches, options) {
11
+ /**
12
+ * Validates every retirement before any patch is written; typos never
13
+ * silently grant a carve-out. Two shapes retire a file from its one
14
+ * selected owner:
15
+ *
16
+ * - a file the patch created (`new file mode` section), now absent from
17
+ * disk and untracked in engine HEAD;
18
+ * - an upstream file the patch modified or deleted, whose engine copy is
19
+ * back at HEAD (present, tracked, no diff). A scan-less re-export keeps
20
+ * `filesAffected` as it is, so without this the stale entry would stay
21
+ * in the manifest and in the reserved-range policy check.
22
+ */
23
+ export async function validateExpectedRemovals(paths, patches, options, queue = patches) {
12
24
  const removed = [
13
25
  ...new Set((options.expectRemoved ?? []).map((file) => normalizeEngineRelativeInput(file, '--expect-removed'))),
14
26
  ];
15
27
  if (removed.length === 0)
16
28
  return removed;
17
29
  const tracked = await listTrackedInHead(paths.engine, removed);
30
+ const dirty = new Set(await getDirtyFiles(paths.engine, [...tracked]));
18
31
  for (const file of removed) {
19
32
  const owners = patches.filter((patch) => patch.filesAffected.includes(file));
20
- if (owners.length !== 1 ||
21
- tracked.has(file) ||
22
- (await removalPathPresent(join(paths.engine, file)))) {
23
- throw new InvalidArgumentError(`--expect-removed ${file} must be absent, untracked in engine HEAD, and owned by exactly one selected patch.`, '--expect-removed');
24
- }
25
33
  const owner = owners[0];
26
- if (!owner)
27
- throw new InvalidArgumentError(`No selected owner for ${file}`, '--expect-removed');
28
- const body = await readText(join(paths.patches, owner.filename));
29
- if (!parseDiffSections(body).some((section) => section.targetPath === file && section.isNewFile)) {
30
- throw new InvalidArgumentError(`--expect-removed ${file} was not created by its selected patch.`, '--expect-removed');
34
+ if (owners.length !== 1 || owner === undefined) {
35
+ throw new InvalidArgumentError(`--expect-removed ${file} must be owned by exactly one selected patch.` +
36
+ unselectedOwnerHint(file, patches, queue), '--expect-removed');
37
+ }
38
+ const section = parseDiffSections(await readText(join(paths.patches, owner.filename))).find((candidate) => candidate.targetPath === file);
39
+ if (tracked.has(file)) {
40
+ assertUpstreamFileBackAtHead(file, owner, section !== undefined && !section.isNewFile, dirty);
41
+ }
42
+ else {
43
+ await assertCreatedFileAbsent(paths, file, owner, section?.isNewFile === true);
31
44
  }
32
45
  if (owner.filesAffected.every((path) => removed.includes(path))) {
33
46
  throw new InvalidArgumentError('Retiring every file would empty the patch; use patch delete instead.', '--expect-removed');
@@ -35,6 +48,38 @@ export async function validateExpectedRemovals(paths, patches, options) {
35
48
  }
36
49
  return removed;
37
50
  }
51
+ /**
52
+ * Each path retires from its own owner, so one run may retire paths from
53
+ * several selected patches. A path whose owner was left out of the
54
+ * selection names that owner, since adding it is the usual fix.
55
+ */
56
+ function unselectedOwnerHint(file, selected, queue) {
57
+ const selectedNames = new Set(selected.map((patch) => patch.filename));
58
+ const outside = queue
59
+ .filter((patch) => !selectedNames.has(patch.filename) && patch.filesAffected.includes(file))
60
+ .map((patch) => patch.filename);
61
+ if (outside.length === 0)
62
+ return '';
63
+ return ` It is owned by ${outside.join(', ')}, which this run did not select; add it to the selection to retire both in one run.`;
64
+ }
65
+ async function assertCreatedFileAbsent(paths, file, owner, createdByOwner) {
66
+ if (await removalPathPresent(join(paths.engine, file))) {
67
+ throw new InvalidArgumentError(`--expect-removed ${file} is untracked in engine HEAD and still present on disk; ` +
68
+ 'a patch-created file can be retired only once it is deleted.', '--expect-removed');
69
+ }
70
+ if (!createdByOwner) {
71
+ throw new InvalidArgumentError(`--expect-removed ${file} was not created by its selected patch ${owner.filename}.`, '--expect-removed');
72
+ }
73
+ }
74
+ function assertUpstreamFileBackAtHead(file, owner, changedByOwner, dirty) {
75
+ if (dirty.has(file)) {
76
+ throw new InvalidArgumentError(`--expect-removed ${file} is tracked in engine HEAD, but its engine copy still differs ` +
77
+ 'from HEAD. Restore the HEAD copy first, then retire it.', '--expect-removed');
78
+ }
79
+ if (!changedByOwner) {
80
+ throw new InvalidArgumentError(`--expect-removed ${file} is not changed by its selected patch ${owner.filename}.`, '--expect-removed');
81
+ }
82
+ }
38
83
  /** A dangling symlink is still an existing owned path, not an approved absence. */
39
84
  async function removalPathPresent(path) {
40
85
  try {
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Per-file owners for `re-export --scan --scan-file <patch>:<path>`.
3
+ *
4
+ * One change often spans a patch that creates files and a patch that
5
+ * registers or imports them. With `--scan-file` bound to a single target
6
+ * patch, that change took two invocations, and the adopting one could not
7
+ * carry the belts the other did. Naming the owner per file lets one call
8
+ * adopt each file into its patch while every other selected patch
9
+ * refreshes scan-less, all under the same `--refuse-foreign-drift` belt
10
+ * and in a single engine-lock hold.
11
+ */
12
+ import type { PatchesManifest, PatchMetadata, ReExportOptions } from '../types/commands/index.js';
13
+ /** A `--scan-file` value split into its optional owner and its path. */
14
+ export interface ScanFileOwnerSpec {
15
+ owner: string | undefined;
16
+ path: string;
17
+ }
18
+ /**
19
+ * Splits `<patch>:<path>` into owner and path. The owner is everything
20
+ * before the first `:`, and only when that part holds no path separator, so
21
+ * a plain path (even one with a later `:`) stays a path. A one-letter
22
+ * prefix before `:/` or `:\` reads as a Windows drive, not a patch.
23
+ */
24
+ export declare function splitScanFileOwner(raw: string): ScanFileOwnerSpec;
25
+ /** Rejoins a split value, so normalization can rewrite the path half only. */
26
+ export declare function joinScanFileOwner(spec: ScanFileOwnerSpec): string;
27
+ /**
28
+ * Argument-shape check, before any manifest is read: a multi-patch call has
29
+ * to say which patch each file joins.
30
+ */
31
+ export declare function validateScanFileOwners(patches: readonly string[], scanFiles: readonly string[]): void;
32
+ /**
33
+ * Resolves each `--scan-file` value to its owning selected patch. Returns
34
+ * `undefined` when no `--scan-file` was given. An unprefixed path belongs
35
+ * to the single selected patch (the historical form).
36
+ */
37
+ export declare function assignScanFileOwners(scanFiles: readonly string[] | undefined, selectedPatches: readonly PatchMetadata[], manifest: PatchesManifest): Map<string, string[]> | undefined;
38
+ /**
39
+ * The options one patch of the loop runs with. A patch with assigned files
40
+ * adopts exactly those; a selected patch without any refreshes scan-less,
41
+ * because `--scan` beside per-file owners scopes adoption to the named
42
+ * files rather than arming a broad directory scan for every other patch.
43
+ */
44
+ export declare function scanOptionsForPatch(options: ReExportOptions, patchFilename: string, assignments: ReadonlyMap<string, string[]> | undefined): ReExportOptions;
@@ -0,0 +1,116 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Per-file owners for `re-export --scan --scan-file <patch>:<path>`.
4
+ *
5
+ * One change often spans a patch that creates files and a patch that
6
+ * registers or imports them. With `--scan-file` bound to a single target
7
+ * patch, that change took two invocations, and the adopting one could not
8
+ * carry the belts the other did. Naming the owner per file lets one call
9
+ * adopt each file into its patch while every other selected patch
10
+ * refreshes scan-less, all under the same `--refuse-foreign-drift` belt
11
+ * and in a single engine-lock hold.
12
+ */
13
+ import { formatPatchNotFoundError } from '../core/patch-identifier-suggest.js';
14
+ import { resolvePatchIdentifier } from '../core/patch-manifest.js';
15
+ import { InvalidArgumentError } from '../errors/base.js';
16
+ /**
17
+ * Splits `<patch>:<path>` into owner and path. The owner is everything
18
+ * before the first `:`, and only when that part holds no path separator, so
19
+ * a plain path (even one with a later `:`) stays a path. A one-letter
20
+ * prefix before `:/` or `:\` reads as a Windows drive, not a patch.
21
+ */
22
+ export function splitScanFileOwner(raw) {
23
+ const colon = raw.indexOf(':');
24
+ if (colon <= 0)
25
+ return { owner: undefined, path: raw };
26
+ const owner = raw.slice(0, colon).trim();
27
+ const path = raw.slice(colon + 1);
28
+ if (owner.length === 0 || /[/\\]/.test(owner))
29
+ return { owner: undefined, path: raw };
30
+ if (/^[A-Za-z]$/.test(owner) && /^[/\\]/.test(path))
31
+ return { owner: undefined, path: raw };
32
+ return { owner, path };
33
+ }
34
+ /** Rejoins a split value, so normalization can rewrite the path half only. */
35
+ export function joinScanFileOwner(spec) {
36
+ return spec.owner === undefined ? spec.path : `${spec.owner}:${spec.path}`;
37
+ }
38
+ /**
39
+ * Argument-shape check, before any manifest is read: a multi-patch call has
40
+ * to say which patch each file joins.
41
+ */
42
+ export function validateScanFileOwners(patches, scanFiles) {
43
+ if (patches.length === 0) {
44
+ throw new InvalidArgumentError('--scan-file needs explicit target patch identifiers.', '--scan-file');
45
+ }
46
+ if (patches.length === 1)
47
+ return;
48
+ const unowned = scanFiles.filter((file) => splitScanFileOwner(file).owner === undefined);
49
+ if (unowned.length > 0) {
50
+ throw new InvalidArgumentError(`--scan-file in a call with ${patches.length} patches must name each file's owning patch ` +
51
+ `as <patch>:<path> (for example --scan-file ${patches[0] ?? '201'}:${unowned[0] ?? '<path>'}). ` +
52
+ `Missing an owner: ${unowned.join(', ')}.`, '--scan-file');
53
+ }
54
+ }
55
+ /**
56
+ * Resolves each `--scan-file` value to its owning selected patch. Returns
57
+ * `undefined` when no `--scan-file` was given. An unprefixed path belongs
58
+ * to the single selected patch (the historical form).
59
+ */
60
+ export function assignScanFileOwners(scanFiles, selectedPatches, manifest) {
61
+ if (scanFiles === undefined)
62
+ return undefined;
63
+ const selected = new Set(selectedPatches.map((patch) => patch.filename));
64
+ const byPatch = new Map();
65
+ const ownerByFile = new Map();
66
+ for (const raw of scanFiles) {
67
+ const { owner, path } = splitScanFileOwner(raw);
68
+ const filename = resolveOwner(owner, selectedPatches, manifest);
69
+ if (!selected.has(filename)) {
70
+ throw new InvalidArgumentError(`--scan-file ${raw}: ${filename} is not among the selected patches. ` +
71
+ 'Pass it as a positional patch too.', '--scan-file');
72
+ }
73
+ const previous = ownerByFile.get(path);
74
+ if (previous !== undefined && previous !== filename) {
75
+ throw new InvalidArgumentError(`--scan-file path is assigned to more than one patch: ${path} (${previous}, ${filename})`, '--scan-file');
76
+ }
77
+ ownerByFile.set(path, filename);
78
+ const files = byPatch.get(filename) ?? [];
79
+ if (!files.includes(path))
80
+ files.push(path);
81
+ byPatch.set(filename, files);
82
+ }
83
+ for (const files of byPatch.values())
84
+ files.sort();
85
+ return byPatch;
86
+ }
87
+ function resolveOwner(owner, selectedPatches, manifest) {
88
+ if (owner === undefined) {
89
+ const only = selectedPatches.length === 1 ? selectedPatches[0] : undefined;
90
+ if (only === undefined) {
91
+ throw new InvalidArgumentError('--scan-file in a multi-patch call must name its owning patch as <patch>:<path>.', '--scan-file');
92
+ }
93
+ return only.filename;
94
+ }
95
+ const match = resolvePatchIdentifier(owner, manifest.patches);
96
+ if (!match) {
97
+ throw new InvalidArgumentError(`--scan-file: ${formatPatchNotFoundError(owner, manifest.patches)}`, '--scan-file');
98
+ }
99
+ return match.filename;
100
+ }
101
+ /**
102
+ * The options one patch of the loop runs with. A patch with assigned files
103
+ * adopts exactly those; a selected patch without any refreshes scan-less,
104
+ * because `--scan` beside per-file owners scopes adoption to the named
105
+ * files rather than arming a broad directory scan for every other patch.
106
+ */
107
+ export function scanOptionsForPatch(options, patchFilename, assignments) {
108
+ if (assignments === undefined)
109
+ return options;
110
+ const assigned = assignments.get(patchFilename);
111
+ if (assigned !== undefined)
112
+ return { ...options, scanFiles: assigned };
113
+ const scanLess = { ...options, scan: false };
114
+ delete scanLess.scanFiles;
115
+ return scanLess;
116
+ }
@@ -1,10 +1,15 @@
1
+ import { type PatchQueueContext } from '../core/patch-lint.js';
1
2
  import type { PatchesManifest } from '../types/commands/index.js';
3
+ import type { FireForgeConfig } from '../types/config.js';
2
4
  export interface ScanResult {
3
5
  updated: string[];
4
6
  added: string[];
5
7
  removed: string[];
6
8
  }
7
- /** Normalizes repeatable `--scan-file` inputs into safe engine-relative paths. */
9
+ /**
10
+ * Normalizes repeatable `--scan-file` inputs into safe engine-relative
11
+ * paths, keeping an optional `<patch>:` owner prefix intact.
12
+ */
8
13
  export declare function normalizeScanFiles(scanFiles: readonly string[] | undefined): string[] | undefined;
9
14
  /** Normalizes one CLI-provided path into a safe engine-relative path. */
10
15
  export declare function normalizeEngineRelativeInput(rawPath: string, flagName: string): string;
@@ -44,15 +49,20 @@ export declare function assertScanAdoptionsHaveNoForwardImports(args: {
44
49
  engineDir: string;
45
50
  patchFilename: string;
46
51
  added: readonly string[];
52
+ /**
53
+ * Threads `patchLint.forwardRegistration` so jar.mn registrations are judged
54
+ * too, and `patchPolicy`, so the remedy names a legal order.
55
+ */
56
+ config?: FireForgeConfig;
57
+ /** `--declarations-out`: where the one `--from`-ready report goes. */
58
+ declarationsOut?: string | undefined;
47
59
  }): Promise<void>;
48
- /**
49
- * The one remedy sentence for a forward import a patch body cannot carry.
50
- * Shared by the `--scan` adoption gate and the plain re-export projection
51
- * gate so both refusals point at the same commands.
52
- *
53
- * @param reRunHint - What to do after the fix ("re-running the scan")
54
- */
55
- export declare function formatForwardImportRemedy(reRunHint: string): string;
60
+ /** The manifest rows a queue context carries, for remedy computations. */
61
+ export declare function manifestFromQueue(ctx: PatchQueueContext): PatchesManifest;
62
+ /** `{ category }` of the named queue entry, or `{}` when it has no metadata. */
63
+ export declare function categoryOf(ctx: PatchQueueContext, filename: string): {
64
+ category?: string;
65
+ };
56
66
  /** Refuses explicit `--scan-file` additions that did not produce patch hunks. */
57
67
  export declare function assertScanFileAdditionsHaveDiffHunks(args: {
58
68
  diffContent: string;
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
- import { dirname, join } from 'node:path';
2
+ import { dirname, join, resolve } from 'node:path';
3
3
  import { confirm } from '@clack/prompts';
4
4
  import { getDiffForFilesAgainstHead } from '../core/git-diff.js';
5
5
  import { listTrackedInHead } from '../core/git-file-ops.js';
@@ -15,15 +15,27 @@ import { mapWithConcurrency } from '../utils/concurrency.js';
15
15
  import { pathExists } from '../utils/fs.js';
16
16
  import { cancel, info, isCancel, warn } from '../utils/logger.js';
17
17
  import { isContainedRelativePath, normalizePathSlashes, stripEnginePrefix, } from '../utils/paths.js';
18
+ import { formatDeclarationsReport, writeDeclarationsReport } from './declarations-report.js';
19
+ import { formatForwardImportRemedy } from './forward-import-remedy.js';
20
+ import { joinScanFileOwner, splitScanFileOwner } from './re-export-scan-owner.js';
18
21
  const SCAN_ADD_COUNT_THRESHOLD = 3;
19
22
  /** Concurrency bound for existence probes (matches the classify/lint pools). */
20
23
  const PATH_PROBE_CONCURRENCY = 8;
21
24
  const SCAN_DIR_COUNT_THRESHOLD = 2;
22
- /** Normalizes repeatable `--scan-file` inputs into safe engine-relative paths. */
25
+ /**
26
+ * Normalizes repeatable `--scan-file` inputs into safe engine-relative
27
+ * paths, keeping an optional `<patch>:` owner prefix intact.
28
+ */
23
29
  export function normalizeScanFiles(scanFiles) {
24
- const normalized = [...new Set(scanFiles ?? [])]
25
- .map((file) => normalizeEngineRelativeInput(file, '--scan-file'))
26
- .sort();
30
+ const normalized = [
31
+ ...new Set((scanFiles ?? []).map((file) => {
32
+ const spec = splitScanFileOwner(file);
33
+ return joinScanFileOwner({
34
+ owner: spec.owner,
35
+ path: normalizeEngineRelativeInput(spec.path, '--scan-file'),
36
+ });
37
+ })),
38
+ ].sort();
27
39
  return normalized.length > 0 ? normalized : undefined;
28
40
  }
29
41
  /** Normalizes one CLI-provided path into a safe engine-relative path. */
@@ -171,7 +183,7 @@ function scanAdditionsNeedConfirmation(added) {
171
183
  * (all adopt through the same scan result), including dry-run.
172
184
  */
173
185
  export async function assertScanAdoptionsHaveNoForwardImports(args) {
174
- const { patchesDir, engineDir, patchFilename, added } = args;
186
+ const { patchesDir, engineDir, patchFilename, added, config } = args;
175
187
  if (added.length === 0)
176
188
  return;
177
189
  const candidateDiff = await getDiffForFilesAgainstHead(engineDir, [...added]);
@@ -180,7 +192,7 @@ export async function assertScanAdoptionsHaveNoForwardImports(args) {
180
192
  const candidateNewFiles = buildNewFileTextProjection(candidateDiff);
181
193
  const candidateCreations = detectNewFilesInDiff(candidateDiff);
182
194
  const candidateAdditions = buildModifiedFileAdditionsFromDiff(candidateDiff);
183
- const baseCtx = await buildPatchQueueContext(patchesDir);
195
+ const baseCtx = await buildPatchQueueContext(patchesDir, config);
184
196
  const projectedEntries = baseCtx.entries.map((entry) => {
185
197
  if (entry.filename !== patchFilename)
186
198
  return entry;
@@ -193,27 +205,51 @@ export async function assertScanAdoptionsHaveNoForwardImports(args) {
193
205
  };
194
206
  });
195
207
  const baselineIssues = lintPatchQueue(baseCtx).filter((i) => i.severity === 'error');
196
- const projectedIssues = lintPatchQueue({ entries: projectedEntries }).filter((i) => i.severity === 'error');
208
+ const projectedIssues = lintPatchQueue({ ...baseCtx, entries: projectedEntries }).filter((i) => i.severity === 'error');
197
209
  const addedSet = new Set(added);
198
- const offending = computeProjectedLintRegressions(baselineIssues, projectedIssues).filter((issue) => issue.check === 'forward-import' && addedSet.has(issue.file));
199
- if (offending.length === 0)
210
+ const regressions = computeProjectedLintRegressions(baselineIssues, projectedIssues);
211
+ const offending = regressions.filter((issue) => issue.check === 'forward-import' && addedSet.has(issue.file));
212
+ // A forward registration (jar.mn packaging, a test manifest, ...) only
213
+ // regresses here because the candidates moved the registered file's
214
+ // creation to this patch, or because a candidate registers it: either way
215
+ // the adoption caused it, and the operator should declare it in the same
216
+ // pass as the import instead of meeting it later in the queue audit.
217
+ const registrations = regressions.filter((issue) => issue.check === 'forward-registration');
218
+ if (offending.length === 0 && registrations.length === 0)
200
219
  return;
201
220
  const details = offending.map((issue) => ` - ${issue.file}: ${issue.message}`).join('\n');
202
- throw new GeneralError(`Refusing to adopt ${offending.length} scanned file${offending.length === 1 ? '' : 's'} into ${patchFilename} ` +
203
- `because they import modules created by later patches:\n${details}\n` +
204
- formatForwardImportRemedy('re-running the scan'));
221
+ const registrationDetails = registrations
222
+ .map((issue) => ` - ${issue.file}: ${issue.message}`)
223
+ .join('\n');
224
+ const parts = [];
225
+ if (offending.length > 0) {
226
+ parts.push(`${offending.length} scanned file${offending.length === 1 ? '' : 's'} import modules created by later patches:\n${details}`);
227
+ }
228
+ if (registrations.length > 0) {
229
+ parts.push(`${registrations.length} forward registration${registrations.length === 1 ? '' : 's'} the adoption would leave dangling:\n${registrationDetails}`);
230
+ }
231
+ const report = await writeDeclarationsReport(dirname(resolve(patchesDir)), [...offending, ...registrations], args.declarationsOut);
232
+ throw new GeneralError(`Refusing to adopt scanned files into ${patchFilename}: ${parts.join('\n')}\n` +
233
+ (report !== undefined ? `${formatDeclarationsReport(report).join('\n')}\n` : '') +
234
+ formatForwardImportRemedy({
235
+ reRunHint: 're-running the scan',
236
+ issues: [...offending, ...registrations],
237
+ manifest: manifestFromQueue(baseCtx),
238
+ ...(args.config !== undefined ? { config: args.config } : {}),
239
+ ...categoryOf(baseCtx, patchFilename),
240
+ }));
205
241
  }
206
- /**
207
- * The one remedy sentence for a forward import a patch body cannot carry.
208
- * Shared by the `--scan` adoption gate and the plain re-export projection
209
- * gate so both refusals point at the same commands.
210
- *
211
- * @param reRunHint - What to do after the fix ("re-running the scan")
212
- */
213
- export function formatForwardImportRemedy(reRunHint) {
214
- return ('Export those files as their own later patch ("fireforge export --order <n>" / ' +
215
- '"fireforge patch split --order <n>") or declare the intentional dependency with ' +
216
- `"fireforge patch staged-dependency --add" before ${reRunHint}.`);
242
+ /** The manifest rows a queue context carries, for remedy computations. */
243
+ export function manifestFromQueue(ctx) {
244
+ return {
245
+ version: 1,
246
+ patches: ctx.entries.flatMap((entry) => (entry.metadata ? [entry.metadata] : [])),
247
+ };
248
+ }
249
+ /** `{ category }` of the named queue entry, or `{}` when it has no metadata. */
250
+ export function categoryOf(ctx, filename) {
251
+ const category = ctx.entries.find((entry) => entry.filename === filename)?.metadata?.category;
252
+ return category !== undefined ? { category } : {};
217
253
  }
218
254
  /** Refuses explicit `--scan-file` additions that did not produce patch hunks. */
219
255
  export function assertScanFileAdditionsHaveDiffHunks(args) {