@hominis/fireforge 0.49.2 → 0.50.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 (334) hide show
  1. package/CHANGELOG.md +40 -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 +28 -0
  10. package/dist/src/commands/check-report.js +78 -0
  11. package/dist/src/commands/check-run.d.ts +28 -0
  12. package/dist/src/commands/check-run.js +104 -0
  13. package/dist/src/commands/check-steps.d.ts +67 -0
  14. package/dist/src/commands/check-steps.js +175 -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/doctor-engine-eslint.js +6 -1
  19. package/dist/src/commands/doctor-source-pin.js +4 -1
  20. package/dist/src/commands/export-adopt-check.d.ts +38 -0
  21. package/dist/src/commands/export-adopt-check.js +98 -0
  22. package/dist/src/commands/export-flow.js +29 -14
  23. package/dist/src/commands/export-placement-gate.d.ts +6 -2
  24. package/dist/src/commands/export-shared.d.ts +2 -1
  25. package/dist/src/commands/export-staged-registration.d.ts +46 -0
  26. package/dist/src/commands/export-staged-registration.js +91 -0
  27. package/dist/src/commands/forward-import-remedy.d.ts +32 -0
  28. package/dist/src/commands/forward-import-remedy.js +63 -0
  29. package/dist/src/commands/furnace/apply.js +3 -3
  30. package/dist/src/commands/furnace/chrome-doc-remove.d.ts +2 -1
  31. package/dist/src/commands/furnace/chrome-doc-remove.js +2 -2
  32. package/dist/src/commands/furnace/chrome-doc.d.ts +2 -1
  33. package/dist/src/commands/furnace/chrome-doc.js +2 -2
  34. package/dist/src/commands/furnace/create.js +2 -2
  35. package/dist/src/commands/furnace/deploy-named.d.ts +48 -0
  36. package/dist/src/commands/furnace/deploy-named.js +107 -0
  37. package/dist/src/commands/furnace/deploy.d.ts +3 -2
  38. package/dist/src/commands/furnace/deploy.js +50 -86
  39. package/dist/src/commands/furnace/diff.js +26 -3
  40. package/dist/src/commands/furnace/index.js +66 -41
  41. package/dist/src/commands/furnace/init.js +13 -6
  42. package/dist/src/commands/furnace/override.js +4 -2
  43. package/dist/src/commands/furnace/preview.js +4 -3
  44. package/dist/src/commands/furnace/refresh.js +2 -2
  45. package/dist/src/commands/furnace/remove.js +4 -3
  46. package/dist/src/commands/furnace/rename-dry-run.d.ts +29 -0
  47. package/dist/src/commands/furnace/rename-dry-run.js +127 -0
  48. package/dist/src/commands/furnace/rename-helpers.d.ts +7 -0
  49. package/dist/src/commands/furnace/rename-helpers.js +15 -1
  50. package/dist/src/commands/furnace/rename-registrations.d.ts +41 -0
  51. package/dist/src/commands/furnace/rename-registrations.js +93 -0
  52. package/dist/src/commands/furnace/rename.d.ts +2 -1
  53. package/dist/src/commands/furnace/rename.js +28 -66
  54. package/dist/src/commands/furnace/scan.d.ts +9 -4
  55. package/dist/src/commands/furnace/scan.js +11 -9
  56. package/dist/src/commands/furnace/sync.js +3 -2
  57. package/dist/src/commands/furnace/validate-report.d.ts +45 -0
  58. package/dist/src/commands/furnace/validate-report.js +91 -0
  59. package/dist/src/commands/furnace/validate.d.ts +1 -1
  60. package/dist/src/commands/furnace/validate.js +127 -114
  61. package/dist/src/commands/furnace/validation-output.js +14 -3
  62. package/dist/src/commands/lint-per-patch.js +31 -60
  63. package/dist/src/commands/lint-report.d.ts +8 -4
  64. package/dist/src/commands/lint-report.js +15 -5
  65. package/dist/src/commands/lint.js +15 -34
  66. package/dist/src/commands/manifest.js +2 -0
  67. package/dist/src/commands/patch/delete-gap-notice.js +3 -1
  68. package/dist/src/commands/patch/delete.d.ts +1 -1
  69. package/dist/src/commands/patch/delete.js +13 -98
  70. package/dist/src/commands/patch/index.d.ts +1 -1
  71. package/dist/src/commands/patch/index.js +4 -2
  72. package/dist/src/commands/patch/lint-ignore.d.ts +5 -1
  73. package/dist/src/commands/patch/lint-ignore.js +65 -28
  74. package/dist/src/commands/patch/move-files-create.js +26 -17
  75. package/dist/src/commands/patch/move-files-into.js +5 -3
  76. package/dist/src/commands/patch/move-files.js +3 -1
  77. package/dist/src/commands/patch/removal-dependents.d.ts +63 -0
  78. package/dist/src/commands/patch/removal-dependents.js +133 -0
  79. package/dist/src/commands/patch/rename.js +7 -0
  80. package/dist/src/commands/patch/retire.d.ts +34 -0
  81. package/dist/src/commands/patch/retire.js +128 -0
  82. package/dist/src/commands/patch/split-plan.d.ts +7 -7
  83. package/dist/src/commands/patch/split-plan.js +45 -28
  84. package/dist/src/commands/patch/split-staged.d.ts +108 -0
  85. package/dist/src/commands/patch/split-staged.js +202 -0
  86. package/dist/src/commands/patch/split.d.ts +5 -0
  87. package/dist/src/commands/patch/split.js +51 -15
  88. package/dist/src/commands/patch/staged-dependency-batch.d.ts +48 -0
  89. package/dist/src/commands/patch/staged-dependency-batch.js +176 -0
  90. package/dist/src/commands/patch/staged-dependency-entries.d.ts +36 -0
  91. package/dist/src/commands/patch/staged-dependency-entries.js +52 -0
  92. package/dist/src/commands/patch/staged-dependency-from.d.ts +14 -0
  93. package/dist/src/commands/patch/staged-dependency-from.js +108 -0
  94. package/dist/src/commands/patch/staged-dependency.js +14 -41
  95. package/dist/src/commands/re-export-adjacent-registrations.d.ts +20 -0
  96. package/dist/src/commands/re-export-adjacent-registrations.js +75 -0
  97. package/dist/src/commands/re-export-adjacent.d.ts +45 -2
  98. package/dist/src/commands/re-export-adjacent.js +156 -37
  99. package/dist/src/commands/re-export-drift.d.ts +36 -8
  100. package/dist/src/commands/re-export-drift.js +73 -49
  101. package/dist/src/commands/re-export-files.js +11 -8
  102. package/dist/src/commands/re-export-lint.d.ts +2 -1
  103. package/dist/src/commands/re-export-lint.js +23 -10
  104. package/dist/src/commands/re-export-options.js +45 -4
  105. package/dist/src/commands/re-export-preflight.d.ts +23 -0
  106. package/dist/src/commands/re-export-preflight.js +160 -0
  107. package/dist/src/commands/re-export-register.js +42 -17
  108. package/dist/src/commands/re-export-removed.d.ts +12 -1
  109. package/dist/src/commands/re-export-removed.js +41 -11
  110. package/dist/src/commands/re-export-scan-owner.d.ts +44 -0
  111. package/dist/src/commands/re-export-scan-owner.js +116 -0
  112. package/dist/src/commands/re-export-scan.d.ts +17 -9
  113. package/dist/src/commands/re-export-scan.js +56 -23
  114. package/dist/src/commands/re-export-single.js +22 -6
  115. package/dist/src/commands/re-export-summary.d.ts +28 -0
  116. package/dist/src/commands/re-export-summary.js +36 -0
  117. package/dist/src/commands/re-export.js +37 -24
  118. package/dist/src/commands/rebase/index.js +17 -17
  119. package/dist/src/commands/run.js +16 -2
  120. package/dist/src/commands/status-check.d.ts +55 -16
  121. package/dist/src/commands/status-check.js +92 -37
  122. package/dist/src/commands/status-json.d.ts +22 -9
  123. package/dist/src/commands/status-json.js +47 -32
  124. package/dist/src/commands/status-output.js +3 -1
  125. package/dist/src/commands/status-scope.d.ts +37 -0
  126. package/dist/src/commands/status-scope.js +54 -0
  127. package/dist/src/commands/status.js +66 -23
  128. package/dist/src/commands/test-browser-preflight.js +19 -3
  129. package/dist/src/commands/test-build-furnace.d.ts +21 -0
  130. package/dist/src/commands/test-build-furnace.js +48 -0
  131. package/dist/src/commands/test-build-phase.js +47 -41
  132. package/dist/src/commands/test-checkjs.js +7 -0
  133. package/dist/src/commands/test-diagnose.js +7 -0
  134. package/dist/src/commands/test-modes.d.ts +16 -1
  135. package/dist/src/commands/test-modes.js +24 -1
  136. package/dist/src/commands/test-multi-verdict.d.ts +61 -0
  137. package/dist/src/commands/test-multi-verdict.js +111 -0
  138. package/dist/src/commands/test-orchestrate.d.ts +43 -0
  139. package/dist/src/commands/test-orchestrate.js +222 -0
  140. package/dist/src/commands/test-perf-scope.d.ts +29 -0
  141. package/dist/src/commands/test-perf-scope.js +83 -0
  142. package/dist/src/commands/test-prelock.d.ts +51 -0
  143. package/dist/src/commands/test-prelock.js +177 -0
  144. package/dist/src/commands/test-register.js +54 -10
  145. package/dist/src/commands/test-run.js +4 -1
  146. package/dist/src/commands/test-stale-gate.js +57 -27
  147. package/dist/src/commands/test-verdict.d.ts +10 -1
  148. package/dist/src/commands/test-verdict.js +28 -1
  149. package/dist/src/commands/test.d.ts +6 -1
  150. package/dist/src/commands/test.js +66 -60
  151. package/dist/src/commands/token-lint.d.ts +14 -0
  152. package/dist/src/commands/token-lint.js +111 -0
  153. package/dist/src/commands/token.js +10 -0
  154. package/dist/src/commands/verify-scope.d.ts +28 -0
  155. package/dist/src/commands/verify-scope.js +67 -0
  156. package/dist/src/commands/verify.d.ts +10 -2
  157. package/dist/src/commands/verify.js +59 -35
  158. package/dist/src/core/build-baseline-types.d.ts +46 -0
  159. package/dist/src/core/build-baseline-validate.js +19 -0
  160. package/dist/src/core/build-baseline.d.ts +5 -1
  161. package/dist/src/core/build-baseline.js +3 -1
  162. package/dist/src/core/build-overwrite-guard.d.ts +50 -0
  163. package/dist/src/core/build-overwrite-guard.js +84 -4
  164. package/dist/src/core/build-prepare.d.ts +7 -0
  165. package/dist/src/core/build-prepare.js +14 -8
  166. package/dist/src/core/build-run-record.d.ts +74 -0
  167. package/dist/src/core/build-run-record.js +136 -0
  168. package/dist/src/core/checkjs-support-heads.d.ts +41 -0
  169. package/dist/src/core/checkjs-support-heads.js +104 -0
  170. package/dist/src/core/config-paths.d.ts +2 -2
  171. package/dist/src/core/config-paths.js +8 -0
  172. package/dist/src/core/config-validate-check.d.ts +8 -0
  173. package/dist/src/core/config-validate-check.js +143 -0
  174. package/dist/src/core/config-validate-file-size.d.ts +9 -6
  175. package/dist/src/core/config-validate-file-size.js +91 -2
  176. package/dist/src/core/config-validate-history.d.ts +11 -0
  177. package/dist/src/core/config-validate-history.js +38 -0
  178. package/dist/src/core/config-validate-patch-policy.js +100 -0
  179. package/dist/src/core/config-validate-test-toolchains.js +7 -0
  180. package/dist/src/core/config-validate-token-scales.d.ts +6 -0
  181. package/dist/src/core/config-validate-token-scales.js +50 -0
  182. package/dist/src/core/config-validate.js +14 -5
  183. package/dist/src/core/config.d.ts +5 -0
  184. package/dist/src/core/config.js +16 -0
  185. package/dist/src/core/coverage-dispatch.d.ts +24 -0
  186. package/dist/src/core/coverage-dispatch.js +44 -0
  187. package/dist/src/core/coverage-extend.d.ts +70 -45
  188. package/dist/src/core/coverage-extend.js +199 -144
  189. package/dist/src/core/coverage-records.d.ts +97 -0
  190. package/dist/src/core/coverage-records.js +350 -0
  191. package/dist/src/core/destructive.d.ts +7 -4
  192. package/dist/src/core/destructive.js +10 -10
  193. package/dist/src/core/engine-session-lock.d.ts +10 -1
  194. package/dist/src/core/engine-session-lock.js +28 -8
  195. package/dist/src/core/export-moved-code-shape.d.ts +49 -11
  196. package/dist/src/core/export-moved-code-shape.js +135 -44
  197. package/dist/src/core/file-lock-queue.d.ts +67 -0
  198. package/dist/src/core/file-lock-queue.js +221 -0
  199. package/dist/src/core/file-lock.d.ts +4 -25
  200. package/dist/src/core/file-lock.js +105 -138
  201. package/dist/src/core/furnace-apply-output.d.ts +10 -0
  202. package/dist/src/core/furnace-apply-output.js +31 -1
  203. package/dist/src/core/furnace-apply.js +12 -2
  204. package/dist/src/core/furnace-composed-source.d.ts +76 -0
  205. package/dist/src/core/furnace-composed-source.js +167 -0
  206. package/dist/src/core/furnace-config-validate-exceptions.d.ts +9 -0
  207. package/dist/src/core/furnace-config-validate-exceptions.js +71 -0
  208. package/dist/src/core/furnace-config-validate.js +8 -0
  209. package/dist/src/core/furnace-css-fragments.d.ts +10 -0
  210. package/dist/src/core/furnace-css-fragments.js +26 -0
  211. package/dist/src/core/furnace-operation.d.ts +3 -3
  212. package/dist/src/core/furnace-registration-remove.d.ts +22 -1
  213. package/dist/src/core/furnace-registration-remove.js +53 -17
  214. package/dist/src/core/furnace-registration-rename.d.ts +26 -0
  215. package/dist/src/core/furnace-registration-rename.js +77 -0
  216. package/dist/src/core/furnace-registration.d.ts +14 -4
  217. package/dist/src/core/furnace-registration.js +44 -10
  218. package/dist/src/core/furnace-staleness.d.ts +7 -0
  219. package/dist/src/core/furnace-staleness.js +34 -24
  220. package/dist/src/core/furnace-validate-accessibility.d.ts +4 -2
  221. package/dist/src/core/furnace-validate-accessibility.js +10 -1
  222. package/dist/src/core/furnace-validate-exceptions.d.ts +40 -0
  223. package/dist/src/core/furnace-validate-exceptions.js +43 -0
  224. package/dist/src/core/furnace-validate-icon-button.d.ts +7 -0
  225. package/dist/src/core/furnace-validate-icon-button.js +49 -0
  226. package/dist/src/core/furnace-validate.js +1 -1
  227. package/dist/src/core/harness-port-fallback.d.ts +60 -0
  228. package/dist/src/core/harness-port-fallback.js +96 -0
  229. package/dist/src/core/history-log-file.d.ts +61 -0
  230. package/dist/src/core/history-log-file.js +157 -0
  231. package/dist/src/core/history-log.d.ts +2 -1
  232. package/dist/src/core/history-log.js +2 -1
  233. package/dist/src/core/json-preserving-edit.d.ts +22 -0
  234. package/dist/src/core/json-preserving-edit.js +199 -0
  235. package/dist/src/core/mach.js +8 -1
  236. package/dist/src/core/negative-control-diff.d.ts +39 -0
  237. package/dist/src/core/negative-control-diff.js +136 -0
  238. package/dist/src/core/negative-control.d.ts +138 -0
  239. package/dist/src/core/negative-control.js +356 -0
  240. package/dist/src/core/objdir-install-manifests.d.ts +73 -0
  241. package/dist/src/core/objdir-install-manifests.js +226 -0
  242. package/dist/src/core/patch-export-update.d.ts +2 -0
  243. package/dist/src/core/patch-export-update.js +1 -0
  244. package/dist/src/core/patch-export.js +3 -4
  245. package/dist/src/core/patch-lint-checkjs-any-import.d.ts +37 -0
  246. package/dist/src/core/patch-lint-checkjs-any-import.js +61 -0
  247. package/dist/src/core/patch-lint-checkjs.js +8 -11
  248. package/dist/src/core/patch-lint-code-lines.d.ts +59 -0
  249. package/dist/src/core/patch-lint-code-lines.js +105 -0
  250. package/dist/src/core/patch-lint-cross.js +12 -4
  251. package/dist/src/core/patch-lint-css.d.ts +8 -2
  252. package/dist/src/core/patch-lint-css.js +10 -4
  253. package/dist/src/core/patch-lint-file-size.d.ts +12 -5
  254. package/dist/src/core/patch-lint-file-size.js +22 -11
  255. package/dist/src/core/patch-lint-forward-registration-extended.d.ts +18 -0
  256. package/dist/src/core/patch-lint-forward-registration-extended.js +35 -10
  257. package/dist/src/core/patch-lint-forward-registration.js +10 -0
  258. package/dist/src/core/patch-lint-ignore.d.ts +152 -0
  259. package/dist/src/core/patch-lint-ignore.js +299 -0
  260. package/dist/src/core/patch-lint-size-thresholds.d.ts +52 -0
  261. package/dist/src/core/patch-lint-size-thresholds.js +110 -0
  262. package/dist/src/core/patch-lint-size.d.ts +117 -0
  263. package/dist/src/core/patch-lint-size.js +323 -0
  264. package/dist/src/core/patch-lint-staged-registration.d.ts +18 -1
  265. package/dist/src/core/patch-lint-staged-registration.js +20 -0
  266. package/dist/src/core/patch-lint-token-scale.d.ts +39 -0
  267. package/dist/src/core/patch-lint-token-scale.js +109 -0
  268. package/dist/src/core/patch-lint.d.ts +11 -83
  269. package/dist/src/core/patch-lint.js +64 -286
  270. package/dist/src/core/patch-lock.js +7 -6
  271. package/dist/src/core/patch-manifest-consistency.js +7 -6
  272. package/dist/src/core/patch-manifest-io.d.ts +6 -3
  273. package/dist/src/core/patch-manifest-io.js +19 -8
  274. package/dist/src/core/patch-manifest-validate.js +34 -2
  275. package/dist/src/core/patch-order-legal.d.ts +25 -0
  276. package/dist/src/core/patch-order-legal.js +83 -0
  277. package/dist/src/core/patch-policy-content.d.ts +40 -0
  278. package/dist/src/core/patch-policy-content.js +216 -0
  279. package/dist/src/core/patch-policy.d.ts +16 -3
  280. package/dist/src/core/patch-policy.js +16 -9
  281. package/dist/src/core/patch-retired.d.ts +35 -0
  282. package/dist/src/core/patch-retired.js +54 -0
  283. package/dist/src/core/patch-subset.d.ts +16 -0
  284. package/dist/src/core/patch-subset.js +66 -0
  285. package/dist/src/core/preflight-wait.d.ts +20 -0
  286. package/dist/src/core/preflight-wait.js +38 -0
  287. package/dist/src/core/rebase-from-version.d.ts +46 -0
  288. package/dist/src/core/rebase-from-version.js +71 -0
  289. package/dist/src/core/run-log.d.ts +20 -0
  290. package/dist/src/core/run-log.js +89 -3
  291. package/dist/src/core/run-profile.d.ts +7 -0
  292. package/dist/src/core/run-profile.js +7 -1
  293. package/dist/src/core/smoke-patterns.d.ts +5 -0
  294. package/dist/src/core/smoke-patterns.js +13 -0
  295. package/dist/src/core/test-harness-crash.d.ts +18 -1
  296. package/dist/src/core/test-harness-crash.js +6 -4
  297. package/dist/src/core/test-harness-signature.d.ts +17 -0
  298. package/dist/src/core/test-harness-signature.js +4 -0
  299. package/dist/src/core/test-run-completeness.d.ts +57 -0
  300. package/dist/src/core/test-run-completeness.js +144 -0
  301. package/dist/src/core/test-stale-check.d.ts +27 -1
  302. package/dist/src/core/test-stale-check.js +39 -49
  303. package/dist/src/core/test-stale-exempt.d.ts +40 -0
  304. package/dist/src/core/test-stale-exempt.js +70 -0
  305. package/dist/src/core/token-lint.d.ts +50 -0
  306. package/dist/src/core/token-lint.js +126 -0
  307. package/dist/src/core/tree-guard.js +3 -0
  308. package/dist/src/core/typecheck-shim.d.ts +1 -1
  309. package/dist/src/core/typecheck-shim.js +90 -2
  310. package/dist/src/index.d.ts +8 -3
  311. package/dist/src/index.js +3 -1
  312. package/dist/src/types/commands/index.d.ts +2 -2
  313. package/dist/src/types/commands/options.d.ts +148 -21
  314. package/dist/src/types/commands/patches.d.ts +75 -1
  315. package/dist/src/types/config.d.ts +192 -0
  316. package/dist/src/types/config.js +9 -0
  317. package/dist/src/types/furnace.d.ts +43 -1
  318. package/dist/src/types/furnace.js +7 -0
  319. package/dist/src/types/index.d.ts +1 -1
  320. package/dist/src/utils/options.d.ts +34 -6
  321. package/dist/src/utils/options.js +86 -5
  322. package/dist/src/utils/path-glob.d.ts +15 -0
  323. package/dist/src/utils/path-glob.js +47 -0
  324. package/dist/src/utils/regex.d.ts +15 -3
  325. package/dist/src/utils/regex.js +37 -16
  326. package/docs/configuration.md +282 -9
  327. package/docs/exit-codes.md +18 -1
  328. package/docs/furnace.md +97 -3
  329. package/docs/lifecycle-invariants.md +52 -19
  330. package/docs/machine-output.md +74 -12
  331. package/docs/patch-workflow.md +297 -22
  332. package/docs/run-logs.md +20 -1
  333. package/docs/testing.md +276 -20
  334. package/package.json +1 -1
@@ -10,6 +10,9 @@ import { GeneralError, InvalidArgumentError } from '../../errors/base.js';
10
10
  import { info, intro, outro } from '../../utils/logger.js';
11
11
  import { addWaitLockOption, resolveWaitLockSeconds } from '../../utils/options.js';
12
12
  import { requirePatchQueue, requirePatchTarget } from './patch-context.js';
13
+ import { stagedDependencyDelta } from './staged-dependency-batch.js';
14
+ import { applyMode, countStagedEntries, dependencyLabel, importView, registrationView, } from './staged-dependency-entries.js';
15
+ import { runBatchAdd } from './staged-dependency-from.js';
13
16
  import { validateStagedDependencyAdd } from './staged-dependency-validate.js';
14
17
  /** Joins the names of the missing flags for error attribution. */
15
18
  function missingFlagList(pairs) {
@@ -146,41 +149,6 @@ function resolveRegistrationRemovalTarget(options, staged) {
146
149
  }
147
150
  return { file, line, creates: '(no match)' };
148
151
  }
149
- function importView(dependency) {
150
- return { ...dependency, matcher: dependency.specifier };
151
- }
152
- function registrationView(dependency) {
153
- return { ...dependency, matcher: dependency.line };
154
- }
155
- function dependencyKey(view) {
156
- return [view.file, view.matcher, view.creates, view.owner ?? ''].join('\0');
157
- }
158
- function dependencyLabel(kind, view) {
159
- const owner = view.owner ? ` owner=${view.owner}` : '';
160
- const reason = view.reason ? ` reason="${view.reason}"` : '';
161
- if (kind === 'import') {
162
- return `${view.file} imports "${view.matcher}" from ${view.creates}${owner}${reason}`;
163
- }
164
- return `${view.file} registers "${view.matcher}" for ${view.creates}${owner}${reason}`;
165
- }
166
- function matchesTarget(view, target) {
167
- return (view.file === target.file &&
168
- view.matcher === target.matcher &&
169
- view.creates === target.creates &&
170
- (target.owner === undefined || view.owner === target.owner));
171
- }
172
- function applyMode(existing, mode, target, toView, addition) {
173
- if (mode === 'clear')
174
- return [];
175
- if (!target)
176
- return [...existing];
177
- if (mode === 'remove')
178
- return existing.filter((entry) => !matchesTarget(toView(entry), target));
179
- const seen = new Set(existing.map((entry) => dependencyKey(toView(entry))));
180
- if (seen.has(dependencyKey(target)) || addition === undefined)
181
- return [...existing];
182
- return [...existing, addition];
183
- }
184
152
  /**
185
153
  * Renders a one-line summary of a staged-dependency metadata change.
186
154
  */
@@ -202,9 +170,6 @@ function describeStagedDependencyChange(beforeCount, afterCount, mode, kind, tar
202
170
  ? `no ${noun} matched: ${dependencyLabel(kind, target)}`
203
171
  : `removed ${beforeCount - afterCount} ${noun} declaration(s): ${dependencyLabel(kind, target)}`;
204
172
  }
205
- function countStagedEntries(staged) {
206
- return (staged?.forwardImports?.length ?? 0) + (staged?.registrations?.length ?? 0);
207
- }
208
173
  /**
209
174
  * Runs the metadata-only staged-dependency mutation command.
210
175
  *
@@ -216,6 +181,10 @@ export async function patchStagedDependencyCommand(projectRoot, identifier, opti
216
181
  const isDryRun = options.dryRun === true;
217
182
  intro(isDryRun ? 'FireForge patch staged-dependency (dry run)' : 'FireForge patch staged-dependency');
218
183
  const mode = modeFromOptions(options);
184
+ if (options.from !== undefined) {
185
+ await runBatchAdd(projectRoot, identifier, mode, options);
186
+ return;
187
+ }
219
188
  const kind = kindFromOptions(options);
220
189
  // The patch queue loads before the declaration fields resolve so
221
190
  // `--remove` can infer `--creates` from the target patch's staged
@@ -281,12 +250,13 @@ export async function patchStagedDependencyCommand(projectRoot, identifier, opti
281
250
  info(`${targetPatch.filename}: ${describeStagedDependencyChange(countStagedEntries(before), countStagedEntries(after), mode, kind, target)}.`);
282
251
  await appendHistoryBestEffort(paths.patches, {
283
252
  operation: 'patch-staged-dependency',
253
+ // Only the delta: the whole list on both sides grew the log
254
+ // quadratically with the list (see staged-dependency-batch.ts).
284
255
  args: {
285
256
  filename: targetPatch.filename,
286
257
  mode,
287
258
  kind,
288
- before: before ?? {},
289
- after: after ?? {},
259
+ ...stagedDependencyDelta(before, after),
290
260
  },
291
261
  ...(options.yes === true ? { yes: true } : {}),
292
262
  result: 'ok',
@@ -313,7 +283,10 @@ export function registerPatchStagedDependency(parent, context) {
313
283
  .option('--line <text>', 'Registration/packaging line as the patch adds it, compared whitespace-trimmed (--kind registration)')
314
284
  .option('--creates <path>', 'Later-created file path relative to engine/')
315
285
  .option('--owner <patch>', 'Exact later patch filename expected to create --creates')
316
- .option('--reason <text>', 'Human-readable rationale stored with the declaration')
286
+ .option('--reason <text>', 'Human-readable rationale stored with the declaration (with --from: the default for entries without one)')
287
+ .option('--from <json-file>', 'Add every declaration in a JSON file under one lock hold (--add only): a "lint --per-patch --report" ' +
288
+ "document (its findings' stagedDeclarations for this patch) or an array of " +
289
+ '{ file, specifier | line, creates, owner?, reason?, patch? } entries')
317
290
  .option('--dry-run', 'Show what would change without writing')
318
291
  .option('-y, --yes', 'Record scripted consent in the destructive-operation history log. This command never prompts; the flag exists for workflow uniformity with commands that do.');
319
292
  addWaitLockOption(command).action(withErrorHandling(async (name, options) => {
@@ -0,0 +1,20 @@
1
+ /** One changed engine file a registration line the patch adds names. */
2
+ export interface RegisteredChangedFile {
3
+ /** Engine-relative path of the named file. */
4
+ file: string;
5
+ /** Engine-relative path of the carrier whose added line names it. */
6
+ carrier: string;
7
+ }
8
+ /**
9
+ * Lists the changed (modified, deleted or untracked) engine files named by
10
+ * the registration lines the patch's owned carriers add relative to HEAD.
11
+ * Only added lines count: an upstream line naming an upstream file is not
12
+ * the patch's registration. A wildcard token is matched against the changed
13
+ * files under its carrier's directory, the only place such a token reaches
14
+ * without the build system.
15
+ *
16
+ * @param engineDir - Engine checkout
17
+ * @param ownedFiles - The patch's current filesAffected
18
+ * @returns Named changed files, first carrier wins, sorted by path
19
+ */
20
+ export declare function findRegisteredChangedFiles(engineDir: string, ownedFiles: readonly string[]): Promise<RegisteredChangedFile[]>;
@@ -0,0 +1,75 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * The registration arm of the scan-less re-export adjacency belt.
4
+ *
5
+ * A file the patch registers (a jar.mn, moz.build or test-manifest line the
6
+ * patch adds names it) belongs with the patch even when it lives in another
7
+ * directory, so an unmanaged one is adjacent in the sense that matters: the
8
+ * refreshed body would ship a registration for a file it leaves out. This
9
+ * module answers which changed engine files those lines name. The caller
10
+ * filters out owned, claimed and tool-managed paths.
11
+ *
12
+ * Split out of re-export-adjacent.ts to keep that module about the
13
+ * classification, not the carrier parsing.
14
+ */
15
+ import { dirname } from 'node:path';
16
+ import { getDiffForFilesAgainstHead } from '../core/git-diff.js';
17
+ import { getDirtyFiles, getModifiedFilesInDir, getUntrackedFilesInDir, } from '../core/git-status.js';
18
+ import { extractAddedLinesPerFile } from '../core/patch-lint-diff.js';
19
+ import { isRegistrationCarrier, resolveCarrierReferences, } from '../core/patch-lint-forward-registration-extended.js';
20
+ import { normalizePathSlashes } from '../utils/paths.js';
21
+ /**
22
+ * Lists the changed (modified, deleted or untracked) engine files named by
23
+ * the registration lines the patch's owned carriers add relative to HEAD.
24
+ * Only added lines count: an upstream line naming an upstream file is not
25
+ * the patch's registration. A wildcard token is matched against the changed
26
+ * files under its carrier's directory, the only place such a token reaches
27
+ * without the build system.
28
+ *
29
+ * @param engineDir - Engine checkout
30
+ * @param ownedFiles - The patch's current filesAffected
31
+ * @returns Named changed files, first carrier wins, sorted by path
32
+ */
33
+ export async function findRegisteredChangedFiles(engineDir, ownedFiles) {
34
+ const carriers = ownedFiles.filter(isRegistrationCarrier);
35
+ if (carriers.length === 0)
36
+ return [];
37
+ const diff = await getDiffForFilesAgainstHead(engineDir, carriers);
38
+ const exactToCarrier = new Map();
39
+ const wildcardsByCarrier = new Map();
40
+ for (const [carrier, lines] of extractAddedLinesPerFile(diff)) {
41
+ for (const ref of resolveCarrierReferences(carrier, lines.join('\n'))) {
42
+ if (ref.exact !== undefined) {
43
+ if (!exactToCarrier.has(ref.exact))
44
+ exactToCarrier.set(ref.exact, carrier);
45
+ }
46
+ else {
47
+ const list = wildcardsByCarrier.get(carrier) ?? [];
48
+ list.push(ref.matches);
49
+ wildcardsByCarrier.set(carrier, list);
50
+ }
51
+ }
52
+ }
53
+ const found = new Map();
54
+ if (exactToCarrier.size > 0) {
55
+ for (const file of await getDirtyFiles(engineDir, [...exactToCarrier.keys()])) {
56
+ const carrier = exactToCarrier.get(file);
57
+ if (carrier !== undefined)
58
+ found.set(file, carrier);
59
+ }
60
+ }
61
+ for (const [carrier, matchers] of wildcardsByCarrier) {
62
+ const scope = normalizePathSlashes(dirname(carrier));
63
+ const [modified, untracked] = await Promise.all([
64
+ getModifiedFilesInDir(engineDir, scope),
65
+ getUntrackedFilesInDir(engineDir, scope),
66
+ ]);
67
+ for (const file of [...modified, ...untracked]) {
68
+ if (!found.has(file) && matchers.some((matches) => matches(file)))
69
+ found.set(file, carrier);
70
+ }
71
+ }
72
+ return [...found.entries()]
73
+ .map(([file, carrier]) => ({ file, carrier }))
74
+ .sort((a, b) => a.file.localeCompare(b.file));
75
+ }
@@ -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
@@ -62,3 +94,14 @@ export declare function reportAdjacentUnmanagedFiles(args: {
62
94
  currentFilesAffected: readonly string[];
63
95
  ctx: AdjacentUnmanagedContext;
64
96
  }): Promise<boolean>;
97
+ /**
98
+ * End-of-run accounting for `--expect-new` and `--adjacent-ignore`.
99
+ *
100
+ * An `--expect-new` path is a promise that the next export adopts it, so a
101
+ * met one is repeated as the export still owed, and an unmet one is a
102
+ * warning: a typo there leaves the refusal armed for the file it meant to
103
+ * admit. An unmatched `--adjacent-ignore` entry is only noted, because a
104
+ * reviewed ignore list is reused across runs and patches, and "not met"
105
+ * is its normal case.
106
+ */
107
+ export declare function reportAdjacentCarveOutUsage(ctx: AdjacentUnmanagedContext): void;
@@ -21,6 +21,34 @@ import { isGeneratedBrandingPath } from '../core/status-classify.js';
21
21
  import { mapWithConcurrency } from '../utils/concurrency.js';
22
22
  import { pathExists } from '../utils/fs.js';
23
23
  import { info, warn } from '../utils/logger.js';
24
+ import { escapeRegex } from '../utils/regex.js';
25
+ import { findRegisteredChangedFiles } from './re-export-adjacent-registrations.js';
26
+ import { normalizeEngineRelativeInput } from './re-export-scan.js';
27
+ /**
28
+ * Builds the run's adjacency context. The path flags are normalized once
29
+ * here, so the per-file comparison matches git's engine-relative output.
30
+ */
31
+ export function buildAdjacentUnmanagedContext(options, binaryName, furnacePrefixes) {
32
+ const normalized = (flag, values) => (values ?? []).map((value) => normalizeEngineRelativeInput(value, flag));
33
+ return {
34
+ binaryName,
35
+ furnacePrefixes,
36
+ refuseAdjacentUnmanaged: options.refuseAdjacentUnmanaged === true,
37
+ approvedUnmanaged: new Set(normalized('--expect-unmanaged', options.expectUnmanaged)),
38
+ approvedSeen: new Set(),
39
+ expectedNew: new Set(normalized('--expect-new', options.expectNew)),
40
+ expectedNewSeen: new Set(),
41
+ adjacentIgnore: normalized('--adjacent-ignore', options.adjacentIgnore),
42
+ adjacentIgnoreSeen: new Set(),
43
+ refusals: [],
44
+ };
45
+ }
46
+ /**
47
+ * The one definition of "adjacent", shared by the per-patch notice and the
48
+ * run-level refusal so the two cannot drift apart.
49
+ */
50
+ export const ADJACENT_MEANS = 'Adjacent means the file sits in the same directory as a file this patch owns, ' +
51
+ 'or a registration line this patch adds (jar.mn, moz.build, test manifest) names it.';
24
52
  /** Concurrency bound for existence probes (matches the classify/lint pools). */
25
53
  const PATH_PROBE_CONCURRENCY = 8;
26
54
  /** Returns the subset of `files` that no longer exist under `engineDir`. */
@@ -42,14 +70,41 @@ function isToolManagedPath(file, isNewFile, ctx) {
42
70
  }
43
71
  return false;
44
72
  }
73
+ /**
74
+ * Matches an `--adjacent-ignore` entry. A `*` stays inside one path
75
+ * segment, the same narrowness as `buildAudit.unpackaged`: a carve-out that
76
+ * silently spans subtrees is how a reviewed exception becomes a blanket one.
77
+ */
78
+ function matchesIgnoreEntry(entry, file) {
79
+ if (!entry.includes('*'))
80
+ return entry === file;
81
+ const pattern = entry
82
+ .split('*')
83
+ .map((part) => escapeRegex(part))
84
+ .join('[^/]*');
85
+ return new RegExp(`^${pattern}$`).test(file);
86
+ }
87
+ /**
88
+ * Lists the unmanaged files a re-export of this patch would plausibly have
89
+ * to capture: changed files directly in a directory the patch owns a file
90
+ * in, and changed files a registration line the patch adds names. A file in
91
+ * a sibling or nested directory is not adjacent. The directory listing
92
+ * recurses (pathspecs do), so it is filtered back to the exact directories,
93
+ * the same footprint the broad `--scan` adopts from.
94
+ */
45
95
  async function findAdjacentUnmanagedFiles(args) {
46
96
  const { currentFilesAffected, engineDir, manifest, patchFilename, ctx } = args;
47
97
  const parentDirs = [...new Set(currentFilesAffected.map((file) => dirname(file)))];
98
+ const parentDirSet = new Set(parentDirs);
48
99
  const currentSet = new Set(currentFilesAffected);
49
100
  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.
101
+ // First anchor wins: a file lives in exactly one directory, and a
102
+ // directory anchor is the stronger statement than a registration one.
52
103
  const candidates = new Map();
104
+ const isCandidate = (file, isNewFile) => !currentSet.has(file) &&
105
+ !claimedByOthers.has(file) &&
106
+ !candidates.has(file) &&
107
+ !isToolManagedPath(file, isNewFile, ctx);
53
108
  for (const dir of parentDirs) {
54
109
  const [modifiedFiles, untrackedFiles] = await Promise.all([
55
110
  getModifiedFilesInDir(engineDir, dir),
@@ -57,41 +112,80 @@ async function findAdjacentUnmanagedFiles(args) {
57
112
  ]);
58
113
  const untrackedSet = new Set(untrackedFiles);
59
114
  for (const file of [...modifiedFiles, ...untrackedFiles]) {
60
- if (currentSet.has(file) || claimedByOthers.has(file))
115
+ if (!parentDirSet.has(dirname(file)))
61
116
  continue;
62
- if (isToolManagedPath(file, untrackedSet.has(file), ctx))
63
- continue;
64
- if (!candidates.has(file))
65
- candidates.set(file, dir);
117
+ if (isCandidate(file, untrackedSet.has(file))) {
118
+ candidates.set(file, { file, anchorDir: dirname(file) });
119
+ }
120
+ }
121
+ }
122
+ const registered = await findRegisteredChangedFiles(engineDir, currentFilesAffected);
123
+ for (const { file, carrier } of registered) {
124
+ // A registered path is a patch candidate whatever its tracking state,
125
+ // so it is classified as new: only a generated path is tool-managed.
126
+ if (isCandidate(file, true)) {
127
+ candidates.set(file, { file, anchorDir: dirname(carrier), registeredBy: carrier });
66
128
  }
67
129
  }
68
- return [...candidates.entries()]
69
- .map(([file, anchorDir]) => ({ file, anchorDir }))
70
- .sort((a, b) => a.file.localeCompare(b.file));
130
+ return [...candidates.values()].sort((a, b) => a.file.localeCompare(b.file));
131
+ }
132
+ function dispositionOf(file, ctx) {
133
+ if (ctx.approvedUnmanaged.has(file)) {
134
+ ctx.approvedSeen.add(file);
135
+ return 'approved';
136
+ }
137
+ if (ctx.expectedNew.has(file)) {
138
+ ctx.expectedNewSeen.add(file);
139
+ return 'expected-new';
140
+ }
141
+ const ignore = ctx.adjacentIgnore.find((entry) => matchesIgnoreEntry(entry, file));
142
+ if (ignore !== undefined) {
143
+ ctx.adjacentIgnoreSeen.add(ignore);
144
+ return 'ignored';
145
+ }
146
+ return 'refusing';
71
147
  }
72
148
  /**
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.
149
+ * Splits the candidates into the ones that refuse and the ones a carve-out
150
+ * (`--expect-unmanaged`, `--expect-new`, `--adjacent-ignore`) admits. Met
151
+ * carve-outs are recorded so an unused one can be reported, and admitted
152
+ * files are returned rather than dropped, so the exception stays visible
153
+ * instead of quietly widening.
77
154
  */
78
155
  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 };
156
+ const groups = {
157
+ refusing: [],
158
+ approved: [],
159
+ 'expected-new': [],
160
+ ignored: [],
161
+ };
162
+ for (const entry of files)
163
+ groups[dispositionOf(entry.file, ctx)].push(entry);
164
+ return groups;
91
165
  }
92
- /** Renders `<file> (beside engine/<dir>)` for a notice line. */
166
+ /** How a notice names one file's anchor. */
167
+ function describeAnchor(entry) {
168
+ return entry.registeredBy !== undefined
169
+ ? `registered by engine/${entry.registeredBy}`
170
+ : `beside engine/${entry.anchorDir}`;
171
+ }
172
+ /** Renders `<file> (beside engine/<dir>)` or `<file> (registered by engine/<carrier>)`. */
93
173
  function describeAnchored(entry) {
94
- return `${entry.file} (beside engine/${entry.anchorDir})`;
174
+ return `${entry.file} (${describeAnchor(entry)})`;
175
+ }
176
+ /** The admitted-file notice lines, one per carve-out that admitted anything. */
177
+ function reportAdmitted(patchFilename, groups) {
178
+ const notices = [
179
+ [groups.approved, 'admitted by --expect-unmanaged'],
180
+ [groups['expected-new'], 'expected by --expect-new'],
181
+ [groups.ignored, 'ignored by --adjacent-ignore'],
182
+ ];
183
+ for (const [entries, how] of notices) {
184
+ if (entries.length === 0)
185
+ continue;
186
+ info(`${patchFilename}: ${entries.length} adjacent unmanaged file(s) ${how} ` +
187
+ `(${entries.map(describeAnchored).join(', ')}) — reported, not refused.`);
188
+ }
95
189
  }
96
190
  /**
97
191
  * Emits the scan-less advisory for one patch: missing manifest files and
@@ -119,20 +213,19 @@ export async function reportAdjacentUnmanagedFiles(args) {
119
213
  patchFilename: patch.filename,
120
214
  ctx,
121
215
  });
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
- }
216
+ const groups = partitionApproved(allUnmanaged, ctx);
217
+ reportAdmitted(patch.filename, groups);
218
+ const unmanagedFiles = groups.refusing;
127
219
  if (unmanagedFiles.length === 0)
128
220
  return false;
129
221
  const firstFew = unmanagedFiles.slice(0, 3).map(describeAnchored).join(', ');
130
222
  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.`);
223
+ warn(`${patch.filename}: found ${unmanagedFiles.length} unmanaged file(s) adjacent to this patch's ownership (${firstFew}${more}). ` +
224
+ `${ADJACENT_MEANS} ` +
225
+ `Plain re-export keeps filesAffected unchanged — use --scan-file to include reviewed files, ` +
226
+ `or --expect-new for a file your next export adopts.`);
134
227
  for (const entry of unmanagedFiles) {
135
- info(` ${entry.file} — beside engine/${entry.anchorDir} — ` +
228
+ info(` ${entry.file} — ${describeAnchor(entry)} — ` +
136
229
  `fireforge re-export ${patch.filename} --scan --scan-file ${entry.file}`);
137
230
  }
138
231
  if (ctx.refuseAdjacentUnmanaged) {
@@ -145,3 +238,29 @@ export async function reportAdjacentUnmanagedFiles(args) {
145
238
  }
146
239
  return false;
147
240
  }
241
+ /**
242
+ * End-of-run accounting for `--expect-new` and `--adjacent-ignore`.
243
+ *
244
+ * An `--expect-new` path is a promise that the next export adopts it, so a
245
+ * met one is repeated as the export still owed, and an unmet one is a
246
+ * warning: a typo there leaves the refusal armed for the file it meant to
247
+ * admit. An unmatched `--adjacent-ignore` entry is only noted, because a
248
+ * reviewed ignore list is reused across runs and patches, and "not met"
249
+ * is its normal case.
250
+ */
251
+ export function reportAdjacentCarveOutUsage(ctx) {
252
+ const unseenNew = [...ctx.expectedNew].filter((file) => !ctx.expectedNewSeen.has(file));
253
+ if (unseenNew.length > 0) {
254
+ warn(`--expect-new path(s) were not met as adjacent unmanaged files this run: ` +
255
+ `${unseenNew.join(', ')}. Check the path for a typo, and that a selected patch owns a ` +
256
+ 'file in its directory or registers it. The refusal stayed armed for everything else.');
257
+ }
258
+ if (ctx.expectedNewSeen.size > 0) {
259
+ info(`--expect-new admitted ${[...ctx.expectedNewSeen].sort().join(', ')}, which no patch owns ` +
260
+ 'yet. Export them next so the queue does not register files it does not ship.');
261
+ }
262
+ const unseenIgnore = ctx.adjacentIgnore.filter((entry) => !ctx.adjacentIgnoreSeen.has(entry));
263
+ if (unseenIgnore.length > 0) {
264
+ info(`--adjacent-ignore entries matched no adjacent file this run: ${unseenIgnore.join(', ')}.`);
265
+ }
266
+ }
@@ -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