@hominis/fireforge 0.35.0 → 0.36.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 (355) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +34 -9
  3. package/dist/bin/fireforge.js +21 -6
  4. package/dist/src/cli.js +22 -5
  5. package/dist/src/commands/bootstrap-checks.js +0 -1
  6. package/dist/src/commands/bootstrap.d.ts +2 -1
  7. package/dist/src/commands/bootstrap.js +16 -7
  8. package/dist/src/commands/build.js +4 -3
  9. package/dist/src/commands/config.js +30 -10
  10. package/dist/src/commands/discard.js +18 -3
  11. package/dist/src/commands/doctor/post-rebase-audit.js +0 -1
  12. package/dist/src/commands/doctor-check-core.js +0 -1
  13. package/dist/src/commands/doctor-external-toolchains.d.ts +3 -0
  14. package/dist/src/commands/doctor-external-toolchains.js +54 -0
  15. package/dist/src/commands/doctor-furnace-jar.js +0 -1
  16. package/dist/src/commands/doctor-furnace-manifest-sync.js +0 -1
  17. package/dist/src/commands/doctor-furnace.js +2 -2
  18. package/dist/src/commands/doctor-working-tree.js +0 -1
  19. package/dist/src/commands/doctor.js +4 -1
  20. package/dist/src/commands/download.js +28 -13
  21. package/dist/src/commands/export-all.js +5 -3
  22. package/dist/src/commands/export-flow.js +12 -7
  23. package/dist/src/commands/export-placement-gate.js +0 -1
  24. package/dist/src/commands/export-placement-policy.js +0 -1
  25. package/dist/src/commands/export-shared.d.ts +5 -1
  26. package/dist/src/commands/export-shared.js +18 -6
  27. package/dist/src/commands/export.js +15 -10
  28. package/dist/src/commands/furnace/apply.js +22 -6
  29. package/dist/src/commands/furnace/chrome-doc-remove.js +0 -1
  30. package/dist/src/commands/furnace/chrome-doc-templates.js +0 -1
  31. package/dist/src/commands/furnace/chrome-doc-tests.js +0 -1
  32. package/dist/src/commands/furnace/chrome-doc.js +0 -1
  33. package/dist/src/commands/furnace/create-browser-test.js +0 -1
  34. package/dist/src/commands/furnace/create-dry-run.js +0 -1
  35. package/dist/src/commands/furnace/create-features.js +0 -1
  36. package/dist/src/commands/furnace/create-mochikit.js +0 -1
  37. package/dist/src/commands/furnace/create-readback.js +0 -1
  38. package/dist/src/commands/furnace/create-templates.js +0 -1
  39. package/dist/src/commands/furnace/create-validation.js +0 -1
  40. package/dist/src/commands/furnace/create-xpcshell.js +0 -1
  41. package/dist/src/commands/furnace/create.js +0 -1
  42. package/dist/src/commands/furnace/deploy.js +7 -83
  43. package/dist/src/commands/furnace/diff.js +0 -1
  44. package/dist/src/commands/furnace/index.js +59 -25
  45. package/dist/src/commands/furnace/init.js +0 -1
  46. package/dist/src/commands/furnace/list.js +0 -1
  47. package/dist/src/commands/furnace/override.js +0 -1
  48. package/dist/src/commands/furnace/preview.js +3 -3
  49. package/dist/src/commands/furnace/refresh.js +0 -1
  50. package/dist/src/commands/furnace/remove-state.d.ts +15 -0
  51. package/dist/src/commands/furnace/remove-state.js +47 -1
  52. package/dist/src/commands/furnace/remove.js +3 -9
  53. package/dist/src/commands/furnace/rename-browser-test.js +1 -5
  54. package/dist/src/commands/furnace/rename-helpers.d.ts +6 -0
  55. package/dist/src/commands/furnace/rename-helpers.js +30 -1
  56. package/dist/src/commands/furnace/rename-xpcshell.js +7 -6
  57. package/dist/src/commands/furnace/rename.js +15 -36
  58. package/dist/src/commands/furnace/scan.js +0 -1
  59. package/dist/src/commands/furnace/status.js +0 -1
  60. package/dist/src/commands/furnace/sync.js +76 -4
  61. package/dist/src/commands/furnace/validate.js +0 -1
  62. package/dist/src/commands/furnace/validation-output.js +0 -1
  63. package/dist/src/commands/import.js +35 -9
  64. package/dist/src/commands/lint-per-patch.js +19 -7
  65. package/dist/src/commands/lint.js +13 -3
  66. package/dist/src/commands/manifest.js +1 -2
  67. package/dist/src/commands/package.js +2 -1
  68. package/dist/src/commands/patch/compact.js +1 -1
  69. package/dist/src/commands/patch/delete.js +1 -1
  70. package/dist/src/commands/patch/index.js +1 -1
  71. package/dist/src/commands/patch/lint-ignore.js +21 -6
  72. package/dist/src/commands/patch/move-files.js +1 -1
  73. package/dist/src/commands/patch/patch-context.js +0 -1
  74. package/dist/src/commands/patch/rename.js +4 -4
  75. package/dist/src/commands/patch/reorder.js +3 -4
  76. package/dist/src/commands/patch/split-plan.js +2 -1
  77. package/dist/src/commands/patch/split.js +4 -4
  78. package/dist/src/commands/patch/staged-dependency.js +1 -1
  79. package/dist/src/commands/patch/tier.js +18 -5
  80. package/dist/src/commands/re-export-bulk-scan.js +0 -1
  81. package/dist/src/commands/re-export-files.js +1 -1
  82. package/dist/src/commands/re-export-options.js +0 -1
  83. package/dist/src/commands/re-export-scan.js +0 -1
  84. package/dist/src/commands/re-export.js +6 -4
  85. package/dist/src/commands/rebase/abort.js +2 -9
  86. package/dist/src/commands/rebase/confirm.js +0 -1
  87. package/dist/src/commands/rebase/conflict-summary.js +0 -1
  88. package/dist/src/commands/rebase/continue.js +0 -1
  89. package/dist/src/commands/rebase/index.d.ts +1 -1
  90. package/dist/src/commands/rebase/index.js +10 -15
  91. package/dist/src/commands/rebase/patch-loop.js +38 -7
  92. package/dist/src/commands/rebase/summary.js +2 -3
  93. package/dist/src/commands/rebase.js +0 -1
  94. package/dist/src/commands/register.js +1 -1
  95. package/dist/src/commands/reset.js +16 -9
  96. package/dist/src/commands/resolve.js +2 -2
  97. package/dist/src/commands/run.js +4 -4
  98. package/dist/src/commands/setup-support.js +0 -1
  99. package/dist/src/commands/setup.js +1 -2
  100. package/dist/src/commands/source.js +1 -2
  101. package/dist/src/commands/status-output.js +1 -12
  102. package/dist/src/commands/status.js +147 -133
  103. package/dist/src/commands/test-appdir.js +0 -1
  104. package/dist/src/commands/test-diagnose.js +18 -2
  105. package/dist/src/commands/test-modes.d.ts +15 -0
  106. package/dist/src/commands/test-modes.js +61 -0
  107. package/dist/src/commands/test-register.d.ts +4 -0
  108. package/dist/src/commands/test-register.js +64 -0
  109. package/dist/src/commands/test-run.d.ts +23 -7
  110. package/dist/src/commands/test-run.js +15 -14
  111. package/dist/src/commands/test.d.ts +0 -4
  112. package/dist/src/commands/test.js +85 -86
  113. package/dist/src/commands/token-coverage.js +0 -1
  114. package/dist/src/commands/token.js +1 -2
  115. package/dist/src/commands/typecheck.js +1 -1
  116. package/dist/src/commands/verify.js +3 -9
  117. package/dist/src/commands/watch.js +5 -1
  118. package/dist/src/commands/wire.js +1 -1
  119. package/dist/src/core/ast-utils.js +0 -1
  120. package/dist/src/core/brand-validation.js +0 -1
  121. package/dist/src/core/branding.js +0 -1
  122. package/dist/src/core/browser-wire.js +0 -1
  123. package/dist/src/core/build-audit-platform.js +0 -1
  124. package/dist/src/core/build-audit-registration.js +0 -1
  125. package/dist/src/core/build-audit-resolve.js +0 -1
  126. package/dist/src/core/build-audit-transforms.js +0 -1
  127. package/dist/src/core/build-audit.js +0 -1
  128. package/dist/src/core/build-baseline-types.js +0 -1
  129. package/dist/src/core/build-baseline.js +0 -1
  130. package/dist/src/core/build-prepare.js +2 -2
  131. package/dist/src/core/config-mutate.js +0 -1
  132. package/dist/src/core/config-paths.d.ts +2 -2
  133. package/dist/src/core/config-paths.js +6 -1
  134. package/dist/src/core/config-state.js +0 -1
  135. package/dist/src/core/config-validate-patch-policy.js +0 -1
  136. package/dist/src/core/config-validate-test-toolchains.d.ts +8 -0
  137. package/dist/src/core/config-validate-test-toolchains.js +105 -0
  138. package/dist/src/core/config-validate.js +3 -1
  139. package/dist/src/core/config.js +0 -1
  140. package/dist/src/core/destructive.js +0 -1
  141. package/dist/src/core/diff-hunks.js +13 -7
  142. package/dist/src/core/engine-changes.js +0 -1
  143. package/dist/src/core/engine-session-lock.d.ts +12 -0
  144. package/dist/src/core/engine-session-lock.js +45 -0
  145. package/dist/src/core/file-lock.d.ts +8 -0
  146. package/dist/src/core/file-lock.js +157 -48
  147. package/dist/src/core/firefox-archive.d.ts +4 -0
  148. package/dist/src/core/firefox-archive.js +6 -1
  149. package/dist/src/core/firefox-cache.js +60 -4
  150. package/dist/src/core/firefox-download.js +0 -1
  151. package/dist/src/core/firefox-extract.js +75 -16
  152. package/dist/src/core/firefox-ignorefile.js +0 -1
  153. package/dist/src/core/firefox.d.ts +12 -0
  154. package/dist/src/core/firefox.js +64 -2
  155. package/dist/src/core/furnace-apply-dry-run.js +0 -1
  156. package/dist/src/core/furnace-apply-ftl.d.ts +7 -3
  157. package/dist/src/core/furnace-apply-ftl.js +10 -5
  158. package/dist/src/core/furnace-apply-helpers.js +0 -1
  159. package/dist/src/core/furnace-apply-output.js +2 -2
  160. package/dist/src/core/furnace-apply.d.ts +7 -1
  161. package/dist/src/core/furnace-apply.js +47 -13
  162. package/dist/src/core/furnace-checksum-utils.js +0 -1
  163. package/dist/src/core/furnace-config-array-utils.js +0 -1
  164. package/dist/src/core/furnace-config-custom.js +0 -1
  165. package/dist/src/core/furnace-config-order.js +0 -1
  166. package/dist/src/core/furnace-config-tokens.js +0 -1
  167. package/dist/src/core/furnace-config-validate.js +0 -1
  168. package/dist/src/core/furnace-config.d.ts +11 -0
  169. package/dist/src/core/furnace-config.js +37 -1
  170. package/dist/src/core/furnace-constants.js +0 -1
  171. package/dist/src/core/furnace-css-fragments.js +0 -1
  172. package/dist/src/core/furnace-graph-utils.js +0 -1
  173. package/dist/src/core/furnace-jsconfig.d.ts +2 -3
  174. package/dist/src/core/furnace-jsconfig.js +25 -18
  175. package/dist/src/core/furnace-marker.js +0 -1
  176. package/dist/src/core/furnace-operation.js +0 -1
  177. package/dist/src/core/furnace-refresh.js +0 -1
  178. package/dist/src/core/furnace-registration-ast.d.ts +14 -0
  179. package/dist/src/core/furnace-registration-ast.js +1 -2
  180. package/dist/src/core/furnace-registration-remove.js +0 -1
  181. package/dist/src/core/furnace-registration-validate.js +1 -5
  182. package/dist/src/core/furnace-registration.js +0 -1
  183. package/dist/src/core/furnace-rollback.js +0 -1
  184. package/dist/src/core/furnace-scanner.js +0 -1
  185. package/dist/src/core/furnace-staleness.js +0 -1
  186. package/dist/src/core/furnace-state-persist.d.ts +72 -0
  187. package/dist/src/core/furnace-state-persist.js +118 -0
  188. package/dist/src/core/furnace-step-errors.d.ts +23 -0
  189. package/dist/src/core/furnace-step-errors.js +25 -0
  190. package/dist/src/core/furnace-stories.js +0 -1
  191. package/dist/src/core/furnace-validate-accessibility.js +0 -1
  192. package/dist/src/core/furnace-validate-checks.js +0 -1
  193. package/dist/src/core/furnace-validate-compatibility.js +0 -1
  194. package/dist/src/core/furnace-validate-helpers.js +0 -1
  195. package/dist/src/core/furnace-validate-registration.js +7 -7
  196. package/dist/src/core/furnace-validate-structure.d.ts +8 -0
  197. package/dist/src/core/furnace-validate-structure.js +11 -2
  198. package/dist/src/core/furnace-validate.js +0 -1
  199. package/dist/src/core/furnace-version-drift.d.ts +6 -3
  200. package/dist/src/core/furnace-version-drift.js +6 -4
  201. package/dist/src/core/git-base.js +0 -1
  202. package/dist/src/core/git-diff.js +0 -1
  203. package/dist/src/core/git-file-ops.js +0 -1
  204. package/dist/src/core/git-status.d.ts +13 -0
  205. package/dist/src/core/git-status.js +32 -2
  206. package/dist/src/core/git.d.ts +10 -1
  207. package/dist/src/core/git.js +63 -6
  208. package/dist/src/core/license-headers.js +0 -1
  209. package/dist/src/core/lint-cache.js +0 -1
  210. package/dist/src/core/lint-projection.js +0 -1
  211. package/dist/src/core/mach-build-artifacts.js +0 -1
  212. package/dist/src/core/mach-error-hints.js +0 -1
  213. package/dist/src/core/mach-mozconfig.js +0 -1
  214. package/dist/src/core/mach-python.js +0 -1
  215. package/dist/src/core/mach-resource-shim.js +0 -1
  216. package/dist/src/core/mach.js +0 -1
  217. package/dist/src/core/manifest-helpers.js +0 -1
  218. package/dist/src/core/manifest-register.js +0 -1
  219. package/dist/src/core/manifest-rules.js +0 -1
  220. package/dist/src/core/manifest-tokenizers.js +0 -1
  221. package/dist/src/core/marionette-port.d.ts +8 -0
  222. package/dist/src/core/marionette-port.js +34 -3
  223. package/dist/src/core/marionette-preflight.js +0 -1
  224. package/dist/src/core/markdown-table.js +0 -1
  225. package/dist/src/core/ownership-table.js +2 -9
  226. package/dist/src/core/parser-fallback.js +0 -1
  227. package/dist/src/core/patch-apply-fuzz.d.ts +27 -9
  228. package/dist/src/core/patch-apply-fuzz.js +71 -34
  229. package/dist/src/core/patch-apply.d.ts +30 -0
  230. package/dist/src/core/patch-apply.js +101 -18
  231. package/dist/src/core/patch-artifact-normalize.d.ts +18 -5
  232. package/dist/src/core/patch-artifact-normalize.js +19 -15
  233. package/dist/src/core/patch-export-coverage.js +0 -1
  234. package/dist/src/core/patch-export-metadata.js +0 -1
  235. package/dist/src/core/patch-export-update.js +2 -3
  236. package/dist/src/core/patch-export.d.ts +2 -6
  237. package/dist/src/core/patch-export.js +24 -48
  238. package/dist/src/core/patch-files.js +0 -1
  239. package/dist/src/core/patch-identifier-suggest.js +0 -1
  240. package/dist/src/core/patch-lint-checkjs.js +0 -1
  241. package/dist/src/core/patch-lint-chrome-jsdoc.js +0 -1
  242. package/dist/src/core/patch-lint-cross.js +0 -1
  243. package/dist/src/core/patch-lint-css.js +0 -1
  244. package/dist/src/core/patch-lint-diff-tag.js +0 -1
  245. package/dist/src/core/patch-lint-diff.d.ts +2 -1
  246. package/dist/src/core/patch-lint-diff.js +17 -32
  247. package/dist/src/core/patch-lint-imports.js +0 -1
  248. package/dist/src/core/patch-lint-jsdoc.js +0 -1
  249. package/dist/src/core/patch-lint-observer.js +0 -1
  250. package/dist/src/core/patch-lint-ownership.js +0 -1
  251. package/dist/src/core/patch-lint-reexports.js +0 -1
  252. package/dist/src/core/patch-lint.d.ts +37 -2
  253. package/dist/src/core/patch-lint.js +47 -34
  254. package/dist/src/core/patch-lock.js +0 -1
  255. package/dist/src/core/patch-manifest-consistency.js +9 -1
  256. package/dist/src/core/patch-manifest-io.d.ts +16 -0
  257. package/dist/src/core/patch-manifest-io.js +30 -5
  258. package/dist/src/core/patch-manifest-query.js +18 -10
  259. package/dist/src/core/patch-manifest-resolve.js +0 -1
  260. package/dist/src/core/patch-manifest-validate.js +0 -1
  261. package/dist/src/core/patch-manifest.d.ts +1 -1
  262. package/dist/src/core/patch-manifest.js +1 -2
  263. package/dist/src/core/patch-parse.d.ts +59 -0
  264. package/dist/src/core/patch-parse.js +209 -92
  265. package/dist/src/core/patch-policy.js +0 -1
  266. package/dist/src/core/patch-registration-refs.js +16 -26
  267. package/dist/src/core/patch-source-metadata.js +0 -1
  268. package/dist/src/core/patch-transform.d.ts +4 -0
  269. package/dist/src/core/patch-transform.js +30 -38
  270. package/dist/src/core/rebase-session.js +0 -1
  271. package/dist/src/core/register-browser-content.js +0 -1
  272. package/dist/src/core/register-module.js +0 -1
  273. package/dist/src/core/register-result.js +0 -1
  274. package/dist/src/core/register-scaffold.js +0 -1
  275. package/dist/src/core/register-shared-css.js +0 -1
  276. package/dist/src/core/register-test-manifest.js +0 -1
  277. package/dist/src/core/register-xpcshell-test.js +0 -1
  278. package/dist/src/core/shared-ftl.js +0 -1
  279. package/dist/src/core/signal-critical.js +0 -1
  280. package/dist/src/core/smoke-patterns.js +0 -1
  281. package/dist/src/core/state-file.js +0 -1
  282. package/dist/src/core/status-classify.d.ts +16 -0
  283. package/dist/src/core/status-classify.js +24 -16
  284. package/dist/src/core/test-harness-crash.d.ts +64 -7
  285. package/dist/src/core/test-harness-crash.js +196 -15
  286. package/dist/src/core/test-harness-output.js +0 -1
  287. package/dist/src/core/test-path-scope.d.ts +21 -13
  288. package/dist/src/core/test-path-scope.js +43 -28
  289. package/dist/src/core/test-stale-check.js +0 -1
  290. package/dist/src/core/test-stale-symlink.js +0 -1
  291. package/dist/src/core/test-xpcshell-retry.js +0 -1
  292. package/dist/src/core/token-coverage.js +0 -1
  293. package/dist/src/core/token-dark-mode.js +0 -1
  294. package/dist/src/core/token-docs.js +0 -1
  295. package/dist/src/core/token-manager.js +0 -1
  296. package/dist/src/core/token-scaffold.js +0 -1
  297. package/dist/src/core/token-variant.js +0 -1
  298. package/dist/src/core/toolchain-preflight.d.ts +31 -10
  299. package/dist/src/core/toolchain-preflight.js +97 -36
  300. package/dist/src/core/typecheck-shim.js +13 -1
  301. package/dist/src/core/typecheck.js +0 -1
  302. package/dist/src/core/wire-destroy.js +0 -1
  303. package/dist/src/core/wire-dom-fragment.js +0 -1
  304. package/dist/src/core/wire-init.js +0 -1
  305. package/dist/src/core/wire-subscript.js +0 -1
  306. package/dist/src/core/wire-targets.js +0 -1
  307. package/dist/src/core/wire-utils.js +0 -1
  308. package/dist/src/core/xpcshell-appdir.js +1 -4
  309. package/dist/src/errors/base.d.ts +29 -0
  310. package/dist/src/errors/base.js +35 -1
  311. package/dist/src/errors/build.js +0 -1
  312. package/dist/src/errors/codes.d.ts +6 -0
  313. package/dist/src/errors/codes.js +6 -1
  314. package/dist/src/errors/config.js +0 -1
  315. package/dist/src/errors/download.js +0 -1
  316. package/dist/src/errors/furnace.d.ts +11 -1
  317. package/dist/src/errors/furnace.js +6 -5
  318. package/dist/src/errors/git.js +0 -1
  319. package/dist/src/errors/patch.d.ts +17 -0
  320. package/dist/src/errors/patch.js +29 -1
  321. package/dist/src/errors/rebase.js +0 -1
  322. package/dist/src/errors/run.js +1 -1
  323. package/dist/src/index.js +0 -1
  324. package/dist/src/types/cli.js +0 -1
  325. package/dist/src/types/commands/index.js +0 -1
  326. package/dist/src/types/commands/options.d.ts +13 -5
  327. package/dist/src/types/commands/options.js +0 -1
  328. package/dist/src/types/commands/patches.d.ts +8 -0
  329. package/dist/src/types/commands/patches.js +0 -1
  330. package/dist/src/types/commands/project.d.ts +11 -2
  331. package/dist/src/types/commands/project.js +0 -1
  332. package/dist/src/types/config.d.ts +32 -0
  333. package/dist/src/types/config.js +0 -1
  334. package/dist/src/types/furnace.d.ts +10 -0
  335. package/dist/src/types/furnace.js +0 -1
  336. package/dist/src/types/index.js +0 -1
  337. package/dist/src/types/typecheck.js +0 -1
  338. package/dist/src/utils/elapsed.js +0 -1
  339. package/dist/src/utils/errors.js +0 -1
  340. package/dist/src/utils/fs.js +37 -5
  341. package/dist/src/utils/logger.d.ts +7 -0
  342. package/dist/src/utils/logger.js +85 -3
  343. package/dist/src/utils/options.d.ts +15 -0
  344. package/dist/src/utils/options.js +25 -1
  345. package/dist/src/utils/package-root.js +11 -4
  346. package/dist/src/utils/parse.js +0 -1
  347. package/dist/src/utils/paths.js +0 -1
  348. package/dist/src/utils/platform.js +0 -1
  349. package/dist/src/utils/process.d.ts +33 -4
  350. package/dist/src/utils/process.js +183 -28
  351. package/dist/src/utils/regex.d.ts +13 -3
  352. package/dist/src/utils/regex.js +53 -4
  353. package/dist/src/utils/validation.d.ts +0 -12
  354. package/dist/src/utils/validation.js +13 -26
  355. package/package.json +8 -7
@@ -14,7 +14,7 @@ import { exec } from '../utils/process.js';
14
14
  import { applyPatchIdempotent, reversePatch } from './git.js';
15
15
  import { getFileContentFromHead } from './git-file-ops.js';
16
16
  import { discoverPatches } from './patch-files.js';
17
- import { findPatchesAffectingFile } from './patch-manifest.js';
17
+ import { findPatchesAffectingFile, loadPatchesManifest } from './patch-manifest.js';
18
18
  import { extractAffectedFiles, extractConflictingFiles, isNewFileInPatch } from './patch-parse.js';
19
19
  import { applyPatchToContent, extractNewFileContent } from './patch-transform.js';
20
20
  // Re-export from split modules so existing import sites continue working
@@ -27,16 +27,23 @@ export { applyPatchToContent, extractNewFileContent } from './patch-transform.js
27
27
  * Applies a single patch.
28
28
  * @param patch - Patch info
29
29
  * @param engineDir - Path to the engine directory
30
+ * @param protectedFiles - Files touched by patches already applied in the
31
+ * current run; the idempotent-recovery reset must not wipe them (see
32
+ * applyPatchIdempotent). Overlapping queues (`--allow-overlap`) share
33
+ * files between patches, and a later patch's recovery used to reset the
34
+ * shared file to HEAD — silently discarding the earlier patch's changes.
30
35
  * @returns Patch result
31
36
  */
32
- async function applySinglePatch(patch, engineDir) {
37
+ async function applySinglePatch(patch, engineDir, protectedFiles) {
33
38
  let patchContent = '';
34
39
  let affectedFiles = [];
35
40
  try {
36
41
  patchContent = await readText(patch.path);
37
42
  affectedFiles = extractAffectedFiles(patchContent);
38
43
  await validatePatchTargets(patch, affectedFiles, engineDir);
39
- await applyPatchIdempotent(patch.path, engineDir);
44
+ await applyPatchIdempotent(patch.path, engineDir, {
45
+ ...(protectedFiles ? { protectedFiles } : {}),
46
+ });
40
47
  return { patch, success: true };
41
48
  }
42
49
  catch (error) {
@@ -48,21 +55,39 @@ async function applySinglePatch(patch, engineDir) {
48
55
  let resolvedNewFiles = false;
49
56
  // Save original content for files we might overwrite, so we can restore on failure
50
57
  const savedContents = new Map();
51
- for (const file of affectedFiles) {
52
- if (isNewFileInPatch(patchContent, file)) {
53
- const targetPath = join(engineDir, file);
54
- if (await pathExists(targetPath)) {
55
- savedContents.set(file, await readText(targetPath));
56
- const content = await extractNewFileContent(patch.path, file);
57
- await writeText(targetPath, content);
58
- resolvedNewFiles = true;
58
+ try {
59
+ for (const file of affectedFiles) {
60
+ if (isNewFileInPatch(patchContent, file)) {
61
+ const targetPath = join(engineDir, file);
62
+ if (await pathExists(targetPath)) {
63
+ savedContents.set(file, await readText(targetPath));
64
+ const content = await extractNewFileContent(patch.path, file);
65
+ await writeText(targetPath, content);
66
+ resolvedNewFiles = true;
67
+ }
59
68
  }
60
69
  }
61
70
  }
71
+ catch (extractError) {
72
+ // extractNewFileContent threw for a LATER target (e.g. a binary
73
+ // section) after earlier targets were already overwritten — restore
74
+ // them before reporting failure, instead of letting the exception
75
+ // skip the restore loop entirely and crash the whole import.
76
+ for (const [file, originalContent] of savedContents) {
77
+ await writeText(join(engineDir, file), originalContent);
78
+ }
79
+ return { patch, success: false, error: toError(extractError).message };
80
+ }
62
81
  if (resolvedNewFiles) {
63
82
  try {
64
- await applyPatchIdempotent(patch.path, engineDir);
65
- return { patch, success: true, autoResolved: true };
83
+ await applyPatchIdempotent(patch.path, engineDir, {
84
+ ...(protectedFiles ? { protectedFiles } : {}),
85
+ });
86
+ // Keep the originals: rollbackPatches needs them if a LATER patch
87
+ // fails, since reverse-applying this new-file patch deletes the
88
+ // target and would otherwise permanently discard the pre-existing
89
+ // content (recoverable from git only if it was tracked at HEAD).
90
+ return { patch, success: true, autoResolved: true, autoResolvedOriginals: savedContents };
66
91
  }
67
92
  catch (retryError) {
68
93
  verbose(`Auto-resolved new-file retry failed for ${patch.filename}: ${toError(retryError).message}`);
@@ -76,7 +101,10 @@ async function applySinglePatch(patch, engineDir) {
76
101
  let errorMessage = applyError.message;
77
102
  try {
78
103
  // Use --reject to apply what we can and create .rej files for what we can't
79
- await applyPatchIdempotent(patch.path, engineDir, { reject: true });
104
+ await applyPatchIdempotent(patch.path, engineDir, {
105
+ reject: true,
106
+ ...(protectedFiles ? { protectedFiles } : {}),
107
+ });
80
108
  // If this somehow succeeds with --reject but failed without, it still shouldn't
81
109
  // happen because applyPatch first runs --check which would fail.
82
110
  // But if it did succeed, we should still return failure because manual fix is needed
@@ -105,6 +133,19 @@ async function rollbackPatches(results, engineDir) {
105
133
  catch (rollbackError) {
106
134
  verbose(`Failed to roll back ${result.patch.filename}: ${toError(rollbackError).message}`);
107
135
  }
136
+ // Reversing an auto-resolved new-file patch deletes the target; put
137
+ // back the pre-existing content the auto-resolve overwrote.
138
+ if (result.autoResolvedOriginals) {
139
+ for (const [file, originalContent] of result.autoResolvedOriginals) {
140
+ try {
141
+ await writeText(join(engineDir, file), originalContent);
142
+ verbose(`Restored pre-existing content of ${file} after rollback`);
143
+ }
144
+ catch (restoreError) {
145
+ verbose(`Could not restore ${file}: ${toError(restoreError).message}`);
146
+ }
147
+ }
148
+ }
108
149
  }
109
150
  }
110
151
  /**
@@ -117,9 +158,15 @@ async function rollbackPatches(results, engineDir) {
117
158
  export async function applyPatches(patchesDir, engineDir) {
118
159
  const patches = await discoverPatches(patchesDir);
119
160
  const results = [];
161
+ const appliedFiles = new Set();
120
162
  for (const patch of patches) {
121
- const result = await applySinglePatch(patch, engineDir);
163
+ const result = await applySinglePatch(patch, engineDir, appliedFiles);
122
164
  results.push(result);
165
+ if (result.success) {
166
+ for (const file of extractAffectedFiles(await readText(patch.path))) {
167
+ appliedFiles.add(file);
168
+ }
169
+ }
123
170
  // Stop on first failure and roll back all previously applied patches
124
171
  if (!result.success) {
125
172
  const succeeded = results.filter((r) => r.success);
@@ -237,7 +284,7 @@ async function resolvePatchWriteDestination(targetPath) {
237
284
  * short-circuit was kept behind the numeric gate so the match stays
238
285
  * single-meaning per identifier.
239
286
  */
240
- function matchesUntilFilename(patchFilename, needle) {
287
+ export function matchesUntilFilename(patchFilename, needle) {
241
288
  const isNumeric = /^\d+$/.test(needle);
242
289
  if (isNumeric) {
243
290
  const order = parseInt(needle, 10);
@@ -298,6 +345,7 @@ export async function applyPatchesWithContinue(patchesDir, engineDir, optionsOrC
298
345
  const succeeded = [];
299
346
  const failed = [];
300
347
  const skipped = [];
348
+ const appliedFiles = new Set();
301
349
  for (let i = 0; i < patches.length; i++) {
302
350
  const patch = patches[i];
303
351
  if (!patch)
@@ -309,9 +357,12 @@ export async function applyPatchesWithContinue(patchesDir, engineDir, optionsOrC
309
357
  skipped.push(patch);
310
358
  continue;
311
359
  }
312
- const result = await applySinglePatch(patch, engineDir);
360
+ const result = await applySinglePatch(patch, engineDir, appliedFiles);
313
361
  if (result.success) {
314
362
  succeeded.push(result);
363
+ for (const file of extractAffectedFiles(await readText(patch.path))) {
364
+ appliedFiles.add(file);
365
+ }
315
366
  }
316
367
  else {
317
368
  // Try to extract conflicting files from error message
@@ -362,4 +413,36 @@ export async function computePatchedContent(patchesDir, engineDir, filePath) {
362
413
  }
363
414
  return content;
364
415
  }
365
- //# sourceMappingURL=patch-apply.js.map
416
+ /**
417
+ * Builds a batched variant of {@link computePatchedContent} that loads the
418
+ * manifest and discovers patch files ONCE for many lookups. The per-call
419
+ * function re-runs `loadPatchesManifest` + `discoverPatches` for every
420
+ * file — O(dirtyFiles × patches) redundant IO when classifying a broad
421
+ * engine edit session during `import`.
422
+ */
423
+ export async function createPatchedContentComputer(patchesDir, engineDir) {
424
+ const manifest = await loadPatchesManifest(patchesDir);
425
+ const patches = await discoverPatches(patchesDir);
426
+ const patchByFilename = new Map(patches.map((p) => [p.filename, p]));
427
+ const affectingByFile = new Map();
428
+ for (const metadata of manifest?.patches ?? []) {
429
+ const patch = patchByFilename.get(metadata.filename);
430
+ if (!patch)
431
+ continue;
432
+ for (const file of metadata.filesAffected) {
433
+ const list = affectingByFile.get(file) ?? [];
434
+ list.push(patch);
435
+ affectingByFile.set(file, list);
436
+ }
437
+ }
438
+ for (const list of affectingByFile.values()) {
439
+ list.sort((a, b) => a.order - b.order);
440
+ }
441
+ return async (filePath) => {
442
+ let content = await getFileContentFromHead(engineDir, filePath);
443
+ for (const patch of affectingByFile.get(filePath) ?? []) {
444
+ content = await applyPatchToContent(content, patch.path, filePath);
445
+ }
446
+ return content;
447
+ };
448
+ }
@@ -1,10 +1,23 @@
1
1
  /**
2
2
  * Normalizes generated patch files before they are written to disk.
3
3
  *
4
- * Kept as a narrow chokepoint for future artifact-level fixes. It must keep
5
- * unified-diff hunk markers intact, but marker-only blank payload lines should
6
- * not carry extra trailing whitespace (`"+ "` / `"- "` / `" "`), because raw
7
- * repository whitespace checks flag those generated patch artifact lines even
8
- * when the engine diff is clean.
4
+ * Currently the identity function, kept as a narrow chokepoint for future
5
+ * artifact-level fixes so every export path (`commitExportedPatch`,
6
+ * `commitPlacementExport`, `updatePatch`, `patch split`) funnels through
7
+ * one place.
8
+ *
9
+ * History: this used to strip marker lines whose payload was pure
10
+ * whitespace (`/^[ +-]\s+$/` → bare marker) to appease repository
11
+ * trailing-whitespace checks. That corrupted real content — Firefox
12
+ * sources contain whitespace-only lines, so a ` `/`-` line whose payload
13
+ * was e.g. two spaces no longer matched the pristine tree (the freshly
14
+ * exported patch failed `git apply --check`), and a `+` line silently
15
+ * changed what the patch produces, making re-import diverge from the
16
+ * engine state it was exported from (2026-07-05 review, finding M2).
17
+ * The whitespace check itself excludes `patches/*.patch`
18
+ * (scripts/check-worktree-whitespace.mjs), so the trimming had no
19
+ * remaining justification. Patch bodies are now written byte-for-byte as
20
+ * git produced them — including the single-space rendering of blank
21
+ * context lines, which `re-export.integration.test.ts` pins.
9
22
  */
10
23
  export declare function normalizePatchArtifact(content: string): string;
@@ -2,21 +2,25 @@
2
2
  /**
3
3
  * Normalizes generated patch files before they are written to disk.
4
4
  *
5
- * Kept as a narrow chokepoint for future artifact-level fixes. It must keep
6
- * unified-diff hunk markers intact, but marker-only blank payload lines should
7
- * not carry extra trailing whitespace (`"+ "` / `"- "` / `" "`), because raw
8
- * repository whitespace checks flag those generated patch artifact lines even
9
- * when the engine diff is clean.
5
+ * Currently the identity function, kept as a narrow chokepoint for future
6
+ * artifact-level fixes so every export path (`commitExportedPatch`,
7
+ * `commitPlacementExport`, `updatePatch`, `patch split`) funnels through
8
+ * one place.
9
+ *
10
+ * History: this used to strip marker lines whose payload was pure
11
+ * whitespace (`/^[ +-]\s+$/` → bare marker) to appease repository
12
+ * trailing-whitespace checks. That corrupted real content — Firefox
13
+ * sources contain whitespace-only lines, so a ` `/`-` line whose payload
14
+ * was e.g. two spaces no longer matched the pristine tree (the freshly
15
+ * exported patch failed `git apply --check`), and a `+` line silently
16
+ * changed what the patch produces, making re-import diverge from the
17
+ * engine state it was exported from (2026-07-05 review, finding M2).
18
+ * The whitespace check itself excludes `patches/*.patch`
19
+ * (scripts/check-worktree-whitespace.mjs), so the trimming had no
20
+ * remaining justification. Patch bodies are now written byte-for-byte as
21
+ * git produced them — including the single-space rendering of blank
22
+ * context lines, which `re-export.integration.test.ts` pins.
10
23
  */
11
24
  export function normalizePatchArtifact(content) {
12
- return content
13
- .split('\n')
14
- .map((line) => {
15
- if (/^[ +-]\s+$/.test(line)) {
16
- return line[0] ?? line;
17
- }
18
- return line;
19
- })
20
- .join('\n');
25
+ return content;
21
26
  }
22
- //# sourceMappingURL=patch-artifact-normalize.js.map
@@ -100,4 +100,3 @@ export async function findAllPatchesForFilesWithDetails(patchesDir, targetFiles,
100
100
  }
101
101
  return results;
102
102
  }
103
- //# sourceMappingURL=patch-export-coverage.js.map
@@ -36,4 +36,3 @@ export async function mutatePatchMetadata(patchesDir, filename, mutator) {
36
36
  return { before: changed.before, after: changed.after };
37
37
  });
38
38
  }
39
- //# sourceMappingURL=patch-export-metadata.js.map
@@ -5,7 +5,7 @@ import { pathExists, readText, writeText } from '../utils/fs.js';
5
5
  import { warn } from '../utils/logger.js';
6
6
  import { withPatchDirectoryLock } from './patch-apply.js';
7
7
  import { normalizePatchArtifact } from './patch-artifact-normalize.js';
8
- import { loadPatchesManifest, mutatePatchRowsInManifest } from './patch-manifest.js';
8
+ import { loadPatchesManifestForWrite, mutatePatchRowsInManifest } from './patch-manifest.js';
9
9
  import { buildProjectedManifest, enforcePatchPolicy } from './patch-policy.js';
10
10
  /**
11
11
  * Updates a patch file body and its manifest row under the same patch
@@ -14,7 +14,7 @@ import { buildProjectedManifest, enforcePatchPolicy } from './patch-policy.js';
14
14
  */
15
15
  export async function updatePatchAndMetadata(patchesDir, filename, newContent, updates, onCommitted, policyGate) {
16
16
  await withPatchDirectoryLock(patchesDir, async () => {
17
- const manifest = await loadPatchesManifest(patchesDir);
17
+ const manifest = await loadPatchesManifestForWrite(patchesDir);
18
18
  if (!manifest) {
19
19
  throw new Error('Cannot update patch metadata: patches.json is missing.');
20
20
  }
@@ -65,4 +65,3 @@ export async function updatePatchAndMetadata(patchesDir, filename, newContent, u
65
65
  }
66
66
  });
67
67
  }
68
- //# sourceMappingURL=patch-export-update.js.map
@@ -30,6 +30,8 @@ export declare function sanitizeName(name: string): string;
30
30
  * double-prefix incident) also collapses. Exported for direct testing.
31
31
  */
32
32
  export declare function stripRedundantCategoryPrefix(sanitizedName: string, category: string): string;
33
+ /** Sanitizes a patch name and drops redundant category/full-stem prefixes. */
34
+ export declare function patchNameSlug(name: string, category: string): string;
33
35
  /**
34
36
  * Generates the next patch filename with category.
35
37
  * @param patchesDir - Path to the patches directory
@@ -99,12 +101,6 @@ export declare function findExistingPatchForFile(patchesDir: string, filePath: s
99
101
  * @param newContent - New patch content
100
102
  */
101
103
  export declare function updatePatch(patchPath: string, newContent: string): Promise<void>;
102
- /**
103
- * Deletes a patch file and removes it from the manifest.
104
- * @param patchesDir - Path to the patches directory
105
- * @param filename - Patch filename to delete
106
- */
107
- export declare function deletePatch(patchesDir: string, filename: string): Promise<void>;
108
104
  /**
109
105
  * Fully computed plan for a pending export. Returned from
110
106
  * {@link planExport} so that `--dry-run` previews can render the full
@@ -1,5 +1,4 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
- import { unlink } from 'node:fs/promises';
3
2
  import { join } from 'node:path';
4
3
  import { toError } from '../utils/errors.js';
5
4
  import { pathExists, readText, removeFile, writeText } from '../utils/fs.js';
@@ -8,7 +7,7 @@ import { PATCH_CATEGORIES } from '../utils/validation.js';
8
7
  import { discoverPatches, withPatchDirectoryLock } from './patch-apply.js';
9
8
  import { normalizePatchArtifact } from './patch-artifact-normalize.js';
10
9
  import { findAllPatchesForFilesWithDetails, } from './patch-export-coverage.js';
11
- import { addPatchToManifest, loadPatchesManifest, PATCHES_MANIFEST, savePatchesManifest, } from './patch-manifest.js';
10
+ import { addPatchToManifest, loadPatchesManifestForWrite, PATCHES_MANIFEST, savePatchesManifest, } from './patch-manifest.js';
12
11
  import { allocatePolicyOrder, enforcePatchPolicy } from './patch-policy.js';
13
12
  export { findAllPatchesForFiles, findAllPatchesForFilesWithDetails, findSupersededPatches, isPatchFullyCovered, } from './patch-export-coverage.js';
14
13
  export { mutatePatchMetadata, updatePatchMetadata, } from './patch-export-metadata.js';
@@ -46,6 +45,9 @@ export function sanitizeName(name) {
46
45
  .replace(/^-+|-+$/g, '')
47
46
  .slice(0, 50);
48
47
  }
48
+ function escapeRegExp(value) {
49
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
50
+ }
49
51
  /**
50
52
  * Strips a leading `NNN-<category>-` prefix from a sanitized name slug.
51
53
  * Operators frequently pass the DESIRED FILENAME stem to `--name`
@@ -56,15 +58,25 @@ export function sanitizeName(name) {
56
58
  * double-prefix incident) also collapses. Exported for direct testing.
57
59
  */
58
60
  export function stripRedundantCategoryPrefix(sanitizedName, category) {
59
- const prefix = new RegExp(`^\\d+-${category}-`);
61
+ const prefixes = [
62
+ new RegExp(`^\\d+-${escapeRegExp(category)}-`),
63
+ new RegExp(`^${escapeRegExp(category)}-`),
64
+ ];
60
65
  let stripped = sanitizedName;
61
- while (prefix.test(stripped)) {
62
- stripped = stripped.replace(prefix, '');
66
+ let changed = true;
67
+ while (changed) {
68
+ changed = false;
69
+ for (const prefix of prefixes) {
70
+ if (prefix.test(stripped)) {
71
+ stripped = stripped.replace(prefix, '');
72
+ changed = true;
73
+ }
74
+ }
63
75
  }
64
76
  return stripped.length > 0 ? stripped : sanitizedName;
65
77
  }
66
- /** Sanitizes a patch name and drops a redundant `NNN-<category>-` prefix. */
67
- function patchNameSlug(name, category) {
78
+ /** Sanitizes a patch name and drops redundant category/full-stem prefixes. */
79
+ export function patchNameSlug(name, category) {
68
80
  return stripRedundantCategoryPrefix(sanitizeName(name), category);
69
81
  }
70
82
  /**
@@ -221,45 +233,6 @@ export async function findExistingPatchForFile(patchesDir, filePath) {
221
233
  export async function updatePatch(patchPath, newContent) {
222
234
  await writeText(patchPath, normalizePatchArtifact(newContent));
223
235
  }
224
- /**
225
- * Deletes a patch file and removes it from the manifest.
226
- * @param patchesDir - Path to the patches directory
227
- * @param filename - Patch filename to delete
228
- */
229
- export async function deletePatch(patchesDir, filename) {
230
- await withPatchDirectoryLock(patchesDir, async () => {
231
- const patchPath = join(patchesDir, filename);
232
- const manifest = await loadPatchesManifest(patchesDir);
233
- const updatedManifest = manifest
234
- ? {
235
- ...manifest,
236
- patches: manifest.patches.filter((patch) => patch.filename !== filename),
237
- }
238
- : null;
239
- // Update manifest first so interrupted deletions leave an explicit repairable
240
- // extra patch file rather than silently dropping metadata for an absent file.
241
- if (updatedManifest) {
242
- await savePatchesManifest(patchesDir, updatedManifest);
243
- }
244
- if (!(await pathExists(patchPath))) {
245
- return;
246
- }
247
- try {
248
- await unlink(patchPath);
249
- }
250
- catch (error) {
251
- if (manifest) {
252
- try {
253
- await savePatchesManifest(patchesDir, manifest);
254
- }
255
- catch (error) {
256
- warn(`Failed to restore manifest after patch deletion error for "${filename}": ${toError(error).message}`);
257
- }
258
- }
259
- throw error;
260
- }
261
- });
262
- }
263
236
  /**
264
237
  * Internal planning helper. Does NOT take the patch directory lock — the
265
238
  * caller must already hold it — because the two public entry points
@@ -269,7 +242,11 @@ export async function deletePatch(patchesDir, filename) {
269
242
  * instead of by parallel implementations that can drift.
270
243
  */
271
244
  async function computeExportPlanUnderLock(input) {
272
- const manifestBefore = await loadPatchesManifest(input.patchesDir);
245
+ // ForWrite: a corrupt manifest read as null here would produce a
246
+ // manifestAfter containing ONLY the new patch — committing that plan
247
+ // wipes the queue metadata, and the rollback path would then delete
248
+ // patches.json entirely because manifestBefore looked absent.
249
+ const manifestBefore = await loadPatchesManifestForWrite(input.patchesDir);
273
250
  const policyOrder = input.config !== undefined
274
251
  ? allocatePolicyOrder(input.config, manifestBefore?.patches ?? [], input.category)
275
252
  : null;
@@ -341,4 +318,3 @@ export async function planExport(input) {
341
318
  };
342
319
  });
343
320
  }
344
- //# sourceMappingURL=patch-export.js.map
@@ -51,4 +51,3 @@ export async function getAllTargetFilesFromPatch(patchPath) {
51
51
  const content = await readText(patchPath);
52
52
  return extractAffectedFiles(content);
53
53
  }
54
- //# sourceMappingURL=patch-files.js.map
@@ -105,4 +105,3 @@ export function formatPatchNotFoundError(identifier, patches) {
105
105
  }
106
106
  return `${lead} No close match found among ${patches.length} patches in the queue. Run "fireforge patch list" to see them.`;
107
107
  }
108
- //# sourceMappingURL=patch-identifier-suggest.js.map
@@ -414,4 +414,3 @@ export async function invokePatchLintCheckJsGrouped(repoDir, patchOwnedFiles, pa
414
414
  : { strict };
415
415
  return runCheckJsGrouped(repoDir, patchOwnedFiles, patchLint.checkJsExtraShim, projectRoot, mode);
416
416
  }
417
- //# sourceMappingURL=patch-lint-checkjs.js.map
@@ -84,4 +84,3 @@ export function lintChromeScriptJsDocForFile(file, content, isChromeOwned, mode)
84
84
  severity: issue.severity ?? mode,
85
85
  }));
86
86
  }
87
- //# sourceMappingURL=patch-lint-chrome-jsdoc.js.map
@@ -495,4 +495,3 @@ export function lintPatchQueueForwardImports(ctx) {
495
495
  export function lintPatchQueue(ctx) {
496
496
  return [...lintPatchQueueDuplicateCreations(ctx), ...lintPatchQueueForwardImports(ctx)];
497
497
  }
498
- //# sourceMappingURL=patch-lint-cross.js.map
@@ -169,4 +169,3 @@ export async function lintPatchedCss(repoDir, affectedFiles, diffContent, config
169
169
  }
170
170
  return issues;
171
171
  }
172
- //# sourceMappingURL=patch-lint-css.js.map
@@ -105,4 +105,3 @@ export function tagLintIssues(issues, diffFiles) {
105
105
  }
106
106
  return issues;
107
107
  }
108
- //# sourceMappingURL=patch-lint-diff-tag.js.map
@@ -6,7 +6,8 @@
6
6
  * cross-patch lint body (in `patch-lint-cross.ts`) can both depend on
7
7
  * the same diff walkers without inducing a circular import. Callers
8
8
  * should keep importing these through `patch-lint.ts` — this file is
9
- * an implementation detail.
9
+ * an implementation detail. The actual line-walking lives in
10
+ * `parseDiffSections` (patch-parse.ts), the codebase's single diff walker.
10
11
  */
11
12
  /**
12
13
  * Extracts new-file paths from a unified diff by scanning for `new file mode` markers.
@@ -7,23 +7,18 @@
7
7
  * cross-patch lint body (in `patch-lint-cross.ts`) can both depend on
8
8
  * the same diff walkers without inducing a circular import. Callers
9
9
  * should keep importing these through `patch-lint.ts` — this file is
10
- * an implementation detail.
10
+ * an implementation detail. The actual line-walking lives in
11
+ * `parseDiffSections` (patch-parse.ts), the codebase's single diff walker.
11
12
  */
13
+ import { parseDiffSections } from './patch-parse.js';
12
14
  /**
13
15
  * Extracts new-file paths from a unified diff by scanning for `new file mode` markers.
14
16
  */
15
17
  export function detectNewFilesInDiff(diffContent) {
16
18
  const newFiles = new Set();
17
- const lines = diffContent.split('\n');
18
- let currentFile = null;
19
- for (const line of lines) {
20
- if (line.startsWith('diff --git')) {
21
- const match = /^diff --git a\/.+ b\/(.+)$/.exec(line);
22
- currentFile = match?.[1] ?? null;
23
- continue;
24
- }
25
- if (line.startsWith('new file mode') && currentFile) {
26
- newFiles.add(currentFile);
19
+ for (const section of parseDiffSections(diffContent)) {
20
+ if (section.isNewFile) {
21
+ newFiles.add(section.targetPath);
27
22
  }
28
23
  }
29
24
  return newFiles;
@@ -34,27 +29,18 @@ export function detectNewFilesInDiff(diffContent) {
34
29
  */
35
30
  export function extractAddedLinesPerFile(diffContent) {
36
31
  const result = new Map();
37
- const lines = diffContent.split('\n');
38
- let currentFile = null;
39
- let inHunk = false;
40
- for (const line of lines) {
41
- if (line.startsWith('diff --git')) {
42
- const match = /^diff --git a\/.+ b\/(.+)$/.exec(line);
43
- currentFile = match?.[1] ?? null;
44
- inHunk = false;
45
- continue;
46
- }
47
- if (line.startsWith('@@')) {
48
- inHunk = true;
49
- continue;
50
- }
51
- if (inHunk && currentFile && line.startsWith('+') && !line.startsWith('+++')) {
52
- let arr = result.get(currentFile);
53
- if (!arr) {
54
- arr = [];
55
- result.set(currentFile, arr);
32
+ for (const section of parseDiffSections(diffContent)) {
33
+ for (const hunk of section.hunks) {
34
+ for (const line of hunk.lines) {
35
+ if (!line.startsWith('+'))
36
+ continue;
37
+ let arr = result.get(section.targetPath);
38
+ if (!arr) {
39
+ arr = [];
40
+ result.set(section.targetPath, arr);
41
+ }
42
+ arr.push(line.slice(1));
56
43
  }
57
- arr.push(line.slice(1));
58
44
  }
59
45
  }
60
46
  return result;
@@ -81,4 +67,3 @@ export function buildModifiedFileAdditionsFromDiff(diff) {
81
67
  }
82
68
  return result;
83
69
  }
84
- //# sourceMappingURL=patch-lint-diff.js.map
@@ -65,4 +65,3 @@ export function hasRelativeImport(content) {
65
65
  }
66
66
  return RELATIVE_IMPORT_FALLBACK_PATTERN.test(stripJsComments(content));
67
67
  }
68
- //# sourceMappingURL=patch-lint-imports.js.map
@@ -450,4 +450,3 @@ export function validateExportJsDoc(source, options) {
450
450
  }
451
451
  return issues;
452
452
  }
453
- //# sourceMappingURL=patch-lint-jsdoc.js.map
@@ -165,4 +165,3 @@ export function lintObserverTopics(strippedContent, file, binaryName) {
165
165
  }
166
166
  return issues;
167
167
  }
168
- //# sourceMappingURL=patch-lint-observer.js.map
@@ -67,4 +67,3 @@ export function resolvePatchOwnedSysMjs(currentNewFiles, patchQueueCtx) {
67
67
  export function resolvePatchOwnedChromeScripts(currentNewFiles, patchQueueCtx) {
68
68
  return resolveOwned(currentNewFiles, patchQueueCtx, (file) => file.endsWith('.js') && !file.endsWith('.sys.mjs'));
69
69
  }
70
- //# sourceMappingURL=patch-lint-ownership.js.map
@@ -5,4 +5,3 @@
5
5
  */
6
6
  export { buildPatchQueueContext, collectForwardImportEdges, collectNewFileCreatorsByPath, extractImportSpecifiers, extractImportSpecifiersWithLines, findForwardImportIgnoreLines, FORWARD_IMPORT_IGNORE_MARKER, isForwardImportableFile, lintPatchQueue, lintPatchQueueDuplicateCreations, lintPatchQueueForwardImports, } from './patch-lint-cross.js';
7
7
  export { buildModifiedFileAdditionsFromDiff, detectNewFilesInDiff } from './patch-lint-diff.js';
8
- //# sourceMappingURL=patch-lint-reexports.js.map