@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
@@ -20,18 +20,20 @@
20
20
  * (`refreshQueueCtxEntry`), so later iterations of an `--all` run lint
21
21
  * against the just-refreshed body instead of the stale one.
22
22
  */
23
- import { buildPatchQueueContext, countNonBinaryDiffLines, detectNewFilesInDiff, formatPatchLintIssue, lintExportedPatch, lintPatchQueue, resolvePatchSizeTier, } from '../core/patch-lint.js';
23
+ import { buildPatchQueueContext, detectNewFilesInDiff, formatPatchLintIssue, lintExportedPatch, lintPatchQueue, measurePatchSize, resolvePatchSizeTier, } from '../core/patch-lint.js';
24
24
  import { buildPerPatchLintCacheKey, getCachedPerPatchLintIssues, getPerPatchLintCacheHeadSha, loadPerPatchLintCache, savePerPatchLintCache, setCachedPerPatchLintIssues, } from '../core/patch-lint-cache.js';
25
25
  import { invalidateNewFileCreatorsCache } from '../core/patch-lint-cross.js';
26
+ import { lintIgnoreCacheTokens } from '../core/patch-lint-ignore.js';
26
27
  import { resolvePatchOwnedSysMjs } from '../core/patch-lint-ownership.js';
27
28
  import { computeProjectedLintRegressions } from '../core/patch-lint-projection.js';
28
29
  import { GeneralError } from '../errors/base.js';
29
30
  import { pathExists } from '../utils/fs.js';
30
31
  import { info, warn } from '../utils/logger.js';
31
32
  import { reportPatchLintOutcome } from './export-shared.js';
33
+ import { formatForwardImportRemedy } from './forward-import-remedy.js';
32
34
  import { buildPerRunCheckJs } from './lint-per-run-checkjs.js';
33
35
  import { projectEntryBody } from './patch/entry-projection.js';
34
- import { formatForwardImportRemedy } from './re-export-scan.js';
36
+ import { categoryOf, manifestFromQueue } from './re-export-scan.js';
35
37
  /**
36
38
  * Builds the once-per-invocation lint context.
37
39
  *
@@ -135,7 +137,7 @@ export async function lintReExportedPatch(args) {
135
137
  queueContext: patchQueueCtx,
136
138
  ...(lintCtx.engineHeadSha === undefined ? {} : { engineHeadSha: lintCtx.engineHeadSha }),
137
139
  });
138
- const cached = getCachedPerPatchLintIssues(cache, patch.filename, cacheKey, args.ignoreChecks);
140
+ const cached = getCachedPerPatchLintIssues(cache, patch.filename, cacheKey, lintIgnoreCacheTokens(args.ignoreChecks));
139
141
  if (cached) {
140
142
  lintCtx.reusedCacheEntries++;
141
143
  reportPatchLintOutcome([...globalIssues, ...cached.issues], args.skipLint);
@@ -167,8 +169,10 @@ export async function lintReExportedPatch(args) {
167
169
  else if (patchQueueCtx) {
168
170
  lintOptions.checkJsReportScope = resolvePatchOwnedSysMjs(detectNewFilesInDiff(diffContent));
169
171
  }
172
+ const patchSize = await measurePatchSize(paths.engine, diffContent, existingFiles, config);
170
173
  const issues = await lintExportedPatch(paths.engine, existingFiles, diffContent, config, {
171
174
  ...lintOptions,
175
+ patchSizeMeasurement: patchSize,
172
176
  ...(patchQueueCtx ? { patchQueueCtx } : {}),
173
177
  ...(args.ignoreChecks ? { ignoreChecks: args.ignoreChecks } : {}),
174
178
  ...(args.patchTier ? { patchTier: args.patchTier } : {}),
@@ -177,8 +181,8 @@ export async function lintReExportedPatch(args) {
177
181
  return {
178
182
  issues,
179
183
  suppressed: suppressedIssues,
180
- lineCount: countNonBinaryDiffLines(diffContent).textLines,
181
- lintIgnore: [...(args.ignoreChecks ?? [])],
184
+ lineCount: patchSize.lines,
185
+ lintIgnore: lintIgnoreCacheTokens(args.ignoreChecks),
182
186
  };
183
187
  }
184
188
  /**
@@ -236,10 +240,11 @@ export function assertRefreshedBodyHasNoNewQueueErrors(args) {
236
240
  const projectedEntries = ctx.entries.map((entry) => entry.filename === patchFilename ? { ...entry, ...projection } : entry);
237
241
  const isError = (issue) => issue.severity === 'error';
238
242
  const baseline = lintPatchQueue(ctx).filter(isError);
239
- const projected = lintPatchQueue({
240
- entries: projectedEntries,
241
- ...(ctx.patchPolicy ? { patchPolicy: ctx.patchPolicy } : {}),
242
- }).filter(isError);
243
+ // The whole context carries over (patchPolicy and the
244
+ // `patchLint.forwardRegistration` scope): dropping the scope here let a
245
+ // forward jar.mn registration pass this gate and surface only in the
246
+ // later queue audit, separately from the forward import it packages.
247
+ const projected = lintPatchQueue({ ...ctx, entries: projectedEntries }).filter(isError);
243
248
  const regressions = computeProjectedLintRegressions(baseline, projected);
244
249
  if (regressions.length === 0) {
245
250
  if (baseline.length > 0 && !lintCtx.queueBaselineWarned) {
@@ -253,7 +258,15 @@ export function assertRefreshedBodyHasNoNewQueueErrors(args) {
253
258
  const hasForwardImport = regressions.some((issue) => issue.check === 'forward-import');
254
259
  const message = `Refusing to re-export ${patchFilename} because the refreshed body introduces ` +
255
260
  `${regressions.length} new cross-patch lint error${regressions.length === 1 ? '' : 's'}:\n${details}` +
256
- (hasForwardImport ? `\n${formatForwardImportRemedy('re-running the re-export')}` : '');
261
+ (hasForwardImport
262
+ ? `\n${formatForwardImportRemedy({
263
+ reRunHint: 're-running the re-export',
264
+ issues: regressions,
265
+ manifest: manifestFromQueue(ctx),
266
+ config: lintCtx.config,
267
+ ...categoryOf(ctx, patchFilename),
268
+ })}`
269
+ : '');
257
270
  if (forceUnsafe) {
258
271
  warn(message);
259
272
  warn('Proceeding because --force-unsafe was provided.');
@@ -1,6 +1,7 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { InvalidArgumentError } from '../errors/base.js';
3
3
  import { info } from '../utils/logger.js';
4
+ import { validateScanFileOwners } from './re-export-scan-owner.js';
4
5
  /**
5
6
  * Heuristic for `--files` positional folding: an extra positional that
6
7
  * contains a path separator and is not a `.patch` filename is an engine
@@ -62,6 +63,39 @@ function validateWhitelistFlagDependencies(options) {
62
63
  options.refuseAdjacentUnmanaged !== true) {
63
64
  throw new InvalidArgumentError('--expect-unmanaged names approved exceptions to --refuse-adjacent-unmanaged and has no effect without it. Pass --refuse-adjacent-unmanaged, or drop --expect-unmanaged.', '--expect-unmanaged');
64
65
  }
66
+ for (const [flag, values] of [
67
+ ['--expect-new', options.expectNew],
68
+ ['--adjacent-ignore', options.adjacentIgnore],
69
+ ]) {
70
+ if (values !== undefined && values.length > 0 && options.refuseAdjacentUnmanaged !== true) {
71
+ throw new InvalidArgumentError(`${flag} relaxes --refuse-adjacent-unmanaged and has no effect without it. Pass --refuse-adjacent-unmanaged, or drop ${flag}.`, flag);
72
+ }
73
+ }
74
+ if (options.adjacentIgnore?.some((entry) => entry.includes('**'))) {
75
+ throw new InvalidArgumentError('--adjacent-ignore takes a path or a glob whose * stays within one path segment; ** is refused, because a subtree carve-out is how a reviewed exception becomes a blanket one.', '--adjacent-ignore');
76
+ }
77
+ }
78
+ /**
79
+ * Refuses a path-valued flag whose value starts with `-`. Commander hands a
80
+ * required option value whatever follows it, so a list that ended early
81
+ * (`--expect-unmanaged --dry-run`) silently turned the next flag into a path
82
+ * and the dry run into a write. No engine path starts with a dash.
83
+ */
84
+ function validatePathFlagValues(options) {
85
+ for (const [flag, values] of [
86
+ ['--expect', options.expect],
87
+ ['--expect-unmanaged', options.expectUnmanaged],
88
+ ['--expect-new', options.expectNew],
89
+ ['--expect-removed', options.expectRemoved],
90
+ ['--adjacent-ignore', options.adjacentIgnore],
91
+ ['--scan-file', options.scanFiles],
92
+ ]) {
93
+ const swallowed = (values ?? []).filter((value) => value.trim().startsWith('-'));
94
+ if (swallowed.length > 0) {
95
+ throw new InvalidArgumentError(`${flag} was given ${swallowed.map((value) => `"${value}"`).join(', ')}, which is a flag, not an engine path: ` +
96
+ `the ${flag} before it had no value, so it took the next argument. Give every ${flag} a path.`, flag);
97
+ }
98
+ }
65
99
  }
66
100
  function validateExpectedRemovalUsage(patches, options) {
67
101
  if (options.expectRemoved?.length &&
@@ -69,9 +103,37 @@ function validateExpectedRemovalUsage(patches, options) {
69
103
  throw new InvalidArgumentError('--expect-removed requires explicit selected patches and the scan-less path (no --scan, --files, or --all).', '--expect-removed');
70
104
  }
71
105
  }
106
+ /**
107
+ * `--hunks-from` and `--exclude-hunks` rebuild one patch's files from named
108
+ * hunks, so they take exactly one explicit patch and no flag that sets the
109
+ * file list another way.
110
+ */
111
+ function validateHunkSelectionUsage(patches, options) {
112
+ if (options.hunksFrom !== undefined && options.excludeHunks !== undefined) {
113
+ throw new InvalidArgumentError('--hunks-from and --exclude-hunks are alternatives; pass one.', '--hunks-from');
114
+ }
115
+ const flag = options.hunksFrom !== undefined
116
+ ? '--hunks-from'
117
+ : options.excludeHunks !== undefined
118
+ ? '--exclude-hunks'
119
+ : undefined;
120
+ if (flag === undefined)
121
+ return;
122
+ if (patches.length !== 1 ||
123
+ options.all ||
124
+ options.scan ||
125
+ options.files !== undefined ||
126
+ options.scanFilesManifest !== undefined) {
127
+ throw new InvalidArgumentError(`${flag} rebuilds one patch's files from named hunks: pass exactly one patch, without --scan, --files or --all.`, flag);
128
+ }
129
+ }
72
130
  /** Validates mutually exclusive `re-export` targeting and metadata options. */
73
131
  export function validateReExportOptionCombinations(patches, options) {
132
+ // First: a swallowed flag would otherwise surface as whichever
133
+ // combination error it happens to trip, naming the wrong problem.
134
+ validatePathFlagValues(options);
74
135
  validateExpectedRemovalUsage(patches, options);
136
+ validateHunkSelectionUsage(patches, options);
75
137
  if (options.files !== undefined) {
76
138
  if (options.all || options.scan) {
77
139
  throw new InvalidArgumentError('--files cannot be combined with --scan or --all.', '--files');
@@ -91,9 +153,10 @@ export function validateReExportOptionCombinations(patches, options) {
91
153
  if (options.scanFilesManifest !== undefined) {
92
154
  throw new InvalidArgumentError('--scan-file cannot be combined with --scan-files.', '--scan-file');
93
155
  }
94
- if (options.all || patches.length !== 1) {
95
- throw new InvalidArgumentError('--scan-file operates on exactly one target patch. Pass a single patch identifier.', '--scan-file');
156
+ if (options.all) {
157
+ throw new InvalidArgumentError('--scan-file cannot be combined with --all. Pass the target patch identifiers.', '--scan-file');
96
158
  }
159
+ validateScanFileOwners(patches, options.scanFiles);
97
160
  }
98
161
  if (options.scanFilesManifest !== undefined) {
99
162
  if (!options.scan)
@@ -102,11 +165,16 @@ export function validateReExportOptionCombinations(patches, options) {
102
165
  throw new InvalidArgumentError('--scan-files selects patches from its manifest and cannot be combined with positional patches or --all.', '--scan-files');
103
166
  }
104
167
  }
105
- if (options.refuseAdjacentUnmanaged === true && (options.scan || options.files !== undefined)) {
106
- throw new InvalidArgumentError('--refuse-adjacent-unmanaged applies to the scan-less path only and cannot be combined with --scan or --files (those set filesAffected explicitly).', '--refuse-adjacent-unmanaged');
168
+ // Beside --scan the belt judges the neighbours the scan did not adopt;
169
+ // --files replaces the file list outright, so there is nothing to judge.
170
+ if (options.refuseAdjacentUnmanaged === true && options.files !== undefined) {
171
+ throw new InvalidArgumentError('--refuse-adjacent-unmanaged cannot be combined with --files (it sets filesAffected explicitly).', '--refuse-adjacent-unmanaged');
107
172
  }
108
- if (options.refuseForeignDrift === true && (options.scan || options.files !== undefined)) {
109
- throw new InvalidArgumentError('--refuse-foreign-drift applies to the scan-less path only and cannot be combined with --scan or --files (those explicitly capture the current engine state).', '--refuse-foreign-drift');
173
+ // Beside --scan the belt covers the files each patch already owns; the
174
+ // files a scan adopts are the run's stated intent. --files replaces the
175
+ // file list outright, so there is no owned set left to guard.
176
+ if (options.refuseForeignDrift === true && options.files !== undefined) {
177
+ throw new InvalidArgumentError('--refuse-foreign-drift cannot be combined with --files (it sets filesAffected explicitly and captures the current engine state).', '--refuse-foreign-drift');
110
178
  }
111
179
  validateWhitelistFlagDependencies(options);
112
180
  const usingTierFlag = options.tier !== undefined;
@@ -0,0 +1,23 @@
1
+ import type { ReExportOptions } from '../types/commands/index.js';
2
+ /**
3
+ * Reads a path list file: one engine-relative path per line, blank lines
4
+ * and `#` comment lines ignored. Backs `--expect-from`, which exists because
5
+ * a list held in one shell variable reaches the CLI as one argument under
6
+ * zsh, and splitting it by hand left every path after the first as a
7
+ * positional patch name.
8
+ *
9
+ * @param file - The list file, relative to the working directory
10
+ * @param flag - Flag name for error messages
11
+ */
12
+ export declare function readPathListFile(file: string, flag: string): Promise<string[]>;
13
+ /**
14
+ * Runs every lock-free argument check: empty or unknown patch names
15
+ * (naming an engine file passed by mistake), option combinations, path
16
+ * flag values that are flags, and path flag values that name no changed
17
+ * file. Throws the refusal the locked run would have thrown, only sooner.
18
+ *
19
+ * @param projectRoot - Project root
20
+ * @param patches - Positional patch arguments as given
21
+ * @param options - Options as the command layer builds them
22
+ */
23
+ export declare function preflightReExportArguments(projectRoot: string, patches: readonly string[], options: ReExportOptions): Promise<void>;
@@ -0,0 +1,160 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Argument checks `fireforge re-export` runs before it queues for the
4
+ * engine session lock.
5
+ *
6
+ * Under contention the lock wait runs to many minutes, and an argument the
7
+ * run was always going to refuse (an empty patch name from an unquoted
8
+ * `"$@"`, a changed-file path passed as a positional, a path flag that
9
+ * swallowed the next flag) used to be reported only after that wait. These
10
+ * checks read the manifest and a path-scoped `git status` without the lock.
11
+ * The command repeats its own resolution under the lock, so a manifest that
12
+ * changes in between is still read fresh there.
13
+ */
14
+ import { readFile } from 'node:fs/promises';
15
+ import { join, resolve } from 'node:path';
16
+ import { getProjectPaths } from '../core/config.js';
17
+ import { isGitRepository } from '../core/git.js';
18
+ import { chunkPathspecs } from '../core/git-base.js';
19
+ import { getDirtyFiles } from '../core/git-status.js';
20
+ import { formatPatchNotFoundError } from '../core/patch-identifier-suggest.js';
21
+ import { loadPatchesManifest, resolvePatchIdentifier } from '../core/patch-manifest.js';
22
+ import { InvalidArgumentError } from '../errors/base.js';
23
+ import { toError } from '../utils/errors.js';
24
+ import { pathExists } from '../utils/fs.js';
25
+ import { normalizeReExportFilesPositionals, validateReExportOptionCombinations, } from './re-export-options.js';
26
+ import { normalizeEngineRelativeInput } from './re-export-scan.js';
27
+ import { splitScanFileOwner } from './re-export-scan-owner.js';
28
+ /**
29
+ * Reads a path list file: one engine-relative path per line, blank lines
30
+ * and `#` comment lines ignored. Backs `--expect-from`, which exists because
31
+ * a list held in one shell variable reaches the CLI as one argument under
32
+ * zsh, and splitting it by hand left every path after the first as a
33
+ * positional patch name.
34
+ *
35
+ * @param file - The list file, relative to the working directory
36
+ * @param flag - Flag name for error messages
37
+ */
38
+ export async function readPathListFile(file, flag) {
39
+ let text;
40
+ try {
41
+ text = await readFile(resolve(file), 'utf8');
42
+ }
43
+ catch (error) {
44
+ throw new InvalidArgumentError(`${flag} could not read ${file}: ${toError(error).message}`, flag);
45
+ }
46
+ const paths = text
47
+ .split(/\r?\n/)
48
+ .map((line) => line.trim())
49
+ .filter((line) => line.length > 0 && !line.startsWith('#'));
50
+ if (paths.length === 0) {
51
+ throw new InvalidArgumentError(`${flag} file ${file} lists no paths. An empty list is usually a generation step that ` +
52
+ `failed; write one engine-relative path per line, or drop ${flag}.`, flag);
53
+ }
54
+ return paths;
55
+ }
56
+ /** Engine-relative form of a positional, or undefined when it cannot be one. */
57
+ function asEnginePath(value) {
58
+ try {
59
+ return normalizeEngineRelativeInput(value, 'patch');
60
+ }
61
+ catch {
62
+ return undefined;
63
+ }
64
+ }
65
+ async function resolvePositionals(engineDir, patches, manifest) {
66
+ for (const identifier of patches) {
67
+ if (resolvePatchIdentifier(identifier, manifest.patches))
68
+ continue;
69
+ const enginePath = asEnginePath(identifier);
70
+ if (enginePath !== undefined && (await pathExists(join(engineDir, enginePath)))) {
71
+ throw new InvalidArgumentError(`"${identifier}" is a file in engine/, not a patch. re-export takes patch identifiers ` +
72
+ `as positionals. To name a file whose drift this run expects, pass --expect ` +
73
+ `${enginePath} (one --expect per path), or list the paths one per line in a file ` +
74
+ 'and pass --expect-from <file>.', identifier);
75
+ }
76
+ throw new InvalidArgumentError(formatPatchNotFoundError(identifier, manifest.patches), identifier);
77
+ }
78
+ }
79
+ /**
80
+ * Refuses a path flag value that names no changed file in engine/. Every
81
+ * one of these flags is about a changed file (drift to expect, an unmanaged
82
+ * file to admit, a file to adopt), so a clean or absent path is a typo or a
83
+ * swallowed argument, and the run would otherwise treat it as a no-op.
84
+ */
85
+ async function assertFlagPathsInStatus(engineDir, options, manifest) {
86
+ const normalize = (flag, values) => [
87
+ flag,
88
+ (values ?? []).map((value) => normalizeEngineRelativeInput(value, flag)),
89
+ ];
90
+ const expectNew = normalize('--expect-new', options.expectNew);
91
+ const byFlag = [
92
+ normalize('--expect', options.expect),
93
+ normalize('--expect-unmanaged', options.expectUnmanaged),
94
+ expectNew,
95
+ normalize('--scan-file', options.scanFiles?.map((value) => splitScanFileOwner(value).path)),
96
+ ];
97
+ const all = [...new Set(byFlag.flatMap(([, values]) => values))];
98
+ if (all.length === 0 || !(await isGitRepository(engineDir)))
99
+ return;
100
+ const changed = new Set();
101
+ try {
102
+ for (const chunk of chunkPathspecs(all)) {
103
+ for (const file of await getDirtyFiles(engineDir, chunk))
104
+ changed.add(file);
105
+ }
106
+ }
107
+ catch {
108
+ // An engine git cannot answer for (no HEAD yet, a broken checkout) is
109
+ // the locked run's engine precondition to report, with its own remedy.
110
+ return;
111
+ }
112
+ for (const [flag, values] of byFlag) {
113
+ const absent = values.filter((value) => !changed.has(value));
114
+ if (absent.length > 0) {
115
+ throw new InvalidArgumentError(`${flag} names no changed file in engine/: ${absent.join(', ')}. Each value must be a ` +
116
+ 'path git status lists (modified, deleted or untracked); a clean or missing path is a ' +
117
+ 'typo or a list that ended early, and the run would have ignored it.', flag);
118
+ }
119
+ }
120
+ const owners = new Map();
121
+ for (const patch of manifest.patches) {
122
+ for (const file of patch.filesAffected)
123
+ owners.set(file, patch.filename);
124
+ }
125
+ const owned = expectNew[1].filter((file) => owners.has(file));
126
+ if (owned.length > 0) {
127
+ throw new InvalidArgumentError(`--expect-new names a file a patch already owns: ` +
128
+ `${owned.map((file) => `${file} (${owners.get(file) ?? ''})`).join(', ')}. ` +
129
+ '--expect-new is for files no patch owns yet; drop it for these.', '--expect-new');
130
+ }
131
+ }
132
+ /**
133
+ * Runs every lock-free argument check: empty or unknown patch names
134
+ * (naming an engine file passed by mistake), option combinations, path
135
+ * flag values that are flags, and path flag values that name no changed
136
+ * file. Throws the refusal the locked run would have thrown, only sooner.
137
+ *
138
+ * @param projectRoot - Project root
139
+ * @param patches - Positional patch arguments as given
140
+ * @param options - Options as the command layer builds them
141
+ */
142
+ export async function preflightReExportArguments(projectRoot, patches, options) {
143
+ const empty = patches.filter((patch) => patch.trim().length === 0).length;
144
+ if (empty > 0) {
145
+ throw new InvalidArgumentError(`re-export was given ${empty} empty patch argument(s). An empty argument is usually a ` +
146
+ 'shell expansion that produced nothing (an unquoted "$@" in a heredoc, an unset ' +
147
+ 'variable); pass patch identifiers, or --all.', 'patches');
148
+ }
149
+ const normalized = normalizeReExportFilesPositionals(patches, options);
150
+ validateReExportOptionCombinations(normalized.patches, normalized.options);
151
+ const paths = getProjectPaths(projectRoot);
152
+ const manifest = await loadPatchesManifest(paths.patches);
153
+ // No manifest: the locked run reports that with its own remedy.
154
+ if (!manifest || manifest.patches.length === 0)
155
+ return;
156
+ if (options.all !== true && options.scanFilesManifest === undefined) {
157
+ await resolvePositionals(paths.engine, normalized.patches, manifest);
158
+ }
159
+ await assertFlagPathsInStatus(paths.engine, normalized.options, manifest);
160
+ }
@@ -0,0 +1,18 @@
1
+ import type { PatchMetadata } from '../types/commands/index.js';
2
+ /** What a scoped dry run could touch: the patches it was asked about. */
3
+ export interface DryRunPurityScope {
4
+ /** The selected patches. */
5
+ patches: readonly PatchMetadata[];
6
+ }
7
+ /**
8
+ * Fingerprints exactly the state a scoped `re-export --dry-run` could
9
+ * disturb. Every key names what it covers, so a violation reads as the
10
+ * path that moved.
11
+ */
12
+ export declare function fingerprintScopedDryRunState(engineDir: string, patchesDir: string, scope: DryRunPurityScope): Promise<Map<string, string>>;
13
+ /**
14
+ * Names whoever else wrote during the dry run: history records from other
15
+ * invocations since it started, and a live holder of the engine-session
16
+ * lock (which a dry run no longer takes). Empty when there is no evidence.
17
+ */
18
+ export declare function describeForeignWriters(patchesDir: string, sinceMs: number): Promise<string[]>;
@@ -0,0 +1,131 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * The scoped half of the `re-export --dry-run` purity guard.
4
+ *
5
+ * A dry run takes no engine-session lock since 0.51.0, so peers keep
6
+ * writing while it runs. The whole-tree fingerprint then reported every
7
+ * peer's write as "invariant violated … This is a FireForge bug", and an
8
+ * immediate repeat was clean. The scoped fingerprint covers only what the
9
+ * dry run could touch: the selected patches' bodies and manifest rows, the
10
+ * engine files they own, the engine HEAD and the engine index. A mismatch
11
+ * that remains is attributed to a foreign writer when the history log or
12
+ * the engine lock shows one in the window.
13
+ */
14
+ import { readFile } from 'node:fs/promises';
15
+ import { dirname, join, resolve } from 'node:path';
16
+ import { readEngineSessionLockStatus } from '../core/engine-session-lock.js';
17
+ import { git } from '../core/git-base.js';
18
+ import { historyRecordIdentity, resolveHistoryLogPath } from '../core/history-log-file.js';
19
+ import { PATCHES_MANIFEST } from '../core/patch-manifest-io.js';
20
+ import { GeneralError } from '../errors/base.js';
21
+ import { getNodeErrorCode, toError } from '../utils/errors.js';
22
+ import { sha256Hex } from '../utils/hash.js';
23
+ import { isArray, isObject } from '../utils/validation.js';
24
+ /** Reads a file's bytes as a hash, `absent` when it does not exist. */
25
+ async function hashOrAbsent(path, label) {
26
+ try {
27
+ return sha256Hex(await readFile(path));
28
+ }
29
+ catch (error) {
30
+ const code = getNodeErrorCode(error);
31
+ if (code === 'ENOENT' || code === 'ENOTDIR')
32
+ return 'absent';
33
+ throw new GeneralError(`[dry-run] cannot fingerprint ${label} for the purity guard: ${toError(error).message}`, error);
34
+ }
35
+ }
36
+ /** The selected rows of `patches.json`, so a peer's write to another row is not a violation. */
37
+ async function selectedRowsFingerprint(patchesDir, filenames) {
38
+ let raw;
39
+ try {
40
+ raw = await readFile(join(patchesDir, PATCHES_MANIFEST), 'utf8');
41
+ }
42
+ catch (error) {
43
+ if (getNodeErrorCode(error) === 'ENOENT')
44
+ return 'absent';
45
+ throw error;
46
+ }
47
+ try {
48
+ const parsed = JSON.parse(raw);
49
+ const rows = isObject(parsed) && isArray(parsed['patches']) ? parsed['patches'] : [];
50
+ const selected = rows.filter((row) => isObject(row) && typeof row['filename'] === 'string' && filenames.has(row['filename']));
51
+ return sha256Hex(JSON.stringify(selected));
52
+ }
53
+ catch {
54
+ // A manifest that no longer parses changed, whoever wrote it.
55
+ return sha256Hex(raw);
56
+ }
57
+ }
58
+ /**
59
+ * Fingerprints exactly the state a scoped `re-export --dry-run` could
60
+ * disturb. Every key names what it covers, so a violation reads as the
61
+ * path that moved.
62
+ */
63
+ export async function fingerprintScopedDryRunState(engineDir, patchesDir, scope) {
64
+ const fingerprint = new Map();
65
+ try {
66
+ fingerprint.set('engine HEAD', (await git(['rev-parse', 'HEAD'], engineDir)).trim());
67
+ // The index as a whole: the historic purity defect was a transient
68
+ // `--intent-to-add` entry, which may land on any path a scan reads.
69
+ fingerprint.set('the engine index', sha256Hex(await git(['ls-files', '--stage', '-z'], engineDir)));
70
+ }
71
+ catch (error) {
72
+ throw new GeneralError(`[dry-run] cannot fingerprint the engine for the purity guard: ${toError(error).message}`, error);
73
+ }
74
+ const filenames = new Set(scope.patches.map((patch) => patch.filename));
75
+ fingerprint.set(`${PATCHES_MANIFEST} rows of ${[...filenames].sort().join(', ')}`, await selectedRowsFingerprint(patchesDir, filenames));
76
+ for (const filename of [...filenames].sort()) {
77
+ fingerprint.set(`patches/${filename}`, await hashOrAbsent(join(patchesDir, filename), `patches/${filename}`));
78
+ }
79
+ const owned = [...new Set(scope.patches.flatMap((patch) => patch.filesAffected))].sort();
80
+ for (const file of owned) {
81
+ fingerprint.set(`engine/${file}`, await hashOrAbsent(join(engineDir, file), `engine/${file}`));
82
+ }
83
+ return fingerprint;
84
+ }
85
+ /** History records another invocation wrote at or after `sinceMs`. */
86
+ async function foreignHistoryRecords(projectRoot, sinceMs) {
87
+ let text;
88
+ try {
89
+ text = await readFile(await resolveHistoryLogPath(projectRoot), 'utf8');
90
+ }
91
+ catch {
92
+ return [];
93
+ }
94
+ const own = historyRecordIdentity().invocationId;
95
+ const writers = [];
96
+ for (const line of text.split('\n').slice(-500)) {
97
+ let record;
98
+ try {
99
+ record = JSON.parse(line);
100
+ }
101
+ catch {
102
+ continue;
103
+ }
104
+ const ts = typeof record.ts === 'string' ? Date.parse(record.ts) : Number.NaN;
105
+ if (!(ts >= sinceMs) || record.invocationId === own)
106
+ continue;
107
+ const session = typeof record.sessionId === 'string' ? `, session ${record.sessionId}` : '';
108
+ writers.push(`${String(record.operation)} (PID ${String(record.pid)}${session})`);
109
+ }
110
+ return writers;
111
+ }
112
+ /**
113
+ * Names whoever else wrote during the dry run: history records from other
114
+ * invocations since it started, and a live holder of the engine-session
115
+ * lock (which a dry run no longer takes). Empty when there is no evidence.
116
+ */
117
+ export async function describeForeignWriters(patchesDir, sinceMs) {
118
+ const projectRoot = dirname(resolve(patchesDir));
119
+ const writers = await foreignHistoryRecords(projectRoot, sinceMs);
120
+ try {
121
+ const lock = await readEngineSessionLockStatus(projectRoot);
122
+ if (lock.held && lock.holder !== undefined && lock.holder.pid !== process.pid) {
123
+ const detail = lock.holder.metadata.length > 0 ? ` (${lock.holder.metadata.join(', ')})` : '';
124
+ writers.push(`the engine-session lock holder, PID ${lock.holder.pid}${detail}`);
125
+ }
126
+ }
127
+ catch {
128
+ // Attribution is best effort; the violation is reported either way.
129
+ }
130
+ return writers;
131
+ }