@hominis/fireforge 0.45.0 → 0.46.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 (359) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/dist/bin/fireforge.js +6 -0
  3. package/dist/build-info.json +4 -4
  4. package/dist/src/cli-usage-error.d.ts +46 -0
  5. package/dist/src/cli-usage-error.js +81 -0
  6. package/dist/src/cli.d.ts +5 -1
  7. package/dist/src/cli.js +20 -21
  8. package/dist/src/commands/bootstrap.js +1 -2
  9. package/dist/src/commands/build.js +15 -6
  10. package/dist/src/commands/destructive-decision.d.ts +19 -0
  11. package/dist/src/commands/destructive-decision.js +31 -0
  12. package/dist/src/commands/doctor-check-core.d.ts +0 -6
  13. package/dist/src/commands/doctor-check-core.js +0 -8
  14. package/dist/src/commands/doctor-orphaned-harness.d.ts +0 -6
  15. package/dist/src/commands/doctor-orphaned-harness.js +4 -12
  16. package/dist/src/commands/doctor-post-rebase-audit.js +1 -1
  17. package/dist/src/commands/doctor.d.ts +2 -0
  18. package/dist/src/commands/doctor.js +7 -8
  19. package/dist/src/commands/download.js +35 -24
  20. package/dist/src/commands/export-all.js +21 -4
  21. package/dist/src/commands/export-flow.js +15 -45
  22. package/dist/src/commands/export-placement-conflicts.js +3 -3
  23. package/dist/src/commands/export-placement-gate.js +2 -8
  24. package/dist/src/commands/export-placement-policy.js +3 -5
  25. package/dist/src/commands/export-shared.d.ts +42 -23
  26. package/dist/src/commands/export-shared.js +4 -19
  27. package/dist/src/commands/export.js +10 -1
  28. package/dist/src/commands/furnace/authoring-config.d.ts +13 -0
  29. package/dist/src/commands/furnace/authoring-config.js +19 -0
  30. package/dist/src/commands/furnace/chrome-doc-remove.js +1 -1
  31. package/dist/src/commands/furnace/create-browser-test.js +3 -8
  32. package/dist/src/commands/furnace/create-dry-run.js +2 -5
  33. package/dist/src/commands/furnace/create-templates.d.ts +17 -1
  34. package/dist/src/commands/furnace/create-templates.js +2 -1
  35. package/dist/src/commands/furnace/create.js +25 -19
  36. package/dist/src/commands/furnace/deploy.js +11 -2
  37. package/dist/src/commands/furnace/init.js +2 -1
  38. package/dist/src/commands/furnace/override.js +28 -19
  39. package/dist/src/commands/furnace/refresh.js +9 -1
  40. package/dist/src/commands/furnace/remove-mutations.d.ts +6 -7
  41. package/dist/src/commands/furnace/remove-mutations.js +0 -1
  42. package/dist/src/commands/furnace/remove.js +2 -6
  43. package/dist/src/commands/furnace/rename-browser-test.js +1 -7
  44. package/dist/src/commands/furnace/rename.js +29 -29
  45. package/dist/src/commands/furnace/status.js +12 -9
  46. package/dist/src/commands/furnace/test-file-name.d.ts +28 -0
  47. package/dist/src/commands/furnace/test-file-name.js +39 -0
  48. package/dist/src/commands/furnace/validation-output.d.ts +21 -9
  49. package/dist/src/commands/furnace/validation-output.js +3 -8
  50. package/dist/src/commands/import.js +2 -9
  51. package/dist/src/commands/lint-per-patch.d.ts +2 -2
  52. package/dist/src/commands/lint-per-patch.js +19 -4
  53. package/dist/src/commands/lint-per-run-checkjs.d.ts +2 -2
  54. package/dist/src/commands/lint.d.ts +2 -1
  55. package/dist/src/commands/lint.js +1 -1
  56. package/dist/src/commands/manifest.js +1 -1
  57. package/dist/src/commands/package.js +2 -4
  58. package/dist/src/commands/patch/compact.js +14 -15
  59. package/dist/src/commands/patch/delete.js +15 -25
  60. package/dist/src/commands/patch/entry-projection.d.ts +13 -0
  61. package/dist/src/commands/patch/entry-projection.js +24 -0
  62. package/dist/src/commands/patch/lint-ignore.d.ts +0 -8
  63. package/dist/src/commands/patch/lint-ignore.js +19 -29
  64. package/dist/src/commands/patch/move-files-create.js +24 -10
  65. package/dist/src/commands/patch/move-files-into.js +38 -34
  66. package/dist/src/commands/patch/move-files.js +4 -13
  67. package/dist/src/commands/patch/rename.js +27 -41
  68. package/dist/src/commands/patch/reorder.js +25 -38
  69. package/dist/src/commands/patch/split-plan.d.ts +4 -5
  70. package/dist/src/commands/patch/split-plan.js +4 -8
  71. package/dist/src/commands/patch/split.js +45 -43
  72. package/dist/src/commands/patch/staged-dependency.js +16 -22
  73. package/dist/src/commands/patch/tier.js +15 -25
  74. package/dist/src/commands/patch-rename-map.d.ts +19 -0
  75. package/dist/src/commands/patch-rename-map.js +36 -0
  76. package/dist/src/commands/pending-resolution.d.ts +15 -0
  77. package/dist/src/commands/pending-resolution.js +25 -0
  78. package/dist/src/commands/re-export-adjacent.d.ts +24 -2
  79. package/dist/src/commands/re-export-adjacent.js +32 -16
  80. package/dist/src/commands/re-export-bulk-scan.js +6 -10
  81. package/dist/src/commands/re-export-drift.js +4 -4
  82. package/dist/src/commands/re-export-files.d.ts +2 -3
  83. package/dist/src/commands/re-export-files.js +38 -29
  84. package/dist/src/commands/re-export-lint.d.ts +3 -4
  85. package/dist/src/commands/re-export-lint.js +9 -2
  86. package/dist/src/commands/re-export-scan.js +5 -2
  87. package/dist/src/commands/re-export-single.d.ts +22 -3
  88. package/dist/src/commands/re-export-single.js +19 -11
  89. package/dist/src/commands/re-export.js +28 -11
  90. package/dist/src/commands/rebase/abort.js +3 -8
  91. package/dist/src/commands/rebase/continue.js +12 -12
  92. package/dist/src/commands/rebase/index.js +2 -2
  93. package/dist/src/commands/rebase/patch-loop.d.ts +2 -2
  94. package/dist/src/commands/rebase/patch-loop.js +2 -7
  95. package/dist/src/commands/resolve.js +18 -10
  96. package/dist/src/commands/run.d.ts +0 -13
  97. package/dist/src/commands/run.js +12 -25
  98. package/dist/src/commands/setup-support.d.ts +16 -4
  99. package/dist/src/commands/setup-support.js +16 -16
  100. package/dist/src/commands/setup.js +3 -9
  101. package/dist/src/commands/source.js +8 -1
  102. package/dist/src/commands/status-check.js +2 -2
  103. package/dist/src/commands/status-output.js +2 -2
  104. package/dist/src/commands/status.js +3 -4
  105. package/dist/src/commands/test-build-phase.d.ts +2 -2
  106. package/dist/src/commands/test-build-phase.js +31 -7
  107. package/dist/src/commands/test-modes.d.ts +2 -2
  108. package/dist/src/commands/test-modes.js +0 -1
  109. package/dist/src/commands/test-register.js +5 -3
  110. package/dist/src/commands/test-run.js +25 -18
  111. package/dist/src/commands/test-stale-gate.js +4 -4
  112. package/dist/src/commands/test-verdict.d.ts +16 -2
  113. package/dist/src/commands/test-verdict.js +18 -3
  114. package/dist/src/commands/test.js +42 -5
  115. package/dist/src/commands/token-list.d.ts +26 -0
  116. package/dist/src/commands/token-list.js +153 -0
  117. package/dist/src/commands/token.js +17 -1
  118. package/dist/src/commands/tree.js +7 -2
  119. package/dist/src/commands/typecheck.js +9 -9
  120. package/dist/src/commands/wire.js +4 -3
  121. package/dist/src/core/branding.d.ts +0 -7
  122. package/dist/src/core/branding.js +3 -2
  123. package/dist/src/core/browser-wire.js +6 -3
  124. package/dist/src/core/build-audit-platform.d.ts +29 -0
  125. package/dist/src/core/build-audit-platform.js +140 -0
  126. package/dist/src/core/build-audit-registration.js +3 -2
  127. package/dist/src/core/build-audit-skip.d.ts +57 -0
  128. package/dist/src/core/build-audit-skip.js +112 -0
  129. package/dist/src/core/build-audit.d.ts +15 -2
  130. package/dist/src/core/build-audit.js +110 -10
  131. package/dist/src/core/build-baseline.d.ts +45 -28
  132. package/dist/src/core/build-baseline.js +6 -29
  133. package/dist/src/core/build-jar-escalation.js +3 -2
  134. package/dist/src/core/build-overwrite-guard.js +1 -1
  135. package/dist/src/core/build-prepare.d.ts +0 -8
  136. package/dist/src/core/build-prepare.js +7 -12
  137. package/dist/src/core/config-paths.d.ts +2 -6
  138. package/dist/src/core/config-paths.js +4 -2
  139. package/dist/src/core/config-state.d.ts +0 -6
  140. package/dist/src/core/config-state.js +1 -19
  141. package/dist/src/core/config-validate-build-audit.d.ts +8 -0
  142. package/dist/src/core/config-validate-build-audit.js +80 -0
  143. package/dist/src/core/config-validate-file-size.js +1 -1
  144. package/dist/src/core/config-validate-patch-policy.d.ts +2 -2
  145. package/dist/src/core/config-validate-patch-policy.js +3 -3
  146. package/dist/src/core/config-validate-test-toolchains.d.ts +2 -2
  147. package/dist/src/core/config-validate-test-toolchains.js +4 -5
  148. package/dist/src/core/config-validate.js +14 -3
  149. package/dist/src/core/config.d.ts +2 -2
  150. package/dist/src/core/config.js +11 -15
  151. package/dist/src/core/coverage-extend.js +4 -4
  152. package/dist/src/core/discard-baseline.js +7 -7
  153. package/dist/src/core/engine-precondition.d.ts +1 -9
  154. package/dist/src/core/engine-precondition.js +3 -8
  155. package/dist/src/core/engine-session-lock.js +13 -17
  156. package/dist/src/core/export-moved-code-shape.js +2 -2
  157. package/dist/src/core/file-lock-owner.d.ts +103 -0
  158. package/dist/src/core/file-lock-owner.js +259 -0
  159. package/dist/src/core/file-lock.d.ts +2 -7
  160. package/dist/src/core/file-lock.js +40 -91
  161. package/dist/src/core/firefox-cache.d.ts +13 -1
  162. package/dist/src/core/firefox-cache.js +59 -34
  163. package/dist/src/core/firefox-download.d.ts +15 -0
  164. package/dist/src/core/firefox-download.js +37 -21
  165. package/dist/src/core/firefox-extract.d.ts +6 -5
  166. package/dist/src/core/firefox-extract.js +36 -18
  167. package/dist/src/core/firefox.d.ts +30 -32
  168. package/dist/src/core/firefox.js +5 -36
  169. package/dist/src/core/furnace-apply-dry-run.d.ts +1 -6
  170. package/dist/src/core/furnace-apply-dry-run.js +1 -7
  171. package/dist/src/core/furnace-apply-helpers.d.ts +0 -11
  172. package/dist/src/core/furnace-apply-helpers.js +8 -19
  173. package/dist/src/core/furnace-apply-overwrite-warn.js +4 -2
  174. package/dist/src/core/furnace-apply.d.ts +1 -1
  175. package/dist/src/core/furnace-apply.js +30 -10
  176. package/dist/src/core/furnace-config.d.ts +1 -1
  177. package/dist/src/core/furnace-config.js +23 -36
  178. package/dist/src/core/furnace-constants.js +3 -2
  179. package/dist/src/core/furnace-dir-entry.d.ts +20 -0
  180. package/dist/src/core/furnace-dir-entry.js +21 -0
  181. package/dist/src/core/furnace-operation.d.ts +2 -0
  182. package/dist/src/core/furnace-operation.js +3 -1
  183. package/dist/src/core/furnace-refresh.d.ts +18 -6
  184. package/dist/src/core/furnace-refresh.js +3 -6
  185. package/dist/src/core/furnace-registration-ast.d.ts +0 -1
  186. package/dist/src/core/furnace-registration-ast.js +0 -3
  187. package/dist/src/core/furnace-registration.d.ts +2 -1
  188. package/dist/src/core/furnace-registration.js +22 -29
  189. package/dist/src/core/furnace-scanner.js +3 -6
  190. package/dist/src/core/furnace-state-persist.d.ts +2 -2
  191. package/dist/src/core/furnace-validate-registration.js +5 -7
  192. package/dist/src/core/git-base.d.ts +3 -1
  193. package/dist/src/core/git-base.js +25 -3
  194. package/dist/src/core/git-diff-new-file.d.ts +40 -0
  195. package/dist/src/core/git-diff-new-file.js +119 -0
  196. package/dist/src/core/git-diff.d.ts +9 -9
  197. package/dist/src/core/git-diff.js +88 -157
  198. package/dist/src/core/git-file-ops.d.ts +0 -7
  199. package/dist/src/core/git-file-ops.js +4 -28
  200. package/dist/src/core/git-readonly-index.d.ts +0 -2
  201. package/dist/src/core/git-readonly-index.js +0 -4
  202. package/dist/src/core/git-status.d.ts +7 -0
  203. package/dist/src/core/git-status.js +21 -9
  204. package/dist/src/core/git.d.ts +0 -6
  205. package/dist/src/core/git.js +14 -43
  206. package/dist/src/core/harness-orphans.js +8 -7
  207. package/dist/src/core/history-log.d.ts +19 -0
  208. package/dist/src/core/history-log.js +29 -0
  209. package/dist/src/core/mach-error-hints.d.ts +0 -5
  210. package/dist/src/core/mach-error-hints.js +1 -1
  211. package/dist/src/core/mach-known-noise-filter.d.ts +4 -3
  212. package/dist/src/core/mach-known-noise-filter.js +7 -9
  213. package/dist/src/core/mach-mozconfig.js +2 -1
  214. package/dist/src/core/mach-python.d.ts +2 -0
  215. package/dist/src/core/mach-python.js +12 -7
  216. package/dist/src/core/mach-resource-shim.d.ts +13 -1
  217. package/dist/src/core/mach-resource-shim.js +112 -9
  218. package/dist/src/core/mach.d.ts +39 -53
  219. package/dist/src/core/mach.js +13 -81
  220. package/dist/src/core/marionette-port.d.ts +0 -17
  221. package/dist/src/core/marionette-port.js +12 -58
  222. package/dist/src/core/marionette-preflight.js +9 -27
  223. package/dist/src/core/mochitest-server-port.js +3 -3
  224. package/dist/src/core/moz-manifest-rules.js +37 -33
  225. package/dist/src/core/patch-apply-fuzz.js +4 -4
  226. package/dist/src/core/patch-apply.d.ts +4 -5
  227. package/dist/src/core/patch-apply.js +6 -12
  228. package/dist/src/core/patch-export-coverage.d.ts +0 -9
  229. package/dist/src/core/patch-export-coverage.js +1 -30
  230. package/dist/src/core/patch-export-update.d.ts +18 -1
  231. package/dist/src/core/patch-export-update.js +4 -3
  232. package/dist/src/core/patch-export.d.ts +11 -21
  233. package/dist/src/core/patch-export.js +44 -66
  234. package/dist/src/core/patch-files.d.ts +0 -4
  235. package/dist/src/core/patch-files.js +0 -11
  236. package/dist/src/core/patch-lint-binary.d.ts +6 -12
  237. package/dist/src/core/patch-lint-cache.d.ts +17 -1
  238. package/dist/src/core/patch-lint-cache.js +3 -6
  239. package/dist/src/core/patch-lint-checkjs.d.ts +33 -16
  240. package/dist/src/core/patch-lint-checkjs.js +37 -24
  241. package/dist/src/core/patch-lint-creators.d.ts +2 -6
  242. package/dist/src/core/patch-lint-cross.d.ts +16 -8
  243. package/dist/src/core/patch-lint-cross.js +49 -92
  244. package/dist/src/core/patch-lint-css.js +10 -6
  245. package/dist/src/core/patch-lint-file-size.js +3 -3
  246. package/dist/src/core/patch-lint-forward-registration.d.ts +58 -0
  247. package/dist/src/core/patch-lint-forward-registration.js +210 -0
  248. package/dist/src/core/patch-lint-jsdoc.js +36 -36
  249. package/dist/src/core/patch-lint-module-registration.d.ts +2 -8
  250. package/dist/src/core/patch-lint-prettier.js +1 -1
  251. package/dist/src/core/patch-lint-queue-types.d.ts +60 -0
  252. package/dist/src/core/patch-lint-queue-types.js +12 -0
  253. package/dist/src/core/patch-lint-staged-registration.d.ts +61 -0
  254. package/dist/src/core/patch-lint-staged-registration.js +90 -0
  255. package/dist/src/core/patch-lint.d.ts +14 -9
  256. package/dist/src/core/patch-lint.js +4 -3
  257. package/dist/src/core/patch-lock.js +3 -3
  258. package/dist/src/core/patch-manifest-io.js +8 -7
  259. package/dist/src/core/patch-parse.d.ts +19 -8
  260. package/dist/src/core/patch-parse.js +25 -0
  261. package/dist/src/core/patch-policy.js +15 -27
  262. package/dist/src/core/patch-source-metadata.d.ts +1 -3
  263. package/dist/src/core/patch-source-metadata.js +0 -4
  264. package/dist/src/core/patch-transform.d.ts +0 -8
  265. package/dist/src/core/patch-transform.js +0 -10
  266. package/dist/src/core/process-cpu-time.js +1 -1
  267. package/dist/src/core/rebase-session.d.ts +2 -8
  268. package/dist/src/core/rebase-session.js +5 -17
  269. package/dist/src/core/register-browser-content.js +3 -2
  270. package/dist/src/core/register-browser-test.js +2 -1
  271. package/dist/src/core/register-module.js +2 -1
  272. package/dist/src/core/register-scaffold.js +5 -4
  273. package/dist/src/core/register-shared-css.js +2 -1
  274. package/dist/src/core/run-log-redact.d.ts +38 -0
  275. package/dist/src/core/run-log-redact.js +69 -0
  276. package/dist/src/core/run-log.d.ts +13 -2
  277. package/dist/src/core/run-log.js +67 -18
  278. package/dist/src/core/signal-critical.js +2 -4
  279. package/dist/src/core/smoke-patterns.d.ts +0 -1
  280. package/dist/src/core/smoke-patterns.js +6 -11
  281. package/dist/src/core/state-file.d.ts +12 -1
  282. package/dist/src/core/state-file.js +14 -1
  283. package/dist/src/core/status-classify.js +2 -1
  284. package/dist/src/core/test-changed-prefs.js +26 -11
  285. package/dist/src/core/test-harness-crash.js +2 -2
  286. package/dist/src/core/test-stale-check.d.ts +0 -2
  287. package/dist/src/core/test-stale-check.js +2 -2
  288. package/dist/src/core/test-xpcshell-retry.d.ts +26 -7
  289. package/dist/src/core/test-xpcshell-retry.js +14 -11
  290. package/dist/src/core/token-category.d.ts +33 -0
  291. package/dist/src/core/token-category.js +56 -48
  292. package/dist/src/core/token-dark-mode.js +2 -14
  293. package/dist/src/core/token-inventory.d.ts +56 -0
  294. package/dist/src/core/token-inventory.js +109 -0
  295. package/dist/src/core/token-manager.d.ts +1 -1
  296. package/dist/src/core/token-manager.js +1 -1
  297. package/dist/src/core/token-variant.js +21 -22
  298. package/dist/src/core/toolchain-preflight.js +1 -1
  299. package/dist/src/core/tree-cow.d.ts +0 -2
  300. package/dist/src/core/tree-cow.js +2 -2
  301. package/dist/src/core/tree-store.js +4 -5
  302. package/dist/src/core/typecheck.js +2 -2
  303. package/dist/src/core/wire-dom-fragment.js +6 -17
  304. package/dist/src/core/wire-utils.js +5 -17
  305. package/dist/src/core/xpcshell-appdir.js +2 -4
  306. package/dist/src/errors/base.d.ts +53 -1
  307. package/dist/src/errors/base.js +79 -11
  308. package/dist/src/errors/build.js +20 -28
  309. package/dist/src/errors/config.d.ts +0 -14
  310. package/dist/src/errors/config.js +13 -34
  311. package/dist/src/errors/download.d.ts +13 -0
  312. package/dist/src/errors/download.js +53 -24
  313. package/dist/src/errors/furnace.js +8 -9
  314. package/dist/src/errors/git.d.ts +8 -8
  315. package/dist/src/errors/git.js +20 -30
  316. package/dist/src/errors/patch.js +8 -10
  317. package/dist/src/errors/rebase.js +7 -7
  318. package/dist/src/types/commands/options.d.ts +77 -123
  319. package/dist/src/types/config.d.ts +34 -2
  320. package/dist/src/types/index.d.ts +0 -1
  321. package/dist/src/utils/assert.d.ts +3 -15
  322. package/dist/src/utils/assert.js +1 -18
  323. package/dist/src/utils/build-info.d.ts +1 -1
  324. package/dist/src/utils/child-env.d.ts +2 -1
  325. package/dist/src/utils/elapsed.js +10 -1
  326. package/dist/src/utils/fs.js +8 -13
  327. package/dist/src/utils/hash.d.ts +12 -0
  328. package/dist/src/utils/hash.js +16 -0
  329. package/dist/src/utils/line-dispatch.d.ts +15 -0
  330. package/dist/src/utils/line-dispatch.js +48 -0
  331. package/dist/src/utils/options.d.ts +1 -7
  332. package/dist/src/utils/options.js +6 -7
  333. package/dist/src/utils/package-root.js +3 -12
  334. package/dist/src/utils/platform.d.ts +0 -4
  335. package/dist/src/utils/process-group.d.ts +2 -1
  336. package/dist/src/utils/process-group.js +20 -19
  337. package/dist/src/utils/process.d.ts +40 -26
  338. package/dist/src/utils/process.js +115 -166
  339. package/dist/src/utils/ps-duration.js +3 -3
  340. package/dist/src/utils/sleep.d.ts +26 -0
  341. package/dist/src/utils/sleep.js +31 -0
  342. package/dist/src/utils/validation.d.ts +2 -38
  343. package/dist/src/utils/validation.js +7 -16
  344. package/docs/configuration.md +89 -1
  345. package/docs/exit-codes.md +31 -19
  346. package/docs/lifecycle-invariants.md +18 -12
  347. package/docs/machine-output.md +31 -8
  348. package/docs/patch-workflow.md +2 -1
  349. package/docs/run-logs.md +44 -4
  350. package/docs/testing.md +8 -1
  351. package/package.json +8 -7
  352. package/dist/src/commands/rebase.d.ts +0 -4
  353. package/dist/src/commands/rebase.js +0 -5
  354. package/dist/src/core/patch-artifact-normalize.d.ts +0 -21
  355. package/dist/src/core/patch-artifact-normalize.js +0 -24
  356. package/dist/src/core/patch-lint-reexports.d.ts +0 -7
  357. package/dist/src/core/patch-lint-reexports.js +0 -8
  358. package/dist/src/core/wire-targets.d.ts +0 -7
  359. package/dist/src/core/wire-targets.js +0 -8
@@ -4,11 +4,10 @@
4
4
  * PatchMetadata.stagedDependencies without rewriting the .patch body.
5
5
  */
6
6
  import { Command } from 'commander';
7
- import { appendHistory } from '../../core/destructive.js';
7
+ import { appendHistoryBestEffort } from '../../core/history-log.js';
8
8
  import { mutatePatchMetadata } from '../../core/patch-export.js';
9
9
  import { GeneralError, InvalidArgumentError } from '../../errors/base.js';
10
- import { toError } from '../../utils/errors.js';
11
- import { info, intro, outro, warn } from '../../utils/logger.js';
10
+ import { info, intro, outro } from '../../utils/logger.js';
12
11
  import { addWaitLockOption, resolveWaitLockSeconds } from '../../utils/options.js';
13
12
  import { requirePatchQueue, requirePatchTarget } from './patch-context.js';
14
13
  import { validateStagedDependencyAdd } from './staged-dependency-validate.js';
@@ -113,7 +112,7 @@ function resolveImportRemovalTarget(options, staged) {
113
112
  const list = candidates
114
113
  .map((entry) => ` - ${dependencyLabel('import', importView(entry))}`)
115
114
  .join('\n');
116
- throw new GeneralError(`--remove matches ${String(candidates.length)} staged forward-imports on this patch; ` +
115
+ throw new GeneralError(`--remove matches ${candidates.length} staged forward-imports on this patch; ` +
117
116
  `pass --creates to pick one:\n${list}`);
118
117
  }
119
118
  return { file, specifier, creates: '(no match)' };
@@ -142,7 +141,7 @@ function resolveRegistrationRemovalTarget(options, staged) {
142
141
  const list = candidates
143
142
  .map((entry) => ` - ${dependencyLabel('registration', registrationView(entry))}`)
144
143
  .join('\n');
145
- throw new GeneralError(`--remove matches ${String(candidates.length)} staged registrations on this patch; ` +
144
+ throw new GeneralError(`--remove matches ${candidates.length} staged registrations on this patch; ` +
146
145
  `pass --creates to pick one:\n${list}`);
147
146
  }
148
147
  return { file, line, creates: '(no match)' };
@@ -280,23 +279,18 @@ export async function patchStagedDependencyCommand(projectRoot, identifier, opti
280
279
  const before = result.before.stagedDependencies;
281
280
  const after = result.after.stagedDependencies;
282
281
  info(`${targetPatch.filename}: ${describeStagedDependencyChange(countStagedEntries(before), countStagedEntries(after), mode, kind, target)}.`);
283
- try {
284
- await appendHistory(paths.patches, {
285
- operation: 'patch-staged-dependency',
286
- args: {
287
- filename: targetPatch.filename,
288
- mode,
289
- kind,
290
- before: before ?? {},
291
- after: after ?? {},
292
- },
293
- ...(options.yes === true ? { yes: true } : {}),
294
- result: 'ok',
295
- });
296
- }
297
- catch (historyError) {
298
- warn(`History log append failed after patch staged-dependency committed (${targetPatch.filename}): ${toError(historyError).message}`);
299
- }
282
+ await appendHistoryBestEffort(paths.patches, {
283
+ operation: 'patch-staged-dependency',
284
+ args: {
285
+ filename: targetPatch.filename,
286
+ mode,
287
+ kind,
288
+ before: before ?? {},
289
+ after: after ?? {},
290
+ },
291
+ ...(options.yes === true ? { yes: true } : {}),
292
+ result: 'ok',
293
+ }, `patch staged-dependency committed (${targetPatch.filename})`);
300
294
  outro('Patch staged-dependency complete');
301
295
  }
302
296
  /**
@@ -15,12 +15,13 @@
15
15
  * `--clear` must be supplied per invocation.
16
16
  */
17
17
  import { Command, Option } from 'commander';
18
- import { appendHistory, confirmDestructive } from '../../core/destructive.js';
18
+ import { confirmDestructive } from '../../core/destructive.js';
19
+ import { appendHistoryBestEffort } from '../../core/history-log.js';
19
20
  import { updatePatchMetadata } from '../../core/patch-export.js';
20
21
  import { InvalidArgumentError } from '../../errors/base.js';
21
- import { toError } from '../../utils/errors.js';
22
- import { info, intro, outro, warn } from '../../utils/logger.js';
22
+ import { info, intro, outro } from '../../utils/logger.js';
23
23
  import { addWaitLockOption, pickDefined, resolveWaitLockSeconds } from '../../utils/options.js';
24
+ import { proceedAfterDecision } from '../destructive-decision.js';
24
25
  import { requirePatchQueue, requirePatchTarget } from './patch-context.js';
25
26
  /**
26
27
  * Runs the `patch tier` command: updates `PatchMetadata.tier` on the
@@ -71,14 +72,8 @@ export async function patchTierCommand(projectRoot, identifier, options = {}) {
71
72
  yes: options.yes === true,
72
73
  dryRun: isDryRun,
73
74
  });
74
- if (decision === 'dry-run') {
75
- outro('Dry run complete — no changes made');
75
+ if (!proceedAfterDecision(decision, 'Cancelled — no changes made'))
76
76
  return;
77
- }
78
- if (decision === 'declined') {
79
- outro('Cancelled — no changes made');
80
- return;
81
- }
82
77
  // Single write under the patch directory lock (delegated inside
83
78
  // updatePatchMetadata). Setting routes through `updates`; clearing
84
79
  // routes through `unsetFields` so TypeScript's exact optional types
@@ -92,21 +87,16 @@ export async function patchTierCommand(projectRoot, identifier, options = {}) {
92
87
  else {
93
88
  await updatePatchMetadata(paths.patches, target.filename, {}, ['tier']);
94
89
  }
95
- try {
96
- await appendHistory(paths.patches, {
97
- operation: 'patch-tier',
98
- args: {
99
- filename: target.filename,
100
- ...(before !== undefined ? { before } : {}),
101
- ...(after !== undefined ? { after } : {}),
102
- },
103
- ...(options.yes === true ? { yes: true } : {}),
104
- result: 'ok',
105
- });
106
- }
107
- catch (historyError) {
108
- warn(`History log append failed after patch tier committed (${target.filename}): ${toError(historyError).message}`);
109
- }
90
+ await appendHistoryBestEffort(paths.patches, {
91
+ operation: 'patch-tier',
92
+ args: {
93
+ filename: target.filename,
94
+ ...(before !== undefined ? { before } : {}),
95
+ ...(after !== undefined ? { after } : {}),
96
+ },
97
+ ...(options.yes === true ? { yes: true } : {}),
98
+ result: 'ok',
99
+ }, `patch tier committed (${target.filename})`);
110
100
  info(`${target.filename}: ${action}.`);
111
101
  outro('Patch tier complete');
112
102
  }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Structural equality for patch rename maps, shared by the placement-mode
3
+ * export preview/commit check (`export-flow.ts`) and `patch reorder`'s
4
+ * under-lock drift check. Both previously carried a byte-identical copy of
5
+ * the sorted-entries comparison; one helper keeps the two commit gates from
6
+ * drifting.
7
+ */
8
+ import type { PatchRenameEntry } from '../core/patch-manifest.js';
9
+ /**
10
+ * Rename-map entries ordered by target order, so previews, history entries
11
+ * and equality checks all walk the queue in its final on-disk order.
12
+ */
13
+ export declare function getSortedRenameEntries(renameMap: Map<string, PatchRenameEntry>): Array<[string, PatchRenameEntry]>;
14
+ /**
15
+ * Compares two rename maps entry by entry after sorting both by target
16
+ * order. Equal when every (current filename, new filename, new order)
17
+ * triple matches positionally.
18
+ */
19
+ export declare function renameMapsEqual(left: Map<string, PatchRenameEntry>, right: Map<string, PatchRenameEntry>): boolean;
@@ -0,0 +1,36 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Structural equality for patch rename maps, shared by the placement-mode
4
+ * export preview/commit check (`export-flow.ts`) and `patch reorder`'s
5
+ * under-lock drift check. Both previously carried a byte-identical copy of
6
+ * the sorted-entries comparison; one helper keeps the two commit gates from
7
+ * drifting.
8
+ */
9
+ /**
10
+ * Rename-map entries ordered by target order, so previews, history entries
11
+ * and equality checks all walk the queue in its final on-disk order.
12
+ */
13
+ export function getSortedRenameEntries(renameMap) {
14
+ return Array.from(renameMap.entries()).sort((a, b) => a[1].newOrder - b[1].newOrder);
15
+ }
16
+ /**
17
+ * Compares two rename maps entry by entry after sorting both by target
18
+ * order. Equal when every (current filename, new filename, new order)
19
+ * triple matches positionally.
20
+ */
21
+ export function renameMapsEqual(left, right) {
22
+ const leftEntries = getSortedRenameEntries(left);
23
+ const rightEntries = getSortedRenameEntries(right);
24
+ if (leftEntries.length !== rightEntries.length) {
25
+ return false;
26
+ }
27
+ return leftEntries.every(([leftFilename, leftEntry], index) => {
28
+ const rightTuple = rightEntries[index];
29
+ if (!rightTuple)
30
+ return false;
31
+ const [rightFilename, rightEntry] = rightTuple;
32
+ return (leftFilename === rightFilename &&
33
+ leftEntry.newFilename === rightEntry.newFilename &&
34
+ leftEntry.newOrder === rightEntry.newOrder);
35
+ });
36
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Clears `pendingResolution` from `state.json` transactionally.
3
+ *
4
+ * Five commands (`doctor --clear-resolution`, `resolve`, `rebase --continue`,
5
+ * `rebase --abort`, and the rebase patch loop) had each copied the same
6
+ * spread-then-`delete` updater. Routing the clear through `updateState` rather
7
+ * than a load/modify/save pair is the load-bearing part: the updater runs
8
+ * inside the state-file lock against the freshest on-disk state, so a
9
+ * concurrent write to an unrelated key (`buildMode`, `baseCommit`) is not
10
+ * clobbered by a stale reload. Copies of that reasoning drift; one does not.
11
+ *
12
+ * A no-op when nothing is pending, so callers need no guard of their own.
13
+ * @param projectRoot - Project root holding `.fireforge/state.json`.
14
+ */
15
+ export declare function clearPendingResolution(projectRoot: string): Promise<void>;
@@ -0,0 +1,25 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { updateState } from '../core/config.js';
3
+ /**
4
+ * Clears `pendingResolution` from `state.json` transactionally.
5
+ *
6
+ * Five commands (`doctor --clear-resolution`, `resolve`, `rebase --continue`,
7
+ * `rebase --abort`, and the rebase patch loop) had each copied the same
8
+ * spread-then-`delete` updater. Routing the clear through `updateState` rather
9
+ * than a load/modify/save pair is the load-bearing part: the updater runs
10
+ * inside the state-file lock against the freshest on-disk state, so a
11
+ * concurrent write to an unrelated key (`buildMode`, `baseCommit`) is not
12
+ * clobbered by a stale reload. Copies of that reasoning drift; one does not.
13
+ *
14
+ * A no-op when nothing is pending, so callers need no guard of their own.
15
+ * @param projectRoot - Project root holding `.fireforge/state.json`.
16
+ */
17
+ export async function clearPendingResolution(projectRoot) {
18
+ await updateState(projectRoot, (current) => {
19
+ if (!current.pendingResolution)
20
+ return current;
21
+ const next = { ...current };
22
+ delete next.pendingResolution;
23
+ return next;
24
+ });
25
+ }
@@ -1,5 +1,5 @@
1
- import type { getProjectPaths } from '../core/config.js';
2
1
  import type { PatchesManifest, PatchMetadata } from '../types/commands/index.js';
2
+ import type { ProjectPaths } from '../types/config.js';
3
3
  /**
4
4
  * Shared per-run context for the adjacency advisory: the classification
5
5
  * inputs computed once by `reExportCommand`, plus the collection of
@@ -18,13 +18,35 @@ export interface AdjacentUnmanagedContext {
18
18
  approvedUnmanaged: ReadonlySet<string>;
19
19
  /** Approved paths actually met this run, so unused ones can be surfaced. */
20
20
  approvedSeen: Set<string>;
21
+ /**
22
+ * Offenders collected across the loop. `files` stays the bare paths that
23
+ * downstream consumers already read; `anchored` carries the same entries
24
+ * with the owned directory that made each one adjacent, so the run-level
25
+ * refusal can name the anchor without re-deriving it.
26
+ */
21
27
  refusals: {
22
28
  patchFilename: string;
23
29
  files: string[];
30
+ anchored: string[];
24
31
  }[];
25
32
  }
26
33
  /** Returns the subset of `files` that no longer exist under `engineDir`. */
27
34
  export declare function findMissingFiles(engineDir: string, files: readonly string[]): Promise<string[]>;
35
+ /**
36
+ * One unmanaged file and the owned directory that made it adjacent.
37
+ *
38
+ * The anchor is what an unattended run could not previously recover: the
39
+ * notice named the offending file and the patch, but not WHICH of the
40
+ * patch's owned locations the file sits beside, so triaging a refusal on a
41
+ * patch that owns files in several directories meant re-deriving the
42
+ * adjacency by hand from the manifest.
43
+ */
44
+ export interface AdjacentUnmanagedFile {
45
+ /** Engine-relative path of the unmanaged file. */
46
+ file: string;
47
+ /** Directory holding at least one file this patch owns. */
48
+ anchorDir: string;
49
+ }
28
50
  /**
29
51
  * Emits the scan-less advisory for one patch: missing manifest files and
30
52
  * unmanaged files adjacent to the patch's ownership. When
@@ -35,7 +57,7 @@ export declare function findMissingFiles(engineDir: string, files: readonly stri
35
57
  */
36
58
  export declare function reportAdjacentUnmanagedFiles(args: {
37
59
  patch: PatchMetadata;
38
- paths: ReturnType<typeof getProjectPaths>;
60
+ paths: ProjectPaths;
39
61
  manifest: PatchesManifest;
40
62
  currentFilesAffected: readonly string[];
41
63
  ctx: AdjacentUnmanagedContext;
@@ -47,7 +47,9 @@ async function findAdjacentUnmanagedFiles(args) {
47
47
  const parentDirs = [...new Set(currentFilesAffected.map((file) => dirname(file)))];
48
48
  const currentSet = new Set(currentFilesAffected);
49
49
  const claimedByOthers = getClaimedFiles(manifest, patchFilename);
50
- const candidates = new Set();
50
+ // First anchor wins: the dirs are scanned in ownership order and a file
51
+ // lives in exactly one of them, so the map is a dedupe, not a choice.
52
+ const candidates = new Map();
51
53
  for (const dir of parentDirs) {
52
54
  const [modifiedFiles, untrackedFiles] = await Promise.all([
53
55
  getModifiedFilesInDir(engineDir, dir),
@@ -59,10 +61,13 @@ async function findAdjacentUnmanagedFiles(args) {
59
61
  continue;
60
62
  if (isToolManagedPath(file, untrackedSet.has(file), ctx))
61
63
  continue;
62
- candidates.add(file);
64
+ if (!candidates.has(file))
65
+ candidates.set(file, dir);
63
66
  }
64
67
  }
65
- return [...candidates].sort();
68
+ return [...candidates.entries()]
69
+ .map(([file, anchorDir]) => ({ file, anchorDir }))
70
+ .sort((a, b) => a.file.localeCompare(b.file));
66
71
  }
67
72
  /**
68
73
  * Splits the candidates into the ones that refuse and the ones a
@@ -73,17 +78,21 @@ async function findAdjacentUnmanagedFiles(args) {
73
78
  function partitionApproved(files, ctx) {
74
79
  const refusing = [];
75
80
  const approved = [];
76
- for (const file of files) {
77
- if (ctx.approvedUnmanaged.has(file)) {
78
- ctx.approvedSeen.add(file);
79
- approved.push(file);
81
+ for (const entry of files) {
82
+ if (ctx.approvedUnmanaged.has(entry.file)) {
83
+ ctx.approvedSeen.add(entry.file);
84
+ approved.push(entry);
80
85
  }
81
86
  else {
82
- refusing.push(file);
87
+ refusing.push(entry);
83
88
  }
84
89
  }
85
90
  return { refusing, approved };
86
91
  }
92
+ /** Renders `<file> (beside engine/<dir>)` for a notice line. */
93
+ function describeAnchored(entry) {
94
+ return `${entry.file} (beside engine/${entry.anchorDir})`;
95
+ }
87
96
  /**
88
97
  * Emits the scan-less advisory for one patch: missing manifest files and
89
98
  * unmanaged files adjacent to the patch's ownership. When
@@ -112,19 +121,26 @@ export async function reportAdjacentUnmanagedFiles(args) {
112
121
  });
113
122
  const { refusing: unmanagedFiles, approved } = partitionApproved(allUnmanaged, ctx);
114
123
  if (approved.length > 0) {
115
- info(`${patch.filename}: ${String(approved.length)} adjacent unmanaged file(s) admitted by ` +
116
- `--expect-unmanaged (${approved.join(', ')}) — reported, not refused.`);
124
+ info(`${patch.filename}: ${approved.length} adjacent unmanaged file(s) admitted by ` +
125
+ `--expect-unmanaged (${approved.map(describeAnchored).join(', ')}) — reported, not refused.`);
117
126
  }
118
127
  if (unmanagedFiles.length === 0)
119
128
  return false;
120
- const firstFew = unmanagedFiles.slice(0, 3).join(', ');
121
- const more = unmanagedFiles.length > 3 ? `, +${String(unmanagedFiles.length - 3)} more` : '';
122
- warn(`${patch.filename}: found ${String(unmanagedFiles.length)} unmanaged file(s) adjacent to this patch's ownership (${firstFew}${more}); plain re-export keeps filesAffected unchanged — use --scan-file to include reviewed files.`);
123
- for (const file of unmanagedFiles) {
124
- info(` ${file} — fireforge re-export ${patch.filename} --scan --scan-file ${file}`);
129
+ const firstFew = unmanagedFiles.slice(0, 3).map(describeAnchored).join(', ');
130
+ const more = unmanagedFiles.length > 3 ? `, +${unmanagedFiles.length - 3} more` : '';
131
+ warn(`${patch.filename}: found ${unmanagedFiles.length} unmanaged file(s) adjacent to this patch's ownership (${firstFew}${more}); ` +
132
+ `adjacent means the file sits in a directory this patch already owns a file in. ` +
133
+ `Plain re-export keeps filesAffected unchanged — use --scan-file to include reviewed files.`);
134
+ for (const entry of unmanagedFiles) {
135
+ info(` ${entry.file} — beside engine/${entry.anchorDir} — ` +
136
+ `fireforge re-export ${patch.filename} --scan --scan-file ${entry.file}`);
125
137
  }
126
138
  if (ctx.refuseAdjacentUnmanaged) {
127
- ctx.refusals.push({ patchFilename: patch.filename, files: unmanagedFiles });
139
+ ctx.refusals.push({
140
+ patchFilename: patch.filename,
141
+ files: unmanagedFiles.map((entry) => entry.file),
142
+ anchored: unmanagedFiles.map(describeAnchored),
143
+ });
128
144
  return true;
129
145
  }
130
146
  return false;
@@ -1,5 +1,4 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
- import { createHash } from 'node:crypto';
3
2
  import { readdir, readFile } from 'node:fs/promises';
4
3
  import { join } from 'node:path';
5
4
  import { isUnavailableGenerationToken, snapshotEngineGeneration, unavailableGenerationReason, } from '../core/engine-session-lock.js';
@@ -10,12 +9,11 @@ import { GeneralError, InvalidArgumentError } from '../errors/base.js';
10
9
  import { mapWithConcurrency } from '../utils/concurrency.js';
11
10
  import { getNodeErrorCode, toError } from '../utils/errors.js';
12
11
  import { readText } from '../utils/fs.js';
12
+ import { sha256Hex } from '../utils/hash.js';
13
+ import { isObject } from '../utils/validation.js';
13
14
  import { normalizeEngineRelativeInput } from './re-export-scan.js';
14
15
  /** Concurrency bound for patch-body hashing (matches the classify/lint pools). */
15
16
  const PATCH_HASH_CONCURRENCY = 8;
16
- function isRecord(value) {
17
- return typeof value === 'object' && value !== null && !Array.isArray(value);
18
- }
19
17
  function parseScanFilesManifest(raw, manifestPath) {
20
18
  let parsed;
21
19
  try {
@@ -24,12 +22,12 @@ function parseScanFilesManifest(raw, manifestPath) {
24
22
  catch (error) {
25
23
  throw new InvalidArgumentError(`--scan-files manifest is not valid JSON (${manifestPath}): ${toError(error).message}`, '--scan-files');
26
24
  }
27
- if (!isRecord(parsed) || !Array.isArray(parsed['assignments'])) {
25
+ if (!isObject(parsed) || !Array.isArray(parsed['assignments'])) {
28
26
  throw new InvalidArgumentError('--scan-files manifest must contain an assignments array.', '--scan-files');
29
27
  }
30
28
  const assignments = [];
31
29
  for (const [index, assignment] of parsed['assignments'].entries()) {
32
- if (!isRecord(assignment)) {
30
+ if (!isObject(assignment)) {
33
31
  throw new InvalidArgumentError(`--scan-files assignments[${index}] must be an object.`, '--scan-files');
34
32
  }
35
33
  const { patch, files } = assignment;
@@ -99,7 +97,7 @@ async function fingerprintDryRunState(engineDir, patchesDir) {
99
97
  throw new GeneralError(`[dry-run] cannot fingerprint the engine working tree for the purity guard: ` +
100
98
  unavailableGenerationReason(generation));
101
99
  }
102
- fingerprint.set('the engine working tree', createHash('sha256').update(generation).digest('hex'));
100
+ fingerprint.set('the engine working tree', sha256Hex(generation));
103
101
  let names = [];
104
102
  try {
105
103
  const entries = await readdir(patchesDir, { withFileTypes: true });
@@ -122,9 +120,7 @@ async function fingerprintDryRunState(engineDir, patchesDir) {
122
120
  const results = await mapWithConcurrency(names, PATCH_HASH_CONCURRENCY, async (name) => {
123
121
  try {
124
122
  return {
125
- hash: createHash('sha256')
126
- .update(await readFile(join(patchesDir, name)))
127
- .digest('hex'),
123
+ hash: sha256Hex(await readFile(join(patchesDir, name))),
128
124
  };
129
125
  }
130
126
  catch (error) {
@@ -101,7 +101,7 @@ function summarizeForeignHunks(payload, foreignAdded, foreignRemoved) {
101
101
  }
102
102
  }
103
103
  if (added > 0 || removed > 0) {
104
- summaries.push(`@@ -${String(hunk.oldStart)},${String(hunk.oldCount)} +${String(hunk.newStart)},${String(hunk.newCount)} @@ (+${String(added)}/-${String(removed)})`);
104
+ summaries.push(`@@ -${hunk.oldStart},${hunk.oldCount} +${hunk.newStart},${hunk.newCount} @@ (+${added}/-${removed})`);
105
105
  }
106
106
  }
107
107
  }
@@ -184,7 +184,7 @@ export async function reportForeignDrift(args) {
184
184
  const recentlyEdited = await findFilesEditedSinceLastExport(engineDir, oldPath, drift.map((d) => d.file));
185
185
  const totalLines = drift.reduce((sum, d) => sum + d.addedLines + d.removedLines, 0);
186
186
  const lineNoun = totalLines === 1 ? 'line' : 'lines';
187
- warn(`${patch.filename}: refreshed body absorbs ${String(totalLines)} ${lineNoun} not present in the old patch body (${String(drift.length)} file(s)):`);
187
+ warn(`${patch.filename}: refreshed body absorbs ${totalLines} ${lineNoun} not present in the old patch body (${drift.length} file(s)):`);
188
188
  for (const entry of drift.slice(0, MAX_FILES_REPORTED)) {
189
189
  const expectedTag = ctx.expectedDriftFiles.has(entry.file) ? ' (expected via --expect)' : '';
190
190
  const authorshipTag = recentlyEdited.has(entry.file)
@@ -194,13 +194,13 @@ export async function reportForeignDrift(args) {
194
194
  info(` ${entry.file}: binary content changed${expectedTag}${authorshipTag}`);
195
195
  continue;
196
196
  }
197
- info(` ${entry.file}: +${String(entry.addedLines)}/-${String(entry.removedLines)} newly captured line(s)${expectedTag}${authorshipTag}`);
197
+ info(` ${entry.file}: +${entry.addedLines}/-${entry.removedLines} newly captured line(s)${expectedTag}${authorshipTag}`);
198
198
  for (const summary of entry.hunkSummaries) {
199
199
  info(` ${summary}`);
200
200
  }
201
201
  }
202
202
  if (drift.length > MAX_FILES_REPORTED) {
203
- info(` +${String(drift.length - MAX_FILES_REPORTED)} more file(s)`);
203
+ info(` +${drift.length - MAX_FILES_REPORTED} more file(s)`);
204
204
  }
205
205
  if (ctx.refuseForeignDrift) {
206
206
  if (unexpected.length === 0) {
@@ -1,6 +1,5 @@
1
- import { getProjectPaths } from '../core/config.js';
2
1
  import type { PatchMetadata, ReExportOptions } from '../types/commands/index.js';
3
- import type { FireForgeConfig } from '../types/config.js';
2
+ import type { FireForgeConfig, ProjectPaths } from '../types/config.js';
4
3
  /**
5
4
  * Handles `re-export --files` end-to-end: computes the projected diff, runs
6
5
  * the per-patch and cross-patch lint against a context in which the target
@@ -13,4 +12,4 @@ import type { FireForgeConfig } from '../types/config.js';
13
12
  * generic single-patch helper is what makes the projection lint run against
14
13
  * the current (unchanged) queue instead of the projected state.
15
14
  */
16
- export declare function reExportFilesInPlace(paths: ReturnType<typeof getProjectPaths>, selectedPatches: PatchMetadata[], options: ReExportOptions, config: FireForgeConfig): Promise<void>;
15
+ export declare function reExportFilesInPlace(paths: ProjectPaths, selectedPatches: PatchMetadata[], options: ReExportOptions, config: FireForgeConfig): Promise<void>;
@@ -1,21 +1,20 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { join } from 'node:path';
3
- import { getProjectPaths } from '../core/config.js';
4
3
  import { appendHistory, confirmDestructive, } from '../core/destructive.js';
5
4
  import { enforceFreshFurnaceSources } from '../core/furnace-stale-export.js';
6
5
  import { getDiffForFilesAgainstHead } from '../core/git-diff.js';
7
6
  import { listTrackedInHead } from '../core/git-file-ops.js';
8
7
  import { extractAffectedFiles } from '../core/patch-apply.js';
9
8
  import { updatePatchAndMetadata } from '../core/patch-export.js';
10
- import { buildModifiedFileAdditionsFromDiff, buildPatchQueueContext, formatPatchLintIssue, lintPatchQueue, } from '../core/patch-lint.js';
9
+ import { buildPatchQueueContext, formatPatchLintIssue, lintPatchQueue, } from '../core/patch-lint.js';
11
10
  import { computeProjectedLintRegressions } from '../core/patch-lint-projection.js';
12
11
  import { loadPatchesManifest } from '../core/patch-manifest.js';
13
12
  import { buildProjectedManifest, enforcePatchPolicy } from '../core/patch-policy.js';
14
- import { buildNewFileTextProjection } from '../core/patch-transform.js';
15
13
  import { InvalidArgumentError } from '../errors/base.js';
16
14
  import { pathExists } from '../utils/fs.js';
17
15
  import { info, outro, success, warn } from '../utils/logger.js';
18
16
  import { runPatchLint } from './export-shared.js';
17
+ import { projectEntryBody } from './patch/entry-projection.js';
19
18
  /**
20
19
  * Computes the effective `tier` and `lintIgnore` carrying both the patch's
21
20
  * existing values and the CLI flag overrides.
@@ -42,17 +41,11 @@ function resolveEffectiveTierAndLintIgnore(target, options) {
42
41
  */
43
42
  async function runProjectedCrossPatchLint(patchesDir, targetFilename, projectedDiff) {
44
43
  const baseCtx = await buildPatchQueueContext(patchesDir);
45
- const projectedNewFiles = buildNewFileTextProjection(projectedDiff);
46
- const projectedModifiedFileAdditions = buildModifiedFileAdditionsFromDiff(projectedDiff);
44
+ const projection = projectEntryBody(projectedDiff);
47
45
  const projectedEntries = baseCtx.entries.map((entry) => {
48
46
  if (entry.filename !== targetFilename)
49
47
  return entry;
50
- return {
51
- ...entry,
52
- diff: projectedDiff,
53
- newFiles: projectedNewFiles,
54
- modifiedFileAdditions: projectedModifiedFileAdditions,
55
- };
48
+ return { ...entry, ...projection };
56
49
  });
57
50
  const baselineIssues = lintPatchQueue(baseCtx).filter((i) => i.severity === 'error');
58
51
  const projectedIssues = lintPatchQueue({ entries: projectedEntries }).filter((i) => i.severity === 'error');
@@ -235,7 +228,16 @@ export async function reExportFilesInPlace(paths, selectedPatches, options, conf
235
228
  const patchQueueCtx = (await pathExists(paths.patches))
236
229
  ? await buildPatchQueueContext(paths.patches)
237
230
  : undefined;
238
- await runPatchLint(paths.engine, actualProjectedFiles, projectedDiff, config, options.skipLint, patchQueueCtx, ignoreChecks, effectiveTier);
231
+ await runPatchLint({
232
+ engineDir: paths.engine,
233
+ filesAffected: actualProjectedFiles,
234
+ diffContent: projectedDiff,
235
+ config,
236
+ skipLint: options.skipLint,
237
+ patchQueueCtx,
238
+ ignoreChecks,
239
+ patchTier: effectiveTier,
240
+ });
239
241
  const conflicts = await runProjectedCrossPatchLint(paths.patches, target.filename, projectedDiff);
240
242
  const filesUpdates = buildFilesModeMetadataUpdates(actualProjectedFiles, options, effectiveLintIgnore, flagIgnoreSet);
241
243
  const manifest = await loadPatchesManifest(paths.patches);
@@ -273,23 +275,30 @@ export async function reExportFilesInPlace(paths, selectedPatches, options, conf
273
275
  // as the mutation (via the onCommitted hook) so two concurrent re-exports
274
276
  // cannot interleave records and a crash between mutation and append cannot
275
277
  // orphan the audit trail.
276
- await updatePatchAndMetadata(paths.patches, target.filename, projectedDiff, filesUpdates, async () => {
277
- await appendHistory(paths.patches, {
278
- operation: 're-export-files',
279
- args: {
280
- filename: target.filename,
281
- files: actualProjectedFiles,
282
- previousFiles: target.filesAffected,
283
- deletionsCaptured: deletedFiles,
284
- },
285
- ...(options.yes === true ? { yes: true } : {}),
286
- ...(options.forceUnsafe === true ? { unsafeOverride: true } : {}),
287
- result: 'ok',
288
- });
289
- }, {
290
- config,
291
- command: 're-export --files',
292
- forceUnsafe: options.forceUnsafe === true,
278
+ await updatePatchAndMetadata({
279
+ patchesDir: paths.patches,
280
+ filename: target.filename,
281
+ newContent: projectedDiff,
282
+ updates: filesUpdates,
283
+ onCommitted: async () => {
284
+ await appendHistory(paths.patches, {
285
+ operation: 're-export-files',
286
+ args: {
287
+ filename: target.filename,
288
+ files: actualProjectedFiles,
289
+ previousFiles: target.filesAffected,
290
+ deletionsCaptured: deletedFiles,
291
+ },
292
+ ...(options.yes === true ? { yes: true } : {}),
293
+ ...(options.forceUnsafe === true ? { unsafeOverride: true } : {}),
294
+ result: 'ok',
295
+ });
296
+ },
297
+ policyGate: {
298
+ config,
299
+ command: 're-export --files',
300
+ forceUnsafe: options.forceUnsafe === true,
301
+ },
293
302
  });
294
303
  success(`Re-exported ${target.filename}`);
295
304
  outro('Re-export complete');
@@ -19,16 +19,15 @@
19
19
  * (`refreshQueueCtxEntry`), so later iterations of an `--all` run lint
20
20
  * against the just-refreshed body instead of the stale one.
21
21
  */
22
- import { getProjectPaths, loadConfig } from '../core/config.js';
23
22
  import { type PatchQueueContext } from '../core/patch-lint.js';
24
23
  import { type PerPatchLintCacheFile } from '../core/patch-lint-cache.js';
25
24
  import type { PatchLintIssue, PatchMetadata } from '../types/commands/index.js';
26
- import type { FireForgeConfig } from '../types/config.js';
25
+ import type { FireForgeConfig, ProjectPaths } from '../types/config.js';
27
26
  import { type PerRunCheckJs } from './lint-per-run-checkjs.js';
28
27
  /** Per-invocation lint context, built once and threaded through the loop. */
29
28
  export interface ReExportLintContext {
30
29
  projectRoot: string;
31
- paths: ReturnType<typeof getProjectPaths>;
30
+ paths: ProjectPaths;
32
31
  config: FireForgeConfig;
33
32
  /** Whole-queue context, built WITH config so the forward-import hints
34
33
  * match `lint --per-patch`; undefined when no patches dir. */
@@ -44,7 +43,7 @@ export interface ReExportLintContext {
44
43
  globalsEmitted: boolean;
45
44
  }
46
45
  /** Builds the once-per-invocation lint context. */
47
- export declare function buildReExportLintContext(projectRoot: string, paths: ReturnType<typeof getProjectPaths>, config: Awaited<ReturnType<typeof loadConfig>>, noCache: boolean): Promise<ReExportLintContext>;
46
+ export declare function buildReExportLintContext(projectRoot: string, paths: ProjectPaths, config: FireForgeConfig, noCache: boolean): Promise<ReExportLintContext>;
48
47
  /** Fresh lint payload carried to the post-write cache store. */
49
48
  export interface ReExportLintResult {
50
49
  issues: PatchLintIssue[];
@@ -20,7 +20,6 @@
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 { getProjectPaths, loadConfig } from '../core/config.js';
24
23
  import { buildPatchQueueContext, countNonBinaryDiffLines, detectNewFilesInDiff, lintExportedPatch, resolvePatchSizeTier, } from '../core/patch-lint.js';
25
24
  import { buildPerPatchLintCacheKey, getCachedPerPatchLintIssues, getPerPatchLintCacheHeadSha, loadPerPatchLintCache, savePerPatchLintCache, setCachedPerPatchLintIssues, } from '../core/patch-lint-cache.js';
26
25
  import { invalidateNewFileCreatorsCache } from '../core/patch-lint-cross.js';
@@ -169,7 +168,15 @@ export async function storeReExportLintResult(lintCtx, patchFilename, projectedM
169
168
  queueContext: patchQueueCtx,
170
169
  ...(lintCtx.engineHeadSha === undefined ? {} : { engineHeadSha: lintCtx.engineHeadSha }),
171
170
  });
172
- setCachedPerPatchLintIssues(cache, patchFilename, cacheKey, result.issues, result.suppressed, result.lineCount, result.lintIgnore);
171
+ setCachedPerPatchLintIssues({
172
+ cache,
173
+ patchFilename,
174
+ key: cacheKey,
175
+ issues: result.issues,
176
+ suppressed: result.suppressed,
177
+ lineCount: result.lineCount,
178
+ lintIgnore: result.lintIgnore,
179
+ });
173
180
  lintCtx.cacheDirty = true;
174
181
  }
175
182
  /**