@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
@@ -1,4 +1,4 @@
1
- import type { PatchesManifest, PatchMetadata } from '../types/commands/index.js';
1
+ import type { PatchesManifest, PatchMetadata, ReExportOptions } from '../types/commands/index.js';
2
2
  import type { ProjectPaths } from '../types/config.js';
3
3
  /**
4
4
  * Shared per-run context for the adjacency advisory: the classification
@@ -18,6 +18,23 @@ export interface AdjacentUnmanagedContext {
18
18
  approvedUnmanaged: ReadonlySet<string>;
19
19
  /** Approved paths actually met this run, so unused ones can be surfaced. */
20
20
  approvedSeen: Set<string>;
21
+ /**
22
+ * Engine-relative paths named by `--expect-new`: files this session
23
+ * created and will adopt with its next export (or `--scan-file`). They
24
+ * pass the belt and are reported, while every other adjacent file still
25
+ * refuses, so a peer's neighbour is not admitted with them.
26
+ */
27
+ expectedNew: ReadonlySet<string>;
28
+ /** `--expect-new` paths actually met this run. */
29
+ expectedNewSeen: Set<string>;
30
+ /**
31
+ * Reviewed `--adjacent-ignore` entries: an engine-relative path, or a
32
+ * glob whose `*` stays within one path segment. Matches are reported in
33
+ * one line and never refuse.
34
+ */
35
+ adjacentIgnore: readonly string[];
36
+ /** `--adjacent-ignore` entries that matched a file this run. */
37
+ adjacentIgnoreSeen: Set<string>;
21
38
  /**
22
39
  * Offenders collected across the loop. `files` stays the bare paths that
23
40
  * downstream consumers already read. `anchored` carries the same entries
@@ -30,6 +47,16 @@ export interface AdjacentUnmanagedContext {
30
47
  anchored: string[];
31
48
  }[];
32
49
  }
50
+ /**
51
+ * Builds the run's adjacency context. The path flags are normalized once
52
+ * here, so the per-file comparison matches git's engine-relative output.
53
+ */
54
+ export declare function buildAdjacentUnmanagedContext(options: ReExportOptions, binaryName: string, furnacePrefixes: ReadonlySet<string>): AdjacentUnmanagedContext;
55
+ /**
56
+ * The one definition of "adjacent", shared by the per-patch notice and the
57
+ * run-level refusal so the two cannot drift apart.
58
+ */
59
+ export declare const ADJACENT_MEANS: string;
33
60
  /** Returns the subset of `files` that no longer exist under `engineDir`. */
34
61
  export declare function findMissingFiles(engineDir: string, files: readonly string[]): Promise<string[]>;
35
62
  /**
@@ -44,8 +71,13 @@ export declare function findMissingFiles(engineDir: string, files: readonly stri
44
71
  export interface AdjacentUnmanagedFile {
45
72
  /** Engine-relative path of the unmanaged file. */
46
73
  file: string;
47
- /** Directory holding at least one file this patch owns. */
74
+ /**
75
+ * Directory holding at least one file this patch owns, or for a
76
+ * registration-named file, the directory of the carrier naming it.
77
+ */
48
78
  anchorDir: string;
79
+ /** The owned carrier whose added registration line names the file. */
80
+ registeredBy?: string;
49
81
  }
50
82
  /**
51
83
  * Emits the scan-less advisory for one patch: missing manifest files and
@@ -61,4 +93,20 @@ export declare function reportAdjacentUnmanagedFiles(args: {
61
93
  manifest: PatchesManifest;
62
94
  currentFilesAffected: readonly string[];
63
95
  ctx: AdjacentUnmanagedContext;
96
+ /**
97
+ * Set beside `--scan`: the scan has already reconciled missing files and
98
+ * adopted what it was asked to, so only the neighbours it left are judged.
99
+ */
100
+ afterScan?: boolean;
64
101
  }): Promise<boolean>;
102
+ /**
103
+ * End-of-run accounting for `--expect-new` and `--adjacent-ignore`.
104
+ *
105
+ * An `--expect-new` path is a promise that the next export adopts it, so a
106
+ * met one is repeated as the export still owed, and an unmet one is a
107
+ * warning: a typo there leaves the refusal armed for the file it meant to
108
+ * admit. An unmatched `--adjacent-ignore` entry is only noted, because a
109
+ * reviewed ignore list is reused across runs and patches, and "not met"
110
+ * is its normal case.
111
+ */
112
+ export declare function reportAdjacentCarveOutUsage(ctx: AdjacentUnmanagedContext): void;
@@ -1,6 +1,6 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  /**
3
- * Scan-less re-export adjacency advisory.
3
+ * Re-export adjacency advisory.
4
4
  *
5
5
  * A plain `re-export` keeps the manifest's filesAffected unchanged, so a
6
6
  * brand-new file an author just created beside a patch's owned files is
@@ -8,7 +8,8 @@
8
8
  * files, filters out tool-managed paths (branding, furnace) so the notice
9
9
  * names only genuinely unmanaged candidates, and, under
10
10
  * `--refuse-adjacent-unmanaged`, lets the caller turn the notice into a
11
- * non-zero refusal.
11
+ * non-zero refusal. Beside `--scan` it judges the files the scan did not
12
+ * adopt, so a targeted `--scan-file` adoption keeps the belt.
12
13
  *
13
14
  * Split out of re-export.ts to keep that file inside the max-lines budget.
14
15
  * Exports helpers consumed by re-export.ts, no registrar.
@@ -21,6 +22,34 @@ import { isGeneratedBrandingPath } from '../core/status-classify.js';
21
22
  import { mapWithConcurrency } from '../utils/concurrency.js';
22
23
  import { pathExists } from '../utils/fs.js';
23
24
  import { info, warn } from '../utils/logger.js';
25
+ import { escapeRegex } from '../utils/regex.js';
26
+ import { findRegisteredChangedFiles } from './re-export-adjacent-registrations.js';
27
+ import { normalizeEngineRelativeInput } from './re-export-scan.js';
28
+ /**
29
+ * Builds the run's adjacency context. The path flags are normalized once
30
+ * here, so the per-file comparison matches git's engine-relative output.
31
+ */
32
+ export function buildAdjacentUnmanagedContext(options, binaryName, furnacePrefixes) {
33
+ const normalized = (flag, values) => (values ?? []).map((value) => normalizeEngineRelativeInput(value, flag));
34
+ return {
35
+ binaryName,
36
+ furnacePrefixes,
37
+ refuseAdjacentUnmanaged: options.refuseAdjacentUnmanaged === true,
38
+ approvedUnmanaged: new Set(normalized('--expect-unmanaged', options.expectUnmanaged)),
39
+ approvedSeen: new Set(),
40
+ expectedNew: new Set(normalized('--expect-new', options.expectNew)),
41
+ expectedNewSeen: new Set(),
42
+ adjacentIgnore: normalized('--adjacent-ignore', options.adjacentIgnore),
43
+ adjacentIgnoreSeen: new Set(),
44
+ refusals: [],
45
+ };
46
+ }
47
+ /**
48
+ * The one definition of "adjacent", shared by the per-patch notice and the
49
+ * run-level refusal so the two cannot drift apart.
50
+ */
51
+ export const ADJACENT_MEANS = 'Adjacent means the file sits in the same directory as a file this patch owns, ' +
52
+ 'or a registration line this patch adds (jar.mn, moz.build, test manifest) names it.';
24
53
  /** Concurrency bound for existence probes (matches the classify/lint pools). */
25
54
  const PATH_PROBE_CONCURRENCY = 8;
26
55
  /** Returns the subset of `files` that no longer exist under `engineDir`. */
@@ -42,14 +71,41 @@ function isToolManagedPath(file, isNewFile, ctx) {
42
71
  }
43
72
  return false;
44
73
  }
74
+ /**
75
+ * Matches an `--adjacent-ignore` entry. A `*` stays inside one path
76
+ * segment, the same narrowness as `buildAudit.unpackaged`: a carve-out that
77
+ * silently spans subtrees is how a reviewed exception becomes a blanket one.
78
+ */
79
+ function matchesIgnoreEntry(entry, file) {
80
+ if (!entry.includes('*'))
81
+ return entry === file;
82
+ const pattern = entry
83
+ .split('*')
84
+ .map((part) => escapeRegex(part))
85
+ .join('[^/]*');
86
+ return new RegExp(`^${pattern}$`).test(file);
87
+ }
88
+ /**
89
+ * Lists the unmanaged files a re-export of this patch would plausibly have
90
+ * to capture: changed files directly in a directory the patch owns a file
91
+ * in, and changed files a registration line the patch adds names. A file in
92
+ * a sibling or nested directory is not adjacent. The directory listing
93
+ * recurses (pathspecs do), so it is filtered back to the exact directories,
94
+ * the same footprint the broad `--scan` adopts from.
95
+ */
45
96
  async function findAdjacentUnmanagedFiles(args) {
46
97
  const { currentFilesAffected, engineDir, manifest, patchFilename, ctx } = args;
47
98
  const parentDirs = [...new Set(currentFilesAffected.map((file) => dirname(file)))];
99
+ const parentDirSet = new Set(parentDirs);
48
100
  const currentSet = new Set(currentFilesAffected);
49
101
  const claimedByOthers = getClaimedFiles(manifest, patchFilename);
50
- // First anchor wins: the dirs are scanned in ownership order and a file
51
- // lives in exactly one of them, so the map is a dedupe, not a choice.
102
+ // First anchor wins: a file lives in exactly one directory, and a
103
+ // directory anchor is the stronger statement than a registration one.
52
104
  const candidates = new Map();
105
+ const isCandidate = (file, isNewFile) => !currentSet.has(file) &&
106
+ !claimedByOthers.has(file) &&
107
+ !candidates.has(file) &&
108
+ !isToolManagedPath(file, isNewFile, ctx);
53
109
  for (const dir of parentDirs) {
54
110
  const [modifiedFiles, untrackedFiles] = await Promise.all([
55
111
  getModifiedFilesInDir(engineDir, dir),
@@ -57,41 +113,80 @@ async function findAdjacentUnmanagedFiles(args) {
57
113
  ]);
58
114
  const untrackedSet = new Set(untrackedFiles);
59
115
  for (const file of [...modifiedFiles, ...untrackedFiles]) {
60
- if (currentSet.has(file) || claimedByOthers.has(file))
116
+ if (!parentDirSet.has(dirname(file)))
61
117
  continue;
62
- if (isToolManagedPath(file, untrackedSet.has(file), ctx))
63
- continue;
64
- if (!candidates.has(file))
65
- candidates.set(file, dir);
118
+ if (isCandidate(file, untrackedSet.has(file))) {
119
+ candidates.set(file, { file, anchorDir: dirname(file) });
120
+ }
121
+ }
122
+ }
123
+ const registered = await findRegisteredChangedFiles(engineDir, currentFilesAffected);
124
+ for (const { file, carrier } of registered) {
125
+ // A registered path is a patch candidate whatever its tracking state,
126
+ // so it is classified as new: only a generated path is tool-managed.
127
+ if (isCandidate(file, true)) {
128
+ candidates.set(file, { file, anchorDir: dirname(carrier), registeredBy: carrier });
66
129
  }
67
130
  }
68
- return [...candidates.entries()]
69
- .map(([file, anchorDir]) => ({ file, anchorDir }))
70
- .sort((a, b) => a.file.localeCompare(b.file));
131
+ return [...candidates.values()].sort((a, b) => a.file.localeCompare(b.file));
132
+ }
133
+ function dispositionOf(file, ctx) {
134
+ if (ctx.approvedUnmanaged.has(file)) {
135
+ ctx.approvedSeen.add(file);
136
+ return 'approved';
137
+ }
138
+ if (ctx.expectedNew.has(file)) {
139
+ ctx.expectedNewSeen.add(file);
140
+ return 'expected-new';
141
+ }
142
+ const ignore = ctx.adjacentIgnore.find((entry) => matchesIgnoreEntry(entry, file));
143
+ if (ignore !== undefined) {
144
+ ctx.adjacentIgnoreSeen.add(ignore);
145
+ return 'ignored';
146
+ }
147
+ return 'refusing';
71
148
  }
72
149
  /**
73
- * Splits the candidates into the ones that refuse and the ones a
74
- * `--expect-unmanaged` carve-out admits. Approved paths are recorded as seen
75
- * so an unused carve-out can be reported, and are returned rather than
76
- * dropped, so the exception stays visible instead of quietly widening.
150
+ * Splits the candidates into the ones that refuse and the ones a carve-out
151
+ * (`--expect-unmanaged`, `--expect-new`, `--adjacent-ignore`) admits. Met
152
+ * carve-outs are recorded so an unused one can be reported, and admitted
153
+ * files are returned rather than dropped, so the exception stays visible
154
+ * instead of quietly widening.
77
155
  */
78
156
  function partitionApproved(files, ctx) {
79
- const refusing = [];
80
- const approved = [];
81
- for (const entry of files) {
82
- if (ctx.approvedUnmanaged.has(entry.file)) {
83
- ctx.approvedSeen.add(entry.file);
84
- approved.push(entry);
85
- }
86
- else {
87
- refusing.push(entry);
88
- }
89
- }
90
- return { refusing, approved };
157
+ const groups = {
158
+ refusing: [],
159
+ approved: [],
160
+ 'expected-new': [],
161
+ ignored: [],
162
+ };
163
+ for (const entry of files)
164
+ groups[dispositionOf(entry.file, ctx)].push(entry);
165
+ return groups;
91
166
  }
92
- /** Renders `<file> (beside engine/<dir>)` for a notice line. */
167
+ /** How a notice names one file's anchor. */
168
+ function describeAnchor(entry) {
169
+ return entry.registeredBy !== undefined
170
+ ? `registered by engine/${entry.registeredBy}`
171
+ : `beside engine/${entry.anchorDir}`;
172
+ }
173
+ /** Renders `<file> (beside engine/<dir>)` or `<file> (registered by engine/<carrier>)`. */
93
174
  function describeAnchored(entry) {
94
- return `${entry.file} (beside engine/${entry.anchorDir})`;
175
+ return `${entry.file} (${describeAnchor(entry)})`;
176
+ }
177
+ /** The admitted-file notice lines, one per carve-out that admitted anything. */
178
+ function reportAdmitted(patchFilename, groups) {
179
+ const notices = [
180
+ [groups.approved, 'admitted by --expect-unmanaged'],
181
+ [groups['expected-new'], 'expected by --expect-new'],
182
+ [groups.ignored, 'ignored by --adjacent-ignore'],
183
+ ];
184
+ for (const [entries, how] of notices) {
185
+ if (entries.length === 0)
186
+ continue;
187
+ info(`${patchFilename}: ${entries.length} adjacent unmanaged file(s) ${how} ` +
188
+ `(${entries.map(describeAnchored).join(', ')}) — reported, not refused.`);
189
+ }
95
190
  }
96
191
  /**
97
192
  * Emits the scan-less advisory for one patch: missing manifest files and
@@ -103,7 +198,7 @@ function describeAnchored(entry) {
103
198
  */
104
199
  export async function reportAdjacentUnmanagedFiles(args) {
105
200
  const { patch, paths, manifest, currentFilesAffected, ctx } = args;
106
- const missingFiles = await findMissingFiles(paths.engine, currentFilesAffected);
201
+ const missingFiles = args.afterScan === true ? [] : await findMissingFiles(paths.engine, currentFilesAffected);
107
202
  if (missingFiles.length > 0) {
108
203
  warn(`${patch.filename}: some files in patches.json no longer exist on disk ` +
109
204
  `(${missingFiles.join(', ')}). Without --scan, re-export keeps the manifest's ` +
@@ -119,20 +214,19 @@ export async function reportAdjacentUnmanagedFiles(args) {
119
214
  patchFilename: patch.filename,
120
215
  ctx,
121
216
  });
122
- const { refusing: unmanagedFiles, approved } = partitionApproved(allUnmanaged, ctx);
123
- if (approved.length > 0) {
124
- info(`${patch.filename}: ${approved.length} adjacent unmanaged file(s) admitted by ` +
125
- `--expect-unmanaged (${approved.map(describeAnchored).join(', ')}) — reported, not refused.`);
126
- }
217
+ const groups = partitionApproved(allUnmanaged, ctx);
218
+ reportAdmitted(patch.filename, groups);
219
+ const unmanagedFiles = groups.refusing;
127
220
  if (unmanagedFiles.length === 0)
128
221
  return false;
129
222
  const firstFew = unmanagedFiles.slice(0, 3).map(describeAnchored).join(', ');
130
223
  const more = unmanagedFiles.length > 3 ? `, +${unmanagedFiles.length - 3} more` : '';
131
- warn(`${patch.filename}: found ${unmanagedFiles.length} unmanaged file(s) adjacent to this patch's ownership (${firstFew}${more}); ` +
132
- `adjacent means the file sits in a directory this patch already owns a file in. ` +
133
- `Plain re-export keeps filesAffected unchanged — use --scan-file to include reviewed files.`);
224
+ warn(`${patch.filename}: found ${unmanagedFiles.length} unmanaged file(s) adjacent to this patch's ownership (${firstFew}${more}). ` +
225
+ `${ADJACENT_MEANS} ` +
226
+ `Plain re-export keeps filesAffected unchanged — use --scan-file to include reviewed files, ` +
227
+ `or --expect-new for a file your next export adopts.`);
134
228
  for (const entry of unmanagedFiles) {
135
- info(` ${entry.file} — beside engine/${entry.anchorDir} — ` +
229
+ info(` ${entry.file} — ${describeAnchor(entry)} — ` +
136
230
  `fireforge re-export ${patch.filename} --scan --scan-file ${entry.file}`);
137
231
  }
138
232
  if (ctx.refuseAdjacentUnmanaged) {
@@ -145,3 +239,29 @@ export async function reportAdjacentUnmanagedFiles(args) {
145
239
  }
146
240
  return false;
147
241
  }
242
+ /**
243
+ * End-of-run accounting for `--expect-new` and `--adjacent-ignore`.
244
+ *
245
+ * An `--expect-new` path is a promise that the next export adopts it, so a
246
+ * met one is repeated as the export still owed, and an unmet one is a
247
+ * warning: a typo there leaves the refusal armed for the file it meant to
248
+ * admit. An unmatched `--adjacent-ignore` entry is only noted, because a
249
+ * reviewed ignore list is reused across runs and patches, and "not met"
250
+ * is its normal case.
251
+ */
252
+ export function reportAdjacentCarveOutUsage(ctx) {
253
+ const unseenNew = [...ctx.expectedNew].filter((file) => !ctx.expectedNewSeen.has(file));
254
+ if (unseenNew.length > 0) {
255
+ warn(`--expect-new path(s) were not met as adjacent unmanaged files this run: ` +
256
+ `${unseenNew.join(', ')}. Check the path for a typo, and that a selected patch owns a ` +
257
+ 'file in its directory or registers it. The refusal stayed armed for everything else.');
258
+ }
259
+ if (ctx.expectedNewSeen.size > 0) {
260
+ info(`--expect-new admitted ${[...ctx.expectedNewSeen].sort().join(', ')}, which no patch owns ` +
261
+ 'yet. Export them next so the queue does not register files it does not ship.');
262
+ }
263
+ const unseenIgnore = ctx.adjacentIgnore.filter((entry) => !ctx.adjacentIgnoreSeen.has(entry));
264
+ if (unseenIgnore.length > 0) {
265
+ info(`--adjacent-ignore entries matched no adjacent file this run: ${unseenIgnore.join(', ')}.`);
266
+ }
267
+ }
@@ -1,4 +1,5 @@
1
1
  import type { PatchesManifest } from '../types/commands/index.js';
2
+ import { type DryRunPurityScope } from './re-export-purity-scope.js';
2
3
  /** Loads and validates a `re-export --scan-files` assignment manifest. */
3
4
  export declare function loadScanFilesAssignments(manifestPath: string, manifest: PatchesManifest): Promise<Map<string, string[]>>;
4
5
  /**
@@ -13,6 +14,6 @@ export declare function loadScanFilesAssignments(manifestPath: string, manifest:
13
14
  * violation surfaces the violation with the original error as `cause`.
14
15
  * No-op outside dry-run.
15
16
  */
16
- export declare function withDryRunPurityGuard<T>(engineDir: string, patchesDir: string, isDryRun: boolean, operation: () => Promise<T>): Promise<T>;
17
+ export declare function withDryRunPurityGuard<T>(engineDir: string, patchesDir: string, isDryRun: boolean, operation: () => Promise<T>, scope?: DryRunPurityScope): Promise<T>;
17
18
  /** Serializes dry-run re-export git inspection against the same project tree. */
18
19
  export declare function withDryRunReExportLock<T>(fireforgeDir: string, isDryRun: boolean, operation: () => Promise<T>): Promise<T>;
@@ -11,6 +11,7 @@ import { getNodeErrorCode, toError } from '../utils/errors.js';
11
11
  import { readText } from '../utils/fs.js';
12
12
  import { sha256Hex } from '../utils/hash.js';
13
13
  import { isObject } from '../utils/validation.js';
14
+ import { describeForeignWriters, fingerprintScopedDryRunState, } from './re-export-purity-scope.js';
14
15
  import { normalizeEngineRelativeInput } from './re-export-scan.js';
15
16
  /** Concurrency bound for patch-body hashing (matches the classify/lint pools). */
16
17
  const PATCH_HASH_CONCURRENCY = 8;
@@ -161,10 +162,14 @@ async function fingerprintDryRunState(engineDir, patchesDir) {
161
162
  * violation surfaces the violation with the original error as `cause`.
162
163
  * No-op outside dry-run.
163
164
  */
164
- export async function withDryRunPurityGuard(engineDir, patchesDir, isDryRun, operation) {
165
+ export async function withDryRunPurityGuard(engineDir, patchesDir, isDryRun, operation, scope) {
165
166
  if (!isDryRun)
166
167
  return operation();
167
- const before = await fingerprintDryRunState(engineDir, patchesDir);
168
+ const fingerprint = () => scope === undefined
169
+ ? fingerprintDryRunState(engineDir, patchesDir)
170
+ : fingerprintScopedDryRunState(engineDir, patchesDir, scope);
171
+ const startedAt = Date.now();
172
+ const before = await fingerprint();
168
173
  let result;
169
174
  let operationError;
170
175
  let failed = false;
@@ -177,7 +182,7 @@ export async function withDryRunPurityGuard(engineDir, patchesDir, isDryRun, ope
177
182
  }
178
183
  let after;
179
184
  try {
180
- after = await fingerprintDryRunState(engineDir, patchesDir);
185
+ after = await fingerprint();
181
186
  }
182
187
  catch (fingerprintError) {
183
188
  // The post-run fingerprint failing must not swallow the dry run's own
@@ -195,10 +200,20 @@ export async function withDryRunPurityGuard(engineDir, patchesDir, isDryRun, ope
195
200
  .sort()
196
201
  .filter((key) => before.get(key) !== after.get(key));
197
202
  if (touched.length > 0) {
198
- throw new GeneralError(`[dry-run] invariant violated: this dry run modified ${touched.join(', ')}. ` +
199
- 'This is a FireForge bug — please report it, and inspect the listed state ' +
200
- '(e.g. `git status` in engine/ and your patches/ diff) before trusting it.' +
201
- (failed ? ` The dry run also failed: ${toError(operationError).message}` : ''), operationError);
203
+ const also = failed ? ` The dry run also failed: ${toError(operationError).message}` : '';
204
+ const writers = await describeForeignWriters(patchesDir, startedAt);
205
+ if (writers.length > 0) {
206
+ throw new GeneralError(`[dry-run] ${touched.join(', ')} changed during this dry run, written by another ` +
207
+ `writer: ${writers.join('; ')}. The preview raced that write; rerun it once the ` +
208
+ `writer is done.${also}`, operationError);
209
+ }
210
+ throw new GeneralError(`[dry-run] invariant violated: ${touched.join(', ')} changed during this dry run. No ` +
211
+ 'other FireForge command recorded a write in that window, so either another writer ' +
212
+ '(an editor, an agent or a git command in engine/) changed it, or this is a FireForge ' +
213
+ 'bug. Rerun the dry run; if it repeats on a quiet tree, please report it, and inspect ' +
214
+ 'the listed state (e.g. `git status` in engine/ and your patches/ diff) before ' +
215
+ 'trusting it.' +
216
+ also, operationError);
202
217
  }
203
218
  if (failed)
204
219
  throw operationError;
@@ -8,7 +8,7 @@
8
8
  * (`re-export-adjacent.ts`) warns about unmanaged neighbour files but is
9
9
  * structurally blind to drift inside owned files. This module compares
10
10
  * the old body against the refreshed body per file and reports the
11
- * payload lines that are about to enter the body. Under
11
+ * payload lines that are about to enter or leave the body. Under
12
12
  * `--refuse-foreign-drift` the patch is skipped and the run exits
13
13
  * non-zero, mirroring `--refuse-adjacent-unmanaged`.
14
14
  *
@@ -41,23 +41,39 @@ export interface ForeignDriftContext {
41
41
  reason: ForeignDriftRefusalReason;
42
42
  }[];
43
43
  }
44
- /** One owned file's foreign drift between the old and refreshed body. */
44
+ /**
45
+ * One owned file's foreign drift between the old and refreshed body,
46
+ * counted as edits to the engine file rather than as body payload, so a
47
+ * deletion-only change is drift like any other.
48
+ */
45
49
  export interface FileForeignDrift {
46
50
  file: string;
47
- /** `+` payload lines in the new body not present in the old body. */
51
+ /**
52
+ * Lines the engine file gained since the old body: `+` payload the new
53
+ * body carries that the old did not, plus `-` payload the old body carried
54
+ * that the new one dropped (an upstream line restored).
55
+ */
48
56
  addedLines: number;
49
- /** `-` payload lines in the new body not present in the old body. */
57
+ /**
58
+ * Lines the engine file lost since the old body: `-` payload the new body
59
+ * carries that the old did not, plus `+` payload the old body carried that
60
+ * the new one dropped (a line the patch added, now deleted).
61
+ */
50
62
  removedLines: number;
51
- /** `@@` headers of new-body hunks carrying foreign lines (capped). */
63
+ /** `@@` headers of the hunks carrying those lines (capped). */
52
64
  hunkSummaries: string[];
53
65
  /** True when a binary section's recorded new-side blob hash changed. */
54
66
  binaryChanged: boolean;
55
67
  }
56
68
  /**
57
69
  * Pure comparison: per owned file, which `+`/`-` payload lines the
58
- * refreshed body carries that the old body did not. Files not in
59
- * `previousFilesAffected` are skipped, since those are intentional
60
- * adoptions (`--scan`) rather than drift.
70
+ * refreshed body carries that the old body did not, and which the old body
71
+ * carried that the refreshed one dropped. Both directions are drift: a
72
+ * concurrent session deleting a line the patch added changes the patch as
73
+ * surely as one adding a line, and a belt that only saw absorbed lines let
74
+ * such a deletion through silently. Files not in `previousFilesAffected`
75
+ * are skipped, since those are intentional adoptions (`--scan`) or
76
+ * retirements (`--expect-removed`) rather than drift.
61
77
  */
62
78
  export declare function computeForeignDrift(oldBody: string, newBody: string, previousFilesAffected: readonly string[]): FileForeignDrift[];
63
79
  /**
@@ -71,6 +87,18 @@ export declare function reportForeignDrift(args: {
71
87
  engineDir: string;
72
88
  newDiffContent: string;
73
89
  ctx: ForeignDriftContext;
90
+ /**
91
+ * The patch's files that the comparison covers. Defaults to its
92
+ * `filesAffected`; the caller narrows it by the `--expect-removed`
93
+ * retirements, which leave the body on purpose.
94
+ */
95
+ ownedFiles?: readonly string[];
96
+ /**
97
+ * Files a `--scan`/`--scan-file` adoption brings in this run. They sit
98
+ * outside the comparison (nothing to compare against) and count as
99
+ * expected, so naming one in `--expect` is not reported as unseen.
100
+ */
101
+ adoptedFiles?: readonly string[];
74
102
  }): Promise<boolean>;
75
103
  /**
76
104
  * Names `--expect` paths that never drifted this run. A typo'd