@hominis/fireforge 0.43.1 → 0.44.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 (470) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/README.md +10 -1
  3. package/dist/bin/fireforge.d.ts +4 -9
  4. package/dist/bin/fireforge.js +42 -40
  5. package/dist/build-info.json +4 -4
  6. package/dist/src/cli.js +74 -8
  7. package/dist/src/commands/bootstrap.js +17 -20
  8. package/dist/src/commands/build.js +25 -32
  9. package/dist/src/commands/config.js +34 -41
  10. package/dist/src/commands/discard.js +8 -9
  11. package/dist/src/commands/doctor-check-core.d.ts +4 -18
  12. package/dist/src/commands/doctor-check-core.js +8 -35
  13. package/dist/src/commands/doctor-external-toolchains.js +2 -2
  14. package/dist/src/commands/doctor-furnace-config-sync.d.ts +18 -0
  15. package/dist/src/commands/{doctor-furnace-manifest-sync.js → doctor-furnace-config-sync.js} +27 -26
  16. package/dist/src/commands/doctor-furnace-jar.d.ts +7 -9
  17. package/dist/src/commands/doctor-furnace-jar.js +7 -9
  18. package/dist/src/commands/doctor-furnace.js +21 -24
  19. package/dist/src/commands/doctor-orphaned-harness.d.ts +16 -20
  20. package/dist/src/commands/doctor-orphaned-harness.js +18 -44
  21. package/dist/src/commands/doctor-post-rebase-audit.d.ts +2 -0
  22. package/dist/src/commands/{doctor/post-rebase-audit.js → doctor-post-rebase-audit.js} +3 -3
  23. package/dist/src/commands/doctor-working-tree.d.ts +10 -12
  24. package/dist/src/commands/doctor-working-tree.js +11 -18
  25. package/dist/src/commands/doctor.js +35 -53
  26. package/dist/src/commands/download.js +32 -48
  27. package/dist/src/commands/export-all.js +29 -33
  28. package/dist/src/commands/export-flow.js +6 -6
  29. package/dist/src/commands/export-placement-conflicts.js +10 -9
  30. package/dist/src/commands/export-placement-gate.js +5 -5
  31. package/dist/src/commands/export-shared.d.ts +30 -33
  32. package/dist/src/commands/export-shared.js +45 -43
  33. package/dist/src/commands/export.js +21 -27
  34. package/dist/src/commands/furnace/apply.js +14 -23
  35. package/dist/src/commands/furnace/chrome-doc-remove.js +14 -21
  36. package/dist/src/commands/furnace/chrome-doc-templates.d.ts +23 -23
  37. package/dist/src/commands/furnace/chrome-doc-templates.js +23 -23
  38. package/dist/src/commands/furnace/chrome-doc-tests.js +26 -29
  39. package/dist/src/commands/furnace/chrome-doc.d.ts +16 -17
  40. package/dist/src/commands/furnace/chrome-doc.js +31 -41
  41. package/dist/src/commands/furnace/create-browser-test.d.ts +4 -4
  42. package/dist/src/commands/furnace/create-browser-test.js +8 -8
  43. package/dist/src/commands/furnace/create-mochikit.d.ts +6 -7
  44. package/dist/src/commands/furnace/create-mochikit.js +8 -9
  45. package/dist/src/commands/furnace/create-readback.d.ts +12 -13
  46. package/dist/src/commands/furnace/create-readback.js +12 -13
  47. package/dist/src/commands/furnace/create-templates.d.ts +22 -28
  48. package/dist/src/commands/furnace/create-templates.js +35 -30
  49. package/dist/src/commands/furnace/create-xpcshell.js +2 -2
  50. package/dist/src/commands/furnace/create.js +33 -23
  51. package/dist/src/commands/furnace/deploy.js +16 -24
  52. package/dist/src/commands/furnace/diff.js +9 -9
  53. package/dist/src/commands/furnace/init.js +33 -36
  54. package/dist/src/commands/furnace/override.js +23 -25
  55. package/dist/src/commands/furnace/preview.js +47 -67
  56. package/dist/src/commands/furnace/refresh.js +12 -16
  57. package/dist/src/commands/furnace/remove-mutations.d.ts +3 -6
  58. package/dist/src/commands/furnace/remove-mutations.js +9 -16
  59. package/dist/src/commands/furnace/remove-state.js +1 -1
  60. package/dist/src/commands/furnace/remove.js +33 -38
  61. package/dist/src/commands/furnace/rename-xpcshell.d.ts +8 -11
  62. package/dist/src/commands/furnace/rename-xpcshell.js +8 -11
  63. package/dist/src/commands/furnace/rename.js +59 -69
  64. package/dist/src/commands/furnace/scan.js +19 -33
  65. package/dist/src/commands/furnace/status.js +12 -9
  66. package/dist/src/commands/furnace/sync.js +9 -16
  67. package/dist/src/commands/furnace/validate.js +20 -20
  68. package/dist/src/commands/import.js +42 -35
  69. package/dist/src/commands/lint-per-patch.js +15 -10
  70. package/dist/src/commands/lint-per-run-checkjs.d.ts +13 -13
  71. package/dist/src/commands/lint-per-run-checkjs.js +13 -13
  72. package/dist/src/commands/lint-report.js +6 -7
  73. package/dist/src/commands/lint.d.ts +15 -16
  74. package/dist/src/commands/lint.js +86 -102
  75. package/dist/src/commands/package.js +6 -11
  76. package/dist/src/commands/patch/compact.js +1 -1
  77. package/dist/src/commands/patch/delete.js +24 -33
  78. package/dist/src/commands/patch/index.d.ts +5 -6
  79. package/dist/src/commands/patch/index.js +10 -12
  80. package/dist/src/commands/patch/lint-ignore.js +14 -13
  81. package/dist/src/commands/patch/move-files-create.d.ts +6 -6
  82. package/dist/src/commands/patch/move-files-create.js +8 -8
  83. package/dist/src/commands/patch/move-files-into.d.ts +6 -6
  84. package/dist/src/commands/patch/move-files-into.js +8 -8
  85. package/dist/src/commands/patch/patch-context.d.ts +2 -3
  86. package/dist/src/commands/patch/patch-context.js +2 -3
  87. package/dist/src/commands/patch/rename.d.ts +10 -11
  88. package/dist/src/commands/patch/rename.js +11 -12
  89. package/dist/src/commands/patch/reorder.js +8 -9
  90. package/dist/src/commands/patch/split-plan.js +1 -1
  91. package/dist/src/commands/patch/split.d.ts +8 -10
  92. package/dist/src/commands/patch/split.js +9 -11
  93. package/dist/src/commands/patch/staged-dependency-validate.js +8 -8
  94. package/dist/src/commands/patch/staged-dependency.js +7 -3
  95. package/dist/src/commands/patch/tier.d.ts +9 -9
  96. package/dist/src/commands/patch/tier.js +18 -15
  97. package/dist/src/commands/re-export-adjacent.js +9 -8
  98. package/dist/src/commands/re-export-bulk-scan.d.ts +9 -9
  99. package/dist/src/commands/re-export-bulk-scan.js +22 -21
  100. package/dist/src/commands/re-export-drift.js +10 -10
  101. package/dist/src/commands/re-export-files.d.ts +6 -7
  102. package/dist/src/commands/re-export-files.js +34 -44
  103. package/dist/src/commands/re-export-lint.d.ts +10 -13
  104. package/dist/src/commands/re-export-lint.js +16 -14
  105. package/dist/src/commands/re-export-options.d.ts +6 -6
  106. package/dist/src/commands/re-export-options.js +6 -6
  107. package/dist/src/commands/re-export-register.d.ts +4 -10
  108. package/dist/src/commands/re-export-register.js +5 -11
  109. package/dist/src/commands/re-export-scan.js +1 -1
  110. package/dist/src/commands/re-export-single.d.ts +3 -3
  111. package/dist/src/commands/re-export-single.js +33 -25
  112. package/dist/src/commands/re-export.js +13 -21
  113. package/dist/src/commands/rebase/abort.js +3 -3
  114. package/dist/src/commands/rebase/confirm.js +2 -1
  115. package/dist/src/commands/rebase/continue.d.ts +1 -1
  116. package/dist/src/commands/rebase/continue.js +14 -9
  117. package/dist/src/commands/rebase/index.js +11 -11
  118. package/dist/src/commands/rebase/patch-loop.d.ts +7 -1
  119. package/dist/src/commands/rebase/patch-loop.js +60 -39
  120. package/dist/src/commands/rebase/summary.js +1 -1
  121. package/dist/src/commands/register.js +5 -7
  122. package/dist/src/commands/reset.js +2 -1
  123. package/dist/src/commands/resolve.d.ts +16 -13
  124. package/dist/src/commands/resolve.js +30 -33
  125. package/dist/src/commands/run.js +33 -35
  126. package/dist/src/commands/setup-support.js +15 -19
  127. package/dist/src/commands/setup.js +13 -9
  128. package/dist/src/commands/status-check.d.ts +6 -6
  129. package/dist/src/commands/status-json.d.ts +6 -6
  130. package/dist/src/commands/status-json.js +5 -0
  131. package/dist/src/commands/status-output.d.ts +5 -5
  132. package/dist/src/commands/status-output.js +6 -6
  133. package/dist/src/commands/status-ownership.d.ts +16 -14
  134. package/dist/src/commands/status-ownership.js +16 -14
  135. package/dist/src/commands/status.js +42 -34
  136. package/dist/src/commands/test-build-phase.js +26 -13
  137. package/dist/src/commands/test-diagnose.js +44 -44
  138. package/dist/src/commands/test-register.js +1 -0
  139. package/dist/src/commands/test-run.d.ts +14 -16
  140. package/dist/src/commands/test-run.js +21 -13
  141. package/dist/src/commands/test-stale-gate.d.ts +15 -16
  142. package/dist/src/commands/test-stale-gate.js +15 -16
  143. package/dist/src/commands/test-verdict.d.ts +20 -3
  144. package/dist/src/commands/test-verdict.js +30 -0
  145. package/dist/src/commands/test.js +83 -76
  146. package/dist/src/commands/token-coverage.js +3 -4
  147. package/dist/src/commands/token.js +16 -18
  148. package/dist/src/commands/tree.js +47 -31
  149. package/dist/src/commands/typecheck.d.ts +10 -12
  150. package/dist/src/commands/typecheck.js +24 -27
  151. package/dist/src/commands/verify.js +13 -15
  152. package/dist/src/commands/watch.js +21 -23
  153. package/dist/src/commands/wire.js +52 -62
  154. package/dist/src/core/ast-utils.d.ts +8 -7
  155. package/dist/src/core/ast-utils.js +8 -7
  156. package/dist/src/core/branding.d.ts +9 -14
  157. package/dist/src/core/branding.js +7 -11
  158. package/dist/src/core/browser-wire.d.ts +1 -1
  159. package/dist/src/core/browser-wire.js +14 -19
  160. package/dist/src/core/build-audit-platform.js +25 -33
  161. package/dist/src/core/build-audit-registration.js +17 -25
  162. package/dist/src/core/build-audit-resolve.d.ts +15 -17
  163. package/dist/src/core/build-audit-resolve.js +27 -33
  164. package/dist/src/core/build-audit-transforms.js +18 -19
  165. package/dist/src/core/build-audit.js +28 -42
  166. package/dist/src/core/build-baseline-types.d.ts +29 -35
  167. package/dist/src/core/build-baseline.d.ts +5 -4
  168. package/dist/src/core/build-baseline.js +14 -14
  169. package/dist/src/core/build-overwrite-guard.d.ts +7 -14
  170. package/dist/src/core/build-overwrite-guard.js +17 -27
  171. package/dist/src/core/build-prepare.d.ts +2 -2
  172. package/dist/src/core/build-prepare.js +8 -9
  173. package/dist/src/core/config-state.js +12 -4
  174. package/dist/src/core/config-validate.js +8 -5
  175. package/dist/src/core/config.d.ts +5 -8
  176. package/dist/src/core/config.js +7 -16
  177. package/dist/src/core/coverage-extend.d.ts +15 -13
  178. package/dist/src/core/coverage-extend.js +15 -13
  179. package/dist/src/core/destructive.d.ts +31 -9
  180. package/dist/src/core/destructive.js +29 -26
  181. package/dist/src/core/diff-hunks.js +10 -12
  182. package/dist/src/core/discard-baseline.d.ts +8 -8
  183. package/dist/src/core/discard-baseline.js +16 -15
  184. package/dist/src/core/display-state.d.ts +12 -14
  185. package/dist/src/core/display-state.js +12 -14
  186. package/dist/src/core/engine-changes.d.ts +2 -4
  187. package/dist/src/core/engine-changes.js +2 -4
  188. package/dist/src/core/engine-precondition.d.ts +28 -13
  189. package/dist/src/core/engine-precondition.js +25 -10
  190. package/dist/src/core/engine-session-lock.d.ts +47 -21
  191. package/dist/src/core/engine-session-lock.js +168 -33
  192. package/dist/src/core/export-moved-code-shape.d.ts +10 -10
  193. package/dist/src/core/export-moved-code-shape.js +10 -10
  194. package/dist/src/core/file-lock.d.ts +62 -5
  195. package/dist/src/core/file-lock.js +196 -59
  196. package/dist/src/core/firefox-cache.js +1 -3
  197. package/dist/src/core/firefox-extract.js +10 -14
  198. package/dist/src/core/firefox.d.ts +7 -5
  199. package/dist/src/core/firefox.js +4 -5
  200. package/dist/src/core/furnace-apply-dry-run.d.ts +2 -2
  201. package/dist/src/core/furnace-apply-dry-run.js +3 -2
  202. package/dist/src/core/furnace-apply-ftl.d.ts +7 -9
  203. package/dist/src/core/furnace-apply-ftl.js +12 -12
  204. package/dist/src/core/furnace-apply-helpers.d.ts +7 -7
  205. package/dist/src/core/furnace-apply-helpers.js +19 -16
  206. package/dist/src/core/furnace-apply-overwrite-warn.d.ts +8 -9
  207. package/dist/src/core/furnace-apply-overwrite-warn.js +8 -9
  208. package/dist/src/core/furnace-apply-undeploy-actions.d.ts +3 -4
  209. package/dist/src/core/furnace-apply-undeploy-actions.js +3 -4
  210. package/dist/src/core/furnace-apply.d.ts +5 -8
  211. package/dist/src/core/furnace-apply.js +19 -24
  212. package/dist/src/core/furnace-checksum-utils.d.ts +14 -0
  213. package/dist/src/core/furnace-checksum-utils.js +16 -0
  214. package/dist/src/core/furnace-config.d.ts +21 -30
  215. package/dist/src/core/furnace-config.js +32 -54
  216. package/dist/src/core/furnace-constants.d.ts +8 -0
  217. package/dist/src/core/furnace-constants.js +10 -2
  218. package/dist/src/core/furnace-css-fragments.d.ts +5 -5
  219. package/dist/src/core/furnace-css-fragments.js +5 -5
  220. package/dist/src/core/furnace-jsconfig.d.ts +11 -12
  221. package/dist/src/core/furnace-jsconfig.js +11 -12
  222. package/dist/src/core/furnace-marker.d.ts +7 -8
  223. package/dist/src/core/furnace-marker.js +7 -8
  224. package/dist/src/core/furnace-operation.d.ts +31 -0
  225. package/dist/src/core/furnace-operation.js +54 -17
  226. package/dist/src/core/furnace-precondition.d.ts +61 -0
  227. package/dist/src/core/furnace-precondition.js +67 -0
  228. package/dist/src/core/furnace-registration-remove.d.ts +5 -6
  229. package/dist/src/core/furnace-registration-remove.js +6 -7
  230. package/dist/src/core/furnace-registration-validate.d.ts +13 -0
  231. package/dist/src/core/furnace-registration-validate.js +20 -1
  232. package/dist/src/core/furnace-registration.d.ts +5 -6
  233. package/dist/src/core/furnace-registration.js +10 -12
  234. package/dist/src/core/furnace-rollback.js +7 -7
  235. package/dist/src/core/furnace-scanner.d.ts +1 -0
  236. package/dist/src/core/furnace-scanner.js +1 -2
  237. package/dist/src/core/furnace-stale-export.d.ts +10 -10
  238. package/dist/src/core/furnace-stale-export.js +10 -10
  239. package/dist/src/core/furnace-state-persist.d.ts +6 -8
  240. package/dist/src/core/furnace-state-persist.js +6 -8
  241. package/dist/src/core/furnace-validate-accessibility.d.ts +22 -0
  242. package/dist/src/core/furnace-validate-accessibility.js +310 -1
  243. package/dist/src/core/furnace-validate-compatibility.js +141 -1
  244. package/dist/src/core/furnace-validate-helpers.d.ts +5 -116
  245. package/dist/src/core/furnace-validate-helpers.js +0 -491
  246. package/dist/src/core/furnace-validate-registration.d.ts +7 -0
  247. package/dist/src/core/furnace-validate-registration.js +115 -41
  248. package/dist/src/core/furnace-validate.d.ts +16 -0
  249. package/dist/src/core/furnace-validate.js +188 -145
  250. package/dist/src/core/furnace-version-drift.d.ts +10 -11
  251. package/dist/src/core/furnace-version-drift.js +10 -11
  252. package/dist/src/core/git-base.d.ts +7 -9
  253. package/dist/src/core/git-base.js +7 -9
  254. package/dist/src/core/git-diff.d.ts +20 -9
  255. package/dist/src/core/git-diff.js +148 -63
  256. package/dist/src/core/git-file-ops.d.ts +19 -19
  257. package/dist/src/core/git-file-ops.js +19 -19
  258. package/dist/src/core/git-readonly-index.d.ts +53 -19
  259. package/dist/src/core/git-readonly-index.js +75 -31
  260. package/dist/src/core/git-status.d.ts +5 -6
  261. package/dist/src/core/git-status.js +5 -6
  262. package/dist/src/core/git.d.ts +9 -10
  263. package/dist/src/core/git.js +54 -70
  264. package/dist/src/core/license-headers.d.ts +17 -23
  265. package/dist/src/core/license-headers.js +17 -23
  266. package/dist/src/core/mach-build-artifacts.d.ts +9 -9
  267. package/dist/src/core/mach-build-artifacts.js +57 -19
  268. package/dist/src/core/mach-error-hints.js +42 -29
  269. package/dist/src/core/mach-known-noise-filter.d.ts +15 -16
  270. package/dist/src/core/mach-known-noise-filter.js +18 -20
  271. package/dist/src/core/mach-mozconfig.d.ts +14 -0
  272. package/dist/src/core/mach-mozconfig.js +30 -0
  273. package/dist/src/core/mach-resource-shim.d.ts +43 -58
  274. package/dist/src/core/mach-resource-shim.js +50 -67
  275. package/dist/src/core/mach.d.ts +37 -41
  276. package/dist/src/core/mach.js +50 -51
  277. package/dist/src/core/marionette-port.d.ts +36 -1
  278. package/dist/src/core/marionette-port.js +96 -31
  279. package/dist/src/core/marionette-preflight.d.ts +8 -12
  280. package/dist/src/core/marionette-preflight.js +86 -52
  281. package/dist/src/core/mochitest-server-port.d.ts +42 -0
  282. package/dist/src/core/mochitest-server-port.js +119 -0
  283. package/dist/src/core/module-resolution-preflight.d.ts +9 -11
  284. package/dist/src/core/module-resolution-preflight.js +9 -11
  285. package/dist/src/core/moz-manifest-helpers.d.ts +79 -0
  286. package/dist/src/core/moz-manifest-helpers.js +163 -0
  287. package/dist/src/core/{manifest-register.d.ts → moz-manifest-register.d.ts} +1 -1
  288. package/dist/src/core/{manifest-register.js → moz-manifest-register.js} +13 -28
  289. package/dist/src/core/{manifest-rules.d.ts → moz-manifest-rules.d.ts} +10 -1
  290. package/dist/src/core/{manifest-rules.js → moz-manifest-rules.js} +31 -24
  291. package/dist/src/core/{manifest-tokenizers.d.ts → moz-manifest-tokenizers.d.ts} +3 -5
  292. package/dist/src/core/{manifest-tokenizers.js → moz-manifest-tokenizers.js} +9 -12
  293. package/dist/src/core/ownership-table.d.ts +14 -16
  294. package/dist/src/core/ownership-table.js +14 -16
  295. package/dist/src/core/parser-fallback.d.ts +15 -1
  296. package/dist/src/core/parser-fallback.js +22 -3
  297. package/dist/src/core/patch-apply-fuzz.d.ts +10 -12
  298. package/dist/src/core/patch-apply-fuzz.js +15 -18
  299. package/dist/src/core/patch-apply.d.ts +13 -34
  300. package/dist/src/core/patch-apply.js +16 -76
  301. package/dist/src/core/patch-artifact-normalize.d.ts +13 -15
  302. package/dist/src/core/patch-artifact-normalize.js +13 -15
  303. package/dist/src/core/patch-export-metadata.d.ts +12 -2
  304. package/dist/src/core/patch-export-metadata.js +13 -4
  305. package/dist/src/core/patch-export-update.d.ts +13 -1
  306. package/dist/src/core/patch-export-update.js +25 -8
  307. package/dist/src/core/patch-export.d.ts +15 -8
  308. package/dist/src/core/patch-export.js +10 -11
  309. package/dist/src/core/patch-files.d.ts +5 -6
  310. package/dist/src/core/patch-files.js +5 -6
  311. package/dist/src/core/patch-identifier-suggest.d.ts +7 -12
  312. package/dist/src/core/patch-identifier-suggest.js +14 -19
  313. package/dist/src/core/patch-lint-binary.d.ts +51 -0
  314. package/dist/src/core/patch-lint-binary.js +39 -0
  315. package/dist/src/core/{lint-cache.d.ts → patch-lint-cache.d.ts} +5 -5
  316. package/dist/src/core/{lint-cache.js → patch-lint-cache.js} +21 -23
  317. package/dist/src/core/patch-lint-checkjs.d.ts +16 -16
  318. package/dist/src/core/patch-lint-checkjs.js +16 -16
  319. package/dist/src/core/patch-lint-chrome-jsdoc.d.ts +6 -9
  320. package/dist/src/core/patch-lint-chrome-jsdoc.js +10 -14
  321. package/dist/src/core/patch-lint-creators.d.ts +8 -8
  322. package/dist/src/core/patch-lint-creators.js +8 -8
  323. package/dist/src/core/patch-lint-cross.d.ts +17 -20
  324. package/dist/src/core/patch-lint-cross.js +16 -16
  325. package/dist/src/core/patch-lint-css.d.ts +3 -5
  326. package/dist/src/core/patch-lint-css.js +17 -22
  327. package/dist/src/core/patch-lint-diff-tag.d.ts +14 -16
  328. package/dist/src/core/patch-lint-diff-tag.js +14 -16
  329. package/dist/src/core/patch-lint-jsdoc.d.ts +7 -8
  330. package/dist/src/core/patch-lint-jsdoc.js +26 -19
  331. package/dist/src/core/patch-lint-mozbuild.js +20 -20
  332. package/dist/src/core/patch-lint-observer.d.ts +7 -7
  333. package/dist/src/core/patch-lint-observer.js +7 -7
  334. package/dist/src/core/patch-lint.d.ts +38 -29
  335. package/dist/src/core/patch-lint.js +89 -121
  336. package/dist/src/core/patch-lock.d.ts +1 -1
  337. package/dist/src/core/patch-lock.js +45 -9
  338. package/dist/src/core/patch-manifest-consistency.d.ts +2 -1
  339. package/dist/src/core/patch-manifest-consistency.js +25 -27
  340. package/dist/src/core/patch-manifest-io.js +14 -18
  341. package/dist/src/core/patch-manifest-resolve.d.ts +9 -12
  342. package/dist/src/core/patch-manifest-resolve.js +9 -12
  343. package/dist/src/core/patch-manifest-validate.d.ts +6 -1
  344. package/dist/src/core/patch-manifest-validate.js +25 -13
  345. package/dist/src/core/patch-parse.d.ts +24 -13
  346. package/dist/src/core/patch-parse.js +19 -14
  347. package/dist/src/core/patch-policy.d.ts +2 -2
  348. package/dist/src/core/patch-policy.js +2 -2
  349. package/dist/src/core/patch-registration-refs.d.ts +7 -8
  350. package/dist/src/core/patch-registration-refs.js +9 -9
  351. package/dist/src/core/process-cpu-time.d.ts +12 -0
  352. package/dist/src/core/process-cpu-time.js +52 -0
  353. package/dist/src/core/rebase-session.d.ts +38 -26
  354. package/dist/src/core/rebase-session.js +55 -28
  355. package/dist/src/core/register-browser-content.js +9 -74
  356. package/dist/src/core/{register-test-manifest.js → register-browser-test.js} +7 -42
  357. package/dist/src/core/register-module.js +10 -43
  358. package/dist/src/core/register-scaffold.d.ts +8 -8
  359. package/dist/src/core/register-scaffold.js +10 -10
  360. package/dist/src/core/register-shared-css.d.ts +9 -12
  361. package/dist/src/core/register-shared-css.js +17 -87
  362. package/dist/src/core/register-xpcshell-test.d.ts +6 -7
  363. package/dist/src/core/register-xpcshell-test.js +7 -8
  364. package/dist/src/core/shared-ftl.js +12 -4
  365. package/dist/src/core/state-file.d.ts +45 -0
  366. package/dist/src/core/state-file.js +34 -0
  367. package/dist/src/core/status-binary.d.ts +18 -11
  368. package/dist/src/core/status-binary.js +19 -12
  369. package/dist/src/core/status-classify.d.ts +30 -36
  370. package/dist/src/core/status-classify.js +21 -26
  371. package/dist/src/core/test-changed-prefs.d.ts +30 -0
  372. package/dist/src/core/test-changed-prefs.js +78 -0
  373. package/dist/src/core/test-harness-crash.d.ts +25 -63
  374. package/dist/src/core/test-harness-crash.js +121 -142
  375. package/dist/src/core/test-harness-signature.d.ts +18 -0
  376. package/dist/src/core/test-harness-signature.js +13 -0
  377. package/dist/src/core/test-path-scope.d.ts +12 -15
  378. package/dist/src/core/test-path-scope.js +15 -18
  379. package/dist/src/core/test-stale-check.d.ts +17 -17
  380. package/dist/src/core/test-stale-check.js +50 -43
  381. package/dist/src/core/test-stall-triage.d.ts +51 -0
  382. package/dist/src/core/test-stall-triage.js +126 -0
  383. package/dist/src/core/token-category.js +5 -7
  384. package/dist/src/core/token-coverage.js +9 -9
  385. package/dist/src/core/token-dark-mode.d.ts +9 -11
  386. package/dist/src/core/token-dark-mode.js +9 -11
  387. package/dist/src/core/token-docs.d.ts +2 -0
  388. package/dist/src/core/token-docs.js +2 -0
  389. package/dist/src/core/token-manager.d.ts +10 -2
  390. package/dist/src/core/token-manager.js +17 -8
  391. package/dist/src/core/token-scaffold.d.ts +7 -7
  392. package/dist/src/core/token-scaffold.js +7 -7
  393. package/dist/src/core/token-variant.d.ts +4 -4
  394. package/dist/src/core/token-variant.js +4 -4
  395. package/dist/src/core/toolchain-preflight.d.ts +20 -21
  396. package/dist/src/core/toolchain-preflight.js +28 -31
  397. package/dist/src/core/tree-guard.js +12 -2
  398. package/dist/src/core/tree-store.d.ts +41 -27
  399. package/dist/src/core/tree-store.js +63 -42
  400. package/dist/src/core/typecheck-shim.d.ts +23 -24
  401. package/dist/src/core/typecheck-shim.js +23 -24
  402. package/dist/src/core/typecheck.js +8 -8
  403. package/dist/src/core/wire-destroy.d.ts +3 -3
  404. package/dist/src/core/wire-destroy.js +7 -15
  405. package/dist/src/core/wire-dom-fragment.d.ts +9 -17
  406. package/dist/src/core/wire-dom-fragment.js +11 -44
  407. package/dist/src/core/wire-init.d.ts +2 -3
  408. package/dist/src/core/wire-init.js +8 -19
  409. package/dist/src/core/wire-subscript.d.ts +2 -3
  410. package/dist/src/core/wire-subscript.js +4 -13
  411. package/dist/src/core/wire-utils.d.ts +7 -8
  412. package/dist/src/core/wire-utils.js +7 -8
  413. package/dist/src/core/xpcshell-appdir.d.ts +38 -45
  414. package/dist/src/core/xpcshell-appdir.js +38 -45
  415. package/dist/src/errors/base.d.ts +37 -16
  416. package/dist/src/errors/base.js +37 -16
  417. package/dist/src/errors/build.d.ts +13 -1
  418. package/dist/src/errors/build.js +14 -2
  419. package/dist/src/errors/codes.d.ts +16 -0
  420. package/dist/src/errors/codes.js +16 -0
  421. package/dist/src/errors/furnace.d.ts +4 -4
  422. package/dist/src/errors/git.d.ts +6 -10
  423. package/dist/src/errors/git.js +6 -10
  424. package/dist/src/errors/patch.d.ts +7 -7
  425. package/dist/src/errors/patch.js +7 -7
  426. package/dist/src/errors/rebase.d.ts +4 -4
  427. package/dist/src/errors/rebase.js +4 -4
  428. package/dist/src/index.d.ts +7 -1
  429. package/dist/src/index.js +5 -0
  430. package/dist/src/types/commands/index.d.ts +2 -2
  431. package/dist/src/types/commands/options.d.ts +88 -70
  432. package/dist/src/types/commands/patches.d.ts +24 -34
  433. package/dist/src/types/commands/project.d.ts +5 -34
  434. package/dist/src/types/config.d.ts +25 -23
  435. package/dist/src/types/config.js +6 -1
  436. package/dist/src/types/furnace.d.ts +37 -13
  437. package/dist/src/types/furnace.js +19 -1
  438. package/dist/src/types/index.d.ts +3 -3
  439. package/dist/src/utils/build-info.d.ts +7 -8
  440. package/dist/src/utils/build-info.js +12 -11
  441. package/dist/src/utils/errors.d.ts +6 -8
  442. package/dist/src/utils/errors.js +6 -8
  443. package/dist/src/utils/logger.d.ts +27 -10
  444. package/dist/src/utils/logger.js +28 -16
  445. package/dist/src/utils/machine-output.d.ts +12 -0
  446. package/dist/src/utils/machine-output.js +33 -0
  447. package/dist/src/utils/options.d.ts +31 -14
  448. package/dist/src/utils/options.js +86 -30
  449. package/dist/src/utils/paths.d.ts +10 -8
  450. package/dist/src/utils/paths.js +10 -8
  451. package/dist/src/utils/platform.d.ts +0 -25
  452. package/dist/src/utils/platform.js +1 -42
  453. package/dist/src/utils/process-group.d.ts +9 -4
  454. package/dist/src/utils/process-group.js +19 -5
  455. package/dist/src/utils/process.d.ts +42 -7
  456. package/dist/src/utils/process.js +44 -17
  457. package/dist/src/utils/ps-duration.d.ts +31 -0
  458. package/dist/src/utils/ps-duration.js +70 -0
  459. package/dist/src/utils/regex.d.ts +9 -10
  460. package/dist/src/utils/regex.js +9 -10
  461. package/dist/src/utils/validation.d.ts +58 -28
  462. package/dist/src/utils/validation.js +60 -33
  463. package/package.json +2 -2
  464. package/dist/src/commands/doctor/post-rebase-audit.d.ts +0 -2
  465. package/dist/src/commands/doctor-furnace-manifest-sync.d.ts +0 -18
  466. package/dist/src/core/manifest-helpers.d.ts +0 -38
  467. package/dist/src/core/manifest-helpers.js +0 -120
  468. /package/dist/src/core/{lint-projection.d.ts → patch-lint-projection.d.ts} +0 -0
  469. /package/dist/src/core/{lint-projection.js → patch-lint-projection.js} +0 -0
  470. /package/dist/src/core/{register-test-manifest.d.ts → register-browser-test.d.ts} +0 -0
@@ -1,11 +1,19 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { join } from 'node:path';
3
- import { GeneralError } from '../errors/base.js';
3
+ import { InconclusiveVerdictError } from '../errors/base.js';
4
4
  import { toError } from '../utils/errors.js';
5
5
  import { info, warn } from '../utils/logger.js';
6
6
  import { readLockStatus, withFileLock } from './file-lock.js';
7
7
  import { git } from './git-base.js';
8
8
  const ENGINE_SESSION_LOCK_PATH = join('.fireforge', 'engine-session.lock');
9
+ /**
10
+ * How much longer than the requested budget a wait may run while its queue
11
+ * position keeps improving, and the absolute ceiling on that. The ceiling
12
+ * matches `--wait-lock`'s own upper bound: an operator who asked for a wait
13
+ * never gets one longer than the largest wait they could have asked for.
14
+ */
15
+ const WAIT_EXTENSION_FACTOR = 4;
16
+ const MAX_WAIT_LOCK_MS = 3600 * 1000;
9
17
  /**
10
18
  * Formats the periodic operator-facing waiting line printed while `--wait-lock`
11
19
  * polls a contended engine session lock. The holder identification comes from
@@ -27,6 +35,22 @@ function formatWaitProgressLine(waitedMs, timeoutMs, holder, queue) {
27
35
  const details = holder.metadata.length > 0 ? ` (${holder.metadata.join(', ')})` : '';
28
36
  return `Waiting for the FireForge engine lock held by PID ${String(holder.pid)}${details} — ${progress}.${position}`;
29
37
  }
38
+ /**
39
+ * Formats the notice printed when the wait budget is EXTENDED because the
40
+ * queue advanced.
41
+ *
42
+ * Exported for direct unit testing. It names both figures — the requested
43
+ * budget and the one now in force — because the whole point is that the two
44
+ * have diverged, and an operator comparing the elapsed time against the
45
+ * number they typed would otherwise conclude the timeout is broken.
46
+ */
47
+ export function formatWaitExtendedLine(ahead, budgetMs, requestedMs) {
48
+ const position = ahead === 0 ? 'you are now next' : `${String(ahead)} still ahead of you`;
49
+ return (`The queue advanced (${position}), so the engine-lock wait was extended to ` +
50
+ `${String(Math.round(budgetMs / 1000))}s total (you asked for ` +
51
+ `${String(Math.round(requestedMs / 1000))}s). A queue that stops moving still ` +
52
+ `gives up on the budget you asked for.`);
53
+ }
30
54
  /**
31
55
  * Runs `operation` while holding the project's engine-session lock, so only
32
56
  * one engine-mutating FireForge command touches `engine/` at a time.
@@ -35,9 +59,9 @@ export async function withEngineSessionLock(projectRoot, command, operation, opt
35
59
  // The lock is bypassed under test by default, and that is deliberate: the
36
60
  // suite runs many engine-mutating commands in-process, which would either
37
61
  // serialise on this lock or deadlock outright.
38
- // `FIREFORGE_ENABLE_ENGINE_SESSION_LOCK_IN_TEST=1` is what lets the lock's
39
- // own tests opt back in. Do not delete this branch — it is the reason the
40
- // suite completes, not an oversight.
62
+ // `FIREFORGE_ENABLE_ENGINE_SESSION_LOCK_IN_TEST=1` lets the lock's own
63
+ // tests opt back in. Do not delete this branch — it is the reason the
64
+ // suite completes.
41
65
  if (process.env['FIREFORGE_ENABLE_ENGINE_SESSION_LOCK_IN_TEST'] !== '1' &&
42
66
  (process.env['NODE_ENV'] === 'test' || process.env['VITEST'] !== undefined)) {
43
67
  return operation();
@@ -53,6 +77,22 @@ export async function withEngineSessionLock(projectRoot, command, operation, opt
53
77
  onWaitProgress: ({ waitedMs, timeoutMs, holder, queue }) => {
54
78
  info(formatWaitProgressLine(waitedMs, timeoutMs, holder, queue));
55
79
  },
80
+ // Say it out loud. An extension is the moment the budget the
81
+ // operator asked for stops being the budget in force, and a wait
82
+ // that silently outlives its own stated timeout is
83
+ // indistinguishable from a broken one — which is how a downstream
84
+ // fork read it.
85
+ onWaitExtended: ({ ahead, budgetMs }) => {
86
+ info(formatWaitExtendedLine(ahead, budgetMs, waitLockSeconds * 1000));
87
+ },
88
+ // A budget that expires one position from the head pays the whole
89
+ // wait for nothing. While the queue is still MOVING the wait is
90
+ // working, so each advance renews the budget — up to a hard
91
+ // ceiling, so a pathological queue cannot wait forever. The
92
+ // no-flag fail-fast path deliberately gets none of this.
93
+ extendWhileAdvancing: {
94
+ maxWaitMs: Math.min(waitLockSeconds * 1000 * WAIT_EXTENSION_FACTOR, MAX_WAIT_LOCK_MS),
95
+ },
56
96
  }
57
97
  : {}),
58
98
  ownerMetadata: [`command=${command}`, `started=${new Date().toISOString()}`],
@@ -63,7 +103,8 @@ export async function withEngineSessionLock(projectRoot, command, operation, opt
63
103
  // (pinned by engine-session-lock tests); extend, don't reword.
64
104
  onTimeoutMessage: `Another FireForge engine-mutating command is already running. ` +
65
105
  `Wait for it to finish, then retry \`${command}\` — or pass --wait-lock [seconds] ` +
66
- `to wait for the lock (bare --wait-lock waits up to 60 seconds).`,
106
+ `to wait for the lock (bare --wait-lock waits up to 60 seconds). ` +
107
+ `Set FIREFORGE_WAIT_LOCK=<seconds> to apply a budget to every command in a session.`,
67
108
  onStaleLockMessage: (ageMs) => `Removed stale FireForge engine session lock (${Math.round(ageMs / 1000)}s old).`,
68
109
  });
69
110
  }
@@ -81,7 +122,7 @@ export async function readEngineSessionLockStatus(projectRoot) {
81
122
  * Renders the engine-session lock snapshot as operator-facing lines.
82
123
  * Pure, so the wording is unit-testable without a lock on disk.
83
124
  */
84
- export function formatEngineSessionLockStatus(snapshot) {
125
+ export function formatEngineSessionLockStatus(snapshot, holderCpuSeconds) {
85
126
  if (!snapshot.held) {
86
127
  return ['Engine session lock: free (no FireForge engine-mutating command is running).'];
87
128
  }
@@ -93,10 +134,25 @@ export function formatEngineSessionLockStatus(snapshot) {
93
134
  ? 'held by an unidentified process (no readable owner record)'
94
135
  : `held by PID ${String(holder.pid)}${holder.alive ? '' : ' (NOT RUNNING — stale lock)'}` +
95
136
  (holder.metadata.length > 0 ? ` (${holder.metadata.join(', ')})` : '');
96
- return [
137
+ const lines = [
97
138
  `Engine session lock: ${identity}, for ${elapsed}.`,
98
139
  `Queue depth: ${String(snapshot.queueDepth)} waiter(s).`,
99
140
  ];
141
+ // Liveness answers "does the process exist"; it does not answer "is it
142
+ // getting anywhere". A holder alive for minutes having used a fraction of
143
+ // a second of CPU is the shape of a wedged command, and every waiter
144
+ // behind it inherits the stall. Stated as an observation with its own
145
+ // caveat — a holder blocked on I/O is also idle, and this must not be read
146
+ // as licence to kill anything.
147
+ if (holder?.alive === true && holderCpuSeconds !== undefined) {
148
+ const cpu = `${holderCpuSeconds.toFixed(1)}s`;
149
+ const idle = snapshot.heldForMs !== undefined && snapshot.heldForMs > 60_000 && holderCpuSeconds < 2
150
+ ? ' — near-zero CPU over a long hold, which is what a WEDGED command looks like; ' +
151
+ 'a holder blocked on I/O looks the same, so confirm before acting'
152
+ : '';
153
+ lines.push(`Holder CPU time: ${cpu}${idle}.`);
154
+ }
155
+ return lines;
100
156
  }
101
157
  /** Prefix marking a generation token whose probe failed. */
102
158
  const UNAVAILABLE_PREFIX = 'unavailable:';
@@ -118,13 +174,12 @@ export async function snapshotEngineGeneration(engineDir) {
118
174
  }
119
175
  catch (error) {
120
176
  lastError = error;
121
- // A contended `.git/index.lock` is TRANSIENT, not a state: some
122
- // other writer held the index for the instant we looked.
123
- // Treating it as "unmeasurable" turns a perfectly good suite into
124
- // `FAIL reason=inconclusive` on a one-sided probe failure, which is
125
- // exactly the false alarm this guard must not raise. Anything else
126
- // — an unreadable `.git`, a non-git `engine/` — is a real state and
127
- // is reported immediately.
177
+ // A contended `.git/index.lock` is TRANSIENT, not a state: some other
178
+ // writer held the index for the instant we looked. Treating it as
179
+ // "unmeasurable" turns a perfectly good suite into
180
+ // `FAIL reason=inconclusive` on a one-sided probe failure. Anything
181
+ // else — an unreadable `.git`, a non-git `engine/` — is a real state
182
+ // and is reported immediately.
128
183
  if (!isIndexLockError(error) || attempt === GENERATION_PROBE_ATTEMPTS - 1)
129
184
  break;
130
185
  await delay(GENERATION_PROBE_RETRY_MS);
@@ -162,31 +217,109 @@ export function isUnavailableGenerationToken(token) {
162
217
  export function unavailableGenerationReason(token) {
163
218
  return token.slice(UNAVAILABLE_PREFIX.length);
164
219
  }
220
+ /** How many changed entries the delta names before truncating. */
221
+ const DELTA_ENTRY_LIMIT = 5;
222
+ /**
223
+ * Splits a generation token into its HEAD and porcelain halves. The porcelain
224
+ * body is `-z` output, so it is NUL-separated and never quoted.
225
+ */
226
+ function splitGenerationToken(token) {
227
+ const separator = token.indexOf('\0');
228
+ if (separator < 0)
229
+ return { head: token, body: '' };
230
+ return { head: token.slice(0, separator), body: token.slice(separator + 1) };
231
+ }
232
+ /**
233
+ * Parses `git status --porcelain=v1 -z` into `XY path` records.
234
+ *
235
+ * The rename/copy shape is the trap: `R`/`C` entries are followed by a
236
+ * SECOND NUL-separated field carrying the original path, and consuming only
237
+ * one desynchronises every record after it — turning a one-file rename into
238
+ * a report that the whole tree moved. Both fields are consumed and the
239
+ * record renders as `orig -> new`.
240
+ */
241
+ function parsePorcelainRecords(body) {
242
+ const fields = body.split('\0').filter((field) => field.length > 0);
243
+ const records = [];
244
+ for (let i = 0; i < fields.length; i++) {
245
+ const field = fields[i] ?? '';
246
+ // `XY ` — two status characters and a space — then the path.
247
+ if (field.length < 4)
248
+ continue;
249
+ const status = field.slice(0, 2);
250
+ const path = field.slice(3);
251
+ if (status.includes('R') || status.includes('C')) {
252
+ const origin = fields[i + 1];
253
+ i += 1;
254
+ records.push(`${status} ${origin ?? '?'} -> ${path}`);
255
+ continue;
256
+ }
257
+ records.push(`${status} ${path}`);
258
+ }
259
+ return records;
260
+ }
261
+ /**
262
+ * Describes what moved between two generation tokens, in operator terms.
263
+ *
264
+ * The refusal used to say only THAT the engine changed, which left the
265
+ * operator to reconstruct the writer by hand — `find -newermt` against the
266
+ * run window, minus the objdir, minus git's own bookkeeping. Everything
267
+ * needed to answer it is already in the two tokens, so the refusal answers
268
+ * it. Pure and total: an unparseable token yields an empty list rather than
269
+ * an exception, because a diagnostic must never convert a refusal into a
270
+ * crash.
271
+ *
272
+ * @param before - Token captured before the run
273
+ * @param after - Token captured after the run
274
+ * @returns Human-readable lines describing the delta; empty when nothing
275
+ * could be attributed
276
+ */
277
+ export function describeEngineGenerationDelta(before, after) {
278
+ const lines = [];
279
+ const from = splitGenerationToken(before);
280
+ const to = splitGenerationToken(after);
281
+ if (from.head !== to.head) {
282
+ lines.push(`HEAD moved: ${from.head} -> ${to.head}`);
283
+ }
284
+ const beforeRecords = new Set(parsePorcelainRecords(from.body));
285
+ const afterRecords = new Set(parsePorcelainRecords(to.body));
286
+ const appeared = [...afterRecords].filter((record) => !beforeRecords.has(record)).sort();
287
+ const disappeared = [...beforeRecords].filter((record) => !afterRecords.has(record)).sort();
288
+ const render = (label, records) => {
289
+ if (records.length === 0)
290
+ return;
291
+ const head = records.slice(0, DELTA_ENTRY_LIMIT);
292
+ const truncated = records.length - head.length;
293
+ lines.push(`${label}: ${head.join(', ')}${truncated > 0 ? `, … (+${String(truncated)} more)` : ''}`);
294
+ };
295
+ render('Working-tree entries that appeared', appeared);
296
+ render('Working-tree entries that went away', disappeared);
297
+ return lines;
298
+ }
165
299
  /**
166
300
  * Throws when the engine's generation token differs from `before`, i.e. the
167
301
  * engine was mutated by another writer while a test run was in flight.
168
302
  *
169
- * **An unmeasurable engine warns rather than throws.** `engine/` is not always
170
- * a git checkout — `fireforge download` extracts a source tarball — so a
171
- * failed probe is a legitimate state and must not fail the run.
303
+ * **An unmeasurable engine warns rather than throws.** `engine/` is not
304
+ * always a git checkout — `fireforge download` extracts a source tarball —
305
+ * so a failed probe is a legitimate state and must not fail the run.
172
306
  *
173
- * What it must also not do is pass *silently*. Before 0.41.0 the failure token
174
- * was `unavailable:<message>` and was compared for equality like any other, so
175
- * a git failure that reproduced across both probes (unreadable `.git`, a
176
- * permissions problem, a corrupt index) produced identical tokens, took the
177
- * `after === before` early return, and blessed a verdict the guard had never
178
- * verified — the precise outcome this guard exists to prevent. Two *differing*
179
- * failure messages were worse still: they fell through to the mutation branch
180
- * and reported a spurious "engine/ changed".
307
+ * What it must also not do is pass *silently*. A failure token compared for
308
+ * equality like any other would let a git failure that reproduces across
309
+ * both probes (unreadable `.git`, a permissions problem, a corrupt index)
310
+ * take the `after === before` early return and bless a verdict the guard
311
+ * never verified. Two *differing* failure messages are worse still: they
312
+ * fall through to the mutation branch and report a spurious
313
+ * "engine/ changed". Hence the prefix comparison rather than a token
314
+ * comparison — the token embeds the error message, and two genuine failures
315
+ * rarely phrase it identically.
181
316
  *
182
317
  * The tolerance is deliberately limited to a probe that failed *both* times:
183
318
  * that is the steady state of a non-git `engine/`, and it warns. A one-sided
184
- * transition is not that state. `engine/` that was measurable and then was not
185
- * means the second probe measured nothing about a checkout that demonstrably
186
- * had something to measure; the reverse means there was never a baseline to
187
- * compare against. Neither is a run whose verdict can be trusted, so both
188
- * throw. Compare on the prefix rather than the token, because the token embeds
189
- * the error message and two genuine failures rarely phrase it identically.
319
+ * transition is not that state. An `engine/` that was measurable and then
320
+ * was not means the second probe measured nothing about a checkout that
321
+ * demonstrably had something to measure; the reverse means there was never a
322
+ * baseline to compare against. Neither verdict can be trusted, so both throw.
190
323
  */
191
324
  export async function assertEngineGenerationUnchanged(engineDir, before) {
192
325
  const after = await snapshotEngineGeneration(engineDir);
@@ -204,12 +337,14 @@ export async function assertEngineGenerationUnchanged(engineDir, before) {
204
337
  'probed afterwards, so there is no baseline to compare against.'
205
338
  : `engine/ was probed before the run but could not be probed afterwards ` +
206
339
  `(${unavailableGenerationReason(after)}), so nothing was measured to compare against.`;
207
- throw new GeneralError(`${detail}\n\n` +
340
+ throw new InconclusiveVerdictError(`${detail}\n\n` +
208
341
  'This test verdict is inconclusive. Restore engine/ to a readable state, then rebuild ' +
209
342
  'and rerun the affected tests.');
210
343
  }
211
344
  if (after === before)
212
345
  return;
213
- throw new GeneralError('engine/ changed while `fireforge test` was running, so this test verdict is invalid/inconclusive.\n\n' +
346
+ const delta = describeEngineGenerationDelta(before, after);
347
+ throw new InconclusiveVerdictError('engine/ changed while `fireforge test` was running, so this test verdict is invalid/inconclusive.\n\n' +
348
+ (delta.length > 0 ? `What moved:\n${delta.map((line) => ` ${line}`).join('\n')}\n\n` : '') +
214
349
  'Use one writer per engine checkout, then rebuild and rerun the affected tests.');
215
350
  }
@@ -5,21 +5,21 @@
5
5
  * A slice that ADDS new files and MOVES lines into them from an existing
6
6
  * patch has no path through the two refusal flags:
7
7
  *
8
- * - `re-export --refuse-adjacent-unmanaged` refuses while the new files
9
- * are still unmanaged; and
8
+ * - `re-export --refuse-adjacent-unmanaged` refuses while the new files are
9
+ * still unmanaged; and
10
10
  * - exporting the new files as their own patch fails cross-patch lint,
11
11
  * because at the projected placement the OLD patch still contains the
12
12
  * moved code.
13
13
  *
14
- * Each guard is individually correct. Together they dead-end, and the way
15
- * out is non-obvious: adopt the new files into the OLD patch first
16
- * (`re-export --scan --scan-file`), then split them out into a patch of
17
- * their own (`patch move-files --create --order N`).
14
+ * Each guard is individually correct. Together they dead-end, and the way out
15
+ * is non-obvious: adopt the new files into the OLD patch first
16
+ * (`re-export --scan --scan-file`), then split them out into a patch of their
17
+ * own (`patch move-files --create --order N`).
18
18
  *
19
- * This module recognises the shape from evidence FireForge already has —
20
- * the pending diff's new-file content versus the added lines the existing
21
- * patches carry — so the refusal can name the sequence instead of leaving
22
- * the operator to derive it.
19
+ * This module recognises the shape from evidence FireForge already has — the
20
+ * pending diff's new-file content versus the added lines the existing patches
21
+ * carry — so the refusal can name the sequence instead of leaving the
22
+ * operator to derive it.
23
23
  */
24
24
  /** One existing patch that already carries lines the new files would add. */
25
25
  export interface MovedCodeOverlap {
@@ -6,21 +6,21 @@
6
6
  * A slice that ADDS new files and MOVES lines into them from an existing
7
7
  * patch has no path through the two refusal flags:
8
8
  *
9
- * - `re-export --refuse-adjacent-unmanaged` refuses while the new files
10
- * are still unmanaged; and
9
+ * - `re-export --refuse-adjacent-unmanaged` refuses while the new files are
10
+ * still unmanaged; and
11
11
  * - exporting the new files as their own patch fails cross-patch lint,
12
12
  * because at the projected placement the OLD patch still contains the
13
13
  * moved code.
14
14
  *
15
- * Each guard is individually correct. Together they dead-end, and the way
16
- * out is non-obvious: adopt the new files into the OLD patch first
17
- * (`re-export --scan --scan-file`), then split them out into a patch of
18
- * their own (`patch move-files --create --order N`).
15
+ * Each guard is individually correct. Together they dead-end, and the way out
16
+ * is non-obvious: adopt the new files into the OLD patch first
17
+ * (`re-export --scan --scan-file`), then split them out into a patch of their
18
+ * own (`patch move-files --create --order N`).
19
19
  *
20
- * This module recognises the shape from evidence FireForge already has —
21
- * the pending diff's new-file content versus the added lines the existing
22
- * patches carry — so the refusal can name the sequence instead of leaving
23
- * the operator to derive it.
20
+ * This module recognises the shape from evidence FireForge already has — the
21
+ * pending diff's new-file content versus the added lines the existing patches
22
+ * carry — so the refusal can name the sequence instead of leaving the
23
+ * operator to derive it.
24
24
  */
25
25
  import { extractAddedLinesPerFile } from './patch-lint-diff.js';
26
26
  /**
@@ -12,11 +12,10 @@ export interface LockHolder {
12
12
  /**
13
13
  * Live queue state around a contended lock.
14
14
  *
15
- * Under several concurrent sessions the lock itself behaved correctly; the
16
- * gap was VISIBILITY — operators inferred queue state from `ps` and their
17
- * own wait lines. Waiters advertise themselves in a sibling directory so
18
- * both the wait output and `fireforge status --lock` can say how many are
19
- * queued and how many are ahead of you.
15
+ * The lock itself behaves correctly under several concurrent sessions; the
16
+ * gap this closes is VISIBILITY. Waiters advertise themselves in a sibling
17
+ * directory so both the wait output and `fireforge status --lock` can say
18
+ * how many are queued and how many are ahead of you.
20
19
  */
21
20
  export interface LockQueueState {
22
21
  /** Live waiters currently queued for the lock, including this process. */
@@ -58,6 +57,37 @@ export interface FileLockOptions {
58
57
  /** Queue state at this poll, when the waiter registry is available. */
59
58
  queue?: LockQueueState | undefined;
60
59
  }) => void;
60
+ /**
61
+ * When set, a wait whose QUEUE POSITION is still improving is granted a
62
+ * fresh `timeoutMs` budget on each advance, up to `maxWaitMs` total.
63
+ *
64
+ * A fixed wall-clock budget against a queue whose depth the tool knows is
65
+ * the wrong shape under several concurrent sessions: a wait that expires
66
+ * one position from the head pays the entire wait and gets nothing. The
67
+ * distinction that matters is not "how long have I waited" but "is this
68
+ * queue moving" — a stalled queue still starves on the original budget,
69
+ * which is the case the timeout exists for.
70
+ *
71
+ * Requires the waiter registry (see {@link LockQueueState}); a lock with
72
+ * no readable queue never extends, because nothing observed an advance.
73
+ */
74
+ extendWhileAdvancing?: {
75
+ maxWaitMs: number;
76
+ };
77
+ /**
78
+ * Invoked when {@link extendWhileAdvancing} actually grants an extension.
79
+ *
80
+ * Separate from {@link onWaitProgress} on purpose: an extension is the one
81
+ * moment the budget the operator asked for stops being the budget in
82
+ * force, and a wait that silently outlives its stated timeout is
83
+ * indistinguishable from a broken one. `budgetMs` is the new TOTAL budget
84
+ * measured from the start of the wait, so it can be compared directly
85
+ * against the requested figure.
86
+ */
87
+ onWaitExtended?: (extension: {
88
+ ahead: number;
89
+ budgetMs: number;
90
+ }) => void;
61
91
  onTimeoutMessage?: string;
62
92
  onStaleLockMessage?: (ageMs: number) => string | undefined;
63
93
  /** Extra owner-file lines for human diagnostics; ignored by lock mechanics. */
@@ -99,5 +129,32 @@ export interface LockStatusSnapshot {
99
129
  * lock is reported as free, never as an error.
100
130
  */
101
131
  export declare function readLockStatus(lockPath: string): Promise<LockStatusSnapshot>;
132
+ /**
133
+ * Removes a lock directory only if this process still owns it, for callers
134
+ * that hold no acquisition token.
135
+ *
136
+ * The ownership half of {@link releaseLock}, minus the token check — a
137
+ * signal-time sweeper knows which lock paths its own operations opened, but
138
+ * not the per-acquisition UUID those acquisitions minted. The PID check is
139
+ * still the one that matters: it stops the sweeper deleting a lock a
140
+ * DIFFERENT process acquired in the window between our operation dying and
141
+ * the sweep running.
142
+ *
143
+ * Never throws: callers are shutdown paths where a slow or failing I/O must
144
+ * not prevent the process from exiting. Returns whether the directory was
145
+ * removed so callers can log accurately.
146
+ *
147
+ * @param lockPath - Lock directory to remove
148
+ * @returns True when the lock was removed, false when it was left in place
149
+ */
150
+ export declare function releaseLockIfOwned(lockPath: string): Promise<boolean>;
151
+ /**
152
+ * Releases every lock this process still holds, for the signal path.
153
+ *
154
+ * Never throws and never rejects: a shutdown path must not be able to keep
155
+ * the process alive. Returns the paths actually removed so the caller can
156
+ * report accurately.
157
+ */
158
+ export declare function forceReleaseHeldLocksForSignal(): Promise<string[]>;
102
159
  /** Runs an async operation while holding a directory lock, with stale-lock recovery. */
103
160
  export declare function withFileLock<T>(lockPath: string, operation: () => Promise<T>, options?: FileLockOptions): Promise<T>;