@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,226 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Reads the objdir's mozpack install manifests to tell how an engine source
4
+ * reaches the runtime a test runs against.
5
+ *
6
+ * mozbuild installs packaged files from manifests under
7
+ * `obj-*\/_build_manifests/install/` and `obj-*\/faster/install_*`. Each
8
+ * line after the version is a `\x1f`-separated entry: type 1 installs a
9
+ * symlink to an absolute source, 2 copies it, 7 preprocesses it, and 5/6
10
+ * are the pattern forms of 1/2 (`base`, `pattern`, `dest dir`). In a
11
+ * non-Windows dev build most chrome and test files are type-1 symlinks back
12
+ * into `engine/`, so an edit to such a file is already what the runtime
13
+ * reads. `_test_files` is rewritten and installed by `mach test` itself on
14
+ * every dispatch, so test files are restaged when a run starts.
15
+ *
16
+ * Every classification is conservative: an entry whose install base is
17
+ * unknown, a symlink that does not resolve to the edited source, or an
18
+ * unreadable manifest leaves the path classified as packaged.
19
+ */
20
+ import { lstat, readdir, readFile, realpath } from 'node:fs/promises';
21
+ import { join, resolve } from 'node:path';
22
+ import { toError } from '../utils/errors.js';
23
+ import { verbose } from '../utils/logger.js';
24
+ import { normalizePathSlashes } from '../utils/paths.js';
25
+ import { escapeRegex } from '../utils/regex.js';
26
+ /** Install-manifest field separator. */
27
+ const FIELD_SEPARATOR = '\x1f';
28
+ /** mozpack entry types that install a symlink. */
29
+ const SYMLINK_TYPE = '1';
30
+ const PATTERN_SYMLINK_TYPE = '5';
31
+ const PATTERN_COPY_TYPE = '6';
32
+ /** Entry types with no source file (existence markers, inline content). */
33
+ const SOURCELESS_TYPES = new Set(['3', '4', '8']);
34
+ /** The manifest `mach test` writes and installs at every dispatch. */
35
+ const DISPATCH_MANIFEST = '_test_files';
36
+ /** Converts a mozpack pattern (`*`, `**`, `?`) to an anchored RegExp. */
37
+ function patternToRegExp(pattern) {
38
+ if (pattern.split('/').includes('..'))
39
+ return undefined;
40
+ let source = '';
41
+ for (let i = 0; i < pattern.length; i += 1) {
42
+ const char = pattern[i] ?? '';
43
+ if (pattern.startsWith('**/', i)) {
44
+ source += '(?:.*/)?';
45
+ i += 2;
46
+ }
47
+ else if (pattern.startsWith('**', i)) {
48
+ source += '.*';
49
+ i += 1;
50
+ }
51
+ else if (char === '*')
52
+ source += '[^/]*';
53
+ else if (char === '?')
54
+ source += '[^/]';
55
+ else
56
+ source += escapeRegex(char);
57
+ }
58
+ return new RegExp(`^${source}$`);
59
+ }
60
+ function engineRelative(roots, absolute) {
61
+ const normalized = normalizePathSlashes(absolute);
62
+ for (const root of roots) {
63
+ if (normalized.startsWith(`${root}/`))
64
+ return normalized.slice(root.length + 1);
65
+ }
66
+ return undefined;
67
+ }
68
+ /** Install base (objdir-relative) of a `_build_manifests/install/<name>` manifest. */
69
+ function buildManifestBase(name) {
70
+ if (name === DISPATCH_MANIFEST || name === '_tests')
71
+ return '_tests';
72
+ const dist = /^dist_(.+)$/.exec(name);
73
+ return dist ? `dist/${dist[1] ?? ''}` : undefined;
74
+ }
75
+ /** `faster/install_*` manifest name → install base, from `faster/Makefile`. */
76
+ async function fasterManifestBases(objDirPath) {
77
+ const bases = new Map();
78
+ const makefile = await readFile(join(objDirPath, 'faster', 'Makefile'), 'utf8').catch(() => '');
79
+ const line = /^INSTALL_MANIFESTS\s*=(.*)$/m.exec(makefile)?.[1] ?? '';
80
+ for (const base of line.trim().split(/\s+/).filter(Boolean)) {
81
+ bases.set(`install_${base.replaceAll('/', '_')}`, base);
82
+ }
83
+ // `mach build faster` installs a merged copy of the per-directory
84
+ // manifests, rooted at dist/bin.
85
+ bases.set('unified_install_dist_bin', 'dist/bin');
86
+ return bases;
87
+ }
88
+ function parseManifest(index, content, installRoot, atDispatch) {
89
+ for (const line of content.split('\n').slice(1)) {
90
+ const fields = line.split(FIELD_SEPARATOR);
91
+ const [type = '', dest = '', source = ''] = fields;
92
+ if (SOURCELESS_TYPES.has(type) || source === '')
93
+ continue;
94
+ const base = engineRelative(index.engineRoots, source);
95
+ if (base === undefined)
96
+ continue;
97
+ if (type === PATTERN_SYMLINK_TYPE || type === PATTERN_COPY_TYPE) {
98
+ const pattern = patternToRegExp(fields[3] ?? '');
99
+ const destDir = fields[4];
100
+ if (pattern === undefined)
101
+ continue;
102
+ index.patterns.push({
103
+ symlink: type === PATTERN_SYMLINK_TYPE,
104
+ base,
105
+ pattern,
106
+ destDir: installRoot === undefined || destDir === undefined
107
+ ? undefined
108
+ : join(installRoot, destDir),
109
+ atDispatch,
110
+ });
111
+ continue;
112
+ }
113
+ const entries = index.explicit.get(base) ?? [];
114
+ entries.push({
115
+ symlink: type === SYMLINK_TYPE,
116
+ dest: installRoot === undefined ? undefined : join(installRoot, dest),
117
+ atDispatch,
118
+ });
119
+ index.explicit.set(base, entries);
120
+ }
121
+ }
122
+ async function readManifests(dir, baseFor, index, objDirPath) {
123
+ let names;
124
+ try {
125
+ names = await readdir(dir);
126
+ }
127
+ catch {
128
+ return false;
129
+ }
130
+ for (const name of names) {
131
+ if (name.startsWith('.') || name.endsWith('.track') || name === 'Makefile')
132
+ continue;
133
+ const content = await readFile(join(dir, name), 'utf8').catch(() => undefined);
134
+ if (content === undefined)
135
+ continue;
136
+ const base = baseFor(name);
137
+ parseManifest(index, content, base === undefined ? undefined : join(objDirPath, base), name === DISPATCH_MANIFEST);
138
+ }
139
+ return true;
140
+ }
141
+ /**
142
+ * Loads every install manifest of `objDir`. Undefined when neither manifest
143
+ * directory is readable, i.e. nothing can be classified.
144
+ *
145
+ * @param engineDir - Absolute engine directory
146
+ * @param objDir - Objdir name under the engine directory
147
+ */
148
+ export async function loadInstallManifestIndex(engineDir, objDir) {
149
+ const objDirPath = join(engineDir, objDir);
150
+ const roots = [normalizePathSlashes(resolve(engineDir))];
151
+ const real = await realpath(engineDir).catch(() => undefined);
152
+ if (real !== undefined && !roots.includes(normalizePathSlashes(real))) {
153
+ roots.push(normalizePathSlashes(real));
154
+ }
155
+ const index = { explicit: new Map(), patterns: [], engineRoots: roots };
156
+ try {
157
+ const fasterBases = await fasterManifestBases(objDirPath);
158
+ const readBuild = await readManifests(join(objDirPath, '_build_manifests', 'install'), buildManifestBase, index, objDirPath);
159
+ const readFaster = await readManifests(join(objDirPath, 'faster'), (name) => fasterBases.get(name), index, objDirPath);
160
+ return readBuild || readFaster ? index : undefined;
161
+ }
162
+ catch (error) {
163
+ verbose(`Install manifests unreadable under ${objDir}: ${toError(error).message}`);
164
+ return undefined;
165
+ }
166
+ }
167
+ /** True when `dest` is a symlink resolving to the same file as `source`. */
168
+ async function isSymlinkTo(dest, source) {
169
+ if (dest === undefined)
170
+ return false;
171
+ try {
172
+ if (!(await lstat(dest)).isSymbolicLink())
173
+ return false;
174
+ return (await realpath(dest)) === (await realpath(source));
175
+ }
176
+ catch {
177
+ return false;
178
+ }
179
+ }
180
+ /**
181
+ * Classifies how `relPath` reaches the runtime. See {@link RuntimeDelivery}.
182
+ *
183
+ * @param index - Index from {@link loadInstallManifestIndex}
184
+ * @param engineDir - Absolute engine directory
185
+ * @param relPath - Engine-relative POSIX path
186
+ */
187
+ export async function classifyRuntimeDelivery(index, engineDir, relPath) {
188
+ const entries = [...(index.explicit.get(relPath) ?? [])];
189
+ for (const pattern of index.patterns) {
190
+ if (!relPath.startsWith(`${pattern.base}/`))
191
+ continue;
192
+ const rest = relPath.slice(pattern.base.length + 1);
193
+ if (!pattern.pattern.test(rest))
194
+ continue;
195
+ entries.push({
196
+ symlink: pattern.symlink,
197
+ dest: pattern.destDir === undefined ? undefined : join(pattern.destDir, rest),
198
+ atDispatch: pattern.atDispatch,
199
+ });
200
+ }
201
+ if (entries.length === 0)
202
+ return 'unlisted';
203
+ if (entries.every((entry) => entry.atDispatch))
204
+ return 'at-dispatch';
205
+ const source = join(engineDir, relPath);
206
+ for (const entry of entries) {
207
+ if (entry.atDispatch)
208
+ continue;
209
+ if (!entry.symlink || !(await isSymlinkTo(entry.dest, source)))
210
+ return 'packaged';
211
+ }
212
+ return 'live';
213
+ }
214
+ /**
215
+ * True when any install-manifest entry installs something from at or
216
+ * beneath the engine-relative directory `dir`, or a pattern entry's base
217
+ * contains it.
218
+ */
219
+ export function installsFromDirectory(index, dir) {
220
+ const prefix = dir.replace(/\/+$/, '');
221
+ const under = (path) => path === prefix || path.startsWith(`${prefix}/`);
222
+ for (const source of index.explicit.keys())
223
+ if (under(source))
224
+ return true;
225
+ return index.patterns.some((pattern) => under(pattern.base) || prefix.startsWith(`${pattern.base}/`));
226
+ }
@@ -0,0 +1,63 @@
1
+ /** One parked file: both sides, null meaning absent. */
2
+ export interface ParkedFile {
3
+ /** Engine-relative path. */
4
+ path: string;
5
+ /** The file as the session left it. */
6
+ before: string | null;
7
+ /** The file the park wrote in its place. */
8
+ after: string | null;
9
+ }
10
+ /** The record of one park. */
11
+ export interface ParkRecord {
12
+ label: string;
13
+ /** The patch whose files were parked. */
14
+ patch: string;
15
+ createdAt: string;
16
+ /** `baseline` parks every drifted hunk, `hunks` only those a diff named. */
17
+ mode: 'baseline' | 'hunks';
18
+ files: ParkedFile[];
19
+ }
20
+ /** What `park.json` holds: the record without file contents. */
21
+ interface ParkIndex extends Omit<ParkRecord, 'files'> {
22
+ files: {
23
+ path: string;
24
+ before: boolean;
25
+ after: boolean;
26
+ }[];
27
+ }
28
+ /** The outcome of restoring one file. */
29
+ export type RestoreOutcome = 'restored' | 'unchanged' | 'merged' | 'conflict';
30
+ /** Writes a park; refuses to overwrite an existing label. */
31
+ export declare function writePark(projectRoot: string, record: ParkRecord): Promise<string>;
32
+ /** Reads a park back, contents included. */
33
+ export declare function readPark(projectRoot: string, label: string): Promise<ParkRecord>;
34
+ /** Every park of the project, oldest first. */
35
+ export declare function listParks(projectRoot: string): Promise<ParkIndex[]>;
36
+ /** Deletes a park. */
37
+ export declare function dropPark(projectRoot: string, label: string): Promise<void>;
38
+ /** Reads an engine file, null when absent. */
39
+ export declare function readEngineFile(engineDir: string, path: string): Promise<string | null>;
40
+ /** Writes an engine file, removing it for null. */
41
+ export declare function writeEngineFile(engineDir: string, path: string, content: string | null): Promise<void>;
42
+ /**
43
+ * Three-way merges `ours` and `theirs` over `base` with `git merge-file`.
44
+ * Conflicts come back as marker blocks.
45
+ */
46
+ export declare function mergeThreeWay(ours: string, base: string, theirs: string, path: string): Promise<{
47
+ content: string;
48
+ conflicts: number;
49
+ }>;
50
+ /**
51
+ * What restoring one parked file writes: `before` when the file is still
52
+ * as the park left it, nothing when it already matches `before`, and a
53
+ * three-way merge otherwise. Absence only merges with absence.
54
+ */
55
+ export declare function planRestore(file: ParkedFile, now: string | null): Promise<{
56
+ outcome: RestoreOutcome;
57
+ content: string | null;
58
+ }>;
59
+ /** Message ids a Fluent file's lines declare (`id = …`, `-term = …`). */
60
+ export declare function fluentIds(lines: readonly string[]): string[];
61
+ /** Lines `before` has that `after` lacks, as a multiset difference. */
62
+ export declare function removedLines(before: string | null, after: string | null): string[];
63
+ export {};
@@ -0,0 +1,216 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Storage and merge logic behind `fireforge park`.
4
+ *
5
+ * Several sessions edit the same Hominis files on one engine, and a peer's
6
+ * `re-export --refuse-foreign-drift` refuses while another session's hunks
7
+ * sit in its patch's files. Setting those hunks aside meant reversing each
8
+ * one by hand. A park records, for every file of one patch that drifted:
9
+ *
10
+ * - `before`: the file as the session left it (what the park takes away),
11
+ * - `after`: what the park writes in its place (the queue-applied
12
+ * baseline, or the working tree minus the named hunks).
13
+ *
14
+ * Restoring is a three-way merge of the file as it is now (ours) with
15
+ * `before` (theirs) over `after` (base), so the owner's commit in between
16
+ * is kept and the parked hunks come back on top of it.
17
+ *
18
+ * A park lives under `.fireforge/park/<label>/`: `park.json` plus one copy
19
+ * of each side per file. Absent files are recorded as absent, never as
20
+ * empty.
21
+ */
22
+ import { mkdtemp, readdir, rm, writeFile } from 'node:fs/promises';
23
+ import { tmpdir } from 'node:os';
24
+ import { dirname, join } from 'node:path';
25
+ import { InvalidArgumentError } from '../errors/base.js';
26
+ import { getNodeErrorCode } from '../utils/errors.js';
27
+ import { ensureDir, pathExists, readJson, readText, removeFile, writeJson, writeText, } from '../utils/fs.js';
28
+ import { exec } from '../utils/process.js';
29
+ import { FIREFORGE_DIR } from './config-paths.js';
30
+ const LABEL_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
31
+ /** The directory holding every park of a project. */
32
+ function parkRoot(projectRoot) {
33
+ return join(projectRoot, FIREFORGE_DIR, 'park');
34
+ }
35
+ /** Refuses a label that is not a plain file-name segment. */
36
+ function assertParkLabel(label) {
37
+ if (!LABEL_PATTERN.test(label)) {
38
+ throw new InvalidArgumentError(`Park label "${label}" must be letters, digits, ".", "_" or "-", starting with a letter or digit.`, '--label');
39
+ }
40
+ }
41
+ function sideDir(dir, side) {
42
+ return join(dir, side);
43
+ }
44
+ /** Writes a park; refuses to overwrite an existing label. */
45
+ export async function writePark(projectRoot, record) {
46
+ assertParkLabel(record.label);
47
+ const dir = join(parkRoot(projectRoot), record.label);
48
+ if (await pathExists(dir)) {
49
+ throw new InvalidArgumentError(`A park named "${record.label}" already exists. Restore or drop it first, or pass another --label.`, '--label');
50
+ }
51
+ for (const file of record.files) {
52
+ for (const side of ['before', 'after']) {
53
+ const content = file[side];
54
+ if (content === null)
55
+ continue;
56
+ const target = join(sideDir(dir, side), file.path);
57
+ await ensureDir(dirname(target));
58
+ await writeText(target, content);
59
+ }
60
+ }
61
+ const index = {
62
+ label: record.label,
63
+ patch: record.patch,
64
+ createdAt: record.createdAt,
65
+ mode: record.mode,
66
+ files: record.files.map((file) => ({
67
+ path: file.path,
68
+ before: file.before !== null,
69
+ after: file.after !== null,
70
+ })),
71
+ };
72
+ await writeJson(join(dir, 'park.json'), index);
73
+ return dir;
74
+ }
75
+ /** Reads a park back, contents included. */
76
+ export async function readPark(projectRoot, label) {
77
+ assertParkLabel(label);
78
+ const dir = join(parkRoot(projectRoot), label);
79
+ if (!(await pathExists(join(dir, 'park.json')))) {
80
+ throw new InvalidArgumentError(`No park named "${label}". List them with "fireforge park --list".`, label);
81
+ }
82
+ const index = await readJson(join(dir, 'park.json'));
83
+ const files = [];
84
+ for (const entry of index.files) {
85
+ files.push({
86
+ path: entry.path,
87
+ before: entry.before ? await readText(join(sideDir(dir, 'before'), entry.path)) : null,
88
+ after: entry.after ? await readText(join(sideDir(dir, 'after'), entry.path)) : null,
89
+ });
90
+ }
91
+ return { ...index, files };
92
+ }
93
+ /** Every park of the project, oldest first. */
94
+ export async function listParks(projectRoot) {
95
+ let names;
96
+ try {
97
+ names = (await readdir(parkRoot(projectRoot), { withFileTypes: true }))
98
+ .filter((entry) => entry.isDirectory())
99
+ .map((entry) => entry.name);
100
+ }
101
+ catch (error) {
102
+ if (getNodeErrorCode(error) === 'ENOENT')
103
+ return [];
104
+ throw error;
105
+ }
106
+ const parks = [];
107
+ for (const name of names) {
108
+ const path = join(parkRoot(projectRoot), name, 'park.json');
109
+ if (await pathExists(path))
110
+ parks.push(await readJson(path));
111
+ }
112
+ return parks.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
113
+ }
114
+ /** Deletes a park. */
115
+ export async function dropPark(projectRoot, label) {
116
+ assertParkLabel(label);
117
+ const dir = join(parkRoot(projectRoot), label);
118
+ if (!(await pathExists(dir))) {
119
+ throw new InvalidArgumentError(`No park named "${label}".`, label);
120
+ }
121
+ await rm(dir, { recursive: true, force: true });
122
+ }
123
+ /** Reads an engine file, null when absent. */
124
+ export async function readEngineFile(engineDir, path) {
125
+ const full = join(engineDir, path);
126
+ return (await pathExists(full)) ? await readText(full) : null;
127
+ }
128
+ /** Writes an engine file, removing it for null. */
129
+ export async function writeEngineFile(engineDir, path, content) {
130
+ const full = join(engineDir, path);
131
+ if (content === null) {
132
+ if (await pathExists(full))
133
+ await removeFile(full);
134
+ return;
135
+ }
136
+ await ensureDir(dirname(full));
137
+ await writeText(full, content);
138
+ }
139
+ /**
140
+ * Three-way merges `ours` and `theirs` over `base` with `git merge-file`.
141
+ * Conflicts come back as marker blocks.
142
+ */
143
+ export async function mergeThreeWay(ours, base, theirs, path) {
144
+ const scratch = await mkdtemp(join(tmpdir(), 'fireforge-park-'));
145
+ try {
146
+ const files = {
147
+ ours: join(scratch, 'ours'),
148
+ base: join(scratch, 'base'),
149
+ theirs: join(scratch, 'theirs'),
150
+ };
151
+ await writeFile(files.ours, ours);
152
+ await writeFile(files.base, base);
153
+ await writeFile(files.theirs, theirs);
154
+ const result = await exec('git', [
155
+ 'merge-file',
156
+ '-p',
157
+ '-L',
158
+ `${path} (now)`,
159
+ '-L',
160
+ `${path} (parked from)`,
161
+ '-L',
162
+ `${path} (parked)`,
163
+ files.ours,
164
+ files.base,
165
+ files.theirs,
166
+ ]);
167
+ if (result.exitCode < 0 || result.exitCode > 127) {
168
+ throw new Error(`git merge-file failed for ${path}: ${result.stderr.trim()}`);
169
+ }
170
+ return { content: result.stdout, conflicts: result.exitCode };
171
+ }
172
+ finally {
173
+ await rm(scratch, { recursive: true, force: true });
174
+ }
175
+ }
176
+ /**
177
+ * What restoring one parked file writes: `before` when the file is still
178
+ * as the park left it, nothing when it already matches `before`, and a
179
+ * three-way merge otherwise. Absence only merges with absence.
180
+ */
181
+ export async function planRestore(file, now) {
182
+ if (now === file.after)
183
+ return { outcome: 'restored', content: file.before };
184
+ if (now === file.before)
185
+ return { outcome: 'unchanged', content: now };
186
+ if (now === null || file.before === null || file.after === null) {
187
+ return { outcome: 'conflict', content: now };
188
+ }
189
+ const merged = await mergeThreeWay(now, file.after, file.before, file.path);
190
+ return { outcome: merged.conflicts > 0 ? 'conflict' : 'merged', content: merged.content };
191
+ }
192
+ /** Message ids a Fluent file's lines declare (`id = …`, `-term = …`). */
193
+ export function fluentIds(lines) {
194
+ const ids = [];
195
+ for (const line of lines) {
196
+ const match = /^(-?[A-Za-z][\w-]*)\s*=/.exec(line);
197
+ if (match?.[1] !== undefined)
198
+ ids.push(match[1]);
199
+ }
200
+ return ids;
201
+ }
202
+ /** Lines `before` has that `after` lacks, as a multiset difference. */
203
+ export function removedLines(before, after) {
204
+ const remaining = new Map();
205
+ for (const line of (after ?? '').split('\n'))
206
+ remaining.set(line, (remaining.get(line) ?? 0) + 1);
207
+ const removed = [];
208
+ for (const line of (before ?? '').split('\n')) {
209
+ const count = remaining.get(line) ?? 0;
210
+ if (count > 0)
211
+ remaining.set(line, count - 1);
212
+ else
213
+ removed.push(line);
214
+ }
215
+ return removed;
216
+ }
@@ -12,6 +12,8 @@ export interface UpdatePatchPolicyGate {
12
12
  config: FireForgeConfig;
13
13
  command: string;
14
14
  forceUnsafe?: boolean;
15
+ /** Restricts enforcement to these rows' findings (see `enforcePatchPolicy`). */
16
+ scope?: readonly string[];
15
17
  }
16
18
  export interface UpdatePatchAndMetadataOptions {
17
19
  /** Patch directory holding the body and `patches.json`. */
@@ -22,6 +24,8 @@ export interface UpdatePatchAndMetadataOptions {
22
24
  newContent: string;
23
25
  /** Manifest-row fields to merge into the patch's metadata. */
24
26
  updates: Partial<PatchMetadata>;
27
+ /** Manifest-row fields to delete, e.g. a staged block left empty. */
28
+ unset?: readonly (keyof PatchMetadata)[] | undefined;
25
29
  /** Hook run under the lock after the mutation succeeds. */
26
30
  onCommitted?: UpdatePatchCommittedHook | undefined;
27
31
  /** Policy gate run against the under-lock projected manifest. */
@@ -47,3 +51,5 @@ export interface UpdatePatchAndMetadataOptions {
47
51
  * already byte-identical to the new content.
48
52
  */
49
53
  export declare function updatePatchAndMetadata(options: UpdatePatchAndMetadataOptions): Promise<boolean>;
54
+ /** A copy of `row` without `fields`. */
55
+ export declare function withoutFields(row: PatchMetadata, fields: readonly (keyof PatchMetadata)[]): PatchMetadata;
@@ -25,7 +25,7 @@ import { buildProjectedManifest, enforcePatchPolicy } from './patch-policy.js';
25
25
  * already byte-identical to the new content.
26
26
  */
27
27
  export async function updatePatchAndMetadata(options) {
28
- const { patchesDir, filename, newContent, updates, onCommitted, policyGate, lockOptions = {}, } = options;
28
+ const { patchesDir, filename, newContent, updates, unset = [], onCommitted, policyGate, lockOptions = {}, } = options;
29
29
  return withPatchDirectoryLock(patchesDir, async () => {
30
30
  const manifest = await loadPatchesManifestForWrite(patchesDir);
31
31
  if (!manifest) {
@@ -41,13 +41,14 @@ export async function updatePatchAndMetadata(options) {
41
41
  }
42
42
  const originalContent = await readText(patchPath);
43
43
  const existingPatch = manifest.patches[patchIndex];
44
- manifest.patches[patchIndex] = { ...existingPatch, ...updates };
44
+ manifest.patches[patchIndex] = withoutFields({ ...existingPatch, ...updates }, unset);
45
45
  if (policyGate !== undefined) {
46
46
  enforcePatchPolicy({
47
47
  config: policyGate.config,
48
48
  manifest: buildProjectedManifest(manifest, manifest.patches),
49
49
  command: policyGate.command,
50
50
  forceUnsafe: policyGate.forceUnsafe === true,
51
+ ...(policyGate.scope !== undefined ? { scope: policyGate.scope } : {}),
51
52
  });
52
53
  }
53
54
  const normalized = newContent;
@@ -58,7 +59,7 @@ export async function updatePatchAndMetadata(options) {
58
59
  await writeText(patchPath, normalized);
59
60
  patchWritten = true;
60
61
  }
61
- await mutatePatchRowsInManifest(patchesDir, [filename], () => ({ set: updates }));
62
+ await mutatePatchRowsInManifest(patchesDir, [filename], () => ({ set: updates, unset }));
62
63
  }
63
64
  catch (error) {
64
65
  if (patchWritten) {
@@ -83,3 +84,12 @@ export async function updatePatchAndMetadata(options) {
83
84
  return bodyChanged;
84
85
  }, lockOptions);
85
86
  }
87
+ /** A copy of `row` without `fields`. */
88
+ export function withoutFields(row, fields) {
89
+ if (fields.length === 0)
90
+ return row;
91
+ const copy = { ...row };
92
+ for (const field of fields)
93
+ Reflect.deleteProperty(copy, field);
94
+ return copy;
95
+ }
@@ -18,9 +18,10 @@ export declare function buildPlanExportInput(input: PlanExportInput): PlanExport
18
18
  /**
19
19
  * Gets the next patch number for a new patch.
20
20
  * @param patchesDir - Path to the patches directory
21
+ * @param reserved - Orders held by `patch reserve`, skipped
21
22
  * @returns Next patch number (e.g., "005" for 4 existing patches)
22
23
  */
23
- export declare function getNextPatchNumber(patchesDir: string): Promise<string>;
24
+ export declare function getNextPatchNumber(patchesDir: string, reserved?: ReadonlySet<number>): Promise<string>;
24
25
  /**
25
26
  * Sanitizes a human-readable name into a filename slug.
26
27
  *
@@ -59,7 +60,7 @@ export declare function patchNameSlug(name: string, category: string): string;
59
60
  * @param name - Human-readable name
60
61
  * @returns Filename like "001-ui-sidebar.patch"
61
62
  */
62
- export declare function getNextPatchFilename(patchesDir: string, category: PatchCategory, name: string): Promise<string>;
63
+ export declare function getNextPatchFilename(patchesDir: string, category: PatchCategory, name: string, reserved?: ReadonlySet<number>): Promise<string>;
63
64
  export interface CommitExportedPatchInput {
64
65
  patchesDir: string;
65
66
  category: PatchCategory;