@hominis/fireforge 0.49.2 → 0.50.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 (334) hide show
  1. package/CHANGELOG.md +40 -1
  2. package/README.md +1 -1
  3. package/dist/bin/fireforge.js +6 -0
  4. package/dist/build-info.json +4 -4
  5. package/dist/src/commands/build-prelock.d.ts +20 -0
  6. package/dist/src/commands/build-prelock.js +50 -0
  7. package/dist/src/commands/build.d.ts +3 -1
  8. package/dist/src/commands/build.js +13 -6
  9. package/dist/src/commands/check-report.d.ts +28 -0
  10. package/dist/src/commands/check-report.js +78 -0
  11. package/dist/src/commands/check-run.d.ts +28 -0
  12. package/dist/src/commands/check-run.js +104 -0
  13. package/dist/src/commands/check-steps.d.ts +67 -0
  14. package/dist/src/commands/check-steps.js +175 -0
  15. package/dist/src/commands/check.d.ts +32 -0
  16. package/dist/src/commands/check.js +160 -0
  17. package/dist/src/commands/config.js +5 -6
  18. package/dist/src/commands/doctor-engine-eslint.js +6 -1
  19. package/dist/src/commands/doctor-source-pin.js +4 -1
  20. package/dist/src/commands/export-adopt-check.d.ts +38 -0
  21. package/dist/src/commands/export-adopt-check.js +98 -0
  22. package/dist/src/commands/export-flow.js +29 -14
  23. package/dist/src/commands/export-placement-gate.d.ts +6 -2
  24. package/dist/src/commands/export-shared.d.ts +2 -1
  25. package/dist/src/commands/export-staged-registration.d.ts +46 -0
  26. package/dist/src/commands/export-staged-registration.js +91 -0
  27. package/dist/src/commands/forward-import-remedy.d.ts +32 -0
  28. package/dist/src/commands/forward-import-remedy.js +63 -0
  29. package/dist/src/commands/furnace/apply.js +3 -3
  30. package/dist/src/commands/furnace/chrome-doc-remove.d.ts +2 -1
  31. package/dist/src/commands/furnace/chrome-doc-remove.js +2 -2
  32. package/dist/src/commands/furnace/chrome-doc.d.ts +2 -1
  33. package/dist/src/commands/furnace/chrome-doc.js +2 -2
  34. package/dist/src/commands/furnace/create.js +2 -2
  35. package/dist/src/commands/furnace/deploy-named.d.ts +48 -0
  36. package/dist/src/commands/furnace/deploy-named.js +107 -0
  37. package/dist/src/commands/furnace/deploy.d.ts +3 -2
  38. package/dist/src/commands/furnace/deploy.js +50 -86
  39. package/dist/src/commands/furnace/diff.js +26 -3
  40. package/dist/src/commands/furnace/index.js +66 -41
  41. package/dist/src/commands/furnace/init.js +13 -6
  42. package/dist/src/commands/furnace/override.js +4 -2
  43. package/dist/src/commands/furnace/preview.js +4 -3
  44. package/dist/src/commands/furnace/refresh.js +2 -2
  45. package/dist/src/commands/furnace/remove.js +4 -3
  46. package/dist/src/commands/furnace/rename-dry-run.d.ts +29 -0
  47. package/dist/src/commands/furnace/rename-dry-run.js +127 -0
  48. package/dist/src/commands/furnace/rename-helpers.d.ts +7 -0
  49. package/dist/src/commands/furnace/rename-helpers.js +15 -1
  50. package/dist/src/commands/furnace/rename-registrations.d.ts +41 -0
  51. package/dist/src/commands/furnace/rename-registrations.js +93 -0
  52. package/dist/src/commands/furnace/rename.d.ts +2 -1
  53. package/dist/src/commands/furnace/rename.js +28 -66
  54. package/dist/src/commands/furnace/scan.d.ts +9 -4
  55. package/dist/src/commands/furnace/scan.js +11 -9
  56. package/dist/src/commands/furnace/sync.js +3 -2
  57. package/dist/src/commands/furnace/validate-report.d.ts +45 -0
  58. package/dist/src/commands/furnace/validate-report.js +91 -0
  59. package/dist/src/commands/furnace/validate.d.ts +1 -1
  60. package/dist/src/commands/furnace/validate.js +127 -114
  61. package/dist/src/commands/furnace/validation-output.js +14 -3
  62. package/dist/src/commands/lint-per-patch.js +31 -60
  63. package/dist/src/commands/lint-report.d.ts +8 -4
  64. package/dist/src/commands/lint-report.js +15 -5
  65. package/dist/src/commands/lint.js +15 -34
  66. package/dist/src/commands/manifest.js +2 -0
  67. package/dist/src/commands/patch/delete-gap-notice.js +3 -1
  68. package/dist/src/commands/patch/delete.d.ts +1 -1
  69. package/dist/src/commands/patch/delete.js +13 -98
  70. package/dist/src/commands/patch/index.d.ts +1 -1
  71. package/dist/src/commands/patch/index.js +4 -2
  72. package/dist/src/commands/patch/lint-ignore.d.ts +5 -1
  73. package/dist/src/commands/patch/lint-ignore.js +65 -28
  74. package/dist/src/commands/patch/move-files-create.js +26 -17
  75. package/dist/src/commands/patch/move-files-into.js +5 -3
  76. package/dist/src/commands/patch/move-files.js +3 -1
  77. package/dist/src/commands/patch/removal-dependents.d.ts +63 -0
  78. package/dist/src/commands/patch/removal-dependents.js +133 -0
  79. package/dist/src/commands/patch/rename.js +7 -0
  80. package/dist/src/commands/patch/retire.d.ts +34 -0
  81. package/dist/src/commands/patch/retire.js +128 -0
  82. package/dist/src/commands/patch/split-plan.d.ts +7 -7
  83. package/dist/src/commands/patch/split-plan.js +45 -28
  84. package/dist/src/commands/patch/split-staged.d.ts +108 -0
  85. package/dist/src/commands/patch/split-staged.js +202 -0
  86. package/dist/src/commands/patch/split.d.ts +5 -0
  87. package/dist/src/commands/patch/split.js +51 -15
  88. package/dist/src/commands/patch/staged-dependency-batch.d.ts +48 -0
  89. package/dist/src/commands/patch/staged-dependency-batch.js +176 -0
  90. package/dist/src/commands/patch/staged-dependency-entries.d.ts +36 -0
  91. package/dist/src/commands/patch/staged-dependency-entries.js +52 -0
  92. package/dist/src/commands/patch/staged-dependency-from.d.ts +14 -0
  93. package/dist/src/commands/patch/staged-dependency-from.js +108 -0
  94. package/dist/src/commands/patch/staged-dependency.js +14 -41
  95. package/dist/src/commands/re-export-adjacent-registrations.d.ts +20 -0
  96. package/dist/src/commands/re-export-adjacent-registrations.js +75 -0
  97. package/dist/src/commands/re-export-adjacent.d.ts +45 -2
  98. package/dist/src/commands/re-export-adjacent.js +156 -37
  99. package/dist/src/commands/re-export-drift.d.ts +36 -8
  100. package/dist/src/commands/re-export-drift.js +73 -49
  101. package/dist/src/commands/re-export-files.js +11 -8
  102. package/dist/src/commands/re-export-lint.d.ts +2 -1
  103. package/dist/src/commands/re-export-lint.js +23 -10
  104. package/dist/src/commands/re-export-options.js +45 -4
  105. package/dist/src/commands/re-export-preflight.d.ts +23 -0
  106. package/dist/src/commands/re-export-preflight.js +160 -0
  107. package/dist/src/commands/re-export-register.js +42 -17
  108. package/dist/src/commands/re-export-removed.d.ts +12 -1
  109. package/dist/src/commands/re-export-removed.js +41 -11
  110. package/dist/src/commands/re-export-scan-owner.d.ts +44 -0
  111. package/dist/src/commands/re-export-scan-owner.js +116 -0
  112. package/dist/src/commands/re-export-scan.d.ts +17 -9
  113. package/dist/src/commands/re-export-scan.js +56 -23
  114. package/dist/src/commands/re-export-single.js +22 -6
  115. package/dist/src/commands/re-export-summary.d.ts +28 -0
  116. package/dist/src/commands/re-export-summary.js +36 -0
  117. package/dist/src/commands/re-export.js +37 -24
  118. package/dist/src/commands/rebase/index.js +17 -17
  119. package/dist/src/commands/run.js +16 -2
  120. package/dist/src/commands/status-check.d.ts +55 -16
  121. package/dist/src/commands/status-check.js +92 -37
  122. package/dist/src/commands/status-json.d.ts +22 -9
  123. package/dist/src/commands/status-json.js +47 -32
  124. package/dist/src/commands/status-output.js +3 -1
  125. package/dist/src/commands/status-scope.d.ts +37 -0
  126. package/dist/src/commands/status-scope.js +54 -0
  127. package/dist/src/commands/status.js +66 -23
  128. package/dist/src/commands/test-browser-preflight.js +19 -3
  129. package/dist/src/commands/test-build-furnace.d.ts +21 -0
  130. package/dist/src/commands/test-build-furnace.js +48 -0
  131. package/dist/src/commands/test-build-phase.js +47 -41
  132. package/dist/src/commands/test-checkjs.js +7 -0
  133. package/dist/src/commands/test-diagnose.js +7 -0
  134. package/dist/src/commands/test-modes.d.ts +16 -1
  135. package/dist/src/commands/test-modes.js +24 -1
  136. package/dist/src/commands/test-multi-verdict.d.ts +61 -0
  137. package/dist/src/commands/test-multi-verdict.js +111 -0
  138. package/dist/src/commands/test-orchestrate.d.ts +43 -0
  139. package/dist/src/commands/test-orchestrate.js +222 -0
  140. package/dist/src/commands/test-perf-scope.d.ts +29 -0
  141. package/dist/src/commands/test-perf-scope.js +83 -0
  142. package/dist/src/commands/test-prelock.d.ts +51 -0
  143. package/dist/src/commands/test-prelock.js +177 -0
  144. package/dist/src/commands/test-register.js +54 -10
  145. package/dist/src/commands/test-run.js +4 -1
  146. package/dist/src/commands/test-stale-gate.js +57 -27
  147. package/dist/src/commands/test-verdict.d.ts +10 -1
  148. package/dist/src/commands/test-verdict.js +28 -1
  149. package/dist/src/commands/test.d.ts +6 -1
  150. package/dist/src/commands/test.js +66 -60
  151. package/dist/src/commands/token-lint.d.ts +14 -0
  152. package/dist/src/commands/token-lint.js +111 -0
  153. package/dist/src/commands/token.js +10 -0
  154. package/dist/src/commands/verify-scope.d.ts +28 -0
  155. package/dist/src/commands/verify-scope.js +67 -0
  156. package/dist/src/commands/verify.d.ts +10 -2
  157. package/dist/src/commands/verify.js +59 -35
  158. package/dist/src/core/build-baseline-types.d.ts +46 -0
  159. package/dist/src/core/build-baseline-validate.js +19 -0
  160. package/dist/src/core/build-baseline.d.ts +5 -1
  161. package/dist/src/core/build-baseline.js +3 -1
  162. package/dist/src/core/build-overwrite-guard.d.ts +50 -0
  163. package/dist/src/core/build-overwrite-guard.js +84 -4
  164. package/dist/src/core/build-prepare.d.ts +7 -0
  165. package/dist/src/core/build-prepare.js +14 -8
  166. package/dist/src/core/build-run-record.d.ts +74 -0
  167. package/dist/src/core/build-run-record.js +136 -0
  168. package/dist/src/core/checkjs-support-heads.d.ts +41 -0
  169. package/dist/src/core/checkjs-support-heads.js +104 -0
  170. package/dist/src/core/config-paths.d.ts +2 -2
  171. package/dist/src/core/config-paths.js +8 -0
  172. package/dist/src/core/config-validate-check.d.ts +8 -0
  173. package/dist/src/core/config-validate-check.js +143 -0
  174. package/dist/src/core/config-validate-file-size.d.ts +9 -6
  175. package/dist/src/core/config-validate-file-size.js +91 -2
  176. package/dist/src/core/config-validate-history.d.ts +11 -0
  177. package/dist/src/core/config-validate-history.js +38 -0
  178. package/dist/src/core/config-validate-patch-policy.js +100 -0
  179. package/dist/src/core/config-validate-test-toolchains.js +7 -0
  180. package/dist/src/core/config-validate-token-scales.d.ts +6 -0
  181. package/dist/src/core/config-validate-token-scales.js +50 -0
  182. package/dist/src/core/config-validate.js +14 -5
  183. package/dist/src/core/config.d.ts +5 -0
  184. package/dist/src/core/config.js +16 -0
  185. package/dist/src/core/coverage-dispatch.d.ts +24 -0
  186. package/dist/src/core/coverage-dispatch.js +44 -0
  187. package/dist/src/core/coverage-extend.d.ts +70 -45
  188. package/dist/src/core/coverage-extend.js +199 -144
  189. package/dist/src/core/coverage-records.d.ts +97 -0
  190. package/dist/src/core/coverage-records.js +350 -0
  191. package/dist/src/core/destructive.d.ts +7 -4
  192. package/dist/src/core/destructive.js +10 -10
  193. package/dist/src/core/engine-session-lock.d.ts +10 -1
  194. package/dist/src/core/engine-session-lock.js +28 -8
  195. package/dist/src/core/export-moved-code-shape.d.ts +49 -11
  196. package/dist/src/core/export-moved-code-shape.js +135 -44
  197. package/dist/src/core/file-lock-queue.d.ts +67 -0
  198. package/dist/src/core/file-lock-queue.js +221 -0
  199. package/dist/src/core/file-lock.d.ts +4 -25
  200. package/dist/src/core/file-lock.js +105 -138
  201. package/dist/src/core/furnace-apply-output.d.ts +10 -0
  202. package/dist/src/core/furnace-apply-output.js +31 -1
  203. package/dist/src/core/furnace-apply.js +12 -2
  204. package/dist/src/core/furnace-composed-source.d.ts +76 -0
  205. package/dist/src/core/furnace-composed-source.js +167 -0
  206. package/dist/src/core/furnace-config-validate-exceptions.d.ts +9 -0
  207. package/dist/src/core/furnace-config-validate-exceptions.js +71 -0
  208. package/dist/src/core/furnace-config-validate.js +8 -0
  209. package/dist/src/core/furnace-css-fragments.d.ts +10 -0
  210. package/dist/src/core/furnace-css-fragments.js +26 -0
  211. package/dist/src/core/furnace-operation.d.ts +3 -3
  212. package/dist/src/core/furnace-registration-remove.d.ts +22 -1
  213. package/dist/src/core/furnace-registration-remove.js +53 -17
  214. package/dist/src/core/furnace-registration-rename.d.ts +26 -0
  215. package/dist/src/core/furnace-registration-rename.js +77 -0
  216. package/dist/src/core/furnace-registration.d.ts +14 -4
  217. package/dist/src/core/furnace-registration.js +44 -10
  218. package/dist/src/core/furnace-staleness.d.ts +7 -0
  219. package/dist/src/core/furnace-staleness.js +34 -24
  220. package/dist/src/core/furnace-validate-accessibility.d.ts +4 -2
  221. package/dist/src/core/furnace-validate-accessibility.js +10 -1
  222. package/dist/src/core/furnace-validate-exceptions.d.ts +40 -0
  223. package/dist/src/core/furnace-validate-exceptions.js +43 -0
  224. package/dist/src/core/furnace-validate-icon-button.d.ts +7 -0
  225. package/dist/src/core/furnace-validate-icon-button.js +49 -0
  226. package/dist/src/core/furnace-validate.js +1 -1
  227. package/dist/src/core/harness-port-fallback.d.ts +60 -0
  228. package/dist/src/core/harness-port-fallback.js +96 -0
  229. package/dist/src/core/history-log-file.d.ts +61 -0
  230. package/dist/src/core/history-log-file.js +157 -0
  231. package/dist/src/core/history-log.d.ts +2 -1
  232. package/dist/src/core/history-log.js +2 -1
  233. package/dist/src/core/json-preserving-edit.d.ts +22 -0
  234. package/dist/src/core/json-preserving-edit.js +199 -0
  235. package/dist/src/core/mach.js +8 -1
  236. package/dist/src/core/negative-control-diff.d.ts +39 -0
  237. package/dist/src/core/negative-control-diff.js +136 -0
  238. package/dist/src/core/negative-control.d.ts +138 -0
  239. package/dist/src/core/negative-control.js +356 -0
  240. package/dist/src/core/objdir-install-manifests.d.ts +73 -0
  241. package/dist/src/core/objdir-install-manifests.js +226 -0
  242. package/dist/src/core/patch-export-update.d.ts +2 -0
  243. package/dist/src/core/patch-export-update.js +1 -0
  244. package/dist/src/core/patch-export.js +3 -4
  245. package/dist/src/core/patch-lint-checkjs-any-import.d.ts +37 -0
  246. package/dist/src/core/patch-lint-checkjs-any-import.js +61 -0
  247. package/dist/src/core/patch-lint-checkjs.js +8 -11
  248. package/dist/src/core/patch-lint-code-lines.d.ts +59 -0
  249. package/dist/src/core/patch-lint-code-lines.js +105 -0
  250. package/dist/src/core/patch-lint-cross.js +12 -4
  251. package/dist/src/core/patch-lint-css.d.ts +8 -2
  252. package/dist/src/core/patch-lint-css.js +10 -4
  253. package/dist/src/core/patch-lint-file-size.d.ts +12 -5
  254. package/dist/src/core/patch-lint-file-size.js +22 -11
  255. package/dist/src/core/patch-lint-forward-registration-extended.d.ts +18 -0
  256. package/dist/src/core/patch-lint-forward-registration-extended.js +35 -10
  257. package/dist/src/core/patch-lint-forward-registration.js +10 -0
  258. package/dist/src/core/patch-lint-ignore.d.ts +152 -0
  259. package/dist/src/core/patch-lint-ignore.js +299 -0
  260. package/dist/src/core/patch-lint-size-thresholds.d.ts +52 -0
  261. package/dist/src/core/patch-lint-size-thresholds.js +110 -0
  262. package/dist/src/core/patch-lint-size.d.ts +117 -0
  263. package/dist/src/core/patch-lint-size.js +323 -0
  264. package/dist/src/core/patch-lint-staged-registration.d.ts +18 -1
  265. package/dist/src/core/patch-lint-staged-registration.js +20 -0
  266. package/dist/src/core/patch-lint-token-scale.d.ts +39 -0
  267. package/dist/src/core/patch-lint-token-scale.js +109 -0
  268. package/dist/src/core/patch-lint.d.ts +11 -83
  269. package/dist/src/core/patch-lint.js +64 -286
  270. package/dist/src/core/patch-lock.js +7 -6
  271. package/dist/src/core/patch-manifest-consistency.js +7 -6
  272. package/dist/src/core/patch-manifest-io.d.ts +6 -3
  273. package/dist/src/core/patch-manifest-io.js +19 -8
  274. package/dist/src/core/patch-manifest-validate.js +34 -2
  275. package/dist/src/core/patch-order-legal.d.ts +25 -0
  276. package/dist/src/core/patch-order-legal.js +83 -0
  277. package/dist/src/core/patch-policy-content.d.ts +40 -0
  278. package/dist/src/core/patch-policy-content.js +216 -0
  279. package/dist/src/core/patch-policy.d.ts +16 -3
  280. package/dist/src/core/patch-policy.js +16 -9
  281. package/dist/src/core/patch-retired.d.ts +35 -0
  282. package/dist/src/core/patch-retired.js +54 -0
  283. package/dist/src/core/patch-subset.d.ts +16 -0
  284. package/dist/src/core/patch-subset.js +66 -0
  285. package/dist/src/core/preflight-wait.d.ts +20 -0
  286. package/dist/src/core/preflight-wait.js +38 -0
  287. package/dist/src/core/rebase-from-version.d.ts +46 -0
  288. package/dist/src/core/rebase-from-version.js +71 -0
  289. package/dist/src/core/run-log.d.ts +20 -0
  290. package/dist/src/core/run-log.js +89 -3
  291. package/dist/src/core/run-profile.d.ts +7 -0
  292. package/dist/src/core/run-profile.js +7 -1
  293. package/dist/src/core/smoke-patterns.d.ts +5 -0
  294. package/dist/src/core/smoke-patterns.js +13 -0
  295. package/dist/src/core/test-harness-crash.d.ts +18 -1
  296. package/dist/src/core/test-harness-crash.js +6 -4
  297. package/dist/src/core/test-harness-signature.d.ts +17 -0
  298. package/dist/src/core/test-harness-signature.js +4 -0
  299. package/dist/src/core/test-run-completeness.d.ts +57 -0
  300. package/dist/src/core/test-run-completeness.js +144 -0
  301. package/dist/src/core/test-stale-check.d.ts +27 -1
  302. package/dist/src/core/test-stale-check.js +39 -49
  303. package/dist/src/core/test-stale-exempt.d.ts +40 -0
  304. package/dist/src/core/test-stale-exempt.js +70 -0
  305. package/dist/src/core/token-lint.d.ts +50 -0
  306. package/dist/src/core/token-lint.js +126 -0
  307. package/dist/src/core/tree-guard.js +3 -0
  308. package/dist/src/core/typecheck-shim.d.ts +1 -1
  309. package/dist/src/core/typecheck-shim.js +90 -2
  310. package/dist/src/index.d.ts +8 -3
  311. package/dist/src/index.js +3 -1
  312. package/dist/src/types/commands/index.d.ts +2 -2
  313. package/dist/src/types/commands/options.d.ts +148 -21
  314. package/dist/src/types/commands/patches.d.ts +75 -1
  315. package/dist/src/types/config.d.ts +192 -0
  316. package/dist/src/types/config.js +9 -0
  317. package/dist/src/types/furnace.d.ts +43 -1
  318. package/dist/src/types/furnace.js +7 -0
  319. package/dist/src/types/index.d.ts +1 -1
  320. package/dist/src/utils/options.d.ts +34 -6
  321. package/dist/src/utils/options.js +86 -5
  322. package/dist/src/utils/path-glob.d.ts +15 -0
  323. package/dist/src/utils/path-glob.js +47 -0
  324. package/dist/src/utils/regex.d.ts +15 -3
  325. package/dist/src/utils/regex.js +37 -16
  326. package/docs/configuration.md +282 -9
  327. package/docs/exit-codes.md +18 -1
  328. package/docs/furnace.md +97 -3
  329. package/docs/lifecycle-invariants.md +52 -19
  330. package/docs/machine-output.md +74 -12
  331. package/docs/patch-workflow.md +297 -22
  332. package/docs/run-logs.md +20 -1
  333. package/docs/testing.md +276 -20
  334. package/package.json +1 -1
@@ -3,16 +3,16 @@ import { join } from 'node:path';
3
3
  import { getProjectPaths, loadConfig } from '../core/config.js';
4
4
  import { assertEngineExists } from '../core/engine-precondition.js';
5
5
  import { assertEngineGenerationUnchanged, snapshotEngineGeneration, } from '../core/engine-session-lock.js';
6
- import { hasBuildArtifacts, hasRunnableBundle } from '../core/mach.js';
6
+ import { hasBuildArtifacts } from '../core/mach.js';
7
7
  import { assertBuildArtifacts } from '../core/mach-build-artifacts.js';
8
8
  import { ensureLaunchableBrowserNotRunning } from '../core/marionette-port.js';
9
9
  import { waitForPreflight } from '../core/preflight-wait.js';
10
- import { closeActiveRunLog, openRunLog, setActiveRunLog, writeToActiveRunLog, } from '../core/run-log.js';
10
+ import { closeActiveRunLog, keepRunLogCopy, openRunLog, setActiveRunLog, writeToActiveRunLog, } from '../core/run-log.js';
11
11
  import { createPostRebuildFailureContext } from '../core/test-harness-output.js';
12
12
  import { analyzeTestPathScopes, formatScopeNotice, } from '../core/test-path-scope.js';
13
13
  import { cleanupProfileFiles, stageProfileFiles, } from '../core/test-profile-files.js';
14
14
  import { assertObjdirMatchesTreeMarker } from '../core/tree-store.js';
15
- import { FireForgeError, GeneralError, PreflightRefusalError } from '../errors/base.js';
15
+ import { FireForgeError, GeneralError, LockContentionError, PreflightRefusalError, } from '../errors/base.js';
16
16
  import { BuildError } from '../errors/build.js';
17
17
  import { toError } from '../utils/errors.js';
18
18
  import { pathExists } from '../utils/fs.js';
@@ -24,7 +24,10 @@ import { runTestBuildPhase } from './test-build-phase.js';
24
24
  import { diagnoseShardOutcome, finalizeSingleRunOutcome } from './test-diagnose.js';
25
25
  import { buildPerfSampleEnv, mergeHarnessEnv, resolveShuffleSeed, shuffleTestGroups, } from './test-harness-env.js';
26
26
  import { removePgidFile, setActivePgidFile } from './test-harness-teardown.js';
27
- import { assertPathlessTestMode, assertTestModeCombinations, canaryTimeoutSeconds, classifyBeforeDispatch, reportCanaryOutcome, resolveCanaryPath, } from './test-modes.js';
27
+ import { assertPathlessTestMode, assertTestModeCombinations, canaryTimeoutSeconds, classifyBeforeDispatch, reportCanaryOutcome, resolveCanaryPath, restartBetweenTestsArgs, } from './test-modes.js';
28
+ import { orchestrateTestRuns } from './test-orchestrate.js';
29
+ import { perfGenerationTolerance } from './test-perf-scope.js';
30
+ import { resolveLaunchablePathForTests, runTestPreLockPreflight, TEST_BUILD_PREFLIGHT, } from './test-prelock.js';
28
31
  import { DEFAULT_HARNESS_RETRIES, finalizeShardedOutcome, runShardedTests, runTestsWithRetries, } from './test-run.js';
29
32
  import { emitFailVerdict, resetVerdictEmission, verdictEmitted } from './test-verdict.js';
30
33
  async function assertTestPathsExist(engineDir, testPaths) {
@@ -77,20 +80,6 @@ function filterRedundantXpcshellFlavorArgs(machArgs, classification) {
77
80
  }
78
81
  return filtered;
79
82
  }
80
- async function resolveLaunchablePathForTests(engineDir, binaryName, objDir) {
81
- if (!objDir)
82
- return undefined;
83
- const bundleCheck = await hasRunnableBundle(engineDir, binaryName, objDir);
84
- if (!bundleCheck.runnable) {
85
- const expectedSuffix = bundleCheck.expectedPath
86
- ? ` (expected at engine/${bundleCheck.expectedPath})`
87
- : '';
88
- throw new GeneralError(`Tests require a complete launchable build${expectedSuffix}. ` +
89
- 'The obj-*/dist/ tree exists but the launchable binary is missing — typically the result of an interrupted or partially failed `fireforge build`.\n\n' +
90
- 'Run "fireforge build" again and let it finish before retrying "fireforge test".');
91
- }
92
- return bundleCheck.expectedPath;
93
- }
94
83
  function logTestSelection(scopes) {
95
84
  if (scopes.length > 0) {
96
85
  const labels = scopes.map((scope) => scope.isDirectory && scope.testFileCount > 0
@@ -103,13 +92,6 @@ function logTestSelection(scopes) {
103
92
  }
104
93
  info('');
105
94
  }
106
- /** Build-artifact preflight wording for `fireforge test`. */
107
- const TEST_BUILD_PREFLIGHT = {
108
- label: 'Tests',
109
- requirement: 'Tests require a completed build.',
110
- remediation: "Run 'fireforge build' first, then run 'fireforge test'.",
111
- requireExisting: true,
112
- };
113
95
  /**
114
96
  * Runs the test command to execute mach tests.
115
97
  *
@@ -121,17 +103,22 @@ const TEST_BUILD_PREFLIGHT = {
121
103
  * since no harness classification exists for such a run. Writers closer to
122
104
  * the harness (doctor, canary, single, sharded, the engine-generation
123
105
  * guard) emit first and win.
106
+ * The lock-free checks (launchable binary, named-file checkJs, the
107
+ * `--wait-browser`/`--wait-port` budgets) run first, outside `underLock`,
108
+ * so a run that would be refused or would only wait never holds a place in
109
+ * the engine lock queue. See `test-prelock.ts`.
124
110
  * @param projectRoot - Root directory of the project
125
111
  * @param testPaths - Test file or directory paths
126
112
  * @param options - Test options
113
+ * @param underLock - Runs the rest of the command under the engine session lock
127
114
  */
128
- export async function testCommand(projectRoot, testPaths, options = {}) {
115
+ export async function testCommand(projectRoot, testPaths, options = {}, underLock = (body) => body()) {
129
116
  intro('FireForge Test');
130
117
  resetVerdictEmission();
131
118
  // Opened before any work so a preflight refusal is logged too. Those are
132
119
  // exactly the runs whose only output a `tail` throws away. Closed in
133
120
  // `finally`, after the verdict line has already read the path.
134
- setActiveRunLog(await openRunLog(projectRoot, 'test'));
121
+ setActiveRunLog(await keepRunLogCopy(await openRunLog(projectRoot, 'test'), options.keepLog, 'test'));
135
122
  setActivePgidFile(options.pgidFile);
136
123
  let staged;
137
124
  try {
@@ -139,12 +126,22 @@ export async function testCommand(projectRoot, testPaths, options = {}) {
139
126
  staged = await stageProfileFiles(projectRoot, options.profileFile);
140
127
  options = { ...options, machArg: [...(options.machArg ?? []), ...staged.args] };
141
128
  }
142
- await runTestCommandBody(projectRoot, testPaths, options, staged?.env);
129
+ const lockedOptions = await runTestPreLockPreflight(projectRoot, testPaths, options);
130
+ const profileEnv = staged?.env;
131
+ await underLock(() => orchestrateTestRuns(projectRoot, testPaths, lockedOptions, (paths, runOptions, hooks) => runTestCommandBody(projectRoot, paths, runOptions, profileEnv, hooks), (error) => {
132
+ renderPreflightRefusal(error);
133
+ emitFailVerdict('preflight', refusalNote(error));
134
+ }));
143
135
  }
144
136
  catch (error) {
145
137
  if (!verdictEmitted()) {
146
- renderPreflightRefusal(error);
147
- emitFailVerdict('preflight', refusalNote(error));
138
+ if (error instanceof LockContentionError) {
139
+ emitFailVerdict('lock-timeout');
140
+ }
141
+ else {
142
+ renderPreflightRefusal(error);
143
+ emitFailVerdict('preflight', refusalNote(error));
144
+ }
148
145
  }
149
146
  throw error;
150
147
  }
@@ -208,9 +205,11 @@ function renderPreflightRefusal(error) {
208
205
  * misdescribe a run whose harness already executed). It is emitted here so the
209
206
  * guard's throw can never leave a stale or missing verdict line behind.
210
207
  */
211
- async function verifyEngineGenerationOrEmitInconclusive(engineDir, before, powerChanged = false) {
208
+ async function verifyEngineGenerationOrEmitInconclusive(engineDir, before, powerChanged = false, perfSamples) {
212
209
  try {
213
- await assertEngineGenerationUnchanged(engineDir, before);
210
+ await (perfSamples === undefined
211
+ ? assertEngineGenerationUnchanged(engineDir, before)
212
+ : assertEngineGenerationUnchanged(engineDir, before, perfGenerationTolerance(engineDir)));
214
213
  if (powerChanged)
215
214
  throw new GeneralError('Perf power source changed during the run; repeat this sitting on a stable power source.');
216
215
  }
@@ -255,9 +254,35 @@ function prepareDispatchArguments(classification, options, canaryPath, projectCo
255
254
  ? filterRedundantXpcshellFlavorArgs(options.machArg, classification)
256
255
  : [];
257
256
  const extraArgs = prepareExtraMachArgs(options, shuffleSeed, canaryPath, projectConfig, forwardedMachArgs);
257
+ extraArgs.push(...restartBetweenTestsArgs(options, suite, forwardedMachArgs));
258
258
  return { suite, shuffleSeed, extraArgs };
259
259
  }
260
- async function runTestCommandBody(projectRoot, testPaths, options = {}, profileEnv) {
260
+ /**
261
+ * Directory arguments mean exactly that directory: mozbuild's test resolver
262
+ * matches paths by string prefix, so a bare directory arg silently sweeps in
263
+ * prefix-named siblings, with `…/test/hominis` also running
264
+ * `…/test/hominis-tiles`, with no indication the scope widened. Each
265
+ * directory argument therefore dispatches as its enumerated explicit
266
+ * test-file list, which cannot prefix-match a sibling. A trailing-`/`
267
+ * normalization does not work, as mach still sweeps the sibling in. Any
268
+ * prefix-siblings are echoed so the narrowed scope is visible.
269
+ *
270
+ * xpcshell appdir auto-injection happens per harness invocation inside
271
+ * `runTestsWithRetries` (src/commands/test-run.ts) so sharded runs probe the
272
+ * manifest for each file individually. See src/core/xpcshell-appdir.ts for
273
+ * the full motivation.
274
+ */
275
+ async function resolveDispatchGroups(engineDir, normalizedPaths) {
276
+ const scopes = await analyzeTestPathScopes(engineDir, normalizedPaths);
277
+ for (const scope of scopes) {
278
+ const notice = formatScopeNotice(scope);
279
+ if (notice)
280
+ info(notice);
281
+ }
282
+ logTestSelection(scopes);
283
+ return scopes.map((scope) => ({ label: scope.requestedPath, paths: scope.dispatchPaths }));
284
+ }
285
+ async function runTestCommandBody(projectRoot, testPaths, options = {}, profileEnv, hooks = {}) {
261
286
  const paths = getProjectPaths(projectRoot);
262
287
  // Check if engine exists
263
288
  await assertEngineExists(paths.engine);
@@ -281,7 +306,7 @@ async function runTestCommandBody(projectRoot, testPaths, options = {}, profileE
281
306
  // against an incomplete build surface the missing-bundle path instead of a
282
307
  // cryptic `Browser process exited during spawn (exit code 1, signal none).
283
308
  // stderr tail: (empty)`.
284
- const launchablePath = await resolveLaunchablePathForTests(paths.engine, projectConfig.binaryName, buildCheck.objDir);
309
+ const launchablePath = await resolveLaunchablePathForTests(projectRoot, paths.engine, projectConfig.binaryName, buildCheck.objDir);
285
310
  const harnessRetries = options.harnessRetries ?? DEFAULT_HARNESS_RETRIES;
286
311
  // Normalized engine-relative request paths, hoisted above the build/stale
287
312
  // gate: the pre-test build records them as the packaging-coverage claim,
@@ -304,7 +329,7 @@ async function runTestCommandBody(projectRoot, testPaths, options = {}, profileE
304
329
  // mach.
305
330
  if (await runTestBuildPhase(projectRoot, paths, projectConfig, harnessRetries, options, {
306
331
  classification,
307
- normalizedPaths,
332
+ normalizedPaths: hooks.coveragePaths ?? normalizedPaths,
308
333
  })) {
309
334
  return;
310
335
  }
@@ -324,31 +349,9 @@ async function runTestCommandBody(projectRoot, testPaths, options = {}, profileE
324
349
  await assertTestPathsExist(paths.engine, normalizedPaths);
325
350
  const { suite, shuffleSeed, extraArgs } = prepareDispatchArguments(classification, options, canaryPath, projectConfig);
326
351
  appendMarionetteForwardingArgs(extraArgs, options, forwardedPort, xpcshellOnly);
327
- // Directory arguments mean exactly that directory: mozbuild's test
328
- // resolver matches paths by string prefix, so a bare directory arg
329
- // silently sweeps in prefix-named siblings, with `…/test/hominis` also
330
- // running `…/test/hominis-tiles`, with no indication the scope widened. Each
331
- // directory argument therefore dispatches as its enumerated explicit
332
- // test-file list, which cannot prefix-match a sibling. A trailing-`/`
333
- // normalization does not work, as mach still sweeps the sibling in. Any
334
- // prefix-siblings are echoed so the narrowed scope is visible.
335
352
  // Classification above intentionally used the raw argument forms. Only the
336
353
  // mach dispatch needs the exact-match shape.
337
- const scopes = await analyzeTestPathScopes(paths.engine, normalizedPaths);
338
- const dispatchGroups = scopes.map((scope) => ({
339
- label: scope.requestedPath,
340
- paths: scope.dispatchPaths,
341
- }));
342
- for (const scope of scopes) {
343
- const notice = formatScopeNotice(scope);
344
- if (notice)
345
- info(notice);
346
- }
347
- // xpcshell appdir auto-injection happens per harness invocation inside
348
- // `runTestsWithRetries` (src/commands/test-run.ts) so sharded runs probe
349
- // the manifest for each file individually. See src/core/xpcshell-appdir.ts
350
- // for the full motivation.
351
- logTestSelection(scopes);
354
+ const dispatchGroups = await resolveDispatchGroups(paths.engine, normalizedPaths);
352
355
  const harnessEnv = mergeHarnessEnv(buildPerfSampleEnv(projectRoot, projectConfig.binaryName, options.perfSamples), profileEnv, shuffleSeed === undefined ? undefined : { FIREFORGE_SHUFFLE_SEED: String(shuffleSeed) });
353
356
  const runCtx = {
354
357
  engineDir: paths.engine,
@@ -365,6 +368,9 @@ async function runTestCommandBody(projectRoot, testPaths, options = {}, profileE
365
368
  const postRebuildContext = options.build
366
369
  ? createPostRebuildFailureContext('fireforge test --build', normalizedPaths)
367
370
  : undefined;
371
+ // A negative control goes in here: after every gate and the build, so
372
+ // packaging cannot overwrite it and stale-build cannot refuse it.
373
+ await hooks.beforeDispatch?.();
368
374
  // Multi-argument requests shard into sequential harness runs by default:
369
375
  // one shared mochitest profile across files bleeds pref/media-query state
370
376
  // into later files. Sharding is per path argument: a directory argument
@@ -390,7 +396,7 @@ async function runTestCommandBody(projectRoot, testPaths, options = {}, profileE
390
396
  // `FAIL reason=inconclusive` and throws, so an invalidated run can
391
397
  // never print `PASS shards=N/N` first. (A throw here masks an
392
398
  // in-flight shard error, as the plain assert always did.)
393
- await verifyEngineGenerationOrEmitInconclusive(paths.engine, generationBefore, runCtx.powerChanged);
399
+ await verifyEngineGenerationOrEmitInconclusive(paths.engine, generationBefore, runCtx.powerChanged, options.perfSamples);
394
400
  }
395
401
  finalizeShardedOutcome(summary);
396
402
  return;
@@ -405,7 +411,7 @@ async function runTestCommandBody(projectRoot, testPaths, options = {}, profileE
405
411
  throw new BuildError('Test process failed to start', 'mach test', error instanceof Error ? error : undefined);
406
412
  }
407
413
  finally {
408
- await verifyEngineGenerationOrEmitInconclusive(paths.engine, generationBefore, runCtx.powerChanged);
414
+ await verifyEngineGenerationOrEmitInconclusive(paths.engine, generationBefore, runCtx.powerChanged, options.perfSamples);
409
415
  }
410
416
  if (canaryPath !== undefined) {
411
417
  reportCanaryOutcome(outcome);
@@ -0,0 +1,14 @@
1
+ /** Options accepted by {@link tokenLintCommand}. */
2
+ export interface TokenLintOptions {
3
+ /** Emit the machine-readable envelope instead of the human report. */
4
+ json?: boolean;
5
+ }
6
+ /**
7
+ * Runs `token lint`. Exits non-zero (GeneralError) when any finding is
8
+ * reported; with `--json` the report is written first, as `status --check
9
+ * --json` does.
10
+ *
11
+ * @param projectRoot - Root directory of the project
12
+ * @param options - Output-mode options
13
+ */
14
+ export declare function tokenLintCommand(projectRoot: string, options?: TokenLintOptions): Promise<void>;
@@ -0,0 +1,111 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * `fireforge token lint`: themed-block lockstep and undeclared token
4
+ * references (see `core/token-lint.ts` for the rules).
5
+ *
6
+ * Helper module: no registrar, called from `token.ts`.
7
+ */
8
+ import { join } from 'node:path';
9
+ import { getProjectPaths, loadConfig } from '../core/config.js';
10
+ import { furnaceConfigExists, loadFurnaceConfig } from '../core/furnace-config.js';
11
+ import { loadPatchesManifest } from '../core/patch-manifest.js';
12
+ import { collectDeclaredTokenNames, lintTokenLockstep, lintUndeclaredTokenReferences, } from '../core/token-lint.js';
13
+ import { getTokensCssPath } from '../core/token-manager.js';
14
+ import { GeneralError } from '../errors/base.js';
15
+ import { toError } from '../utils/errors.js';
16
+ import { pathExists, readText } from '../utils/fs.js';
17
+ import { info, intro, outro, setMachineOutputMode, setStdoutSealed, success, warn, } from '../utils/logger.js';
18
+ import { emitMachineError, MACHINE_OUTPUT_SCHEMA_VERSION } from '../utils/machine-output.js';
19
+ /** Token prefix and exempt names from furnace.json, with the init default. */
20
+ async function loadTokenSettings(projectRoot, binaryName) {
21
+ const fallback = { tokenPrefix: `--${binaryName}-`, exempt: new Set() };
22
+ if (!(await furnaceConfigExists(projectRoot)))
23
+ return fallback;
24
+ try {
25
+ const furnace = await loadFurnaceConfig(projectRoot);
26
+ return {
27
+ tokenPrefix: furnace.tokenPrefix ?? fallback.tokenPrefix,
28
+ exempt: new Set([...(furnace.tokenAllowlist ?? []), ...(furnace.runtimeVariables ?? [])]),
29
+ };
30
+ }
31
+ catch (error) {
32
+ warn(`Could not load furnace.json; using the default token prefix (${toError(error).message})`);
33
+ return fallback;
34
+ }
35
+ }
36
+ async function collectTokenLint(projectRoot) {
37
+ const paths = getProjectPaths(projectRoot);
38
+ const config = await loadConfig(projectRoot);
39
+ const tokensCssPath = getTokensCssPath(config.binaryName);
40
+ const tokensFile = join(paths.engine, tokensCssPath);
41
+ if (!(await pathExists(tokensFile))) {
42
+ throw new GeneralError(`Token CSS file not found: ${tokensCssPath}. Run "fireforge furnace init" to seed it.`);
43
+ }
44
+ const { tokenPrefix, exempt } = await loadTokenSettings(projectRoot, config.binaryName);
45
+ const tokensCss = await readText(tokensFile);
46
+ const lines = tokensCss.split('\n');
47
+ const declared = collectDeclaredTokenNames(lines);
48
+ const findings = [
49
+ ...lintTokenLockstep(lines, tokensCssPath, tokenPrefix),
50
+ ...lintUndeclaredTokenReferences(tokensCssPath, tokensCss, tokenPrefix, declared, exempt),
51
+ ];
52
+ // Every CSS file the patch queue touches: the fork's own stylesheets and
53
+ // the upstream ones it edits.
54
+ const manifest = await loadPatchesManifest(paths.patches);
55
+ const cssFiles = [
56
+ ...new Set((manifest?.patches ?? []).flatMap((patch) => patch.filesAffected.filter((file) => file.endsWith('.css') && file !== tokensCssPath))),
57
+ ].sort();
58
+ let filesScanned = 1;
59
+ for (const file of cssFiles) {
60
+ const absolute = join(paths.engine, file);
61
+ if (!(await pathExists(absolute)))
62
+ continue;
63
+ filesScanned++;
64
+ findings.push(...lintUndeclaredTokenReferences(file, await readText(absolute), tokenPrefix, declared, exempt));
65
+ }
66
+ return { tokensCssPath, tokenPrefix, filesScanned, findings };
67
+ }
68
+ function failureMessage(count) {
69
+ return `token lint found ${String(count)} finding(s).`;
70
+ }
71
+ /**
72
+ * Runs `token lint`. Exits non-zero (GeneralError) when any finding is
73
+ * reported; with `--json` the report is written first, as `status --check
74
+ * --json` does.
75
+ *
76
+ * @param projectRoot - Root directory of the project
77
+ * @param options - Output-mode options
78
+ */
79
+ export async function tokenLintCommand(projectRoot, options = {}) {
80
+ if (options.json === true) {
81
+ setMachineOutputMode(true);
82
+ let findings = 0;
83
+ try {
84
+ const report = await collectTokenLint(projectRoot);
85
+ findings = report.findings.length;
86
+ process.stdout.write(`${JSON.stringify({ schemaVersion: MACHINE_OUTPUT_SCHEMA_VERSION, ...report }, null, 2)}\n`);
87
+ setStdoutSealed(true);
88
+ }
89
+ catch (error) {
90
+ emitMachineError('token-lint-failed', toError(error).message);
91
+ }
92
+ finally {
93
+ setMachineOutputMode(false);
94
+ }
95
+ if (findings > 0)
96
+ throw new GeneralError(failureMessage(findings));
97
+ return;
98
+ }
99
+ intro('Token Lint');
100
+ const report = await collectTokenLint(projectRoot);
101
+ for (const finding of report.findings) {
102
+ warn(`${finding.file}:${String(finding.line)} [${finding.check}] ${finding.message}`);
103
+ }
104
+ info(`${String(report.filesScanned)} CSS file(s) scanned for ${report.tokenPrefix}* tokens`);
105
+ if (report.findings.length > 0) {
106
+ outro('Token lint failed');
107
+ throw new GeneralError(failureMessage(report.findings.length));
108
+ }
109
+ success('Themed blocks are in lockstep with the base block; every token reference resolves');
110
+ outro('Token lint passed');
111
+ }
@@ -10,6 +10,7 @@ import { info, intro, outro, success, warn } from '../utils/logger.js';
10
10
  import { pickDefined } from '../utils/options.js';
11
11
  import { normalizeTokenName } from '../utils/validation.js';
12
12
  import { tokenCoverageCommand } from './token-coverage.js';
13
+ import { tokenLintCommand } from './token-lint.js';
13
14
  import { tokenListCommand, tokenShowCommand } from './token-list.js';
14
15
  async function normalizeTokenNameForProject(projectRoot, rawTokenName) {
15
16
  if (rawTokenName.startsWith('--')) {
@@ -217,6 +218,15 @@ export function registerToken(program, { getProjectRoot, withErrorHandling }) {
217
218
  .action(withErrorHandling(async (tokenName, options) => {
218
219
  await tokenShowCommand(getProjectRoot(), tokenName, pickDefined({ json: options.json }));
219
220
  }));
221
+ token
222
+ .command('lint')
223
+ .description('Check the token system: themed override blocks declare only tokens the base :root ' +
224
+ 'declares, and every var(--<prefix>…) in the tokens CSS or a patch-touched CSS file ' +
225
+ 'names a declared token. Exits non-zero on any finding')
226
+ .option('--json', 'Emit the machine-readable envelope on stdout (see docs/machine-output.md)')
227
+ .action(withErrorHandling(async (options) => {
228
+ await tokenLintCommand(getProjectRoot(), pickDefined({ json: options.json }));
229
+ }));
220
230
  token
221
231
  .command('coverage')
222
232
  .description('Measure design token usage across modified CSS files')
@@ -0,0 +1,28 @@
1
+ import type { PatchLintIssue, PatchMetadata } from '../types/commands/index.js';
2
+ /** The resolved scope: manifest filenames, and every path they claim. */
3
+ export interface VerifyScope {
4
+ patches: ReadonlySet<string>;
5
+ files: ReadonlySet<string>;
6
+ }
7
+ /**
8
+ * Resolves raw `--patches` values against the manifest. Returns undefined
9
+ * when the flag was not given and refuses a name that matches no patch.
10
+ *
11
+ * @param patchesDir - Absolute path to the patches directory
12
+ * @param requested - Raw `--patches` values (each may be a comma list)
13
+ */
14
+ export declare function resolveVerifyScope(patchesDir: string, requested: readonly string[] | undefined): Promise<VerifyScope | undefined>;
15
+ /**
16
+ * True when a filename-keyed finding (manifest consistency, policy) is in
17
+ * scope: it names a scoped patch, or it names no patch at all.
18
+ */
19
+ export declare function isFilenameInScope(scope: VerifyScope | undefined, filename: string): boolean;
20
+ /** True when any of the implicated patches is in scope. */
21
+ export declare function anyPatchInScope(scope: VerifyScope | undefined, filenames: readonly string[]): boolean;
22
+ /**
23
+ * Keeps the lint issues that implicate a scoped patch. An issue without a
24
+ * `patches` list is attributed through its file instead.
25
+ */
26
+ export declare function scopeLintIssues(issues: PatchLintIssue[], scope: VerifyScope | undefined): PatchLintIssue[];
27
+ /** Keeps the manifest rows in scope. No scope keeps all. */
28
+ export declare function scopePatches<T extends Pick<PatchMetadata, 'filename'>>(patches: readonly T[], scope: VerifyScope | undefined): T[];
@@ -0,0 +1,67 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * `verify --patches <list>`: judges only the named patches' findings. Split
4
+ * out of verify.ts so the per-group attribution rules sit in one place.
5
+ *
6
+ * A finding is in scope when it names a scoped patch: a manifest or policy
7
+ * issue by its filename, a cross-patch claim or lint issue by any patch it
8
+ * implicates, a drifted file by its owning patch, a dangling registration
9
+ * by the patch that registers it. Findings about the queue as a whole (a
10
+ * manifest that does not parse) are kept, because no patch can be judged
11
+ * past them. Unowned worktree changes belong to no patch and drop out.
12
+ */
13
+ import { loadPatchesManifest } from '../core/patch-manifest.js';
14
+ import { selectPatchSubset } from '../core/patch-subset.js';
15
+ import { GeneralError } from '../errors/base.js';
16
+ /**
17
+ * Resolves raw `--patches` values against the manifest. Returns undefined
18
+ * when the flag was not given and refuses a name that matches no patch.
19
+ *
20
+ * @param patchesDir - Absolute path to the patches directory
21
+ * @param requested - Raw `--patches` values (each may be a comma list)
22
+ */
23
+ export async function resolveVerifyScope(patchesDir, requested) {
24
+ if (requested === undefined || requested.length === 0)
25
+ return undefined;
26
+ const manifest = await loadPatchesManifest(patchesDir);
27
+ if (!manifest || manifest.patches.length === 0) {
28
+ throw new GeneralError('--patches needs a patch queue, but patches.json lists no patches.');
29
+ }
30
+ const subset = selectPatchSubset(manifest, requested, '--patches selects patches (filename, stem, order number, manifest name, or slug).');
31
+ return {
32
+ patches: new Set(subset.map((patch) => patch.filename)),
33
+ files: new Set(subset.flatMap((patch) => patch.filesAffected)),
34
+ };
35
+ }
36
+ /**
37
+ * True when a filename-keyed finding (manifest consistency, policy) is in
38
+ * scope: it names a scoped patch, or it names no patch at all.
39
+ */
40
+ export function isFilenameInScope(scope, filename) {
41
+ if (scope === undefined)
42
+ return true;
43
+ return scope.patches.has(filename) || !filename.endsWith('.patch');
44
+ }
45
+ /** True when any of the implicated patches is in scope. */
46
+ export function anyPatchInScope(scope, filenames) {
47
+ if (scope === undefined)
48
+ return true;
49
+ return filenames.some((filename) => scope.patches.has(filename));
50
+ }
51
+ /**
52
+ * Keeps the lint issues that implicate a scoped patch. An issue without a
53
+ * `patches` list is attributed through its file instead.
54
+ */
55
+ export function scopeLintIssues(issues, scope) {
56
+ if (scope === undefined)
57
+ return issues;
58
+ return issues.filter((issue) => issue.patches !== undefined && issue.patches.length > 0
59
+ ? anyPatchInScope(scope, issue.patches)
60
+ : scope.files.has(issue.file));
61
+ }
62
+ /** Keeps the manifest rows in scope. No scope keeps all. */
63
+ export function scopePatches(patches, scope) {
64
+ if (scope === undefined)
65
+ return [...patches];
66
+ return patches.filter((patch) => scope.patches.has(patch.filename));
67
+ }
@@ -15,6 +15,7 @@
15
15
  */
16
16
  import { Command } from 'commander';
17
17
  import type { CommandContext } from '../types/cli.js';
18
+ import { type VerifyScope } from './verify-scope.js';
18
19
  interface VerifyIssueGroup {
19
20
  title: string;
20
21
  issues: string[];
@@ -31,15 +32,22 @@ export interface PatchQueueHealth {
31
32
  * Collects the same queue-health findings reported by `fireforge verify`
32
33
  * without printing. Used by doctor recovery paths that need a read-only
33
34
  * "is this queue healthy?" decision before clearing stale state.
35
+ *
36
+ * @param projectRoot - Project root directory
37
+ * @param scope - `verify --patches`: keep only the findings that name these
38
+ * patches (see verify-scope.ts). Omitted, every finding counts.
34
39
  */
35
- export declare function collectPatchQueueHealth(projectRoot: string): Promise<PatchQueueHealth>;
40
+ export declare function collectPatchQueueHealth(projectRoot: string, scope?: VerifyScope): Promise<PatchQueueHealth>;
36
41
  /**
37
42
  * Runs the `verify` command: manifest consistency + cross-patch lint.
38
43
  * Read-only. Exits non-zero on any error-severity finding.
39
44
  *
40
45
  * @param projectRoot - Project root directory
46
+ * @param options - `patches`: the raw `--patches` values
41
47
  */
42
- export declare function verifyCommand(projectRoot: string): Promise<void>;
48
+ export declare function verifyCommand(projectRoot: string, options?: {
49
+ patches?: string[];
50
+ }): Promise<void>;
43
51
  /**
44
52
  * Registers the `verify` command on the CLI program.
45
53
  *