@hominis/fireforge 0.35.0 → 0.36.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 (355) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +34 -9
  3. package/dist/bin/fireforge.js +21 -6
  4. package/dist/src/cli.js +22 -5
  5. package/dist/src/commands/bootstrap-checks.js +0 -1
  6. package/dist/src/commands/bootstrap.d.ts +2 -1
  7. package/dist/src/commands/bootstrap.js +16 -7
  8. package/dist/src/commands/build.js +4 -3
  9. package/dist/src/commands/config.js +30 -10
  10. package/dist/src/commands/discard.js +18 -3
  11. package/dist/src/commands/doctor/post-rebase-audit.js +0 -1
  12. package/dist/src/commands/doctor-check-core.js +0 -1
  13. package/dist/src/commands/doctor-external-toolchains.d.ts +3 -0
  14. package/dist/src/commands/doctor-external-toolchains.js +54 -0
  15. package/dist/src/commands/doctor-furnace-jar.js +0 -1
  16. package/dist/src/commands/doctor-furnace-manifest-sync.js +0 -1
  17. package/dist/src/commands/doctor-furnace.js +2 -2
  18. package/dist/src/commands/doctor-working-tree.js +0 -1
  19. package/dist/src/commands/doctor.js +4 -1
  20. package/dist/src/commands/download.js +28 -13
  21. package/dist/src/commands/export-all.js +5 -3
  22. package/dist/src/commands/export-flow.js +12 -7
  23. package/dist/src/commands/export-placement-gate.js +0 -1
  24. package/dist/src/commands/export-placement-policy.js +0 -1
  25. package/dist/src/commands/export-shared.d.ts +5 -1
  26. package/dist/src/commands/export-shared.js +18 -6
  27. package/dist/src/commands/export.js +15 -10
  28. package/dist/src/commands/furnace/apply.js +22 -6
  29. package/dist/src/commands/furnace/chrome-doc-remove.js +0 -1
  30. package/dist/src/commands/furnace/chrome-doc-templates.js +0 -1
  31. package/dist/src/commands/furnace/chrome-doc-tests.js +0 -1
  32. package/dist/src/commands/furnace/chrome-doc.js +0 -1
  33. package/dist/src/commands/furnace/create-browser-test.js +0 -1
  34. package/dist/src/commands/furnace/create-dry-run.js +0 -1
  35. package/dist/src/commands/furnace/create-features.js +0 -1
  36. package/dist/src/commands/furnace/create-mochikit.js +0 -1
  37. package/dist/src/commands/furnace/create-readback.js +0 -1
  38. package/dist/src/commands/furnace/create-templates.js +0 -1
  39. package/dist/src/commands/furnace/create-validation.js +0 -1
  40. package/dist/src/commands/furnace/create-xpcshell.js +0 -1
  41. package/dist/src/commands/furnace/create.js +0 -1
  42. package/dist/src/commands/furnace/deploy.js +7 -83
  43. package/dist/src/commands/furnace/diff.js +0 -1
  44. package/dist/src/commands/furnace/index.js +59 -25
  45. package/dist/src/commands/furnace/init.js +0 -1
  46. package/dist/src/commands/furnace/list.js +0 -1
  47. package/dist/src/commands/furnace/override.js +0 -1
  48. package/dist/src/commands/furnace/preview.js +3 -3
  49. package/dist/src/commands/furnace/refresh.js +0 -1
  50. package/dist/src/commands/furnace/remove-state.d.ts +15 -0
  51. package/dist/src/commands/furnace/remove-state.js +47 -1
  52. package/dist/src/commands/furnace/remove.js +3 -9
  53. package/dist/src/commands/furnace/rename-browser-test.js +1 -5
  54. package/dist/src/commands/furnace/rename-helpers.d.ts +6 -0
  55. package/dist/src/commands/furnace/rename-helpers.js +30 -1
  56. package/dist/src/commands/furnace/rename-xpcshell.js +7 -6
  57. package/dist/src/commands/furnace/rename.js +15 -36
  58. package/dist/src/commands/furnace/scan.js +0 -1
  59. package/dist/src/commands/furnace/status.js +0 -1
  60. package/dist/src/commands/furnace/sync.js +76 -4
  61. package/dist/src/commands/furnace/validate.js +0 -1
  62. package/dist/src/commands/furnace/validation-output.js +0 -1
  63. package/dist/src/commands/import.js +35 -9
  64. package/dist/src/commands/lint-per-patch.js +19 -7
  65. package/dist/src/commands/lint.js +13 -3
  66. package/dist/src/commands/manifest.js +1 -2
  67. package/dist/src/commands/package.js +2 -1
  68. package/dist/src/commands/patch/compact.js +1 -1
  69. package/dist/src/commands/patch/delete.js +1 -1
  70. package/dist/src/commands/patch/index.js +1 -1
  71. package/dist/src/commands/patch/lint-ignore.js +21 -6
  72. package/dist/src/commands/patch/move-files.js +1 -1
  73. package/dist/src/commands/patch/patch-context.js +0 -1
  74. package/dist/src/commands/patch/rename.js +4 -4
  75. package/dist/src/commands/patch/reorder.js +3 -4
  76. package/dist/src/commands/patch/split-plan.js +2 -1
  77. package/dist/src/commands/patch/split.js +4 -4
  78. package/dist/src/commands/patch/staged-dependency.js +1 -1
  79. package/dist/src/commands/patch/tier.js +18 -5
  80. package/dist/src/commands/re-export-bulk-scan.js +0 -1
  81. package/dist/src/commands/re-export-files.js +1 -1
  82. package/dist/src/commands/re-export-options.js +0 -1
  83. package/dist/src/commands/re-export-scan.js +0 -1
  84. package/dist/src/commands/re-export.js +6 -4
  85. package/dist/src/commands/rebase/abort.js +2 -9
  86. package/dist/src/commands/rebase/confirm.js +0 -1
  87. package/dist/src/commands/rebase/conflict-summary.js +0 -1
  88. package/dist/src/commands/rebase/continue.js +0 -1
  89. package/dist/src/commands/rebase/index.d.ts +1 -1
  90. package/dist/src/commands/rebase/index.js +10 -15
  91. package/dist/src/commands/rebase/patch-loop.js +38 -7
  92. package/dist/src/commands/rebase/summary.js +2 -3
  93. package/dist/src/commands/rebase.js +0 -1
  94. package/dist/src/commands/register.js +1 -1
  95. package/dist/src/commands/reset.js +16 -9
  96. package/dist/src/commands/resolve.js +2 -2
  97. package/dist/src/commands/run.js +4 -4
  98. package/dist/src/commands/setup-support.js +0 -1
  99. package/dist/src/commands/setup.js +1 -2
  100. package/dist/src/commands/source.js +1 -2
  101. package/dist/src/commands/status-output.js +1 -12
  102. package/dist/src/commands/status.js +147 -133
  103. package/dist/src/commands/test-appdir.js +0 -1
  104. package/dist/src/commands/test-diagnose.js +18 -2
  105. package/dist/src/commands/test-modes.d.ts +15 -0
  106. package/dist/src/commands/test-modes.js +61 -0
  107. package/dist/src/commands/test-register.d.ts +4 -0
  108. package/dist/src/commands/test-register.js +64 -0
  109. package/dist/src/commands/test-run.d.ts +23 -7
  110. package/dist/src/commands/test-run.js +15 -14
  111. package/dist/src/commands/test.d.ts +0 -4
  112. package/dist/src/commands/test.js +85 -86
  113. package/dist/src/commands/token-coverage.js +0 -1
  114. package/dist/src/commands/token.js +1 -2
  115. package/dist/src/commands/typecheck.js +1 -1
  116. package/dist/src/commands/verify.js +3 -9
  117. package/dist/src/commands/watch.js +5 -1
  118. package/dist/src/commands/wire.js +1 -1
  119. package/dist/src/core/ast-utils.js +0 -1
  120. package/dist/src/core/brand-validation.js +0 -1
  121. package/dist/src/core/branding.js +0 -1
  122. package/dist/src/core/browser-wire.js +0 -1
  123. package/dist/src/core/build-audit-platform.js +0 -1
  124. package/dist/src/core/build-audit-registration.js +0 -1
  125. package/dist/src/core/build-audit-resolve.js +0 -1
  126. package/dist/src/core/build-audit-transforms.js +0 -1
  127. package/dist/src/core/build-audit.js +0 -1
  128. package/dist/src/core/build-baseline-types.js +0 -1
  129. package/dist/src/core/build-baseline.js +0 -1
  130. package/dist/src/core/build-prepare.js +2 -2
  131. package/dist/src/core/config-mutate.js +0 -1
  132. package/dist/src/core/config-paths.d.ts +2 -2
  133. package/dist/src/core/config-paths.js +6 -1
  134. package/dist/src/core/config-state.js +0 -1
  135. package/dist/src/core/config-validate-patch-policy.js +0 -1
  136. package/dist/src/core/config-validate-test-toolchains.d.ts +8 -0
  137. package/dist/src/core/config-validate-test-toolchains.js +105 -0
  138. package/dist/src/core/config-validate.js +3 -1
  139. package/dist/src/core/config.js +0 -1
  140. package/dist/src/core/destructive.js +0 -1
  141. package/dist/src/core/diff-hunks.js +13 -7
  142. package/dist/src/core/engine-changes.js +0 -1
  143. package/dist/src/core/engine-session-lock.d.ts +12 -0
  144. package/dist/src/core/engine-session-lock.js +45 -0
  145. package/dist/src/core/file-lock.d.ts +8 -0
  146. package/dist/src/core/file-lock.js +157 -48
  147. package/dist/src/core/firefox-archive.d.ts +4 -0
  148. package/dist/src/core/firefox-archive.js +6 -1
  149. package/dist/src/core/firefox-cache.js +60 -4
  150. package/dist/src/core/firefox-download.js +0 -1
  151. package/dist/src/core/firefox-extract.js +75 -16
  152. package/dist/src/core/firefox-ignorefile.js +0 -1
  153. package/dist/src/core/firefox.d.ts +12 -0
  154. package/dist/src/core/firefox.js +64 -2
  155. package/dist/src/core/furnace-apply-dry-run.js +0 -1
  156. package/dist/src/core/furnace-apply-ftl.d.ts +7 -3
  157. package/dist/src/core/furnace-apply-ftl.js +10 -5
  158. package/dist/src/core/furnace-apply-helpers.js +0 -1
  159. package/dist/src/core/furnace-apply-output.js +2 -2
  160. package/dist/src/core/furnace-apply.d.ts +7 -1
  161. package/dist/src/core/furnace-apply.js +47 -13
  162. package/dist/src/core/furnace-checksum-utils.js +0 -1
  163. package/dist/src/core/furnace-config-array-utils.js +0 -1
  164. package/dist/src/core/furnace-config-custom.js +0 -1
  165. package/dist/src/core/furnace-config-order.js +0 -1
  166. package/dist/src/core/furnace-config-tokens.js +0 -1
  167. package/dist/src/core/furnace-config-validate.js +0 -1
  168. package/dist/src/core/furnace-config.d.ts +11 -0
  169. package/dist/src/core/furnace-config.js +37 -1
  170. package/dist/src/core/furnace-constants.js +0 -1
  171. package/dist/src/core/furnace-css-fragments.js +0 -1
  172. package/dist/src/core/furnace-graph-utils.js +0 -1
  173. package/dist/src/core/furnace-jsconfig.d.ts +2 -3
  174. package/dist/src/core/furnace-jsconfig.js +25 -18
  175. package/dist/src/core/furnace-marker.js +0 -1
  176. package/dist/src/core/furnace-operation.js +0 -1
  177. package/dist/src/core/furnace-refresh.js +0 -1
  178. package/dist/src/core/furnace-registration-ast.d.ts +14 -0
  179. package/dist/src/core/furnace-registration-ast.js +1 -2
  180. package/dist/src/core/furnace-registration-remove.js +0 -1
  181. package/dist/src/core/furnace-registration-validate.js +1 -5
  182. package/dist/src/core/furnace-registration.js +0 -1
  183. package/dist/src/core/furnace-rollback.js +0 -1
  184. package/dist/src/core/furnace-scanner.js +0 -1
  185. package/dist/src/core/furnace-staleness.js +0 -1
  186. package/dist/src/core/furnace-state-persist.d.ts +72 -0
  187. package/dist/src/core/furnace-state-persist.js +118 -0
  188. package/dist/src/core/furnace-step-errors.d.ts +23 -0
  189. package/dist/src/core/furnace-step-errors.js +25 -0
  190. package/dist/src/core/furnace-stories.js +0 -1
  191. package/dist/src/core/furnace-validate-accessibility.js +0 -1
  192. package/dist/src/core/furnace-validate-checks.js +0 -1
  193. package/dist/src/core/furnace-validate-compatibility.js +0 -1
  194. package/dist/src/core/furnace-validate-helpers.js +0 -1
  195. package/dist/src/core/furnace-validate-registration.js +7 -7
  196. package/dist/src/core/furnace-validate-structure.d.ts +8 -0
  197. package/dist/src/core/furnace-validate-structure.js +11 -2
  198. package/dist/src/core/furnace-validate.js +0 -1
  199. package/dist/src/core/furnace-version-drift.d.ts +6 -3
  200. package/dist/src/core/furnace-version-drift.js +6 -4
  201. package/dist/src/core/git-base.js +0 -1
  202. package/dist/src/core/git-diff.js +0 -1
  203. package/dist/src/core/git-file-ops.js +0 -1
  204. package/dist/src/core/git-status.d.ts +13 -0
  205. package/dist/src/core/git-status.js +32 -2
  206. package/dist/src/core/git.d.ts +10 -1
  207. package/dist/src/core/git.js +63 -6
  208. package/dist/src/core/license-headers.js +0 -1
  209. package/dist/src/core/lint-cache.js +0 -1
  210. package/dist/src/core/lint-projection.js +0 -1
  211. package/dist/src/core/mach-build-artifacts.js +0 -1
  212. package/dist/src/core/mach-error-hints.js +0 -1
  213. package/dist/src/core/mach-mozconfig.js +0 -1
  214. package/dist/src/core/mach-python.js +0 -1
  215. package/dist/src/core/mach-resource-shim.js +0 -1
  216. package/dist/src/core/mach.js +0 -1
  217. package/dist/src/core/manifest-helpers.js +0 -1
  218. package/dist/src/core/manifest-register.js +0 -1
  219. package/dist/src/core/manifest-rules.js +0 -1
  220. package/dist/src/core/manifest-tokenizers.js +0 -1
  221. package/dist/src/core/marionette-port.d.ts +8 -0
  222. package/dist/src/core/marionette-port.js +34 -3
  223. package/dist/src/core/marionette-preflight.js +0 -1
  224. package/dist/src/core/markdown-table.js +0 -1
  225. package/dist/src/core/ownership-table.js +2 -9
  226. package/dist/src/core/parser-fallback.js +0 -1
  227. package/dist/src/core/patch-apply-fuzz.d.ts +27 -9
  228. package/dist/src/core/patch-apply-fuzz.js +71 -34
  229. package/dist/src/core/patch-apply.d.ts +30 -0
  230. package/dist/src/core/patch-apply.js +101 -18
  231. package/dist/src/core/patch-artifact-normalize.d.ts +18 -5
  232. package/dist/src/core/patch-artifact-normalize.js +19 -15
  233. package/dist/src/core/patch-export-coverage.js +0 -1
  234. package/dist/src/core/patch-export-metadata.js +0 -1
  235. package/dist/src/core/patch-export-update.js +2 -3
  236. package/dist/src/core/patch-export.d.ts +2 -6
  237. package/dist/src/core/patch-export.js +24 -48
  238. package/dist/src/core/patch-files.js +0 -1
  239. package/dist/src/core/patch-identifier-suggest.js +0 -1
  240. package/dist/src/core/patch-lint-checkjs.js +0 -1
  241. package/dist/src/core/patch-lint-chrome-jsdoc.js +0 -1
  242. package/dist/src/core/patch-lint-cross.js +0 -1
  243. package/dist/src/core/patch-lint-css.js +0 -1
  244. package/dist/src/core/patch-lint-diff-tag.js +0 -1
  245. package/dist/src/core/patch-lint-diff.d.ts +2 -1
  246. package/dist/src/core/patch-lint-diff.js +17 -32
  247. package/dist/src/core/patch-lint-imports.js +0 -1
  248. package/dist/src/core/patch-lint-jsdoc.js +0 -1
  249. package/dist/src/core/patch-lint-observer.js +0 -1
  250. package/dist/src/core/patch-lint-ownership.js +0 -1
  251. package/dist/src/core/patch-lint-reexports.js +0 -1
  252. package/dist/src/core/patch-lint.d.ts +37 -2
  253. package/dist/src/core/patch-lint.js +47 -34
  254. package/dist/src/core/patch-lock.js +0 -1
  255. package/dist/src/core/patch-manifest-consistency.js +9 -1
  256. package/dist/src/core/patch-manifest-io.d.ts +16 -0
  257. package/dist/src/core/patch-manifest-io.js +30 -5
  258. package/dist/src/core/patch-manifest-query.js +18 -10
  259. package/dist/src/core/patch-manifest-resolve.js +0 -1
  260. package/dist/src/core/patch-manifest-validate.js +0 -1
  261. package/dist/src/core/patch-manifest.d.ts +1 -1
  262. package/dist/src/core/patch-manifest.js +1 -2
  263. package/dist/src/core/patch-parse.d.ts +59 -0
  264. package/dist/src/core/patch-parse.js +209 -92
  265. package/dist/src/core/patch-policy.js +0 -1
  266. package/dist/src/core/patch-registration-refs.js +16 -26
  267. package/dist/src/core/patch-source-metadata.js +0 -1
  268. package/dist/src/core/patch-transform.d.ts +4 -0
  269. package/dist/src/core/patch-transform.js +30 -38
  270. package/dist/src/core/rebase-session.js +0 -1
  271. package/dist/src/core/register-browser-content.js +0 -1
  272. package/dist/src/core/register-module.js +0 -1
  273. package/dist/src/core/register-result.js +0 -1
  274. package/dist/src/core/register-scaffold.js +0 -1
  275. package/dist/src/core/register-shared-css.js +0 -1
  276. package/dist/src/core/register-test-manifest.js +0 -1
  277. package/dist/src/core/register-xpcshell-test.js +0 -1
  278. package/dist/src/core/shared-ftl.js +0 -1
  279. package/dist/src/core/signal-critical.js +0 -1
  280. package/dist/src/core/smoke-patterns.js +0 -1
  281. package/dist/src/core/state-file.js +0 -1
  282. package/dist/src/core/status-classify.d.ts +16 -0
  283. package/dist/src/core/status-classify.js +24 -16
  284. package/dist/src/core/test-harness-crash.d.ts +64 -7
  285. package/dist/src/core/test-harness-crash.js +196 -15
  286. package/dist/src/core/test-harness-output.js +0 -1
  287. package/dist/src/core/test-path-scope.d.ts +21 -13
  288. package/dist/src/core/test-path-scope.js +43 -28
  289. package/dist/src/core/test-stale-check.js +0 -1
  290. package/dist/src/core/test-stale-symlink.js +0 -1
  291. package/dist/src/core/test-xpcshell-retry.js +0 -1
  292. package/dist/src/core/token-coverage.js +0 -1
  293. package/dist/src/core/token-dark-mode.js +0 -1
  294. package/dist/src/core/token-docs.js +0 -1
  295. package/dist/src/core/token-manager.js +0 -1
  296. package/dist/src/core/token-scaffold.js +0 -1
  297. package/dist/src/core/token-variant.js +0 -1
  298. package/dist/src/core/toolchain-preflight.d.ts +31 -10
  299. package/dist/src/core/toolchain-preflight.js +97 -36
  300. package/dist/src/core/typecheck-shim.js +13 -1
  301. package/dist/src/core/typecheck.js +0 -1
  302. package/dist/src/core/wire-destroy.js +0 -1
  303. package/dist/src/core/wire-dom-fragment.js +0 -1
  304. package/dist/src/core/wire-init.js +0 -1
  305. package/dist/src/core/wire-subscript.js +0 -1
  306. package/dist/src/core/wire-targets.js +0 -1
  307. package/dist/src/core/wire-utils.js +0 -1
  308. package/dist/src/core/xpcshell-appdir.js +1 -4
  309. package/dist/src/errors/base.d.ts +29 -0
  310. package/dist/src/errors/base.js +35 -1
  311. package/dist/src/errors/build.js +0 -1
  312. package/dist/src/errors/codes.d.ts +6 -0
  313. package/dist/src/errors/codes.js +6 -1
  314. package/dist/src/errors/config.js +0 -1
  315. package/dist/src/errors/download.js +0 -1
  316. package/dist/src/errors/furnace.d.ts +11 -1
  317. package/dist/src/errors/furnace.js +6 -5
  318. package/dist/src/errors/git.js +0 -1
  319. package/dist/src/errors/patch.d.ts +17 -0
  320. package/dist/src/errors/patch.js +29 -1
  321. package/dist/src/errors/rebase.js +0 -1
  322. package/dist/src/errors/run.js +1 -1
  323. package/dist/src/index.js +0 -1
  324. package/dist/src/types/cli.js +0 -1
  325. package/dist/src/types/commands/index.js +0 -1
  326. package/dist/src/types/commands/options.d.ts +13 -5
  327. package/dist/src/types/commands/options.js +0 -1
  328. package/dist/src/types/commands/patches.d.ts +8 -0
  329. package/dist/src/types/commands/patches.js +0 -1
  330. package/dist/src/types/commands/project.d.ts +11 -2
  331. package/dist/src/types/commands/project.js +0 -1
  332. package/dist/src/types/config.d.ts +32 -0
  333. package/dist/src/types/config.js +0 -1
  334. package/dist/src/types/furnace.d.ts +10 -0
  335. package/dist/src/types/furnace.js +0 -1
  336. package/dist/src/types/index.js +0 -1
  337. package/dist/src/types/typecheck.js +0 -1
  338. package/dist/src/utils/elapsed.js +0 -1
  339. package/dist/src/utils/errors.js +0 -1
  340. package/dist/src/utils/fs.js +37 -5
  341. package/dist/src/utils/logger.d.ts +7 -0
  342. package/dist/src/utils/logger.js +85 -3
  343. package/dist/src/utils/options.d.ts +15 -0
  344. package/dist/src/utils/options.js +25 -1
  345. package/dist/src/utils/package-root.js +11 -4
  346. package/dist/src/utils/parse.js +0 -1
  347. package/dist/src/utils/paths.js +0 -1
  348. package/dist/src/utils/platform.js +0 -1
  349. package/dist/src/utils/process.d.ts +33 -4
  350. package/dist/src/utils/process.js +183 -28
  351. package/dist/src/utils/regex.d.ts +13 -3
  352. package/dist/src/utils/regex.js +53 -4
  353. package/dist/src/utils/validation.d.ts +0 -12
  354. package/dist/src/utils/validation.js +13 -26
  355. package/package.json +8 -7
@@ -1,10 +1,11 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { join } from 'node:path';
3
3
  import { confirm } from '@clack/prompts';
4
+ import { Command } from 'commander';
4
5
  import { getProjectPaths, loadConfig, loadState, updateState } from '../core/config.js';
5
6
  import { getHead } from '../core/git.js';
6
7
  import { getDirtyFiles } from '../core/git-status.js';
7
- import { applyPatchesWithContinue, computePatchedContent, countPatches, discoverPatches, extractAffectedFiles, PatchError, } from '../core/patch-apply.js';
8
+ import { applyPatchesWithContinue, countPatches, createPatchedContentComputer, discoverPatches, extractAffectedFiles, matchesUntilFilename, PatchError, } from '../core/patch-apply.js';
8
9
  import { checkVersionCompatibility, loadPatchesManifest, validatePatchesManifestConsistency, validatePatchIntegrity, } from '../core/patch-manifest.js';
9
10
  import { getPatchSourceVersion } from '../core/patch-source-metadata.js';
10
11
  import { GeneralError } from '../errors/base.js';
@@ -25,11 +26,17 @@ function isSafeIoFallback(error) {
25
26
  const code = error?.code;
26
27
  return typeof code === 'string' && SAFE_IO_FALLBACK_CODES.has(code);
27
28
  }
29
+ /** Concurrency bound for per-file classification (each call spawns git). */
30
+ const UNMANAGED_CLASSIFY_CONCURRENCY = 8;
28
31
  async function getUnmanagedDirtyFiles(engineDir, patchesDir, dirtyFiles) {
29
- const classifications = await Promise.all(dirtyFiles.map(async (file) => {
32
+ // One manifest+patch-discovery load for the whole batch (the per-call
33
+ // computePatchedContent re-read everything for every file), and bounded
34
+ // concurrency instead of an unbounded Promise.all over git spawns.
35
+ const computeExpected = await createPatchedContentComputer(patchesDir, engineDir);
36
+ const classifications = await mapWithConcurrency(dirtyFiles, UNMANAGED_CLASSIFY_CONCURRENCY, async (file) => {
30
37
  try {
31
38
  const [expected, exists] = await Promise.all([
32
- computePatchedContent(patchesDir, engineDir, file),
39
+ computeExpected(file),
33
40
  pathExists(join(engineDir, file)),
34
41
  ]);
35
42
  const actual = exists ? await readText(join(engineDir, file)) : null;
@@ -51,9 +58,24 @@ async function getUnmanagedDirtyFiles(engineDir, patchesDir, dirtyFiles) {
51
58
  verbose(`Treating ${file} as unmanaged because patched-content classification failed with IO error: ${toError(error).message}`);
52
59
  return file;
53
60
  }
54
- }));
61
+ });
55
62
  return classifications.filter((file) => file !== null).sort();
56
63
  }
64
+ /** Maps items with at most `limit` in-flight promises. Preserves order. */
65
+ async function mapWithConcurrency(items, limit, fn) {
66
+ const results = new Array(items.length);
67
+ let next = 0;
68
+ const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
69
+ for (;;) {
70
+ const index = next++;
71
+ if (index >= items.length)
72
+ return;
73
+ results[index] = await fn(items[index]);
74
+ }
75
+ });
76
+ await Promise.all(workers);
77
+ return results;
78
+ }
57
79
  function reportForcedOverwriteRisk(unmanagedDirtyFiles) {
58
80
  warn(`--force will overwrite ${unmanagedDirtyFiles.length} unmanaged change${unmanagedDirtyFiles.length === 1 ? '' : 's'} in patch-touched file${unmanagedDirtyFiles.length === 1 ? '' : 's'}:`);
59
81
  for (const file of unmanagedDirtyFiles) {
@@ -150,7 +172,7 @@ async function checkEngineDrift(engineDir, baseCommit, forceImport) {
150
172
  const currentHead = await getHead(engineDir);
151
173
  if (currentHead === baseCommit)
152
174
  return true;
153
- if (!process.stdin.isTTY) {
175
+ if (!(process.stdin.isTTY && process.stdout.isTTY)) {
154
176
  if (!forceImport) {
155
177
  throw new GeneralError('Engine HEAD has drifted from base commit. Re-run with --force to bypass drift check.');
156
178
  }
@@ -189,8 +211,13 @@ function buildUntilFilenameSet(patches, until) {
189
211
  const set = new Set();
190
212
  if (until === undefined)
191
213
  return set;
192
- const normalized = until.endsWith('.patch') ? until : `${until}.patch`;
193
- const target = patches.find((p) => p.filename === until || p.filename === normalized);
214
+ // Resolve the identifier with the SAME matcher the apply loop uses
215
+ // (`matchesUntilFilename` accepts filenames, extension-less names, AND
216
+ // bare ordinals). The previous filename-only match meant
217
+ // `import --until 5` produced an EMPTY scope set — the UI previewed
218
+ // "0 patches", the integrity gates filtered every in-range issue away —
219
+ // and then the apply loop happily applied patches 1..5 anyway.
220
+ const target = patches.find((p) => matchesUntilFilename(p.filename, until));
194
221
  if (!target)
195
222
  return set;
196
223
  for (const patch of patches) {
@@ -266,7 +293,7 @@ async function gateImportIntegrity(paths, untilFilenameSet, until, forceImport)
266
293
  if (forceImport) {
267
294
  warn('Continuing because --force was provided. Integrity issues were not resolved.\n');
268
295
  }
269
- else if (!process.stdin.isTTY) {
296
+ else if (!(process.stdin.isTTY && process.stdout.isTTY)) {
270
297
  throw new GeneralError(`Refusing to import while ${integrityIssues.length} patch integrity issue(s) are unresolved. ` +
271
298
  `Fix the issues reported above (see "fireforge doctor") or re-run with --force to continue anyway.`);
272
299
  }
@@ -421,4 +448,3 @@ export function registerImport(program, { getProjectRoot, withErrorHandling }) {
421
448
  await importCommand(getProjectRoot(), pickDefined(merged));
422
449
  }));
423
450
  }
424
- //# sourceMappingURL=import.js.map
@@ -1,6 +1,6 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { join } from 'node:path';
3
- import { loadConfig } from '../core/config.js';
3
+ import { getProjectPaths, loadConfig } from '../core/config.js';
4
4
  import { getDiffForFilesAgainstHead } from '../core/git-diff.js';
5
5
  import { buildPerPatchLintCacheKey, getCachedPerPatchLintIssues, getPerPatchLintCacheHeadSha, loadPerPatchLintCache, savePerPatchLintCache, setCachedPerPatchLintIssues, } from '../core/lint-cache.js';
6
6
  import { buildPatchQueueContext, lintExportedPatch, lintPatchQueue, resolvePatchSizeTier, } from '../core/patch-lint.js';
@@ -215,13 +215,26 @@ function reportPerPatchOutcome(issues, linted, skipped, options) {
215
215
  * than silently linting nothing.
216
216
  */
217
217
  function selectPatchSubset(manifest, requested) {
218
- const matches = (p, name) => p.filename === name ||
219
- p.filename === `${name}.patch` ||
220
- p.filename.replace(/\.patch$/, '') === name.replace(/\.patch$/, '') ||
221
- p.name === name;
218
+ const normalizedRequests = requested
219
+ .flatMap((entry) => entry.split(','))
220
+ .map((entry) => entry.trim())
221
+ .filter((entry) => entry.length > 0);
222
+ const patchAliases = (p) => {
223
+ const aliases = new Set([p.filename, p.filename.replace(/\.patch$/, ''), p.name]);
224
+ const match = /^(\d+)-([a-z]+)-(.+)\.patch$/.exec(p.filename);
225
+ if (match?.[2] && match[3]) {
226
+ aliases.add(`${match[2]}-${match[3]}`);
227
+ aliases.add(match[3]);
228
+ }
229
+ return aliases;
230
+ };
231
+ const matches = (p, name) => {
232
+ const stem = name.replace(/\.patch$/, '');
233
+ return (patchAliases(p).has(name) || patchAliases(p).has(stem) || patchAliases(p).has(`${stem}.patch`));
234
+ };
222
235
  const selected = [];
223
236
  const seen = new Set();
224
- for (const name of requested) {
237
+ for (const name of normalizedRequests) {
225
238
  const found = manifest.patches.filter((p) => matches(p, name));
226
239
  if (found.length === 0) {
227
240
  const available = manifest.patches.map((p) => p.filename).join(', ');
@@ -340,4 +353,3 @@ export async function lintPerPatch(projectRoot, paths, options = {}) {
340
353
  }
341
354
  reportPerPatchOutcome(issues, linted, skipped, options);
342
355
  }
343
- //# sourceMappingURL=lint-per-patch.js.map
@@ -1,6 +1,7 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { stat } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
+ import { Command } from 'commander';
4
5
  import { isBrandingManagedPath } from '../core/branding.js';
5
6
  import { getProjectPaths, loadConfig } from '../core/config.js';
6
7
  import { collectFurnaceManagedPrefixes } from '../core/furnace-config.js';
@@ -510,7 +511,7 @@ export function registerLint(program, { getProjectRoot, withErrorHandling }) {
510
511
  .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)')
511
512
  .option('--only-introduced', 'Fail only on issues tagged [introduced] (requires --since). Cumulative errors still print but do not set a non-zero exit.')
512
513
  .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.")
513
- .option('--patches <names...>', 'With --per-patch, lint only the named patches (by filename or manifest name) instead of the whole queue. Queue-level findings are scoped to files those patches touch.')
514
+ .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.')
514
515
  .option('--max-warnings <n>', 'Fail when lint reports more than <n> warning(s); use 0 for warning-clean release gates.')
515
516
  .option('--no-cache', 'Bypass per-patch lint result cache reads and writes.')
516
517
  .action(withErrorHandling(async (paths, options) => {
@@ -539,9 +540,19 @@ export function registerLint(program, { getProjectRoot, withErrorHandling }) {
539
540
  }
540
541
  await lintCommand(getProjectRoot(), paths, lintOptions);
541
542
  }));
542
- lint
543
+ const lintCache = lint
543
544
  .command('cache')
544
545
  .description('Manage the per-patch lint result cache')
546
+ // Commander routes `fireforge lint cache` here even when the operator
547
+ // meant to lint an engine directory literally named `cache`. A bare
548
+ // `lint cache` (no subcommand) used to silently do nothing — make the
549
+ // ambiguity explicit instead of doing the wrong operation quietly.
550
+ .action(withErrorHandling(() => {
551
+ info('Nothing to do: "lint cache" manages the lint result cache (try "lint cache clear"). ' +
552
+ 'To lint a directory named cache/, disambiguate with a trailing slash: "fireforge lint cache/".');
553
+ return Promise.resolve();
554
+ }));
555
+ lintCache
545
556
  .command('clear')
546
557
  .description('Clear cached per-patch lint results')
547
558
  .action(withErrorHandling(async () => {
@@ -551,4 +562,3 @@ export function registerLint(program, { getProjectRoot, withErrorHandling }) {
551
562
  outro('Lint cache cleared');
552
563
  }));
553
564
  }
554
- //# sourceMappingURL=lint.js.map
@@ -20,7 +20,7 @@ import { registerRun } from './run.js';
20
20
  import { registerSetup } from './setup.js';
21
21
  import { registerSource } from './source.js';
22
22
  import { registerStatus } from './status.js';
23
- import { registerTest } from './test.js';
23
+ import { registerTest } from './test-register.js';
24
24
  import { registerToken } from './token.js';
25
25
  import { registerTypecheck } from './typecheck.js';
26
26
  import { registerVerify } from './verify.js';
@@ -60,4 +60,3 @@ export const COMMAND_MANIFEST = [
60
60
  { name: 'verify', group: 'diagnostics', register: registerVerify },
61
61
  { name: 'furnace', group: 'components', register: registerFurnace },
62
62
  ];
63
- //# sourceMappingURL=manifest.js.map
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { Command } from 'commander';
1
3
  import { validateBrandOverride } from '../core/brand-validation.js';
2
4
  import { prepareBuildEnvironment } from '../core/build-prepare.js';
3
5
  import { getProjectPaths, loadConfig } from '../core/config.js';
@@ -88,4 +90,3 @@ export function registerPackage(program, { getProjectRoot, withErrorHandling })
88
90
  await packageCommand(getProjectRoot(), pickDefined(options));
89
91
  }));
90
92
  }
91
- //# sourceMappingURL=package.js.map
@@ -9,6 +9,7 @@
9
9
  * compaction is range-aware (each category range compacts independently,
10
10
  * reserved ranges and out-of-range strays are left untouched).
11
11
  */
12
+ import { Command } from 'commander';
12
13
  import { loadConfig } from '../../core/config.js';
13
14
  import { appendHistory, confirmDestructive } from '../../core/destructive.js';
14
15
  import { withPatchDirectoryLock } from '../../core/patch-lock.js';
@@ -206,4 +207,3 @@ export function registerPatchCompact(parent, context) {
206
207
  await patchCompactCommand(getProjectRoot(), pickDefined(options));
207
208
  }));
208
209
  }
209
- //# sourceMappingURL=compact.js.map
@@ -8,6 +8,7 @@
8
8
  * `--dry-run`, and appends to `patches/.fireforge-history.jsonl` on success.
9
9
  */
10
10
  import { basename } from 'node:path';
11
+ import { Command } from 'commander';
11
12
  import { appendHistory, confirmDestructive } from '../../core/destructive.js';
12
13
  import { buildPatchQueueContext, extractImportSpecifiersWithLines, findForwardImportIgnoreLines, isForwardImportableFile, } from '../../core/patch-lint.js';
13
14
  import { withPatchDirectoryLock } from '../../core/patch-lock.js';
@@ -202,4 +203,3 @@ export function registerPatchDelete(parent, context) {
202
203
  await patchDeleteCommand(getProjectRoot(), name, pickDefined(options));
203
204
  }));
204
205
  }
205
- //# sourceMappingURL=delete.js.map
@@ -7,6 +7,7 @@
7
7
  * Queue-level verbs like `lint`, `export`, `verify`, and `status` stay
8
8
  * flat.
9
9
  */
10
+ import { Command } from 'commander';
10
11
  import { registerPatchCompact } from './compact.js';
11
12
  import { registerPatchDelete } from './delete.js';
12
13
  import { registerPatchLintIgnore } from './lint-ignore.js';
@@ -45,4 +46,3 @@ export function registerPatch(program, context) {
45
46
  registerPatchStagedDependency(patch, context);
46
47
  registerPatchTier(patch, context);
47
48
  }
48
- //# sourceMappingURL=index.js.map
@@ -19,7 +19,8 @@
19
19
  * write — important when an operator scripts repeated invocations or
20
20
  * runs `--add` and `--remove` back-to-back.
21
21
  */
22
- import { appendHistory } from '../../core/destructive.js';
22
+ import { Command } from 'commander';
23
+ import { appendHistory, confirmDestructive } from '../../core/destructive.js';
23
24
  import { mutatePatchMetadata } from '../../core/patch-export.js';
24
25
  import { GeneralError, InvalidArgumentError } from '../../errors/base.js';
25
26
  import { toError } from '../../utils/errors.js';
@@ -107,13 +108,28 @@ export async function patchLintIgnoreCommand(projectRoot, identifier, options =
107
108
  const values = mode === 'add' ? (options.add ?? []) : mode === 'remove' ? (options.remove ?? []) : [];
108
109
  const { paths, manifest } = await requirePatchQueue(projectRoot);
109
110
  const target = requirePatchTarget(identifier, manifest.patches);
110
- if (isDryRun) {
111
- const existing = target.lintIgnore ?? [];
112
- const projected = applyMode(existing, mode, values) ?? [];
113
- info(`[dry-run] ${target.filename}: ${describeChange(existing, projected, mode, values)}.`);
111
+ // Destructive-operation contract (docs/lifecycle-invariants.md): manifest
112
+ // metadata mutations get summary + dry-run + confirmation/--yes + history
113
+ // uniformly. This command used to accept --yes without ever prompting, so
114
+ // the flag only appeared in the history record — and suppressing lint
115
+ // findings is precisely the mutation an operator should consciously
116
+ // approve.
117
+ const projectedSummary = describeChange(target.lintIgnore ?? [], applyMode(target.lintIgnore ?? [], mode, values) ?? [], mode, values);
118
+ const decision = await confirmDestructive({
119
+ operation: 'patch-lint-ignore',
120
+ title: `Patch lint-ignore: ${target.filename}`,
121
+ summary: [`${target.filename}: ${projectedSummary}`],
122
+ yes: options.yes === true,
123
+ dryRun: isDryRun,
124
+ });
125
+ if (decision === 'dry-run') {
114
126
  outro('Dry run complete — no changes made');
115
127
  return;
116
128
  }
129
+ if (decision === 'cancelled') {
130
+ outro('Cancelled — no changes made');
131
+ return;
132
+ }
117
133
  const result = await mutatePatchMetadata(paths.patches, target.filename, (existing) => {
118
134
  const next = applyMode(existing.lintIgnore ?? [], mode, values);
119
135
  // Either set the new list when non-empty or unset the field
@@ -186,4 +202,3 @@ export function registerPatchLintIgnore(parent, context) {
186
202
  await patchLintIgnoreCommand(getProjectRoot(), name, normalized);
187
203
  }));
188
204
  }
189
- //# sourceMappingURL=lint-ignore.js.map
@@ -4,6 +4,7 @@
4
4
  * re-export choreography needed to move file ownership between two patches.
5
5
  */
6
6
  import { relative } from 'node:path';
7
+ import { Command } from 'commander';
7
8
  import { getProjectPaths } from '../../core/config.js';
8
9
  import { formatPatchNotFoundError } from '../../core/patch-identifier-suggest.js';
9
10
  import { loadPatchesManifest, resolvePatchIdentifier } from '../../core/patch-manifest.js';
@@ -136,4 +137,3 @@ export function registerPatchMoveFiles(parent, context) {
136
137
  await patchMoveFilesCommand(getProjectRoot(), from, to, options);
137
138
  }));
138
139
  }
139
- //# sourceMappingURL=move-files.js.map
@@ -50,4 +50,3 @@ export function requirePatchTarget(identifier, patches) {
50
50
  }
51
51
  return target;
52
52
  }
53
- //# sourceMappingURL=patch-context.js.map
@@ -18,9 +18,10 @@
18
18
  */
19
19
  import { rename as fsRename } from 'node:fs/promises';
20
20
  import { join } from 'node:path';
21
+ import { Command } from 'commander';
21
22
  import { loadConfig } from '../../core/config.js';
22
23
  import { appendHistory, confirmDestructive } from '../../core/destructive.js';
23
- import { sanitizeName } from '../../core/patch-export.js';
24
+ import { patchNameSlug } from '../../core/patch-export.js';
24
25
  import { withPatchDirectoryLock } from '../../core/patch-lock.js';
25
26
  import { loadPatchesManifest, rewriteStagedDependencyOwners, savePatchesManifest, } from '../../core/patch-manifest.js';
26
27
  import { buildProjectedManifest, enforcePatchPolicy } from '../../core/patch-policy.js';
@@ -163,7 +164,7 @@ export async function patchRenameCommand(projectRoot, identifier, options = {})
163
164
  if (!split) {
164
165
  throw new GeneralError(`Cannot rename ${target.filename}: filename does not match the expected {ordinal}-{category}-{slug}.patch convention. Re-export the patch instead.`);
165
166
  }
166
- const newSlug = sanitizeName(options.to);
167
+ const newSlug = patchNameSlug(options.to, split.category);
167
168
  if (newSlug === '') {
168
169
  throw new InvalidArgumentError('--to must contain at least one alphanumeric character after sanitisation.', 'patch rename');
169
170
  }
@@ -258,7 +259,7 @@ export function registerPatchRename(parent, context) {
258
259
  parent
259
260
  .command('rename <name>')
260
261
  .description('Rename a patch: filename + manifest name (and optional description) update without rewriting the .patch body.')
261
- .requiredOption('--to <new-name>', 'New human-readable name (sanitised into the filename slug)')
262
+ .requiredOption('--to <new-name>', 'New human-readable name, category-prefixed slug, or full filename stem (normalised into the filename slug)')
262
263
  .option('--description <text>', 'Replacement description (omit to leave description unchanged)')
263
264
  .option('--dry-run', 'Show what would change without writing')
264
265
  .option('-y, --yes', 'Skip confirmation prompt (required for non-TTY)')
@@ -267,4 +268,3 @@ export function registerPatchRename(parent, context) {
267
268
  await patchRenameCommand(getProjectRoot(), name, pickDefined(options));
268
269
  }));
269
270
  }
270
- //# sourceMappingURL=rename.js.map
@@ -8,7 +8,7 @@
8
8
  * `lintPatchQueue` so reorders that would introduce a forward-import fail
9
9
  * before any bytes move.
10
10
  */
11
- import { Option } from 'commander';
11
+ import { Command, Option } from 'commander';
12
12
  import { loadConfig } from '../../core/config.js';
13
13
  import { appendHistory, confirmDestructive, } from '../../core/destructive.js';
14
14
  import { buildPatchQueueContext, lintPatchQueue, } from '../../core/patch-lint.js';
@@ -18,7 +18,7 @@ import { applyRenameMapToManifest, enforcePatchPolicy } from '../../core/patch-p
18
18
  import { GeneralError, InvalidArgumentError } from '../../errors/base.js';
19
19
  import { toError } from '../../utils/errors.js';
20
20
  import { info, intro, outro, warn } from '../../utils/logger.js';
21
- import { pickDefined } from '../../utils/options.js';
21
+ import { commanderArgParser, pickDefined } from '../../utils/options.js';
22
22
  import { parsePositiveIntegerFlag } from '../../utils/validation.js';
23
23
  import { requirePatchQueue, requirePatchTarget } from './patch-context.js';
24
24
  /** Zero-pads an ordinal number to the given width. */
@@ -372,7 +372,7 @@ export function registerPatchReorder(parent, context) {
372
372
  parent
373
373
  .command('reorder <name>')
374
374
  .description('Move a patch to a different position in the queue (destructive)')
375
- .addOption(new Option('--to <order>', 'Destination ordinal').argParser((v) => parsePositiveIntegerFlag('--to', v)))
375
+ .addOption(new Option('--to <order>', 'Destination ordinal').argParser(commanderArgParser((v) => parsePositiveIntegerFlag('--to', v))))
376
376
  .option('--before <anchor>', 'Place the patch immediately before <anchor>')
377
377
  .option('--after <anchor>', 'Place the patch immediately after <anchor>')
378
378
  .option('--dry-run', 'Show what would happen without writing')
@@ -382,4 +382,3 @@ export function registerPatchReorder(parent, context) {
382
382
  await patchReorderCommand(getProjectRoot(), name, pickDefined(options));
383
383
  }));
384
384
  }
385
- //# sourceMappingURL=reorder.js.map
@@ -7,6 +7,7 @@
7
7
  * budget; consumed only by the split command.
8
8
  */
9
9
  import { join } from 'node:path';
10
+ import {} from '../../core/destructive.js';
10
11
  import { getDiffForFilesAgainstHead } from '../../core/git-diff.js';
11
12
  import { computeProjectedLintRegressions } from '../../core/lint-projection.js';
12
13
  import { extractAffectedFiles } from '../../core/patch-apply.js';
@@ -18,6 +19,7 @@ import { extractNewFileContentFromDiff } from '../../core/patch-transform.js';
18
19
  import { GeneralError, InvalidArgumentError } from '../../errors/base.js';
19
20
  import { pathExists } from '../../utils/fs.js';
20
21
  import { warn } from '../../utils/logger.js';
22
+ import {} from '../export-flow.js';
21
23
  /**
22
24
  *
23
25
  */
@@ -249,4 +251,3 @@ export function buildSplitSummary(plan) {
249
251
  }
250
252
  return summary;
251
253
  }
252
- //# sourceMappingURL=split-plan.js.map
@@ -18,6 +18,7 @@
18
18
  * the worktree.
19
19
  */
20
20
  import { join } from 'node:path';
21
+ import { Command } from 'commander';
21
22
  import { getProjectPaths, loadConfig } from '../../core/config.js';
22
23
  import { appendHistory, confirmDestructive } from '../../core/destructive.js';
23
24
  import { normalizePatchArtifact } from '../../core/patch-artifact-normalize.js';
@@ -29,7 +30,7 @@ import { GeneralError, InvalidArgumentError } from '../../errors/base.js';
29
30
  import { toError } from '../../utils/errors.js';
30
31
  import { readText, removeFile, writeText } from '../../utils/fs.js';
31
32
  import { info, intro, outro, success, warn } from '../../utils/logger.js';
32
- import { pickDefined } from '../../utils/options.js';
33
+ import { commanderArgParser, pickDefined } from '../../utils/options.js';
33
34
  import { placementPlansEqual, resolvePlacementPlan } from '../export-flow.js';
34
35
  import { runPatchLint } from '../export-shared.js';
35
36
  import { assertSourceOwnsFiles, buildNewPatchMetadata, buildSplitDiff, buildSplitSummary, findOwnerRewriteHolders, mergeStagedForwardImports, projectSplitManifest, rewriteSplitOwners, runProjectedSplitLint, } from './split-plan.js';
@@ -258,13 +259,13 @@ export function registerPatchSplit(parent, context) {
258
259
  .requiredOption('--name <name>', 'Name for the new patch')
259
260
  .option('--category <category>', "Category for the new patch (default: the source patch's)")
260
261
  .option('--description <desc>', 'Description for the new patch')
261
- .option('--order <n>', 'Exact sparse order for the new patch', (raw) => {
262
+ .option('--order <n>', 'Exact sparse order for the new patch', commanderArgParser((raw) => {
262
263
  const n = Number.parseInt(raw, 10);
263
264
  if (!Number.isInteger(n) || n <= 0) {
264
265
  throw new InvalidArgumentError(`--order must be a positive integer, got "${raw}".`, '--order');
265
266
  }
266
267
  return n;
267
- })
268
+ }))
268
269
  .option('--before <patch>', 'Place the new patch before this patch')
269
270
  .option('--after <patch>', 'Place the new patch after this patch (default: the source patch)')
270
271
  .option('--dry-run', 'Show what would happen without writing')
@@ -289,4 +290,3 @@ export function registerPatchSplit(parent, context) {
289
290
  });
290
291
  }));
291
292
  }
292
- //# sourceMappingURL=split.js.map
@@ -3,6 +3,7 @@
3
3
  * `fireforge patch staged-dependency <name>` — edits
4
4
  * PatchMetadata.stagedDependencies without rewriting the .patch body.
5
5
  */
6
+ import { Command } from 'commander';
6
7
  import { appendHistory } from '../../core/destructive.js';
7
8
  import { mutatePatchMetadata } from '../../core/patch-export.js';
8
9
  import { GeneralError, InvalidArgumentError } from '../../errors/base.js';
@@ -161,4 +162,3 @@ export function registerPatchStagedDependency(parent, context) {
161
162
  await patchStagedDependencyCommand(getProjectRoot(), name, options);
162
163
  }));
163
164
  }
164
- //# sourceMappingURL=staged-dependency.js.map
@@ -14,8 +14,8 @@
14
14
  * Modes are mutually exclusive: exactly one of `--tier <branding>` or
15
15
  * `--clear` must be supplied per invocation.
16
16
  */
17
- import { Option } from 'commander';
18
- import { appendHistory } from '../../core/destructive.js';
17
+ import { Command, Option } from 'commander';
18
+ import { appendHistory, confirmDestructive } from '../../core/destructive.js';
19
19
  import { updatePatchMetadata } from '../../core/patch-export.js';
20
20
  import { InvalidArgumentError } from '../../errors/base.js';
21
21
  import { toError } from '../../utils/errors.js';
@@ -60,11 +60,25 @@ export async function patchTierCommand(projectRoot, identifier, options = {}) {
60
60
  : before === undefined
61
61
  ? `set tier to "${after}"`
62
62
  : `change tier from "${before}" to "${after}"`;
63
- if (isDryRun) {
64
- info(`[dry-run] ${target.filename}: would ${action}.`);
63
+ // Destructive-operation contract (docs/lifecycle-invariants.md): manifest
64
+ // metadata mutations get summary + dry-run + confirmation/--yes + history
65
+ // uniformly. This command used to accept --yes without ever prompting, so
66
+ // the flag only appeared in the history record.
67
+ const decision = await confirmDestructive({
68
+ operation: 'patch-tier',
69
+ title: `Patch tier: ${target.filename}`,
70
+ summary: [`${target.filename}: ${action}`],
71
+ yes: options.yes === true,
72
+ dryRun: isDryRun,
73
+ });
74
+ if (decision === 'dry-run') {
65
75
  outro('Dry run complete — no changes made');
66
76
  return;
67
77
  }
78
+ if (decision === 'cancelled') {
79
+ outro('Cancelled — no changes made');
80
+ return;
81
+ }
68
82
  // Single write under the patch directory lock (delegated inside
69
83
  // updatePatchMetadata). Setting routes through `updates`; clearing
70
84
  // routes through `unsetFields` so TypeScript's exact optional types
@@ -116,4 +130,3 @@ export function registerPatchTier(parent, context) {
116
130
  });
117
131
  }));
118
132
  }
119
- //# sourceMappingURL=tier.js.map
@@ -78,4 +78,3 @@ export async function withDryRunReExportLock(fireforgeDir, isDryRun, operation)
78
78
  onStaleLockMessage: (ageMs) => `Removing stale FireForge re-export dry-run lock (age: ${Math.round(ageMs / 1000)}s). A previous dry-run process may have crashed.`,
79
79
  });
80
80
  }
81
- //# sourceMappingURL=re-export-bulk-scan.js.map
@@ -1,5 +1,6 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { join } from 'node:path';
3
+ import { getProjectPaths } from '../core/config.js';
3
4
  import { appendHistory, confirmDestructive } from '../core/destructive.js';
4
5
  import { getDiffForFilesAgainstHead } from '../core/git-diff.js';
5
6
  import { computeProjectedLintRegressions } from '../core/lint-projection.js';
@@ -260,4 +261,3 @@ export async function reExportFilesInPlace(paths, selectedPatches, options, conf
260
261
  success(`Re-exported ${target.filename}`);
261
262
  outro('Re-export complete');
262
263
  }
263
- //# sourceMappingURL=re-export-files.js.map
@@ -83,4 +83,3 @@ export function validateReExportOptionCombinations(patches, options) {
83
83
  throw new InvalidArgumentError('--tier and --lint-ignore require explicit patch identifiers and cannot be combined with --all (different patches typically need different metadata).', '--all');
84
84
  }
85
85
  }
86
- //# sourceMappingURL=re-export-options.js.map
@@ -144,4 +144,3 @@ export function assertScanFileAdditionsHaveDiffHunks(args) {
144
144
  return;
145
145
  throw new InvalidArgumentError(`Refusing to re-export ${patchFilename} with --scan-file because ${noDiffScanFiles.length} explicit added path${noDiffScanFiles.length === 1 ? '' : 's'} produced no diff hunks (${noDiffScanFiles.join(', ')}). Remove unchanged paths or modify them before retrying.`, '--scan-file');
146
146
  }
147
- //# sourceMappingURL=re-export-scan.js.map
@@ -1,8 +1,10 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
+ /* eslint-disable max-lines -- Re-export is at the guardrail; splitting register plumbing is unrelated to this fix. */
2
3
  import { dirname, join } from 'node:path';
3
4
  import { multiselect } from '@clack/prompts';
4
- import { Option } from 'commander';
5
+ import { Command, Option } from 'commander';
5
6
  import { getProjectPaths, loadConfig } from '../core/config.js';
7
+ import { withEngineSessionLock } from '../core/engine-session-lock.js';
6
8
  import { isGitRepository } from '../core/git.js';
7
9
  import { getDiffForFilesAgainstHead } from '../core/git-diff.js';
8
10
  import { getModifiedFilesInDir, getUntrackedFilesInDir } from '../core/git-status.js';
@@ -421,13 +423,13 @@ export function registerReExport(program, { getProjectRoot, withErrorHandling })
421
423
  .option('--lint-ignore <check-id>', 'Append a lint check ID to the patch\'s PatchMetadata.lintIgnore (union, de-duped, repeatable). Mutually exclusive with --all. Use "fireforge patch lint-ignore" for --remove / --clear.', (value, prev) => [...prev, value], [])
422
424
  .action(withErrorHandling(async (patches, options) => {
423
425
  const { tier, lintIgnore, scanFile, scanFiles, ...rest } = options;
424
- await reExportCommand(getProjectRoot(), patches, {
426
+ const projectRoot = getProjectRoot();
427
+ await withEngineSessionLock(projectRoot, 're-export', () => reExportCommand(projectRoot, patches, {
425
428
  ...pickDefined(rest),
426
429
  ...(scanFile !== undefined && scanFile.length > 0 ? { scanFiles: scanFile } : {}),
427
430
  ...(scanFiles !== undefined ? { scanFilesManifest: scanFiles } : {}),
428
431
  ...(tier !== undefined ? { tier: tier } : {}),
429
432
  ...(lintIgnore !== undefined && lintIgnore.length > 0 ? { lintIgnore } : {}),
430
- });
433
+ }));
431
434
  }));
432
435
  }
433
- //# sourceMappingURL=re-export.js.map
@@ -3,11 +3,10 @@
3
3
  * Rebase abort flow.
4
4
  */
5
5
  import { getProjectPaths, updateState } from '../../core/config.js';
6
- import { getFurnacePaths, updateFurnaceState } from '../../core/furnace-config.js';
6
+ import { clearAppliedFurnaceState } from '../../core/furnace-config.js';
7
7
  import { resetChanges } from '../../core/git.js';
8
8
  import { clearRebaseSession, loadRebaseSession } from '../../core/rebase-session.js';
9
9
  import { NoRebaseSessionError } from '../../errors/rebase.js';
10
- import { pathExists } from '../../utils/fs.js';
11
10
  import { intro, outro, spinner, success } from '../../utils/logger.js';
12
11
  import { confirmDirtyEngineReset } from './confirm.js';
13
12
  /**
@@ -47,12 +46,7 @@ export async function handleAbort(projectRoot, yes) {
47
46
  // misleading now that reset already succeeded). The rebase session is
48
47
  // still kept on disk so the user can retry the abort and let it
49
48
  // idempotently re-clear furnace state.
50
- const furnacePaths = getFurnacePaths(projectRoot);
51
- if (await pathExists(furnacePaths.furnaceState)) {
52
- await updateFurnaceState(projectRoot, (current) => ({
53
- ...(current.pendingRepair ? { pendingRepair: current.pendingRepair } : {}),
54
- }));
55
- }
49
+ await clearAppliedFurnaceState(projectRoot);
56
50
  // Step 3: clear pending resolution transactionally.
57
51
  await updateState(projectRoot, (current) => {
58
52
  if (!current.pendingResolution)
@@ -67,4 +61,3 @@ export async function handleAbort(projectRoot, yes) {
67
61
  success('Rebase aborted and session cleared.');
68
62
  outro('Rebase aborted');
69
63
  }
70
- //# sourceMappingURL=abort.js.map
@@ -30,4 +30,3 @@ export async function confirmDirtyEngineReset({ engineDir, yes, nonInteractiveCo
30
30
  }
31
31
  return true;
32
32
  }
33
- //# sourceMappingURL=confirm.js.map
@@ -35,4 +35,3 @@ export function buildRebaseConflictSummary(args) {
35
35
  ],
36
36
  };
37
37
  }
38
- //# sourceMappingURL=conflict-summary.js.map
@@ -104,4 +104,3 @@ export async function handleContinue(projectRoot, maxFuzz) {
104
104
  // Continue applying remaining patches
105
105
  await runPatchLoop(projectRoot, session, paths, maxFuzz);
106
106
  }
107
- //# sourceMappingURL=continue.js.map
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Orchestrates the full patch-rebase workflow:
5
5
  * 1. Reset engine to baseline
6
- * 2. Apply each patch with escalating fuzz
6
+ * 2. Apply each patch with escalating context reduction (git apply -C<n>)
7
7
  * 3. Pause on failures for manual resolution
8
8
  * 4. Re-export successfully applied patches with the new version stamp
9
9
  *