@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
@@ -1,4 +1,4 @@
1
- import type { getFurnacePaths } from '../../core/furnace-config.js';
1
+ import type { FurnacePaths } from '../../core/furnace-config.js';
2
2
  import type { DryRunAction, FurnaceConfig, ValidationIssue } from '../../types/furnace.js';
3
3
  import { type SpinnerHandle } from '../../utils/logger.js';
4
4
  /**
@@ -16,15 +16,27 @@ export type ValidationResult = {
16
16
  componentCount: number;
17
17
  skippedValidationCount: number;
18
18
  };
19
+ export interface RunDeployValidationOptions {
20
+ /** Active spinner handle for progress display. */
21
+ validateSpinner: SpinnerHandle;
22
+ /** Optional component name (validates all if omitted). */
23
+ name: string | undefined;
24
+ /** Loaded Furnace configuration. */
25
+ config: FurnaceConfig;
26
+ /** Resolved Furnace workspace paths. */
27
+ furnacePaths: FurnacePaths;
28
+ /** Names of components whose apply step failed. */
29
+ failedComponents: Set<string>;
30
+ /** Whether deploy is running in dry-run mode. */
31
+ isDryRun: boolean;
32
+ /** Root directory of the project. */
33
+ projectRoot: string;
34
+ /** Projected actions of a dry run, used to filter projected issues. */
35
+ dryRunActions?: DryRunAction[] | undefined;
36
+ }
19
37
  /**
20
38
  * Runs the validation phase of a furnace deploy, checking all or a single component.
21
- * @param validateSpinner - Active spinner handle for progress display
22
- * @param name - Optional component name (validates all if omitted)
23
- * @param config - Loaded Furnace configuration
24
- * @param furnacePaths - Resolved Furnace workspace paths
25
- * @param failedComponents - Names of components whose apply step failed
26
- * @param isDryRun - Whether deploy is running in dry-run mode
27
- * @param projectRoot - Root directory of the project
39
+ * @param options - See {@link RunDeployValidationOptions}
28
40
  * @returns Validation counts, or `done: true` if the caller should early-return
29
41
  */
30
- export declare function runDeployValidation(validateSpinner: SpinnerHandle, name: string | undefined, config: FurnaceConfig, furnacePaths: ReturnType<typeof getFurnacePaths>, failedComponents: Set<string>, isDryRun: boolean, projectRoot: string, dryRunActions?: DryRunAction[]): Promise<ValidationResult>;
42
+ export declare function runDeployValidation(options: RunDeployValidationOptions): Promise<ValidationResult>;
@@ -56,16 +56,11 @@ function resolveNamedValidationTarget(name, config, furnacePaths) {
56
56
  }
57
57
  /**
58
58
  * Runs the validation phase of a furnace deploy, checking all or a single component.
59
- * @param validateSpinner - Active spinner handle for progress display
60
- * @param name - Optional component name (validates all if omitted)
61
- * @param config - Loaded Furnace configuration
62
- * @param furnacePaths - Resolved Furnace workspace paths
63
- * @param failedComponents - Names of components whose apply step failed
64
- * @param isDryRun - Whether deploy is running in dry-run mode
65
- * @param projectRoot - Root directory of the project
59
+ * @param options - See {@link RunDeployValidationOptions}
66
60
  * @returns Validation counts, or `done: true` if the caller should early-return
67
61
  */
68
- export async function runDeployValidation(validateSpinner, name, config, furnacePaths, failedComponents, isDryRun, projectRoot, dryRunActions) {
62
+ export async function runDeployValidation(options) {
63
+ const { validateSpinner, name, config, furnacePaths, failedComponents, isDryRun, projectRoot, dryRunActions, } = options;
69
64
  let totalErrors = 0;
70
65
  let totalWarnings = 0;
71
66
  let componentCount = 0;
@@ -437,16 +437,9 @@ export function registerImport(program, { getProjectRoot, withErrorHandling }) {
437
437
  // prompt, so a scripted import can run unattended while the integrity
438
438
  // refusal stays armed — which is the guard you actually want in CI.
439
439
  .option('-y, --yes', 'Answer the drift prompt non-interactively. Unlike --force, this does NOT waive the patch-integrity gate.')
440
- .option('--until <patch>', 'Apply patches only up to and including this patch (alias: --stop-at)')
441
- .option('--stop-at <patch>', 'Alias for --until')
440
+ .option('--until <patch>', 'Apply patches only up to and including this patch')
442
441
  .option('--dry-run', 'Preview which patches would be applied without modifying the engine')
443
442
  .action(withErrorHandling(async (options) => {
444
- // Accept both spellings; --until wins when both are passed.
445
- const merged = { ...options };
446
- if (merged.until === undefined && merged.stopAt !== undefined) {
447
- merged.until = merged.stopAt;
448
- }
449
- delete merged.stopAt;
450
- await importCommand(getProjectRoot(), pickDefined(merged));
443
+ await importCommand(getProjectRoot(), pickDefined(options));
451
444
  }));
452
445
  }
@@ -1,5 +1,5 @@
1
- import { getProjectPaths } from '../core/config.js';
2
1
  import type { LintCommandOptions } from '../types/commands/index.js';
2
+ import type { ProjectPaths } from '../types/config.js';
3
3
  /**
4
4
  * Lints each patch in the queue as its own isolated diff, honouring
5
5
  * per-patch `lintIgnore` entries. Cross-patch rules still run once over
@@ -7,4 +7,4 @@ import type { LintCommandOptions } from '../types/commands/index.js';
7
7
  * With `options.patches` set, only the named subset is linted (and the
8
8
  * queue-level findings are scoped to files those patches touch).
9
9
  */
10
- export declare function lintPerPatch(projectRoot: string, paths: ReturnType<typeof getProjectPaths>, options?: LintCommandOptions): Promise<void>;
10
+ export declare function lintPerPatch(projectRoot: string, paths: ProjectPaths, options?: LintCommandOptions): Promise<void>;
@@ -1,6 +1,6 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { join } from 'node:path';
3
- import { getProjectPaths, loadConfig } from '../core/config.js';
3
+ import { loadConfig } from '../core/config.js';
4
4
  import { getDiffForFilesAgainstHead } from '../core/git-diff.js';
5
5
  import { withPrivateGitIndex } from '../core/git-readonly-index.js';
6
6
  import { buildPatchQueueContext, countNonBinaryDiffLines, formatPatchLintIssue, lintExportedPatch, lintPatchQueue, resolvePatchSizeTier, } from '../core/patch-lint.js';
@@ -169,7 +169,15 @@ async function applyPerPatchResults(subset, results, issues, checkJs, cache) {
169
169
  issues.push(...(await checkJs.getGlobal()));
170
170
  }
171
171
  if (result.cacheWrite && cache) {
172
- setCachedPerPatchLintIssues(cache, patch.filename, result.cacheWrite.key, result.rawIssues, result.suppressedIssues, result.lineCount, result.cacheWrite.lintIgnore);
172
+ setCachedPerPatchLintIssues({
173
+ cache,
174
+ patchFilename: patch.filename,
175
+ key: result.cacheWrite.key,
176
+ issues: result.rawIssues,
177
+ suppressed: result.suppressedIssues,
178
+ lineCount: result.lineCount,
179
+ lintIgnore: result.cacheWrite.lintIgnore,
180
+ });
173
181
  totals.cacheDirty = true;
174
182
  }
175
183
  for (const issue of result.rawIssues) {
@@ -267,7 +275,7 @@ function reportPerPatchOutcome(issues, linted, skipped, options) {
267
275
  /**
268
276
  * Resolves the `--patches <name…>` subset filter against the manifest,
269
277
  * matching each requested name tolerantly (exact filename, filename ±
270
- * `.patch`, or the manifest `name` field). Throws listing the available
278
+ * `.patch`, the bare order number, or the manifest `name` field). Throws listing the available
271
279
  * patches when a requested name matches none, so a typo fails loud rather
272
280
  * than silently linting nothing.
273
281
  */
@@ -278,7 +286,14 @@ function selectPatchSubset(manifest, requested) {
278
286
  .filter((entry) => entry.length > 0);
279
287
  const patchAliases = (p) => {
280
288
  const aliases = new Set([p.filename, p.filename.replace(/\.patch$/, ''), p.name]);
289
+ // The bare order number, both as the manifest records it and as the
290
+ // filename spells it (`2` and `002`). The refusal message offers stems,
291
+ // and an operator reading `102-ui-canvas-tiles.patch` in a lint finding
292
+ // reaches for `102` first.
293
+ aliases.add(String(p.order));
281
294
  const match = /^(\d+)-([a-z]+)-(.+)\.patch$/.exec(p.filename);
295
+ if (match?.[1])
296
+ aliases.add(match[1]);
282
297
  if (match?.[2] && match[3]) {
283
298
  aliases.add(`${match[2]}-${match[3]}`);
284
299
  aliases.add(match[3]);
@@ -296,7 +311,7 @@ function selectPatchSubset(manifest, requested) {
296
311
  if (found.length === 0) {
297
312
  const available = manifest.patches.map((p) => p.filename).join(', ');
298
313
  throw new GeneralError(`No patch in the queue matches "${name}". In --per-patch mode, positional arguments ` +
299
- `and --patches select patches (filename, stem, manifest name, or slug) — not engine ` +
314
+ `and --patches select patches (filename, stem, order number, manifest name, or slug) — not engine ` +
300
315
  `files; drop --per-patch to lint engine paths. Available patches: ${available}`);
301
316
  }
302
317
  for (const p of found) {
@@ -20,10 +20,10 @@
20
20
  * less than cold" via `probeCheckJsGlobalIssues` instead of building
21
21
  * anything.
22
22
  */
23
- import type { getProjectPaths, loadConfig } from '../core/config.js';
24
23
  import { type GroupedCheckJsResult } from '../core/patch-lint-checkjs.js';
25
24
  import type { PatchQueueContext } from '../core/patch-lint-cross.js';
26
25
  import type { PatchLintIssue } from '../types/commands/index.js';
26
+ import type { FireForgeConfig, ProjectPaths } from '../types/config.js';
27
27
  /**
28
28
  * Queue-wide checkJs program built once per run and sliced per patch, so a
29
29
  * single type regression surfaces once against its owning patch instead of
@@ -61,4 +61,4 @@ export interface PerRunCheckJs {
61
61
  * subset's owned files and the test-file pass runs only the subset's own
62
62
  * scripts (head.js discovery still spans the queue).
63
63
  */
64
- export declare function buildPerRunCheckJs(projectRoot: string, paths: ReturnType<typeof getProjectPaths>, config: Awaited<ReturnType<typeof loadConfig>>, ctx: PatchQueueContext, rootScopePatches?: ReadonlySet<string>): PerRunCheckJs | undefined;
64
+ export declare function buildPerRunCheckJs(projectRoot: string, paths: ProjectPaths, config: FireForgeConfig, ctx: PatchQueueContext, rootScopePatches?: ReadonlySet<string>): PerRunCheckJs | undefined;
@@ -1,4 +1,5 @@
1
1
  import { Command } from 'commander';
2
+ import { type PatchQueueContext } from '../core/patch-lint.js';
2
3
  import type { CommandContext } from '../types/cli.js';
3
4
  import type { LintCommandOptions, PatchLintIssue } from '../types/commands/index.js';
4
5
  /**
@@ -34,7 +35,7 @@ export interface AggregateLintIgnoreResult {
34
35
  * @param ctx - Patch queue context used to attribute file → patch.
35
36
  * @returns Filtered issue list and the count of dropped findings.
36
37
  */
37
- export declare function applyAggregateLintIgnoreSuppression(issues: PatchLintIssue[], ctx: import('../core/patch-lint.js').PatchQueueContext): AggregateLintIgnoreResult;
38
+ export declare function applyAggregateLintIgnoreSuppression(issues: PatchLintIssue[], ctx: PatchQueueContext): AggregateLintIgnoreResult;
38
39
  /**
39
40
  * Runs the lint command to check engine changes against patch quality rules.
40
41
  * @param projectRoot - Root directory of the project
@@ -495,7 +495,7 @@ export function registerLint(program, { getProjectRoot, withErrorHandling }) {
495
495
  .option('--since <git-rev>', 'Tag issues as [introduced] or [cumulative] based on whether the file changed since <git-rev> (e.g. HEAD, a branch, a SHA)')
496
496
  .option('--only-introduced', 'Fail only on issues tagged [introduced] (requires --since). Cumulative errors still print but do not set a non-zero exit.')
497
497
  .option('--per-patch', "Lint each patch in the queue as its own isolated diff. Rescopes patch-size rules so they fire against individual patches rather than the aggregate. Honours each patch's `lintIgnore` entries.")
498
- .option('--patches <names...>', 'With --per-patch, lint only the named patches. Accepts repeated flags, comma lists, full filenames/stems, manifest names, category-prefixed slugs, or bare slugs. Positional arguments after --per-patch are treated the same way.')
498
+ .option('--patches <names...>', 'With --per-patch, lint only the named patches. Accepts repeated flags, comma lists, full filenames/stems, bare order numbers, manifest names, category-prefixed slugs, or bare slugs. Positional arguments after --per-patch are treated the same way.')
499
499
  .option('--max-warnings <n>', 'Fail when lint reports more than <n> warning(s); use 0 for warning-clean release gates.')
500
500
  .option('--no-cache', 'Bypass per-patch lint result cache reads and writes.')
501
501
  .option('--report <path>', 'With --per-patch, write a machine-readable JSON report (per-patch size metrics, tier, thresholds, issues, and lintIgnore-suppressed issues) to <path>.')
@@ -12,7 +12,7 @@ import { registerLint } from './lint.js';
12
12
  import { registerPackage } from './package.js';
13
13
  import { registerPatch } from './patch/index.js';
14
14
  import { registerReExport } from './re-export-register.js';
15
- import { registerRebase } from './rebase.js';
15
+ import { registerRebase } from './rebase/index.js';
16
16
  import { registerRegister } from './register.js';
17
17
  import { registerReset } from './reset.js';
18
18
  import { registerResolve } from './resolve.js';
@@ -8,6 +8,7 @@ import { hasBuildArtifacts, machPackageCapture } from '../core/mach.js';
8
8
  import { assertBuildArtifacts } from '../core/mach-build-artifacts.js';
9
9
  import { explainMachError } from '../core/mach-error-hints.js';
10
10
  import { BuildError } from '../errors/build.js';
11
+ import { elapsedSince } from '../utils/elapsed.js';
11
12
  import { error, info, intro, outro, verbose } from '../utils/logger.js';
12
13
  import { pickDefined } from '../utils/options.js';
13
14
  /**
@@ -54,10 +55,7 @@ export async function packageCommand(projectRoot, options) {
54
55
  catch (error) {
55
56
  throw new BuildError('Package process failed to start', 'mach package', error instanceof Error ? error : undefined);
56
57
  }
57
- const duration = Date.now() - startTime;
58
- const minutes = Math.floor(duration / 60000);
59
- const seconds = Math.floor((duration % 60000) / 1000);
60
- const timeStr = minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
58
+ const timeStr = elapsedSince(startTime);
61
59
  if (result.exitCode !== 0) {
62
60
  error(`Packaging failed after ${timeStr}`);
63
61
  const combinedOutput = `${result.stdout}\n${result.stderr}`;
@@ -11,14 +11,16 @@
11
11
  */
12
12
  import { Command } from 'commander';
13
13
  import { loadConfig } from '../../core/config.js';
14
- import { appendHistory, confirmDestructive } from '../../core/destructive.js';
14
+ import { confirmDestructive } from '../../core/destructive.js';
15
+ import { appendHistoryBestEffort } from '../../core/history-log.js';
15
16
  import { withPatchDirectoryLock } from '../../core/patch-lock.js';
16
17
  import { loadPatchesManifest, renumberPatchesInManifest, } from '../../core/patch-manifest.js';
17
18
  import { applyRenameMapToManifest, enforcePatchPolicy } from '../../core/patch-policy.js';
18
19
  import { GeneralError } from '../../errors/base.js';
19
- import { toError } from '../../utils/errors.js';
20
20
  import { info, intro, outro, warn } from '../../utils/logger.js';
21
21
  import { addWaitLockOption, pickDefined, resolveWaitLockSeconds } from '../../utils/options.js';
22
+ import { proceedAfterDecision } from '../destructive-decision.js';
23
+ import { renameMapsEqual } from '../patch-rename-map.js';
22
24
  import { requirePatchQueue } from './patch-context.js';
23
25
  import { rebuildFilenameForOrder } from './reorder.js';
24
26
  /** True when `order` falls inside a configured reserved range. */
@@ -139,14 +141,8 @@ export async function patchCompactCommand(projectRoot, options = {}) {
139
141
  dryRun: options.dryRun === true,
140
142
  unsafeOverride: options.forceUnsafe === true,
141
143
  });
142
- if (decision === 'dry-run') {
143
- outro('Dry run complete — no changes made');
144
+ if (!proceedAfterDecision(decision, 'Compact cancelled'))
144
145
  return;
145
- }
146
- if (decision === 'declined') {
147
- outro('Compact cancelled');
148
- return;
149
- }
150
146
  await withPatchDirectoryLock(paths.patches, async () => {
151
147
  const currentManifest = await loadPatchesManifest(paths.patches);
152
148
  if (!currentManifest) {
@@ -157,6 +153,14 @@ export async function patchCompactCommand(projectRoot, options = {}) {
157
153
  info('Patch queue was compacted by another process. Nothing to do.');
158
154
  return;
159
155
  }
156
+ // The operator confirmed a specific plan. A queue that changed between
157
+ // the prompt and the lock (an export landing a new patch, a delete)
158
+ // yields a different plan whose renames were never shown — refuse
159
+ // rather than commit them under a history entry that says "ok", the
160
+ // same gate `patch reorder` applies.
161
+ if (!renameMapsEqual(renameMap, currentRenameMap)) {
162
+ throw new GeneralError('Patch queue changed while waiting for confirmation. Re-run compact to recompute the rename plan.');
163
+ }
160
164
  enforcePatchPolicy({
161
165
  config,
162
166
  manifest: applyRenameMapToManifest(currentManifest, currentRenameMap),
@@ -179,12 +183,7 @@ export async function patchCompactCommand(projectRoot, options = {}) {
179
183
  ...(options.forceUnsafe === true ? { unsafeOverride: true } : {}),
180
184
  result: 'ok',
181
185
  };
182
- try {
183
- await appendHistory(paths.patches, historyEntry);
184
- }
185
- catch (historyError) {
186
- warn(`History log append failed after patch compact committed: ${toError(historyError).message}`);
187
- }
186
+ await appendHistoryBestEffort(paths.patches, historyEntry, `patch compact committed`);
188
187
  }, { waitLockSeconds: resolveWaitLockSeconds(options.waitLock), command: 'patch compact' });
189
188
  info(`Compacted ${renameMap.size} patch(es).`);
190
189
  outro('Compact complete');
@@ -9,13 +9,14 @@
9
9
  */
10
10
  import { basename } from 'node:path';
11
11
  import { Command } from 'commander';
12
- import { appendHistory, confirmDestructive } from '../../core/destructive.js';
12
+ import { confirmDestructive } from '../../core/destructive.js';
13
+ import { appendHistoryBestEffort } from '../../core/history-log.js';
13
14
  import { buildPatchQueueContext, extractImportSpecifiersWithLines, findForwardImportIgnoreLines, isForwardImportableFile, } from '../../core/patch-lint.js';
14
15
  import { withPatchDirectoryLock } from '../../core/patch-lock.js';
15
16
  import { removePatchFileAndManifest } from '../../core/patch-manifest.js';
16
- import { toError } from '../../utils/errors.js';
17
17
  import { info, intro, outro, warn } from '../../utils/logger.js';
18
18
  import { addWaitLockOption, pickDefined, resolveWaitLockSeconds } from '../../utils/options.js';
19
+ import { proceedAfterDecision } from '../destructive-decision.js';
19
20
  import { requirePatchQueue, requirePatchTarget } from './patch-context.js';
20
21
  /**
21
22
  * Runs the `patch delete` command: removes a patch file and its manifest
@@ -138,14 +139,8 @@ export async function patchDeleteCommand(projectRoot, identifier, options = {})
138
139
  unsafeOverride: options.forceUnsafe === true,
139
140
  conflicts,
140
141
  });
141
- if (decision === 'dry-run') {
142
- outro('Dry run complete — no changes made');
142
+ if (!proceedAfterDecision(decision, 'Delete cancelled'))
143
143
  return;
144
- }
145
- if (decision === 'declined') {
146
- outro('Delete cancelled');
147
- return;
148
- }
149
144
  // Proceed: remove under the patch directory lock so concurrent exports
150
145
  // cannot race us into the same manifest row. The history append lives
151
146
  // inside the same lock so two concurrent deletes cannot interleave history
@@ -156,22 +151,17 @@ export async function patchDeleteCommand(projectRoot, identifier, options = {})
156
151
  // failure to the caller would mislead.
157
152
  await withPatchDirectoryLock(paths.patches, async () => {
158
153
  await removePatchFileAndManifest(paths.patches, target.filename);
159
- try {
160
- await appendHistory(paths.patches, {
161
- operation: 'patch-delete',
162
- args: {
163
- filename: target.filename,
164
- order: target.order,
165
- filesAffected: target.filesAffected,
166
- },
167
- ...(options.yes === true ? { yes: true } : {}),
168
- ...(options.forceUnsafe === true ? { unsafeOverride: true } : {}),
169
- result: 'ok',
170
- });
171
- }
172
- catch (historyError) {
173
- warn(`History log append failed after patch delete committed (${target.filename}): ${toError(historyError).message}`);
174
- }
154
+ await appendHistoryBestEffort(paths.patches, {
155
+ operation: 'patch-delete',
156
+ args: {
157
+ filename: target.filename,
158
+ order: target.order,
159
+ filesAffected: target.filesAffected,
160
+ },
161
+ ...(options.yes === true ? { yes: true } : {}),
162
+ ...(options.forceUnsafe === true ? { unsafeOverride: true } : {}),
163
+ result: 'ok',
164
+ }, `patch delete committed (${target.filename})`);
175
165
  }, { waitLockSeconds: resolveWaitLockSeconds(options.waitLock), command: 'patch delete' });
176
166
  info(`Deleted ${target.filename}.`);
177
167
  outro('Delete complete');
@@ -0,0 +1,13 @@
1
+ import type { PatchQueueEntry } from '../../core/patch-lint.js';
2
+ /**
3
+ * Projects a rewritten patch body into the three fields cross-patch lint reads
4
+ * off a queue entry.
5
+ *
6
+ * `patch split` and `patch move-files` each had a byte-identical copy of this
7
+ * (`buildEntryProjection` / `projectEntryBody`). Both feed the same linter, so
8
+ * a change to what the projection carries has to reach both or the two
9
+ * commands start refusing different things for the same queue.
10
+ * @param diff - The rewritten patch body.
11
+ * @returns The diff plus its derived new-file and modified-file projections.
12
+ */
13
+ export declare function projectEntryBody(diff: string): Pick<PatchQueueEntry, 'diff' | 'newFiles' | 'createdFiles' | 'modifiedFileAdditions'>;
@@ -0,0 +1,24 @@
1
+ import { buildModifiedFileAdditionsFromDiff } from '../../core/patch-lint.js';
2
+ import { detectNewFilesInDiff } from '../../core/patch-lint-diff.js';
3
+ import { buildNewFileTextProjection } from '../../core/patch-transform.js';
4
+ /**
5
+ * Projects a rewritten patch body into the three fields cross-patch lint reads
6
+ * off a queue entry.
7
+ *
8
+ * `patch split` and `patch move-files` each had a byte-identical copy of this
9
+ * (`buildEntryProjection` / `projectEntryBody`). Both feed the same linter, so
10
+ * a change to what the projection carries has to reach both or the two
11
+ * commands start refusing different things for the same queue.
12
+ * @param diff - The rewritten patch body.
13
+ * @returns The diff plus its derived new-file and modified-file projections.
14
+ */
15
+ export function projectEntryBody(diff) {
16
+ const newFiles = buildNewFileTextProjection(diff);
17
+ return {
18
+ diff,
19
+ newFiles,
20
+ // Body-kind agnostic: a binary creation is still a creation.
21
+ createdFiles: detectNewFilesInDiff(diff),
22
+ modifiedFileAdditions: buildModifiedFileAdditionsFromDiff(diff),
23
+ };
24
+ }
@@ -21,13 +21,6 @@
21
21
  import { Command } from 'commander';
22
22
  import type { CommandContext } from '../../types/cli.js';
23
23
  import type { PatchLintIgnoreOptions } from '../../types/commands/index.js';
24
- type LintIgnoreMode = 'add' | 'remove' | 'clear';
25
- /**
26
- * Renders a one-line summary of the planned change for use in
27
- * `info()` / dry-run / history args. Exported for unit-testing the
28
- * message format directly without mocking the logger transport.
29
- */
30
- export declare function describeChange(before: ReadonlyArray<string>, after: ReadonlyArray<string>, mode: LintIgnoreMode, values: ReadonlyArray<string>): string;
31
24
  /**
32
25
  * Runs the `patch lint-ignore` command: reads the patch's existing
33
26
  * `lintIgnore`, applies the requested mode, and writes the manifest.
@@ -44,4 +37,3 @@ export declare function patchLintIgnoreCommand(projectRoot: string, identifier:
44
37
  * @param context - Shared CLI registration context
45
38
  */
46
39
  export declare function registerPatchLintIgnore(parent: Command, context: CommandContext): void;
47
- export {};
@@ -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(', ')}`);