@hominis/fireforge 0.49.2 → 0.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (400) hide show
  1. package/CHANGELOG.md +87 -1
  2. package/README.md +1 -1
  3. package/dist/bin/fireforge.js +6 -0
  4. package/dist/build-info.json +4 -4
  5. package/dist/src/commands/build-prelock.d.ts +20 -0
  6. package/dist/src/commands/build-prelock.js +50 -0
  7. package/dist/src/commands/build.d.ts +3 -1
  8. package/dist/src/commands/build.js +13 -6
  9. package/dist/src/commands/check-report.d.ts +41 -0
  10. package/dist/src/commands/check-report.js +91 -0
  11. package/dist/src/commands/check-run.d.ts +33 -0
  12. package/dist/src/commands/check-run.js +112 -0
  13. package/dist/src/commands/check-steps.d.ts +75 -0
  14. package/dist/src/commands/check-steps.js +200 -0
  15. package/dist/src/commands/check.d.ts +32 -0
  16. package/dist/src/commands/check.js +160 -0
  17. package/dist/src/commands/config.js +5 -6
  18. package/dist/src/commands/declarations-report.d.ts +43 -0
  19. package/dist/src/commands/declarations-report.js +76 -0
  20. package/dist/src/commands/doctor-engine-eslint.js +6 -1
  21. package/dist/src/commands/doctor-source-pin.js +4 -1
  22. package/dist/src/commands/export-adopt-check.d.ts +38 -0
  23. package/dist/src/commands/export-adopt-check.js +98 -0
  24. package/dist/src/commands/export-flow.js +29 -14
  25. package/dist/src/commands/export-placement-gate.d.ts +6 -2
  26. package/dist/src/commands/export-placement-gate.js +8 -0
  27. package/dist/src/commands/export-shared.d.ts +2 -1
  28. package/dist/src/commands/export-staged-registration.d.ts +46 -0
  29. package/dist/src/commands/export-staged-registration.js +91 -0
  30. package/dist/src/commands/forward-import-remedy.d.ts +32 -0
  31. package/dist/src/commands/forward-import-remedy.js +63 -0
  32. package/dist/src/commands/furnace/apply.js +3 -3
  33. package/dist/src/commands/furnace/chrome-doc-remove.d.ts +2 -1
  34. package/dist/src/commands/furnace/chrome-doc-remove.js +2 -2
  35. package/dist/src/commands/furnace/chrome-doc.d.ts +2 -1
  36. package/dist/src/commands/furnace/chrome-doc.js +2 -2
  37. package/dist/src/commands/furnace/create.js +2 -2
  38. package/dist/src/commands/furnace/deploy-named.d.ts +48 -0
  39. package/dist/src/commands/furnace/deploy-named.js +107 -0
  40. package/dist/src/commands/furnace/deploy.d.ts +3 -2
  41. package/dist/src/commands/furnace/deploy.js +50 -86
  42. package/dist/src/commands/furnace/diff.js +26 -3
  43. package/dist/src/commands/furnace/index.js +66 -41
  44. package/dist/src/commands/furnace/init.js +13 -6
  45. package/dist/src/commands/furnace/override.js +4 -2
  46. package/dist/src/commands/furnace/preview.js +4 -3
  47. package/dist/src/commands/furnace/refresh.js +2 -2
  48. package/dist/src/commands/furnace/remove.js +4 -3
  49. package/dist/src/commands/furnace/rename-dry-run.d.ts +29 -0
  50. package/dist/src/commands/furnace/rename-dry-run.js +127 -0
  51. package/dist/src/commands/furnace/rename-helpers.d.ts +7 -0
  52. package/dist/src/commands/furnace/rename-helpers.js +15 -1
  53. package/dist/src/commands/furnace/rename-registrations.d.ts +41 -0
  54. package/dist/src/commands/furnace/rename-registrations.js +98 -0
  55. package/dist/src/commands/furnace/rename.d.ts +2 -1
  56. package/dist/src/commands/furnace/rename.js +28 -66
  57. package/dist/src/commands/furnace/scan.d.ts +9 -4
  58. package/dist/src/commands/furnace/scan.js +11 -9
  59. package/dist/src/commands/furnace/sync.js +3 -2
  60. package/dist/src/commands/furnace/validate-report.d.ts +45 -0
  61. package/dist/src/commands/furnace/validate-report.js +91 -0
  62. package/dist/src/commands/furnace/validate.d.ts +1 -1
  63. package/dist/src/commands/furnace/validate.js +151 -113
  64. package/dist/src/commands/furnace/validation-output.js +14 -3
  65. package/dist/src/commands/lint-per-patch.js +36 -61
  66. package/dist/src/commands/lint-per-run-checkjs.js +2 -2
  67. package/dist/src/commands/lint-report.d.ts +8 -4
  68. package/dist/src/commands/lint-report.js +20 -5
  69. package/dist/src/commands/lint.js +15 -34
  70. package/dist/src/commands/manifest.js +4 -0
  71. package/dist/src/commands/park.d.ts +22 -0
  72. package/dist/src/commands/park.js +232 -0
  73. package/dist/src/commands/patch/delete-gap-notice.js +3 -1
  74. package/dist/src/commands/patch/delete.d.ts +1 -1
  75. package/dist/src/commands/patch/delete.js +24 -104
  76. package/dist/src/commands/patch/index.d.ts +3 -3
  77. package/dist/src/commands/patch/index.js +10 -4
  78. package/dist/src/commands/patch/lint-ignore-from.d.ts +44 -0
  79. package/dist/src/commands/patch/lint-ignore-from.js +142 -0
  80. package/dist/src/commands/patch/lint-ignore.d.ts +5 -1
  81. package/dist/src/commands/patch/lint-ignore.js +78 -31
  82. package/dist/src/commands/patch/move-files-create.js +34 -18
  83. package/dist/src/commands/patch/move-files-into.js +15 -7
  84. package/dist/src/commands/patch/move-files.js +6 -2
  85. package/dist/src/commands/patch/removal-created-files.d.ts +48 -0
  86. package/dist/src/commands/patch/removal-created-files.js +83 -0
  87. package/dist/src/commands/patch/removal-dependents.d.ts +63 -0
  88. package/dist/src/commands/patch/removal-dependents.js +133 -0
  89. package/dist/src/commands/patch/rename.js +20 -1
  90. package/dist/src/commands/patch/reserve.d.ts +33 -0
  91. package/dist/src/commands/patch/reserve.js +152 -0
  92. package/dist/src/commands/patch/retire.d.ts +34 -0
  93. package/dist/src/commands/patch/retire.js +133 -0
  94. package/dist/src/commands/patch/split-plan.d.ts +17 -8
  95. package/dist/src/commands/patch/split-plan.js +60 -28
  96. package/dist/src/commands/patch/split-staged.d.ts +108 -0
  97. package/dist/src/commands/patch/split-staged.js +202 -0
  98. package/dist/src/commands/patch/split.d.ts +5 -0
  99. package/dist/src/commands/patch/split.js +62 -17
  100. package/dist/src/commands/patch/stage.d.ts +34 -0
  101. package/dist/src/commands/patch/stage.js +153 -0
  102. package/dist/src/commands/patch/staged-dependency-batch.d.ts +48 -0
  103. package/dist/src/commands/patch/staged-dependency-batch.js +176 -0
  104. package/dist/src/commands/patch/staged-dependency-entries.d.ts +36 -0
  105. package/dist/src/commands/patch/staged-dependency-entries.js +52 -0
  106. package/dist/src/commands/patch/staged-dependency-from.d.ts +14 -0
  107. package/dist/src/commands/patch/staged-dependency-from.js +108 -0
  108. package/dist/src/commands/patch/staged-dependency-remove.d.ts +31 -0
  109. package/dist/src/commands/patch/staged-dependency-remove.js +80 -0
  110. package/dist/src/commands/patch/staged-dependency.js +54 -139
  111. package/dist/src/commands/re-export-adjacent-registrations.d.ts +20 -0
  112. package/dist/src/commands/re-export-adjacent-registrations.js +75 -0
  113. package/dist/src/commands/re-export-adjacent.d.ts +50 -2
  114. package/dist/src/commands/re-export-adjacent.js +160 -40
  115. package/dist/src/commands/re-export-bulk-scan.d.ts +2 -1
  116. package/dist/src/commands/re-export-bulk-scan.js +22 -7
  117. package/dist/src/commands/re-export-drift.d.ts +36 -8
  118. package/dist/src/commands/re-export-drift.js +73 -49
  119. package/dist/src/commands/re-export-files.js +11 -8
  120. package/dist/src/commands/re-export-hunks.d.ts +35 -0
  121. package/dist/src/commands/re-export-hunks.js +220 -0
  122. package/dist/src/commands/re-export-lint.d.ts +2 -1
  123. package/dist/src/commands/re-export-lint.js +23 -10
  124. package/dist/src/commands/re-export-options.js +74 -6
  125. package/dist/src/commands/re-export-preflight.d.ts +23 -0
  126. package/dist/src/commands/re-export-preflight.js +160 -0
  127. package/dist/src/commands/re-export-purity-scope.d.ts +18 -0
  128. package/dist/src/commands/re-export-purity-scope.js +131 -0
  129. package/dist/src/commands/re-export-register.js +56 -17
  130. package/dist/src/commands/re-export-removed.d.ts +13 -2
  131. package/dist/src/commands/re-export-removed.js +57 -12
  132. package/dist/src/commands/re-export-scan-owner.d.ts +44 -0
  133. package/dist/src/commands/re-export-scan-owner.js +116 -0
  134. package/dist/src/commands/re-export-scan.d.ts +19 -9
  135. package/dist/src/commands/re-export-scan.js +60 -24
  136. package/dist/src/commands/re-export-single.js +122 -46
  137. package/dist/src/commands/re-export-stale-registrations.d.ts +18 -0
  138. package/dist/src/commands/re-export-stale-registrations.js +62 -0
  139. package/dist/src/commands/re-export-summary.d.ts +28 -0
  140. package/dist/src/commands/re-export-summary.js +36 -0
  141. package/dist/src/commands/re-export.js +71 -57
  142. package/dist/src/commands/rebase/index.js +17 -17
  143. package/dist/src/commands/run.js +16 -2
  144. package/dist/src/commands/status-check.d.ts +55 -16
  145. package/dist/src/commands/status-check.js +92 -37
  146. package/dist/src/commands/status-json.d.ts +22 -9
  147. package/dist/src/commands/status-json.js +47 -32
  148. package/dist/src/commands/status-output.js +3 -1
  149. package/dist/src/commands/status-scope.d.ts +37 -0
  150. package/dist/src/commands/status-scope.js +54 -0
  151. package/dist/src/commands/status.js +66 -23
  152. package/dist/src/commands/test-browser-preflight.js +19 -3
  153. package/dist/src/commands/test-build-furnace.d.ts +22 -0
  154. package/dist/src/commands/test-build-furnace.js +54 -0
  155. package/dist/src/commands/test-build-phase.js +64 -41
  156. package/dist/src/commands/test-checkjs.js +10 -2
  157. package/dist/src/commands/test-diagnose.js +7 -0
  158. package/dist/src/commands/test-dispatch.d.ts +39 -0
  159. package/dist/src/commands/test-dispatch.js +211 -0
  160. package/dist/src/commands/test-furnace-reach.d.ts +37 -0
  161. package/dist/src/commands/test-furnace-reach.js +132 -0
  162. package/dist/src/commands/test-modes.d.ts +16 -1
  163. package/dist/src/commands/test-modes.js +24 -1
  164. package/dist/src/commands/test-multi-verdict.d.ts +61 -0
  165. package/dist/src/commands/test-multi-verdict.js +111 -0
  166. package/dist/src/commands/test-orchestrate.d.ts +52 -0
  167. package/dist/src/commands/test-orchestrate.js +240 -0
  168. package/dist/src/commands/test-perf-scope.d.ts +50 -0
  169. package/dist/src/commands/test-perf-scope.js +138 -0
  170. package/dist/src/commands/test-prelock.d.ts +51 -0
  171. package/dist/src/commands/test-prelock.js +273 -0
  172. package/dist/src/commands/test-register.js +69 -10
  173. package/dist/src/commands/test-results.d.ts +62 -0
  174. package/dist/src/commands/test-results.js +62 -0
  175. package/dist/src/commands/test-run.d.ts +13 -55
  176. package/dist/src/commands/test-run.js +74 -99
  177. package/dist/src/commands/test-shards.d.ts +110 -0
  178. package/dist/src/commands/test-shards.js +255 -0
  179. package/dist/src/commands/test-stale-gate.d.ts +5 -1
  180. package/dist/src/commands/test-stale-gate.js +60 -28
  181. package/dist/src/commands/test-verdict.d.ts +11 -1
  182. package/dist/src/commands/test-verdict.js +28 -1
  183. package/dist/src/commands/test.d.ts +6 -1
  184. package/dist/src/commands/test.js +120 -124
  185. package/dist/src/commands/token-lint.d.ts +14 -0
  186. package/dist/src/commands/token-lint.js +151 -0
  187. package/dist/src/commands/token.js +10 -0
  188. package/dist/src/commands/verify-scope.d.ts +28 -0
  189. package/dist/src/commands/verify-scope.js +67 -0
  190. package/dist/src/commands/verify.d.ts +10 -2
  191. package/dist/src/commands/verify.js +67 -36
  192. package/dist/src/core/build-baseline-types.d.ts +46 -0
  193. package/dist/src/core/build-baseline-validate.js +19 -0
  194. package/dist/src/core/build-baseline.d.ts +5 -1
  195. package/dist/src/core/build-baseline.js +3 -1
  196. package/dist/src/core/build-overwrite-guard.d.ts +50 -0
  197. package/dist/src/core/build-overwrite-guard.js +84 -4
  198. package/dist/src/core/build-prepare.d.ts +13 -0
  199. package/dist/src/core/build-prepare.js +42 -10
  200. package/dist/src/core/build-run-record.d.ts +74 -0
  201. package/dist/src/core/build-run-record.js +136 -0
  202. package/dist/src/core/checkjs-support-heads.d.ts +41 -0
  203. package/dist/src/core/checkjs-support-heads.js +104 -0
  204. package/dist/src/core/config-paths.d.ts +2 -2
  205. package/dist/src/core/config-paths.js +10 -0
  206. package/dist/src/core/config-validate-check.d.ts +8 -0
  207. package/dist/src/core/config-validate-check.js +219 -0
  208. package/dist/src/core/config-validate-file-size.d.ts +9 -6
  209. package/dist/src/core/config-validate-file-size.js +91 -2
  210. package/dist/src/core/config-validate-history.d.ts +11 -0
  211. package/dist/src/core/config-validate-history.js +38 -0
  212. package/dist/src/core/config-validate-patch-policy.js +100 -0
  213. package/dist/src/core/config-validate-test-toolchains.js +21 -0
  214. package/dist/src/core/config-validate-token-scales.d.ts +9 -0
  215. package/dist/src/core/config-validate-token-scales.js +92 -0
  216. package/dist/src/core/config-validate.js +20 -5
  217. package/dist/src/core/config.d.ts +5 -0
  218. package/dist/src/core/config.js +16 -0
  219. package/dist/src/core/coverage-dispatch.d.ts +24 -0
  220. package/dist/src/core/coverage-dispatch.js +44 -0
  221. package/dist/src/core/coverage-extend.d.ts +70 -45
  222. package/dist/src/core/coverage-extend.js +199 -144
  223. package/dist/src/core/coverage-records.d.ts +108 -0
  224. package/dist/src/core/coverage-records.js +384 -0
  225. package/dist/src/core/destructive.d.ts +7 -4
  226. package/dist/src/core/destructive.js +10 -10
  227. package/dist/src/core/engine-session-lock.d.ts +16 -1
  228. package/dist/src/core/engine-session-lock.js +38 -10
  229. package/dist/src/core/export-moved-code-shape.d.ts +49 -11
  230. package/dist/src/core/export-moved-code-shape.js +135 -44
  231. package/dist/src/core/file-lock-queue.d.ts +67 -0
  232. package/dist/src/core/file-lock-queue.js +221 -0
  233. package/dist/src/core/file-lock-shared.d.ts +57 -0
  234. package/dist/src/core/file-lock-shared.js +117 -0
  235. package/dist/src/core/file-lock.d.ts +13 -25
  236. package/dist/src/core/file-lock.js +163 -140
  237. package/dist/src/core/furnace-apply-output.d.ts +10 -0
  238. package/dist/src/core/furnace-apply-output.js +31 -1
  239. package/dist/src/core/furnace-apply.js +22 -12
  240. package/dist/src/core/furnace-composed-source.d.ts +76 -0
  241. package/dist/src/core/furnace-composed-source.js +167 -0
  242. package/dist/src/core/furnace-config-validate-exceptions.d.ts +16 -0
  243. package/dist/src/core/furnace-config-validate-exceptions.js +102 -0
  244. package/dist/src/core/furnace-config-validate.js +14 -0
  245. package/dist/src/core/furnace-css-fragments.d.ts +10 -0
  246. package/dist/src/core/furnace-css-fragments.js +26 -0
  247. package/dist/src/core/furnace-operation.d.ts +3 -3
  248. package/dist/src/core/furnace-registration-jar-inplace.d.ts +12 -0
  249. package/dist/src/core/furnace-registration-jar-inplace.js +80 -0
  250. package/dist/src/core/furnace-registration-remove.d.ts +22 -1
  251. package/dist/src/core/furnace-registration-remove.js +53 -17
  252. package/dist/src/core/furnace-registration-rename.d.ts +26 -0
  253. package/dist/src/core/furnace-registration-rename.js +77 -0
  254. package/dist/src/core/furnace-registration.d.ts +14 -4
  255. package/dist/src/core/furnace-registration.js +44 -10
  256. package/dist/src/core/furnace-staleness.d.ts +7 -0
  257. package/dist/src/core/furnace-staleness.js +34 -24
  258. package/dist/src/core/furnace-validate-accessibility.d.ts +8 -4
  259. package/dist/src/core/furnace-validate-accessibility.js +71 -28
  260. package/dist/src/core/furnace-validate-exceptions.d.ts +48 -0
  261. package/dist/src/core/furnace-validate-exceptions.js +80 -0
  262. package/dist/src/core/furnace-validate-icon-button.d.ts +7 -0
  263. package/dist/src/core/furnace-validate-icon-button.js +78 -0
  264. package/dist/src/core/furnace-validate.js +1 -1
  265. package/dist/src/core/harness-port-fallback.d.ts +60 -0
  266. package/dist/src/core/harness-port-fallback.js +96 -0
  267. package/dist/src/core/history-log-file.d.ts +61 -0
  268. package/dist/src/core/history-log-file.js +164 -0
  269. package/dist/src/core/history-log.d.ts +2 -1
  270. package/dist/src/core/history-log.js +2 -1
  271. package/dist/src/core/json-preserving-edit.d.ts +22 -0
  272. package/dist/src/core/json-preserving-edit.js +199 -0
  273. package/dist/src/core/line-prefixer.d.ts +18 -0
  274. package/dist/src/core/line-prefixer.js +34 -0
  275. package/dist/src/core/mach.d.ts +7 -0
  276. package/dist/src/core/mach.js +32 -10
  277. package/dist/src/core/module-resolution-preflight.d.ts +19 -3
  278. package/dist/src/core/module-resolution-preflight.js +24 -6
  279. package/dist/src/core/negative-control-compiled.d.ts +27 -0
  280. package/dist/src/core/negative-control-compiled.js +73 -0
  281. package/dist/src/core/negative-control-diff.d.ts +51 -0
  282. package/dist/src/core/negative-control-diff.js +179 -0
  283. package/dist/src/core/negative-control.d.ts +150 -0
  284. package/dist/src/core/negative-control.js +399 -0
  285. package/dist/src/core/objdir-install-manifests.d.ts +73 -0
  286. package/dist/src/core/objdir-install-manifests.js +226 -0
  287. package/dist/src/core/park-store.d.ts +63 -0
  288. package/dist/src/core/park-store.js +216 -0
  289. package/dist/src/core/patch-export-update.d.ts +6 -0
  290. package/dist/src/core/patch-export-update.js +13 -3
  291. package/dist/src/core/patch-export.d.ts +3 -2
  292. package/dist/src/core/patch-export.js +22 -16
  293. package/dist/src/core/patch-files.js +26 -8
  294. package/dist/src/core/patch-lint-checkjs-any-import.d.ts +39 -0
  295. package/dist/src/core/patch-lint-checkjs-any-import.js +63 -0
  296. package/dist/src/core/patch-lint-checkjs-diagnostics.d.ts +17 -0
  297. package/dist/src/core/patch-lint-checkjs-diagnostics.js +32 -0
  298. package/dist/src/core/patch-lint-checkjs.d.ts +10 -1
  299. package/dist/src/core/patch-lint-checkjs.js +20 -13
  300. package/dist/src/core/patch-lint-code-lines.d.ts +59 -0
  301. package/dist/src/core/patch-lint-code-lines.js +105 -0
  302. package/dist/src/core/patch-lint-cross.d.ts +3 -1
  303. package/dist/src/core/patch-lint-cross.js +17 -4
  304. package/dist/src/core/patch-lint-css.d.ts +8 -2
  305. package/dist/src/core/patch-lint-css.js +24 -14
  306. package/dist/src/core/patch-lint-file-size.d.ts +12 -5
  307. package/dist/src/core/patch-lint-file-size.js +22 -11
  308. package/dist/src/core/patch-lint-forward-registration-extended.d.ts +23 -0
  309. package/dist/src/core/patch-lint-forward-registration-extended.js +36 -11
  310. package/dist/src/core/patch-lint-forward-registration.js +10 -0
  311. package/dist/src/core/patch-lint-ignore.d.ts +196 -0
  312. package/dist/src/core/patch-lint-ignore.js +429 -0
  313. package/dist/src/core/patch-lint-ownership.d.ts +10 -0
  314. package/dist/src/core/patch-lint-ownership.js +26 -0
  315. package/dist/src/core/patch-lint-size-thresholds.d.ts +52 -0
  316. package/dist/src/core/patch-lint-size-thresholds.js +110 -0
  317. package/dist/src/core/patch-lint-size.d.ts +117 -0
  318. package/dist/src/core/patch-lint-size.js +331 -0
  319. package/dist/src/core/patch-lint-staged-registration.d.ts +18 -1
  320. package/dist/src/core/patch-lint-staged-registration.js +20 -0
  321. package/dist/src/core/patch-lint-token-scale.d.ts +57 -0
  322. package/dist/src/core/patch-lint-token-scale.js +286 -0
  323. package/dist/src/core/patch-lint.d.ts +11 -83
  324. package/dist/src/core/patch-lint.js +66 -288
  325. package/dist/src/core/patch-lock.js +7 -6
  326. package/dist/src/core/patch-manifest-consistency.js +7 -6
  327. package/dist/src/core/patch-manifest-io.d.ts +6 -3
  328. package/dist/src/core/patch-manifest-io.js +27 -9
  329. package/dist/src/core/patch-manifest-key-order.d.ts +24 -0
  330. package/dist/src/core/patch-manifest-key-order.js +61 -0
  331. package/dist/src/core/patch-manifest-stage.d.ts +46 -0
  332. package/dist/src/core/patch-manifest-stage.js +96 -0
  333. package/dist/src/core/patch-manifest-validate.js +61 -3
  334. package/dist/src/core/patch-order-legal.d.ts +26 -0
  335. package/dist/src/core/patch-order-legal.js +88 -0
  336. package/dist/src/core/patch-policy-content.d.ts +40 -0
  337. package/dist/src/core/patch-policy-content.js +216 -0
  338. package/dist/src/core/patch-policy.d.ts +22 -5
  339. package/dist/src/core/patch-policy.js +26 -12
  340. package/dist/src/core/patch-retired.d.ts +57 -0
  341. package/dist/src/core/patch-retired.js +92 -0
  342. package/dist/src/core/patch-subset.d.ts +16 -0
  343. package/dist/src/core/patch-subset.js +66 -0
  344. package/dist/src/core/preflight-wait.d.ts +20 -0
  345. package/dist/src/core/preflight-wait.js +38 -0
  346. package/dist/src/core/rebase-from-version.d.ts +46 -0
  347. package/dist/src/core/rebase-from-version.js +71 -0
  348. package/dist/src/core/run-log.d.ts +26 -0
  349. package/dist/src/core/run-log.js +106 -4
  350. package/dist/src/core/run-profile.d.ts +7 -0
  351. package/dist/src/core/run-profile.js +7 -1
  352. package/dist/src/core/smoke-patterns.d.ts +5 -0
  353. package/dist/src/core/smoke-patterns.js +13 -0
  354. package/dist/src/core/test-harness-crash.d.ts +19 -1
  355. package/dist/src/core/test-harness-crash.js +8 -9
  356. package/dist/src/core/test-harness-signature.d.ts +33 -0
  357. package/dist/src/core/test-harness-signature.js +23 -1
  358. package/dist/src/core/test-raw-log.d.ts +66 -0
  359. package/dist/src/core/test-raw-log.js +122 -0
  360. package/dist/src/core/test-run-completeness.d.ts +90 -0
  361. package/dist/src/core/test-run-completeness.js +184 -0
  362. package/dist/src/core/test-stale-check.d.ts +27 -1
  363. package/dist/src/core/test-stale-check.js +39 -49
  364. package/dist/src/core/test-stale-exempt.d.ts +40 -0
  365. package/dist/src/core/test-stale-exempt.js +70 -0
  366. package/dist/src/core/test-xpcshell-retry.d.ts +2 -0
  367. package/dist/src/core/test-xpcshell-retry.js +2 -1
  368. package/dist/src/core/token-lint.d.ts +53 -0
  369. package/dist/src/core/token-lint.js +131 -0
  370. package/dist/src/core/token-sibling-sheets.d.ts +46 -0
  371. package/dist/src/core/token-sibling-sheets.js +119 -0
  372. package/dist/src/core/tree-guard.js +5 -0
  373. package/dist/src/core/typecheck-shim.d.ts +6 -2
  374. package/dist/src/core/typecheck-shim.js +205 -4
  375. package/dist/src/core/typecheck.js +3 -1
  376. package/dist/src/index.d.ts +14 -3
  377. package/dist/src/index.js +6 -1
  378. package/dist/src/types/commands/index.d.ts +2 -2
  379. package/dist/src/types/commands/options.d.ts +212 -24
  380. package/dist/src/types/commands/patches.d.ts +118 -1
  381. package/dist/src/types/config.d.ts +264 -0
  382. package/dist/src/types/config.js +9 -0
  383. package/dist/src/types/furnace.d.ts +63 -1
  384. package/dist/src/types/furnace.js +7 -0
  385. package/dist/src/types/index.d.ts +1 -1
  386. package/dist/src/utils/options.d.ts +36 -7
  387. package/dist/src/utils/options.js +94 -8
  388. package/dist/src/utils/path-glob.d.ts +15 -0
  389. package/dist/src/utils/path-glob.js +47 -0
  390. package/dist/src/utils/regex.d.ts +15 -3
  391. package/dist/src/utils/regex.js +37 -16
  392. package/docs/configuration.md +369 -11
  393. package/docs/exit-codes.md +18 -1
  394. package/docs/furnace.md +139 -3
  395. package/docs/lifecycle-invariants.md +65 -30
  396. package/docs/machine-output.md +120 -12
  397. package/docs/patch-workflow.md +458 -22
  398. package/docs/run-logs.md +20 -1
  399. package/docs/testing.md +346 -21
  400. package/package.json +3 -3
@@ -9,7 +9,7 @@
9
9
  * (`re-export-adjacent.ts`) warns about unmanaged neighbour files but is
10
10
  * structurally blind to drift inside owned files. This module compares
11
11
  * the old body against the refreshed body per file and reports the
12
- * payload lines that are about to enter the body. Under
12
+ * payload lines that are about to enter or leave the body. Under
13
13
  * `--refuse-foreign-drift` the patch is skipped and the run exits
14
14
  * non-zero, mirroring `--refuse-adjacent-unmanaged`.
15
15
  *
@@ -74,8 +74,15 @@ function multisetExcess(next, previous) {
74
74
  }
75
75
  return excess;
76
76
  }
77
- function summarizeForeignHunks(payload, foreignAdded, foreignRemoved) {
77
+ /**
78
+ * Summarizes the hunks of `payload` that carry the budgeted `+`/`-` lines.
79
+ * `describe` renders the per-hunk counts, so the old body's dropped lines
80
+ * read in the same engine-edit direction as the new body's absorbed ones.
81
+ */
82
+ function summarizeForeignHunks(payload, foreignAdded, foreignRemoved, describe) {
78
83
  const summaries = [];
84
+ if (payload === undefined)
85
+ return summaries;
79
86
  const addedBudget = new Map(foreignAdded);
80
87
  const removedBudget = new Map(foreignRemoved);
81
88
  for (const section of payload.sections) {
@@ -101,7 +108,7 @@ function summarizeForeignHunks(payload, foreignAdded, foreignRemoved) {
101
108
  }
102
109
  }
103
110
  if (added > 0 || removed > 0) {
104
- summaries.push(`@@ -${hunk.oldStart},${hunk.oldCount} +${hunk.newStart},${hunk.newCount} @@ (+${added}/-${removed})`);
111
+ summaries.push(`@@ -${hunk.oldStart},${hunk.oldCount} +${hunk.newStart},${hunk.newCount} @@ ${describe(added, removed)}`);
105
112
  }
106
113
  }
107
114
  }
@@ -113,47 +120,71 @@ function countExcess(excess) {
113
120
  total += count;
114
121
  return total;
115
122
  }
123
+ const EMPTY_PAYLOAD_LINES = new Map();
116
124
  /**
117
125
  * Pure comparison: per owned file, which `+`/`-` payload lines the
118
- * refreshed body carries that the old body did not. Files not in
119
- * `previousFilesAffected` are skipped, since those are intentional
120
- * adoptions (`--scan`) rather than drift.
126
+ * refreshed body carries that the old body did not, and which the old body
127
+ * carried that the refreshed one dropped. Both directions are drift: a
128
+ * concurrent session deleting a line the patch added changes the patch as
129
+ * surely as one adding a line, and a belt that only saw absorbed lines let
130
+ * such a deletion through silently. Files not in `previousFilesAffected`
131
+ * are skipped, since those are intentional adoptions (`--scan`) or
132
+ * retirements (`--expect-removed`) rather than drift.
121
133
  */
122
134
  export function computeForeignDrift(oldBody, newBody, previousFilesAffected) {
123
135
  const owned = new Set(previousFilesAffected);
124
136
  const oldByFile = collectPayloadByFile(oldBody);
125
137
  const newByFile = collectPayloadByFile(newBody);
126
138
  const drift = [];
127
- for (const [file, next] of newByFile) {
139
+ // New-body order first, then files only the old body carried.
140
+ const files = new Set([...newByFile.keys(), ...oldByFile.keys()]);
141
+ for (const file of files) {
128
142
  if (!owned.has(file))
129
143
  continue;
144
+ const next = newByFile.get(file);
130
145
  const previous = oldByFile.get(file);
131
- if (next.isBinary || previous?.isBinary === true) {
132
- const binaryChanged = next.binaryNewHash === undefined ||
133
- previous?.binaryNewHash === undefined ||
134
- !(next.binaryNewHash.startsWith(previous.binaryNewHash) ||
135
- previous.binaryNewHash.startsWith(next.binaryNewHash));
136
- if (binaryChanged) {
137
- drift.push({ file, addedLines: 0, removedLines: 0, hunkSummaries: [], binaryChanged });
146
+ if (next?.isBinary === true || previous?.isBinary === true) {
147
+ if (binaryContentChanged(next, previous)) {
148
+ drift.push({
149
+ file,
150
+ addedLines: 0,
151
+ removedLines: 0,
152
+ hunkSummaries: [],
153
+ binaryChanged: true,
154
+ });
138
155
  }
139
156
  continue;
140
157
  }
141
- const foreignAdded = multisetExcess(next.added, previous?.added ?? new Map());
142
- const foreignRemoved = multisetExcess(next.removed, previous?.removed ?? new Map());
143
- const addedLines = countExcess(foreignAdded);
144
- const removedLines = countExcess(foreignRemoved);
158
+ const absorbedAdded = multisetExcess(next?.added ?? EMPTY_PAYLOAD_LINES, previous?.added ?? EMPTY_PAYLOAD_LINES);
159
+ const absorbedRemoved = multisetExcess(next?.removed ?? EMPTY_PAYLOAD_LINES, previous?.removed ?? EMPTY_PAYLOAD_LINES);
160
+ const droppedAdded = multisetExcess(previous?.added ?? EMPTY_PAYLOAD_LINES, next?.added ?? EMPTY_PAYLOAD_LINES);
161
+ const droppedRemoved = multisetExcess(previous?.removed ?? EMPTY_PAYLOAD_LINES, next?.removed ?? EMPTY_PAYLOAD_LINES);
162
+ const addedLines = countExcess(absorbedAdded) + countExcess(droppedRemoved);
163
+ const removedLines = countExcess(absorbedRemoved) + countExcess(droppedAdded);
145
164
  if (addedLines === 0 && removedLines === 0)
146
165
  continue;
147
166
  drift.push({
148
167
  file,
149
168
  addedLines,
150
169
  removedLines,
151
- hunkSummaries: summarizeForeignHunks(next, foreignAdded, foreignRemoved).slice(0, MAX_HUNK_SUMMARIES_PER_FILE),
170
+ hunkSummaries: [
171
+ ...summarizeForeignHunks(next, absorbedAdded, absorbedRemoved, (plus, minus) => `(+${plus}/-${minus})`),
172
+ // A dropped `+` line is one the engine lost; a dropped `-` line is an
173
+ // upstream line the engine has back.
174
+ ...summarizeForeignHunks(previous, droppedAdded, droppedRemoved, (plus, minus) => `in the old body, dropped (+${minus}/-${plus})`),
175
+ ].slice(0, MAX_HUNK_SUMMARIES_PER_FILE),
152
176
  binaryChanged: false,
153
177
  });
154
178
  }
155
179
  return drift;
156
180
  }
181
+ /** True when a binary section appeared, vanished, or its new-side blob hash moved. */
182
+ function binaryContentChanged(next, previous) {
183
+ if (next?.binaryNewHash === undefined || previous?.binaryNewHash === undefined)
184
+ return true;
185
+ return !(next.binaryNewHash.startsWith(previous.binaryNewHash) ||
186
+ previous.binaryNewHash.startsWith(next.binaryNewHash));
187
+ }
157
188
  /**
158
189
  * Reads the old body, prints the always-on drift preview (including under
159
190
  * dry-run and `--scan`), and, when `ctx.refuseForeignDrift` is set,
@@ -161,6 +192,10 @@ export function computeForeignDrift(oldBody, newBody, previousFilesAffected) {
161
192
  */
162
193
  export async function reportForeignDrift(args) {
163
194
  const { patch, patchesDir, engineDir, newDiffContent, ctx } = args;
195
+ for (const file of args.adoptedFiles ?? []) {
196
+ if (ctx.expectedDriftFiles.has(file))
197
+ ctx.expectedSeen.add(file);
198
+ }
164
199
  const oldPath = join(patchesDir, patch.filename);
165
200
  let oldBody;
166
201
  try {
@@ -172,7 +207,7 @@ export async function reportForeignDrift(args) {
172
207
  catch (error) {
173
208
  return refuseUnreadableBaseline(ctx, patch.filename, `old body unreadable (${toError(error).message})`);
174
209
  }
175
- const drift = computeForeignDrift(oldBody, newDiffContent, patch.filesAffected);
210
+ const drift = computeForeignDrift(oldBody, newDiffContent, args.ownedFiles ?? patch.filesAffected);
176
211
  ctx.evaluationRuns += 1;
177
212
  if (drift.length === 0)
178
213
  return false;
@@ -184,7 +219,7 @@ export async function reportForeignDrift(args) {
184
219
  const recentlyEdited = await findFilesEditedSinceLastExport(engineDir, oldPath, drift.map((d) => d.file));
185
220
  const totalLines = drift.reduce((sum, d) => sum + d.addedLines + d.removedLines, 0);
186
221
  const lineNoun = totalLines === 1 ? 'line' : 'lines';
187
- warn(`${patch.filename}: refreshed body absorbs ${totalLines} ${lineNoun} not present in the old patch body (${drift.length} file(s)):`);
222
+ warn(`${patch.filename}: refreshed body changes ${totalLines} ${lineNoun} against the old patch body (${drift.length} file(s)):`);
188
223
  for (const entry of drift.slice(0, MAX_FILES_REPORTED)) {
189
224
  const expectedTag = ctx.expectedDriftFiles.has(entry.file) ? ' (expected via --expect)' : '';
190
225
  const authorshipTag = recentlyEdited.has(entry.file)
@@ -194,7 +229,7 @@ export async function reportForeignDrift(args) {
194
229
  info(` ${entry.file}: binary content changed${expectedTag}${authorshipTag}`);
195
230
  continue;
196
231
  }
197
- info(` ${entry.file}: +${entry.addedLines}/-${entry.removedLines} newly captured line(s)${expectedTag}${authorshipTag}`);
232
+ info(` ${entry.file}: +${entry.addedLines} added / -${entry.removedLines} removed line(s)${expectedTag}${authorshipTag}`);
198
233
  for (const summary of entry.hunkSummaries) {
199
234
  info(` ${summary}`);
200
235
  }
@@ -274,31 +309,24 @@ function refuseUnreadableBaseline(ctx, patchFilename, detail) {
274
309
  /**
275
310
  * States, verbatim in the notice, what the `--expect` whitelist is compared
276
311
  * 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:
312
+ * the drift belt looked at the files they named, so it has to describe the
313
+ * measurement exactly:
283
314
  *
284
315
  * - Compared: for each file already in the patch's `filesAffected`, the
285
316
  * `+`/`-` 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.
317
+ * body did not, and the ones the old body carried that the refreshed body
318
+ * dropped (`computeForeignDrift`). It is a content comparison in both
319
+ * directions, so a deletion-only edit is drift too.
288
320
  * - When: per patch, on the body about to be written, before the write.
289
321
  * Never against post-write state.
290
322
  *
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 there is no drift to see. The belt exists to catch a
295
- * concurrent session's edits being silently swallowed into a body, not
296
- * every difference between two bodies. Stating that is the fix. Widening
297
- * the comparison would turn every ordinary line removal into a refusal.
323
+ * An earlier copy compared one direction only, so a correctly captured
324
+ * deletion read as an `--expect` that matched nothing, and a peer's
325
+ * deletion in a file outside `--expect` passed the belt silently.
298
326
  */
299
327
  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.';
328
+ "patch's filesAffected, the payload lines the refreshed body adds or drops against the old " +
329
+ 'patch body. It never looks at post-write state.';
302
330
  /**
303
331
  * Names `--expect` paths that never drifted this run. A typo'd
304
332
  * `--expect` path silently degrades the flag back to refusing the slice it
@@ -318,17 +346,13 @@ export function warnUnseenExpectedDrift(driftCtx) {
318
346
  `${unseen.join(', ')}.\n` +
319
347
  `${EXPECT_MEASUREMENT_NOTE}\n` +
320
348
  '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 ' +
349
+ " 1. A typo in the path, or a path in none of the selected patches' filesAffected and " +
350
+ 'not adopted by this run.\n' +
351
+ ' 2. Adoption already captured it: a "re-export <p> --scan --scan-file <f>" run also ' +
328
352
  "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 ' +
353
+ 'legitimately sees nothing left to change. Informational — a chain that treats this ' +
330
354
  '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 ' +
355
+ ' 3. The slice was already captured by an earlier export in this session.\n' +
356
+ ' 4. The engine content already matches the patch body — a concurrent session may have ' +
333
357
  'exported the same change, so the drift converged before this run looked.');
334
358
  }
@@ -7,6 +7,7 @@ import { listTrackedInHead } from '../core/git-file-ops.js';
7
7
  import { extractAffectedFiles } from '../core/patch-apply.js';
8
8
  import { updatePatchAndMetadata } from '../core/patch-export.js';
9
9
  import { buildPatchQueueContext, formatPatchLintIssue, lintPatchQueue, } from '../core/patch-lint.js';
10
+ import { buildLintIgnoreChecks, mergeLintIgnoreIds } from '../core/patch-lint-ignore.js';
10
11
  import { computeProjectedLintRegressions } from '../core/patch-lint-projection.js';
11
12
  import { loadPatchesManifest } from '../core/patch-manifest.js';
12
13
  import { buildProjectedManifest, enforcePatchPolicy } from '../core/patch-policy.js';
@@ -25,10 +26,8 @@ import { projectEntryBody } from './patch/entry-projection.js';
25
26
  * so the caller can drop the field rather than write an empty array.
26
27
  */
27
28
  function resolveEffectiveTierAndLintIgnore(target, options) {
28
- const existingIgnoreSet = new Set(target.lintIgnore ?? []);
29
29
  const flagIgnoreSet = new Set(options.lintIgnore ?? []);
30
- const mergedIgnoreSet = new Set([...existingIgnoreSet, ...flagIgnoreSet]);
31
- const effectiveLintIgnore = mergedIgnoreSet.size > 0 ? [...mergedIgnoreSet] : undefined;
30
+ const effectiveLintIgnore = mergeLintIgnoreIds(target.lintIgnore, [...flagIgnoreSet]);
32
31
  const effectiveTier = options.tier ?? target.tier;
33
32
  return { effectiveTier, effectiveLintIgnore, flagIgnoreSet };
34
33
  }
@@ -39,8 +38,10 @@ function resolveEffectiveTierAndLintIgnore(target, options) {
39
38
  * shrink. Pre-existing cross-patch errors are surfaced as a non-blocking
40
39
  * warning so the user does not walk away thinking the queue is clean.
41
40
  */
42
- async function runProjectedCrossPatchLint(patchesDir, targetFilename, projectedDiff) {
43
- const baseCtx = await buildPatchQueueContext(patchesDir);
41
+ async function runProjectedCrossPatchLint(patchesDir, targetFilename, projectedDiff, config) {
42
+ // The config threads `patchLint.forwardRegistration` through, so a forward
43
+ // jar.mn registration is judged the way `lint --per-patch` judges it.
44
+ const baseCtx = await buildPatchQueueContext(patchesDir, config);
44
45
  const projection = projectEntryBody(projectedDiff);
45
46
  const projectedEntries = baseCtx.entries.map((entry) => {
46
47
  if (entry.filename !== targetFilename)
@@ -48,7 +49,7 @@ async function runProjectedCrossPatchLint(patchesDir, targetFilename, projectedD
48
49
  return { ...entry, ...projection };
49
50
  });
50
51
  const baselineIssues = lintPatchQueue(baseCtx).filter((i) => i.severity === 'error');
51
- const projectedIssues = lintPatchQueue({ entries: projectedEntries }).filter((i) => i.severity === 'error');
52
+ const projectedIssues = lintPatchQueue({ ...baseCtx, entries: projectedEntries }).filter((i) => i.severity === 'error');
52
53
  const regressions = computeProjectedLintRegressions(baselineIssues, projectedIssues);
53
54
  if (baselineIssues.length > 0 && regressions.length === 0) {
54
55
  warn(`Note: projected queue still has ${baselineIssues.length} pre-existing ` +
@@ -223,7 +224,7 @@ export async function reExportFilesInPlace(paths, selectedPatches, options, conf
223
224
  // `--tier` and `--lint-ignore` participate too, with append/union
224
225
  // semantics on the lint-ignore list.
225
226
  const { effectiveTier, effectiveLintIgnore, flagIgnoreSet } = resolveEffectiveTierAndLintIgnore(target, options);
226
- const ignoreChecks = effectiveLintIgnore ? new Set(effectiveLintIgnore) : undefined;
227
+ const ignoreChecks = buildLintIgnoreChecks(effectiveLintIgnore);
227
228
  const patchQueueCtx = (await pathExists(paths.patches))
228
229
  ? await buildPatchQueueContext(paths.patches)
229
230
  : undefined;
@@ -237,7 +238,7 @@ export async function reExportFilesInPlace(paths, selectedPatches, options, conf
237
238
  ignoreChecks,
238
239
  patchTier: effectiveTier,
239
240
  });
240
- const conflicts = await runProjectedCrossPatchLint(paths.patches, target.filename, projectedDiff);
241
+ const conflicts = await runProjectedCrossPatchLint(paths.patches, target.filename, projectedDiff, config);
241
242
  const filesUpdates = buildFilesModeMetadataUpdates(actualProjectedFiles, options, effectiveLintIgnore, flagIgnoreSet);
242
243
  const manifest = await loadPatchesManifest(paths.patches);
243
244
  if (manifest) {
@@ -246,6 +247,7 @@ export async function reExportFilesInPlace(paths, selectedPatches, options, conf
246
247
  manifest: buildProjectedManifest(manifest, manifest.patches.map((entry) => entry.filename === target.filename ? { ...entry, ...filesUpdates } : entry)),
247
248
  command: 're-export --files',
248
249
  forceUnsafe: options.forceUnsafe === true,
250
+ scope: [target.filename],
249
251
  });
250
252
  }
251
253
  const decision = await confirmFilesModeProjection({
@@ -297,6 +299,7 @@ export async function reExportFilesInPlace(paths, selectedPatches, options, conf
297
299
  config,
298
300
  command: 're-export --files',
299
301
  forceUnsafe: options.forceUnsafe === true,
302
+ scope: [target.filename],
300
303
  },
301
304
  });
302
305
  success(`Re-exported ${target.filename}`);
@@ -0,0 +1,35 @@
1
+ import { type ParsedHunk } from '../core/patch-parse.js';
2
+ /** Which hunk mode a re-export runs in, and the diff naming the hunks. */
3
+ export interface HunkSelection {
4
+ mode: 'include' | 'exclude';
5
+ diffPath: string;
6
+ flag: '--hunks-from' | '--exclude-hunks';
7
+ }
8
+ /** The selection a re-export's options ask for, if any. */
9
+ export declare function hunkSelectionOf(options: {
10
+ hunksFrom?: string;
11
+ excludeHunks?: string;
12
+ }): HunkSelection | undefined;
13
+ /** Splits a diff into its raw per-file sections, keyed by target path. */
14
+ export declare function splitRawSections(diff: string): Map<string, string>;
15
+ /**
16
+ * Applies `hunks` to `content` by context, forward or reversed. A hunk
17
+ * whose context is not found refuses, naming it.
18
+ */
19
+ export declare function applyHunksByContext(content: string, hunks: readonly ParsedHunk[], reverse: boolean, file: string): string;
20
+ /**
21
+ * Builds the re-export body for a hunk-selected run: named files from the
22
+ * selected hunks, the rest from the old body (`--hunks-from`) or the normal
23
+ * working-tree diff (`--exclude-hunks`).
24
+ *
25
+ * @param args.normalDiff - The diff a plain re-export would write
26
+ * @param args.oldBody - The patch's current body
27
+ * @param args.files - The files the refreshed patch owns
28
+ */
29
+ export declare function buildHunkSelectedDiff(args: {
30
+ engineDir: string;
31
+ selection: HunkSelection;
32
+ oldBody: string;
33
+ normalDiff: string;
34
+ files: readonly string[];
35
+ }): Promise<string>;
@@ -0,0 +1,220 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * `re-export --hunks-from <diff>` and `--exclude-hunks <diff>`: export a
4
+ * file by hunk rather than as a whole.
5
+ *
6
+ * Re-export captures a file's working-tree content against engine HEAD, so
7
+ * on a shared engine a peer's unexported line in the same manifest or chrome
8
+ * document landed in the exporting patch, and the only way out was for the
9
+ * peer to park its hunk by hand. These two modes build the file's new
10
+ * content from the hunks a session names:
11
+ *
12
+ * - `--hunks-from`: the patch's current body for the file (HEAD with the
13
+ * old section applied) plus only the named hunks. Files the diff does
14
+ * not name keep their old section.
15
+ * - `--exclude-hunks`: the working tree minus the named hunks. Files the
16
+ * diff does not name export as usual.
17
+ *
18
+ * Hunks are placed by their context, searched outward from the line their
19
+ * header names, so a diff taken before a peer's edit above it still applies.
20
+ * The synthetic content is diffed against HEAD by git itself, on a
21
+ * throwaway index, so the section is byte-for-byte what a plain re-export
22
+ * would write for that content.
23
+ */
24
+ import { mkdtemp, rm, writeFile } from 'node:fs/promises';
25
+ import { tmpdir } from 'node:os';
26
+ import { join } from 'node:path';
27
+ import { git } from '../core/git-base.js';
28
+ import { getFileContentAtRef, listTrackedInHead } from '../core/git-file-ops.js';
29
+ import { mintDisposableGitIndex } from '../core/git-readonly-index.js';
30
+ import { parseDiffGitHeader, parseDiffSections, } from '../core/patch-parse.js';
31
+ import { applyPatchTextToContent } from '../core/patch-transform.js';
32
+ import { GeneralError, InvalidArgumentError } from '../errors/base.js';
33
+ import { PatchError } from '../errors/patch.js';
34
+ import { pathExists, readText } from '../utils/fs.js';
35
+ import { normalizeEngineRelativeInput } from './re-export-scan.js';
36
+ /** The selection a re-export's options ask for, if any. */
37
+ export function hunkSelectionOf(options) {
38
+ if (options.hunksFrom !== undefined) {
39
+ return { mode: 'include', diffPath: options.hunksFrom, flag: '--hunks-from' };
40
+ }
41
+ if (options.excludeHunks !== undefined) {
42
+ return { mode: 'exclude', diffPath: options.excludeHunks, flag: '--exclude-hunks' };
43
+ }
44
+ return undefined;
45
+ }
46
+ /** Splits a diff into its raw per-file sections, keyed by target path. */
47
+ export function splitRawSections(diff) {
48
+ const sections = new Map();
49
+ let path;
50
+ let lines = [];
51
+ const flush = () => {
52
+ if (path !== undefined)
53
+ sections.set(path, `${lines.join('\n')}\n`);
54
+ };
55
+ for (const line of diff.split('\n')) {
56
+ const header = parseDiffGitHeader(line);
57
+ if (header) {
58
+ flush();
59
+ path = header.targetPath;
60
+ lines = [line];
61
+ }
62
+ else if (path !== undefined) {
63
+ lines.push(line);
64
+ }
65
+ }
66
+ flush();
67
+ for (const [key, text] of sections)
68
+ sections.set(key, text.replace(/\n+$/, '\n'));
69
+ return sections;
70
+ }
71
+ /** The old and new line blocks a hunk replaces, swapped when reversed. */
72
+ function hunkBlocks(hunk, reverse) {
73
+ const oldLines = [];
74
+ const newLines = [];
75
+ for (const line of hunk.lines) {
76
+ const marker = line[0];
77
+ const text = line.slice(1);
78
+ if (marker === ' ' || marker === '-')
79
+ oldLines.push(text);
80
+ if (marker === ' ' || marker === '+')
81
+ newLines.push(text);
82
+ }
83
+ return reverse ? { from: newLines, to: oldLines } : { from: oldLines, to: newLines };
84
+ }
85
+ function matchesAt(lines, block, at) {
86
+ if (at < 0 || at + block.length > lines.length)
87
+ return false;
88
+ return block.every((line, index) => lines[at + index] === line);
89
+ }
90
+ /** The match of `block` nearest `expected`, at or after `floor`. */
91
+ function findBlock(lines, block, expected, floor) {
92
+ for (let distance = 0; distance <= lines.length; distance++) {
93
+ for (const at of distance === 0 ? [expected] : [expected - distance, expected + distance]) {
94
+ if (at >= floor && matchesAt(lines, block, at))
95
+ return at;
96
+ }
97
+ }
98
+ return undefined;
99
+ }
100
+ /**
101
+ * Applies `hunks` to `content` by context, forward or reversed. A hunk
102
+ * whose context is not found refuses, naming it.
103
+ */
104
+ export function applyHunksByContext(content, hunks, reverse, file) {
105
+ const endsWithNewline = content.endsWith('\n') || content === '';
106
+ const lines = content === '' ? [] : content.replace(/\n$/, '').split('\n');
107
+ let offset = 0;
108
+ let floor = 0;
109
+ for (const hunk of hunks) {
110
+ const { from, to } = hunkBlocks(hunk, reverse);
111
+ const start = (reverse ? hunk.newStart : hunk.oldStart) - (from.length === 0 ? 0 : 1);
112
+ const expected = Math.max(0, start + offset);
113
+ const at = from.length === 0 ? expected : findBlock(lines, from, expected, floor);
114
+ if (at === undefined) {
115
+ throw new PatchError(`The hunk @@ -${hunk.oldStart},${hunk.oldCount} +${hunk.newStart},${hunk.newCount} @@ ` +
116
+ `does not ${reverse ? 'match the working tree' : 'apply to the patch body'} of ${file}: ` +
117
+ `its ${reverse ? 'new' : 'old'}-side lines were not found.`);
118
+ }
119
+ lines.splice(at, from.length, ...to);
120
+ offset += to.length - from.length + (at - expected);
121
+ floor = at + to.length;
122
+ }
123
+ if (lines.length === 0)
124
+ return '';
125
+ return `${lines.join('\n')}${endsWithNewline ? '\n' : ''}`;
126
+ }
127
+ /**
128
+ * Diffs `content` for `file` against engine HEAD the way a re-export would,
129
+ * on a throwaway index so the shared one never moves.
130
+ */
131
+ async function diffContentAgainstHead(engineDir, file, content, tracked) {
132
+ const index = await mintDisposableGitIndex(engineDir);
133
+ if (index === undefined) {
134
+ throw new GeneralError(`Cannot build a private git index in ${engineDir} to diff the hunk-selected ${file}.`);
135
+ }
136
+ const scratch = await mkdtemp(join(tmpdir(), 'fireforge-hunks-'));
137
+ try {
138
+ const temp = join(scratch, 'content');
139
+ await writeFile(temp, content);
140
+ const blob = (await git(['hash-object', '-w', '--path', file, '--', temp], engineDir)).trim();
141
+ const mode = tracked
142
+ ? ((await git(['ls-tree', 'HEAD', '--', file], engineDir)).split(/\s/)[0] ?? '100644')
143
+ : '100644';
144
+ await git(['update-index', '--add', '--cacheinfo', `${mode},${blob},${file}`], engineDir, {
145
+ env: index.env,
146
+ });
147
+ return await git(['diff', '--binary', '--cached', 'HEAD', '--', file], engineDir, {
148
+ env: index.env,
149
+ });
150
+ }
151
+ finally {
152
+ await rm(scratch, { recursive: true, force: true });
153
+ await index.dispose();
154
+ }
155
+ }
156
+ /** Reads and checks the hunk diff: text sections for files the patch owns. */
157
+ async function readSelection(selection, owned) {
158
+ if (!(await pathExists(selection.diffPath))) {
159
+ throw new InvalidArgumentError(`${selection.flag} ${selection.diffPath} does not exist.`, selection.flag);
160
+ }
161
+ const sections = parseDiffSections(await readText(selection.diffPath));
162
+ if (sections.length === 0) {
163
+ throw new InvalidArgumentError(`${selection.flag} ${selection.diffPath} holds no "diff --git" section.`, selection.flag);
164
+ }
165
+ const ownedSet = new Set(owned);
166
+ const byFile = new Map();
167
+ for (const section of sections) {
168
+ const file = normalizeEngineRelativeInput(section.targetPath, selection.flag);
169
+ if (section.isBinary || section.sourcePath !== section.targetPath) {
170
+ throw new InvalidArgumentError(`${selection.flag}: the section for ${file} is binary or a rename; only text hunks can be selected.`, selection.flag);
171
+ }
172
+ if (!ownedSet.has(file)) {
173
+ throw new InvalidArgumentError(`${selection.flag}: ${file} is not a file this patch owns.`, selection.flag);
174
+ }
175
+ byFile.set(file, section);
176
+ }
177
+ return byFile;
178
+ }
179
+ /**
180
+ * Builds the re-export body for a hunk-selected run: named files from the
181
+ * selected hunks, the rest from the old body (`--hunks-from`) or the normal
182
+ * working-tree diff (`--exclude-hunks`).
183
+ *
184
+ * @param args.normalDiff - The diff a plain re-export would write
185
+ * @param args.oldBody - The patch's current body
186
+ * @param args.files - The files the refreshed patch owns
187
+ */
188
+ export async function buildHunkSelectedDiff(args) {
189
+ const { engineDir, selection, oldBody, normalDiff, files } = args;
190
+ const named = await readSelection(selection, files);
191
+ const tracked = await listTrackedInHead(engineDir, [...named.keys()]);
192
+ const fallback = splitRawSections(selection.mode === 'include' ? oldBody : normalDiff);
193
+ const out = [];
194
+ for (const file of [...files].sort()) {
195
+ const section = named.get(file);
196
+ if (section === undefined) {
197
+ const raw = fallback.get(file);
198
+ if (raw !== undefined)
199
+ out.push(raw);
200
+ continue;
201
+ }
202
+ const head = tracked.has(file) ? await getFileContentAtRef(engineDir, file) : null;
203
+ let content;
204
+ if (selection.mode === 'include') {
205
+ const base = applyPatchTextToContent(head, oldBody, file);
206
+ content = applyHunksByContext(base, section.hunks, false, file);
207
+ }
208
+ else {
209
+ const worktree = join(engineDir, file);
210
+ const current = (await pathExists(worktree)) ? await readText(worktree) : '';
211
+ content = applyHunksByContext(current, section.hunks, true, file);
212
+ }
213
+ if (head !== null && content === head)
214
+ continue;
215
+ const diff = await diffContentAgainstHead(engineDir, file, content, tracked.has(file));
216
+ if (diff.trim())
217
+ out.push(diff.endsWith('\n') ? diff : `${diff}\n`);
218
+ }
219
+ return out.join('');
220
+ }
@@ -21,6 +21,7 @@
21
21
  */
22
22
  import { type PatchQueueContext } from '../core/patch-lint.js';
23
23
  import { type PerPatchLintCacheFile } from '../core/patch-lint-cache.js';
24
+ import { type LintIgnoreChecks } from '../core/patch-lint-ignore.js';
24
25
  import type { PatchLintIssue, PatchMetadata } from '../types/commands/index.js';
25
26
  import type { FireForgeConfig, ProjectPaths } from '../types/config.js';
26
27
  import { type PerRunCheckJs } from './lint-per-run-checkjs.js';
@@ -92,7 +93,7 @@ export declare function lintReExportedPatch(args: {
92
93
  existingFiles: string[];
93
94
  diffContent: string;
94
95
  skipLint?: boolean;
95
- ignoreChecks?: ReadonlySet<string>;
96
+ ignoreChecks?: LintIgnoreChecks;
96
97
  patchTier?: 'branding';
97
98
  }): Promise<ReExportLintResult | null>;
98
99
  /**