@hominis/fireforge 0.52.0 → 0.53.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 (243) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/build-info.json +4 -4
  3. package/dist/src/commands/bootstrap.js +2 -3
  4. package/dist/src/commands/build-prelock.d.ts +3 -3
  5. package/dist/src/commands/build-prelock.js +3 -3
  6. package/dist/src/commands/build.js +4 -0
  7. package/dist/src/commands/declarations-report.d.ts +9 -3
  8. package/dist/src/commands/declarations-report.js +18 -11
  9. package/dist/src/commands/discard.js +3 -3
  10. package/dist/src/commands/doctor-furnace-config-sync.js +4 -4
  11. package/dist/src/commands/doctor-furnace.js +5 -5
  12. package/dist/src/commands/export-adopt-check.d.ts +5 -6
  13. package/dist/src/commands/export-adopt-check.js +5 -6
  14. package/dist/src/commands/export-flow.js +6 -9
  15. package/dist/src/commands/export-staged-registration.d.ts +4 -4
  16. package/dist/src/commands/export-staged-registration.js +4 -4
  17. package/dist/src/commands/forward-import-remedy.d.ts +2 -3
  18. package/dist/src/commands/forward-import-remedy.js +2 -3
  19. package/dist/src/commands/furnace/create-dry-run.d.ts +2 -2
  20. package/dist/src/commands/furnace/deploy-fragment-includers.d.ts +2 -2
  21. package/dist/src/commands/furnace/deploy-fragment-includers.js +2 -2
  22. package/dist/src/commands/furnace/index.js +2 -2
  23. package/dist/src/commands/lint.js +4 -4
  24. package/dist/src/commands/patch/compact.js +1 -1
  25. package/dist/src/commands/patch/lint-ignore-from.d.ts +3 -4
  26. package/dist/src/commands/patch/lint-ignore-from.js +3 -4
  27. package/dist/src/commands/patch/move-files-adopt.d.ts +33 -0
  28. package/dist/src/commands/patch/move-files-adopt.js +65 -0
  29. package/dist/src/commands/patch/move-files-create.js +41 -20
  30. package/dist/src/commands/patch/move-files-into.d.ts +18 -0
  31. package/dist/src/commands/patch/move-files-into.js +125 -60
  32. package/dist/src/commands/patch/move-files-retire.d.ts +39 -0
  33. package/dist/src/commands/patch/move-files-retire.js +91 -0
  34. package/dist/src/commands/patch/move-files.d.ts +3 -0
  35. package/dist/src/commands/patch/move-files.js +42 -12
  36. package/dist/src/commands/patch/move-size-tier.d.ts +27 -0
  37. package/dist/src/commands/patch/move-size-tier.js +66 -0
  38. package/dist/src/commands/patch/rename.js +1 -1
  39. package/dist/src/commands/patch/reorder-projection.d.ts +17 -0
  40. package/dist/src/commands/patch/reorder-projection.js +49 -0
  41. package/dist/src/commands/patch/reorder-shift.d.ts +30 -0
  42. package/dist/src/commands/patch/reorder-shift.js +307 -0
  43. package/dist/src/commands/patch/reorder.d.ts +9 -15
  44. package/dist/src/commands/patch/reorder.js +52 -50
  45. package/dist/src/commands/patch/split-plan.d.ts +31 -3
  46. package/dist/src/commands/patch/split-plan.js +36 -12
  47. package/dist/src/commands/patch/split-staged.d.ts +2 -2
  48. package/dist/src/commands/patch/split-staged.js +2 -2
  49. package/dist/src/commands/patch/split.js +18 -5
  50. package/dist/src/commands/patch/staged-dependency-entries.d.ts +9 -1
  51. package/dist/src/commands/patch/staged-dependency-entries.js +32 -4
  52. package/dist/src/commands/patch/staged-dependency-from.js +15 -9
  53. package/dist/src/commands/patch/staged-dependency.js +13 -9
  54. package/dist/src/commands/patch/tier.js +2 -2
  55. package/dist/src/commands/patch-rename-map.d.ts +14 -3
  56. package/dist/src/commands/patch-rename-map.js +21 -3
  57. package/dist/src/commands/re-export-adjacent.d.ts +4 -5
  58. package/dist/src/commands/re-export-lint.d.ts +4 -3
  59. package/dist/src/commands/re-export-lint.js +4 -3
  60. package/dist/src/commands/re-export-preflight.js +1 -2
  61. package/dist/src/commands/re-export-purity-scope.d.ts +1 -1
  62. package/dist/src/commands/re-export-purity-scope.js +13 -7
  63. package/dist/src/commands/re-export-retired-declarations.d.ts +22 -0
  64. package/dist/src/commands/re-export-retired-declarations.js +113 -0
  65. package/dist/src/commands/re-export-single.js +2 -2
  66. package/dist/src/commands/re-export-stale-registrations.d.ts +27 -6
  67. package/dist/src/commands/re-export-stale-registrations.js +65 -21
  68. package/dist/src/commands/re-export.js +4 -0
  69. package/dist/src/commands/rebase/continue.js +3 -3
  70. package/dist/src/commands/register.js +5 -4
  71. package/dist/src/commands/status.js +1 -1
  72. package/dist/src/commands/test-build-furnace.d.ts +1 -2
  73. package/dist/src/commands/test-build-furnace.js +1 -2
  74. package/dist/src/commands/test-build-phase.d.ts +8 -0
  75. package/dist/src/commands/test-build-phase.js +42 -7
  76. package/dist/src/commands/test-control-restore.d.ts +40 -0
  77. package/dist/src/commands/test-control-restore.js +86 -0
  78. package/dist/src/commands/test-dispatch.js +68 -7
  79. package/dist/src/commands/test-multi-verdict.js +1 -0
  80. package/dist/src/commands/test-orchestrate.d.ts +6 -0
  81. package/dist/src/commands/test-orchestrate.js +28 -0
  82. package/dist/src/commands/test-perf-scope.d.ts +1 -1
  83. package/dist/src/commands/test-perf-scope.js +1 -1
  84. package/dist/src/commands/test-prelock.d.ts +7 -8
  85. package/dist/src/commands/test-prelock.js +15 -12
  86. package/dist/src/commands/test-register.js +2 -2
  87. package/dist/src/commands/test-results.d.ts +10 -1
  88. package/dist/src/commands/test-results.js +20 -3
  89. package/dist/src/commands/test-run.d.ts +6 -0
  90. package/dist/src/commands/test-run.js +23 -4
  91. package/dist/src/commands/test-shards.d.ts +7 -3
  92. package/dist/src/commands/test-shards.js +24 -7
  93. package/dist/src/commands/test-stale-gate.d.ts +6 -1
  94. package/dist/src/commands/test-stale-gate.js +40 -2
  95. package/dist/src/commands/test-verdict.d.ts +5 -3
  96. package/dist/src/commands/test.js +10 -0
  97. package/dist/src/commands/token.js +2 -2
  98. package/dist/src/commands/verify.js +1 -1
  99. package/dist/src/commands/watch.js +1 -1
  100. package/dist/src/commands/wire.js +1 -1
  101. package/dist/src/core/build-audit-resolve.d.ts +4 -5
  102. package/dist/src/core/build-audit-resolve.js +4 -5
  103. package/dist/src/core/build-audit-skip.d.ts +2 -2
  104. package/dist/src/core/build-audit.d.ts +3 -4
  105. package/dist/src/core/build-baseline-types.d.ts +24 -3
  106. package/dist/src/core/build-baseline-validate.d.ts +30 -1
  107. package/dist/src/core/build-baseline-validate.js +127 -35
  108. package/dist/src/core/build-baseline.d.ts +8 -2
  109. package/dist/src/core/build-baseline.js +25 -16
  110. package/dist/src/core/build-jar-escalation.d.ts +4 -4
  111. package/dist/src/core/build-jar-escalation.js +4 -4
  112. package/dist/src/core/build-prepare.js +4 -4
  113. package/dist/src/core/config-validate.js +4 -4
  114. package/dist/src/core/coverage-extend.d.ts +5 -3
  115. package/dist/src/core/coverage-extend.js +8 -3
  116. package/dist/src/core/coverage-records.d.ts +4 -5
  117. package/dist/src/core/coverage-records.js +4 -5
  118. package/dist/src/core/destructive.d.ts +2 -2
  119. package/dist/src/core/destructive.js +2 -2
  120. package/dist/src/core/discard-baseline.d.ts +2 -2
  121. package/dist/src/core/discard-baseline.js +3 -3
  122. package/dist/src/core/engine-session-lock.d.ts +1 -1
  123. package/dist/src/core/engine-session-lock.js +1 -1
  124. package/dist/src/core/file-lock-owner.d.ts +5 -5
  125. package/dist/src/core/file-lock-owner.js +5 -5
  126. package/dist/src/core/file-lock-queue.js +6 -8
  127. package/dist/src/core/file-lock-shared.d.ts +4 -4
  128. package/dist/src/core/file-lock-shared.js +4 -4
  129. package/dist/src/core/file-lock.js +7 -10
  130. package/dist/src/core/firefox.js +2 -2
  131. package/dist/src/core/furnace-apply-overwrite-warn.d.ts +1 -1
  132. package/dist/src/core/furnace-apply-overwrite-warn.js +1 -1
  133. package/dist/src/core/furnace-apply.js +3 -3
  134. package/dist/src/core/furnace-operation.js +5 -5
  135. package/dist/src/core/furnace-registration-jar-inplace.js +5 -6
  136. package/dist/src/core/furnace-registration-rename.d.ts +5 -5
  137. package/dist/src/core/furnace-registration-rename.js +5 -5
  138. package/dist/src/core/furnace-registration-validate.d.ts +2 -1
  139. package/dist/src/core/furnace-registration-validate.js +2 -1
  140. package/dist/src/core/furnace-registration.js +2 -2
  141. package/dist/src/core/git-base.js +4 -3
  142. package/dist/src/core/git-diff.js +1 -1
  143. package/dist/src/core/git.js +1 -1
  144. package/dist/src/core/harness-orphans.d.ts +6 -6
  145. package/dist/src/core/harness-orphans.js +6 -6
  146. package/dist/src/core/harness-port-reservation.d.ts +60 -0
  147. package/dist/src/core/harness-port-reservation.js +122 -0
  148. package/dist/src/core/history-log-file.d.ts +9 -9
  149. package/dist/src/core/history-log-file.js +15 -15
  150. package/dist/src/core/history-log.d.ts +3 -3
  151. package/dist/src/core/history-log.js +3 -3
  152. package/dist/src/core/launchable-build.d.ts +39 -0
  153. package/dist/src/core/launchable-build.js +85 -0
  154. package/dist/src/core/marionette-port.js +1 -3
  155. package/dist/src/core/moz-manifest-rules.js +3 -2
  156. package/dist/src/core/negative-control-compiled.d.ts +13 -3
  157. package/dist/src/core/negative-control-compiled.js +27 -5
  158. package/dist/src/core/parser-fallback.d.ts +5 -5
  159. package/dist/src/core/parser-fallback.js +5 -5
  160. package/dist/src/core/patch-identifier-suggest.d.ts +6 -0
  161. package/dist/src/core/patch-identifier-suggest.js +11 -4
  162. package/dist/src/core/patch-lint-checkjs.d.ts +1 -1
  163. package/dist/src/core/patch-lint-checkjs.js +1 -1
  164. package/dist/src/core/patch-lint-css.js +2 -2
  165. package/dist/src/core/patch-lint-file-size.d.ts +5 -7
  166. package/dist/src/core/patch-lint-file-size.js +5 -7
  167. package/dist/src/core/patch-lint-ignore.d.ts +3 -3
  168. package/dist/src/core/patch-lint-ignore.js +3 -3
  169. package/dist/src/core/patch-lint-prettier.d.ts +4 -6
  170. package/dist/src/core/patch-lint-prettier.js +4 -6
  171. package/dist/src/core/patch-lint-projection.d.ts +1 -1
  172. package/dist/src/core/patch-lint-projection.js +1 -1
  173. package/dist/src/core/patch-lint-queue-types.d.ts +2 -2
  174. package/dist/src/core/patch-lint-queue-types.js +2 -2
  175. package/dist/src/core/patch-manifest-consistency.d.ts +2 -2
  176. package/dist/src/core/patch-manifest-consistency.js +2 -2
  177. package/dist/src/core/patch-manifest-io.d.ts +5 -1
  178. package/dist/src/core/patch-manifest-io.js +12 -10
  179. package/dist/src/core/patch-manifest-key-order.d.ts +3 -4
  180. package/dist/src/core/patch-manifest-key-order.js +3 -4
  181. package/dist/src/core/patch-parse.d.ts +3 -3
  182. package/dist/src/core/patch-parse.js +3 -3
  183. package/dist/src/core/patch-policy.d.ts +1 -1
  184. package/dist/src/core/patch-policy.js +1 -1
  185. package/dist/src/core/patch-source-metadata.d.ts +1 -1
  186. package/dist/src/core/patch-source-metadata.js +1 -1
  187. package/dist/src/core/patch-subset.d.ts +5 -3
  188. package/dist/src/core/patch-subset.js +8 -7
  189. package/dist/src/core/patch-transform.d.ts +2 -2
  190. package/dist/src/core/patch-transform.js +2 -2
  191. package/dist/src/core/rebase-session.js +4 -6
  192. package/dist/src/core/register-xpcshell-test.d.ts +28 -7
  193. package/dist/src/core/register-xpcshell-test.js +76 -27
  194. package/dist/src/core/run-log.d.ts +8 -8
  195. package/dist/src/core/run-log.js +8 -8
  196. package/dist/src/core/status-classify.d.ts +1 -2
  197. package/dist/src/core/status-classify.js +1 -2
  198. package/dist/src/core/test-harness-crash.d.ts +5 -3
  199. package/dist/src/core/test-harness-crash.js +13 -14
  200. package/dist/src/core/test-harness-infra.d.ts +32 -0
  201. package/dist/src/core/test-harness-infra.js +69 -0
  202. package/dist/src/core/test-harness-signature.d.ts +6 -0
  203. package/dist/src/core/test-stale-check.d.ts +38 -0
  204. package/dist/src/core/test-stale-check.js +82 -1
  205. package/dist/src/core/test-stall-triage.js +5 -7
  206. package/dist/src/core/token-category.js +2 -2
  207. package/dist/src/core/token-inventory.js +3 -3
  208. package/dist/src/core/token-manager.d.ts +2 -2
  209. package/dist/src/core/token-manager.js +5 -7
  210. package/dist/src/core/token-variant.d.ts +6 -6
  211. package/dist/src/core/token-variant.js +7 -7
  212. package/dist/src/core/typecheck-shim.js +1 -1
  213. package/dist/src/core/wire-destroy.d.ts +1 -1
  214. package/dist/src/core/wire-destroy.js +1 -1
  215. package/dist/src/core/wire-dom-fragment.js +1 -1
  216. package/dist/src/core/wire-init.d.ts +1 -1
  217. package/dist/src/core/wire-init.js +1 -1
  218. package/dist/src/core/wire-subscript.d.ts +1 -1
  219. package/dist/src/core/wire-subscript.js +1 -1
  220. package/dist/src/core/wire-utils.d.ts +2 -2
  221. package/dist/src/core/wire-utils.js +3 -3
  222. package/dist/src/errors/base.d.ts +1 -1
  223. package/dist/src/errors/base.js +1 -1
  224. package/dist/src/errors/rebase.d.ts +1 -1
  225. package/dist/src/errors/rebase.js +1 -1
  226. package/dist/src/types/commands/options.d.ts +26 -3
  227. package/dist/src/types/config.d.ts +5 -7
  228. package/dist/src/utils/options.js +2 -2
  229. package/dist/src/utils/process.js +2 -2
  230. package/dist/src/utils/regex.d.ts +2 -2
  231. package/dist/src/utils/regex.js +3 -3
  232. package/dist/src/utils/sleep.d.ts +5 -6
  233. package/dist/src/utils/sleep.js +5 -6
  234. package/dist/src/utils/validation.js +2 -2
  235. package/docs/configuration.md +21 -23
  236. package/docs/exit-codes.md +8 -11
  237. package/docs/furnace.md +3 -3
  238. package/docs/lifecycle-invariants.md +36 -45
  239. package/docs/machine-output.md +41 -24
  240. package/docs/patch-workflow.md +129 -24
  241. package/docs/run-logs.md +8 -9
  242. package/docs/testing.md +105 -46
  243. package/package.json +1 -1
@@ -22,6 +22,8 @@
22
22
  * legitimately have a fresh `dist/` with no FireForge-recorded baseline
23
23
  * update.
24
24
  */
25
+ import { stat } from 'node:fs/promises';
26
+ import { join, relative } from 'node:path';
25
27
  import { toError } from '../utils/errors.js';
26
28
  import { verbose, warn } from '../utils/logger.js';
27
29
  import { normalizePathSlashes } from '../utils/paths.js';
@@ -30,6 +32,8 @@ import { readBuildBaseline } from './build-baseline.js';
30
32
  import { hashEngineFile } from './coverage-extend.js';
31
33
  import { isTestManifestPath } from './coverage-records.js';
32
34
  import { collectChangedEnginePaths, dropPathsMatchingFingerprints } from './engine-changes.js';
35
+ import { readLinkedAt } from './launchable-build.js';
36
+ import { isCompiledSourcePath } from './negative-control-compiled.js';
33
37
  import { formatStaleExemptions, partitionStaleCandidates, } from './test-stale-exempt.js';
34
38
  /** Cap on the number of changed paths rendered inline. */
35
39
  const STALE_PATHS_LIMIT = 10;
@@ -91,6 +95,83 @@ export function formatStaleBuildWarning(result) {
91
95
  '"fireforge build --ui") first. Passing --build skips this check.' +
92
96
  exemptLine);
93
97
  }
98
+ /**
99
+ * Path segments whose compiled sources never reach the binary under test
100
+ * (gtests, cppunittests, CI config YAML): an edit there must not make the
101
+ * browser read as stale.
102
+ */
103
+ const NON_RUNTIME_SEGMENTS = new Set(['test', 'tests', 'gtest', 'testing', 'taskcluster']);
104
+ /**
105
+ * Changed compiled sources (C++, Rust, IDL, WebIDL, IPDL, StaticPrefList
106
+ * YAML, …) whose mtime is newer than the launchable build's last link.
107
+ * `mach build faster` compiles none of them, so such a run tests the
108
+ * binary from before the edit, which reads as a failed fix rather than a
109
+ * stale build. Candidates are the engine paths changed since the baseline
110
+ * plus every workdir edit; a fork's persistent patch diff is older than any
111
+ * build that followed it, so only genuine post-build edits remain.
112
+ * Returns undefined when there is nothing to compare against (no
113
+ * launchable binary).
114
+ *
115
+ * @param engineDir - Absolute engine directory
116
+ * @param baseline - Last-build baseline, when one exists
117
+ * @param launchablePath - Engine-relative launchable binary path
118
+ */
119
+ export async function findCompiledSourcesNewerThanBinary(engineDir, baseline, launchablePath) {
120
+ if (launchablePath === undefined)
121
+ return undefined;
122
+ const linked = await readLinkedAt(engineDir, launchablePath);
123
+ if (linked === undefined)
124
+ return undefined;
125
+ const changed = await collectChangedEnginePaths(engineDir, baseline, 'Stale-compiled preflight');
126
+ const candidates = changed.filter((path) => isCompiledSourcePath(path) &&
127
+ !path.split('/').some((segment) => NON_RUNTIME_SEGMENTS.has(segment)));
128
+ const paths = [];
129
+ for (const path of candidates) {
130
+ try {
131
+ const { mtimeMs } = await stat(join(engineDir, path));
132
+ if (mtimeMs > linked.mtimeMs)
133
+ paths.push(path);
134
+ }
135
+ catch {
136
+ // A deleted source cannot be newer than anything; the build that
137
+ // drops it is the stale-build check's business.
138
+ }
139
+ }
140
+ return {
141
+ paths: paths.sort(),
142
+ linkedPath: relative(engineDir, linked.path),
143
+ linkedAt: new Date(linked.mtimeMs),
144
+ };
145
+ }
146
+ /**
147
+ * The `stale-compiled` refusal (or, under `--allow-stale-build`, warning).
148
+ *
149
+ * @param result - A non-empty {@link StaleCompiledResult}
150
+ */
151
+ export function formatStaleCompiledRefusal(result) {
152
+ const head = result.paths.slice(0, STALE_PATHS_LIMIT).map((path) => `engine/${path}`);
153
+ const truncated = result.paths.length - head.length;
154
+ const list = head.join(', ') + (truncated > 0 ? `, … (+${truncated} more)` : '');
155
+ return (`Compiled sources changed after the binary under test was linked ` +
156
+ `(engine/${result.linkedPath}, ${result.linkedAt.toISOString()}): ${list}.\n` +
157
+ 'The run would test the binary from before those edits, so a red reads as a failed fix ' +
158
+ 'and a green proves nothing about them. Run "fireforge build" first, or rerun with ' +
159
+ '"fireforge test --build", which escalates to a full mach build when compiled sources ' +
160
+ 'changed. --allow-stale-build turns this refusal into a warning.');
161
+ }
162
+ /**
163
+ * The notice that explains why a `test --build` pre-test build escalated
164
+ * to a full `mach build` for stale compiled sources.
165
+ *
166
+ * @param result - A non-empty {@link StaleCompiledResult}
167
+ */
168
+ export function formatStaleCompiledBuildNotice(result) {
169
+ const head = result.paths.slice(0, STALE_PATHS_LIMIT).map((path) => `engine/${path}`);
170
+ const truncated = result.paths.length - head.length;
171
+ return (`Compiled sources changed after the binary was linked (${head.join(', ')}` +
172
+ `${truncated > 0 ? `, … (+${truncated} more)` : ''}); escalating this pre-test build to a ` +
173
+ 'full mach build, since mach build faster compiles nothing.');
174
+ }
94
175
  /**
95
176
  * Sentinel returned by {@link findUncoveredRequestPaths} when a full-suite
96
177
  * request (no paths) is checked against a scoped coverage record.
@@ -200,7 +281,7 @@ export function formatTestCoverageRefusal(uncovered, coverage, changedManifests
200
281
  };
201
282
  const rebuildTargets = uncovered.filter((p) => p !== FULL_SUITE_REQUEST);
202
283
  // Per-path retention is the default, so rebuilding only the uncovered
203
- // paths no longer costs anyone else's claim. Saying so is what keeps a
284
+ // paths costs no one else's claim. Saying so is what keeps a
204
285
  // peer from rebuilding everything "to be safe".
205
286
  const keepHint = rebuildTargets.length > 0
206
287
  ? ' That scoped build keeps every other recorded path whose own staging inputs (manifest directory and the fixtures its manifest names) and engine/mozconfig are unchanged.'
@@ -37,9 +37,8 @@ const NO_OUTPUT_STALL_CONTROL_STEP = 'First, run a known-good control test. If t
37
37
  * - (4) is mechanical and was root-caused downstream: the harness serves
38
38
  * its manifest from `server.js` on 8888, and a survivor of an
39
39
  * interrupted run keeps the port. Its probe is exact, and the preflight
40
- * in `mochitest-server-port.ts` now refuses it before a run starts.
41
- * - (3) is now mechanical and root-caused, replacing the correlation an
42
- * earlier revision recorded. A `chrome://` (or `resource://`) URL that
40
+ * in `mochitest-server-port.ts` refuses it before a run starts.
41
+ * - (3) is mechanical and root-caused. A `chrome://` (or `resource://`) URL that
43
42
  * resolves to nothing reaches `CheckForBrokenChromeURL`
44
43
  * (netwerk/base/nsNetUtil.cpp), which outside automation only
45
44
  * `printf_stderr`s `Missing chrome or resource URL: <uri>`, but under
@@ -47,9 +46,8 @@ const NO_OUTPUT_STALL_CONTROL_STEP = 'First, run a known-good control test. If t
47
46
  * `MOZ_CRASH_UNSAFE_PRINTF("Missing chrome or resource URLs: %s")`. A
48
47
  * downstream reproduction symbolicated the faulting frame as
49
48
  * `CheckForBrokenChromeURL`, which is the established diagnosis the bar
50
- * in `mach-error-hints.ts` asks for. The discredited "stalls first
51
- * paint" story an even earlier revision asserted must not come back: a
52
- * failed image load in Gecko fires `error` and does not gate the
49
+ * in `mach-error-hints.ts` asks for. It is not a "stalls first paint"
50
+ * problem: a failed image load in Gecko fires `error` and does not gate the
53
51
  * document load event.
54
52
  *
55
53
  * Detection by log line is not specified for (3), because of the mechanism
@@ -62,7 +60,7 @@ const NO_OUTPUT_STALL_CONTROL_STEP = 'First, run a known-good control test. If t
62
60
  * the census points at the two artefacts that do carry it: the OS crash
63
61
  * report (whose faulting frame names `CheckForBrokenChromeURL`) and the
64
62
  * out-of-automation smoke probe (which prints the non-crashing spelling
65
- * and, since 0.45.0, counts it as an unallowed error so the exit code
63
+ * and counts it as an unallowed error so the exit code
66
64
  * agrees with the capture).
67
65
  */
68
66
  const NO_OUTPUT_STALL_TRIAGE = [
@@ -18,8 +18,8 @@ import { escapeRegex } from '../utils/regex.js';
18
18
  * Body of the first `/* … *\/` comment on `line`, or `undefined` when the
19
19
  * line carries no closed block comment.
20
20
  *
21
- * This uses index arithmetic rather than a regex: every regex spelling of
22
- * "banner comment" this module used to carry was super-linear on a line that
21
+ * This uses index arithmetic rather than a regex: the natural regex
22
+ * spellings of "banner comment" are super-linear on a line that
23
23
  * repeats the opening shape (CodeQL `js/polynomial-redos`), and `tokens.css`
24
24
  * is a file FireForge reads out of a consumer's engine tree, so a
25
25
  * pathological line needs no attacker to arrive.
@@ -4,9 +4,9 @@
4
4
  * tokens each declares, and what a single token resolves to in every theme
5
5
  * block that mentions it.
6
6
  *
7
- * `token add` requires `--category` and, until now, nothing reported the
8
- * categories a project actually has, so the only way to name one correctly
9
- * was to hand-parse a neighbouring `= Category =` banner out of the file.
7
+ * `token add` requires `--category`, so it needs a report of the categories
8
+ * a project actually has; without one, the only way to name one correctly
9
+ * is to hand-parse a neighbouring `= Category =` banner out of the file.
10
10
  * This module is that report. It reuses the banner and
11
11
  * category-region model from `token-category.ts` rather than parsing
12
12
  * tokens CSS a second way: two readers that disagree on what a banner is
@@ -4,7 +4,7 @@ import { type TokenDeclarationLocation } from './token-category.js';
4
4
  *
5
5
  * Derived from {@link TOKEN_MODES} so the runtime allowlist and the type
6
6
  * cannot drift: adding a member here without updating the list (or the
7
- * reverse) is no longer possible, because there is only one list.
7
+ * reverse) is impossible, because there is only one list.
8
8
  */
9
9
  export declare const TOKEN_MODES: readonly ["auto", "static", "override"];
10
10
  /** Dark mode behaviour for a token. */
@@ -21,7 +21,7 @@ export interface AddTokenOptions {
21
21
  value: string;
22
22
  /**
23
23
  * Token category matching section headers in the CSS file. Required for a
24
- * base declaration. Ignored, and no longer required, under
24
+ * base declaration. Ignored, and not required, under
25
25
  * {@link AddTokenOptions.variant}, which routes into a `:root<selector>`
26
26
  * block and never touches a category section.
27
27
  */
@@ -17,7 +17,7 @@ import { findVariantBlockDeclaration, insertVariantDeclaration, validateVariantS
17
17
  *
18
18
  * Derived from {@link TOKEN_MODES} so the runtime allowlist and the type
19
19
  * cannot drift: adding a member here without updating the list (or the
20
- * reverse) is no longer possible, because there is only one list.
20
+ * reverse) is impossible, because there is only one list.
21
21
  */
22
22
  export const TOKEN_MODES = ['auto', 'static', 'override'];
23
23
  /** Narrows an arbitrary string to a {@link TokenMode}. */
@@ -109,12 +109,10 @@ function normalizeVariantOption(options) {
109
109
  * Narrows {@link AddTokenOptions.category} for the base-declaration path,
110
110
  * which cannot proceed without one.
111
111
  *
112
- * `--category` used to be unconditionally required, including under
113
- * `--variant`, where the declaration is routed into a `:root<selector>`
114
- * block and the category names nothing about where the token lands. The
115
- * requirement existed so the argument would not be silently discarded,
116
- * a fair goal that produced a mandatory argument describing nothing. Under
117
- * a variant it is now optional. Everywhere else this states the reason
112
+ * `--category` is optional under `--variant`, where the declaration is
113
+ * routed into a `:root<selector>` block and the category names nothing
114
+ * about where the token lands, so requiring it there would make a
115
+ * mandatory argument describing nothing. Everywhere else this states the reason
118
116
  * rather than leaving commander to print a bare "required option not
119
117
  * specified".
120
118
  *
@@ -28,12 +28,12 @@ export type VariantValidation = {
28
28
  * Boolean-attribute fragments are returned unchanged.
29
29
  *
30
30
  * Structure is decided by {@link rootAttributeSelector}, the matcher's own
31
- * parse, rather than by a second regex. 0.44.2 widened
32
- * the matcher to consume consecutive `[…]` groups and stop at a pseudo-class
33
- * tail, and the validator did not move with it, so
34
- * `:root[data-skin="humanist"]:not([data-private])` was a block the tool
35
- * could find but would not let you name. Running both halves off one parse
36
- * is what stops them drifting apart again. Character-level safety stays a
31
+ * parse, rather than by a second regex. The matcher consumes consecutive
32
+ * `[…]` groups and stops at a pseudo-class tail, and a separate validator
33
+ * that does not follow it leaves blocks such as
34
+ * `:root[data-skin="humanist"]:not([data-private])` findable but
35
+ * unnameable. Running both halves off one parse keeps them from drifting
36
+ * apart. Character-level safety stays a
37
37
  * separate, per-group check, because the matcher finds bracket boundaries
38
38
  * and validates no characters at all.
39
39
  *
@@ -68,12 +68,12 @@ function normalizeAttributeFragment(group) {
68
68
  * Boolean-attribute fragments are returned unchanged.
69
69
  *
70
70
  * Structure is decided by {@link rootAttributeSelector}, the matcher's own
71
- * parse, rather than by a second regex. 0.44.2 widened
72
- * the matcher to consume consecutive `[…]` groups and stop at a pseudo-class
73
- * tail, and the validator did not move with it, so
74
- * `:root[data-skin="humanist"]:not([data-private])` was a block the tool
75
- * could find but would not let you name. Running both halves off one parse
76
- * is what stops them drifting apart again. Character-level safety stays a
71
+ * parse, rather than by a second regex. The matcher consumes consecutive
72
+ * `[…]` groups and stops at a pseudo-class tail, and a separate validator
73
+ * that does not follow it leaves blocks such as
74
+ * `:root[data-skin="humanist"]:not([data-private])` findable but
75
+ * unnameable. Running both halves off one parse keeps them from drifting
76
+ * apart. Character-level safety stays a
77
77
  * separate, per-group check, because the matcher finds bracket boundaries
78
78
  * and validates no characters at all.
79
79
  *
@@ -194,7 +194,7 @@ function findVariantBlock(lines, variant) {
194
194
  if (selector === undefined || canonicalSelector(selector.attributes) !== want)
195
195
  continue;
196
196
  // An unqualified request still matches a qualified block: that is the
197
- // 0.44.2 behaviour `variantBlockQualifier` exists to report, since
197
+ // behaviour `variantBlockQualifier` exists to report, since
198
198
  // silently skipping the block is the worse failure. A request that names
199
199
  // a qualifier, though, means it, so it must match.
200
200
  if (wantQualifier !== '' && canonicalSelector(selector.qualifier) !== wantQualifier)
@@ -291,7 +291,7 @@ export const UNDEFINED_IDENTIFIER_CODES = new Set([
291
291
  ]);
292
292
  /**
293
293
  * Typed Lit, opt-in through `typecheck.litTypes`. Declares the two chrome
294
- * modules widgets import Lit from, so they no longer fall to the
294
+ * modules widgets import Lit from, so they do not fall to the
295
295
  * `chrome:*` wildcard as `any`: `LitElement` (with `ReactiveElement`'s
296
296
  * lifecycle) and `MozLitElement` carry closed member lists, so a misspelt
297
297
  * member of a widget host is TS2339, as `Cu` is in the built-in shim.
@@ -11,7 +11,7 @@
11
11
  */
12
12
  export declare function addDestroyAST(content: string, expression: string, marker?: string): string;
13
13
  /**
14
- * Legacy regex/line-based implementation preserved as fallback.
14
+ * Regex/line-based fallback implementation.
15
15
  */
16
16
  export declare function legacyAddDestroy(content: string, expression: string, marker?: string): string;
17
17
  /**
@@ -65,7 +65,7 @@ export function addDestroyAST(content, expression, marker = DEFAULT_MARKER) {
65
65
  return ms.toString();
66
66
  }
67
67
  /**
68
- * Legacy regex/line-based implementation preserved as fallback.
68
+ * Regex/line-based fallback implementation.
69
69
  */
70
70
  export function legacyAddDestroy(content, expression, marker = DEFAULT_MARKER) {
71
71
  const name = extractNameFromExpression(expression);
@@ -66,7 +66,7 @@ export async function probeDomFragmentInsertionPoint(engineDir, domFilePath, tar
66
66
  // dry-run is allowed to proceed too.
67
67
  return;
68
68
  }
69
- // Check the tokenised and legacy insertion paths symmetrically with
69
+ // Check the tokenised and line-based insertion paths symmetrically with
70
70
  // the real run. Either helper returning without throwing is sufficient
71
71
  // evidence that the real run can land the directive.
72
72
  addDomFragmentTokenized(content, includeDirective);
@@ -13,7 +13,7 @@
13
13
  */
14
14
  export declare function addInitAST(content: string, expression: string, after?: string, marker?: string): string;
15
15
  /**
16
- * Legacy regex/line-based implementation preserved as fallback.
16
+ * Regex/line-based fallback implementation.
17
17
  */
18
18
  export declare function legacyAddInit(content: string, expression: string, after?: string, marker?: string): string;
19
19
  /**
@@ -112,7 +112,7 @@ export function addInitAST(content, expression, after, marker = DEFAULT_MARKER)
112
112
  return ms.toString();
113
113
  }
114
114
  /**
115
- * Legacy regex/line-based implementation preserved as fallback.
115
+ * Regex/line-based fallback implementation.
116
116
  */
117
117
  export function legacyAddInit(content, expression, after, marker = DEFAULT_MARKER) {
118
118
  const name = extractNameFromExpression(expression);
@@ -10,7 +10,7 @@
10
10
  */
11
11
  export declare function addSubscriptAST(content: string, name: string, marker?: string): string;
12
12
  /**
13
- * Legacy regex/line-based implementation preserved as fallback.
13
+ * Regex/line-based fallback implementation.
14
14
  */
15
15
  export declare function legacyAddSubscript(content: string, name: string, marker?: string): string;
16
16
  /**
@@ -73,7 +73,7 @@ export function addSubscriptAST(content, name, marker = DEFAULT_MARKER) {
73
73
  return ms.toString();
74
74
  }
75
75
  /**
76
- * Legacy regex/line-based implementation preserved as fallback.
76
+ * Regex/line-based fallback implementation.
77
77
  */
78
78
  export function legacyAddSubscript(content, name, marker = DEFAULT_MARKER) {
79
79
  const lines = content.split('\n');
@@ -104,7 +104,7 @@ export declare function walkToTryBlockEnd(lines: string[], startLine: number, op
104
104
  }): number;
105
105
  /**
106
106
  * Scans the entire file and returns the net brace balance so callers can
107
- * assert that a legacy fallback mutation did not silently introduce or
107
+ * assert that a line-based fallback mutation did not silently introduce or
108
108
  * drop a `{` / `}`. The helper reuses {@link countBraceDepth} so strings,
109
109
  * comments, and regex literals are handled consistently with the walker.
110
110
  *
@@ -117,7 +117,7 @@ export declare function computeFileBraceBalance(content: string): {
117
117
  balanced: boolean;
118
118
  };
119
119
  /**
120
- * Round-trip guard used after a legacy fallback mutation: if the file's
120
+ * Round-trip guard used after a line-based fallback mutation: if the file's
121
121
  * net brace balance drifts between `before` and `after`, something went
122
122
  * wrong and the fallback is refusing to write a corrupted file. Expects
123
123
  * the delta to be exactly zero, since wire fallbacks only insert whole
@@ -159,7 +159,7 @@ export function tokenizeXhtml(lines) {
159
159
  });
160
160
  }
161
161
  // ---------------------------------------------------------------------------
162
- // Legacy (line-based) helpers, shared by fallback implementations in
162
+ // Line-based helpers, shared by fallback implementations in
163
163
  // the wire target modules. Extracted to reduce duplication and make the brace-
164
164
  // walking logic independently testable.
165
165
  // ---------------------------------------------------------------------------
@@ -224,7 +224,7 @@ export function walkToTryBlockEnd(lines, startLine, options) {
224
224
  }
225
225
  /**
226
226
  * Scans the entire file and returns the net brace balance so callers can
227
- * assert that a legacy fallback mutation did not silently introduce or
227
+ * assert that a line-based fallback mutation did not silently introduce or
228
228
  * drop a `{` / `}`. The helper reuses {@link countBraceDepth} so strings,
229
229
  * comments, and regex literals are handled consistently with the walker.
230
230
  *
@@ -244,7 +244,7 @@ export function computeFileBraceBalance(content) {
244
244
  return { depth, balanced: depth === 0 };
245
245
  }
246
246
  /**
247
- * Round-trip guard used after a legacy fallback mutation: if the file's
247
+ * Round-trip guard used after a line-based fallback mutation: if the file's
248
248
  * net brace balance drifts between `before` and `after`, something went
249
249
  * wrong and the fallback is refusing to write a corrupted file. Expects
250
250
  * the delta to be exactly zero, since wire fallbacks only insert whole
@@ -80,7 +80,7 @@ export declare class InternalInvariantError extends FireForgeError {
80
80
  get userMessage(): string;
81
81
  }
82
82
  /**
83
- * Raised when a legacy regex/brace-depth fallback parser decides it
83
+ * Raised when a regex/brace-depth fallback parser decides it
84
84
  * cannot safely perform its mutation, e.g. because a block it expected
85
85
  * to walk never closes, because the inserted result fails a round-trip
86
86
  * brace balance check, or because an expected pattern is missing.
@@ -106,7 +106,7 @@ export class InternalInvariantError extends FireForgeError {
106
106
  }
107
107
  }
108
108
  /**
109
- * Raised when a legacy regex/brace-depth fallback parser decides it
109
+ * Raised when a regex/brace-depth fallback parser decides it
110
110
  * cannot safely perform its mutation, e.g. because a block it expected
111
111
  * to walk never closes, because the inserted result fails a round-trip
112
112
  * brace balance check, or because an expected pattern is missing.
@@ -33,7 +33,7 @@ export declare class CorruptRebaseSessionError extends RebaseError {
33
33
  }
34
34
  /**
35
35
  * Thrown by `rebase --dry-run` when the replay rejects at least one patch.
36
- * A dry run that exited 0 regardless used to read as "the rebase is ready".
36
+ * A dry run that exited 0 regardless would read as "the rebase is ready".
37
37
  */
38
38
  export declare class RebaseDryRunRejectError extends RebaseError {
39
39
  readonly rejected: readonly string[];
@@ -51,7 +51,7 @@ export class CorruptRebaseSessionError extends RebaseError {
51
51
  }
52
52
  /**
53
53
  * Thrown by `rebase --dry-run` when the replay rejects at least one patch.
54
- * A dry run that exited 0 regardless used to read as "the rebase is ready".
54
+ * A dry run that exited 0 regardless would read as "the rebase is ready".
55
55
  */
56
56
  export class RebaseDryRunRejectError extends RebaseError {
57
57
  rejected;
@@ -516,6 +516,8 @@ export interface PatchSplitOptions extends Confirmable, PatchLockWaitable, Unsaf
516
516
  skipLint?: boolean;
517
517
  /** Where a refusal writes its one `--from`-ready declaration report. */
518
518
  declarationsOut?: string;
519
+ /** Write the committed old-to-new filename map of renumbered patches here as JSON. */
520
+ mapOut?: string;
519
521
  }
520
522
  /**
521
523
  * Options for the `fireforge patch move-files` subcommand. Without
@@ -541,6 +543,16 @@ export interface PatchMoveFilesOptions extends Confirmable, PatchLockWaitable, U
541
543
  skipLint?: boolean;
542
544
  /** Where a refusal writes its one `--from`-ready declaration report. */
543
545
  declarationsOut?: string;
546
+ /**
547
+ * New engine files no patch owns, adopted into the destination in the
548
+ * same transaction (engine-relative, repeatable).
549
+ */
550
+ scanFile?: string[];
551
+ /**
552
+ * Allow the move to empty the source: re-point the declarations it owns
553
+ * to the destination and retire its slot. Not valid with `create`.
554
+ */
555
+ retireEmpty?: boolean;
544
556
  }
545
557
  /**
546
558
  * Options for the rebase command.
@@ -673,8 +685,8 @@ export interface TestOptions extends EngineLockWaitable {
673
685
  * build would overwrite engine content recorded in neither a patch body
674
686
  * nor the pristine baseline. Same meaning as `fireforge build`'s flag of
675
687
  * the same name. A scripted `test --build` is exactly the shape the
676
- * multi-session hazard shows up in, and until now it was the one shape
677
- * that could not arm the refusal. Valid only with `--build`/`--build-only`.
688
+ * multi-session hazard shows up in, so it must be able to arm the
689
+ * refusal too. Valid only with `--build`/`--build-only`.
678
690
  */
679
691
  refuseUnexportedDrift?: boolean;
680
692
  /**
@@ -1073,7 +1085,7 @@ export interface PatchRenameOptions extends Confirmable, DryRunnable, PatchLockW
1073
1085
  /**
1074
1086
  * New category (validated against configured categories). Rewrites
1075
1087
  * the filename prefix and the manifest row in one transaction, so
1076
- * recategorising no longer requires `patch delete` + fresh export.
1088
+ * recategorising does not require `patch delete` + fresh export.
1077
1089
  */
1078
1090
  category?: string;
1079
1091
  /**
@@ -1096,6 +1108,17 @@ export interface PatchReorderOptions extends PatchLockWaitable {
1096
1108
  to?: number;
1097
1109
  before?: string;
1098
1110
  after?: string;
1111
+ /**
1112
+ * Shift a run of patches up by this many orders (`--shift <n> --from
1113
+ * <order> [--through <order>]`) instead of moving one patch.
1114
+ */
1115
+ shift?: number;
1116
+ /** First order of the run `shift` moves. */
1117
+ from?: number;
1118
+ /** Last order of the run `shift` moves (default: the end of the run). */
1119
+ through?: number;
1120
+ /** Write the committed old-to-new filename map here as JSON. */
1121
+ mapOut?: string;
1099
1122
  yes?: boolean;
1100
1123
  dryRun?: boolean;
1101
1124
  forceUnsafe?: boolean;
@@ -433,11 +433,10 @@ export interface PatchLintFileSizeTier {
433
433
  /**
434
434
  * Overrides for the `file-too-large` thresholds, per file class.
435
435
  *
436
- * These were module constants until 0.45.0, and nothing in `fireforge.json`
437
- * could move them. That is a problem specifically because the recommended
438
- * gate posture is `--max-warnings 0`: under it the `warning` band is not a
439
- * soft limit at all, so a project whose controllers legitimately sit a few
440
- * lines over 750 has a hard failure with no dial, at exactly the moment
436
+ * Configurable because the recommended gate posture is `--max-warnings 0`:
437
+ * under it the `warning` band is not a soft limit at all, so with fixed
438
+ * thresholds a project whose controllers legitimately sit a few lines over
439
+ * 750 would have a hard failure with no dial, at exactly the moment
441
440
  * someone is deciding whether to restructure a file.
442
441
  */
443
442
  export interface PatchLintFileSizeThresholds {
@@ -466,8 +465,7 @@ export interface PatchLintPatchSizeTier {
466
465
  /**
467
466
  * Overrides for the patch-size thresholds, per tier.
468
467
  *
469
- * Module constants until 0.50.0. The defaults are the old constants: lines
470
- * general 800/1500/3000, test 1500/3000/6000, branding 8000/18000/30000;
468
+ * Defaults: lines general 800/1500/3000, test 1500/3000/6000, branding 8000/18000/30000;
471
469
  * files warn above 5 (general, test) or 60 (branding). The files triple has
472
470
  * no notice or error band by default: an unset `files.notice` follows the
473
471
  * effective `files.warning`, and an unset `files.error` never fires.
@@ -71,8 +71,8 @@ export const WAIT_LOCK_ENV_VAR = 'FIREFORGE_WAIT_LOCK';
71
71
  /**
72
72
  * Bounds shared by the flag and the environment variable. The ceiling is a
73
73
  * day: on a shared engine a re-export or `test --build` queued behind a
74
- * peer's long series can need more than the hour the cap used to be, and a
75
- * longer wait then had to be a requeue after exit 15.
74
+ * peer's long series can need more than an hour, and a cap below that turns
75
+ * a longer wait into a requeue after exit 15.
76
76
  */
77
77
  const MIN_WAIT_LOCK_SECONDS = 1;
78
78
  export const MAX_WAIT_LOCK_SECONDS = 86_400;
@@ -173,7 +173,7 @@ async function spawnTracked(spec) {
173
173
  let rejection;
174
174
  child.on('error', (error) => {
175
175
  // Abort/startup failure: make sure a partially-started tree does not
176
- // outlive the dispatch (a mach dying at startup used to strand
176
+ // outlive the dispatch (a mach dying at startup would otherwise strand
177
177
  // multiprocessing workers).
178
178
  if (options.processGroup === true) {
179
179
  killProcessTree(child, 'SIGKILL', usesProcessGroup);
@@ -229,7 +229,7 @@ export async function execStream(command, args, options = {}) {
229
229
  args,
230
230
  options,
231
231
  graceMs: 1500,
232
- // A detached group leader no longer receives the terminal's Ctrl+C, so
232
+ // A detached group leader does not receive the terminal's Ctrl+C, so
233
233
  // the forwarder is mandatory (not just graceful-UX) when processGroup
234
234
  // is set. A plain streamed child does not get one.
235
235
  forwardWithoutProcessGroup: false,
@@ -18,7 +18,7 @@ export declare function hasRawCssColors(content: string): boolean;
18
18
  * the line is blanked, corrupting whatever lint pass consumes the stripped
19
19
  * text. The source is tokenized as a module, then as a script. Firefox
20
20
  * chrome sources can contain constructs acorn tokenizes under neither goal.
21
- * Those fall back to the legacy regex strip, which handles strings but not
21
+ * Those fall back to the regex-only strip, which handles strings but not
22
22
  * regex literals.
23
23
  */
24
24
  export declare function stripJsComments(source: string): string;
@@ -30,7 +30,7 @@ export declare function stripJsComments(source: string): string;
30
30
  * legacy octal escape is a tokenizer error only under the module goal.
31
31
  * Callers that must not under-count when tokenizing fails (the code-line
32
32
  * size metric) read the `null` and pick their own fallback rather than the
33
- * legacy regex strip, which can blank code.
33
+ * regex-only strip, which can blank code.
34
34
  */
35
35
  export declare function stripJsCommentsParsed(source: string): string | null;
36
36
  /**
@@ -34,7 +34,7 @@ export function hasRawCssColors(content) {
34
34
  * the line is blanked, corrupting whatever lint pass consumes the stripped
35
35
  * text. The source is tokenized as a module, then as a script. Firefox
36
36
  * chrome sources can contain constructs acorn tokenizes under neither goal.
37
- * Those fall back to the legacy regex strip, which handles strings but not
37
+ * Those fall back to the regex-only strip, which handles strings but not
38
38
  * regex literals.
39
39
  */
40
40
  export function stripJsComments(source) {
@@ -48,7 +48,7 @@ export function stripJsComments(source) {
48
48
  * legacy octal escape is a tokenizer error only under the module goal.
49
49
  * Callers that must not under-count when tokenizing fails (the code-line
50
50
  * size metric) read the `null` and pick their own fallback rather than the
51
- * legacy regex strip, which can blank code.
51
+ * regex-only strip, which can blank code.
52
52
  */
53
53
  export function stripJsCommentsParsed(source) {
54
54
  const comments = collectJsComments(source, 'module') ?? collectJsComments(source, 'script');
@@ -91,7 +91,7 @@ function collectJsComments(source, sourceType) {
91
91
  return comments;
92
92
  }
93
93
  /**
94
- * Legacy pure-regex comment strip, used only when acorn cannot tokenize
94
+ * Pure-regex comment strip, used only when acorn cannot tokenize
95
95
  * the source (preprocessor directives, syntax errors). Strings survive.
96
96
  * Regex literals are not modeled, so `//` inside a regex blanks the rest
97
97
  * of the line.
@@ -1,12 +1,11 @@
1
1
  /**
2
2
  * The single awaitable delay used across FireForge.
3
3
  *
4
- * Five modules had grown a private copy of this (`sleep`, `delay`,
5
- * `sweepDelay`) and four more sites inlined
6
- * `new Promise((resolve) => setTimeout(resolve, ms))`. The copies disagreed on
7
- * the one property that actually matters (whether the pending timer holds
8
- * the event loop open) and the disagreement was invisible at each call site.
9
- * Both behaviours now live here, and callers state which one they need.
4
+ * Callers use this rather than a private `sleep`/`delay` or an inline
5
+ * `new Promise((resolve) => setTimeout(resolve, ms))`, because such copies
6
+ * disagree on the one property that actually matters (whether the pending
7
+ * timer holds the event loop open) and the disagreement is invisible at each
8
+ * call site. Both behaviours live here, and callers state which one they need.
10
9
  *
11
10
  * The default is a ref'd timer, because the dangerous mistake is the other
12
11
  * one: an unref'd delay awaited between a SIGTERM and its escalation lets Node