@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
@@ -1,5 +1,5 @@
1
1
  import { GeneralError, InvalidArgumentError } from '../errors/base.js';
2
- const ALL_CLASSIFICATIONS = [
2
+ const ALL_CHECK_CLASSES = [
3
3
  'patch-backed',
4
4
  'patch-owned-drift',
5
5
  'unmanaged',
@@ -7,12 +7,15 @@ const ALL_CLASSIFICATIONS = [
7
7
  'furnace',
8
8
  'conflict',
9
9
  'binary-unsupported',
10
+ 'ownership-conflict',
10
11
  ];
11
12
  /**
12
13
  * Default `--check` policy: everything outside {patch-backed, branding,
13
14
  * furnace, binary-unsupported} fails. `binary-unsupported` is excluded so
14
15
  * an honest "cannot compare" does not keep the gate permanently red.
15
16
  * Strict CI can opt in via `--fail-on binary-unsupported`.
17
+ * `ownership-conflict` is opt-in too: the human `--ownership` mode already
18
+ * fails on it, and adding it here would change the default gate.
16
19
  */
17
20
  const DEFAULT_FAIL_CLASSIFICATIONS = [
18
21
  'unmanaged',
@@ -20,13 +23,19 @@ const DEFAULT_FAIL_CLASSIFICATIONS = [
20
23
  'conflict',
21
24
  ];
22
25
  /**
23
- * Resolves the enforcement policy from the raw flags: `--fail-on` implies
24
- * `--check` and replaces the default set. Both are refused alongside the
25
- * modes that skip (or elide) classification.
26
+ * Resolves the enforcement policy from the raw flags. `--fail-on`,
27
+ * `--allow` and `--fail-on-unknown` each imply `--check`. `--fail-on`
28
+ * replaces the default set and `--allow` subtracts from it (naming one
29
+ * class in both is refused). `--fail-on-unknown` needs `--allow`: without
30
+ * an allow list every class the caller did not name, `patch-backed`
31
+ * included, would fail. All of them are refused alongside the modes that
32
+ * skip (or elide) classification.
26
33
  */
27
34
  export function resolveStatusCheckPolicy(options) {
28
- const failOnList = parseFailOnClassifications(options.failOn);
29
- const checkEnabled = options.check === true || failOnList !== undefined;
35
+ const failOnList = parseCheckClassList(options.failOn, '--fail-on');
36
+ const allowList = parseCheckClassList(options.allow, '--allow');
37
+ const failOnUnknown = options.failOnUnknown === true;
38
+ const checkEnabled = options.check === true || failOnList !== undefined || allowList !== undefined || failOnUnknown;
30
39
  if (checkEnabled &&
31
40
  (options.raw === true ||
32
41
  options.unmanaged === true ||
@@ -34,17 +43,32 @@ export function resolveStatusCheckPolicy(options) {
34
43
  options.testCoverage === true)) {
35
44
  throw new InvalidArgumentError('--check cannot be combined with --raw, --unmanaged, --ownership, or --test-coverage. Use the default view or --json.', '--check');
36
45
  }
37
- return { checkEnabled, failOn: failOnList ?? DEFAULT_FAIL_CLASSIFICATIONS };
46
+ if (failOnUnknown && allowList === undefined) {
47
+ throw new InvalidArgumentError('--fail-on-unknown requires --allow naming the classes that may pass. Without it every class you did not name, patch-backed included, would fail.', '--fail-on-unknown');
48
+ }
49
+ const overlap = (failOnList ?? []).filter((entry) => allowList?.includes(entry) === true);
50
+ if (overlap.length > 0) {
51
+ throw new InvalidArgumentError(`--fail-on and --allow both name ${overlap.join(', ')}. A class either fails or is allowed.`, '--allow');
52
+ }
53
+ const base = failOnList ?? DEFAULT_FAIL_CLASSIFICATIONS;
54
+ const policy = {
55
+ checkEnabled,
56
+ failOn: allowList ? base.filter((entry) => !allowList.includes(entry)) : base,
57
+ };
58
+ if (allowList !== undefined)
59
+ policy.allow = allowList;
60
+ if (failOnUnknown)
61
+ policy.failOnUnknown = true;
62
+ return policy;
38
63
  }
39
- function isClassification(value) {
40
- return ALL_CLASSIFICATIONS.includes(value);
64
+ function isCheckClass(value) {
65
+ return ALL_CHECK_CLASSES.includes(value);
41
66
  }
42
67
  /**
43
- * Parses a comma-separated `--fail-on` value into a classification list.
44
- * Returns undefined when the flag was not given (the caller then applies
45
- * the default policy only under `--check`).
68
+ * Parses a comma-separated `--fail-on`/`--allow` value into a class list.
69
+ * Returns undefined when the flag was not given.
46
70
  */
47
- function parseFailOnClassifications(raw) {
71
+ function parseCheckClassList(raw, flag) {
48
72
  if (raw === undefined)
49
73
  return undefined;
50
74
  const parts = raw
@@ -52,52 +76,83 @@ function parseFailOnClassifications(raw) {
52
76
  .map((part) => part.trim())
53
77
  .filter((part) => part.length > 0);
54
78
  if (parts.length === 0) {
55
- throw new InvalidArgumentError(`--fail-on requires at least one classification. Valid: ${ALL_CLASSIFICATIONS.join(', ')}.`, '--fail-on');
79
+ throw new InvalidArgumentError(`${flag} requires at least one classification. Valid: ${ALL_CHECK_CLASSES.join(', ')}.`, flag);
56
80
  }
57
- const classifications = [];
81
+ const classes = [];
58
82
  for (const part of parts) {
59
- if (!isClassification(part)) {
60
- throw new InvalidArgumentError(`Unknown --fail-on classification "${part}". Valid: ${ALL_CLASSIFICATIONS.join(', ')}.`, '--fail-on');
83
+ if (!isCheckClass(part)) {
84
+ throw new InvalidArgumentError(`Unknown ${flag} classification "${part}". Valid: ${ALL_CHECK_CLASSES.join(', ')}.`, flag);
61
85
  }
62
- if (!classifications.includes(part))
63
- classifications.push(part);
86
+ if (!classes.includes(part))
87
+ classes.push(part);
64
88
  }
65
- return classifications;
89
+ return classes;
90
+ }
91
+ /**
92
+ * True when the policy can fail on `ownership-conflict`, so the caller has
93
+ * to build the ownership rows even without `--include-ownership`.
94
+ */
95
+ export function policyNeedsOwnership(policy) {
96
+ if (!policy.checkEnabled)
97
+ return false;
98
+ if (policy.failOn.includes('ownership-conflict'))
99
+ return true;
100
+ return policy.failOnUnknown === true && policy.allow?.includes('ownership-conflict') !== true;
66
101
  }
67
102
  const CHECK_FILE_PREVIEW_MAX = 3;
68
103
  /**
69
- * Collects the fail-set classifications that have offending files, in
70
- * policy order with full file lists. Shared by the human `--check`
71
- * verdict and the `--json --summary` payload so the two views can never
72
- * disagree about what failed.
104
+ * Collects the failing classes that have offending files, with full file
105
+ * lists: the fail set in policy order, then (under `--fail-on-unknown`)
106
+ * every other present class neither list names, in first-seen order.
107
+ * Shared by the human `--check` verdict and the JSON `check` block so the
108
+ * two views can never disagree about what failed.
109
+ *
110
+ * @param classified - Classified worktree entries
111
+ * @param policy - Resolved policy
112
+ * @param ownershipConflicts - Paths claimed by more than one patch. Only
113
+ * read for the `ownership-conflict` class
73
114
  */
74
- export function collectStatusCheckOffenders(classified, policy) {
115
+ export function collectStatusCheckOffenders(classified, policy, ownershipConflicts = []) {
116
+ const filesOf = (checkClass) => checkClass === 'ownership-conflict'
117
+ ? [...ownershipConflicts]
118
+ : classified.filter((entry) => entry.classification === checkClass).map((e) => e.file);
75
119
  const offenders = [];
76
- for (const classification of policy.failOn) {
77
- const files = classified
78
- .filter((entry) => entry.classification === classification)
79
- .map((entry) => entry.file);
120
+ for (const checkClass of policy.failOn) {
121
+ const files = filesOf(checkClass);
80
122
  if (files.length === 0)
81
123
  continue;
82
- offenders.push({ classification, count: files.length, files });
124
+ offenders.push({ classification: checkClass, count: files.length, files });
125
+ }
126
+ if (policy.failOnUnknown !== true)
127
+ return offenders;
128
+ const named = new Set([...policy.failOn, ...(policy.allow ?? [])]);
129
+ const present = [...new Set(classified.map((e) => e.classification))];
130
+ if (ownershipConflicts.length > 0)
131
+ present.push('ownership-conflict');
132
+ for (const checkClass of present) {
133
+ if (named.has(checkClass))
134
+ continue;
135
+ const files = filesOf(checkClass);
136
+ offenders.push({ classification: checkClass, count: files.length, files, unnamed: true });
83
137
  }
84
138
  return offenders;
85
139
  }
86
140
  /**
87
- * Throws a GeneralError (exit 1) when the policy is enabled and any of
88
- * its classifications is non-empty, naming each offending classification
89
- * with its first few files. A disabled policy or a fully tool-covered
90
- * tree passes silently.
141
+ * Throws a GeneralError (exit 1) when the policy is enabled and any
142
+ * failing class is non-empty, naming each offending class with its first
143
+ * few files. A disabled policy or a fully tool-covered tree passes
144
+ * silently.
91
145
  */
92
- export function runStatusCheck(classified, policy) {
146
+ export function runStatusCheck(classified, policy, ownershipConflicts = []) {
93
147
  if (!policy.checkEnabled)
94
148
  return;
95
- const offending = collectStatusCheckOffenders(classified, policy).map((offender) => {
149
+ const offending = collectStatusCheckOffenders(classified, policy, ownershipConflicts).map((offender) => {
96
150
  const preview = offender.files.slice(0, CHECK_FILE_PREVIEW_MAX).join(', ');
97
151
  const more = offender.count > CHECK_FILE_PREVIEW_MAX
98
152
  ? `, +${offender.count - CHECK_FILE_PREVIEW_MAX} more`
99
153
  : '';
100
- return `${offender.count} ${offender.classification} (${preview}${more})`;
154
+ const unnamed = offender.unnamed === true ? ' [named by neither --fail-on nor --allow]' : '';
155
+ return `${offender.count} ${offender.classification}${unnamed} (${preview}${more})`;
101
156
  });
102
157
  if (offending.length === 0)
103
158
  return;
@@ -9,24 +9,37 @@
9
9
  * `files[]` entirely, because engine-clean gates need the verdict,
10
10
  * per-class counts, and offender names, not a payload that grows with
11
11
  * the queue (175 KB+ on a large one).
12
+ *
13
+ * Both carry the `check` block when the `--check` policy is active, and the
14
+ * `scope` block under `--patches`.
12
15
  */
13
16
  import type { ClassifiedFile } from '../core/status-classify.js';
14
17
  import { type StatusCheckPolicy } from './status-check.js';
15
18
  import type { OwnershipJsonBlock } from './status-ownership.js';
19
+ import type { StatusScope } from './status-scope.js';
20
+ /** What a JSON payload adds beside the classification counts. */
21
+ export interface StatusJsonContext {
22
+ policy: StatusCheckPolicy;
23
+ /** Paths claimed by more than one patch, for the `ownership-conflict` class. */
24
+ ownershipConflicts: readonly string[];
25
+ /** The `--include-ownership` block, when asked for. */
26
+ ownership?: OwnershipJsonBlock | undefined;
27
+ /** The `--patches` scope, when given. */
28
+ scope?: StatusScope | undefined;
29
+ }
16
30
  /**
17
- * Full `--json` payload: summary plus the per-file `files[]` list, and,
18
- * under `--include-ownership`, the additive `ownership` block.
19
- * Additive within schemaVersion 1: a consumer that does not ask for the
31
+ * Full `--json` payload: summary plus the per-file `files[]` list, and the
32
+ * additive `check`, `scope` and `ownership` blocks when their flags are
33
+ * set. Additive within schemaVersion 1: a consumer that does not ask for a
20
34
  * block never sees the key.
21
35
  */
22
- export declare function renderJsonStatus(classified: readonly ClassifiedFile[], ownership?: OwnershipJsonBlock): void;
36
+ export declare function renderJsonStatus(classified: readonly ClassifiedFile[], context: StatusJsonContext): void;
23
37
  /**
24
38
  * Summary-only `--json --summary` payload. Counts are always present. When
25
39
  * the `--check`/`--fail-on` policy is active, the payload adds a `check`
26
40
  * block with the active fail set, the verdict, and the offending files per
27
- * fail-set classification (sourced from the same collector as the human
28
- * `--check` message, so the two views can never disagree). The absent
29
- * `files[]` is the mode signal within schemaVersion 1. Consumers opt in via
30
- * the flag.
41
+ * failing class (sourced from the same collector as the human `--check`
42
+ * message, so the two views can never disagree). The absent `files[]` is
43
+ * the mode signal within schemaVersion 1. Consumers opt in via the flag.
31
44
  */
32
- export declare function renderJsonSummaryStatus(classified: readonly ClassifiedFile[], policy: StatusCheckPolicy, ownership?: OwnershipJsonBlock): void;
45
+ export declare function renderJsonSummaryStatus(classified: readonly ClassifiedFile[], context: StatusJsonContext): void;
@@ -16,12 +16,45 @@ function countByClassification(classified) {
16
16
  return byClassification;
17
17
  }
18
18
  /**
19
- * Full `--json` payload: summary plus the per-file `files[]` list, and,
20
- * under `--include-ownership`, the additive `ownership` block.
21
- * Additive within schemaVersion 1: a consumer that does not ask for the
19
+ * Builds the `check` block, or undefined when no policy is active.
20
+ * `allow` and `failOnUnknown` appear only when the caller set them, so a
21
+ * consumer that never passes those flags sees the block it always did.
22
+ */
23
+ function buildCheckBlock(classified, context) {
24
+ const { policy } = context;
25
+ if (!policy.checkEnabled)
26
+ return undefined;
27
+ const offenders = collectStatusCheckOffenders(classified, policy, context.ownershipConflicts);
28
+ return {
29
+ enabled: true,
30
+ failOn: policy.failOn,
31
+ ...(policy.allow !== undefined ? { allow: policy.allow } : {}),
32
+ ...(policy.failOnUnknown === true ? { failOnUnknown: true } : {}),
33
+ failed: offenders.length > 0,
34
+ offenders,
35
+ };
36
+ }
37
+ /** The additive tail both payloads share: check, scope, ownership. */
38
+ function buildTrailingBlocks(classified, context) {
39
+ const check = buildCheckBlock(classified, context);
40
+ return {
41
+ ...(check !== undefined ? { check } : {}),
42
+ ...(context.scope !== undefined ? { scope: context.scope } : {}),
43
+ ...(context.ownership !== undefined ? { ownership: context.ownership } : {}),
44
+ };
45
+ }
46
+ function writePayload(output) {
47
+ process.stdout.write(JSON.stringify(output, null, 2) + '\n');
48
+ // Payload written: stdout is now spoken for. See docs/machine-output.md.
49
+ setStdoutSealed(true);
50
+ }
51
+ /**
52
+ * Full `--json` payload: summary plus the per-file `files[]` list, and the
53
+ * additive `check`, `scope` and `ownership` blocks when their flags are
54
+ * set. Additive within schemaVersion 1: a consumer that does not ask for a
22
55
  * block never sees the key.
23
56
  */
24
- export function renderJsonStatus(classified, ownership) {
57
+ export function renderJsonStatus(classified, context) {
25
58
  const outputFiles = classified.map((f) => {
26
59
  const entry = {
27
60
  file: f.file,
@@ -34,49 +67,31 @@ export function renderJsonStatus(classified, ownership) {
34
67
  }
35
68
  return entry;
36
69
  });
37
- const output = {
70
+ writePayload({
38
71
  schemaVersion: 1,
39
72
  summary: {
40
73
  total: outputFiles.length,
41
74
  byClassification: countByClassification(classified),
42
75
  },
43
76
  files: outputFiles,
44
- ...(ownership !== undefined ? { ownership } : {}),
45
- };
46
- process.stdout.write(JSON.stringify(output, null, 2) + '\n');
47
- // Payload written: stdout is now spoken for. See docs/machine-output.md.
48
- setStdoutSealed(true);
77
+ ...buildTrailingBlocks(classified, context),
78
+ });
49
79
  }
50
80
  /**
51
81
  * Summary-only `--json --summary` payload. Counts are always present. When
52
82
  * the `--check`/`--fail-on` policy is active, the payload adds a `check`
53
83
  * block with the active fail set, the verdict, and the offending files per
54
- * fail-set classification (sourced from the same collector as the human
55
- * `--check` message, so the two views can never disagree). The absent
56
- * `files[]` is the mode signal within schemaVersion 1. Consumers opt in via
57
- * the flag.
84
+ * failing class (sourced from the same collector as the human `--check`
85
+ * message, so the two views can never disagree). The absent `files[]` is
86
+ * the mode signal within schemaVersion 1. Consumers opt in via the flag.
58
87
  */
59
- export function renderJsonSummaryStatus(classified, policy, ownership) {
60
- const output = {
88
+ export function renderJsonSummaryStatus(classified, context) {
89
+ writePayload({
61
90
  schemaVersion: 1,
62
91
  summary: {
63
92
  total: classified.length,
64
93
  byClassification: countByClassification(classified),
65
94
  },
66
- };
67
- if (policy.checkEnabled) {
68
- const offenders = collectStatusCheckOffenders(classified, policy);
69
- output.check = {
70
- enabled: true,
71
- failOn: policy.failOn,
72
- failed: offenders.length > 0,
73
- offenders,
74
- };
75
- }
76
- if (ownership !== undefined) {
77
- output.ownership = ownership;
78
- }
79
- process.stdout.write(JSON.stringify(output, null, 2) + '\n');
80
- // Payload written: stdout is now spoken for. See docs/machine-output.md.
81
- setStdoutSealed(true);
95
+ ...buildTrailingBlocks(classified, context),
96
+ });
82
97
  }
@@ -220,6 +220,8 @@ export function renderTestCoverageStatus(baseline) {
220
220
  }
221
221
  const list = coverage.map((path) => ` - ${path}`).join('\n');
222
222
  info(`Test packaging coverage: scoped to ${coverage.length} path(s):\n${list}\n` +
223
- 'A "fireforge test" over paths outside this list will be refused as uncovered.');
223
+ (baseline.fullCoverageAnchor === undefined
224
+ ? 'A "fireforge test" over paths outside this list will be refused as uncovered.'
225
+ : 'A "fireforge test" over paths outside this list stays covered by the earlier full build while their own staging inputs are unchanged, and is refused otherwise.'));
224
226
  outro(`Coverage: scoped (${coverage.length} paths)`);
225
227
  }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * `status --patches <list>`: judges only the named patches' files. Split
3
+ * out of status.ts, which sits at the max-lines budget.
4
+ *
5
+ * On a shared engine a peer's unexported work keeps a tree-wide gate red
6
+ * for everyone. The scope keeps a file when the patch that owns it (or,
7
+ * for a conflict, any patch that claims it) is named, and an ownership
8
+ * row when any of its owners is named. An unmanaged file has no owning
9
+ * patch, so no scope covers it: scoped runs never judge unexported files.
10
+ * That is the point of the scope, and also its limit, which the docs say.
11
+ */
12
+ import type { OwnershipRow } from '../core/ownership-table.js';
13
+ import type { ClassifiedFile, StatusFile } from '../core/status-classify.js';
14
+ /** The resolved `--patches` scope: manifest filenames, in request order. */
15
+ export interface StatusScope {
16
+ patches: string[];
17
+ }
18
+ /**
19
+ * Resolves raw `--patches` values against the manifest. Returns undefined
20
+ * when the flag was not given, and refuses a name that matches no patch
21
+ * (so a typo fails loud instead of judging nothing).
22
+ *
23
+ * @param patchesDir - Absolute path to the patches directory
24
+ * @param requested - Raw `--patches` values (each may be a comma list)
25
+ */
26
+ export declare function resolveStatusScope(patchesDir: string, requested: readonly string[] | undefined): Promise<StatusScope | undefined>;
27
+ /** Keeps only the classified entries the scope covers. No scope keeps all. */
28
+ export declare function scopeClassified(classified: ClassifiedFile[], scope: StatusScope | undefined): ClassifiedFile[];
29
+ /** Keeps only the ownership rows with an owner in scope. No scope keeps all. */
30
+ export declare function scopeOwnershipRows(rows: OwnershipRow[], scope: StatusScope | undefined): OwnershipRow[];
31
+ /**
32
+ * Builds the ownership rows from the full scan (classification needs every
33
+ * file to find claims), then narrows them to the scope.
34
+ */
35
+ export declare function collectScopedOwnershipRows(patchesDir: string, files: StatusFile[], classified: readonly ClassifiedFile[], scope: StatusScope | undefined): Promise<OwnershipRow[]>;
36
+ /** The paths the `ownership-conflict` check class judges. */
37
+ export declare function ownershipConflictPaths(rows: readonly OwnershipRow[]): string[];
@@ -0,0 +1,54 @@
1
+ import { loadPatchesManifest } from '../core/patch-manifest.js';
2
+ import { selectPatchSubset } from '../core/patch-subset.js';
3
+ import { GeneralError } from '../errors/base.js';
4
+ import { collectOwnershipRows } from './status-ownership.js';
5
+ /**
6
+ * Resolves raw `--patches` values against the manifest. Returns undefined
7
+ * when the flag was not given, and refuses a name that matches no patch
8
+ * (so a typo fails loud instead of judging nothing).
9
+ *
10
+ * @param patchesDir - Absolute path to the patches directory
11
+ * @param requested - Raw `--patches` values (each may be a comma list)
12
+ */
13
+ export async function resolveStatusScope(patchesDir, requested) {
14
+ if (requested === undefined || requested.length === 0)
15
+ return undefined;
16
+ const manifest = await loadPatchesManifest(patchesDir);
17
+ if (!manifest || manifest.patches.length === 0) {
18
+ throw new GeneralError('--patches needs a patch queue, but patches.json lists no patches.');
19
+ }
20
+ const subset = selectPatchSubset(manifest, requested, '--patches selects patches (filename, stem, order number, manifest name, or slug), not engine files.');
21
+ return { patches: subset.map((patch) => patch.filename) };
22
+ }
23
+ /** True when the entry's owner, or any patch claiming it, is in scope. */
24
+ function isInScope(entry, names) {
25
+ if (entry.owner !== undefined && names.has(entry.owner))
26
+ return true;
27
+ return entry.claimedBy?.some((patch) => names.has(patch)) === true;
28
+ }
29
+ /** Keeps only the classified entries the scope covers. No scope keeps all. */
30
+ export function scopeClassified(classified, scope) {
31
+ if (scope === undefined)
32
+ return classified;
33
+ const names = new Set(scope.patches);
34
+ return classified.filter((entry) => isInScope(entry, names));
35
+ }
36
+ /** Keeps only the ownership rows with an owner in scope. No scope keeps all. */
37
+ export function scopeOwnershipRows(rows, scope) {
38
+ if (scope === undefined)
39
+ return rows;
40
+ const names = new Set(scope.patches);
41
+ return rows.filter((row) => row.owners.some((owner) => names.has(owner)));
42
+ }
43
+ /**
44
+ * Builds the ownership rows from the full scan (classification needs every
45
+ * file to find claims), then narrows them to the scope.
46
+ */
47
+ export async function collectScopedOwnershipRows(patchesDir, files, classified, scope) {
48
+ const rows = await collectOwnershipRows(patchesDir, (await loadPatchesManifest(patchesDir))?.patches ?? [], files, classified);
49
+ return scopeOwnershipRows(rows, scope);
50
+ }
51
+ /** The paths the `ownership-conflict` check class judges. */
52
+ export function ownershipConflictPaths(rows) {
53
+ return rows.filter((row) => row.conflict).map((row) => row.path);
54
+ }
@@ -8,17 +8,17 @@ import { collectFurnaceManagedPrefixes } from '../core/furnace-config.js';
8
8
  import { getHead, getStatusWithCodes, isGitRepository, isMissingHeadError } from '../core/git.js';
9
9
  import { listUntrackedFilesInDirs, resolveMaxUntrackedFilesPerDir } from '../core/git-status.js';
10
10
  import { renderOwnershipTable } from '../core/ownership-table.js';
11
- import { loadPatchesManifest } from '../core/patch-manifest.js';
12
11
  import { readProcessCpuSeconds } from '../core/process-cpu-time.js';
13
12
  import { classifyFiles } from '../core/status-classify.js';
14
13
  import { GeneralError, InvalidArgumentError } from '../errors/base.js';
15
14
  import { FIREFORGE_TMP_PATH_PATTERN, pathExists } from '../utils/fs.js';
16
15
  import { info, intro, isMachineOutputMode, outro, setMachineOutputMode, warn, } from '../utils/logger.js';
17
16
  import { emitMachineError } from '../utils/machine-output.js';
18
- import { resolveStatusCheckPolicy, runStatusCheck, } from './status-check.js';
17
+ import { policyNeedsOwnership, resolveStatusCheckPolicy, runStatusCheck, } from './status-check.js';
19
18
  import { renderJsonStatus, renderJsonSummaryStatus } from './status-json.js';
20
19
  import { renderDefaultStatus, renderTestCoverageStatus, renderUnmanagedOnly, } from './status-output.js';
21
- import { buildOwnershipJsonBlock, collectOwnershipRows, summarizeOwnership, } from './status-ownership.js';
20
+ import { buildOwnershipJsonBlock, summarizeOwnership } from './status-ownership.js';
21
+ import { collectScopedOwnershipRows, ownershipConflictPaths, resolveStatusScope, scopeClassified, } from './status-scope.js';
22
22
  /**
23
23
  * Renders raw worktree status as machine-parseable porcelain-style output.
24
24
  * Each line is: STATUS<tab>FILE
@@ -121,26 +121,47 @@ async function scanEngineStatusFiles(engineDir, requireGitRepository) {
121
121
  * policy, which stays the only exit driver on this path.
122
122
  *
123
123
  * `--include-ownership` is a modifier rather than a mode: it appends an
124
- * ownership block without touching exit semantics, so an ownership
125
- * conflict still fails only the human `--ownership` mode.
124
+ * ownership block without touching exit semantics. Ownership conflicts
125
+ * fail this path only when the policy names `ownership-conflict` (or
126
+ * leaves it unnamed under `--fail-on-unknown`).
126
127
  *
127
- * @param classified - Classified worktree entries
128
+ * @param classified - Classified worktree entries, unscoped
128
129
  * @param files - The same entries pre-classification, for ownership rows
129
130
  * @param paths - Resolved project paths
130
131
  * @param options - Status display options
131
132
  * @param checkPolicy - Resolved `--check`/`--fail-on` policy
133
+ * @param scope - The `--patches` scope, when given
132
134
  */
133
- async function renderJsonMode(classified, files, paths, options, checkPolicy) {
134
- const ownership = options.includeOwnership === true
135
- ? buildOwnershipJsonBlock(await collectOwnershipRows(paths.patches, (await loadPatchesManifest(paths.patches))?.patches ?? [], files, classified))
136
- : undefined;
135
+ async function renderJsonMode(classified, files, paths, options, checkPolicy, scope) {
136
+ const rows = options.includeOwnership === true || policyNeedsOwnership(checkPolicy)
137
+ ? await collectScopedOwnershipRows(paths.patches, files, classified, scope)
138
+ : [];
139
+ const context = {
140
+ policy: checkPolicy,
141
+ ownershipConflicts: ownershipConflictPaths(rows),
142
+ ownership: options.includeOwnership === true ? buildOwnershipJsonBlock(rows) : undefined,
143
+ scope,
144
+ };
145
+ const scoped = scopeClassified(classified, scope);
137
146
  if (options.summary === true) {
138
- renderJsonSummaryStatus(classified, checkPolicy, ownership);
147
+ renderJsonSummaryStatus(scoped, context);
139
148
  }
140
149
  else {
141
- renderJsonStatus(classified, ownership);
150
+ renderJsonStatus(scoped, context);
142
151
  }
143
- runStatusCheck(classified, checkPolicy);
152
+ runStatusCheck(scoped, checkPolicy, context.ownershipConflicts);
153
+ }
154
+ /**
155
+ * Applies the `--check` policy to the human default view. Builds the
156
+ * ownership rows only when the policy can fail on `ownership-conflict`,
157
+ * which it can on a clean tree too (two patches claiming one path need no
158
+ * worktree change).
159
+ */
160
+ async function runHumanStatusCheck(classified, files, paths, checkPolicy, scope) {
161
+ const conflicts = policyNeedsOwnership(checkPolicy)
162
+ ? ownershipConflictPaths(await collectScopedOwnershipRows(paths.patches, files, classified, scope))
163
+ : [];
164
+ runStatusCheck(scopeClassified(classified, scope), checkPolicy, conflicts);
144
165
  }
145
166
  /**
146
167
  * Detects the "unborn HEAD" aftermath of an interrupted `fireforge download`:
@@ -200,8 +221,12 @@ export async function statusCommand(projectRoot, options = {}) {
200
221
  setMachineOutputMode(previousMachineOutputMode);
201
222
  }
202
223
  }
203
- /** The status body proper. The machine-mode lifecycle lives in {@link statusCommand}. */
204
- async function runStatusCommandBody(projectRoot, options) {
224
+ /**
225
+ * Refuses flag combinations no view can honour, before any output: one
226
+ * mode at most, and the JSON-only and scope modifiers only where they mean
227
+ * something.
228
+ */
229
+ function assertStatusFlagCombination(options) {
205
230
  const modeCount = [
206
231
  options.raw,
207
232
  options.unmanaged,
@@ -224,6 +249,18 @@ async function runStatusCommandBody(projectRoot, options) {
224
249
  if (options.includeOwnership === true && options.json !== true) {
225
250
  throw new InvalidArgumentError('--include-ownership requires --json. Use --ownership for the human table.', '--include-ownership');
226
251
  }
252
+ // --patches scopes the classifying views and the ownership table only.
253
+ if (options.patches !== undefined &&
254
+ (options.raw === true ||
255
+ options.unmanaged === true ||
256
+ options.testCoverage === true ||
257
+ options.lock === true)) {
258
+ throw new InvalidArgumentError('--patches cannot be combined with --raw, --unmanaged, --test-coverage, or --lock.', '--patches');
259
+ }
260
+ }
261
+ /** The status body proper. The machine-mode lifecycle lives in {@link statusCommand}. */
262
+ async function runStatusCommandBody(projectRoot, options) {
263
+ assertStatusFlagCombination(options);
227
264
  // --check / --fail-on enforcement policy. Applies only
228
265
  // where classification runs (the default view and --json).
229
266
  const checkPolicy = resolveStatusCheckPolicy(options);
@@ -253,6 +290,7 @@ async function runStatusCommandBody(projectRoot, options) {
253
290
  }
254
291
  const paths = getProjectPaths(projectRoot);
255
292
  const config = await loadConfig(projectRoot);
293
+ const scope = await resolveStatusScope(paths.patches, options.patches);
256
294
  // Shared envelope: see src/utils/machine-output.ts and
257
295
  // docs/machine-output.md. `status` was the only command that had one.
258
296
  // Ownership mode is a flat file→patch table. Sources are the manifest's
@@ -265,13 +303,12 @@ async function runStatusCommandBody(projectRoot, options) {
265
303
  // Rung 1 only. See the note below on why this branch does not take the
266
304
  // full assertEngineGitReady ladder.
267
305
  await assertEngineExists(paths.engine);
268
- const manifest = await loadPatchesManifest(paths.patches);
269
306
  // The same scan and the same classification pass as every other mode.
270
307
  // This branch keeps its historical guard shape on purpose:
271
308
  // no baseline-commit assertion, and a non-git engine degrades to an
272
309
  // empty table rather than the recovery banner.
273
310
  const rawFilesOwnership = await scanEngineStatusFiles(paths.engine, false);
274
- const rows = await collectOwnershipRows(paths.patches, manifest?.patches ?? [], rawFilesOwnership, await classifyStatusFiles(rawFilesOwnership, paths, projectRoot, config.binaryName));
311
+ const rows = await collectScopedOwnershipRows(paths.patches, rawFilesOwnership, await classifyStatusFiles(rawFilesOwnership, paths, projectRoot, config.binaryName), scope);
275
312
  renderOwnershipTable(rows);
276
313
  const { managed, unmanaged, conflicts } = summarizeOwnership(rows);
277
314
  const parts = [`${managed} managed`];
@@ -313,7 +350,7 @@ async function runStatusCommandBody(projectRoot, options) {
313
350
  // `[]` here and return before the human fallback.
314
351
  if (options.json) {
315
352
  const classified = await classifyStatusFiles(files, paths, projectRoot, config.binaryName);
316
- await renderJsonMode(classified, files, paths, options, checkPolicy);
353
+ await renderJsonMode(classified, files, paths, options, checkPolicy, scope);
317
354
  return;
318
355
  }
319
356
  // `--raw` consumers parse the native `git status --porcelain` output
@@ -326,6 +363,7 @@ async function runStatusCommandBody(projectRoot, options) {
326
363
  if (files.length === 0) {
327
364
  info('No modified files');
328
365
  outro('Working tree clean');
366
+ await runHumanStatusCheck([], files, paths, checkPolicy, scope);
329
367
  return;
330
368
  }
331
369
  // Raw mode: existing behavior
@@ -333,8 +371,10 @@ async function runStatusCommandBody(projectRoot, options) {
333
371
  renderRawStatus(files);
334
372
  return;
335
373
  }
336
- // Patch-aware classification
337
- const classified = await classifyStatusFiles(files, paths, projectRoot, config.binaryName);
374
+ // Patch-aware classification. The full scan is classified (claims need
375
+ // every file) and the buckets show only what the scope covers.
376
+ const allClassified = await classifyStatusFiles(files, paths, projectRoot, config.binaryName);
377
+ const classified = scopeClassified(allClassified, scope);
338
378
  const buckets = {
339
379
  conflict: classified.filter((f) => f.classification === 'conflict'),
340
380
  unmanaged: classified.filter((f) => f.classification === 'unmanaged'),
@@ -349,8 +389,8 @@ async function runStatusCommandBody(projectRoot, options) {
349
389
  await renderUnmanagedOnly(buckets.unmanaged, files.length, projectRoot, config.binaryName);
350
390
  return;
351
391
  }
352
- await renderDefaultStatus(files.length, buckets, projectRoot, config.binaryName);
353
- runStatusCheck(classified, checkPolicy);
392
+ await renderDefaultStatus(scope ? classified.length : files.length, buckets, projectRoot, config.binaryName);
393
+ await runHumanStatusCheck(allClassified, files, paths, checkPolicy, scope);
354
394
  }
355
395
  /** Registers the status command on the CLI program. */
356
396
  export function registerStatus(program, { getProjectRoot, withErrorHandling }) {
@@ -366,7 +406,10 @@ export function registerStatus(program, { getProjectRoot, withErrorHandling }) {
366
406
  .option('--summary', 'With --json: emit only summary counts (plus offending files when --check/--fail-on is active), omitting the per-file files[] payload')
367
407
  .option('--include-ownership', 'With --json (composes with --summary): append an ownership block (path->owning-patch rows plus managed/unmanaged/conflict counts) to the payload, so one scan serves the classification, ownership, and check views. Does not change exit semantics.')
368
408
  .option('--check', 'Exit non-zero when any unmanaged, patch-owned-drift, or conflict file exists (composes with --json; combine with --fail-on for finer policy)')
369
- .option('--fail-on <classifications>', 'Comma-separated classification list that fails --check, replacing the default set (implies --check). Valid: patch-backed, patch-owned-drift, unmanaged, branding, furnace, conflict, binary-unsupported')
409
+ .option('--fail-on <classifications>', 'Comma-separated classification list that fails --check, replacing the default set (implies --check). Valid: patch-backed, patch-owned-drift, unmanaged, branding, furnace, conflict, binary-unsupported, ownership-conflict (a path claimed by more than one patch)')
410
+ .option('--allow <classifications>', 'Comma-separated classification list that never fails --check, removed from the fail set (implies --check; same values as --fail-on)')
411
+ .option('--fail-on-unknown', 'With --allow: also fail on every present classification that neither --fail-on nor --allow names, so a classification added in a later FireForge fails closed (implies --check)')
412
+ .option('--patches <names...>', 'Judge only the named patches: keeps the files each owns or claims and the ownership rows naming it (filename, stem, order number, manifest name, or slug; repeat or comma-separate). Unmanaged files belong to no patch and drop out. Default view, --json and --ownership only')
370
413
  .action(withErrorHandling(async (options) => {
371
414
  await statusCommand(getProjectRoot(), options);
372
415
  }));