@hominis/fireforge 0.49.2 → 0.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (400) hide show
  1. package/CHANGELOG.md +87 -1
  2. package/README.md +1 -1
  3. package/dist/bin/fireforge.js +6 -0
  4. package/dist/build-info.json +4 -4
  5. package/dist/src/commands/build-prelock.d.ts +20 -0
  6. package/dist/src/commands/build-prelock.js +50 -0
  7. package/dist/src/commands/build.d.ts +3 -1
  8. package/dist/src/commands/build.js +13 -6
  9. package/dist/src/commands/check-report.d.ts +41 -0
  10. package/dist/src/commands/check-report.js +91 -0
  11. package/dist/src/commands/check-run.d.ts +33 -0
  12. package/dist/src/commands/check-run.js +112 -0
  13. package/dist/src/commands/check-steps.d.ts +75 -0
  14. package/dist/src/commands/check-steps.js +200 -0
  15. package/dist/src/commands/check.d.ts +32 -0
  16. package/dist/src/commands/check.js +160 -0
  17. package/dist/src/commands/config.js +5 -6
  18. package/dist/src/commands/declarations-report.d.ts +43 -0
  19. package/dist/src/commands/declarations-report.js +76 -0
  20. package/dist/src/commands/doctor-engine-eslint.js +6 -1
  21. package/dist/src/commands/doctor-source-pin.js +4 -1
  22. package/dist/src/commands/export-adopt-check.d.ts +38 -0
  23. package/dist/src/commands/export-adopt-check.js +98 -0
  24. package/dist/src/commands/export-flow.js +29 -14
  25. package/dist/src/commands/export-placement-gate.d.ts +6 -2
  26. package/dist/src/commands/export-placement-gate.js +8 -0
  27. package/dist/src/commands/export-shared.d.ts +2 -1
  28. package/dist/src/commands/export-staged-registration.d.ts +46 -0
  29. package/dist/src/commands/export-staged-registration.js +91 -0
  30. package/dist/src/commands/forward-import-remedy.d.ts +32 -0
  31. package/dist/src/commands/forward-import-remedy.js +63 -0
  32. package/dist/src/commands/furnace/apply.js +3 -3
  33. package/dist/src/commands/furnace/chrome-doc-remove.d.ts +2 -1
  34. package/dist/src/commands/furnace/chrome-doc-remove.js +2 -2
  35. package/dist/src/commands/furnace/chrome-doc.d.ts +2 -1
  36. package/dist/src/commands/furnace/chrome-doc.js +2 -2
  37. package/dist/src/commands/furnace/create.js +2 -2
  38. package/dist/src/commands/furnace/deploy-named.d.ts +48 -0
  39. package/dist/src/commands/furnace/deploy-named.js +107 -0
  40. package/dist/src/commands/furnace/deploy.d.ts +3 -2
  41. package/dist/src/commands/furnace/deploy.js +50 -86
  42. package/dist/src/commands/furnace/diff.js +26 -3
  43. package/dist/src/commands/furnace/index.js +66 -41
  44. package/dist/src/commands/furnace/init.js +13 -6
  45. package/dist/src/commands/furnace/override.js +4 -2
  46. package/dist/src/commands/furnace/preview.js +4 -3
  47. package/dist/src/commands/furnace/refresh.js +2 -2
  48. package/dist/src/commands/furnace/remove.js +4 -3
  49. package/dist/src/commands/furnace/rename-dry-run.d.ts +29 -0
  50. package/dist/src/commands/furnace/rename-dry-run.js +127 -0
  51. package/dist/src/commands/furnace/rename-helpers.d.ts +7 -0
  52. package/dist/src/commands/furnace/rename-helpers.js +15 -1
  53. package/dist/src/commands/furnace/rename-registrations.d.ts +41 -0
  54. package/dist/src/commands/furnace/rename-registrations.js +98 -0
  55. package/dist/src/commands/furnace/rename.d.ts +2 -1
  56. package/dist/src/commands/furnace/rename.js +28 -66
  57. package/dist/src/commands/furnace/scan.d.ts +9 -4
  58. package/dist/src/commands/furnace/scan.js +11 -9
  59. package/dist/src/commands/furnace/sync.js +3 -2
  60. package/dist/src/commands/furnace/validate-report.d.ts +45 -0
  61. package/dist/src/commands/furnace/validate-report.js +91 -0
  62. package/dist/src/commands/furnace/validate.d.ts +1 -1
  63. package/dist/src/commands/furnace/validate.js +151 -113
  64. package/dist/src/commands/furnace/validation-output.js +14 -3
  65. package/dist/src/commands/lint-per-patch.js +36 -61
  66. package/dist/src/commands/lint-per-run-checkjs.js +2 -2
  67. package/dist/src/commands/lint-report.d.ts +8 -4
  68. package/dist/src/commands/lint-report.js +20 -5
  69. package/dist/src/commands/lint.js +15 -34
  70. package/dist/src/commands/manifest.js +4 -0
  71. package/dist/src/commands/park.d.ts +22 -0
  72. package/dist/src/commands/park.js +232 -0
  73. package/dist/src/commands/patch/delete-gap-notice.js +3 -1
  74. package/dist/src/commands/patch/delete.d.ts +1 -1
  75. package/dist/src/commands/patch/delete.js +24 -104
  76. package/dist/src/commands/patch/index.d.ts +3 -3
  77. package/dist/src/commands/patch/index.js +10 -4
  78. package/dist/src/commands/patch/lint-ignore-from.d.ts +44 -0
  79. package/dist/src/commands/patch/lint-ignore-from.js +142 -0
  80. package/dist/src/commands/patch/lint-ignore.d.ts +5 -1
  81. package/dist/src/commands/patch/lint-ignore.js +78 -31
  82. package/dist/src/commands/patch/move-files-create.js +34 -18
  83. package/dist/src/commands/patch/move-files-into.js +15 -7
  84. package/dist/src/commands/patch/move-files.js +6 -2
  85. package/dist/src/commands/patch/removal-created-files.d.ts +48 -0
  86. package/dist/src/commands/patch/removal-created-files.js +83 -0
  87. package/dist/src/commands/patch/removal-dependents.d.ts +63 -0
  88. package/dist/src/commands/patch/removal-dependents.js +133 -0
  89. package/dist/src/commands/patch/rename.js +20 -1
  90. package/dist/src/commands/patch/reserve.d.ts +33 -0
  91. package/dist/src/commands/patch/reserve.js +152 -0
  92. package/dist/src/commands/patch/retire.d.ts +34 -0
  93. package/dist/src/commands/patch/retire.js +133 -0
  94. package/dist/src/commands/patch/split-plan.d.ts +17 -8
  95. package/dist/src/commands/patch/split-plan.js +60 -28
  96. package/dist/src/commands/patch/split-staged.d.ts +108 -0
  97. package/dist/src/commands/patch/split-staged.js +202 -0
  98. package/dist/src/commands/patch/split.d.ts +5 -0
  99. package/dist/src/commands/patch/split.js +62 -17
  100. package/dist/src/commands/patch/stage.d.ts +34 -0
  101. package/dist/src/commands/patch/stage.js +153 -0
  102. package/dist/src/commands/patch/staged-dependency-batch.d.ts +48 -0
  103. package/dist/src/commands/patch/staged-dependency-batch.js +176 -0
  104. package/dist/src/commands/patch/staged-dependency-entries.d.ts +36 -0
  105. package/dist/src/commands/patch/staged-dependency-entries.js +52 -0
  106. package/dist/src/commands/patch/staged-dependency-from.d.ts +14 -0
  107. package/dist/src/commands/patch/staged-dependency-from.js +108 -0
  108. package/dist/src/commands/patch/staged-dependency-remove.d.ts +31 -0
  109. package/dist/src/commands/patch/staged-dependency-remove.js +80 -0
  110. package/dist/src/commands/patch/staged-dependency.js +54 -139
  111. package/dist/src/commands/re-export-adjacent-registrations.d.ts +20 -0
  112. package/dist/src/commands/re-export-adjacent-registrations.js +75 -0
  113. package/dist/src/commands/re-export-adjacent.d.ts +50 -2
  114. package/dist/src/commands/re-export-adjacent.js +160 -40
  115. package/dist/src/commands/re-export-bulk-scan.d.ts +2 -1
  116. package/dist/src/commands/re-export-bulk-scan.js +22 -7
  117. package/dist/src/commands/re-export-drift.d.ts +36 -8
  118. package/dist/src/commands/re-export-drift.js +73 -49
  119. package/dist/src/commands/re-export-files.js +11 -8
  120. package/dist/src/commands/re-export-hunks.d.ts +35 -0
  121. package/dist/src/commands/re-export-hunks.js +220 -0
  122. package/dist/src/commands/re-export-lint.d.ts +2 -1
  123. package/dist/src/commands/re-export-lint.js +23 -10
  124. package/dist/src/commands/re-export-options.js +74 -6
  125. package/dist/src/commands/re-export-preflight.d.ts +23 -0
  126. package/dist/src/commands/re-export-preflight.js +160 -0
  127. package/dist/src/commands/re-export-purity-scope.d.ts +18 -0
  128. package/dist/src/commands/re-export-purity-scope.js +131 -0
  129. package/dist/src/commands/re-export-register.js +56 -17
  130. package/dist/src/commands/re-export-removed.d.ts +13 -2
  131. package/dist/src/commands/re-export-removed.js +57 -12
  132. package/dist/src/commands/re-export-scan-owner.d.ts +44 -0
  133. package/dist/src/commands/re-export-scan-owner.js +116 -0
  134. package/dist/src/commands/re-export-scan.d.ts +19 -9
  135. package/dist/src/commands/re-export-scan.js +60 -24
  136. package/dist/src/commands/re-export-single.js +122 -46
  137. package/dist/src/commands/re-export-stale-registrations.d.ts +18 -0
  138. package/dist/src/commands/re-export-stale-registrations.js +62 -0
  139. package/dist/src/commands/re-export-summary.d.ts +28 -0
  140. package/dist/src/commands/re-export-summary.js +36 -0
  141. package/dist/src/commands/re-export.js +71 -57
  142. package/dist/src/commands/rebase/index.js +17 -17
  143. package/dist/src/commands/run.js +16 -2
  144. package/dist/src/commands/status-check.d.ts +55 -16
  145. package/dist/src/commands/status-check.js +92 -37
  146. package/dist/src/commands/status-json.d.ts +22 -9
  147. package/dist/src/commands/status-json.js +47 -32
  148. package/dist/src/commands/status-output.js +3 -1
  149. package/dist/src/commands/status-scope.d.ts +37 -0
  150. package/dist/src/commands/status-scope.js +54 -0
  151. package/dist/src/commands/status.js +66 -23
  152. package/dist/src/commands/test-browser-preflight.js +19 -3
  153. package/dist/src/commands/test-build-furnace.d.ts +22 -0
  154. package/dist/src/commands/test-build-furnace.js +54 -0
  155. package/dist/src/commands/test-build-phase.js +64 -41
  156. package/dist/src/commands/test-checkjs.js +10 -2
  157. package/dist/src/commands/test-diagnose.js +7 -0
  158. package/dist/src/commands/test-dispatch.d.ts +39 -0
  159. package/dist/src/commands/test-dispatch.js +211 -0
  160. package/dist/src/commands/test-furnace-reach.d.ts +37 -0
  161. package/dist/src/commands/test-furnace-reach.js +132 -0
  162. package/dist/src/commands/test-modes.d.ts +16 -1
  163. package/dist/src/commands/test-modes.js +24 -1
  164. package/dist/src/commands/test-multi-verdict.d.ts +61 -0
  165. package/dist/src/commands/test-multi-verdict.js +111 -0
  166. package/dist/src/commands/test-orchestrate.d.ts +52 -0
  167. package/dist/src/commands/test-orchestrate.js +240 -0
  168. package/dist/src/commands/test-perf-scope.d.ts +50 -0
  169. package/dist/src/commands/test-perf-scope.js +138 -0
  170. package/dist/src/commands/test-prelock.d.ts +51 -0
  171. package/dist/src/commands/test-prelock.js +273 -0
  172. package/dist/src/commands/test-register.js +69 -10
  173. package/dist/src/commands/test-results.d.ts +62 -0
  174. package/dist/src/commands/test-results.js +62 -0
  175. package/dist/src/commands/test-run.d.ts +13 -55
  176. package/dist/src/commands/test-run.js +74 -99
  177. package/dist/src/commands/test-shards.d.ts +110 -0
  178. package/dist/src/commands/test-shards.js +255 -0
  179. package/dist/src/commands/test-stale-gate.d.ts +5 -1
  180. package/dist/src/commands/test-stale-gate.js +60 -28
  181. package/dist/src/commands/test-verdict.d.ts +11 -1
  182. package/dist/src/commands/test-verdict.js +28 -1
  183. package/dist/src/commands/test.d.ts +6 -1
  184. package/dist/src/commands/test.js +120 -124
  185. package/dist/src/commands/token-lint.d.ts +14 -0
  186. package/dist/src/commands/token-lint.js +151 -0
  187. package/dist/src/commands/token.js +10 -0
  188. package/dist/src/commands/verify-scope.d.ts +28 -0
  189. package/dist/src/commands/verify-scope.js +67 -0
  190. package/dist/src/commands/verify.d.ts +10 -2
  191. package/dist/src/commands/verify.js +67 -36
  192. package/dist/src/core/build-baseline-types.d.ts +46 -0
  193. package/dist/src/core/build-baseline-validate.js +19 -0
  194. package/dist/src/core/build-baseline.d.ts +5 -1
  195. package/dist/src/core/build-baseline.js +3 -1
  196. package/dist/src/core/build-overwrite-guard.d.ts +50 -0
  197. package/dist/src/core/build-overwrite-guard.js +84 -4
  198. package/dist/src/core/build-prepare.d.ts +13 -0
  199. package/dist/src/core/build-prepare.js +42 -10
  200. package/dist/src/core/build-run-record.d.ts +74 -0
  201. package/dist/src/core/build-run-record.js +136 -0
  202. package/dist/src/core/checkjs-support-heads.d.ts +41 -0
  203. package/dist/src/core/checkjs-support-heads.js +104 -0
  204. package/dist/src/core/config-paths.d.ts +2 -2
  205. package/dist/src/core/config-paths.js +10 -0
  206. package/dist/src/core/config-validate-check.d.ts +8 -0
  207. package/dist/src/core/config-validate-check.js +219 -0
  208. package/dist/src/core/config-validate-file-size.d.ts +9 -6
  209. package/dist/src/core/config-validate-file-size.js +91 -2
  210. package/dist/src/core/config-validate-history.d.ts +11 -0
  211. package/dist/src/core/config-validate-history.js +38 -0
  212. package/dist/src/core/config-validate-patch-policy.js +100 -0
  213. package/dist/src/core/config-validate-test-toolchains.js +21 -0
  214. package/dist/src/core/config-validate-token-scales.d.ts +9 -0
  215. package/dist/src/core/config-validate-token-scales.js +92 -0
  216. package/dist/src/core/config-validate.js +20 -5
  217. package/dist/src/core/config.d.ts +5 -0
  218. package/dist/src/core/config.js +16 -0
  219. package/dist/src/core/coverage-dispatch.d.ts +24 -0
  220. package/dist/src/core/coverage-dispatch.js +44 -0
  221. package/dist/src/core/coverage-extend.d.ts +70 -45
  222. package/dist/src/core/coverage-extend.js +199 -144
  223. package/dist/src/core/coverage-records.d.ts +108 -0
  224. package/dist/src/core/coverage-records.js +384 -0
  225. package/dist/src/core/destructive.d.ts +7 -4
  226. package/dist/src/core/destructive.js +10 -10
  227. package/dist/src/core/engine-session-lock.d.ts +16 -1
  228. package/dist/src/core/engine-session-lock.js +38 -10
  229. package/dist/src/core/export-moved-code-shape.d.ts +49 -11
  230. package/dist/src/core/export-moved-code-shape.js +135 -44
  231. package/dist/src/core/file-lock-queue.d.ts +67 -0
  232. package/dist/src/core/file-lock-queue.js +221 -0
  233. package/dist/src/core/file-lock-shared.d.ts +57 -0
  234. package/dist/src/core/file-lock-shared.js +117 -0
  235. package/dist/src/core/file-lock.d.ts +13 -25
  236. package/dist/src/core/file-lock.js +163 -140
  237. package/dist/src/core/furnace-apply-output.d.ts +10 -0
  238. package/dist/src/core/furnace-apply-output.js +31 -1
  239. package/dist/src/core/furnace-apply.js +22 -12
  240. package/dist/src/core/furnace-composed-source.d.ts +76 -0
  241. package/dist/src/core/furnace-composed-source.js +167 -0
  242. package/dist/src/core/furnace-config-validate-exceptions.d.ts +16 -0
  243. package/dist/src/core/furnace-config-validate-exceptions.js +102 -0
  244. package/dist/src/core/furnace-config-validate.js +14 -0
  245. package/dist/src/core/furnace-css-fragments.d.ts +10 -0
  246. package/dist/src/core/furnace-css-fragments.js +26 -0
  247. package/dist/src/core/furnace-operation.d.ts +3 -3
  248. package/dist/src/core/furnace-registration-jar-inplace.d.ts +12 -0
  249. package/dist/src/core/furnace-registration-jar-inplace.js +80 -0
  250. package/dist/src/core/furnace-registration-remove.d.ts +22 -1
  251. package/dist/src/core/furnace-registration-remove.js +53 -17
  252. package/dist/src/core/furnace-registration-rename.d.ts +26 -0
  253. package/dist/src/core/furnace-registration-rename.js +77 -0
  254. package/dist/src/core/furnace-registration.d.ts +14 -4
  255. package/dist/src/core/furnace-registration.js +44 -10
  256. package/dist/src/core/furnace-staleness.d.ts +7 -0
  257. package/dist/src/core/furnace-staleness.js +34 -24
  258. package/dist/src/core/furnace-validate-accessibility.d.ts +8 -4
  259. package/dist/src/core/furnace-validate-accessibility.js +71 -28
  260. package/dist/src/core/furnace-validate-exceptions.d.ts +48 -0
  261. package/dist/src/core/furnace-validate-exceptions.js +80 -0
  262. package/dist/src/core/furnace-validate-icon-button.d.ts +7 -0
  263. package/dist/src/core/furnace-validate-icon-button.js +78 -0
  264. package/dist/src/core/furnace-validate.js +1 -1
  265. package/dist/src/core/harness-port-fallback.d.ts +60 -0
  266. package/dist/src/core/harness-port-fallback.js +96 -0
  267. package/dist/src/core/history-log-file.d.ts +61 -0
  268. package/dist/src/core/history-log-file.js +164 -0
  269. package/dist/src/core/history-log.d.ts +2 -1
  270. package/dist/src/core/history-log.js +2 -1
  271. package/dist/src/core/json-preserving-edit.d.ts +22 -0
  272. package/dist/src/core/json-preserving-edit.js +199 -0
  273. package/dist/src/core/line-prefixer.d.ts +18 -0
  274. package/dist/src/core/line-prefixer.js +34 -0
  275. package/dist/src/core/mach.d.ts +7 -0
  276. package/dist/src/core/mach.js +32 -10
  277. package/dist/src/core/module-resolution-preflight.d.ts +19 -3
  278. package/dist/src/core/module-resolution-preflight.js +24 -6
  279. package/dist/src/core/negative-control-compiled.d.ts +27 -0
  280. package/dist/src/core/negative-control-compiled.js +73 -0
  281. package/dist/src/core/negative-control-diff.d.ts +51 -0
  282. package/dist/src/core/negative-control-diff.js +179 -0
  283. package/dist/src/core/negative-control.d.ts +150 -0
  284. package/dist/src/core/negative-control.js +399 -0
  285. package/dist/src/core/objdir-install-manifests.d.ts +73 -0
  286. package/dist/src/core/objdir-install-manifests.js +226 -0
  287. package/dist/src/core/park-store.d.ts +63 -0
  288. package/dist/src/core/park-store.js +216 -0
  289. package/dist/src/core/patch-export-update.d.ts +6 -0
  290. package/dist/src/core/patch-export-update.js +13 -3
  291. package/dist/src/core/patch-export.d.ts +3 -2
  292. package/dist/src/core/patch-export.js +22 -16
  293. package/dist/src/core/patch-files.js +26 -8
  294. package/dist/src/core/patch-lint-checkjs-any-import.d.ts +39 -0
  295. package/dist/src/core/patch-lint-checkjs-any-import.js +63 -0
  296. package/dist/src/core/patch-lint-checkjs-diagnostics.d.ts +17 -0
  297. package/dist/src/core/patch-lint-checkjs-diagnostics.js +32 -0
  298. package/dist/src/core/patch-lint-checkjs.d.ts +10 -1
  299. package/dist/src/core/patch-lint-checkjs.js +20 -13
  300. package/dist/src/core/patch-lint-code-lines.d.ts +59 -0
  301. package/dist/src/core/patch-lint-code-lines.js +105 -0
  302. package/dist/src/core/patch-lint-cross.d.ts +3 -1
  303. package/dist/src/core/patch-lint-cross.js +17 -4
  304. package/dist/src/core/patch-lint-css.d.ts +8 -2
  305. package/dist/src/core/patch-lint-css.js +24 -14
  306. package/dist/src/core/patch-lint-file-size.d.ts +12 -5
  307. package/dist/src/core/patch-lint-file-size.js +22 -11
  308. package/dist/src/core/patch-lint-forward-registration-extended.d.ts +23 -0
  309. package/dist/src/core/patch-lint-forward-registration-extended.js +36 -11
  310. package/dist/src/core/patch-lint-forward-registration.js +10 -0
  311. package/dist/src/core/patch-lint-ignore.d.ts +196 -0
  312. package/dist/src/core/patch-lint-ignore.js +429 -0
  313. package/dist/src/core/patch-lint-ownership.d.ts +10 -0
  314. package/dist/src/core/patch-lint-ownership.js +26 -0
  315. package/dist/src/core/patch-lint-size-thresholds.d.ts +52 -0
  316. package/dist/src/core/patch-lint-size-thresholds.js +110 -0
  317. package/dist/src/core/patch-lint-size.d.ts +117 -0
  318. package/dist/src/core/patch-lint-size.js +331 -0
  319. package/dist/src/core/patch-lint-staged-registration.d.ts +18 -1
  320. package/dist/src/core/patch-lint-staged-registration.js +20 -0
  321. package/dist/src/core/patch-lint-token-scale.d.ts +57 -0
  322. package/dist/src/core/patch-lint-token-scale.js +286 -0
  323. package/dist/src/core/patch-lint.d.ts +11 -83
  324. package/dist/src/core/patch-lint.js +66 -288
  325. package/dist/src/core/patch-lock.js +7 -6
  326. package/dist/src/core/patch-manifest-consistency.js +7 -6
  327. package/dist/src/core/patch-manifest-io.d.ts +6 -3
  328. package/dist/src/core/patch-manifest-io.js +27 -9
  329. package/dist/src/core/patch-manifest-key-order.d.ts +24 -0
  330. package/dist/src/core/patch-manifest-key-order.js +61 -0
  331. package/dist/src/core/patch-manifest-stage.d.ts +46 -0
  332. package/dist/src/core/patch-manifest-stage.js +96 -0
  333. package/dist/src/core/patch-manifest-validate.js +61 -3
  334. package/dist/src/core/patch-order-legal.d.ts +26 -0
  335. package/dist/src/core/patch-order-legal.js +88 -0
  336. package/dist/src/core/patch-policy-content.d.ts +40 -0
  337. package/dist/src/core/patch-policy-content.js +216 -0
  338. package/dist/src/core/patch-policy.d.ts +22 -5
  339. package/dist/src/core/patch-policy.js +26 -12
  340. package/dist/src/core/patch-retired.d.ts +57 -0
  341. package/dist/src/core/patch-retired.js +92 -0
  342. package/dist/src/core/patch-subset.d.ts +16 -0
  343. package/dist/src/core/patch-subset.js +66 -0
  344. package/dist/src/core/preflight-wait.d.ts +20 -0
  345. package/dist/src/core/preflight-wait.js +38 -0
  346. package/dist/src/core/rebase-from-version.d.ts +46 -0
  347. package/dist/src/core/rebase-from-version.js +71 -0
  348. package/dist/src/core/run-log.d.ts +26 -0
  349. package/dist/src/core/run-log.js +106 -4
  350. package/dist/src/core/run-profile.d.ts +7 -0
  351. package/dist/src/core/run-profile.js +7 -1
  352. package/dist/src/core/smoke-patterns.d.ts +5 -0
  353. package/dist/src/core/smoke-patterns.js +13 -0
  354. package/dist/src/core/test-harness-crash.d.ts +19 -1
  355. package/dist/src/core/test-harness-crash.js +8 -9
  356. package/dist/src/core/test-harness-signature.d.ts +33 -0
  357. package/dist/src/core/test-harness-signature.js +23 -1
  358. package/dist/src/core/test-raw-log.d.ts +66 -0
  359. package/dist/src/core/test-raw-log.js +122 -0
  360. package/dist/src/core/test-run-completeness.d.ts +90 -0
  361. package/dist/src/core/test-run-completeness.js +184 -0
  362. package/dist/src/core/test-stale-check.d.ts +27 -1
  363. package/dist/src/core/test-stale-check.js +39 -49
  364. package/dist/src/core/test-stale-exempt.d.ts +40 -0
  365. package/dist/src/core/test-stale-exempt.js +70 -0
  366. package/dist/src/core/test-xpcshell-retry.d.ts +2 -0
  367. package/dist/src/core/test-xpcshell-retry.js +2 -1
  368. package/dist/src/core/token-lint.d.ts +53 -0
  369. package/dist/src/core/token-lint.js +131 -0
  370. package/dist/src/core/token-sibling-sheets.d.ts +46 -0
  371. package/dist/src/core/token-sibling-sheets.js +119 -0
  372. package/dist/src/core/tree-guard.js +5 -0
  373. package/dist/src/core/typecheck-shim.d.ts +6 -2
  374. package/dist/src/core/typecheck-shim.js +205 -4
  375. package/dist/src/core/typecheck.js +3 -1
  376. package/dist/src/index.d.ts +14 -3
  377. package/dist/src/index.js +6 -1
  378. package/dist/src/types/commands/index.d.ts +2 -2
  379. package/dist/src/types/commands/options.d.ts +212 -24
  380. package/dist/src/types/commands/patches.d.ts +118 -1
  381. package/dist/src/types/config.d.ts +264 -0
  382. package/dist/src/types/config.js +9 -0
  383. package/dist/src/types/furnace.d.ts +63 -1
  384. package/dist/src/types/furnace.js +7 -0
  385. package/dist/src/types/index.d.ts +1 -1
  386. package/dist/src/utils/options.d.ts +36 -7
  387. package/dist/src/utils/options.js +94 -8
  388. package/dist/src/utils/path-glob.d.ts +15 -0
  389. package/dist/src/utils/path-glob.js +47 -0
  390. package/dist/src/utils/regex.d.ts +15 -3
  391. package/dist/src/utils/regex.js +37 -16
  392. package/docs/configuration.md +369 -11
  393. package/docs/exit-codes.md +18 -1
  394. package/docs/furnace.md +139 -3
  395. package/docs/lifecycle-invariants.md +65 -30
  396. package/docs/machine-output.md +120 -12
  397. package/docs/patch-workflow.md +458 -22
  398. package/docs/run-logs.md +20 -1
  399. package/docs/testing.md +346 -21
  400. package/package.json +3 -3
@@ -0,0 +1,151 @@
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 { buildPatchQueueContext } from '../core/patch-lint.js';
12
+ import { buildJarTargetIndex } from '../core/patch-lint-forward-registration-extended.js';
13
+ import { loadPatchesManifest } from '../core/patch-manifest.js';
14
+ import { collectDeclaredTokenNames, lintTokenLockstep, lintUndeclaredTokenReferences, } from '../core/token-lint.js';
15
+ import { getTokensCssPath } from '../core/token-manager.js';
16
+ import { buildSiblingSheetMap } from '../core/token-sibling-sheets.js';
17
+ import { GeneralError } from '../errors/base.js';
18
+ import { toError } from '../utils/errors.js';
19
+ import { pathExists, readText } from '../utils/fs.js';
20
+ import { info, intro, outro, setMachineOutputMode, setStdoutSealed, success, warn, } from '../utils/logger.js';
21
+ import { emitMachineError, MACHINE_OUTPUT_SCHEMA_VERSION } from '../utils/machine-output.js';
22
+ /** Token prefix, exempt names and sibling sheets from furnace.json, with the init default. */
23
+ async function loadTokenSettings(projectRoot, binaryName) {
24
+ const fallback = { tokenPrefix: `--${binaryName}-`, exempt: new Set() };
25
+ if (!(await furnaceConfigExists(projectRoot)))
26
+ return fallback;
27
+ try {
28
+ const furnace = await loadFurnaceConfig(projectRoot);
29
+ return {
30
+ tokenPrefix: furnace.tokenPrefix ?? fallback.tokenPrefix,
31
+ exempt: new Set([...(furnace.tokenAllowlist ?? []), ...(furnace.runtimeVariables ?? [])]),
32
+ siblingSheets: furnace.tokenSiblingSheets,
33
+ };
34
+ }
35
+ catch (error) {
36
+ warn(`Could not load furnace.json; using the default token prefix (${toError(error).message})`);
37
+ return fallback;
38
+ }
39
+ }
40
+ async function collectTokenLint(projectRoot) {
41
+ const paths = getProjectPaths(projectRoot);
42
+ const config = await loadConfig(projectRoot);
43
+ const tokensCssPath = getTokensCssPath(config.binaryName);
44
+ const tokensFile = join(paths.engine, tokensCssPath);
45
+ if (!(await pathExists(tokensFile))) {
46
+ throw new GeneralError(`Token CSS file not found: ${tokensCssPath}. Run "fireforge furnace init" to seed it.`);
47
+ }
48
+ const { tokenPrefix, exempt, siblingSheets } = await loadTokenSettings(projectRoot, config.binaryName);
49
+ const tokensCss = await readText(tokensFile);
50
+ const lines = tokensCss.split('\n');
51
+ const declared = collectDeclaredTokenNames(lines);
52
+ const findings = [
53
+ ...lintTokenLockstep(lines, tokensCssPath, tokenPrefix),
54
+ ...lintUndeclaredTokenReferences(tokensCssPath, tokensCss, tokenPrefix, declared, exempt),
55
+ ];
56
+ // Every CSS file the patch queue touches: the fork's own stylesheets and
57
+ // the upstream ones it edits.
58
+ const manifest = await loadPatchesManifest(paths.patches);
59
+ const touched = [...new Set((manifest?.patches ?? []).flatMap((patch) => patch.filesAffected))];
60
+ const cssFiles = touched.filter((file) => file.endsWith('.css') && file !== tokensCssPath).sort();
61
+ const contents = await readEngineFiles(paths.engine, touched.filter((file) => LINKING_FILE.test(file)));
62
+ const siblings = buildSiblingSheetMap({
63
+ documents: contents,
64
+ jarTargets: buildJarTargetIndex((await buildPatchQueueContext(paths.patches)).entries),
65
+ sheets: cssFiles,
66
+ explicit: siblingSheets,
67
+ });
68
+ let filesScanned = 1;
69
+ for (const file of cssFiles) {
70
+ const css = contents.get(file);
71
+ if (css === undefined)
72
+ continue;
73
+ filesScanned++;
74
+ findings.push(...lintUndeclaredTokenReferences(file, css, tokenPrefix, await withSiblingDeclarations(paths.engine, declared, siblings.get(file), contents), exempt));
75
+ }
76
+ return { tokensCssPath, tokenPrefix, filesScanned, findings };
77
+ }
78
+ /** Files whose content can link a stylesheet: markup, scripts, and sheets (`@import`). */
79
+ const LINKING_FILE = /\.(?:css|xhtml|html|xml|mjs|js)$/;
80
+ /** Reads the engine files that exist, keyed by engine-relative path. */
81
+ async function readEngineFiles(engineDir, files) {
82
+ const contents = new Map();
83
+ for (const file of files) {
84
+ const absolute = join(engineDir, file);
85
+ if (await pathExists(absolute))
86
+ contents.set(file, await readText(absolute));
87
+ }
88
+ return contents;
89
+ }
90
+ /** The tokens CSS declarations plus every declaration of a sheet's siblings. */
91
+ async function withSiblingDeclarations(engineDir, declared, siblings, contents) {
92
+ if (siblings === undefined || siblings.size === 0)
93
+ return declared;
94
+ const merged = new Set(declared);
95
+ for (const sibling of siblings) {
96
+ if (!contents.has(sibling)) {
97
+ const absolute = join(engineDir, sibling);
98
+ if (!(await pathExists(absolute)))
99
+ continue;
100
+ contents.set(sibling, await readText(absolute));
101
+ }
102
+ const css = contents.get(sibling) ?? '';
103
+ for (const name of collectDeclaredTokenNames(css.split('\n')))
104
+ merged.add(name);
105
+ }
106
+ return merged;
107
+ }
108
+ function failureMessage(count) {
109
+ return `token lint found ${String(count)} finding(s).`;
110
+ }
111
+ /**
112
+ * Runs `token lint`. Exits non-zero (GeneralError) when any finding is
113
+ * reported; with `--json` the report is written first, as `status --check
114
+ * --json` does.
115
+ *
116
+ * @param projectRoot - Root directory of the project
117
+ * @param options - Output-mode options
118
+ */
119
+ export async function tokenLintCommand(projectRoot, options = {}) {
120
+ if (options.json === true) {
121
+ setMachineOutputMode(true);
122
+ let findings = 0;
123
+ try {
124
+ const report = await collectTokenLint(projectRoot);
125
+ findings = report.findings.length;
126
+ process.stdout.write(`${JSON.stringify({ schemaVersion: MACHINE_OUTPUT_SCHEMA_VERSION, ...report }, null, 2)}\n`);
127
+ setStdoutSealed(true);
128
+ }
129
+ catch (error) {
130
+ emitMachineError('token-lint-failed', toError(error).message);
131
+ }
132
+ finally {
133
+ setMachineOutputMode(false);
134
+ }
135
+ if (findings > 0)
136
+ throw new GeneralError(failureMessage(findings));
137
+ return;
138
+ }
139
+ intro('Token Lint');
140
+ const report = await collectTokenLint(projectRoot);
141
+ for (const finding of report.findings) {
142
+ warn(`${finding.file}:${String(finding.line)} [${finding.check}] ${finding.message}`);
143
+ }
144
+ info(`${String(report.filesScanned)} CSS file(s) scanned for ${report.tokenPrefix}* tokens`);
145
+ if (report.findings.length > 0) {
146
+ outro('Token lint failed');
147
+ throw new GeneralError(failureMessage(report.findings.length));
148
+ }
149
+ success('Themed blocks are in lockstep with the base block; every token reference resolves');
150
+ outro('Token lint passed');
151
+ }
@@ -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
  *
@@ -21,16 +21,18 @@ import { collectFurnaceManagedPrefixes } from '../core/furnace-config.js';
21
21
  import { isGitRepository } from '../core/git.js';
22
22
  import { withPrivateGitIndex } from '../core/git-readonly-index.js';
23
23
  import { expandUntrackedDirectoryEntries, getWorkingTreeStatus } from '../core/git-status.js';
24
- import { findUnresolvedSystemModuleImports, formatUnresolvedSystemModuleImports, } from '../core/module-resolution-preflight.js';
24
+ import { collectWorktreeModuleFiles, findUnresolvedSystemModuleImports, formatUnresolvedSystemModuleImports, } from '../core/module-resolution-preflight.js';
25
25
  import { buildPatchQueueContext, formatPatchLintIssue, lintPatchQueue, } from '../core/patch-lint.js';
26
26
  import { loadPatchesManifest, validatePatchesManifestConsistency } from '../core/patch-manifest.js';
27
27
  import { evaluatePatchPolicy } from '../core/patch-policy.js';
28
+ import { evaluatePatchPolicyContentForQueue } from '../core/patch-policy-content.js';
28
29
  import { collectPatchRegistrationReferences } from '../core/patch-registration-refs.js';
29
30
  import { buildPatchClaims } from '../core/status-classify.js';
30
31
  import { classifyFiles } from '../core/status-classify.js';
31
32
  import { GeneralError } from '../errors/base.js';
32
33
  import { pathExists, readText } from '../utils/fs.js';
33
34
  import { info, intro, outro, success, warn } from '../utils/logger.js';
35
+ import { anyPatchInScope, isFilenameInScope, resolveVerifyScope, scopeLintIssues, scopePatches, } from './verify-scope.js';
34
36
  /**
35
37
  * Walks each patch body in the manifest, extracts the set of
36
38
  * component-shaped registration references it adds (widget paths
@@ -41,7 +43,7 @@ import { info, intro, outro, success, warn } from '../utils/logger.js';
41
43
  * registers a file that nothing in the world supplies, which fails at
42
44
  * install time.
43
45
  */
44
- async function detectDanglingRegistrations(patchesDir, engineDir, patches) {
46
+ async function detectDanglingRegistrations(patchesDir, engineDir, patches, scope) {
45
47
  // Aggregate the set of all paths any patch in the queue is responsible for
46
48
  // (per `filesAffected`). Individual patch bodies are deliberately not
47
49
  // parsed for new-file creations here: `filesAffected` is already the
@@ -54,8 +56,9 @@ async function detectDanglingRegistrations(patchesDir, engineDir, patches) {
54
56
  coveredByPatches.add(file);
55
57
  }
56
58
  }
59
+ // Every patch supplies targets; only the scoped ones are judged.
57
60
  const issues = [];
58
- for (const patch of patches) {
61
+ for (const patch of scopePatches(patches, scope)) {
59
62
  const patchPath = join(patchesDir, patch.filename);
60
63
  if (!(await pathExists(patchPath)))
61
64
  continue;
@@ -100,7 +103,7 @@ function detectCrossPatchFileClaims(manifestPatches) {
100
103
  }
101
104
  return results;
102
105
  }
103
- async function detectWorktreeOwnershipDrift(projectRoot, engineDir, patchesDir, binaryName) {
106
+ async function detectWorktreeOwnershipDrift(projectRoot, engineDir, patchesDir, binaryName, scope) {
104
107
  if (!(await pathExists(engineDir)) || !(await isGitRepository(engineDir))) {
105
108
  return { unowned: [], patchOwnedDrift: [] };
106
109
  }
@@ -108,24 +111,51 @@ async function detectWorktreeOwnershipDrift(projectRoot, engineDir, patchesDir,
108
111
  const furnacePrefixes = await collectFurnaceManagedPrefixes(projectRoot);
109
112
  const classified = await classifyFiles(entries, engineDir, patchesDir, binaryName, furnacePrefixes);
110
113
  return {
114
+ // An unowned change belongs to no patch, so no --patches scope covers it.
111
115
  unowned: [
112
116
  ...new Set(classified
113
- .filter((entry) => entry.classification === 'unmanaged')
117
+ .filter((entry) => scope === undefined && entry.classification === 'unmanaged')
114
118
  .map((entry) => entry.file)),
115
119
  ].sort(),
116
120
  patchOwnedDrift: [
117
121
  ...new Set(classified
118
- .filter((entry) => entry.classification === 'patch-owned-drift')
122
+ .filter((entry) => entry.classification === 'patch-owned-drift' &&
123
+ anyPatchInScope(scope, entry.owner === undefined ? [] : [entry.owner]))
119
124
  .map((entry) => entry.file)),
120
125
  ].sort(),
121
126
  };
122
127
  }
128
+ /** The warning groups for unowned changes and patch-owned drift. */
129
+ function buildWorktreeDriftGroups(drift) {
130
+ const groups = [];
131
+ if (drift.unowned.length > 0) {
132
+ groups.push({
133
+ title: `Unowned worktree changes (${drift.unowned.length})`,
134
+ issues: drift.unowned.map((file) => `${file} is changed in engine/ but is not listed in any patch filesAffected entry`),
135
+ errorCount: 0,
136
+ warningCount: drift.unowned.length,
137
+ });
138
+ }
139
+ if (drift.patchOwnedDrift.length > 0) {
140
+ groups.push({
141
+ title: `Patch-owned worktree drift (${drift.patchOwnedDrift.length})`,
142
+ issues: drift.patchOwnedDrift.map((file) => `${file} is claimed by exactly one patch, but engine/ no longer matches that patch output`),
143
+ errorCount: 0,
144
+ warningCount: drift.patchOwnedDrift.length,
145
+ });
146
+ }
147
+ return groups;
148
+ }
123
149
  /**
124
150
  * Collects the same queue-health findings reported by `fireforge verify`
125
151
  * without printing. Used by doctor recovery paths that need a read-only
126
152
  * "is this queue healthy?" decision before clearing stale state.
153
+ *
154
+ * @param projectRoot - Project root directory
155
+ * @param scope - `verify --patches`: keep only the findings that name these
156
+ * patches (see verify-scope.ts). Omitted, every finding counts.
127
157
  */
128
- export async function collectPatchQueueHealth(projectRoot) {
158
+ export async function collectPatchQueueHealth(projectRoot, scope) {
129
159
  const paths = getProjectPaths(projectRoot);
130
160
  const config = await loadConfig(projectRoot);
131
161
  if (!(await pathExists(paths.patches))) {
@@ -139,7 +169,7 @@ export async function collectPatchQueueHealth(projectRoot) {
139
169
  const groups = [];
140
170
  let errorCount = 0;
141
171
  let warningCount = 0;
142
- const consistencyIssues = await validatePatchesManifestConsistency(paths.patches);
172
+ const consistencyIssues = (await validatePatchesManifestConsistency(paths.patches)).filter((issue) => isFilenameInScope(scope, issue.filename));
143
173
  if (consistencyIssues.length > 0) {
144
174
  const issues = consistencyIssues.map((issue) => `[${issue.code}] ${issue.message}`);
145
175
  groups.push({
@@ -152,7 +182,10 @@ export async function collectPatchQueueHealth(projectRoot) {
152
182
  }
153
183
  const manifest = await loadPatchesManifest(paths.patches);
154
184
  if (manifest) {
155
- const policyIssues = evaluatePatchPolicy(config, manifest);
185
+ const policyIssues = [
186
+ ...evaluatePatchPolicy(config, manifest),
187
+ ...(await evaluatePatchPolicyContentForQueue(config, projectRoot, paths.patches, manifest)),
188
+ ].filter((issue) => isFilenameInScope(scope, issue.filename));
156
189
  if (policyIssues.length > 0) {
157
190
  const policyErrors = policyIssues.filter((issue) => issue.severity === 'error').length;
158
191
  const policyWarnings = policyIssues.length - policyErrors;
@@ -173,8 +206,15 @@ export async function collectPatchQueueHealth(projectRoot) {
173
206
  // CREATES. This asks the same question of the engine, so an import ADDED
174
207
  // to an existing module is named as text instead of surfacing later as a
175
208
  // bare `xpcshell return code: -11` with zero output.
209
+ // Unscoped, the engine worktree joins in: an unexported chrome script
210
+ // importing an unexported, unregistered module crashes every objdir
211
+ // start before either file is in the queue. A scoped run never judges
212
+ // unmanaged files.
176
213
  if (await pathExists(paths.engine)) {
177
- const unresolved = await findUnresolvedSystemModuleImports(paths.engine, manifest.patches.flatMap((patch) => patch.filesAffected));
214
+ const worktree = scope === undefined && (await isGitRepository(paths.engine))
215
+ ? await collectWorktreeModuleFiles(paths.engine)
216
+ : undefined;
217
+ const unresolved = await findUnresolvedSystemModuleImports(paths.engine, scopePatches(manifest.patches, scope).flatMap((patch) => patch.filesAffected), worktree);
178
218
  if (unresolved.length > 0) {
179
219
  groups.push({
180
220
  title: `Unresolvable system-module imports (${unresolved.length})`,
@@ -185,7 +225,7 @@ export async function collectPatchQueueHealth(projectRoot) {
185
225
  errorCount += unresolved.length;
186
226
  }
187
227
  }
188
- const crossClaims = detectCrossPatchFileClaims(manifest.patches);
228
+ const crossClaims = detectCrossPatchFileClaims(manifest.patches).filter((claim) => anyPatchInScope(scope, claim.filenames));
189
229
  if (crossClaims.length > 0) {
190
230
  groups.push({
191
231
  title: `Cross-patch filesAffected conflicts (${crossClaims.length})`,
@@ -197,7 +237,7 @@ export async function collectPatchQueueHealth(projectRoot) {
197
237
  }
198
238
  }
199
239
  const ctx = await buildPatchQueueContext(paths.patches);
200
- const lintIssues = lintPatchQueue(ctx);
240
+ const lintIssues = scopeLintIssues(lintPatchQueue(ctx), scope);
201
241
  if (lintIssues.length > 0) {
202
242
  const lintErrors = lintIssues.filter((issue) => issue.severity === 'error').length;
203
243
  const lintWarnings = lintIssues.filter((issue) => issue.severity === 'warning').length;
@@ -214,26 +254,11 @@ export async function collectPatchQueueHealth(projectRoot) {
214
254
  warningCount += lintWarnings;
215
255
  }
216
256
  if (manifest) {
217
- const worktreeDrift = await detectWorktreeOwnershipDrift(projectRoot, paths.engine, paths.patches, config.binaryName);
218
- if (worktreeDrift.unowned.length > 0) {
219
- groups.push({
220
- title: `Unowned worktree changes (${worktreeDrift.unowned.length})`,
221
- issues: worktreeDrift.unowned.map((file) => `${file} is changed in engine/ but is not listed in any patch filesAffected entry`),
222
- errorCount: 0,
223
- warningCount: worktreeDrift.unowned.length,
224
- });
225
- warningCount += worktreeDrift.unowned.length;
226
- }
227
- if (worktreeDrift.patchOwnedDrift.length > 0) {
228
- groups.push({
229
- title: `Patch-owned worktree drift (${worktreeDrift.patchOwnedDrift.length})`,
230
- issues: worktreeDrift.patchOwnedDrift.map((file) => `${file} is claimed by exactly one patch, but engine/ no longer matches that patch output`),
231
- errorCount: 0,
232
- warningCount: worktreeDrift.patchOwnedDrift.length,
233
- });
234
- warningCount += worktreeDrift.patchOwnedDrift.length;
235
- }
236
- const registrationIssues = await detectDanglingRegistrations(paths.patches, paths.engine, manifest.patches);
257
+ const worktreeDrift = await detectWorktreeOwnershipDrift(projectRoot, paths.engine, paths.patches, config.binaryName, scope);
258
+ const driftGroups = buildWorktreeDriftGroups(worktreeDrift);
259
+ groups.push(...driftGroups);
260
+ warningCount += driftGroups.reduce((sum, group) => sum + group.warningCount, 0);
261
+ const registrationIssues = await detectDanglingRegistrations(paths.patches, paths.engine, manifest.patches, scope);
237
262
  if (registrationIssues.length > 0) {
238
263
  groups.push({
239
264
  title: `Dangling registration references (${registrationIssues.length})`,
@@ -256,15 +281,20 @@ export async function collectPatchQueueHealth(projectRoot) {
256
281
  * Read-only. Exits non-zero on any error-severity finding.
257
282
  *
258
283
  * @param projectRoot - Project root directory
284
+ * @param options - `patches`: the raw `--patches` values
259
285
  */
260
- export async function verifyCommand(projectRoot) {
286
+ export async function verifyCommand(projectRoot, options = {}) {
261
287
  intro('FireForge Verify');
288
+ const scope = await resolveVerifyScope(getProjectPaths(projectRoot).patches, options.patches);
289
+ if (scope !== undefined) {
290
+ info(`Judging only: ${[...scope.patches].join(', ')}`);
291
+ }
262
292
  // `verify` is read-only to the OPERATOR but not to git: the working-tree
263
293
  // status probe refreshes (and rewrites) `engine/.git/index`, making this
264
294
  // command a second writer of the primary checkout and invalidating a
265
295
  // concurrent `fireforge test`'s engine fingerprint. A private index keeps
266
296
  // every refresh off the primary checkout.
267
- const health = await withPrivateGitIndex(getProjectPaths(projectRoot).engine, () => collectPatchQueueHealth(projectRoot));
297
+ const health = await withPrivateGitIndex(getProjectPaths(projectRoot).engine, () => collectPatchQueueHealth(projectRoot, scope));
268
298
  if (!health.hasPatchesDirectory) {
269
299
  info('No patches directory. Nothing to verify.');
270
300
  outro('Verify clean');
@@ -298,7 +328,8 @@ export function registerVerify(program, { getProjectRoot, withErrorHandling }) {
298
328
  program
299
329
  .command('verify')
300
330
  .description('Read-only fsck for the patch queue (manifest + cross-patch lint)')
301
- .action(withErrorHandling(async () => {
302
- await verifyCommand(getProjectRoot());
331
+ .option('--patches <names...>', 'Judge only findings that name these patches (filename, stem, order number, manifest name, or slug; repeat or comma-separate). Queue-wide findings stay; unowned worktree changes drop out')
332
+ .action(withErrorHandling(async (options) => {
333
+ await verifyCommand(getProjectRoot(), options);
303
334
  }));
304
335
  }
@@ -95,8 +95,28 @@ export interface BuildBaseline {
95
95
  * replaces coverage with a dropped-path notice, or refuses when the caller
96
96
  * required --extend-coverage. Legacy packageable-only anchors cannot retain
97
97
  * coverage because they cannot prove test scripts and fixtures unchanged.
98
+ *
99
+ * Since 0.50.0 retention is decided per staged path: see
100
+ * {@link BuildBaseline.testCoverageRecords} and
101
+ * {@link BuildBaseline.fullCoverageAnchor}.
98
102
  */
99
103
  testPackagingCoverage?: TestPackagingCoverage;
104
+ /**
105
+ * Per-path anchors of a scoped claim, keyed by the request path in
106
+ * {@link BuildBaseline.testPackagingCoverage}. Each record holds that
107
+ * path's own staging inputs and their content at the build that staged
108
+ * it, so a later scoped build keeps the path while those inputs are
109
+ * unchanged, whatever moved elsewhere in the engine. A listed path without
110
+ * a record falls back to the baseline-wide anchor. Absent on full claims.
111
+ */
112
+ testCoverageRecords?: Record<string, CoverageRecord>;
113
+ /**
114
+ * The anchor of the last full-coverage build, carried forward by scoped
115
+ * builds once something outside their scope moved. A request path outside
116
+ * the scoped list stays covered while its own staging inputs are unchanged
117
+ * against this anchor. The check runs at dispatch, per requested path.
118
+ */
119
+ fullCoverageAnchor?: CoverageAnchor;
100
120
  /**
101
121
  * Hex-encoded SHA-256 of `engine/mozconfig` as it stood for this build.
102
122
  * Recorded because the mozconfig is regenerated from project-side
@@ -141,5 +161,31 @@ export interface StaticComponentsBaseline {
141
161
  */
142
162
  fingerprints: Record<string, string>;
143
163
  }
164
+ /**
165
+ * Content anchor of one packaging claim. A path's staged content is
166
+ * `fingerprints[path]` when recorded, otherwise its content at
167
+ * `engineHeadSha`.
168
+ */
169
+ export interface CoverageAnchor {
170
+ /** Engine HEAD the staged content is relative to. */
171
+ engineHeadSha: string;
172
+ /** Hex SHA-256 of `engine/mozconfig` the claim was configured with. */
173
+ mozconfigHash: string;
174
+ /**
175
+ * Hex SHA-256 (or {@link DELETED_FILE_FINGERPRINT}) per engine-relative
176
+ * path whose staged content differs from `engineHeadSha`.
177
+ */
178
+ fingerprints: Record<string, string>;
179
+ }
180
+ /** Anchor of one staged request path. See {@link BuildBaseline.testCoverageRecords}. */
181
+ export interface CoverageRecord extends CoverageAnchor {
182
+ /**
183
+ * Engine-relative paths (files or directories) whose content decides what
184
+ * the harness stages for the request path: its manifest directory plus
185
+ * whatever a manifest there names outside it. `fingerprints` is
186
+ * restricted to these.
187
+ */
188
+ inputs: string[];
189
+ }
144
190
  /** Coverage claim of the packaged test runtime. See {@link BuildBaseline.testPackagingCoverage}. */
145
191
  export type TestPackagingCoverage = 'full' | string[];
@@ -18,6 +18,20 @@ function isFingerprintMap(value) {
18
18
  return (isObject(value) &&
19
19
  Object.entries(value).every(([path, fingerprint]) => isBaselinePath(path) && isFingerprint(fingerprint)));
20
20
  }
21
+ function isCoverageAnchor(value) {
22
+ return (isObject(value) &&
23
+ typeof value['engineHeadSha'] === 'string' &&
24
+ isFingerprint(value['mozconfigHash']) &&
25
+ isFingerprintMap(value['fingerprints']));
26
+ }
27
+ function isCoverageRecordMap(value) {
28
+ return (isObject(value) &&
29
+ Object.entries(value).every(([path, record]) => isBaselinePath(path) &&
30
+ isCoverageAnchor(record) &&
31
+ isObject(record) &&
32
+ Array.isArray(record['inputs']) &&
33
+ record['inputs'].every(isBaselinePath)));
34
+ }
21
35
  /** Validates persisted fields without converting malformed coverage into a full claim. */
22
36
  export function isBuildBaseline(value) {
23
37
  if (!isObject(value))
@@ -45,6 +59,11 @@ export function isBuildBaseline(value) {
45
59
  return false;
46
60
  if (value['recordedBy'] !== undefined && typeof value['recordedBy'] !== 'string')
47
61
  return false;
62
+ if (value['testCoverageRecords'] !== undefined &&
63
+ !isCoverageRecordMap(value['testCoverageRecords']))
64
+ return false;
65
+ if (value['fullCoverageAnchor'] !== undefined && !isCoverageAnchor(value['fullCoverageAnchor']))
66
+ return false;
48
67
  const components = value['staticComponentsBaseline'];
49
68
  return (components === undefined ||
50
69
  (isObject(components) &&
@@ -15,7 +15,7 @@
15
15
  * on successful build completion. A failed build does not update it, so a
16
16
  * subsequent run still audits against the last known-good tree.
17
17
  */
18
- import { type BuildBaseline, type TestPackagingCoverage } from './build-baseline-types.js';
18
+ import { type BuildBaseline, type CoverageAnchor, type CoverageRecord, type TestPackagingCoverage } from './build-baseline-types.js';
19
19
  /**
20
20
  * Which mach build produced the baseline being written. Decides whether
21
21
  * the `jar.mn` half of {@link BuildBaseline.buildInputFingerprints} is
@@ -52,6 +52,10 @@ export interface WriteBuildBaselineOptions {
52
52
  * Omitted → field left off the marker.
53
53
  */
54
54
  testPackagingCoverage?: TestPackagingCoverage | undefined;
55
+ /** Per-path anchors of a scoped claim. Omitted → field left off the marker. */
56
+ testCoverageRecords?: Record<string, CoverageRecord> | undefined;
57
+ /** Carried full-build anchor of a narrowed claim. Omitted → field left off. */
58
+ fullCoverageAnchor?: CoverageAnchor | undefined;
55
59
  /**
56
60
  * The baseline this write replaces, when the caller has it. A scoped
57
61
  * (non-`'full'`) write carries its `staticComponentsBaseline` forward