@hominis/fireforge 0.44.4 → 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 (378) hide show
  1. package/CHANGELOG.md +120 -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-source-pin.d.ts +29 -0
  18. package/dist/src/commands/doctor-source-pin.js +81 -0
  19. package/dist/src/commands/doctor.d.ts +2 -0
  20. package/dist/src/commands/doctor.js +9 -8
  21. package/dist/src/commands/download.js +35 -24
  22. package/dist/src/commands/export-all.js +21 -4
  23. package/dist/src/commands/export-flow.js +15 -60
  24. package/dist/src/commands/export-placement-conflicts.js +3 -3
  25. package/dist/src/commands/export-placement-gate.js +2 -8
  26. package/dist/src/commands/export-placement-policy.js +3 -5
  27. package/dist/src/commands/export-shared.d.ts +42 -23
  28. package/dist/src/commands/export-shared.js +4 -19
  29. package/dist/src/commands/export.js +10 -1
  30. package/dist/src/commands/furnace/authoring-config.d.ts +13 -0
  31. package/dist/src/commands/furnace/authoring-config.js +19 -0
  32. package/dist/src/commands/furnace/chrome-doc-remove.js +1 -1
  33. package/dist/src/commands/furnace/create-browser-test.js +3 -8
  34. package/dist/src/commands/furnace/create-dry-run.js +2 -5
  35. package/dist/src/commands/furnace/create-templates.d.ts +17 -1
  36. package/dist/src/commands/furnace/create-templates.js +2 -1
  37. package/dist/src/commands/furnace/create.js +25 -19
  38. package/dist/src/commands/furnace/deploy.js +11 -2
  39. package/dist/src/commands/furnace/init.js +2 -1
  40. package/dist/src/commands/furnace/override.js +28 -19
  41. package/dist/src/commands/furnace/refresh.js +9 -1
  42. package/dist/src/commands/furnace/remove-mutations.d.ts +6 -7
  43. package/dist/src/commands/furnace/remove-mutations.js +0 -1
  44. package/dist/src/commands/furnace/remove.js +2 -6
  45. package/dist/src/commands/furnace/rename-browser-test.js +1 -7
  46. package/dist/src/commands/furnace/rename.js +29 -29
  47. package/dist/src/commands/furnace/status.js +12 -9
  48. package/dist/src/commands/furnace/test-file-name.d.ts +28 -0
  49. package/dist/src/commands/furnace/test-file-name.js +39 -0
  50. package/dist/src/commands/furnace/validation-output.d.ts +21 -9
  51. package/dist/src/commands/furnace/validation-output.js +3 -8
  52. package/dist/src/commands/import.js +2 -9
  53. package/dist/src/commands/lint-per-patch.d.ts +2 -2
  54. package/dist/src/commands/lint-per-patch.js +19 -4
  55. package/dist/src/commands/lint-per-run-checkjs.d.ts +2 -2
  56. package/dist/src/commands/lint.d.ts +2 -1
  57. package/dist/src/commands/lint.js +1 -1
  58. package/dist/src/commands/manifest.js +1 -1
  59. package/dist/src/commands/package.js +2 -4
  60. package/dist/src/commands/patch/compact.js +14 -15
  61. package/dist/src/commands/patch/delete.js +15 -25
  62. package/dist/src/commands/patch/entry-projection.d.ts +13 -0
  63. package/dist/src/commands/patch/entry-projection.js +24 -0
  64. package/dist/src/commands/patch/lint-ignore.d.ts +0 -8
  65. package/dist/src/commands/patch/lint-ignore.js +19 -29
  66. package/dist/src/commands/patch/move-files-create.js +24 -10
  67. package/dist/src/commands/patch/move-files-into.js +38 -37
  68. package/dist/src/commands/patch/move-files.js +4 -13
  69. package/dist/src/commands/patch/rename.js +27 -41
  70. package/dist/src/commands/patch/reorder.js +25 -38
  71. package/dist/src/commands/patch/split-plan.d.ts +4 -5
  72. package/dist/src/commands/patch/split-plan.js +4 -11
  73. package/dist/src/commands/patch/split.js +45 -43
  74. package/dist/src/commands/patch/staged-dependency.js +16 -22
  75. package/dist/src/commands/patch/tier.js +15 -25
  76. package/dist/src/commands/patch-rename-map.d.ts +19 -0
  77. package/dist/src/commands/patch-rename-map.js +36 -0
  78. package/dist/src/commands/pending-resolution.d.ts +15 -0
  79. package/dist/src/commands/pending-resolution.js +25 -0
  80. package/dist/src/commands/re-export-adjacent.d.ts +32 -2
  81. package/dist/src/commands/re-export-adjacent.js +51 -10
  82. package/dist/src/commands/re-export-bulk-scan.js +6 -10
  83. package/dist/src/commands/re-export-drift.d.ts +8 -0
  84. package/dist/src/commands/re-export-drift.js +65 -4
  85. package/dist/src/commands/re-export-files.d.ts +2 -3
  86. package/dist/src/commands/re-export-files.js +38 -32
  87. package/dist/src/commands/re-export-lint.d.ts +3 -4
  88. package/dist/src/commands/re-export-lint.js +9 -2
  89. package/dist/src/commands/re-export-options.js +19 -5
  90. package/dist/src/commands/re-export-register.js +5 -1
  91. package/dist/src/commands/re-export-scan.js +8 -8
  92. package/dist/src/commands/re-export-single.d.ts +22 -3
  93. package/dist/src/commands/re-export-single.js +19 -11
  94. package/dist/src/commands/re-export.js +44 -27
  95. package/dist/src/commands/rebase/abort.js +3 -8
  96. package/dist/src/commands/rebase/continue.js +12 -12
  97. package/dist/src/commands/rebase/index.js +2 -2
  98. package/dist/src/commands/rebase/patch-loop.d.ts +2 -2
  99. package/dist/src/commands/rebase/patch-loop.js +2 -7
  100. package/dist/src/commands/resolve.js +18 -10
  101. package/dist/src/commands/run.d.ts +0 -13
  102. package/dist/src/commands/run.js +12 -25
  103. package/dist/src/commands/setup-support.d.ts +16 -4
  104. package/dist/src/commands/setup-support.js +16 -16
  105. package/dist/src/commands/setup.js +3 -9
  106. package/dist/src/commands/source.js +8 -1
  107. package/dist/src/commands/status-check.js +2 -2
  108. package/dist/src/commands/status-output.js +2 -2
  109. package/dist/src/commands/status.js +3 -4
  110. package/dist/src/commands/test-build-phase.d.ts +2 -2
  111. package/dist/src/commands/test-build-phase.js +33 -8
  112. package/dist/src/commands/test-diagnose.js +14 -5
  113. package/dist/src/commands/test-modes.d.ts +2 -2
  114. package/dist/src/commands/test-modes.js +0 -1
  115. package/dist/src/commands/test-register.js +6 -3
  116. package/dist/src/commands/test-run.js +29 -21
  117. package/dist/src/commands/test-stale-gate.js +8 -5
  118. package/dist/src/commands/test-verdict.d.ts +16 -2
  119. package/dist/src/commands/test-verdict.js +18 -3
  120. package/dist/src/commands/test.js +51 -7
  121. package/dist/src/commands/token-list.d.ts +26 -0
  122. package/dist/src/commands/token-list.js +153 -0
  123. package/dist/src/commands/token.js +17 -1
  124. package/dist/src/commands/tree.js +7 -2
  125. package/dist/src/commands/typecheck.js +9 -9
  126. package/dist/src/commands/wire.js +4 -3
  127. package/dist/src/core/branding.d.ts +0 -7
  128. package/dist/src/core/branding.js +3 -2
  129. package/dist/src/core/browser-wire.js +6 -3
  130. package/dist/src/core/build-audit-platform.d.ts +29 -0
  131. package/dist/src/core/build-audit-platform.js +140 -0
  132. package/dist/src/core/build-audit-registration.js +3 -2
  133. package/dist/src/core/build-audit-skip.d.ts +57 -0
  134. package/dist/src/core/build-audit-skip.js +112 -0
  135. package/dist/src/core/build-audit.d.ts +15 -2
  136. package/dist/src/core/build-audit.js +154 -12
  137. package/dist/src/core/build-baseline.d.ts +45 -28
  138. package/dist/src/core/build-baseline.js +6 -29
  139. package/dist/src/core/build-jar-escalation.d.ts +64 -0
  140. package/dist/src/core/build-jar-escalation.js +128 -0
  141. package/dist/src/core/build-overwrite-guard.js +1 -1
  142. package/dist/src/core/build-prepare.d.ts +5 -2
  143. package/dist/src/core/build-prepare.js +34 -6
  144. package/dist/src/core/config-paths.d.ts +2 -6
  145. package/dist/src/core/config-paths.js +4 -2
  146. package/dist/src/core/config-state.d.ts +0 -6
  147. package/dist/src/core/config-state.js +1 -19
  148. package/dist/src/core/config-validate-build-audit.d.ts +8 -0
  149. package/dist/src/core/config-validate-build-audit.js +80 -0
  150. package/dist/src/core/config-validate-file-size.d.ts +12 -0
  151. package/dist/src/core/config-validate-file-size.js +65 -0
  152. package/dist/src/core/config-validate-patch-policy.d.ts +2 -2
  153. package/dist/src/core/config-validate-patch-policy.js +3 -3
  154. package/dist/src/core/config-validate-test-toolchains.d.ts +2 -2
  155. package/dist/src/core/config-validate-test-toolchains.js +4 -5
  156. package/dist/src/core/config-validate.js +20 -3
  157. package/dist/src/core/config.d.ts +2 -2
  158. package/dist/src/core/config.js +11 -15
  159. package/dist/src/core/coverage-extend.js +4 -4
  160. package/dist/src/core/discard-baseline.js +7 -7
  161. package/dist/src/core/engine-precondition.d.ts +1 -9
  162. package/dist/src/core/engine-precondition.js +3 -8
  163. package/dist/src/core/engine-session-lock.js +13 -17
  164. package/dist/src/core/export-moved-code-shape.js +2 -2
  165. package/dist/src/core/file-lock-owner.d.ts +103 -0
  166. package/dist/src/core/file-lock-owner.js +259 -0
  167. package/dist/src/core/file-lock.d.ts +2 -7
  168. package/dist/src/core/file-lock.js +40 -91
  169. package/dist/src/core/firefox-cache.d.ts +13 -1
  170. package/dist/src/core/firefox-cache.js +59 -34
  171. package/dist/src/core/firefox-download.d.ts +15 -0
  172. package/dist/src/core/firefox-download.js +37 -21
  173. package/dist/src/core/firefox-extract.d.ts +6 -5
  174. package/dist/src/core/firefox-extract.js +36 -18
  175. package/dist/src/core/firefox.d.ts +30 -32
  176. package/dist/src/core/firefox.js +5 -36
  177. package/dist/src/core/furnace-apply-dry-run.d.ts +1 -6
  178. package/dist/src/core/furnace-apply-dry-run.js +1 -7
  179. package/dist/src/core/furnace-apply-helpers.d.ts +0 -11
  180. package/dist/src/core/furnace-apply-helpers.js +8 -19
  181. package/dist/src/core/furnace-apply-overwrite-warn.js +4 -2
  182. package/dist/src/core/furnace-apply.d.ts +1 -1
  183. package/dist/src/core/furnace-apply.js +30 -10
  184. package/dist/src/core/furnace-config.d.ts +1 -1
  185. package/dist/src/core/furnace-config.js +23 -36
  186. package/dist/src/core/furnace-constants.js +3 -2
  187. package/dist/src/core/furnace-dir-entry.d.ts +20 -0
  188. package/dist/src/core/furnace-dir-entry.js +21 -0
  189. package/dist/src/core/furnace-operation.d.ts +2 -0
  190. package/dist/src/core/furnace-operation.js +3 -1
  191. package/dist/src/core/furnace-refresh.d.ts +18 -6
  192. package/dist/src/core/furnace-refresh.js +3 -6
  193. package/dist/src/core/furnace-registration-ast.d.ts +0 -1
  194. package/dist/src/core/furnace-registration-ast.js +0 -3
  195. package/dist/src/core/furnace-registration.d.ts +2 -1
  196. package/dist/src/core/furnace-registration.js +22 -29
  197. package/dist/src/core/furnace-scanner.js +3 -6
  198. package/dist/src/core/furnace-state-persist.d.ts +2 -2
  199. package/dist/src/core/furnace-validate-registration.js +5 -7
  200. package/dist/src/core/git-base.d.ts +3 -1
  201. package/dist/src/core/git-base.js +25 -3
  202. package/dist/src/core/git-diff-new-file.d.ts +40 -0
  203. package/dist/src/core/git-diff-new-file.js +119 -0
  204. package/dist/src/core/git-diff.d.ts +9 -9
  205. package/dist/src/core/git-diff.js +88 -157
  206. package/dist/src/core/git-file-ops.d.ts +0 -7
  207. package/dist/src/core/git-file-ops.js +4 -28
  208. package/dist/src/core/git-readonly-index.d.ts +0 -2
  209. package/dist/src/core/git-readonly-index.js +0 -4
  210. package/dist/src/core/git-status.d.ts +7 -0
  211. package/dist/src/core/git-status.js +21 -9
  212. package/dist/src/core/git.d.ts +0 -6
  213. package/dist/src/core/git.js +14 -43
  214. package/dist/src/core/harness-orphans.d.ts +73 -0
  215. package/dist/src/core/harness-orphans.js +187 -0
  216. package/dist/src/core/history-log.d.ts +19 -0
  217. package/dist/src/core/history-log.js +29 -0
  218. package/dist/src/core/mach-error-hints.d.ts +0 -5
  219. package/dist/src/core/mach-error-hints.js +1 -1
  220. package/dist/src/core/mach-known-noise-filter.d.ts +25 -3
  221. package/dist/src/core/mach-known-noise-filter.js +31 -10
  222. package/dist/src/core/mach-mozconfig.js +2 -1
  223. package/dist/src/core/mach-python.d.ts +2 -0
  224. package/dist/src/core/mach-python.js +12 -7
  225. package/dist/src/core/mach-resource-shim.d.ts +13 -1
  226. package/dist/src/core/mach-resource-shim.js +112 -9
  227. package/dist/src/core/mach.d.ts +46 -53
  228. package/dist/src/core/mach.js +31 -83
  229. package/dist/src/core/marionette-port.d.ts +0 -17
  230. package/dist/src/core/marionette-port.js +12 -58
  231. package/dist/src/core/marionette-preflight.js +9 -27
  232. package/dist/src/core/mochitest-server-port.js +3 -3
  233. package/dist/src/core/moz-manifest-rules.js +37 -33
  234. package/dist/src/core/patch-apply-fuzz.js +4 -4
  235. package/dist/src/core/patch-apply.d.ts +4 -5
  236. package/dist/src/core/patch-apply.js +55 -32
  237. package/dist/src/core/patch-export-coverage.d.ts +0 -9
  238. package/dist/src/core/patch-export-coverage.js +1 -30
  239. package/dist/src/core/patch-export-update.d.ts +18 -1
  240. package/dist/src/core/patch-export-update.js +4 -3
  241. package/dist/src/core/patch-export.d.ts +11 -21
  242. package/dist/src/core/patch-export.js +44 -66
  243. package/dist/src/core/patch-files.d.ts +0 -4
  244. package/dist/src/core/patch-files.js +0 -11
  245. package/dist/src/core/patch-lint-binary.d.ts +6 -12
  246. package/dist/src/core/patch-lint-cache.d.ts +17 -1
  247. package/dist/src/core/patch-lint-cache.js +3 -6
  248. package/dist/src/core/patch-lint-checkjs.d.ts +33 -16
  249. package/dist/src/core/patch-lint-checkjs.js +43 -25
  250. package/dist/src/core/patch-lint-creators.d.ts +2 -6
  251. package/dist/src/core/patch-lint-cross.d.ts +16 -8
  252. package/dist/src/core/patch-lint-cross.js +49 -92
  253. package/dist/src/core/patch-lint-css.js +10 -6
  254. package/dist/src/core/patch-lint-file-size.d.ts +52 -0
  255. package/dist/src/core/patch-lint-file-size.js +53 -0
  256. package/dist/src/core/patch-lint-forward-registration.d.ts +58 -0
  257. package/dist/src/core/patch-lint-forward-registration.js +210 -0
  258. package/dist/src/core/patch-lint-jsdoc.js +36 -36
  259. package/dist/src/core/patch-lint-module-registration.d.ts +2 -8
  260. package/dist/src/core/patch-lint-prettier.d.ts +36 -0
  261. package/dist/src/core/patch-lint-prettier.js +126 -0
  262. package/dist/src/core/patch-lint-queue-types.d.ts +60 -0
  263. package/dist/src/core/patch-lint-queue-types.js +12 -0
  264. package/dist/src/core/patch-lint-staged-registration.d.ts +61 -0
  265. package/dist/src/core/patch-lint-staged-registration.js +90 -0
  266. package/dist/src/core/patch-lint-unmanaged-companion.d.ts +106 -0
  267. package/dist/src/core/patch-lint-unmanaged-companion.js +214 -0
  268. package/dist/src/core/patch-lint.d.ts +14 -9
  269. package/dist/src/core/patch-lint.js +27 -27
  270. package/dist/src/core/patch-lock.js +3 -3
  271. package/dist/src/core/patch-manifest-io.js +8 -7
  272. package/dist/src/core/patch-parse.d.ts +19 -8
  273. package/dist/src/core/patch-parse.js +25 -0
  274. package/dist/src/core/patch-policy.js +15 -27
  275. package/dist/src/core/patch-source-metadata.d.ts +1 -3
  276. package/dist/src/core/patch-source-metadata.js +0 -4
  277. package/dist/src/core/patch-transform.d.ts +21 -8
  278. package/dist/src/core/patch-transform.js +38 -10
  279. package/dist/src/core/process-cpu-time.js +1 -1
  280. package/dist/src/core/rebase-session.d.ts +2 -8
  281. package/dist/src/core/rebase-session.js +5 -17
  282. package/dist/src/core/register-browser-content.js +3 -2
  283. package/dist/src/core/register-browser-test.js +2 -1
  284. package/dist/src/core/register-module.js +2 -1
  285. package/dist/src/core/register-scaffold.js +5 -4
  286. package/dist/src/core/register-shared-css.js +2 -1
  287. package/dist/src/core/run-log-redact.d.ts +38 -0
  288. package/dist/src/core/run-log-redact.js +69 -0
  289. package/dist/src/core/run-log.d.ts +13 -2
  290. package/dist/src/core/run-log.js +67 -18
  291. package/dist/src/core/signal-critical.js +2 -4
  292. package/dist/src/core/smoke-patterns.d.ts +0 -1
  293. package/dist/src/core/smoke-patterns.js +18 -11
  294. package/dist/src/core/state-file.d.ts +12 -1
  295. package/dist/src/core/state-file.js +14 -1
  296. package/dist/src/core/status-classify.js +2 -1
  297. package/dist/src/core/test-changed-prefs.js +26 -11
  298. package/dist/src/core/test-harness-crash.js +61 -48
  299. package/dist/src/core/test-harness-verdict-notes.d.ts +55 -0
  300. package/dist/src/core/test-harness-verdict-notes.js +48 -0
  301. package/dist/src/core/test-stale-check.d.ts +23 -8
  302. package/dist/src/core/test-stale-check.js +77 -4
  303. package/dist/src/core/test-stall-triage.js +37 -19
  304. package/dist/src/core/test-xpcshell-retry.d.ts +26 -7
  305. package/dist/src/core/test-xpcshell-retry.js +14 -11
  306. package/dist/src/core/token-category.d.ts +33 -0
  307. package/dist/src/core/token-category.js +56 -48
  308. package/dist/src/core/token-dark-mode.js +2 -14
  309. package/dist/src/core/token-inventory.d.ts +56 -0
  310. package/dist/src/core/token-inventory.js +109 -0
  311. package/dist/src/core/token-manager.d.ts +1 -1
  312. package/dist/src/core/token-manager.js +1 -1
  313. package/dist/src/core/token-variant.js +21 -22
  314. package/dist/src/core/toolchain-preflight.js +1 -1
  315. package/dist/src/core/tree-cow.d.ts +0 -2
  316. package/dist/src/core/tree-cow.js +2 -2
  317. package/dist/src/core/tree-store.js +4 -5
  318. package/dist/src/core/typecheck.js +2 -2
  319. package/dist/src/core/wire-dom-fragment.js +6 -17
  320. package/dist/src/core/wire-utils.js +5 -17
  321. package/dist/src/core/xpcshell-appdir.js +2 -4
  322. package/dist/src/errors/base.d.ts +53 -1
  323. package/dist/src/errors/base.js +79 -11
  324. package/dist/src/errors/build.d.ts +28 -0
  325. package/dist/src/errors/build.js +51 -10
  326. package/dist/src/errors/config.d.ts +0 -14
  327. package/dist/src/errors/config.js +13 -34
  328. package/dist/src/errors/download.d.ts +13 -0
  329. package/dist/src/errors/download.js +53 -24
  330. package/dist/src/errors/furnace.js +8 -9
  331. package/dist/src/errors/git.d.ts +8 -8
  332. package/dist/src/errors/git.js +20 -30
  333. package/dist/src/errors/patch.js +8 -10
  334. package/dist/src/errors/rebase.js +7 -7
  335. package/dist/src/types/commands/options.d.ts +96 -123
  336. package/dist/src/types/commands/patches.d.ts +17 -6
  337. package/dist/src/types/config.d.ts +75 -2
  338. package/dist/src/types/index.d.ts +0 -1
  339. package/dist/src/utils/assert.d.ts +3 -15
  340. package/dist/src/utils/assert.js +1 -18
  341. package/dist/src/utils/build-info.d.ts +1 -1
  342. package/dist/src/utils/child-env.d.ts +2 -1
  343. package/dist/src/utils/elapsed.js +10 -1
  344. package/dist/src/utils/fs.d.ts +4 -2
  345. package/dist/src/utils/fs.js +11 -14
  346. package/dist/src/utils/hash.d.ts +12 -0
  347. package/dist/src/utils/hash.js +16 -0
  348. package/dist/src/utils/line-dispatch.d.ts +15 -0
  349. package/dist/src/utils/line-dispatch.js +48 -0
  350. package/dist/src/utils/options.d.ts +1 -7
  351. package/dist/src/utils/options.js +6 -7
  352. package/dist/src/utils/package-root.js +3 -12
  353. package/dist/src/utils/platform.d.ts +0 -4
  354. package/dist/src/utils/process-group.d.ts +2 -1
  355. package/dist/src/utils/process-group.js +20 -19
  356. package/dist/src/utils/process.d.ts +40 -26
  357. package/dist/src/utils/process.js +115 -166
  358. package/dist/src/utils/ps-duration.js +3 -3
  359. package/dist/src/utils/sleep.d.ts +26 -0
  360. package/dist/src/utils/sleep.js +31 -0
  361. package/dist/src/utils/validation.d.ts +2 -38
  362. package/dist/src/utils/validation.js +7 -16
  363. package/docs/configuration.md +136 -1
  364. package/docs/exit-codes.md +34 -20
  365. package/docs/lifecycle-invariants.md +18 -12
  366. package/docs/machine-output.md +31 -8
  367. package/docs/patch-workflow.md +2 -1
  368. package/docs/run-logs.md +44 -4
  369. package/docs/testing.md +70 -11
  370. package/package.json +8 -7
  371. package/dist/src/commands/rebase.d.ts +0 -4
  372. package/dist/src/commands/rebase.js +0 -5
  373. package/dist/src/core/patch-artifact-normalize.d.ts +0 -21
  374. package/dist/src/core/patch-artifact-normalize.js +0 -24
  375. package/dist/src/core/patch-lint-reexports.d.ts +0 -7
  376. package/dist/src/core/patch-lint-reexports.js +0 -8
  377. package/dist/src/core/wire-targets.d.ts +0 -7
  378. package/dist/src/core/wire-targets.js +0 -8
@@ -20,12 +20,13 @@
20
20
  * runs `--add` and `--remove` back-to-back.
21
21
  */
22
22
  import { Command } from 'commander';
23
- import { appendHistory, confirmDestructive } from '../../core/destructive.js';
23
+ import { confirmDestructive } from '../../core/destructive.js';
24
+ import { appendHistoryBestEffort } from '../../core/history-log.js';
24
25
  import { mutatePatchMetadata } from '../../core/patch-export.js';
25
26
  import { GeneralError, InvalidArgumentError } from '../../errors/base.js';
26
- import { toError } from '../../utils/errors.js';
27
27
  import { info, intro, outro, warn } from '../../utils/logger.js';
28
28
  import { addWaitLockOption, resolveWaitLockSeconds, stringListOption, } from '../../utils/options.js';
29
+ import { proceedAfterDecision } from '../destructive-decision.js';
29
30
  import { requirePatchQueue, requirePatchTarget } from './patch-context.js';
30
31
  /**
31
32
  * Printed whenever `--add` lands a new check id. The waiver this command
@@ -65,7 +66,7 @@ function applyMode(existing, mode, values) {
65
66
  * `info()` / dry-run / history args. Exported for unit-testing the
66
67
  * message format directly without mocking the logger transport.
67
68
  */
68
- export function describeChange(before, after, mode, values) {
69
+ function describeChange(before, after, mode, values) {
69
70
  const beforeSet = new Set(before);
70
71
  const afterSet = new Set(after);
71
72
  if (mode === 'clear') {
@@ -135,17 +136,11 @@ export async function patchLintIgnoreCommand(projectRoot, identifier, options =
135
136
  yes: options.yes === true,
136
137
  dryRun: isDryRun,
137
138
  });
138
- if (decision === 'dry-run') {
139
- if (addsNewIds) {
140
- warn(LINT_IGNORE_REVIEW_WARNING);
141
- }
142
- outro('Dry run complete — no changes made');
143
- return;
139
+ if (decision === 'dry-run' && addsNewIds) {
140
+ warn(LINT_IGNORE_REVIEW_WARNING);
144
141
  }
145
- if (decision === 'declined') {
146
- outro('Cancelled — no changes made');
142
+ if (!proceedAfterDecision(decision, 'Cancelled — no changes made'))
147
143
  return;
148
- }
149
144
  const result = await mutatePatchMetadata(paths.patches, target.filename, (existing) => {
150
145
  const next = applyMode(existing.lintIgnore ?? [], mode, values);
151
146
  // Either set the new list when non-empty or unset the field
@@ -167,23 +162,18 @@ export async function patchLintIgnoreCommand(projectRoot, identifier, options =
167
162
  if (mode === 'add' && projected.length > existing.length) {
168
163
  warn(LINT_IGNORE_REVIEW_WARNING);
169
164
  }
170
- try {
171
- await appendHistory(paths.patches, {
172
- operation: 'patch-lint-ignore',
173
- args: {
174
- filename: target.filename,
175
- mode,
176
- values: [...values],
177
- before: existing,
178
- after: projected,
179
- },
180
- ...(options.yes === true ? { yes: true } : {}),
181
- result: 'ok',
182
- });
183
- }
184
- catch (historyError) {
185
- warn(`History log append failed after patch lint-ignore committed (${target.filename}): ${toError(historyError).message}`);
186
- }
165
+ await appendHistoryBestEffort(paths.patches, {
166
+ operation: 'patch-lint-ignore',
167
+ args: {
168
+ filename: target.filename,
169
+ mode,
170
+ values: [...values],
171
+ before: existing,
172
+ after: projected,
173
+ },
174
+ ...(options.yes === true ? { yes: true } : {}),
175
+ result: 'ok',
176
+ }, `patch lint-ignore committed (${target.filename})`);
187
177
  outro('Patch lint-ignore complete');
188
178
  }
189
179
  /**
@@ -15,10 +15,12 @@ import { assertConfirmationAvailable, confirmDestructive } from '../../core/dest
15
15
  import { formatPatchNotFoundError } from '../../core/patch-identifier-suggest.js';
16
16
  import { buildPatchQueueContext } from '../../core/patch-lint.js';
17
17
  import { loadPatchesManifest, resolvePatchIdentifier } from '../../core/patch-manifest.js';
18
+ import { formatPatchOrder } from '../../core/patch-parse.js';
18
19
  import { enforcePatchPolicy } from '../../core/patch-policy.js';
19
20
  import { GeneralError, InvalidArgumentError } from '../../errors/base.js';
20
21
  import { info, intro, outro, success } from '../../utils/logger.js';
21
22
  import { normalizePatchDisplayName } from '../../utils/validation.js';
23
+ import { proceedAfterDecision } from '../destructive-decision.js';
22
24
  import { resolvePlacementPlan } from '../export-flow.js';
23
25
  import { runPatchLint } from '../export-shared.js';
24
26
  import { commitPatchSplit } from './split.js';
@@ -95,8 +97,26 @@ export async function patchMoveFilesCreateCommand(projectRoot, fromIdentifier, n
95
97
  // projection lint is blind.
96
98
  const patchQueueCtx = await buildPatchQueueContext(paths.patches, config);
97
99
  const ignoreChecks = source.lintIgnore ? new Set(source.lintIgnore) : undefined;
98
- await runPatchLint(paths.engine, remainingFiles, remainingDiff, config, options.skipLint, patchQueueCtx, ignoreChecks, source.tier);
99
- await runPatchLint(paths.engine, movedFiles, movedDiff, config, options.skipLint, patchQueueCtx, ignoreChecks, source.tier);
100
+ await runPatchLint({
101
+ engineDir: paths.engine,
102
+ filesAffected: remainingFiles,
103
+ diffContent: remainingDiff,
104
+ config,
105
+ skipLint: options.skipLint,
106
+ patchQueueCtx,
107
+ ignoreChecks,
108
+ patchTier: source.tier,
109
+ });
110
+ await runPatchLint({
111
+ engineDir: paths.engine,
112
+ filesAffected: movedFiles,
113
+ diffContent: movedDiff,
114
+ config,
115
+ skipLint: options.skipLint,
116
+ patchQueueCtx,
117
+ ignoreChecks,
118
+ patchTier: source.tier,
119
+ });
100
120
  const { conflicts, stagedDependencyAdditions } = runProjectedSplitLint(plan, patchQueueCtx);
101
121
  plan.stagedDependencyAdditions = stagedDependencyAdditions;
102
122
  const newMetadata = buildNewPatchMetadata(plan, config);
@@ -118,16 +138,10 @@ export async function patchMoveFilesCreateCommand(projectRoot, fromIdentifier, n
118
138
  unsafeOverride: options.forceUnsafe === true,
119
139
  conflicts,
120
140
  });
121
- if (decision === 'dry-run') {
122
- outro('Dry run complete — no changes made');
141
+ if (!proceedAfterDecision(decision, 'Move cancelled'))
123
142
  return;
124
- }
125
- if (decision === 'declined') {
126
- outro('Move cancelled');
127
- return;
128
- }
129
143
  await commitPatchSplit(paths.patches, plan, newMetadata, options, config);
130
- success(`Created ${placement.newFilename} (order ${String(placement.insertionOrder).padStart(3, '0')}) ` +
144
+ success(`Created ${placement.newFilename} (order ${formatPatchOrder(placement.insertionOrder)}) ` +
131
145
  `and moved ${plan.movedFiles.length} file(s) out of ${source.filename}`);
132
146
  if (plan.ownerRewrites.length > 0) {
133
147
  info(`Re-pointed staged-dependency owners in: ${plan.ownerRewrites.join(', ')}`);
@@ -15,27 +15,21 @@
15
15
  * the worktree.
16
16
  */
17
17
  import { join } from 'node:path';
18
- import { appendHistory, assertConfirmationAvailable, confirmDestructive, } from '../../core/destructive.js';
19
- import { normalizePatchArtifact } from '../../core/patch-artifact-normalize.js';
20
- import { buildModifiedFileAdditionsFromDiff, buildPatchQueueContext, collectForwardImportEdges, detectNewFilesInDiff, formatPatchLintIssue, lintPatchQueue, } from '../../core/patch-lint.js';
18
+ import { assertConfirmationAvailable, confirmDestructive, } from '../../core/destructive.js';
19
+ import { appendHistoryBestEffort } from '../../core/history-log.js';
20
+ import { buildPatchQueueContext, collectForwardImportEdges, formatPatchLintIssue, lintPatchQueue, } from '../../core/patch-lint.js';
21
21
  import { computeProjectedLintRegressions } from '../../core/patch-lint-projection.js';
22
22
  import { withPatchDirectoryLock } from '../../core/patch-lock.js';
23
23
  import { loadPatchesManifest, savePatchesManifest, validatePatchesManifest, } from '../../core/patch-manifest.js';
24
- import { extractNewFileContentFromDiff } from '../../core/patch-transform.js';
25
24
  import { GeneralError, InvalidArgumentError } from '../../errors/base.js';
26
25
  import { toError } from '../../utils/errors.js';
27
26
  import { readText, writeText } from '../../utils/fs.js';
28
27
  import { info, outro, success, warn } from '../../utils/logger.js';
29
28
  import { resolveWaitLockSeconds } from '../../utils/options.js';
29
+ import { proceedAfterDecision } from '../destructive-decision.js';
30
30
  import { runPatchLint } from '../export-shared.js';
31
+ import { projectEntryBody } from './entry-projection.js';
31
32
  import { buildSplitDiff, findOwnerRewriteHolders, mergeStagedForwardImports, rewriteSplitOwners, } from './split-plan.js';
32
- function projectEntryBody(diff) {
33
- const newFiles = new Map();
34
- for (const path of detectNewFilesInDiff(diff)) {
35
- newFiles.set(path, extractNewFileContentFromDiff(diff, path));
36
- }
37
- return { diff, newFiles, modifiedFileAdditions: buildModifiedFileAdditionsFromDiff(diff) };
38
- }
39
33
  /**
40
34
  * Projects the post-move queue through cross-patch lint, auto-declaring the
41
35
  * forward edges the move introduces into the target (an importer that used
@@ -124,9 +118,9 @@ async function commitMoveInto(patchesDir, plan, waitLockSeconds) {
124
118
  let sourceWritten = false;
125
119
  let targetWritten = false;
126
120
  try {
127
- await writeText(sourcePath, normalizePatchArtifact(plan.sourceDiff));
121
+ await writeText(sourcePath, plan.sourceDiff);
128
122
  sourceWritten = true;
129
- await writeText(targetPath, normalizePatchArtifact(plan.targetDiff));
123
+ await writeText(targetPath, plan.targetDiff);
130
124
  targetWritten = true;
131
125
  const updatedPatches = manifest.patches.map((patch) => {
132
126
  let updated = rewriteSplitOwners(patch, plan.source.filename, movedSet, plan.target.filename);
@@ -143,21 +137,16 @@ async function commitMoveInto(patchesDir, plan, waitLockSeconds) {
143
137
  });
144
138
  const updated = validatePatchesManifest({ ...manifest, patches: updatedPatches });
145
139
  await savePatchesManifest(patchesDir, updated);
146
- try {
147
- await appendHistory(patchesDir, {
148
- operation: 'patch-move-files',
149
- args: {
150
- source: plan.source.filename,
151
- target: plan.target.filename,
152
- files: plan.movedFiles,
153
- ownerRewrites: plan.ownerRewrites,
154
- },
155
- result: 'ok',
156
- });
157
- }
158
- catch (historyError) {
159
- warn(`History log append failed after patch move-files committed: ${toError(historyError).message}`);
160
- }
140
+ await appendHistoryBestEffort(patchesDir, {
141
+ operation: 'patch-move-files',
142
+ args: {
143
+ source: plan.source.filename,
144
+ target: plan.target.filename,
145
+ files: plan.movedFiles,
146
+ ownerRewrites: plan.ownerRewrites,
147
+ },
148
+ result: 'ok',
149
+ }, `patch move-files committed`);
161
150
  }
162
151
  catch (error) {
163
152
  if (targetWritten) {
@@ -202,8 +191,26 @@ export async function runMoveFilesInto(args) {
202
191
  // parity) resolves cross-patch imports and sibling head.js harness roots
203
192
  // instead of linting each projected body blind.
204
193
  const patchQueueCtx = await buildPatchQueueContext(patchesDir, args.config);
205
- await runPatchLint(enginePath, sourceAfter, sourceDiff, args.config, options.skipLint, patchQueueCtx, source.lintIgnore ? new Set(source.lintIgnore) : undefined, source.tier);
206
- await runPatchLint(enginePath, targetAfter, targetDiff, args.config, options.skipLint, patchQueueCtx, target.lintIgnore ? new Set(target.lintIgnore) : undefined, target.tier);
194
+ await runPatchLint({
195
+ engineDir: enginePath,
196
+ filesAffected: sourceAfter,
197
+ diffContent: sourceDiff,
198
+ config: args.config,
199
+ skipLint: options.skipLint,
200
+ patchQueueCtx,
201
+ ignoreChecks: source.lintIgnore ? new Set(source.lintIgnore) : undefined,
202
+ patchTier: source.tier,
203
+ });
204
+ await runPatchLint({
205
+ engineDir: enginePath,
206
+ filesAffected: targetAfter,
207
+ diffContent: targetDiff,
208
+ config: args.config,
209
+ skipLint: options.skipLint,
210
+ patchQueueCtx,
211
+ ignoreChecks: target.lintIgnore ? new Set(target.lintIgnore) : undefined,
212
+ patchTier: target.tier,
213
+ });
207
214
  const manifest = await loadPatchesManifest(patchesDir);
208
215
  const plan = {
209
216
  source,
@@ -234,14 +241,8 @@ export async function runMoveFilesInto(args) {
234
241
  unsafeOverride: options.forceUnsafe === true,
235
242
  conflicts,
236
243
  });
237
- if (decision === 'dry-run') {
238
- outro('Dry run complete — no changes made');
244
+ if (!proceedAfterDecision(decision, 'Move cancelled'))
239
245
  return;
240
- }
241
- if (decision === 'declined') {
242
- outro('Move cancelled');
243
- return;
244
- }
245
246
  await commitMoveInto(patchesDir, plan, resolveWaitLockSeconds(options.waitLock));
246
247
  success(`Moved ${files.length} file(s) from ${source.filename} to ${target.filename}`);
247
248
  if (plan.ownerRewrites.length > 0) {
@@ -13,13 +13,10 @@ import { loadPatchesManifest, resolvePatchIdentifier } from '../../core/patch-ma
13
13
  import { GeneralError, InvalidArgumentError } from '../../errors/base.js';
14
14
  import { pathExists } from '../../utils/fs.js';
15
15
  import { info, intro, note, warn } from '../../utils/logger.js';
16
- import { addWaitLockOption, commanderArgParser, pickDefined } from '../../utils/options.js';
16
+ import { addWaitLockOption, commanderArgParser, pickDefined, stringListOption, } from '../../utils/options.js';
17
+ import { parsePositiveIntegerFlag } from '../../utils/validation.js';
17
18
  import { patchMoveFilesCreateCommand } from './move-files-create.js';
18
19
  import { runMoveFilesInto } from './move-files-into.js';
19
- function collectOption(value, previous) {
20
- previous.push(value);
21
- return previous;
22
- }
23
20
  function normalizeFileList(files) {
24
21
  const cleaned = (files ?? []).map((file) => file.trim()).filter((file) => file.length > 0);
25
22
  return [...new Set(cleaned)].sort((left, right) => left.localeCompare(right));
@@ -165,15 +162,9 @@ export function registerPatchMoveFiles(parent, context) {
165
162
  .command('move-files <from> <to>')
166
163
  .description('Preview the re-export commands needed to move file ownership between patches, ' +
167
164
  'or create the target patch and move the files in one transaction with --create --order <n>.')
168
- .option('--file <path>', 'File path relative to engine/ to move (repeatable)', collectOption, [])
165
+ .option('--file <path>', 'File path relative to engine/ to move (repeatable)', ...stringListOption())
169
166
  .option('--create', 'Create <to> as a new patch and move the files into it (requires --order)')
170
- .option('--order <n>', 'Exact sparse order for the created patch (only valid with --create)', commanderArgParser((raw) => {
171
- const n = Number.parseInt(raw, 10);
172
- if (!Number.isInteger(n) || n <= 0) {
173
- throw new InvalidArgumentError(`--order must be a positive integer, got "${raw}".`, '--order');
174
- }
175
- return n;
176
- }))
167
+ .option('--order <n>', 'Exact sparse order for the created patch (only valid with --create)', commanderArgParser((raw) => parsePositiveIntegerFlag('--order', raw)))
177
168
  .option('--category <category>', "Category for the created patch (default: the source patch's)")
178
169
  .option('-d, --description <desc>', 'Description for the created patch')
179
170
  .option('--dry-run', 'Show what would happen without writing')
@@ -19,7 +19,8 @@ import { rename as fsRename } from 'node:fs/promises';
19
19
  import { join } from 'node:path';
20
20
  import { Command } from 'commander';
21
21
  import { loadConfig } from '../../core/config.js';
22
- import { appendHistory, confirmDestructive } from '../../core/destructive.js';
22
+ import { confirmDestructive } from '../../core/destructive.js';
23
+ import { appendHistoryBestEffort } from '../../core/history-log.js';
23
24
  import { patchNameSlug } from '../../core/patch-export.js';
24
25
  import { buildPatchQueueContext, formatPatchLintIssue, lintPatchQueue, } from '../../core/patch-lint.js';
25
26
  import { withPatchDirectoryLock } from '../../core/patch-lock.js';
@@ -30,6 +31,8 @@ import { toError } from '../../utils/errors.js';
30
31
  import { pathExists } from '../../utils/fs.js';
31
32
  import { info, intro, outro, warn } from '../../utils/logger.js';
32
33
  import { addWaitLockOption, commanderArgParser, pickDefined, resolveWaitLockSeconds, } from '../../utils/options.js';
34
+ import { parsePositiveIntegerFlag } from '../../utils/validation.js';
35
+ import { proceedAfterDecision } from '../destructive-decision.js';
33
36
  import { requirePatchQueue, requirePatchTarget } from './patch-context.js';
34
37
  import { projectReorder } from './reorder.js';
35
38
  /**
@@ -76,7 +79,7 @@ async function commitRenameUnderLock(input) {
76
79
  if (orderChanging && input.newOrder !== undefined) {
77
80
  const orderHolder = fresh.patches.find((p) => p.order === input.newOrder && p.filename !== target.filename);
78
81
  if (orderHolder) {
79
- throw new InvalidArgumentError(`Order ${String(input.newOrder)} was claimed by ${orderHolder.filename} concurrently. Pick an unused order, or use "fireforge patch reorder" to renumber siblings.`, '--order');
82
+ throw new InvalidArgumentError(`Order ${input.newOrder} was claimed by ${orderHolder.filename} concurrently. Pick an unused order, or use "fireforge patch reorder" to renumber siblings.`, '--order');
80
83
  }
81
84
  }
82
85
  let oldPath;
@@ -136,28 +139,23 @@ async function commitRenameUnderLock(input) {
136
139
  else {
137
140
  await savePatchesManifest(patchesDir, fresh);
138
141
  }
139
- try {
140
- await appendHistory(patchesDir, {
141
- operation: 'patch-rename',
142
- args: {
143
- oldFilename: target.filename,
144
- newFilename,
145
- oldName: target.name,
146
- newName,
147
- ...(descriptionChanging ? { oldDescription: target.description, newDescription } : {}),
148
- ...(orderChanging ? { oldOrder: target.order, newOrder: input.newOrder } : {}),
149
- ...(categoryChanging
150
- ? { oldCategory: target.category, newCategory: input.newCategory }
151
- : {}),
152
- },
153
- ...(input.yes === true ? { yes: true } : {}),
154
- ...(input.forceUnsafe === true ? { unsafeOverride: true } : {}),
155
- result: 'ok',
156
- });
157
- }
158
- catch (historyError) {
159
- warn(`History log append failed after patch rename committed (${newFilename}): ${toError(historyError).message}`);
160
- }
142
+ await appendHistoryBestEffort(patchesDir, {
143
+ operation: 'patch-rename',
144
+ args: {
145
+ oldFilename: target.filename,
146
+ newFilename,
147
+ oldName: target.name,
148
+ newName,
149
+ ...(descriptionChanging ? { oldDescription: target.description, newDescription } : {}),
150
+ ...(orderChanging ? { oldOrder: target.order, newOrder: input.newOrder } : {}),
151
+ ...(categoryChanging
152
+ ? { oldCategory: target.category, newCategory: input.newCategory }
153
+ : {}),
154
+ },
155
+ ...(input.yes === true ? { yes: true } : {}),
156
+ ...(input.forceUnsafe === true ? { unsafeOverride: true } : {}),
157
+ result: 'ok',
158
+ }, `patch rename committed (${newFilename})`);
161
159
  }, { waitLockSeconds: input.waitLockSeconds, command: 'patch rename' });
162
160
  }
163
161
  /** Validates the flags and resolves the new filename/identity fields. */
@@ -215,14 +213,14 @@ async function assertRenamePreconditions(patchesDir, manifest, target, plan, opt
215
213
  if (plan.orderChanging && options.order !== undefined) {
216
214
  const holder = manifest.patches.find((p) => p.order === options.order && p.filename !== target.filename);
217
215
  if (holder) {
218
- throw new InvalidArgumentError(`Order ${String(options.order)} is already used by ${holder.filename}. Pick an unused order, or use "fireforge patch reorder ${target.filename} --to ${String(options.order)}" to renumber siblings.`, '--order');
216
+ throw new InvalidArgumentError(`Order ${options.order} is already used by ${holder.filename}. Pick an unused order, or use "fireforge patch reorder ${target.filename} --to ${options.order}" to renumber siblings.`, '--order');
219
217
  }
220
218
  if (options.forceUnsafe !== true) {
221
219
  const baseCtx = await buildPatchQueueContext(patchesDir, config);
222
220
  const projected = projectReorder(baseCtx, new Map([[target.filename, { newFilename: plan.newFilename, newOrder: options.order }]]));
223
221
  const projectedErrors = lintPatchQueue(projected).filter((i) => i.severity === 'error');
224
222
  if (projectedErrors.length > 0) {
225
- throw new InvalidArgumentError(`Refusing to run patch rename: the order change would introduce ${String(projectedErrors.length)} cross-patch lint error(s):\n ${projectedErrors
223
+ throw new InvalidArgumentError(`Refusing to run patch rename: the order change would introduce ${projectedErrors.length} cross-patch lint error(s):\n ${projectedErrors
226
224
  .map(formatPatchLintIssue)
227
225
  .join('\n ')}\nPass --force-unsafe to override.`, '--force-unsafe');
228
226
  }
@@ -248,7 +246,7 @@ function buildRenameSummary(target, plan, options) {
248
246
  summary.push(`description: "${target.description || '(none)'}" → "${options.description ?? '(none)'}"`);
249
247
  }
250
248
  if (plan.orderChanging && options.order !== undefined) {
251
- summary.push(`order: ${String(target.order)} → ${String(options.order)}`);
249
+ summary.push(`order: ${target.order} → ${options.order}`);
252
250
  }
253
251
  if (plan.categoryChanging) {
254
252
  summary.push(`category: ${target.category} → ${plan.newCategory}`);
@@ -309,14 +307,8 @@ export async function patchRenameCommand(projectRoot, identifier, options = {})
309
307
  dryRun: isDryRun,
310
308
  conflicts: null,
311
309
  });
312
- if (decision === 'dry-run') {
313
- outro('Dry run complete — no changes made');
314
- return;
315
- }
316
- if (decision === 'declined') {
317
- outro('Rename cancelled');
310
+ if (!proceedAfterDecision(decision, 'Rename cancelled'))
318
311
  return;
319
- }
320
312
  await commitRenameUnderLock({
321
313
  patchesDir: paths.patches,
322
314
  target,
@@ -356,13 +348,7 @@ export function registerPatchRename(parent, context) {
356
348
  .description('Rename a patch: filename, manifest name, category, order, and/or description update in one transaction without rewriting the .patch body.')
357
349
  .option('--to <new-name>', 'New human-readable name, category-prefixed slug, or full filename stem (normalised into the filename slug)')
358
350
  .option('--category <category>', 'New category for the patch (validated against configured categories); rewrites the filename prefix and manifest row in one transaction')
359
- .option('--order <n>', 'Move the patch to this exact unused order; refuses on collision — use "patch reorder" to renumber siblings', commanderArgParser((raw) => {
360
- const n = Number.parseInt(raw, 10);
361
- if (!Number.isInteger(n) || n <= 0) {
362
- throw new InvalidArgumentError(`--order must be a positive integer, got "${raw}".`, '--order');
363
- }
364
- return n;
365
- }))
351
+ .option('--order <n>', 'Move the patch to this exact unused order; refuses on collision — use "patch reorder" to renumber siblings', commanderArgParser((raw) => parsePositiveIntegerFlag('--order', raw)))
366
352
  .option('-d, --description <text>', 'Replacement description (omit to leave description unchanged)')
367
353
  .option('--dry-run', 'Show what would change without writing')
368
354
  .option('-y, --yes', 'Skip confirmation prompt (required for non-TTY)')
@@ -10,16 +10,18 @@
10
10
  */
11
11
  import { Command, Option } from 'commander';
12
12
  import { loadConfig } from '../../core/config.js';
13
- import { appendHistory, confirmDestructive, } from '../../core/destructive.js';
13
+ import { confirmDestructive, } from '../../core/destructive.js';
14
+ import { appendHistoryBestEffort } from '../../core/history-log.js';
14
15
  import { buildPatchQueueContext, formatPatchLintIssue, lintPatchQueue, } from '../../core/patch-lint.js';
15
16
  import { withPatchDirectoryLock } from '../../core/patch-lock.js';
16
17
  import { loadPatchesManifest, renumberPatchesInManifest, resolvePatchIdentifier, rewriteStagedDependencyOwners, } from '../../core/patch-manifest.js';
17
18
  import { applyRenameMapToManifest, enforcePatchPolicy } from '../../core/patch-policy.js';
18
19
  import { GeneralError, InvalidArgumentError } from '../../errors/base.js';
19
- import { toError } from '../../utils/errors.js';
20
- import { info, intro, outro, warn } from '../../utils/logger.js';
20
+ import { info, intro, outro } from '../../utils/logger.js';
21
21
  import { addWaitLockOption, commanderArgParser, pickDefined, resolveWaitLockSeconds, } from '../../utils/options.js';
22
22
  import { parsePositiveIntegerFlag } from '../../utils/validation.js';
23
+ import { proceedAfterDecision } from '../destructive-decision.js';
24
+ import { getSortedRenameEntries, renameMapsEqual } from '../patch-rename-map.js';
23
25
  import { requirePatchQueue, requirePatchTarget } from './patch-context.js';
24
26
  /** Zero-pads an ordinal number to the given width. */
25
27
  function padOrder(value, width) {
@@ -98,25 +100,6 @@ function computeRenameMap(manifestPatches, target, destinationOrder) {
98
100
  });
99
101
  return renames;
100
102
  }
101
- function getSortedRenameEntries(renameMap) {
102
- return Array.from(renameMap.entries()).sort((a, b) => a[1].newOrder - b[1].newOrder);
103
- }
104
- function renameMapsEqual(left, right) {
105
- const leftEntries = getSortedRenameEntries(left);
106
- const rightEntries = getSortedRenameEntries(right);
107
- if (leftEntries.length !== rightEntries.length) {
108
- return false;
109
- }
110
- return leftEntries.every(([leftFilename, leftEntry], index) => {
111
- const rightTuple = rightEntries[index];
112
- if (!rightTuple)
113
- return false;
114
- const [rightFilename, rightEntry] = rightTuple;
115
- return (leftFilename === rightFilename &&
116
- leftEntry.newFilename === rightEntry.newFilename &&
117
- leftEntry.newOrder === rightEntry.newOrder);
118
- });
119
- }
120
103
  /**
121
104
  * Applies a rename map to a {@link PatchQueueContext} so cross-patch lint
122
105
  * can run against the projected state without touching disk. Exported for
@@ -159,7 +142,7 @@ function resolveDestination(target, manifestPatches, options) {
159
142
  // integers, but the function is reachable from tests that may pass
160
143
  // `{ to: NaN }` directly.
161
144
  if (!Number.isInteger(options.to) || options.to <= 0) {
162
- throw new InvalidArgumentError(`--to must be a positive integer, got ${String(options.to)}.`, '--to');
145
+ throw new InvalidArgumentError(`--to must be a positive integer, got ${options.to}.`, '--to');
163
146
  }
164
147
  return { destinationOrder: options.to, anchorFilename: undefined };
165
148
  }
@@ -195,7 +178,14 @@ function resolveDestination(target, manifestPatches, options) {
195
178
  }
196
179
  return { destinationOrder: anchor.order + 1, anchorFilename: anchor.filename };
197
180
  }
198
- async function commitReorderPlan(patchesDir, target, renameMap, anchorFilename, options, config, buildHistoryEntry) {
181
+ /**
182
+ * Re-validates the reorder against the freshly-loaded manifest and commits
183
+ * it, all under the patch directory lock.
184
+ *
185
+ * @param commitOptions - See {@link CommitReorderPlanOptions}
186
+ */
187
+ async function commitReorderPlan(commitOptions) {
188
+ const { patchesDir, target, renameMap, anchorFilename, options, config, buildHistoryEntry } = commitOptions;
199
189
  await withPatchDirectoryLock(patchesDir, async () => {
200
190
  const currentManifest = await loadPatchesManifest(patchesDir);
201
191
  if (!currentManifest || currentManifest.patches.length === 0) {
@@ -248,12 +238,7 @@ async function commitReorderPlan(patchesDir, target, renameMap, anchorFilename,
248
238
  // permissions) is warned but not re-thrown: the mutation has already
249
239
  // succeeded and is not reversible, so surfacing it as a command
250
240
  // failure would mislead.
251
- try {
252
- await appendHistory(patchesDir, buildHistoryEntry(currentRenameMap));
253
- }
254
- catch (historyError) {
255
- warn(`History log append failed after patch reorder committed: ${toError(historyError).message}`);
256
- }
241
+ await appendHistoryBestEffort(patchesDir, buildHistoryEntry(currentRenameMap), `patch reorder committed`);
257
242
  }, { waitLockSeconds: resolveWaitLockSeconds(options.waitLock), command: 'patch reorder' });
258
243
  }
259
244
  /**
@@ -329,14 +314,8 @@ export async function patchReorderCommand(projectRoot, identifier, options = {})
329
314
  unsafeOverride: options.forceUnsafe === true,
330
315
  conflicts,
331
316
  });
332
- if (decision === 'dry-run') {
333
- outro('Dry run complete — no changes made');
334
- return;
335
- }
336
- if (decision === 'declined') {
337
- outro('Reorder cancelled');
317
+ if (!proceedAfterDecision(decision, 'Reorder cancelled'))
338
318
  return;
339
- }
340
319
  // The history entry is built inside commitReorderPlan (still under the
341
320
  // lock) from the *final* rename map, not the pre-confirmation one, so
342
321
  // the destinationOrder and renames mirror what actually landed on disk.
@@ -359,7 +338,15 @@ export async function patchReorderCommand(projectRoot, identifier, options = {})
359
338
  result: 'ok',
360
339
  };
361
340
  };
362
- await commitReorderPlan(paths.patches, target, renameMap, anchorFilename, options, config, buildHistoryEntry);
341
+ await commitReorderPlan({
342
+ patchesDir: paths.patches,
343
+ target,
344
+ renameMap,
345
+ anchorFilename,
346
+ options,
347
+ config,
348
+ buildHistoryEntry,
349
+ });
363
350
  info(`Reordered ${renameMap.size} patch(es).`);
364
351
  outro('Reorder complete');
365
352
  }
@@ -6,10 +6,9 @@
6
6
  * budget; consumed only by the split command.
7
7
  */
8
8
  import { type ConflictReport } from '../../core/destructive.js';
9
- import { buildPatchQueueContext } from '../../core/patch-lint.js';
10
- import { buildProjectedManifest } from '../../core/patch-policy.js';
9
+ import type { PatchQueueContext } from '../../core/patch-lint-cross.js';
11
10
  import type { PatchStagedForwardImport } from '../../types/commands/index.js';
12
- import type { PatchCategory, PatchMetadata } from '../../types/commands/index.js';
11
+ import type { PatchCategory, PatchesManifest, PatchMetadata } from '../../types/commands/index.js';
13
12
  import type { FireForgeConfig } from '../../types/config.js';
14
13
  import { type PlacementPlan } from '../export-flow.js';
15
14
  /** Everything the commit step needs, computed and confirmed up front. */
@@ -68,7 +67,7 @@ export declare function mergeStagedForwardImports(patch: PatchMetadata, decls: r
68
67
  * patch are auto-declared (and the declarations returned) so the projection
69
68
  * matches the real per-patch gate the split leaves behind.
70
69
  */
71
- export declare function runProjectedSplitLint(plan: SplitPlan, baseCtx: Awaited<ReturnType<typeof buildPatchQueueContext>>): {
70
+ export declare function runProjectedSplitLint(plan: SplitPlan, baseCtx: PatchQueueContext): {
72
71
  conflicts: ConflictReport | null;
73
72
  stagedDependencyAdditions: Map<string, PatchStagedForwardImport[]>;
74
73
  };
@@ -76,7 +75,7 @@ export declare function runProjectedSplitLint(plan: SplitPlan, baseCtx: Awaited<
76
75
  export declare function projectSplitManifest(manifest: {
77
76
  version: 1;
78
77
  patches: PatchMetadata[];
79
- }, plan: SplitPlan, newMetadata: PatchMetadata): ReturnType<typeof buildProjectedManifest>;
78
+ }, plan: SplitPlan, newMetadata: PatchMetadata): PatchesManifest;
80
79
  /**
81
80
  * Builds the manifest row for the patch created by a split, carrying the
82
81
  * source's provenance (product, version, category) onto the new patch.
@@ -10,16 +10,16 @@ import { join } from 'node:path';
10
10
  import {} from '../../core/destructive.js';
11
11
  import { getDiffForFilesAgainstHead } from '../../core/git-diff.js';
12
12
  import { extractAffectedFiles } from '../../core/patch-apply.js';
13
- import { buildModifiedFileAdditionsFromDiff, buildPatchQueueContext, collectForwardImportEdges, detectNewFilesInDiff, formatPatchLintIssue, lintPatchQueue, } from '../../core/patch-lint.js';
13
+ import { collectForwardImportEdges, formatPatchLintIssue, lintPatchQueue, } from '../../core/patch-lint.js';
14
14
  import { computeProjectedLintRegressions } from '../../core/patch-lint-projection.js';
15
15
  import { rewriteStagedDependencyOwners } from '../../core/patch-manifest.js';
16
16
  import { applyRenameMapToManifest, buildProjectedManifest } from '../../core/patch-policy.js';
17
17
  import { buildPatchSourceMetadata } from '../../core/patch-source-metadata.js';
18
- import { extractNewFileContentFromDiff } from '../../core/patch-transform.js';
19
18
  import { GeneralError, InvalidArgumentError } from '../../errors/base.js';
20
19
  import { pathExists } from '../../utils/fs.js';
21
20
  import { warn } from '../../utils/logger.js';
22
21
  import {} from '../export-flow.js';
22
+ import { projectEntryBody } from './entry-projection.js';
23
23
  /**
24
24
  * Refuses the split unless the source patch currently owns every requested
25
25
  * file. Splitting out a file the source does not own would produce a new patch
@@ -81,13 +81,6 @@ export function rewriteSplitOwners(patch, sourceFilename, movedSet, newFilename)
81
81
  },
82
82
  };
83
83
  }
84
- function buildEntryProjection(diff) {
85
- const newFiles = new Map();
86
- for (const path of detectNewFilesInDiff(diff)) {
87
- newFiles.set(path, extractNewFileContentFromDiff(diff, path));
88
- }
89
- return { diff, newFiles, modifiedFileAdditions: buildModifiedFileAdditionsFromDiff(diff) };
90
- }
91
84
  /** Builds the projected post-split queue entries (renumber + shrunken source + new patch). */
92
85
  function buildProjectedSplitEntries(baseCtx, plan) {
93
86
  const movedSet = new Set(plan.movedFiles);
@@ -104,13 +97,13 @@ function buildProjectedSplitEntries(baseCtx, plan) {
104
97
  : { ...entry, metadata };
105
98
  if (entry.filename !== plan.source.filename)
106
99
  return base;
107
- return { ...base, ...buildEntryProjection(plan.remainingDiff) };
100
+ return { ...base, ...projectEntryBody(plan.remainingDiff) };
108
101
  });
109
102
  entries.push({
110
103
  filename: plan.placement.newFilename,
111
104
  order: plan.placement.insertionOrder,
112
105
  metadata: null,
113
- ...buildEntryProjection(plan.movedDiff),
106
+ ...projectEntryBody(plan.movedDiff),
114
107
  });
115
108
  entries.sort((a, b) => a.order - b.order || a.filename.localeCompare(b.filename));
116
109
  return entries;