@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
@@ -0,0 +1,350 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Per-path packaging-coverage anchors for scoped `fireforge test --build`.
4
+ *
5
+ * A scoped build used to carry one claim anchored to the whole engine, so
6
+ * any edit outside the new scope (a peer's, on a shared engine, nearly
7
+ * always) replaced it. Each staged request path now carries its own anchor
8
+ * restricted to its own staging inputs, and a claim is dropped only when
9
+ * one of those inputs changed.
10
+ *
11
+ * The staging inputs of a request path are its manifest directory (the
12
+ * nearest directory at or above it that holds a test manifest) plus every
13
+ * path a manifest there names outside that directory (`support-files`,
14
+ * `head`, `!/`-rooted shared fixtures). Without the full mozbuild graph
15
+ * that is the closest sound boundary: an edit outside it cannot change
16
+ * what the harness stages for that path.
17
+ */
18
+ import { readdir, readFile, stat } from 'node:fs/promises';
19
+ import { join, posix } from 'node:path';
20
+ import { getNodeErrorCode, toError } from '../utils/errors.js';
21
+ import { sha256Hex } from '../utils/hash.js';
22
+ import { verbose } from '../utils/logger.js';
23
+ import { normalizePathSlashes } from '../utils/paths.js';
24
+ import { DELETED_FILE_FINGERPRINT, } from './build-baseline-types.js';
25
+ import { getHead, isMissingHeadError } from './git.js';
26
+ import { git } from './git-base.js';
27
+ import { getUntrackedFiles } from './git-status.js';
28
+ /**
29
+ * Basenames of the test manifests whose entries decide what a packaged test
30
+ * runtime stages.
31
+ */
32
+ const TEST_MANIFEST_BASENAMES = new Set([
33
+ 'xpcshell.toml',
34
+ 'xpcshell.ini',
35
+ 'browser.toml',
36
+ 'browser.ini',
37
+ 'mochitest.toml',
38
+ 'mochitest.ini',
39
+ 'chrome.toml',
40
+ 'chrome.ini',
41
+ 'a11y.toml',
42
+ 'a11y.ini',
43
+ ]);
44
+ /** Directory entries a directory request's manifest walk visits at most. */
45
+ const MAX_WALK_ENTRIES = 5000;
46
+ /**
47
+ * Hex-encoded SHA-256 of an engine-relative file, matching the digest the
48
+ * baseline writer produces. A missing file hashes to
49
+ * {@link DELETED_FILE_FINGERPRINT}. `undefined` on any other IO error,
50
+ * which every caller treats as "cannot prove unchanged".
51
+ * @param engineDir - Path to the engine directory
52
+ * @param relPath - Engine-relative POSIX path
53
+ */
54
+ export async function hashEngineFile(engineDir, relPath) {
55
+ try {
56
+ return sha256Hex(await readFile(join(engineDir, relPath)));
57
+ }
58
+ catch (error) {
59
+ if (getNodeErrorCode(error) === 'ENOENT')
60
+ return DELETED_FILE_FINGERPRINT;
61
+ verbose(`Coverage anchor: could not hash ${relPath} — ${toError(error).message}`);
62
+ return undefined;
63
+ }
64
+ }
65
+ /** True for a test manifest path (by basename). */
66
+ export function isTestManifestPath(path) {
67
+ return TEST_MANIFEST_BASENAMES.has(posix.basename(path));
68
+ }
69
+ /** Normalizes a request path: forward slashes, no `./`, no trailing slash. */
70
+ export function normalizeRequestPath(path) {
71
+ const normalized = posix.normalize(normalizePathSlashes(path.trim())).replace(/\/+$/, '');
72
+ return normalized === '.' ? '' : normalized;
73
+ }
74
+ /** True when `file` is `prefix` or sits beneath it. The empty prefix is the whole tree. */
75
+ export function isUnderPath(file, prefix) {
76
+ return prefix === '' || file === prefix || file.startsWith(`${prefix}/`);
77
+ }
78
+ /**
79
+ * Reads HEAD and the dirty set. Throws on a git failure other than an
80
+ * unborn HEAD: callers treat that as "nothing can be proven unchanged".
81
+ * @param engineDir - Path to the engine directory
82
+ */
83
+ export async function readEngineContentState(engineDir) {
84
+ let head;
85
+ try {
86
+ head = await getHead(engineDir);
87
+ }
88
+ catch (error) {
89
+ if (!isMissingHeadError(error))
90
+ throw error;
91
+ }
92
+ const dirty = new Set();
93
+ if (head !== undefined) {
94
+ for (const line of (await git(['diff', '--name-only', 'HEAD'], engineDir)).split('\n')) {
95
+ if (line.trim())
96
+ dirty.add(line.trim());
97
+ }
98
+ }
99
+ for (const file of await getUntrackedFiles(engineDir))
100
+ dirty.add(file);
101
+ return { engineDir, head, dirty, hashes: new Map(), diffs: new Map() };
102
+ }
103
+ function liveHash(state, path) {
104
+ let pending = state.hashes.get(path);
105
+ if (pending === undefined) {
106
+ pending = hashEngineFile(state.engineDir, path);
107
+ state.hashes.set(path, pending);
108
+ }
109
+ return pending;
110
+ }
111
+ /** Paths committed between `from` and HEAD, or undefined when `from` cannot be diffed. */
112
+ function committedSince(state, from) {
113
+ let pending = state.diffs.get(from);
114
+ if (pending === undefined) {
115
+ pending = git(['diff', '--name-only', `${from}..HEAD`], state.engineDir).then((output) => new Set(output
116
+ .split('\n')
117
+ .map((line) => line.trim())
118
+ .filter(Boolean)), (error) => {
119
+ verbose(`Coverage anchor: cannot diff ${from}..HEAD — ${toError(error).message}`);
120
+ return undefined;
121
+ });
122
+ state.diffs.set(from, pending);
123
+ }
124
+ return pending;
125
+ }
126
+ /**
127
+ * Paths whose content may differ from the anchor, restricted to `inputs`
128
+ * (undefined: the whole tree) and excluding `rebuilt` paths. Undefined when
129
+ * the anchor cannot be compared at all (no usable HEAD on either side).
130
+ */
131
+ async function candidatePaths(state, anchor, inputs, rebuilt) {
132
+ if (state.head === undefined || anchor.engineHeadSha === '')
133
+ return undefined;
134
+ const inScope = (file) => (inputs === undefined || inputs.some((input) => isUnderPath(file, input))) &&
135
+ !rebuilt.some((path) => isUnderPath(file, path));
136
+ const candidates = new Set();
137
+ if (state.head !== anchor.engineHeadSha) {
138
+ const committed = await committedSince(state, anchor.engineHeadSha);
139
+ if (committed === undefined)
140
+ return undefined;
141
+ for (const file of committed)
142
+ if (inScope(file))
143
+ candidates.add(file);
144
+ }
145
+ for (const file of state.dirty)
146
+ if (inScope(file))
147
+ candidates.add(file);
148
+ for (const file of Object.keys(anchor.fingerprints))
149
+ if (inScope(file))
150
+ candidates.add(file);
151
+ return candidates;
152
+ }
153
+ /**
154
+ * Inputs whose live content differs from what the anchor staged, sorted.
155
+ * Undefined when the anchor cannot be compared, which every caller treats as
156
+ * "changed".
157
+ *
158
+ * @param state - Engine state from {@link readEngineContentState}
159
+ * @param anchor - The claim's anchor
160
+ * @param inputs - Staging inputs to compare, or undefined for the whole tree
161
+ * @param rebuilt - Request paths the current build stages afresh
162
+ */
163
+ export async function findChangedInputs(state, anchor, inputs, rebuilt = []) {
164
+ const candidates = await candidatePaths(state, anchor, inputs, rebuilt);
165
+ if (candidates === undefined)
166
+ return undefined;
167
+ const changed = [];
168
+ for (const file of candidates) {
169
+ const recorded = anchor.fingerprints[file];
170
+ // Not recorded: the staged copy is the anchor HEAD's, and the file is
171
+ // a candidate only because it was committed or dirtied since.
172
+ if (recorded === undefined || (await liveHash(state, file)) !== recorded)
173
+ changed.push(file);
174
+ }
175
+ return changed.sort();
176
+ }
177
+ /**
178
+ * The anchor after the current build restaged `rebuilt`: recorded content
179
+ * for those paths becomes their live content. Undefined when that content
180
+ * cannot be read or the anchor cannot be compared.
181
+ */
182
+ export async function refreshAnchor(state, anchor, rebuilt) {
183
+ const everything = await candidatePaths(state, anchor, undefined, []);
184
+ if (everything === undefined)
185
+ return undefined;
186
+ const restaged = (file) => rebuilt.some((path) => isUnderPath(file, path));
187
+ const fingerprints = {};
188
+ for (const [file, hash] of Object.entries(anchor.fingerprints)) {
189
+ if (!restaged(file))
190
+ fingerprints[file] = hash;
191
+ }
192
+ for (const file of everything) {
193
+ // A restaged file equal to the anchor HEAD's content needs no entry.
194
+ if (!restaged(file) || (state.head === anchor.engineHeadSha && !state.dirty.has(file))) {
195
+ continue;
196
+ }
197
+ const live = await liveHash(state, file);
198
+ if (live === undefined)
199
+ return undefined;
200
+ fingerprints[file] = live;
201
+ }
202
+ return { ...anchor, fingerprints };
203
+ }
204
+ /** Nearest directory at or above `dir` that holds a test manifest, or undefined. */
205
+ async function findManifestDirectory(engineDir, dir) {
206
+ for (let current = dir;; current = posix.dirname(current)) {
207
+ const base = current === '.' ? '' : current;
208
+ try {
209
+ const entries = await readdir(join(engineDir, base));
210
+ if (entries.some((entry) => TEST_MANIFEST_BASENAMES.has(entry)))
211
+ return base;
212
+ }
213
+ catch {
214
+ // A missing directory has no manifest. Keep walking up.
215
+ }
216
+ if (base === '')
217
+ return undefined;
218
+ }
219
+ }
220
+ /** Test manifests at or beneath `dir`, bounded by {@link MAX_WALK_ENTRIES}. */
221
+ async function findManifestsBeneath(engineDir, dir) {
222
+ const manifests = [];
223
+ const pending = [dir];
224
+ let visited = 0;
225
+ while (pending.length > 0 && visited < MAX_WALK_ENTRIES) {
226
+ const current = pending.pop() ?? '';
227
+ let entries;
228
+ try {
229
+ entries = await readdir(join(engineDir, current), { withFileTypes: true });
230
+ }
231
+ catch {
232
+ continue;
233
+ }
234
+ for (const entry of entries) {
235
+ visited += 1;
236
+ const path = current ? `${current}/${entry.name}` : entry.name;
237
+ if (entry.isDirectory())
238
+ pending.push(path);
239
+ else if (TEST_MANIFEST_BASENAMES.has(entry.name))
240
+ manifests.push(path);
241
+ }
242
+ }
243
+ return manifests;
244
+ }
245
+ /**
246
+ * Engine-relative paths a manifest names outside its own directory. Bare
247
+ * names stay inside it. `!/` and `/` prefixes are engine-root relative, as
248
+ * in mozbuild manifests. A glob keeps the directory part before its first
249
+ * wildcard segment.
250
+ */
251
+ export function manifestExternalReferences(manifestPath, text) {
252
+ const dir = posix.dirname(manifestPath);
253
+ const refs = [];
254
+ for (const token of text.match(/[\w.!~@+\-/*?{}]+/g) ?? []) {
255
+ if (!token.includes('/') || token.includes('//'))
256
+ continue;
257
+ const rooted = token.startsWith('!/')
258
+ ? token.slice(2)
259
+ : token.startsWith('/')
260
+ ? token.slice(1)
261
+ : undefined;
262
+ const resolved = rooted === undefined ? posix.join(dir, token) : posix.normalize(rooted);
263
+ const segments = resolved.split('/');
264
+ const wildcard = segments.findIndex((segment) => /[*?{}]/.test(segment));
265
+ const path = normalizeRequestPath(wildcard >= 0 ? segments.slice(0, wildcard).join('/') : resolved);
266
+ if (path === '' || path.startsWith('..') || isUnderPath(path, dir))
267
+ continue;
268
+ refs.push(path);
269
+ }
270
+ return refs;
271
+ }
272
+ /** Drops entries that sit beneath another entry, then sorts. */
273
+ function collapsePaths(paths) {
274
+ const unique = [...new Set(paths)].sort();
275
+ return unique.filter((path) => !unique.some((other) => other !== path && isUnderPath(path, other)));
276
+ }
277
+ /**
278
+ * Staging inputs of one request path. See the module comment.
279
+ * @param engineDir - Path to the engine directory
280
+ * @param requestPath - Engine-relative request path (file or directory)
281
+ */
282
+ export async function collectStagingInputs(engineDir, requestPath) {
283
+ const path = normalizeRequestPath(requestPath);
284
+ let isDirectory = false;
285
+ try {
286
+ isDirectory = (await stat(join(engineDir, path))).isDirectory();
287
+ }
288
+ catch {
289
+ // A missing request path is staged by nothing. Its directory still decides.
290
+ }
291
+ const start = isDirectory ? path : posix.dirname(path);
292
+ const base = await findManifestDirectory(engineDir, start === '.' ? '' : start);
293
+ const fallback = start === '.' || start === '' ? path : start;
294
+ const root = base === undefined || base === '' ? fallback : base;
295
+ const manifests = new Set();
296
+ for (const entry of await readdir(join(engineDir, root)).catch(() => [])) {
297
+ if (TEST_MANIFEST_BASENAMES.has(entry))
298
+ manifests.add(`${root}/${entry}`);
299
+ }
300
+ if (isDirectory) {
301
+ for (const manifest of await findManifestsBeneath(engineDir, path))
302
+ manifests.add(manifest);
303
+ }
304
+ const inputs = [root, path];
305
+ for (const manifest of manifests) {
306
+ try {
307
+ const text = await readFile(join(engineDir, manifest), 'utf8');
308
+ inputs.push(...manifestExternalReferences(manifest, text));
309
+ }
310
+ catch (error) {
311
+ verbose(`Coverage anchor: cannot read ${manifest} — ${toError(error).message}`);
312
+ }
313
+ }
314
+ return collapsePaths(inputs);
315
+ }
316
+ /**
317
+ * Records the anchor of a request path this build staged. Undefined when
318
+ * HEAD is unborn or an input's content cannot be read: the path is still
319
+ * claimed, and later retention falls back to the baseline-wide anchor.
320
+ *
321
+ * @param state - Engine state after the build
322
+ * @param requestPath - Engine-relative request path
323
+ * @param mozconfigHash - Hash of the mozconfig the build configured with
324
+ */
325
+ export async function createCoverageRecord(state, requestPath, mozconfigHash) {
326
+ if (state.head === undefined)
327
+ return undefined;
328
+ const inputs = await collectStagingInputs(state.engineDir, requestPath);
329
+ return narrowAnchor(state, { engineHeadSha: state.head, mozconfigHash, fingerprints: {} }, inputs, [...state.dirty]);
330
+ }
331
+ /**
332
+ * Restricts an anchor to `inputs`. `extraDirty` adds paths whose live content
333
+ * becomes the recorded one (the dirty set of a record being created).
334
+ */
335
+ export async function narrowAnchor(state, anchor, inputs, extraDirty = []) {
336
+ const fingerprints = {};
337
+ for (const [file, hash] of Object.entries(anchor.fingerprints)) {
338
+ if (inputs.some((input) => isUnderPath(file, input)))
339
+ fingerprints[file] = hash;
340
+ }
341
+ for (const file of extraDirty) {
342
+ if (!inputs.some((input) => isUnderPath(file, input)))
343
+ continue;
344
+ const live = await liveHash(state, file);
345
+ if (live === undefined)
346
+ return undefined;
347
+ fingerprints[file] = live;
348
+ }
349
+ return { ...anchor, fingerprints, inputs };
350
+ }
@@ -11,8 +11,6 @@
11
11
  * forward-import that later-patch lint would then block). Without a single
12
12
  * helper, every new destructive command would re-implement this and drift.
13
13
  */
14
- /** Filename of the audit log, relative to the patches directory. */
15
- export declare const HISTORY_LOG_FILENAME = ".fireforge-history.jsonl";
16
14
  /**
17
15
  * A structural conflict that must block the operation even under `--force`.
18
16
  *
@@ -128,12 +126,17 @@ export declare function assertConfirmationAvailable(operation: string, options:
128
126
  */
129
127
  export declare function confirmDestructive(input: DestructiveOpInput): Promise<DestructiveOpResult>;
130
128
  /**
131
- * Appends a single JSONL record to `patches/.fireforge-history.jsonl`.
129
+ * Appends a single JSONL record to the project's patch history log
130
+ * (`.fireforge/history.jsonl` unless `fireforge.json#history.path` says
131
+ * otherwise; a log at the pre-0.50 `patches/.fireforge-history.jsonl` is
132
+ * moved there first).
132
133
  *
133
134
  * Call order matters: append only after the mutation succeeds, never
134
135
  * pre-emptively, so rolled-back failures do not leave ghost entries. The log
135
136
  * is append-only and advisory: no code path reads it back. It exists so
136
- * operators have a post-hoc audit trail when something goes wrong.
137
+ * operators have a post-hoc audit trail when something goes wrong. Every
138
+ * record carries the writing invocation's id (and `FIREFORGE_SESSION_ID`
139
+ * when set), so a session sharing the checkout can pick out its own lines.
137
140
  *
138
141
  * @param patchesDir - Path to the patches directory
139
142
  * @param entry - Serializable history record
@@ -12,14 +12,10 @@
12
12
  * forward-import that later-patch lint would then block). Without a single
13
13
  * helper, every new destructive command would re-implement this and drift.
14
14
  */
15
- import { appendFile } from 'node:fs/promises';
16
- import { join } from 'node:path';
17
15
  import { confirm } from '@clack/prompts';
18
16
  import { CancellationError, InvalidArgumentError } from '../errors/base.js';
19
- import { ensureDir } from '../utils/fs.js';
20
17
  import { info, isCancel, warn } from '../utils/logger.js';
21
- /** Filename of the audit log, relative to the patches directory. */
22
- export const HISTORY_LOG_FILENAME = '.fireforge-history.jsonl';
18
+ import { appendHistoryLine, historyRecordIdentity } from './history-log-file.js';
23
19
  /**
24
20
  * Prints the change summary lines at warn-severity so the prompt stands out.
25
21
  */
@@ -161,22 +157,26 @@ export async function confirmDestructive(input) {
161
157
  return 'proceed';
162
158
  }
163
159
  /**
164
- * Appends a single JSONL record to `patches/.fireforge-history.jsonl`.
160
+ * Appends a single JSONL record to the project's patch history log
161
+ * (`.fireforge/history.jsonl` unless `fireforge.json#history.path` says
162
+ * otherwise; a log at the pre-0.50 `patches/.fireforge-history.jsonl` is
163
+ * moved there first).
165
164
  *
166
165
  * Call order matters: append only after the mutation succeeds, never
167
166
  * pre-emptively, so rolled-back failures do not leave ghost entries. The log
168
167
  * is append-only and advisory: no code path reads it back. It exists so
169
- * operators have a post-hoc audit trail when something goes wrong.
168
+ * operators have a post-hoc audit trail when something goes wrong. Every
169
+ * record carries the writing invocation's id (and `FIREFORGE_SESSION_ID`
170
+ * when set), so a session sharing the checkout can pick out its own lines.
170
171
  *
171
172
  * @param patchesDir - Path to the patches directory
172
173
  * @param entry - Serializable history record
173
174
  */
174
175
  export async function appendHistory(patchesDir, entry) {
175
- await ensureDir(patchesDir);
176
176
  const record = {
177
177
  ts: new Date().toISOString(),
178
+ ...historyRecordIdentity(),
178
179
  ...entry,
179
180
  };
180
- const line = `${JSON.stringify(record)}\n`;
181
- await appendFile(join(patchesDir, HISTORY_LOG_FILENAME), line, 'utf-8');
181
+ await appendHistoryLine(patchesDir, JSON.stringify(record));
182
182
  }
@@ -59,6 +59,15 @@ export declare function isIndexLockError(error: unknown): boolean;
59
59
  export declare function isUnavailableGenerationToken(token: string): boolean;
60
60
  /** Strips the marker prefix off an unavailable token for display. */
61
61
  export declare function unavailableGenerationReason(token: string): string;
62
+ /**
63
+ * Engine-relative paths whose working-tree status differs between two
64
+ * generation tokens (both sides of a rename), and whether HEAD moved. An
65
+ * untracked directory keeps git's trailing `/`.
66
+ */
67
+ export declare function generationDeltaPaths(before: string, after: string): {
68
+ headMoved: boolean;
69
+ paths: string[];
70
+ };
62
71
  /**
63
72
  * Describes what moved between two generation tokens, in operator terms.
64
73
  *
@@ -101,4 +110,4 @@ export declare function describeEngineGenerationDelta(before: string, after: str
101
110
  * demonstrably had something to measure. The reverse means there was never a
102
111
  * baseline to compare against. Neither verdict can be trusted, so both throw.
103
112
  */
104
- export declare function assertEngineGenerationUnchanged(engineDir: string, before: string): Promise<void>;
113
+ export declare function assertEngineGenerationUnchanged(engineDir: string, before: string, tolerateDelta?: (before: string, after: string) => Promise<boolean>): Promise<void>;
@@ -4,7 +4,7 @@ import { InconclusiveVerdictError } from '../errors/base.js';
4
4
  import { toError } from '../utils/errors.js';
5
5
  import { info, warn } from '../utils/logger.js';
6
6
  import { sleep } from '../utils/sleep.js';
7
- import { readLockStatus, withFileLock } from './file-lock.js';
7
+ import { formatLockQueuePosition, readLockStatus, withFileLock, } from './file-lock.js';
8
8
  import { git } from './git-base.js';
9
9
  const ENGINE_SESSION_LOCK_PATH = join('.fireforge', 'engine-session.lock');
10
10
  /**
@@ -24,12 +24,9 @@ const MAX_WAIT_LOCK_MS = 3600 * 1000;
24
24
  function formatWaitProgressLine(waitedMs, timeoutMs, holder, queue) {
25
25
  const progress = `${Math.round(waitedMs / 1000)}s of up to ${Math.round(timeoutMs / 1000)}s`;
26
26
  // Queue position: under several concurrent sessions the wait
27
- // line alone left operators inferring their place from `ps`.
28
- const position = queue === undefined || queue.depth === 0
29
- ? ''
30
- : queue.ahead === 0
31
- ? ` You are next in a queue of ${queue.depth}.`
32
- : ` ${queue.ahead} ahead of you (queue of ${queue.depth}).`;
27
+ // line alone left operators inferring their place from `ps`. It names the
28
+ // ordering rule, because the count is only useful if the grant honours it.
29
+ const position = formatLockQueuePosition(queue);
33
30
  if (holder === undefined) {
34
31
  return `Waiting for the FireForge engine lock — ${progress}.${position}`;
35
32
  }
@@ -254,6 +251,25 @@ function parsePorcelainRecords(body) {
254
251
  }
255
252
  return records;
256
253
  }
254
+ /**
255
+ * Engine-relative paths whose working-tree status differs between two
256
+ * generation tokens (both sides of a rename), and whether HEAD moved. An
257
+ * untracked directory keeps git's trailing `/`.
258
+ */
259
+ export function generationDeltaPaths(before, after) {
260
+ const from = splitGenerationToken(before);
261
+ const to = splitGenerationToken(after);
262
+ const beforeRecords = new Set(parsePorcelainRecords(from.body));
263
+ const afterRecords = new Set(parsePorcelainRecords(to.body));
264
+ const paths = new Set();
265
+ for (const record of [...afterRecords, ...beforeRecords]) {
266
+ if (beforeRecords.has(record) && afterRecords.has(record))
267
+ continue;
268
+ for (const path of record.slice(3).split(' -> '))
269
+ paths.add(path);
270
+ }
271
+ return { headMoved: from.head !== to.head, paths: [...paths].sort() };
272
+ }
257
273
  /**
258
274
  * Describes what moved between two generation tokens, in operator terms.
259
275
  *
@@ -317,7 +333,7 @@ export function describeEngineGenerationDelta(before, after) {
317
333
  * demonstrably had something to measure. The reverse means there was never a
318
334
  * baseline to compare against. Neither verdict can be trusted, so both throw.
319
335
  */
320
- export async function assertEngineGenerationUnchanged(engineDir, before) {
336
+ export async function assertEngineGenerationUnchanged(engineDir, before, tolerateDelta) {
321
337
  const after = await snapshotEngineGeneration(engineDir);
322
338
  const beforeUnavailable = isUnavailableGenerationToken(before);
323
339
  const afterUnavailable = isUnavailableGenerationToken(after);
@@ -339,6 +355,10 @@ export async function assertEngineGenerationUnchanged(engineDir, before) {
339
355
  }
340
356
  if (after === before)
341
357
  return;
358
+ // A caller that can prove the delta never reached the runtime under test
359
+ // (a perf sample, see `test-perf-scope.ts`) keeps its verdict.
360
+ if (tolerateDelta !== undefined && (await tolerateDelta(before, after)))
361
+ return;
342
362
  const delta = describeEngineGenerationDelta(before, after);
343
363
  throw new InconclusiveVerdictError('engine/ changed while `fireforge test` was running, so this test verdict is invalid/inconclusive.\n\n' +
344
364
  (delta.length > 0 ? `What moved:\n${delta.map((line) => ` ${line}`).join('\n')}\n\n` : '') +
@@ -21,14 +21,25 @@
21
21
  * carry), so the refusal can name the sequence instead of leaving the
22
22
  * operator to derive it.
23
23
  */
24
+ /** One run of consecutive lines a new file shares with an existing patch. */
25
+ export interface MovedCodeBlock {
26
+ /** New-file path in the pending diff. */
27
+ file: string;
28
+ /** File in the source patch that carries the same run. */
29
+ sourceFile: string;
30
+ /** The matched substantive lines, trimmed, in order. */
31
+ lines: string[];
32
+ }
24
33
  /** One existing patch that already carries lines the new files would add. */
25
34
  export interface MovedCodeOverlap {
26
35
  /** Filename of the patch that still contains the moved lines. */
27
36
  sourcePatch: string;
28
37
  /** New-file paths in the pending diff whose lines it overlaps. */
29
38
  files: string[];
30
- /** Count of distinct non-trivial lines shared. */
39
+ /** Count of substantive lines inside the matched blocks. */
31
40
  sharedLines: number;
41
+ /** The matched blocks, so the claim can be checked line by line. */
42
+ blocks: MovedCodeBlock[];
32
43
  }
33
44
  /** Minimal queue view this detector needs. */
34
45
  export interface MovedCodeQueueEntry {
@@ -36,9 +47,9 @@ export interface MovedCodeQueueEntry {
36
47
  diff: string;
37
48
  }
38
49
  /**
39
- * Finds existing patches that still carry substantive lines the pending
40
- * diff's new files would add, the fingerprint of code moved out of an
41
- * existing patch into a new file.
50
+ * Finds existing patches that still carry a block of substantive lines the
51
+ * pending diff's new files would add, the fingerprint of code moved out of
52
+ * an existing patch into a new file.
42
53
  *
43
54
  * Pure. Overlaps are returned strongest-first so the message names the
44
55
  * most likely source patch.
@@ -48,13 +59,40 @@ export interface MovedCodeQueueEntry {
48
59
  * @returns Overlaps, or an empty list when the shape is not present
49
60
  */
50
61
  export declare function detectMovedCodeOverlaps(pendingDiff: string, entries: readonly MovedCodeQueueEntry[]): MovedCodeOverlap[];
62
+ /** What the placement projection learned about the pending export. */
63
+ export interface MovedCodeAdviceInput {
64
+ /** Result of {@link detectMovedCodeOverlaps}. */
65
+ overlaps: readonly MovedCodeOverlap[];
66
+ /** The filename the pending export would take. */
67
+ newPatchName: string;
68
+ /** The order the pending export would take. */
69
+ insertionOrder: number;
70
+ /**
71
+ * New system modules in the pending diff that no patch registers
72
+ * (`unregistered-system-module` findings on the new patch).
73
+ */
74
+ unregisteredModules?: readonly string[];
75
+ /**
76
+ * True when every error in the exported content is a missing
77
+ * registration: registering is then the whole fix, and adopt-then-split
78
+ * is not offered at all.
79
+ */
80
+ registrationResolvesAll?: boolean;
81
+ /**
82
+ * Why adopting the new files into the strongest overlap's source patch
83
+ * would itself be refused (forward imports, size limit). Empty when the
84
+ * adopt is clean.
85
+ */
86
+ adoptBlockers?: readonly string[];
87
+ }
51
88
  /**
52
- * Renders the adopt-then-split remedy for the strongest overlap, as lines
53
- * appended to the refusal's detail list.
89
+ * Renders the remedy lines appended to a placement refusal: "register
90
+ * first" when new modules lack their moz.build registration, then the
91
+ * adopt-then-split sequence for a real moved-code block, but only when the
92
+ * adopt itself would pass. Each claim names its evidence (the matched
93
+ * lines, the blocking findings) so it can be checked.
54
94
  *
55
- * @param overlaps - Result of {@link detectMovedCodeOverlaps}
56
- * @param newPatchName - The filename the pending export would take
57
- * @param insertionOrder - The order the pending export would take
58
- * @returns Detail lines, or an empty list when the shape is not present
95
+ * @param input - See {@link MovedCodeAdviceInput}
96
+ * @returns Detail lines, or an empty list when neither shape is present
59
97
  */
60
- export declare function formatAdoptThenSplitRemedy(overlaps: readonly MovedCodeOverlap[], newPatchName: string, insertionOrder: number): string[];
98
+ export declare function formatMovedCodeAdvice(input: MovedCodeAdviceInput): string[];