@hominis/fireforge 0.49.2 → 0.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (400) hide show
  1. package/CHANGELOG.md +87 -1
  2. package/README.md +1 -1
  3. package/dist/bin/fireforge.js +6 -0
  4. package/dist/build-info.json +4 -4
  5. package/dist/src/commands/build-prelock.d.ts +20 -0
  6. package/dist/src/commands/build-prelock.js +50 -0
  7. package/dist/src/commands/build.d.ts +3 -1
  8. package/dist/src/commands/build.js +13 -6
  9. package/dist/src/commands/check-report.d.ts +41 -0
  10. package/dist/src/commands/check-report.js +91 -0
  11. package/dist/src/commands/check-run.d.ts +33 -0
  12. package/dist/src/commands/check-run.js +112 -0
  13. package/dist/src/commands/check-steps.d.ts +75 -0
  14. package/dist/src/commands/check-steps.js +200 -0
  15. package/dist/src/commands/check.d.ts +32 -0
  16. package/dist/src/commands/check.js +160 -0
  17. package/dist/src/commands/config.js +5 -6
  18. package/dist/src/commands/declarations-report.d.ts +43 -0
  19. package/dist/src/commands/declarations-report.js +76 -0
  20. package/dist/src/commands/doctor-engine-eslint.js +6 -1
  21. package/dist/src/commands/doctor-source-pin.js +4 -1
  22. package/dist/src/commands/export-adopt-check.d.ts +38 -0
  23. package/dist/src/commands/export-adopt-check.js +98 -0
  24. package/dist/src/commands/export-flow.js +29 -14
  25. package/dist/src/commands/export-placement-gate.d.ts +6 -2
  26. package/dist/src/commands/export-placement-gate.js +8 -0
  27. package/dist/src/commands/export-shared.d.ts +2 -1
  28. package/dist/src/commands/export-staged-registration.d.ts +46 -0
  29. package/dist/src/commands/export-staged-registration.js +91 -0
  30. package/dist/src/commands/forward-import-remedy.d.ts +32 -0
  31. package/dist/src/commands/forward-import-remedy.js +63 -0
  32. package/dist/src/commands/furnace/apply.js +3 -3
  33. package/dist/src/commands/furnace/chrome-doc-remove.d.ts +2 -1
  34. package/dist/src/commands/furnace/chrome-doc-remove.js +2 -2
  35. package/dist/src/commands/furnace/chrome-doc.d.ts +2 -1
  36. package/dist/src/commands/furnace/chrome-doc.js +2 -2
  37. package/dist/src/commands/furnace/create.js +2 -2
  38. package/dist/src/commands/furnace/deploy-named.d.ts +48 -0
  39. package/dist/src/commands/furnace/deploy-named.js +107 -0
  40. package/dist/src/commands/furnace/deploy.d.ts +3 -2
  41. package/dist/src/commands/furnace/deploy.js +50 -86
  42. package/dist/src/commands/furnace/diff.js +26 -3
  43. package/dist/src/commands/furnace/index.js +66 -41
  44. package/dist/src/commands/furnace/init.js +13 -6
  45. package/dist/src/commands/furnace/override.js +4 -2
  46. package/dist/src/commands/furnace/preview.js +4 -3
  47. package/dist/src/commands/furnace/refresh.js +2 -2
  48. package/dist/src/commands/furnace/remove.js +4 -3
  49. package/dist/src/commands/furnace/rename-dry-run.d.ts +29 -0
  50. package/dist/src/commands/furnace/rename-dry-run.js +127 -0
  51. package/dist/src/commands/furnace/rename-helpers.d.ts +7 -0
  52. package/dist/src/commands/furnace/rename-helpers.js +15 -1
  53. package/dist/src/commands/furnace/rename-registrations.d.ts +41 -0
  54. package/dist/src/commands/furnace/rename-registrations.js +98 -0
  55. package/dist/src/commands/furnace/rename.d.ts +2 -1
  56. package/dist/src/commands/furnace/rename.js +28 -66
  57. package/dist/src/commands/furnace/scan.d.ts +9 -4
  58. package/dist/src/commands/furnace/scan.js +11 -9
  59. package/dist/src/commands/furnace/sync.js +3 -2
  60. package/dist/src/commands/furnace/validate-report.d.ts +45 -0
  61. package/dist/src/commands/furnace/validate-report.js +91 -0
  62. package/dist/src/commands/furnace/validate.d.ts +1 -1
  63. package/dist/src/commands/furnace/validate.js +151 -113
  64. package/dist/src/commands/furnace/validation-output.js +14 -3
  65. package/dist/src/commands/lint-per-patch.js +36 -61
  66. package/dist/src/commands/lint-per-run-checkjs.js +2 -2
  67. package/dist/src/commands/lint-report.d.ts +8 -4
  68. package/dist/src/commands/lint-report.js +20 -5
  69. package/dist/src/commands/lint.js +15 -34
  70. package/dist/src/commands/manifest.js +4 -0
  71. package/dist/src/commands/park.d.ts +22 -0
  72. package/dist/src/commands/park.js +232 -0
  73. package/dist/src/commands/patch/delete-gap-notice.js +3 -1
  74. package/dist/src/commands/patch/delete.d.ts +1 -1
  75. package/dist/src/commands/patch/delete.js +24 -104
  76. package/dist/src/commands/patch/index.d.ts +3 -3
  77. package/dist/src/commands/patch/index.js +10 -4
  78. package/dist/src/commands/patch/lint-ignore-from.d.ts +44 -0
  79. package/dist/src/commands/patch/lint-ignore-from.js +142 -0
  80. package/dist/src/commands/patch/lint-ignore.d.ts +5 -1
  81. package/dist/src/commands/patch/lint-ignore.js +78 -31
  82. package/dist/src/commands/patch/move-files-create.js +34 -18
  83. package/dist/src/commands/patch/move-files-into.js +15 -7
  84. package/dist/src/commands/patch/move-files.js +6 -2
  85. package/dist/src/commands/patch/removal-created-files.d.ts +48 -0
  86. package/dist/src/commands/patch/removal-created-files.js +83 -0
  87. package/dist/src/commands/patch/removal-dependents.d.ts +63 -0
  88. package/dist/src/commands/patch/removal-dependents.js +133 -0
  89. package/dist/src/commands/patch/rename.js +20 -1
  90. package/dist/src/commands/patch/reserve.d.ts +33 -0
  91. package/dist/src/commands/patch/reserve.js +152 -0
  92. package/dist/src/commands/patch/retire.d.ts +34 -0
  93. package/dist/src/commands/patch/retire.js +133 -0
  94. package/dist/src/commands/patch/split-plan.d.ts +17 -8
  95. package/dist/src/commands/patch/split-plan.js +60 -28
  96. package/dist/src/commands/patch/split-staged.d.ts +108 -0
  97. package/dist/src/commands/patch/split-staged.js +202 -0
  98. package/dist/src/commands/patch/split.d.ts +5 -0
  99. package/dist/src/commands/patch/split.js +62 -17
  100. package/dist/src/commands/patch/stage.d.ts +34 -0
  101. package/dist/src/commands/patch/stage.js +153 -0
  102. package/dist/src/commands/patch/staged-dependency-batch.d.ts +48 -0
  103. package/dist/src/commands/patch/staged-dependency-batch.js +176 -0
  104. package/dist/src/commands/patch/staged-dependency-entries.d.ts +36 -0
  105. package/dist/src/commands/patch/staged-dependency-entries.js +52 -0
  106. package/dist/src/commands/patch/staged-dependency-from.d.ts +14 -0
  107. package/dist/src/commands/patch/staged-dependency-from.js +108 -0
  108. package/dist/src/commands/patch/staged-dependency-remove.d.ts +31 -0
  109. package/dist/src/commands/patch/staged-dependency-remove.js +80 -0
  110. package/dist/src/commands/patch/staged-dependency.js +54 -139
  111. package/dist/src/commands/re-export-adjacent-registrations.d.ts +20 -0
  112. package/dist/src/commands/re-export-adjacent-registrations.js +75 -0
  113. package/dist/src/commands/re-export-adjacent.d.ts +50 -2
  114. package/dist/src/commands/re-export-adjacent.js +160 -40
  115. package/dist/src/commands/re-export-bulk-scan.d.ts +2 -1
  116. package/dist/src/commands/re-export-bulk-scan.js +22 -7
  117. package/dist/src/commands/re-export-drift.d.ts +36 -8
  118. package/dist/src/commands/re-export-drift.js +73 -49
  119. package/dist/src/commands/re-export-files.js +11 -8
  120. package/dist/src/commands/re-export-hunks.d.ts +35 -0
  121. package/dist/src/commands/re-export-hunks.js +220 -0
  122. package/dist/src/commands/re-export-lint.d.ts +2 -1
  123. package/dist/src/commands/re-export-lint.js +23 -10
  124. package/dist/src/commands/re-export-options.js +74 -6
  125. package/dist/src/commands/re-export-preflight.d.ts +23 -0
  126. package/dist/src/commands/re-export-preflight.js +160 -0
  127. package/dist/src/commands/re-export-purity-scope.d.ts +18 -0
  128. package/dist/src/commands/re-export-purity-scope.js +131 -0
  129. package/dist/src/commands/re-export-register.js +56 -17
  130. package/dist/src/commands/re-export-removed.d.ts +13 -2
  131. package/dist/src/commands/re-export-removed.js +57 -12
  132. package/dist/src/commands/re-export-scan-owner.d.ts +44 -0
  133. package/dist/src/commands/re-export-scan-owner.js +116 -0
  134. package/dist/src/commands/re-export-scan.d.ts +19 -9
  135. package/dist/src/commands/re-export-scan.js +60 -24
  136. package/dist/src/commands/re-export-single.js +122 -46
  137. package/dist/src/commands/re-export-stale-registrations.d.ts +18 -0
  138. package/dist/src/commands/re-export-stale-registrations.js +62 -0
  139. package/dist/src/commands/re-export-summary.d.ts +28 -0
  140. package/dist/src/commands/re-export-summary.js +36 -0
  141. package/dist/src/commands/re-export.js +71 -57
  142. package/dist/src/commands/rebase/index.js +17 -17
  143. package/dist/src/commands/run.js +16 -2
  144. package/dist/src/commands/status-check.d.ts +55 -16
  145. package/dist/src/commands/status-check.js +92 -37
  146. package/dist/src/commands/status-json.d.ts +22 -9
  147. package/dist/src/commands/status-json.js +47 -32
  148. package/dist/src/commands/status-output.js +3 -1
  149. package/dist/src/commands/status-scope.d.ts +37 -0
  150. package/dist/src/commands/status-scope.js +54 -0
  151. package/dist/src/commands/status.js +66 -23
  152. package/dist/src/commands/test-browser-preflight.js +19 -3
  153. package/dist/src/commands/test-build-furnace.d.ts +22 -0
  154. package/dist/src/commands/test-build-furnace.js +54 -0
  155. package/dist/src/commands/test-build-phase.js +64 -41
  156. package/dist/src/commands/test-checkjs.js +10 -2
  157. package/dist/src/commands/test-diagnose.js +7 -0
  158. package/dist/src/commands/test-dispatch.d.ts +39 -0
  159. package/dist/src/commands/test-dispatch.js +211 -0
  160. package/dist/src/commands/test-furnace-reach.d.ts +37 -0
  161. package/dist/src/commands/test-furnace-reach.js +132 -0
  162. package/dist/src/commands/test-modes.d.ts +16 -1
  163. package/dist/src/commands/test-modes.js +24 -1
  164. package/dist/src/commands/test-multi-verdict.d.ts +61 -0
  165. package/dist/src/commands/test-multi-verdict.js +111 -0
  166. package/dist/src/commands/test-orchestrate.d.ts +52 -0
  167. package/dist/src/commands/test-orchestrate.js +240 -0
  168. package/dist/src/commands/test-perf-scope.d.ts +50 -0
  169. package/dist/src/commands/test-perf-scope.js +138 -0
  170. package/dist/src/commands/test-prelock.d.ts +51 -0
  171. package/dist/src/commands/test-prelock.js +273 -0
  172. package/dist/src/commands/test-register.js +69 -10
  173. package/dist/src/commands/test-results.d.ts +62 -0
  174. package/dist/src/commands/test-results.js +62 -0
  175. package/dist/src/commands/test-run.d.ts +13 -55
  176. package/dist/src/commands/test-run.js +74 -99
  177. package/dist/src/commands/test-shards.d.ts +110 -0
  178. package/dist/src/commands/test-shards.js +255 -0
  179. package/dist/src/commands/test-stale-gate.d.ts +5 -1
  180. package/dist/src/commands/test-stale-gate.js +60 -28
  181. package/dist/src/commands/test-verdict.d.ts +11 -1
  182. package/dist/src/commands/test-verdict.js +28 -1
  183. package/dist/src/commands/test.d.ts +6 -1
  184. package/dist/src/commands/test.js +120 -124
  185. package/dist/src/commands/token-lint.d.ts +14 -0
  186. package/dist/src/commands/token-lint.js +151 -0
  187. package/dist/src/commands/token.js +10 -0
  188. package/dist/src/commands/verify-scope.d.ts +28 -0
  189. package/dist/src/commands/verify-scope.js +67 -0
  190. package/dist/src/commands/verify.d.ts +10 -2
  191. package/dist/src/commands/verify.js +67 -36
  192. package/dist/src/core/build-baseline-types.d.ts +46 -0
  193. package/dist/src/core/build-baseline-validate.js +19 -0
  194. package/dist/src/core/build-baseline.d.ts +5 -1
  195. package/dist/src/core/build-baseline.js +3 -1
  196. package/dist/src/core/build-overwrite-guard.d.ts +50 -0
  197. package/dist/src/core/build-overwrite-guard.js +84 -4
  198. package/dist/src/core/build-prepare.d.ts +13 -0
  199. package/dist/src/core/build-prepare.js +42 -10
  200. package/dist/src/core/build-run-record.d.ts +74 -0
  201. package/dist/src/core/build-run-record.js +136 -0
  202. package/dist/src/core/checkjs-support-heads.d.ts +41 -0
  203. package/dist/src/core/checkjs-support-heads.js +104 -0
  204. package/dist/src/core/config-paths.d.ts +2 -2
  205. package/dist/src/core/config-paths.js +10 -0
  206. package/dist/src/core/config-validate-check.d.ts +8 -0
  207. package/dist/src/core/config-validate-check.js +219 -0
  208. package/dist/src/core/config-validate-file-size.d.ts +9 -6
  209. package/dist/src/core/config-validate-file-size.js +91 -2
  210. package/dist/src/core/config-validate-history.d.ts +11 -0
  211. package/dist/src/core/config-validate-history.js +38 -0
  212. package/dist/src/core/config-validate-patch-policy.js +100 -0
  213. package/dist/src/core/config-validate-test-toolchains.js +21 -0
  214. package/dist/src/core/config-validate-token-scales.d.ts +9 -0
  215. package/dist/src/core/config-validate-token-scales.js +92 -0
  216. package/dist/src/core/config-validate.js +20 -5
  217. package/dist/src/core/config.d.ts +5 -0
  218. package/dist/src/core/config.js +16 -0
  219. package/dist/src/core/coverage-dispatch.d.ts +24 -0
  220. package/dist/src/core/coverage-dispatch.js +44 -0
  221. package/dist/src/core/coverage-extend.d.ts +70 -45
  222. package/dist/src/core/coverage-extend.js +199 -144
  223. package/dist/src/core/coverage-records.d.ts +108 -0
  224. package/dist/src/core/coverage-records.js +384 -0
  225. package/dist/src/core/destructive.d.ts +7 -4
  226. package/dist/src/core/destructive.js +10 -10
  227. package/dist/src/core/engine-session-lock.d.ts +16 -1
  228. package/dist/src/core/engine-session-lock.js +38 -10
  229. package/dist/src/core/export-moved-code-shape.d.ts +49 -11
  230. package/dist/src/core/export-moved-code-shape.js +135 -44
  231. package/dist/src/core/file-lock-queue.d.ts +67 -0
  232. package/dist/src/core/file-lock-queue.js +221 -0
  233. package/dist/src/core/file-lock-shared.d.ts +57 -0
  234. package/dist/src/core/file-lock-shared.js +117 -0
  235. package/dist/src/core/file-lock.d.ts +13 -25
  236. package/dist/src/core/file-lock.js +163 -140
  237. package/dist/src/core/furnace-apply-output.d.ts +10 -0
  238. package/dist/src/core/furnace-apply-output.js +31 -1
  239. package/dist/src/core/furnace-apply.js +22 -12
  240. package/dist/src/core/furnace-composed-source.d.ts +76 -0
  241. package/dist/src/core/furnace-composed-source.js +167 -0
  242. package/dist/src/core/furnace-config-validate-exceptions.d.ts +16 -0
  243. package/dist/src/core/furnace-config-validate-exceptions.js +102 -0
  244. package/dist/src/core/furnace-config-validate.js +14 -0
  245. package/dist/src/core/furnace-css-fragments.d.ts +10 -0
  246. package/dist/src/core/furnace-css-fragments.js +26 -0
  247. package/dist/src/core/furnace-operation.d.ts +3 -3
  248. package/dist/src/core/furnace-registration-jar-inplace.d.ts +12 -0
  249. package/dist/src/core/furnace-registration-jar-inplace.js +80 -0
  250. package/dist/src/core/furnace-registration-remove.d.ts +22 -1
  251. package/dist/src/core/furnace-registration-remove.js +53 -17
  252. package/dist/src/core/furnace-registration-rename.d.ts +26 -0
  253. package/dist/src/core/furnace-registration-rename.js +77 -0
  254. package/dist/src/core/furnace-registration.d.ts +14 -4
  255. package/dist/src/core/furnace-registration.js +44 -10
  256. package/dist/src/core/furnace-staleness.d.ts +7 -0
  257. package/dist/src/core/furnace-staleness.js +34 -24
  258. package/dist/src/core/furnace-validate-accessibility.d.ts +8 -4
  259. package/dist/src/core/furnace-validate-accessibility.js +71 -28
  260. package/dist/src/core/furnace-validate-exceptions.d.ts +48 -0
  261. package/dist/src/core/furnace-validate-exceptions.js +80 -0
  262. package/dist/src/core/furnace-validate-icon-button.d.ts +7 -0
  263. package/dist/src/core/furnace-validate-icon-button.js +78 -0
  264. package/dist/src/core/furnace-validate.js +1 -1
  265. package/dist/src/core/harness-port-fallback.d.ts +60 -0
  266. package/dist/src/core/harness-port-fallback.js +96 -0
  267. package/dist/src/core/history-log-file.d.ts +61 -0
  268. package/dist/src/core/history-log-file.js +164 -0
  269. package/dist/src/core/history-log.d.ts +2 -1
  270. package/dist/src/core/history-log.js +2 -1
  271. package/dist/src/core/json-preserving-edit.d.ts +22 -0
  272. package/dist/src/core/json-preserving-edit.js +199 -0
  273. package/dist/src/core/line-prefixer.d.ts +18 -0
  274. package/dist/src/core/line-prefixer.js +34 -0
  275. package/dist/src/core/mach.d.ts +7 -0
  276. package/dist/src/core/mach.js +32 -10
  277. package/dist/src/core/module-resolution-preflight.d.ts +19 -3
  278. package/dist/src/core/module-resolution-preflight.js +24 -6
  279. package/dist/src/core/negative-control-compiled.d.ts +27 -0
  280. package/dist/src/core/negative-control-compiled.js +73 -0
  281. package/dist/src/core/negative-control-diff.d.ts +51 -0
  282. package/dist/src/core/negative-control-diff.js +179 -0
  283. package/dist/src/core/negative-control.d.ts +150 -0
  284. package/dist/src/core/negative-control.js +399 -0
  285. package/dist/src/core/objdir-install-manifests.d.ts +73 -0
  286. package/dist/src/core/objdir-install-manifests.js +226 -0
  287. package/dist/src/core/park-store.d.ts +63 -0
  288. package/dist/src/core/park-store.js +216 -0
  289. package/dist/src/core/patch-export-update.d.ts +6 -0
  290. package/dist/src/core/patch-export-update.js +13 -3
  291. package/dist/src/core/patch-export.d.ts +3 -2
  292. package/dist/src/core/patch-export.js +22 -16
  293. package/dist/src/core/patch-files.js +26 -8
  294. package/dist/src/core/patch-lint-checkjs-any-import.d.ts +39 -0
  295. package/dist/src/core/patch-lint-checkjs-any-import.js +63 -0
  296. package/dist/src/core/patch-lint-checkjs-diagnostics.d.ts +17 -0
  297. package/dist/src/core/patch-lint-checkjs-diagnostics.js +32 -0
  298. package/dist/src/core/patch-lint-checkjs.d.ts +10 -1
  299. package/dist/src/core/patch-lint-checkjs.js +20 -13
  300. package/dist/src/core/patch-lint-code-lines.d.ts +59 -0
  301. package/dist/src/core/patch-lint-code-lines.js +105 -0
  302. package/dist/src/core/patch-lint-cross.d.ts +3 -1
  303. package/dist/src/core/patch-lint-cross.js +17 -4
  304. package/dist/src/core/patch-lint-css.d.ts +8 -2
  305. package/dist/src/core/patch-lint-css.js +24 -14
  306. package/dist/src/core/patch-lint-file-size.d.ts +12 -5
  307. package/dist/src/core/patch-lint-file-size.js +22 -11
  308. package/dist/src/core/patch-lint-forward-registration-extended.d.ts +23 -0
  309. package/dist/src/core/patch-lint-forward-registration-extended.js +36 -11
  310. package/dist/src/core/patch-lint-forward-registration.js +10 -0
  311. package/dist/src/core/patch-lint-ignore.d.ts +196 -0
  312. package/dist/src/core/patch-lint-ignore.js +429 -0
  313. package/dist/src/core/patch-lint-ownership.d.ts +10 -0
  314. package/dist/src/core/patch-lint-ownership.js +26 -0
  315. package/dist/src/core/patch-lint-size-thresholds.d.ts +52 -0
  316. package/dist/src/core/patch-lint-size-thresholds.js +110 -0
  317. package/dist/src/core/patch-lint-size.d.ts +117 -0
  318. package/dist/src/core/patch-lint-size.js +331 -0
  319. package/dist/src/core/patch-lint-staged-registration.d.ts +18 -1
  320. package/dist/src/core/patch-lint-staged-registration.js +20 -0
  321. package/dist/src/core/patch-lint-token-scale.d.ts +57 -0
  322. package/dist/src/core/patch-lint-token-scale.js +286 -0
  323. package/dist/src/core/patch-lint.d.ts +11 -83
  324. package/dist/src/core/patch-lint.js +66 -288
  325. package/dist/src/core/patch-lock.js +7 -6
  326. package/dist/src/core/patch-manifest-consistency.js +7 -6
  327. package/dist/src/core/patch-manifest-io.d.ts +6 -3
  328. package/dist/src/core/patch-manifest-io.js +27 -9
  329. package/dist/src/core/patch-manifest-key-order.d.ts +24 -0
  330. package/dist/src/core/patch-manifest-key-order.js +61 -0
  331. package/dist/src/core/patch-manifest-stage.d.ts +46 -0
  332. package/dist/src/core/patch-manifest-stage.js +96 -0
  333. package/dist/src/core/patch-manifest-validate.js +61 -3
  334. package/dist/src/core/patch-order-legal.d.ts +26 -0
  335. package/dist/src/core/patch-order-legal.js +88 -0
  336. package/dist/src/core/patch-policy-content.d.ts +40 -0
  337. package/dist/src/core/patch-policy-content.js +216 -0
  338. package/dist/src/core/patch-policy.d.ts +22 -5
  339. package/dist/src/core/patch-policy.js +26 -12
  340. package/dist/src/core/patch-retired.d.ts +57 -0
  341. package/dist/src/core/patch-retired.js +92 -0
  342. package/dist/src/core/patch-subset.d.ts +16 -0
  343. package/dist/src/core/patch-subset.js +66 -0
  344. package/dist/src/core/preflight-wait.d.ts +20 -0
  345. package/dist/src/core/preflight-wait.js +38 -0
  346. package/dist/src/core/rebase-from-version.d.ts +46 -0
  347. package/dist/src/core/rebase-from-version.js +71 -0
  348. package/dist/src/core/run-log.d.ts +26 -0
  349. package/dist/src/core/run-log.js +106 -4
  350. package/dist/src/core/run-profile.d.ts +7 -0
  351. package/dist/src/core/run-profile.js +7 -1
  352. package/dist/src/core/smoke-patterns.d.ts +5 -0
  353. package/dist/src/core/smoke-patterns.js +13 -0
  354. package/dist/src/core/test-harness-crash.d.ts +19 -1
  355. package/dist/src/core/test-harness-crash.js +8 -9
  356. package/dist/src/core/test-harness-signature.d.ts +33 -0
  357. package/dist/src/core/test-harness-signature.js +23 -1
  358. package/dist/src/core/test-raw-log.d.ts +66 -0
  359. package/dist/src/core/test-raw-log.js +122 -0
  360. package/dist/src/core/test-run-completeness.d.ts +90 -0
  361. package/dist/src/core/test-run-completeness.js +184 -0
  362. package/dist/src/core/test-stale-check.d.ts +27 -1
  363. package/dist/src/core/test-stale-check.js +39 -49
  364. package/dist/src/core/test-stale-exempt.d.ts +40 -0
  365. package/dist/src/core/test-stale-exempt.js +70 -0
  366. package/dist/src/core/test-xpcshell-retry.d.ts +2 -0
  367. package/dist/src/core/test-xpcshell-retry.js +2 -1
  368. package/dist/src/core/token-lint.d.ts +53 -0
  369. package/dist/src/core/token-lint.js +131 -0
  370. package/dist/src/core/token-sibling-sheets.d.ts +46 -0
  371. package/dist/src/core/token-sibling-sheets.js +119 -0
  372. package/dist/src/core/tree-guard.js +5 -0
  373. package/dist/src/core/typecheck-shim.d.ts +6 -2
  374. package/dist/src/core/typecheck-shim.js +205 -4
  375. package/dist/src/core/typecheck.js +3 -1
  376. package/dist/src/index.d.ts +14 -3
  377. package/dist/src/index.js +6 -1
  378. package/dist/src/types/commands/index.d.ts +2 -2
  379. package/dist/src/types/commands/options.d.ts +212 -24
  380. package/dist/src/types/commands/patches.d.ts +118 -1
  381. package/dist/src/types/config.d.ts +264 -0
  382. package/dist/src/types/config.js +9 -0
  383. package/dist/src/types/furnace.d.ts +63 -1
  384. package/dist/src/types/furnace.js +7 -0
  385. package/dist/src/types/index.d.ts +1 -1
  386. package/dist/src/utils/options.d.ts +36 -7
  387. package/dist/src/utils/options.js +94 -8
  388. package/dist/src/utils/path-glob.d.ts +15 -0
  389. package/dist/src/utils/path-glob.js +47 -0
  390. package/dist/src/utils/regex.d.ts +15 -3
  391. package/dist/src/utils/regex.js +37 -16
  392. package/docs/configuration.md +369 -11
  393. package/docs/exit-codes.md +18 -1
  394. package/docs/furnace.md +139 -3
  395. package/docs/lifecycle-invariants.md +65 -30
  396. package/docs/machine-output.md +120 -12
  397. package/docs/patch-workflow.md +458 -22
  398. package/docs/run-logs.md +20 -1
  399. package/docs/testing.md +346 -21
  400. package/package.json +3 -3
@@ -0,0 +1,48 @@
1
+ /**
2
+ * What `patch delete` and `patch retire` do to the files the removed patch
3
+ * claims, shared so both commands say and do the same.
4
+ *
5
+ * A file the patch modifies stays in the engine with its edits, unmanaged.
6
+ * A file the patch creates is different: once the patch is gone nothing
7
+ * owns it, so a created file still on disk turns into an unmanaged file the
8
+ * engine-clean gate reports. The summary names each such file (in the dry
9
+ * run and at the prompt), and `--delete-created` removes them as part of
10
+ * the confirmed operation.
11
+ */
12
+ import type { PatchQueueContext } from '../../core/patch-lint.js';
13
+ import type { PatchMetadata } from '../../types/commands/index.js';
14
+ /** The summary lines for a removal's claimed files, and what to delete. */
15
+ export interface ClaimedFilePlan {
16
+ /** One summary line per claimed file, plus a header. */
17
+ summary: string[];
18
+ /** Created files still on disk that the removal will delete. */
19
+ deleteOnCommit: string[];
20
+ }
21
+ /**
22
+ * Plans the claimed-file half of a removal.
23
+ *
24
+ * @param ctx - Queue context built before the removal
25
+ * @param target - The patch about to leave the queue
26
+ * @param engineDir - Engine directory
27
+ * @param deleteCreated - Whether `--delete-created` was passed
28
+ */
29
+ export declare function planClaimedFiles(ctx: PatchQueueContext, target: PatchMetadata, engineDir: string, deleteCreated: boolean): Promise<ClaimedFilePlan>;
30
+ /**
31
+ * Deletes the created files a confirmed removal planned to delete.
32
+ *
33
+ * @param engineDir - Engine directory
34
+ * @param files - Engine-relative paths from {@link planClaimedFiles}
35
+ */
36
+ export declare function deleteCreatedFiles(engineDir: string, files: readonly string[]): Promise<void>;
37
+ /**
38
+ * Runs a removal's locked commit, inside the engine-session lock as well
39
+ * when it will delete engine files, so no engine-mutating peer runs while
40
+ * created files disappear under it.
41
+ *
42
+ * @param projectRoot - Project root directory
43
+ * @param command - Command name for lock attribution
44
+ * @param plan - The claimed-file plan
45
+ * @param waitLockSeconds - Wait budget for the engine lock
46
+ * @param operation - The patch-directory-locked commit
47
+ */
48
+ export declare function withCreatedFileLock(projectRoot: string, command: string, plan: ClaimedFilePlan, waitLockSeconds: number | undefined, operation: () => Promise<void>): Promise<void>;
@@ -0,0 +1,83 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * What `patch delete` and `patch retire` do to the files the removed patch
4
+ * claims, shared so both commands say and do the same.
5
+ *
6
+ * A file the patch modifies stays in the engine with its edits, unmanaged.
7
+ * A file the patch creates is different: once the patch is gone nothing
8
+ * owns it, so a created file still on disk turns into an unmanaged file the
9
+ * engine-clean gate reports. The summary names each such file (in the dry
10
+ * run and at the prompt), and `--delete-created` removes them as part of
11
+ * the confirmed operation.
12
+ */
13
+ import { join } from 'node:path';
14
+ import { withEngineSessionLock } from '../../core/engine-session-lock.js';
15
+ import { pathExists, removeFile } from '../../utils/fs.js';
16
+ /**
17
+ * Plans the claimed-file half of a removal.
18
+ *
19
+ * @param ctx - Queue context built before the removal
20
+ * @param target - The patch about to leave the queue
21
+ * @param engineDir - Engine directory
22
+ * @param deleteCreated - Whether `--delete-created` was passed
23
+ */
24
+ export async function planClaimedFiles(ctx, target, engineDir, deleteCreated) {
25
+ const created = ctx.entries.find((entry) => entry.filename === target.filename)?.createdFiles;
26
+ const summary = [`files currently claimed by this patch (${target.filesAffected.length}):`];
27
+ const deleteOnCommit = [];
28
+ let lingering = 0;
29
+ for (const file of target.filesAffected) {
30
+ if (created?.has(file) !== true) {
31
+ summary.push(` ${file} → will become unmanaged`);
32
+ continue;
33
+ }
34
+ if (!(await pathExists(join(engineDir, file)))) {
35
+ summary.push(` ${file} → created by this patch; already gone`);
36
+ continue;
37
+ }
38
+ if (deleteCreated) {
39
+ deleteOnCommit.push(file);
40
+ summary.push(` ${file} → created by this patch; will be deleted (--delete-created)`);
41
+ }
42
+ else {
43
+ lingering++;
44
+ summary.push(` ${file} → created by this patch and still on disk: will become unmanaged`);
45
+ }
46
+ }
47
+ if (lingering > 0) {
48
+ summary.push(`${lingering} created file(s) are still on disk. Delete them first, or pass ` +
49
+ '--delete-created to remove them with the patch; otherwise status reports them as unmanaged.');
50
+ }
51
+ return { summary, deleteOnCommit };
52
+ }
53
+ /**
54
+ * Deletes the created files a confirmed removal planned to delete.
55
+ *
56
+ * @param engineDir - Engine directory
57
+ * @param files - Engine-relative paths from {@link planClaimedFiles}
58
+ */
59
+ export async function deleteCreatedFiles(engineDir, files) {
60
+ for (const file of files) {
61
+ await removeFile(join(engineDir, file));
62
+ }
63
+ }
64
+ /**
65
+ * Runs a removal's locked commit, inside the engine-session lock as well
66
+ * when it will delete engine files, so no engine-mutating peer runs while
67
+ * created files disappear under it.
68
+ *
69
+ * @param projectRoot - Project root directory
70
+ * @param command - Command name for lock attribution
71
+ * @param plan - The claimed-file plan
72
+ * @param waitLockSeconds - Wait budget for the engine lock
73
+ * @param operation - The patch-directory-locked commit
74
+ */
75
+ export async function withCreatedFileLock(projectRoot, command, plan, waitLockSeconds, operation) {
76
+ if (plan.deleteOnCommit.length === 0) {
77
+ await operation();
78
+ return;
79
+ }
80
+ await withEngineSessionLock(projectRoot, command, operation, {
81
+ ...(waitLockSeconds !== undefined ? { waitLockSeconds } : {}),
82
+ });
83
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * The dependency scan `patch delete` and `patch retire` share: which later
3
+ * patches import a file the removed patch creates, and which staged
4
+ * declarations name it as their owner. Both commands take the patch out of
5
+ * the queue, so both must refuse and warn on exactly the same evidence.
6
+ */
7
+ import type { ConflictReport } from '../../core/destructive.js';
8
+ import { type PatchQueueContext } from '../../core/patch-lint.js';
9
+ import type { PatchMetadata } from '../../types/commands/index.js';
10
+ /**
11
+ * Lists every later patch site that imports a file `target` creates.
12
+ *
13
+ * Both source-site maps are covered: `newFiles` (files the later patch
14
+ * creates) and `modifiedFileAdditions` (added lines against files that
15
+ * already exist). Scanning only newFiles misses a later patch adding
16
+ * `import "./TargetHelper.sys.mjs"` to an existing file. Uses the shared
17
+ * specifier extractor so dynamic `import()` and
18
+ * `ChromeUtils.defineESModuleGetters` are picked up: the forward-import lint
19
+ * rule already covers those forms, and removal safety must match the same
20
+ * set or it silently drops dependencies.
21
+ *
22
+ * @param baseCtx - Queue context built before the removal
23
+ * @param target - The patch about to leave the queue
24
+ * @param command - Names for the "queue changed" refusal, e.g.
25
+ * `{ noun: 'deletion', verb: 'delete' }`
26
+ * @returns One description per dependent patch
27
+ */
28
+ export declare function collectRemovalDependents(baseCtx: PatchQueueContext, target: PatchMetadata, command: {
29
+ noun: string;
30
+ verb: string;
31
+ }): string[];
32
+ /**
33
+ * Warns once per patch whose staged forward-import names `target` as its
34
+ * owner. The dangling reference also surfaces via cross-patch lint later,
35
+ * but warning here puts the exact cleanup command in front of the operator
36
+ * at decision time.
37
+ *
38
+ * @param baseCtx - Queue context built before the removal
39
+ * @param target - The patch about to leave the queue
40
+ * @param verb - Command name used in the warning ("delete", "retire")
41
+ */
42
+ export declare function warnDanglingStagedOwners(baseCtx: PatchQueueContext, target: PatchMetadata, verb: string): void;
43
+ /**
44
+ * The structural conflict a removal with dependents raises.
45
+ *
46
+ * The wording spells out the runtime impact: `git apply` does not resolve
47
+ * imports and succeeds even when a later patch imports a file the target
48
+ * created, so the queue re-imports cleanly. The breakage surfaces at
49
+ * browser startup when `ChromeUtils.importESModule` cannot locate the
50
+ * deleted module. Operators who plan to re-introduce the imported files
51
+ * (rename, refactor) need to know this is the impact model rather than a
52
+ * patch-application failure.
53
+ *
54
+ * @param dependents - Result of {@link collectRemovalDependents}
55
+ * @param target - The patch about to leave the queue
56
+ */
57
+ export declare function removalConflict(dependents: readonly string[], target: PatchMetadata): ConflictReport | null;
58
+ /**
59
+ * Snapshot of the queue inputs a removal's dependency scan read, compared
60
+ * again under the lock so a change between the prompt and the commit
61
+ * refuses instead of committing an unreviewed removal.
62
+ */
63
+ export declare function queueSnapshot(context: PatchQueueContext): string;
@@ -0,0 +1,133 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * The dependency scan `patch delete` and `patch retire` share: which later
4
+ * patches import a file the removed patch creates, and which staged
5
+ * declarations name it as their owner. Both commands take the patch out of
6
+ * the queue, so both must refuse and warn on exactly the same evidence.
7
+ */
8
+ import { basename } from 'node:path';
9
+ import { extractImportSpecifiersWithLines, findForwardImportIgnoreLines, isForwardImportableFile, } from '../../core/patch-lint.js';
10
+ import { GeneralError } from '../../errors/base.js';
11
+ import { warn } from '../../utils/logger.js';
12
+ /**
13
+ * Lists every later patch site that imports a file `target` creates.
14
+ *
15
+ * Both source-site maps are covered: `newFiles` (files the later patch
16
+ * creates) and `modifiedFileAdditions` (added lines against files that
17
+ * already exist). Scanning only newFiles misses a later patch adding
18
+ * `import "./TargetHelper.sys.mjs"` to an existing file. Uses the shared
19
+ * specifier extractor so dynamic `import()` and
20
+ * `ChromeUtils.defineESModuleGetters` are picked up: the forward-import lint
21
+ * rule already covers those forms, and removal safety must match the same
22
+ * set or it silently drops dependencies.
23
+ *
24
+ * @param baseCtx - Queue context built before the removal
25
+ * @param target - The patch about to leave the queue
26
+ * @param command - Names for the "queue changed" refusal, e.g.
27
+ * `{ noun: 'deletion', verb: 'delete' }`
28
+ * @returns One description per dependent patch
29
+ */
30
+ export function collectRemovalDependents(baseCtx, target, command) {
31
+ const targetEntry = baseCtx.entries.find((e) => e.filename === target.filename);
32
+ if (!targetEntry || JSON.stringify(targetEntry.metadata) !== JSON.stringify(target)) {
33
+ throw new GeneralError(`Patch queue changed while preparing ${command.noun}. Re-run patch ${command.verb} to review the current target.`);
34
+ }
35
+ const targetNewFileLeaves = new Set();
36
+ for (const fullPath of targetEntry.newFiles.keys()) {
37
+ targetNewFileLeaves.add(basename(fullPath));
38
+ }
39
+ const dependents = [];
40
+ const scanSite = (entryFilename, sitePath, content) => {
41
+ if (!isForwardImportableFile(sitePath))
42
+ return false;
43
+ const ignoredLines = findForwardImportIgnoreLines(content);
44
+ const specifiers = extractImportSpecifiersWithLines(content);
45
+ for (const { specifier, line } of specifiers) {
46
+ if (ignoredLines.has(line))
47
+ continue;
48
+ const cleaned = specifier.split(/[?#]/)[0] ?? specifier;
49
+ const leaf = basename(cleaned);
50
+ if (!leaf || !isForwardImportableFile(leaf))
51
+ continue;
52
+ if (targetNewFileLeaves.has(leaf)) {
53
+ dependents.push(`${entryFilename} (${sitePath}) imports "${specifier}" which would be deleted`);
54
+ return true;
55
+ }
56
+ }
57
+ return false;
58
+ };
59
+ for (const entry of baseCtx.entries) {
60
+ if (entry.filename === target.filename)
61
+ continue;
62
+ if (entry.order < target.order)
63
+ continue;
64
+ let matched = false;
65
+ for (const [newFile, content] of entry.newFiles) {
66
+ if (scanSite(entry.filename, newFile, content)) {
67
+ matched = true;
68
+ break;
69
+ }
70
+ }
71
+ if (matched)
72
+ continue;
73
+ for (const [modifiedPath, addedContent] of entry.modifiedFileAdditions) {
74
+ if (scanSite(entry.filename, modifiedPath, addedContent))
75
+ break;
76
+ }
77
+ }
78
+ return dependents;
79
+ }
80
+ /**
81
+ * Warns once per patch whose staged forward-import names `target` as its
82
+ * owner. The dangling reference also surfaces via cross-patch lint later,
83
+ * but warning here puts the exact cleanup command in front of the operator
84
+ * at decision time.
85
+ *
86
+ * @param baseCtx - Queue context built before the removal
87
+ * @param target - The patch about to leave the queue
88
+ * @param verb - Command name used in the warning ("delete", "retire")
89
+ */
90
+ export function warnDanglingStagedOwners(baseCtx, target, verb) {
91
+ const holders = baseCtx.entries.filter((entry) => entry.filename !== target.filename &&
92
+ (entry.metadata?.stagedDependencies?.forwardImports ?? []).some((fi) => fi.owner === target.filename));
93
+ for (const holder of holders) {
94
+ warn(`${holder.filename} declares a staged dependency with owner ${target.filename}; ` +
95
+ `after the ${verb}, update it via "fireforge patch staged-dependency ${holder.filename} --remove ..." ` +
96
+ 'or re-point the owner at the patch that will create the file.');
97
+ }
98
+ }
99
+ /**
100
+ * The structural conflict a removal with dependents raises.
101
+ *
102
+ * The wording spells out the runtime impact: `git apply` does not resolve
103
+ * imports and succeeds even when a later patch imports a file the target
104
+ * created, so the queue re-imports cleanly. The breakage surfaces at
105
+ * browser startup when `ChromeUtils.importESModule` cannot locate the
106
+ * deleted module. Operators who plan to re-introduce the imported files
107
+ * (rename, refactor) need to know this is the impact model rather than a
108
+ * patch-application failure.
109
+ *
110
+ * @param dependents - Result of {@link collectRemovalDependents}
111
+ * @param target - The patch about to leave the queue
112
+ */
113
+ export function removalConflict(dependents, target) {
114
+ if (dependents.length === 0)
115
+ return null;
116
+ return {
117
+ reason: `${dependents.length} later patch(es) contain import statements that reference files created by ${target.filename}. Patch application itself will still succeed, but runtime imports will fail at browser startup until those files are re-introduced.`,
118
+ details: [...dependents],
119
+ };
120
+ }
121
+ /**
122
+ * Snapshot of the queue inputs a removal's dependency scan read, compared
123
+ * again under the lock so a change between the prompt and the commit
124
+ * refuses instead of committing an unreviewed removal.
125
+ */
126
+ export function queueSnapshot(context) {
127
+ return JSON.stringify(context.entries.map(({ filename, order, diff, metadata }) => ({
128
+ filename,
129
+ order,
130
+ diff,
131
+ metadata,
132
+ })));
133
+ }
@@ -26,6 +26,7 @@ import { buildPatchQueueContext, formatPatchLintIssue, lintPatchQueue, } from '.
26
26
  import { withPatchDirectoryLock } from '../../core/patch-lock.js';
27
27
  import { loadPatchesManifest, rewriteStagedDependencyOwners, savePatchesManifest, } from '../../core/patch-manifest.js';
28
28
  import { buildProjectedManifest, enforcePatchPolicy, getPatchPolicyCategories, isCategoryAllowedByConfig, } from '../../core/patch-policy.js';
29
+ import { carryReservedSlots, carryRetiredSlots } from '../../core/patch-retired.js';
29
30
  import { GeneralError, InvalidArgumentError } from '../../errors/base.js';
30
31
  import { toError } from '../../utils/errors.js';
31
32
  import { pathExists } from '../../utils/fs.js';
@@ -115,6 +116,17 @@ async function commitRenameUnderLock(input) {
115
116
  ...(descriptionChanging ? { description: newDescription ?? '' } : {}),
116
117
  };
117
118
  }
119
+ // A rename onto a retired or reserved order fills that slot.
120
+ const retired = carryRetiredSlots(fresh, fresh.patches);
121
+ if (retired === undefined)
122
+ delete fresh.retired;
123
+ else
124
+ fresh.retired = retired;
125
+ const reserved = carryReservedSlots(fresh, fresh.patches);
126
+ if (reserved === undefined)
127
+ delete fresh.reserved;
128
+ else
129
+ fresh.reserved = reserved;
118
130
  enforcePatchPolicy({
119
131
  config: input.config,
120
132
  manifest: buildProjectedManifest(fresh, fresh.patches),
@@ -299,11 +311,18 @@ export async function patchRenameCommand(projectRoot, identifier, options = {})
299
311
  command: 'patch rename',
300
312
  forceUnsafe: options.forceUnsafe === true,
301
313
  });
314
+ // A description-only edit renames nothing and moves no order, so it needs
315
+ // no confirmation: scripts may change a description without --yes.
316
+ const descriptionOnly = descriptionChanging &&
317
+ !filenameChanging &&
318
+ !nameChanging &&
319
+ !orderChanging &&
320
+ !categoryChanging;
302
321
  const decision = await confirmDestructive({
303
322
  operation: 'patch-rename',
304
323
  title: `Rename ${target.filename}`,
305
324
  summary,
306
- yes: options.yes === true,
325
+ yes: options.yes === true || descriptionOnly,
307
326
  dryRun: isDryRun,
308
327
  conflicts: null,
309
328
  });
@@ -0,0 +1,33 @@
1
+ /**
2
+ * `fireforge patch reserve <order>`: holds an order number for a patch a
3
+ * session has agreed to export but has not exported yet.
4
+ *
5
+ * The case it exists for: under `patchPolicy.allowGaps: false`, a session
6
+ * cannot `export --order 571` while a peer has claimed 569 and 570 but not
7
+ * exported them, because the unfilled orders read as a gap. A reservation
8
+ * is a placeholder in `patches.json#reserved` that the gap check counts as
9
+ * occupied and the earliest-legal-order search counts as taken. The export
10
+ * that lands on the order fills it (see `core/patch-retired.ts`), and
11
+ * `--release` drops a reservation whose patch will not come.
12
+ *
13
+ * Metadata-only: no `.patch` file is written and no row is added, so
14
+ * apply, lint and rebase never see a reservation.
15
+ */
16
+ import { Command } from 'commander';
17
+ import type { CommandContext } from '../../types/cli.js';
18
+ import type { PatchReserveOptions } from '../../types/commands/index.js';
19
+ /**
20
+ * Runs the `patch reserve` command.
21
+ *
22
+ * @param projectRoot - Project root directory
23
+ * @param order - Order number to reserve or release
24
+ * @param options - Command options
25
+ */
26
+ export declare function patchReserveCommand(projectRoot: string, order: number, options?: PatchReserveOptions): Promise<void>;
27
+ /**
28
+ * Registers the `patch reserve` subcommand on the `patch` parent.
29
+ *
30
+ * @param parent - Parent Commander command
31
+ * @param context - Shared CLI registration context
32
+ */
33
+ export declare function registerPatchReserve(parent: Command, context: CommandContext): void;
@@ -0,0 +1,152 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * `fireforge patch reserve <order>`: holds an order number for a patch a
4
+ * session has agreed to export but has not exported yet.
5
+ *
6
+ * The case it exists for: under `patchPolicy.allowGaps: false`, a session
7
+ * cannot `export --order 571` while a peer has claimed 569 and 570 but not
8
+ * exported them, because the unfilled orders read as a gap. A reservation
9
+ * is a placeholder in `patches.json#reserved` that the gap check counts as
10
+ * occupied and the earliest-legal-order search counts as taken. The export
11
+ * that lands on the order fills it (see `core/patch-retired.ts`), and
12
+ * `--release` drops a reservation whose patch will not come.
13
+ *
14
+ * Metadata-only: no `.patch` file is written and no row is added, so
15
+ * apply, lint and rebase never see a reservation.
16
+ */
17
+ import { Command } from 'commander';
18
+ import { getProjectPaths, loadConfig } from '../../core/config.js';
19
+ import { appendHistoryBestEffort } from '../../core/history-log.js';
20
+ import { withPatchDirectoryLock } from '../../core/patch-lock.js';
21
+ import { loadPatchesManifestForWrite, savePatchesManifest } from '../../core/patch-manifest.js';
22
+ import { formatPatchOrder } from '../../core/patch-parse.js';
23
+ import { categoryRangeForOrder, categoryRangeLabel, isCategoryAllowedByConfig, } from '../../core/patch-policy.js';
24
+ import { InvalidArgumentError } from '../../errors/base.js';
25
+ import { info, intro, outro } from '../../utils/logger.js';
26
+ import { addWaitLockOption, pickDefined, resolveWaitLockSeconds } from '../../utils/options.js';
27
+ import { parsePositiveIntegerFlag } from '../../utils/validation.js';
28
+ /**
29
+ * Refuses a reservation the manifest or the policy cannot hold: an order a
30
+ * patch, a retired slot or another reservation already takes, or one
31
+ * outside the category's configured ranges.
32
+ */
33
+ function assertReservable(manifest, config, order, category) {
34
+ const label = formatPatchOrder(order);
35
+ const patch = manifest.patches.find((entry) => entry.order === order);
36
+ if (patch !== undefined) {
37
+ throw new InvalidArgumentError(`Order ${label} is taken by ${patch.filename}.`, '<order>');
38
+ }
39
+ if ((manifest.retired ?? []).some((slot) => slot.order === order)) {
40
+ throw new InvalidArgumentError(`Order ${label} is a retired slot; "fireforge export --order ${label}" fills it without a reservation.`, '<order>');
41
+ }
42
+ const held = manifest.reserved?.find((slot) => slot.order === order);
43
+ if (held !== undefined) {
44
+ throw new InvalidArgumentError(`Order ${label} is already reserved by ${held.owner} (since ${held.reservedAt}).`, '<order>');
45
+ }
46
+ const policy = config.patchPolicy;
47
+ if (policy !== undefined && categoryRangeForOrder(policy, category, order) === null) {
48
+ throw new InvalidArgumentError(`Order ${label} is outside the ${category} range (${categoryRangeLabel(policy.ranges, category)}).`, '<order>');
49
+ }
50
+ }
51
+ /**
52
+ * Runs the `patch reserve` command.
53
+ *
54
+ * @param projectRoot - Project root directory
55
+ * @param order - Order number to reserve or release
56
+ * @param options - Command options
57
+ */
58
+ export async function patchReserveCommand(projectRoot, order, options = {}) {
59
+ const releasing = options.release === true;
60
+ const verb = releasing ? 'release' : 'reserve';
61
+ intro(options.dryRun ? `FireForge patch ${verb} (dry run)` : `FireForge patch ${verb}`);
62
+ const config = await loadConfig(projectRoot);
63
+ const label = formatPatchOrder(order);
64
+ let category;
65
+ let owner;
66
+ if (!releasing) {
67
+ category = options.category;
68
+ owner = options.owner?.trim();
69
+ if (category === undefined || owner === undefined || owner === '') {
70
+ throw new InvalidArgumentError('patch reserve requires --category and --owner (or --release to drop a reservation).', [category === undefined ? '--category' : '', owner ? '' : '--owner']
71
+ .filter(Boolean)
72
+ .join(', '));
73
+ }
74
+ if (!isCategoryAllowedByConfig(config, category)) {
75
+ throw new InvalidArgumentError(`Unknown patch category "${category}".`, '--category');
76
+ }
77
+ }
78
+ else if (options.category !== undefined || options.owner !== undefined) {
79
+ throw new InvalidArgumentError('--release takes only the order; --category and --owner apply when reserving.', '--release');
80
+ }
81
+ const { patches: patchesDir } = getProjectPaths(projectRoot);
82
+ await withPatchDirectoryLock(patchesDir, async () => {
83
+ const manifest = (await loadPatchesManifestForWrite(patchesDir)) ?? {
84
+ version: 1,
85
+ patches: [],
86
+ };
87
+ const reserved = manifest.reserved ?? [];
88
+ let slot;
89
+ let next;
90
+ if (releasing) {
91
+ const held = reserved.find((entry) => entry.order === order);
92
+ if (held === undefined) {
93
+ throw new InvalidArgumentError(`Order ${label} is not reserved.`, '<order>');
94
+ }
95
+ slot = held;
96
+ next = reserved.filter((entry) => entry.order !== order);
97
+ }
98
+ else {
99
+ assertReservable(manifest, config, order, category);
100
+ slot = {
101
+ order,
102
+ category: category,
103
+ owner: owner,
104
+ reservedAt: new Date().toISOString(),
105
+ ...(options.reason !== undefined ? { reason: options.reason } : {}),
106
+ };
107
+ next = [...reserved, slot].sort((a, b) => a.order - b.order);
108
+ }
109
+ const summary = releasing
110
+ ? `release order ${label} (reserved by ${slot.owner})`
111
+ : `reserve order ${label} for ${slot.owner} (category: ${slot.category})`;
112
+ if (options.dryRun === true) {
113
+ info(`[dry-run] ${summary}`);
114
+ return;
115
+ }
116
+ if (next.length > 0)
117
+ manifest.reserved = next;
118
+ else
119
+ delete manifest.reserved;
120
+ await savePatchesManifest(patchesDir, manifest);
121
+ await appendHistoryBestEffort(patchesDir, {
122
+ operation: releasing ? 'patch-reserve-release' : 'patch-reserve',
123
+ args: { ...slot },
124
+ result: 'ok',
125
+ }, `patch ${verb} committed (${label})`);
126
+ info(releasing
127
+ ? `Released order ${label}.`
128
+ : `Reserved order ${label} for ${slot.owner}; "fireforge export --order ${label}" fills it.`);
129
+ }, { waitLockSeconds: resolveWaitLockSeconds(options.waitLock), command: `patch ${verb}` });
130
+ outro(options.dryRun ? 'Dry run complete — no changes made' : `Patch ${verb} complete`);
131
+ }
132
+ /**
133
+ * Registers the `patch reserve` subcommand on the `patch` parent.
134
+ *
135
+ * @param parent - Parent Commander command
136
+ * @param context - Shared CLI registration context
137
+ */
138
+ export function registerPatchReserve(parent, context) {
139
+ const { getProjectRoot, withErrorHandling } = context;
140
+ const command = parent
141
+ .command('reserve <order>')
142
+ .description('Hold an order number for a patch not exported yet, so patchPolicy.allowGaps: false ' +
143
+ 'sees no gap; the export that takes the order fills it (metadata only)')
144
+ .option('--category <category>', 'Category the reserved patch will carry')
145
+ .option('--owner <label>', 'Who holds the reservation (a session label or planned filename)')
146
+ .option('--reason <text>', 'Rationale recorded on the reserved slot')
147
+ .option('--release', 'Drop the reservation at <order> instead of creating one')
148
+ .option('--dry-run', 'Show what would change without writing');
149
+ addWaitLockOption(command).action(withErrorHandling(async (rawOrder, options) => {
150
+ await patchReserveCommand(getProjectRoot(), parsePositiveIntegerFlag('<order>', rawOrder), pickDefined(options));
151
+ }));
152
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * `fireforge patch retire <name>`: removes a patch from the queue and keeps
3
+ * its order number spent as a retired slot in `patches.json#retired`.
4
+ *
5
+ * The case it exists for: a rename leaves a patch owning only files that no
6
+ * longer exist. `patch delete` opens a numeric gap that
7
+ * `patchPolicy.allowGaps: false` refuses, and `patch compact` renumbers
8
+ * every later patch, including ones a peer session has in flight. Retiring
9
+ * removes the patch file and row like a delete, and records the order as a
10
+ * placeholder the gap check counts as occupied. Nothing else reads the
11
+ * placeholder (see `core/patch-retired.ts`), and a later
12
+ * `export --order <n>` that takes the number fills it.
13
+ *
14
+ * Shares the delete's dependency refusal, dry-run, confirmation and history
15
+ * contract, so the two differ only in what happens to the order number.
16
+ */
17
+ import { Command } from 'commander';
18
+ import type { CommandContext } from '../../types/cli.js';
19
+ import type { PatchRetireOptions } from '../../types/commands/index.js';
20
+ /**
21
+ * Runs the `patch retire` command.
22
+ *
23
+ * @param projectRoot - Project root directory
24
+ * @param identifier - Patch filename, ordinal, or manifest name to retire
25
+ * @param options - Command options
26
+ */
27
+ export declare function patchRetireCommand(projectRoot: string, identifier: string, options?: PatchRetireOptions): Promise<void>;
28
+ /**
29
+ * Registers the `patch retire` subcommand on the `patch` parent.
30
+ *
31
+ * @param parent - Parent Commander command
32
+ * @param context - Shared CLI registration context
33
+ */
34
+ export declare function registerPatchRetire(parent: Command, context: CommandContext): void;