@hominis/fireforge 0.44.4 → 0.46.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 (378) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/dist/bin/fireforge.js +6 -0
  3. package/dist/build-info.json +4 -4
  4. package/dist/src/cli-usage-error.d.ts +46 -0
  5. package/dist/src/cli-usage-error.js +81 -0
  6. package/dist/src/cli.d.ts +5 -1
  7. package/dist/src/cli.js +20 -21
  8. package/dist/src/commands/bootstrap.js +1 -2
  9. package/dist/src/commands/build.js +15 -6
  10. package/dist/src/commands/destructive-decision.d.ts +19 -0
  11. package/dist/src/commands/destructive-decision.js +31 -0
  12. package/dist/src/commands/doctor-check-core.d.ts +0 -6
  13. package/dist/src/commands/doctor-check-core.js +0 -8
  14. package/dist/src/commands/doctor-orphaned-harness.d.ts +0 -6
  15. package/dist/src/commands/doctor-orphaned-harness.js +4 -12
  16. package/dist/src/commands/doctor-post-rebase-audit.js +1 -1
  17. package/dist/src/commands/doctor-source-pin.d.ts +29 -0
  18. package/dist/src/commands/doctor-source-pin.js +81 -0
  19. package/dist/src/commands/doctor.d.ts +2 -0
  20. package/dist/src/commands/doctor.js +9 -8
  21. package/dist/src/commands/download.js +35 -24
  22. package/dist/src/commands/export-all.js +21 -4
  23. package/dist/src/commands/export-flow.js +15 -60
  24. package/dist/src/commands/export-placement-conflicts.js +3 -3
  25. package/dist/src/commands/export-placement-gate.js +2 -8
  26. package/dist/src/commands/export-placement-policy.js +3 -5
  27. package/dist/src/commands/export-shared.d.ts +42 -23
  28. package/dist/src/commands/export-shared.js +4 -19
  29. package/dist/src/commands/export.js +10 -1
  30. package/dist/src/commands/furnace/authoring-config.d.ts +13 -0
  31. package/dist/src/commands/furnace/authoring-config.js +19 -0
  32. package/dist/src/commands/furnace/chrome-doc-remove.js +1 -1
  33. package/dist/src/commands/furnace/create-browser-test.js +3 -8
  34. package/dist/src/commands/furnace/create-dry-run.js +2 -5
  35. package/dist/src/commands/furnace/create-templates.d.ts +17 -1
  36. package/dist/src/commands/furnace/create-templates.js +2 -1
  37. package/dist/src/commands/furnace/create.js +25 -19
  38. package/dist/src/commands/furnace/deploy.js +11 -2
  39. package/dist/src/commands/furnace/init.js +2 -1
  40. package/dist/src/commands/furnace/override.js +28 -19
  41. package/dist/src/commands/furnace/refresh.js +9 -1
  42. package/dist/src/commands/furnace/remove-mutations.d.ts +6 -7
  43. package/dist/src/commands/furnace/remove-mutations.js +0 -1
  44. package/dist/src/commands/furnace/remove.js +2 -6
  45. package/dist/src/commands/furnace/rename-browser-test.js +1 -7
  46. package/dist/src/commands/furnace/rename.js +29 -29
  47. package/dist/src/commands/furnace/status.js +12 -9
  48. package/dist/src/commands/furnace/test-file-name.d.ts +28 -0
  49. package/dist/src/commands/furnace/test-file-name.js +39 -0
  50. package/dist/src/commands/furnace/validation-output.d.ts +21 -9
  51. package/dist/src/commands/furnace/validation-output.js +3 -8
  52. package/dist/src/commands/import.js +2 -9
  53. package/dist/src/commands/lint-per-patch.d.ts +2 -2
  54. package/dist/src/commands/lint-per-patch.js +19 -4
  55. package/dist/src/commands/lint-per-run-checkjs.d.ts +2 -2
  56. package/dist/src/commands/lint.d.ts +2 -1
  57. package/dist/src/commands/lint.js +1 -1
  58. package/dist/src/commands/manifest.js +1 -1
  59. package/dist/src/commands/package.js +2 -4
  60. package/dist/src/commands/patch/compact.js +14 -15
  61. package/dist/src/commands/patch/delete.js +15 -25
  62. package/dist/src/commands/patch/entry-projection.d.ts +13 -0
  63. package/dist/src/commands/patch/entry-projection.js +24 -0
  64. package/dist/src/commands/patch/lint-ignore.d.ts +0 -8
  65. package/dist/src/commands/patch/lint-ignore.js +19 -29
  66. package/dist/src/commands/patch/move-files-create.js +24 -10
  67. package/dist/src/commands/patch/move-files-into.js +38 -37
  68. package/dist/src/commands/patch/move-files.js +4 -13
  69. package/dist/src/commands/patch/rename.js +27 -41
  70. package/dist/src/commands/patch/reorder.js +25 -38
  71. package/dist/src/commands/patch/split-plan.d.ts +4 -5
  72. package/dist/src/commands/patch/split-plan.js +4 -11
  73. package/dist/src/commands/patch/split.js +45 -43
  74. package/dist/src/commands/patch/staged-dependency.js +16 -22
  75. package/dist/src/commands/patch/tier.js +15 -25
  76. package/dist/src/commands/patch-rename-map.d.ts +19 -0
  77. package/dist/src/commands/patch-rename-map.js +36 -0
  78. package/dist/src/commands/pending-resolution.d.ts +15 -0
  79. package/dist/src/commands/pending-resolution.js +25 -0
  80. package/dist/src/commands/re-export-adjacent.d.ts +32 -2
  81. package/dist/src/commands/re-export-adjacent.js +51 -10
  82. package/dist/src/commands/re-export-bulk-scan.js +6 -10
  83. package/dist/src/commands/re-export-drift.d.ts +8 -0
  84. package/dist/src/commands/re-export-drift.js +65 -4
  85. package/dist/src/commands/re-export-files.d.ts +2 -3
  86. package/dist/src/commands/re-export-files.js +38 -32
  87. package/dist/src/commands/re-export-lint.d.ts +3 -4
  88. package/dist/src/commands/re-export-lint.js +9 -2
  89. package/dist/src/commands/re-export-options.js +19 -5
  90. package/dist/src/commands/re-export-register.js +5 -1
  91. package/dist/src/commands/re-export-scan.js +8 -8
  92. package/dist/src/commands/re-export-single.d.ts +22 -3
  93. package/dist/src/commands/re-export-single.js +19 -11
  94. package/dist/src/commands/re-export.js +44 -27
  95. package/dist/src/commands/rebase/abort.js +3 -8
  96. package/dist/src/commands/rebase/continue.js +12 -12
  97. package/dist/src/commands/rebase/index.js +2 -2
  98. package/dist/src/commands/rebase/patch-loop.d.ts +2 -2
  99. package/dist/src/commands/rebase/patch-loop.js +2 -7
  100. package/dist/src/commands/resolve.js +18 -10
  101. package/dist/src/commands/run.d.ts +0 -13
  102. package/dist/src/commands/run.js +12 -25
  103. package/dist/src/commands/setup-support.d.ts +16 -4
  104. package/dist/src/commands/setup-support.js +16 -16
  105. package/dist/src/commands/setup.js +3 -9
  106. package/dist/src/commands/source.js +8 -1
  107. package/dist/src/commands/status-check.js +2 -2
  108. package/dist/src/commands/status-output.js +2 -2
  109. package/dist/src/commands/status.js +3 -4
  110. package/dist/src/commands/test-build-phase.d.ts +2 -2
  111. package/dist/src/commands/test-build-phase.js +33 -8
  112. package/dist/src/commands/test-diagnose.js +14 -5
  113. package/dist/src/commands/test-modes.d.ts +2 -2
  114. package/dist/src/commands/test-modes.js +0 -1
  115. package/dist/src/commands/test-register.js +6 -3
  116. package/dist/src/commands/test-run.js +29 -21
  117. package/dist/src/commands/test-stale-gate.js +8 -5
  118. package/dist/src/commands/test-verdict.d.ts +16 -2
  119. package/dist/src/commands/test-verdict.js +18 -3
  120. package/dist/src/commands/test.js +51 -7
  121. package/dist/src/commands/token-list.d.ts +26 -0
  122. package/dist/src/commands/token-list.js +153 -0
  123. package/dist/src/commands/token.js +17 -1
  124. package/dist/src/commands/tree.js +7 -2
  125. package/dist/src/commands/typecheck.js +9 -9
  126. package/dist/src/commands/wire.js +4 -3
  127. package/dist/src/core/branding.d.ts +0 -7
  128. package/dist/src/core/branding.js +3 -2
  129. package/dist/src/core/browser-wire.js +6 -3
  130. package/dist/src/core/build-audit-platform.d.ts +29 -0
  131. package/dist/src/core/build-audit-platform.js +140 -0
  132. package/dist/src/core/build-audit-registration.js +3 -2
  133. package/dist/src/core/build-audit-skip.d.ts +57 -0
  134. package/dist/src/core/build-audit-skip.js +112 -0
  135. package/dist/src/core/build-audit.d.ts +15 -2
  136. package/dist/src/core/build-audit.js +154 -12
  137. package/dist/src/core/build-baseline.d.ts +45 -28
  138. package/dist/src/core/build-baseline.js +6 -29
  139. package/dist/src/core/build-jar-escalation.d.ts +64 -0
  140. package/dist/src/core/build-jar-escalation.js +128 -0
  141. package/dist/src/core/build-overwrite-guard.js +1 -1
  142. package/dist/src/core/build-prepare.d.ts +5 -2
  143. package/dist/src/core/build-prepare.js +34 -6
  144. package/dist/src/core/config-paths.d.ts +2 -6
  145. package/dist/src/core/config-paths.js +4 -2
  146. package/dist/src/core/config-state.d.ts +0 -6
  147. package/dist/src/core/config-state.js +1 -19
  148. package/dist/src/core/config-validate-build-audit.d.ts +8 -0
  149. package/dist/src/core/config-validate-build-audit.js +80 -0
  150. package/dist/src/core/config-validate-file-size.d.ts +12 -0
  151. package/dist/src/core/config-validate-file-size.js +65 -0
  152. package/dist/src/core/config-validate-patch-policy.d.ts +2 -2
  153. package/dist/src/core/config-validate-patch-policy.js +3 -3
  154. package/dist/src/core/config-validate-test-toolchains.d.ts +2 -2
  155. package/dist/src/core/config-validate-test-toolchains.js +4 -5
  156. package/dist/src/core/config-validate.js +20 -3
  157. package/dist/src/core/config.d.ts +2 -2
  158. package/dist/src/core/config.js +11 -15
  159. package/dist/src/core/coverage-extend.js +4 -4
  160. package/dist/src/core/discard-baseline.js +7 -7
  161. package/dist/src/core/engine-precondition.d.ts +1 -9
  162. package/dist/src/core/engine-precondition.js +3 -8
  163. package/dist/src/core/engine-session-lock.js +13 -17
  164. package/dist/src/core/export-moved-code-shape.js +2 -2
  165. package/dist/src/core/file-lock-owner.d.ts +103 -0
  166. package/dist/src/core/file-lock-owner.js +259 -0
  167. package/dist/src/core/file-lock.d.ts +2 -7
  168. package/dist/src/core/file-lock.js +40 -91
  169. package/dist/src/core/firefox-cache.d.ts +13 -1
  170. package/dist/src/core/firefox-cache.js +59 -34
  171. package/dist/src/core/firefox-download.d.ts +15 -0
  172. package/dist/src/core/firefox-download.js +37 -21
  173. package/dist/src/core/firefox-extract.d.ts +6 -5
  174. package/dist/src/core/firefox-extract.js +36 -18
  175. package/dist/src/core/firefox.d.ts +30 -32
  176. package/dist/src/core/firefox.js +5 -36
  177. package/dist/src/core/furnace-apply-dry-run.d.ts +1 -6
  178. package/dist/src/core/furnace-apply-dry-run.js +1 -7
  179. package/dist/src/core/furnace-apply-helpers.d.ts +0 -11
  180. package/dist/src/core/furnace-apply-helpers.js +8 -19
  181. package/dist/src/core/furnace-apply-overwrite-warn.js +4 -2
  182. package/dist/src/core/furnace-apply.d.ts +1 -1
  183. package/dist/src/core/furnace-apply.js +30 -10
  184. package/dist/src/core/furnace-config.d.ts +1 -1
  185. package/dist/src/core/furnace-config.js +23 -36
  186. package/dist/src/core/furnace-constants.js +3 -2
  187. package/dist/src/core/furnace-dir-entry.d.ts +20 -0
  188. package/dist/src/core/furnace-dir-entry.js +21 -0
  189. package/dist/src/core/furnace-operation.d.ts +2 -0
  190. package/dist/src/core/furnace-operation.js +3 -1
  191. package/dist/src/core/furnace-refresh.d.ts +18 -6
  192. package/dist/src/core/furnace-refresh.js +3 -6
  193. package/dist/src/core/furnace-registration-ast.d.ts +0 -1
  194. package/dist/src/core/furnace-registration-ast.js +0 -3
  195. package/dist/src/core/furnace-registration.d.ts +2 -1
  196. package/dist/src/core/furnace-registration.js +22 -29
  197. package/dist/src/core/furnace-scanner.js +3 -6
  198. package/dist/src/core/furnace-state-persist.d.ts +2 -2
  199. package/dist/src/core/furnace-validate-registration.js +5 -7
  200. package/dist/src/core/git-base.d.ts +3 -1
  201. package/dist/src/core/git-base.js +25 -3
  202. package/dist/src/core/git-diff-new-file.d.ts +40 -0
  203. package/dist/src/core/git-diff-new-file.js +119 -0
  204. package/dist/src/core/git-diff.d.ts +9 -9
  205. package/dist/src/core/git-diff.js +88 -157
  206. package/dist/src/core/git-file-ops.d.ts +0 -7
  207. package/dist/src/core/git-file-ops.js +4 -28
  208. package/dist/src/core/git-readonly-index.d.ts +0 -2
  209. package/dist/src/core/git-readonly-index.js +0 -4
  210. package/dist/src/core/git-status.d.ts +7 -0
  211. package/dist/src/core/git-status.js +21 -9
  212. package/dist/src/core/git.d.ts +0 -6
  213. package/dist/src/core/git.js +14 -43
  214. package/dist/src/core/harness-orphans.d.ts +73 -0
  215. package/dist/src/core/harness-orphans.js +187 -0
  216. package/dist/src/core/history-log.d.ts +19 -0
  217. package/dist/src/core/history-log.js +29 -0
  218. package/dist/src/core/mach-error-hints.d.ts +0 -5
  219. package/dist/src/core/mach-error-hints.js +1 -1
  220. package/dist/src/core/mach-known-noise-filter.d.ts +25 -3
  221. package/dist/src/core/mach-known-noise-filter.js +31 -10
  222. package/dist/src/core/mach-mozconfig.js +2 -1
  223. package/dist/src/core/mach-python.d.ts +2 -0
  224. package/dist/src/core/mach-python.js +12 -7
  225. package/dist/src/core/mach-resource-shim.d.ts +13 -1
  226. package/dist/src/core/mach-resource-shim.js +112 -9
  227. package/dist/src/core/mach.d.ts +46 -53
  228. package/dist/src/core/mach.js +31 -83
  229. package/dist/src/core/marionette-port.d.ts +0 -17
  230. package/dist/src/core/marionette-port.js +12 -58
  231. package/dist/src/core/marionette-preflight.js +9 -27
  232. package/dist/src/core/mochitest-server-port.js +3 -3
  233. package/dist/src/core/moz-manifest-rules.js +37 -33
  234. package/dist/src/core/patch-apply-fuzz.js +4 -4
  235. package/dist/src/core/patch-apply.d.ts +4 -5
  236. package/dist/src/core/patch-apply.js +55 -32
  237. package/dist/src/core/patch-export-coverage.d.ts +0 -9
  238. package/dist/src/core/patch-export-coverage.js +1 -30
  239. package/dist/src/core/patch-export-update.d.ts +18 -1
  240. package/dist/src/core/patch-export-update.js +4 -3
  241. package/dist/src/core/patch-export.d.ts +11 -21
  242. package/dist/src/core/patch-export.js +44 -66
  243. package/dist/src/core/patch-files.d.ts +0 -4
  244. package/dist/src/core/patch-files.js +0 -11
  245. package/dist/src/core/patch-lint-binary.d.ts +6 -12
  246. package/dist/src/core/patch-lint-cache.d.ts +17 -1
  247. package/dist/src/core/patch-lint-cache.js +3 -6
  248. package/dist/src/core/patch-lint-checkjs.d.ts +33 -16
  249. package/dist/src/core/patch-lint-checkjs.js +43 -25
  250. package/dist/src/core/patch-lint-creators.d.ts +2 -6
  251. package/dist/src/core/patch-lint-cross.d.ts +16 -8
  252. package/dist/src/core/patch-lint-cross.js +49 -92
  253. package/dist/src/core/patch-lint-css.js +10 -6
  254. package/dist/src/core/patch-lint-file-size.d.ts +52 -0
  255. package/dist/src/core/patch-lint-file-size.js +53 -0
  256. package/dist/src/core/patch-lint-forward-registration.d.ts +58 -0
  257. package/dist/src/core/patch-lint-forward-registration.js +210 -0
  258. package/dist/src/core/patch-lint-jsdoc.js +36 -36
  259. package/dist/src/core/patch-lint-module-registration.d.ts +2 -8
  260. package/dist/src/core/patch-lint-prettier.d.ts +36 -0
  261. package/dist/src/core/patch-lint-prettier.js +126 -0
  262. package/dist/src/core/patch-lint-queue-types.d.ts +60 -0
  263. package/dist/src/core/patch-lint-queue-types.js +12 -0
  264. package/dist/src/core/patch-lint-staged-registration.d.ts +61 -0
  265. package/dist/src/core/patch-lint-staged-registration.js +90 -0
  266. package/dist/src/core/patch-lint-unmanaged-companion.d.ts +106 -0
  267. package/dist/src/core/patch-lint-unmanaged-companion.js +214 -0
  268. package/dist/src/core/patch-lint.d.ts +14 -9
  269. package/dist/src/core/patch-lint.js +27 -27
  270. package/dist/src/core/patch-lock.js +3 -3
  271. package/dist/src/core/patch-manifest-io.js +8 -7
  272. package/dist/src/core/patch-parse.d.ts +19 -8
  273. package/dist/src/core/patch-parse.js +25 -0
  274. package/dist/src/core/patch-policy.js +15 -27
  275. package/dist/src/core/patch-source-metadata.d.ts +1 -3
  276. package/dist/src/core/patch-source-metadata.js +0 -4
  277. package/dist/src/core/patch-transform.d.ts +21 -8
  278. package/dist/src/core/patch-transform.js +38 -10
  279. package/dist/src/core/process-cpu-time.js +1 -1
  280. package/dist/src/core/rebase-session.d.ts +2 -8
  281. package/dist/src/core/rebase-session.js +5 -17
  282. package/dist/src/core/register-browser-content.js +3 -2
  283. package/dist/src/core/register-browser-test.js +2 -1
  284. package/dist/src/core/register-module.js +2 -1
  285. package/dist/src/core/register-scaffold.js +5 -4
  286. package/dist/src/core/register-shared-css.js +2 -1
  287. package/dist/src/core/run-log-redact.d.ts +38 -0
  288. package/dist/src/core/run-log-redact.js +69 -0
  289. package/dist/src/core/run-log.d.ts +13 -2
  290. package/dist/src/core/run-log.js +67 -18
  291. package/dist/src/core/signal-critical.js +2 -4
  292. package/dist/src/core/smoke-patterns.d.ts +0 -1
  293. package/dist/src/core/smoke-patterns.js +18 -11
  294. package/dist/src/core/state-file.d.ts +12 -1
  295. package/dist/src/core/state-file.js +14 -1
  296. package/dist/src/core/status-classify.js +2 -1
  297. package/dist/src/core/test-changed-prefs.js +26 -11
  298. package/dist/src/core/test-harness-crash.js +61 -48
  299. package/dist/src/core/test-harness-verdict-notes.d.ts +55 -0
  300. package/dist/src/core/test-harness-verdict-notes.js +48 -0
  301. package/dist/src/core/test-stale-check.d.ts +23 -8
  302. package/dist/src/core/test-stale-check.js +77 -4
  303. package/dist/src/core/test-stall-triage.js +37 -19
  304. package/dist/src/core/test-xpcshell-retry.d.ts +26 -7
  305. package/dist/src/core/test-xpcshell-retry.js +14 -11
  306. package/dist/src/core/token-category.d.ts +33 -0
  307. package/dist/src/core/token-category.js +56 -48
  308. package/dist/src/core/token-dark-mode.js +2 -14
  309. package/dist/src/core/token-inventory.d.ts +56 -0
  310. package/dist/src/core/token-inventory.js +109 -0
  311. package/dist/src/core/token-manager.d.ts +1 -1
  312. package/dist/src/core/token-manager.js +1 -1
  313. package/dist/src/core/token-variant.js +21 -22
  314. package/dist/src/core/toolchain-preflight.js +1 -1
  315. package/dist/src/core/tree-cow.d.ts +0 -2
  316. package/dist/src/core/tree-cow.js +2 -2
  317. package/dist/src/core/tree-store.js +4 -5
  318. package/dist/src/core/typecheck.js +2 -2
  319. package/dist/src/core/wire-dom-fragment.js +6 -17
  320. package/dist/src/core/wire-utils.js +5 -17
  321. package/dist/src/core/xpcshell-appdir.js +2 -4
  322. package/dist/src/errors/base.d.ts +53 -1
  323. package/dist/src/errors/base.js +79 -11
  324. package/dist/src/errors/build.d.ts +28 -0
  325. package/dist/src/errors/build.js +51 -10
  326. package/dist/src/errors/config.d.ts +0 -14
  327. package/dist/src/errors/config.js +13 -34
  328. package/dist/src/errors/download.d.ts +13 -0
  329. package/dist/src/errors/download.js +53 -24
  330. package/dist/src/errors/furnace.js +8 -9
  331. package/dist/src/errors/git.d.ts +8 -8
  332. package/dist/src/errors/git.js +20 -30
  333. package/dist/src/errors/patch.js +8 -10
  334. package/dist/src/errors/rebase.js +7 -7
  335. package/dist/src/types/commands/options.d.ts +96 -123
  336. package/dist/src/types/commands/patches.d.ts +17 -6
  337. package/dist/src/types/config.d.ts +75 -2
  338. package/dist/src/types/index.d.ts +0 -1
  339. package/dist/src/utils/assert.d.ts +3 -15
  340. package/dist/src/utils/assert.js +1 -18
  341. package/dist/src/utils/build-info.d.ts +1 -1
  342. package/dist/src/utils/child-env.d.ts +2 -1
  343. package/dist/src/utils/elapsed.js +10 -1
  344. package/dist/src/utils/fs.d.ts +4 -2
  345. package/dist/src/utils/fs.js +11 -14
  346. package/dist/src/utils/hash.d.ts +12 -0
  347. package/dist/src/utils/hash.js +16 -0
  348. package/dist/src/utils/line-dispatch.d.ts +15 -0
  349. package/dist/src/utils/line-dispatch.js +48 -0
  350. package/dist/src/utils/options.d.ts +1 -7
  351. package/dist/src/utils/options.js +6 -7
  352. package/dist/src/utils/package-root.js +3 -12
  353. package/dist/src/utils/platform.d.ts +0 -4
  354. package/dist/src/utils/process-group.d.ts +2 -1
  355. package/dist/src/utils/process-group.js +20 -19
  356. package/dist/src/utils/process.d.ts +40 -26
  357. package/dist/src/utils/process.js +115 -166
  358. package/dist/src/utils/ps-duration.js +3 -3
  359. package/dist/src/utils/sleep.d.ts +26 -0
  360. package/dist/src/utils/sleep.js +31 -0
  361. package/dist/src/utils/validation.d.ts +2 -38
  362. package/dist/src/utils/validation.js +7 -16
  363. package/docs/configuration.md +136 -1
  364. package/docs/exit-codes.md +34 -20
  365. package/docs/lifecycle-invariants.md +18 -12
  366. package/docs/machine-output.md +31 -8
  367. package/docs/patch-workflow.md +2 -1
  368. package/docs/run-logs.md +44 -4
  369. package/docs/testing.md +70 -11
  370. package/package.json +8 -7
  371. package/dist/src/commands/rebase.d.ts +0 -4
  372. package/dist/src/commands/rebase.js +0 -5
  373. package/dist/src/core/patch-artifact-normalize.d.ts +0 -21
  374. package/dist/src/core/patch-artifact-normalize.js +0 -24
  375. package/dist/src/core/patch-lint-reexports.d.ts +0 -7
  376. package/dist/src/core/patch-lint-reexports.js +0 -8
  377. package/dist/src/core/wire-targets.d.ts +0 -7
  378. package/dist/src/core/wire-targets.js +0 -8
@@ -101,7 +101,7 @@ function summarizeForeignHunks(payload, foreignAdded, foreignRemoved) {
101
101
  }
102
102
  }
103
103
  if (added > 0 || removed > 0) {
104
- summaries.push(`@@ -${String(hunk.oldStart)},${String(hunk.oldCount)} +${String(hunk.newStart)},${String(hunk.newCount)} @@ (+${String(added)}/-${String(removed)})`);
104
+ summaries.push(`@@ -${hunk.oldStart},${hunk.oldCount} +${hunk.newStart},${hunk.newCount} @@ (+${added}/-${removed})`);
105
105
  }
106
106
  }
107
107
  }
@@ -184,7 +184,7 @@ export async function reportForeignDrift(args) {
184
184
  const recentlyEdited = await findFilesEditedSinceLastExport(engineDir, oldPath, drift.map((d) => d.file));
185
185
  const totalLines = drift.reduce((sum, d) => sum + d.addedLines + d.removedLines, 0);
186
186
  const lineNoun = totalLines === 1 ? 'line' : 'lines';
187
- warn(`${patch.filename}: refreshed body absorbs ${String(totalLines)} ${lineNoun} not present in the old patch body (${String(drift.length)} file(s)):`);
187
+ warn(`${patch.filename}: refreshed body absorbs ${totalLines} ${lineNoun} not present in the old patch body (${drift.length} file(s)):`);
188
188
  for (const entry of drift.slice(0, MAX_FILES_REPORTED)) {
189
189
  const expectedTag = ctx.expectedDriftFiles.has(entry.file) ? ' (expected via --expect)' : '';
190
190
  const authorshipTag = recentlyEdited.has(entry.file)
@@ -194,13 +194,13 @@ export async function reportForeignDrift(args) {
194
194
  info(` ${entry.file}: binary content changed${expectedTag}${authorshipTag}`);
195
195
  continue;
196
196
  }
197
- info(` ${entry.file}: +${String(entry.addedLines)}/-${String(entry.removedLines)} newly captured line(s)${expectedTag}${authorshipTag}`);
197
+ info(` ${entry.file}: +${entry.addedLines}/-${entry.removedLines} newly captured line(s)${expectedTag}${authorshipTag}`);
198
198
  for (const summary of entry.hunkSummaries) {
199
199
  info(` ${summary}`);
200
200
  }
201
201
  }
202
202
  if (drift.length > MAX_FILES_REPORTED) {
203
- info(` +${String(drift.length - MAX_FILES_REPORTED)} more file(s)`);
203
+ info(` +${drift.length - MAX_FILES_REPORTED} more file(s)`);
204
204
  }
205
205
  if (ctx.refuseForeignDrift) {
206
206
  if (unexpected.length === 0) {
@@ -271,3 +271,64 @@ function refuseUnreadableBaseline(ctx, patchFilename, detail) {
271
271
  warn(`${patchFilename}: skipped (--refuse-foreign-drift) — ${detail}; the drift comparison cannot run, refusing fail-closed.`);
272
272
  return true;
273
273
  }
274
+ /**
275
+ * States, verbatim in the notice, WHAT the `--expect` whitelist is compared
276
+ * against and WHEN. This notice is the only feedback an operator gets that
277
+ * the drift belt looked at the files they named, and a downstream report
278
+ * showed the previous copy actively misleading: it warned "showed no drift"
279
+ * over an edit that WAS captured correctly, and none of the causes it named
280
+ * held, so the operator's only recourse was `grep -c` on the written body.
281
+ *
282
+ * The measurement, stated exactly:
283
+ *
284
+ * - COMPARED: for each file already in the patch's `filesAffected`, the
285
+ * `+`/`-` payload lines the REFRESHED body carries that the OLD patch
286
+ * body did not (`computeForeignDrift`). It is a content comparison, and
287
+ * it is ONE-DIRECTIONAL: excess in the new body only.
288
+ * - WHEN: per patch, on the body about to be written, BEFORE the write —
289
+ * never against post-write state.
290
+ *
291
+ * The one-directional half is what the earlier copy hid, and it is the
292
+ * cause the report's first shape actually hit: deleting a line from an
293
+ * engine file makes the refreshed body DROP that file's `+` line. Nothing
294
+ * is absorbed, so by design there is no drift to see — the belt exists to
295
+ * catch a concurrent session's edits being silently swallowed INTO a body,
296
+ * not every difference between two bodies. Naming it is the fix; widening
297
+ * the comparison would turn every ordinary line removal into a refusal.
298
+ */
299
+ const EXPECT_MEASUREMENT_NOTE = '--expect is evaluated against CONTENT, before the write: for each file already in the ' +
300
+ "patch's filesAffected, the payload lines the refreshed body carries that the old patch " +
301
+ 'body did not. It never looks at post-write state.';
302
+ /**
303
+ * Names `--expect` paths that never drifted this run. A typo'd
304
+ * `--expect` path silently degrades the flag back to refusing the slice it
305
+ * was meant to admit, so surface the mismatch — but only as a warning: an
306
+ * expected file legitimately shows no drift in several shapes, all named
307
+ * below.
308
+ */
309
+ export function warnUnseenExpectedDrift(driftCtx) {
310
+ const unseen = [...driftCtx.expectedDriftFiles].filter((file) => !driftCtx.expectedSeen.has(file));
311
+ if (unseen.length === 0)
312
+ return;
313
+ if (driftCtx.evaluationRuns === 0) {
314
+ warn(`--expect path(s) were not evaluated because no selected patch reached the drift check (for example, an earlier refusal): ${unseen.join(', ')}`);
315
+ return;
316
+ }
317
+ warn('--expect path(s) showed no drift this run: ' +
318
+ `${unseen.join(', ')}.\n` +
319
+ `${EXPECT_MEASUREMENT_NOTE}\n` +
320
+ 'Causes, in rough order of likelihood:\n' +
321
+ ' 1. Your edit only REMOVED lines from the body (you deleted a line from the engine ' +
322
+ 'file). The comparison is one-directional — it reports lines ABSORBED into the body — ' +
323
+ 'so a correctly captured deletion is not drift and never will be. Confirm the capture ' +
324
+ 'with "grep -c" on the written patch body.\n' +
325
+ " 2. A typo in the path, or a path not in the patch's filesAffected (files adopted " +
326
+ 'this run via --scan/--scan-file are excluded from the comparison by design).\n' +
327
+ ' 3. Adoption already captured it: a "re-export <p> --scan --scan-file <f>" run also ' +
328
+ "captures that patch's other drifted files, so a scan-less re-export afterwards " +
329
+ 'legitimately sees nothing left to absorb. Informational — a chain that treats this ' +
330
+ 'warning as an error will false-red on it.\n' +
331
+ ' 4. The slice was already captured by an earlier export in this session.\n' +
332
+ ' 5. The engine content already matches the patch body — a concurrent session may have ' +
333
+ 'exported the same change, so the drift converged before this run looked.');
334
+ }
@@ -1,6 +1,5 @@
1
- import { getProjectPaths } from '../core/config.js';
2
1
  import type { PatchMetadata, ReExportOptions } from '../types/commands/index.js';
3
- import type { FireForgeConfig } from '../types/config.js';
2
+ import type { FireForgeConfig, ProjectPaths } from '../types/config.js';
4
3
  /**
5
4
  * Handles `re-export --files` end-to-end: computes the projected diff, runs
6
5
  * the per-patch and cross-patch lint against a context in which the target
@@ -13,4 +12,4 @@ import type { FireForgeConfig } from '../types/config.js';
13
12
  * generic single-patch helper is what makes the projection lint run against
14
13
  * the current (unchanged) queue instead of the projected state.
15
14
  */
16
- export declare function reExportFilesInPlace(paths: ReturnType<typeof getProjectPaths>, selectedPatches: PatchMetadata[], options: ReExportOptions, config: FireForgeConfig): Promise<void>;
15
+ export declare function reExportFilesInPlace(paths: ProjectPaths, selectedPatches: PatchMetadata[], options: ReExportOptions, config: FireForgeConfig): Promise<void>;
@@ -1,21 +1,20 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { join } from 'node:path';
3
- import { getProjectPaths } from '../core/config.js';
4
3
  import { appendHistory, confirmDestructive, } from '../core/destructive.js';
5
4
  import { enforceFreshFurnaceSources } from '../core/furnace-stale-export.js';
6
5
  import { getDiffForFilesAgainstHead } from '../core/git-diff.js';
7
6
  import { listTrackedInHead } from '../core/git-file-ops.js';
8
7
  import { extractAffectedFiles } from '../core/patch-apply.js';
9
8
  import { updatePatchAndMetadata } from '../core/patch-export.js';
10
- import { buildModifiedFileAdditionsFromDiff, buildPatchQueueContext, detectNewFilesInDiff, formatPatchLintIssue, lintPatchQueue, } from '../core/patch-lint.js';
9
+ import { buildPatchQueueContext, formatPatchLintIssue, lintPatchQueue, } from '../core/patch-lint.js';
11
10
  import { computeProjectedLintRegressions } from '../core/patch-lint-projection.js';
12
11
  import { loadPatchesManifest } from '../core/patch-manifest.js';
13
12
  import { buildProjectedManifest, enforcePatchPolicy } from '../core/patch-policy.js';
14
- import { extractNewFileContentFromDiff } from '../core/patch-transform.js';
15
13
  import { InvalidArgumentError } from '../errors/base.js';
16
14
  import { pathExists } from '../utils/fs.js';
17
15
  import { info, outro, success, warn } from '../utils/logger.js';
18
16
  import { runPatchLint } from './export-shared.js';
17
+ import { projectEntryBody } from './patch/entry-projection.js';
19
18
  /**
20
19
  * Computes the effective `tier` and `lintIgnore` carrying both the patch's
21
20
  * existing values and the CLI flag overrides.
@@ -42,20 +41,11 @@ function resolveEffectiveTierAndLintIgnore(target, options) {
42
41
  */
43
42
  async function runProjectedCrossPatchLint(patchesDir, targetFilename, projectedDiff) {
44
43
  const baseCtx = await buildPatchQueueContext(patchesDir);
45
- const projectedNewFiles = new Map();
46
- for (const path of detectNewFilesInDiff(projectedDiff)) {
47
- projectedNewFiles.set(path, extractNewFileContentFromDiff(projectedDiff, path));
48
- }
49
- const projectedModifiedFileAdditions = buildModifiedFileAdditionsFromDiff(projectedDiff);
44
+ const projection = projectEntryBody(projectedDiff);
50
45
  const projectedEntries = baseCtx.entries.map((entry) => {
51
46
  if (entry.filename !== targetFilename)
52
47
  return entry;
53
- return {
54
- ...entry,
55
- diff: projectedDiff,
56
- newFiles: projectedNewFiles,
57
- modifiedFileAdditions: projectedModifiedFileAdditions,
58
- };
48
+ return { ...entry, ...projection };
59
49
  });
60
50
  const baselineIssues = lintPatchQueue(baseCtx).filter((i) => i.severity === 'error');
61
51
  const projectedIssues = lintPatchQueue({ entries: projectedEntries }).filter((i) => i.severity === 'error');
@@ -238,7 +228,16 @@ export async function reExportFilesInPlace(paths, selectedPatches, options, conf
238
228
  const patchQueueCtx = (await pathExists(paths.patches))
239
229
  ? await buildPatchQueueContext(paths.patches)
240
230
  : undefined;
241
- await runPatchLint(paths.engine, actualProjectedFiles, projectedDiff, config, options.skipLint, patchQueueCtx, ignoreChecks, effectiveTier);
231
+ await runPatchLint({
232
+ engineDir: paths.engine,
233
+ filesAffected: actualProjectedFiles,
234
+ diffContent: projectedDiff,
235
+ config,
236
+ skipLint: options.skipLint,
237
+ patchQueueCtx,
238
+ ignoreChecks,
239
+ patchTier: effectiveTier,
240
+ });
242
241
  const conflicts = await runProjectedCrossPatchLint(paths.patches, target.filename, projectedDiff);
243
242
  const filesUpdates = buildFilesModeMetadataUpdates(actualProjectedFiles, options, effectiveLintIgnore, flagIgnoreSet);
244
243
  const manifest = await loadPatchesManifest(paths.patches);
@@ -276,23 +275,30 @@ export async function reExportFilesInPlace(paths, selectedPatches, options, conf
276
275
  // as the mutation (via the onCommitted hook) so two concurrent re-exports
277
276
  // cannot interleave records and a crash between mutation and append cannot
278
277
  // orphan the audit trail.
279
- await updatePatchAndMetadata(paths.patches, target.filename, projectedDiff, filesUpdates, async () => {
280
- await appendHistory(paths.patches, {
281
- operation: 're-export-files',
282
- args: {
283
- filename: target.filename,
284
- files: actualProjectedFiles,
285
- previousFiles: target.filesAffected,
286
- deletionsCaptured: deletedFiles,
287
- },
288
- ...(options.yes === true ? { yes: true } : {}),
289
- ...(options.forceUnsafe === true ? { unsafeOverride: true } : {}),
290
- result: 'ok',
291
- });
292
- }, {
293
- config,
294
- command: 're-export --files',
295
- forceUnsafe: options.forceUnsafe === true,
278
+ await updatePatchAndMetadata({
279
+ patchesDir: paths.patches,
280
+ filename: target.filename,
281
+ newContent: projectedDiff,
282
+ updates: filesUpdates,
283
+ onCommitted: async () => {
284
+ await appendHistory(paths.patches, {
285
+ operation: 're-export-files',
286
+ args: {
287
+ filename: target.filename,
288
+ files: actualProjectedFiles,
289
+ previousFiles: target.filesAffected,
290
+ deletionsCaptured: deletedFiles,
291
+ },
292
+ ...(options.yes === true ? { yes: true } : {}),
293
+ ...(options.forceUnsafe === true ? { unsafeOverride: true } : {}),
294
+ result: 'ok',
295
+ });
296
+ },
297
+ policyGate: {
298
+ config,
299
+ command: 're-export --files',
300
+ forceUnsafe: options.forceUnsafe === true,
301
+ },
296
302
  });
297
303
  success(`Re-exported ${target.filename}`);
298
304
  outro('Re-export complete');
@@ -19,16 +19,15 @@
19
19
  * (`refreshQueueCtxEntry`), so later iterations of an `--all` run lint
20
20
  * against the just-refreshed body instead of the stale one.
21
21
  */
22
- import { getProjectPaths, loadConfig } from '../core/config.js';
23
22
  import { type PatchQueueContext } from '../core/patch-lint.js';
24
23
  import { type PerPatchLintCacheFile } from '../core/patch-lint-cache.js';
25
24
  import type { PatchLintIssue, PatchMetadata } from '../types/commands/index.js';
26
- import type { FireForgeConfig } from '../types/config.js';
25
+ import type { FireForgeConfig, ProjectPaths } from '../types/config.js';
27
26
  import { type PerRunCheckJs } from './lint-per-run-checkjs.js';
28
27
  /** Per-invocation lint context, built once and threaded through the loop. */
29
28
  export interface ReExportLintContext {
30
29
  projectRoot: string;
31
- paths: ReturnType<typeof getProjectPaths>;
30
+ paths: ProjectPaths;
32
31
  config: FireForgeConfig;
33
32
  /** Whole-queue context, built WITH config so the forward-import hints
34
33
  * match `lint --per-patch`; undefined when no patches dir. */
@@ -44,7 +43,7 @@ export interface ReExportLintContext {
44
43
  globalsEmitted: boolean;
45
44
  }
46
45
  /** Builds the once-per-invocation lint context. */
47
- export declare function buildReExportLintContext(projectRoot: string, paths: ReturnType<typeof getProjectPaths>, config: Awaited<ReturnType<typeof loadConfig>>, noCache: boolean): Promise<ReExportLintContext>;
46
+ export declare function buildReExportLintContext(projectRoot: string, paths: ProjectPaths, config: FireForgeConfig, noCache: boolean): Promise<ReExportLintContext>;
48
47
  /** Fresh lint payload carried to the post-write cache store. */
49
48
  export interface ReExportLintResult {
50
49
  issues: PatchLintIssue[];
@@ -20,7 +20,6 @@
20
20
  * (`refreshQueueCtxEntry`), so later iterations of an `--all` run lint
21
21
  * against the just-refreshed body instead of the stale one.
22
22
  */
23
- import { getProjectPaths, loadConfig } from '../core/config.js';
24
23
  import { buildPatchQueueContext, countNonBinaryDiffLines, detectNewFilesInDiff, lintExportedPatch, resolvePatchSizeTier, } from '../core/patch-lint.js';
25
24
  import { buildPerPatchLintCacheKey, getCachedPerPatchLintIssues, getPerPatchLintCacheHeadSha, loadPerPatchLintCache, savePerPatchLintCache, setCachedPerPatchLintIssues, } from '../core/patch-lint-cache.js';
26
25
  import { invalidateNewFileCreatorsCache } from '../core/patch-lint-cross.js';
@@ -169,7 +168,15 @@ export async function storeReExportLintResult(lintCtx, patchFilename, projectedM
169
168
  queueContext: patchQueueCtx,
170
169
  ...(lintCtx.engineHeadSha === undefined ? {} : { engineHeadSha: lintCtx.engineHeadSha }),
171
170
  });
172
- setCachedPerPatchLintIssues(cache, patchFilename, cacheKey, result.issues, result.suppressed, result.lineCount, result.lintIgnore);
171
+ setCachedPerPatchLintIssues({
172
+ cache,
173
+ patchFilename,
174
+ key: cacheKey,
175
+ issues: result.issues,
176
+ suppressed: result.suppressed,
177
+ lineCount: result.lineCount,
178
+ lintIgnore: result.lintIgnore,
179
+ });
173
180
  lintCtx.cacheDirty = true;
174
181
  }
175
182
  /**
@@ -45,6 +45,24 @@ export function applyReExportFilesPositionalFolding(patches, options) {
45
45
  }
46
46
  return { patches: normalized.patches, options: normalized.options };
47
47
  }
48
+ /**
49
+ * Each carve-out flag is inert without the refusal it modifies, and a
50
+ * silently-inert whitelist is the worst shape: the operator believes an
51
+ * exception is in force while the refusal it was meant to relax was never
52
+ * armed. Refuse the combination instead of ignoring it.
53
+ */
54
+ function validateWhitelistFlagDependencies(options) {
55
+ if (options.expect !== undefined &&
56
+ options.expect.length > 0 &&
57
+ options.refuseForeignDrift !== true) {
58
+ throw new InvalidArgumentError('--expect names files whose drift is expected under --refuse-foreign-drift and has no effect without it. Pass --refuse-foreign-drift, or drop --expect.', '--expect');
59
+ }
60
+ if (options.expectUnmanaged !== undefined &&
61
+ options.expectUnmanaged.length > 0 &&
62
+ options.refuseAdjacentUnmanaged !== true) {
63
+ throw new InvalidArgumentError('--expect-unmanaged names approved exceptions to --refuse-adjacent-unmanaged and has no effect without it. Pass --refuse-adjacent-unmanaged, or drop --expect-unmanaged.', '--expect-unmanaged');
64
+ }
65
+ }
48
66
  /** Validates mutually exclusive `re-export` targeting and metadata options. */
49
67
  export function validateReExportOptionCombinations(patches, options) {
50
68
  if (options.files !== undefined) {
@@ -83,11 +101,7 @@ export function validateReExportOptionCombinations(patches, options) {
83
101
  if (options.refuseForeignDrift === true && (options.scan || options.files !== undefined)) {
84
102
  throw new InvalidArgumentError('--refuse-foreign-drift applies to the scan-less path only and cannot be combined with --scan or --files (those explicitly capture the current engine state).', '--refuse-foreign-drift');
85
103
  }
86
- if (options.expect !== undefined &&
87
- options.expect.length > 0 &&
88
- options.refuseForeignDrift !== true) {
89
- throw new InvalidArgumentError('--expect names files whose drift is expected under --refuse-foreign-drift and has no effect without it. Pass --refuse-foreign-drift, or drop --expect.', '--expect');
90
- }
104
+ validateWhitelistFlagDependencies(options);
91
105
  const usingTierFlag = options.tier !== undefined;
92
106
  const usingLintIgnoreFlag = options.lintIgnore !== undefined && options.lintIgnore.length > 0;
93
107
  if (options.all && (usingTierFlag || usingLintIgnoreFlag)) {
@@ -29,6 +29,7 @@ export function registerReExport(program, { getProjectRoot, withErrorHandling })
29
29
  .option('--refuse-adjacent-unmanaged', 'Refuse a scan-less re-export (non-zero exit, patch not written) when unmanaged files exist adjacent to the patch ownership, instead of warning. Mutually exclusive with --scan and --files.')
30
30
  .option('--refuse-foreign-drift', "Refuse a scan-less re-export (non-zero exit, patch not written) when the refreshed body would absorb engine lines not present in the old patch body — protects multi-session checkouts from silently capturing another session's uncommitted edits. Mutually exclusive with --scan and --files.")
31
31
  .option('--expect <path>', "With --refuse-foreign-drift, name an engine-relative file whose drift is this session's intended edit: drift confined to --expect files proceeds, drift anywhere else still refuses. Repeatable, and the whitelist applies across EVERY patch in this invocation, not per patch — so pass all the patches and all their --expect paths in one call rather than looping one patch at a time.", ...stringListOption())
32
+ .option('--expect-unmanaged <path>', 'With --refuse-adjacent-unmanaged, name an engine-relative file that is a REVIEWED, recorded exception to the adjacency refusal: an approved unmanaged path is still listed, but no longer refuses. Repeatable, and the carve-out applies across EVERY patch in this invocation, not per patch. Use it to keep the belt armed for everything else instead of dropping --refuse-adjacent-unmanaged entirely.', ...stringListOption())
32
33
  .option('--dry-run', 'Show what would change without writing')
33
34
  .option('--skip-lint', 'Skip patch lint checks (downgrade errors to warnings)')
34
35
  .option('--no-cache', 'Bypass per-patch lint result cache reads and writes for this re-export')
@@ -40,7 +41,7 @@ export function registerReExport(program, { getProjectRoot, withErrorHandling })
40
41
  .addOption(new Option('--tier <tier>', 'Force a tier override on the selected patch (only "branding" recognised). Mutually exclusive with --all.').choices(['branding']))
41
42
  .option('--lint-ignore <check-id>', 'Append a lint check ID to the patch\'s PatchMetadata.lintIgnore (union, de-duped, repeatable). Mutually exclusive with --all. Use "fireforge patch lint-ignore" for --remove / --clear.', ...stringListOption());
42
43
  addWaitLockOption(reExport).action(withErrorHandling(async (patches, options) => {
43
- const { tier, lintIgnore, scanFile, scanFiles, cache, expect, ...rest } = options;
44
+ const { tier, lintIgnore, scanFile, scanFiles, cache, expect, expectUnmanaged, ...rest } = options;
44
45
  const projectRoot = getProjectRoot();
45
46
  await withEngineSessionLock(projectRoot, 're-export', () => reExportCommand(projectRoot, patches, {
46
47
  ...pickDefined(rest),
@@ -49,6 +50,9 @@ export function registerReExport(program, { getProjectRoot, withErrorHandling })
49
50
  ...(tier !== undefined ? { tier: tier } : {}),
50
51
  ...(lintIgnore !== undefined && lintIgnore.length > 0 ? { lintIgnore } : {}),
51
52
  ...(expect !== undefined && expect.length > 0 ? { expect } : {}),
53
+ ...(expectUnmanaged !== undefined && expectUnmanaged.length > 0
54
+ ? { expectUnmanaged }
55
+ : {}),
52
56
  ...(cache === false ? { noCache: true } : {}),
53
57
  }), { waitLockSeconds: resolveWaitLockSeconds(options.waitLock) });
54
58
  }));
@@ -5,10 +5,11 @@ import { getDiffForFilesAgainstHead } from '../core/git-diff.js';
5
5
  import { listTrackedInHead } from '../core/git-file-ops.js';
6
6
  import { getModifiedFilesInDir, getUntrackedFilesInDir } from '../core/git-status.js';
7
7
  import { extractAffectedFiles } from '../core/patch-apply.js';
8
- import { buildModifiedFileAdditionsFromDiff, buildPatchQueueContext, detectNewFilesInDiff, lintPatchQueue, } from '../core/patch-lint.js';
8
+ import { buildModifiedFileAdditionsFromDiff, buildPatchQueueContext, lintPatchQueue, } from '../core/patch-lint.js';
9
+ import { detectNewFilesInDiff } from '../core/patch-lint-diff.js';
9
10
  import { computeProjectedLintRegressions } from '../core/patch-lint-projection.js';
10
11
  import { getClaimedFiles } from '../core/patch-manifest.js';
11
- import { extractNewFileContentFromDiff } from '../core/patch-transform.js';
12
+ import { buildNewFileTextProjection } from '../core/patch-transform.js';
12
13
  import { GeneralError, InvalidArgumentError } from '../errors/base.js';
13
14
  import { mapWithConcurrency } from '../utils/concurrency.js';
14
15
  import { pathExists } from '../utils/fs.js';
@@ -135,14 +136,14 @@ export async function confirmBroadScanAdditions(args) {
135
136
  if (isDryRun || yes || !scanAdditionsNeedConfirmation(added))
136
137
  return true;
137
138
  const dirCount = new Set(added.map((f) => dirname(f))).size;
138
- warn(`${patchFilename}: --scan would add ${String(added.length)} file(s) that span ${String(dirCount)} director${dirCount === 1 ? 'y' : 'ies'}. ` +
139
+ warn(`${patchFilename}: --scan would add ${added.length} file(s) that span ${dirCount} director${dirCount === 1 ? 'y' : 'ies'}. ` +
139
140
  'Broad scans can silently pull adjacent features into a patch — review the diff before continuing.');
140
141
  if (!isInteractive) {
141
142
  throw new GeneralError(`Refusing to broaden "${patchFilename}" via --scan in non-interactive mode. ` +
142
143
  'Pass --yes to acknowledge the expansion, or run with --dry-run first to review.');
143
144
  }
144
145
  const confirmed = await confirm({
145
- message: `Proceed and broaden ${patchFilename} with ${String(added.length)} newly discovered file(s)?`,
146
+ message: `Proceed and broaden ${patchFilename} with ${added.length} newly discovered file(s)?`,
146
147
  initialValue: false,
147
148
  });
148
149
  if (isCancel(confirmed) || !confirmed) {
@@ -176,10 +177,8 @@ export async function assertScanAdoptionsHaveNoForwardImports(args) {
176
177
  const candidateDiff = await getDiffForFilesAgainstHead(engineDir, [...added]);
177
178
  if (!candidateDiff.trim())
178
179
  return;
179
- const candidateNewFiles = new Map();
180
- for (const path of detectNewFilesInDiff(candidateDiff)) {
181
- candidateNewFiles.set(path, extractNewFileContentFromDiff(candidateDiff, path));
182
- }
180
+ const candidateNewFiles = buildNewFileTextProjection(candidateDiff);
181
+ const candidateCreations = detectNewFilesInDiff(candidateDiff);
183
182
  const candidateAdditions = buildModifiedFileAdditionsFromDiff(candidateDiff);
184
183
  const baseCtx = await buildPatchQueueContext(patchesDir);
185
184
  const projectedEntries = baseCtx.entries.map((entry) => {
@@ -189,6 +188,7 @@ export async function assertScanAdoptionsHaveNoForwardImports(args) {
189
188
  ...entry,
190
189
  diff: `${entry.diff}\n${candidateDiff}`,
191
190
  newFiles: new Map([...entry.newFiles, ...candidateNewFiles]),
191
+ createdFiles: new Set([...entry.createdFiles, ...candidateCreations]),
192
192
  modifiedFileAdditions: new Map([...entry.modifiedFileAdditions, ...candidateAdditions]),
193
193
  };
194
194
  });
@@ -4,12 +4,31 @@
4
4
  * loop calls {@link reExportSinglePatchWithIndexLockRetry} per selected
5
5
  * patch.
6
6
  */
7
- import { getProjectPaths } from '../core/config.js';
8
7
  import type { PatchesManifest, PatchMetadata, ReExportOptions } from '../types/commands/index.js';
9
- import type { FireForgeConfig } from '../types/config.js';
8
+ import type { FireForgeConfig, ProjectPaths } from '../types/config.js';
10
9
  import type { AdjacentUnmanagedContext } from './re-export-adjacent.js';
11
10
  import type { ForeignDriftContext } from './re-export-drift.js';
12
11
  import { type ReExportLintContext } from './re-export-lint.js';
12
+ export interface ReExportSinglePatchArgs {
13
+ /** Manifest row for the patch being refreshed. */
14
+ patch: PatchMetadata;
15
+ /** Resolved project paths. */
16
+ paths: ProjectPaths;
17
+ /** Patch queue manifest as loaded by the orchestrator. */
18
+ manifest: PatchesManifest;
19
+ /** Effective re-export options for this patch. */
20
+ options: ReExportOptions;
21
+ /** Whether the run is a dry run. */
22
+ isDryRun: boolean;
23
+ /** Project configuration. */
24
+ config: FireForgeConfig;
25
+ /** Adjacent-unmanaged-file reporting context. */
26
+ adjacentCtx: AdjacentUnmanagedContext;
27
+ /** Foreign-drift reporting context. */
28
+ driftCtx: ForeignDriftContext;
29
+ /** Per-patch lint context, including the shared lint cache. */
30
+ lintCtx: ReExportLintContext;
31
+ }
13
32
  /**
14
33
  * Retries a single patch re-export exactly once when the failure is
15
34
  * transient git `index.lock` contention (an external git process holding
@@ -17,4 +36,4 @@ import { type ReExportLintContext } from './re-export-lint.js';
17
36
  * from the top, so it re-reads clean state; a second lock failure
18
37
  * propagates to the loop's honest "N of M" accounting.
19
38
  */
20
- export declare function reExportSinglePatchWithIndexLockRetry(patch: PatchMetadata, paths: ReturnType<typeof getProjectPaths>, manifest: PatchesManifest, options: ReExportOptions, isDryRun: boolean, config: FireForgeConfig, adjacentCtx: AdjacentUnmanagedContext, driftCtx: ForeignDriftContext, lintCtx: ReExportLintContext): Promise<boolean>;
39
+ export declare function reExportSinglePatchWithIndexLockRetry(args: ReExportSinglePatchArgs): Promise<boolean>;
@@ -5,7 +5,6 @@
5
5
  * loop calls {@link reExportSinglePatchWithIndexLockRetry} per selected
6
6
  * patch.
7
7
  */
8
- import { getProjectPaths } from '../core/config.js';
9
8
  import { stdioIsInteractive } from '../core/destructive.js';
10
9
  import { enforceFreshFurnaceSources } from '../core/furnace-stale-export.js';
11
10
  import { getDiffForFilesAgainstHead } from '../core/git-diff.js';
@@ -14,6 +13,7 @@ import { updatePatchAndMetadata } from '../core/patch-export.js';
14
13
  import { buildProjectedManifest, enforcePatchPolicy } from '../core/patch-policy.js';
15
14
  import { isGitIndexLockConflict } from '../errors/git.js';
16
15
  import { info, success, warn } from '../utils/logger.js';
16
+ import { sleep } from '../utils/sleep.js';
17
17
  import { findMissingFiles, reportAdjacentUnmanagedFiles } from './re-export-adjacent.js';
18
18
  import { reportForeignDrift } from './re-export-drift.js';
19
19
  import { lintReExportedPatch, refreshQueueCtxEntry, storeReExportLintResult, } from './re-export-lint.js';
@@ -26,20 +26,21 @@ const GIT_INDEX_LOCK_RETRY_MS = 300;
26
26
  * from the top, so it re-reads clean state; a second lock failure
27
27
  * propagates to the loop's honest "N of M" accounting.
28
28
  */
29
- export async function reExportSinglePatchWithIndexLockRetry(patch, paths, manifest, options, isDryRun, config, adjacentCtx, driftCtx, lintCtx) {
29
+ export async function reExportSinglePatchWithIndexLockRetry(args) {
30
30
  try {
31
- return await reExportSinglePatch(patch, paths, manifest, options, isDryRun, config, adjacentCtx, driftCtx, lintCtx);
31
+ return await reExportSinglePatch(args);
32
32
  }
33
33
  catch (error) {
34
34
  if (!isGitIndexLockConflict(error)) {
35
35
  throw error;
36
36
  }
37
- warn(`${patch.filename}: git index.lock contention detected — retrying once after ${String(GIT_INDEX_LOCK_RETRY_MS)} ms...`);
38
- await new Promise((resolve) => setTimeout(resolve, GIT_INDEX_LOCK_RETRY_MS));
39
- return await reExportSinglePatch(patch, paths, manifest, options, isDryRun, config, adjacentCtx, driftCtx, lintCtx);
37
+ warn(`${args.patch.filename}: git index.lock contention detected — retrying once after ${GIT_INDEX_LOCK_RETRY_MS} ms...`);
38
+ await sleep(GIT_INDEX_LOCK_RETRY_MS);
39
+ return await reExportSinglePatch(args);
40
40
  }
41
41
  }
42
- async function reExportSinglePatch(patch, paths, manifest, options, isDryRun, config, adjacentCtx, driftCtx, lintCtx) {
42
+ async function reExportSinglePatch(args) {
43
+ const { patch, paths, manifest, options, isDryRun, config, adjacentCtx, driftCtx, lintCtx } = args;
43
44
  let currentFilesAffected = [...patch.filesAffected];
44
45
  // --- Scan for new/removed files ---
45
46
  if (options.scan) {
@@ -219,10 +220,17 @@ async function reExportSinglePatch(patch, paths, manifest, options, isDryRun, co
219
220
  */
220
221
  async function commitRefreshedPatch(args) {
221
222
  const { patch, paths, manifest, options, config, lintCtx, diffContent, updates } = args;
222
- const bodyChanged = await updatePatchAndMetadata(paths.patches, patch.filename, diffContent, updates, undefined, {
223
- config,
224
- command: 're-export',
225
- forceUnsafe: options.forceUnsafe === true,
223
+ const bodyChanged = await updatePatchAndMetadata({
224
+ patchesDir: paths.patches,
225
+ filename: patch.filename,
226
+ newContent: diffContent,
227
+ updates,
228
+ onCommitted: undefined,
229
+ policyGate: {
230
+ config,
231
+ command: 're-export',
232
+ forceUnsafe: options.forceUnsafe === true,
233
+ },
226
234
  });
227
235
  refreshQueueCtxEntry(lintCtx, patch.filename, diffContent);
228
236
  if (args.lintResult !== null) {