@hominis/fireforge 0.49.1 → 0.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (336) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +1 -1
  3. package/dist/bin/fireforge.js +6 -0
  4. package/dist/build-info.json +4 -4
  5. package/dist/src/commands/build-prelock.d.ts +20 -0
  6. package/dist/src/commands/build-prelock.js +50 -0
  7. package/dist/src/commands/build.d.ts +3 -1
  8. package/dist/src/commands/build.js +13 -6
  9. package/dist/src/commands/check-report.d.ts +28 -0
  10. package/dist/src/commands/check-report.js +78 -0
  11. package/dist/src/commands/check-run.d.ts +28 -0
  12. package/dist/src/commands/check-run.js +104 -0
  13. package/dist/src/commands/check-steps.d.ts +67 -0
  14. package/dist/src/commands/check-steps.js +175 -0
  15. package/dist/src/commands/check.d.ts +32 -0
  16. package/dist/src/commands/check.js +160 -0
  17. package/dist/src/commands/config.js +5 -6
  18. package/dist/src/commands/doctor-engine-eslint.js +6 -1
  19. package/dist/src/commands/doctor-source-pin.js +4 -1
  20. package/dist/src/commands/export-adopt-check.d.ts +38 -0
  21. package/dist/src/commands/export-adopt-check.js +98 -0
  22. package/dist/src/commands/export-flow.js +29 -14
  23. package/dist/src/commands/export-placement-gate.d.ts +6 -2
  24. package/dist/src/commands/export-shared.d.ts +2 -1
  25. package/dist/src/commands/export-staged-registration.d.ts +46 -0
  26. package/dist/src/commands/export-staged-registration.js +91 -0
  27. package/dist/src/commands/forward-import-remedy.d.ts +32 -0
  28. package/dist/src/commands/forward-import-remedy.js +63 -0
  29. package/dist/src/commands/furnace/apply.js +3 -3
  30. package/dist/src/commands/furnace/chrome-doc-remove.d.ts +2 -1
  31. package/dist/src/commands/furnace/chrome-doc-remove.js +2 -2
  32. package/dist/src/commands/furnace/chrome-doc.d.ts +2 -1
  33. package/dist/src/commands/furnace/chrome-doc.js +2 -2
  34. package/dist/src/commands/furnace/create-templates.d.ts +8 -3
  35. package/dist/src/commands/furnace/create-templates.js +8 -23
  36. package/dist/src/commands/furnace/create.js +2 -2
  37. package/dist/src/commands/furnace/deploy-named.d.ts +48 -0
  38. package/dist/src/commands/furnace/deploy-named.js +107 -0
  39. package/dist/src/commands/furnace/deploy.d.ts +3 -2
  40. package/dist/src/commands/furnace/deploy.js +50 -86
  41. package/dist/src/commands/furnace/diff.js +26 -3
  42. package/dist/src/commands/furnace/index.js +66 -41
  43. package/dist/src/commands/furnace/init.js +13 -6
  44. package/dist/src/commands/furnace/override.js +4 -2
  45. package/dist/src/commands/furnace/preview.js +4 -3
  46. package/dist/src/commands/furnace/refresh.js +2 -2
  47. package/dist/src/commands/furnace/remove.js +4 -3
  48. package/dist/src/commands/furnace/rename-dry-run.d.ts +29 -0
  49. package/dist/src/commands/furnace/rename-dry-run.js +127 -0
  50. package/dist/src/commands/furnace/rename-helpers.d.ts +7 -0
  51. package/dist/src/commands/furnace/rename-helpers.js +15 -1
  52. package/dist/src/commands/furnace/rename-registrations.d.ts +41 -0
  53. package/dist/src/commands/furnace/rename-registrations.js +93 -0
  54. package/dist/src/commands/furnace/rename.d.ts +2 -1
  55. package/dist/src/commands/furnace/rename.js +28 -66
  56. package/dist/src/commands/furnace/scan.d.ts +9 -4
  57. package/dist/src/commands/furnace/scan.js +11 -9
  58. package/dist/src/commands/furnace/sync.js +3 -2
  59. package/dist/src/commands/furnace/validate-report.d.ts +45 -0
  60. package/dist/src/commands/furnace/validate-report.js +91 -0
  61. package/dist/src/commands/furnace/validate.d.ts +1 -1
  62. package/dist/src/commands/furnace/validate.js +127 -114
  63. package/dist/src/commands/furnace/validation-output.js +14 -3
  64. package/dist/src/commands/lint-per-patch.js +31 -60
  65. package/dist/src/commands/lint-report.d.ts +8 -4
  66. package/dist/src/commands/lint-report.js +15 -5
  67. package/dist/src/commands/lint.js +15 -34
  68. package/dist/src/commands/manifest.js +2 -0
  69. package/dist/src/commands/patch/delete-gap-notice.js +3 -1
  70. package/dist/src/commands/patch/delete.d.ts +1 -1
  71. package/dist/src/commands/patch/delete.js +13 -98
  72. package/dist/src/commands/patch/index.d.ts +1 -1
  73. package/dist/src/commands/patch/index.js +4 -2
  74. package/dist/src/commands/patch/lint-ignore.d.ts +5 -1
  75. package/dist/src/commands/patch/lint-ignore.js +65 -28
  76. package/dist/src/commands/patch/move-files-create.js +26 -17
  77. package/dist/src/commands/patch/move-files-into.js +5 -3
  78. package/dist/src/commands/patch/move-files.js +3 -1
  79. package/dist/src/commands/patch/removal-dependents.d.ts +63 -0
  80. package/dist/src/commands/patch/removal-dependents.js +133 -0
  81. package/dist/src/commands/patch/rename.js +7 -0
  82. package/dist/src/commands/patch/retire.d.ts +34 -0
  83. package/dist/src/commands/patch/retire.js +128 -0
  84. package/dist/src/commands/patch/split-plan.d.ts +7 -7
  85. package/dist/src/commands/patch/split-plan.js +45 -28
  86. package/dist/src/commands/patch/split-staged.d.ts +108 -0
  87. package/dist/src/commands/patch/split-staged.js +202 -0
  88. package/dist/src/commands/patch/split.d.ts +5 -0
  89. package/dist/src/commands/patch/split.js +51 -15
  90. package/dist/src/commands/patch/staged-dependency-batch.d.ts +48 -0
  91. package/dist/src/commands/patch/staged-dependency-batch.js +176 -0
  92. package/dist/src/commands/patch/staged-dependency-entries.d.ts +36 -0
  93. package/dist/src/commands/patch/staged-dependency-entries.js +52 -0
  94. package/dist/src/commands/patch/staged-dependency-from.d.ts +14 -0
  95. package/dist/src/commands/patch/staged-dependency-from.js +108 -0
  96. package/dist/src/commands/patch/staged-dependency.js +14 -41
  97. package/dist/src/commands/re-export-adjacent-registrations.d.ts +20 -0
  98. package/dist/src/commands/re-export-adjacent-registrations.js +75 -0
  99. package/dist/src/commands/re-export-adjacent.d.ts +45 -2
  100. package/dist/src/commands/re-export-adjacent.js +156 -37
  101. package/dist/src/commands/re-export-drift.d.ts +36 -8
  102. package/dist/src/commands/re-export-drift.js +73 -49
  103. package/dist/src/commands/re-export-files.js +11 -8
  104. package/dist/src/commands/re-export-lint.d.ts +2 -1
  105. package/dist/src/commands/re-export-lint.js +23 -10
  106. package/dist/src/commands/re-export-options.js +45 -4
  107. package/dist/src/commands/re-export-preflight.d.ts +23 -0
  108. package/dist/src/commands/re-export-preflight.js +160 -0
  109. package/dist/src/commands/re-export-register.js +42 -17
  110. package/dist/src/commands/re-export-removed.d.ts +12 -1
  111. package/dist/src/commands/re-export-removed.js +41 -11
  112. package/dist/src/commands/re-export-scan-owner.d.ts +44 -0
  113. package/dist/src/commands/re-export-scan-owner.js +116 -0
  114. package/dist/src/commands/re-export-scan.d.ts +17 -9
  115. package/dist/src/commands/re-export-scan.js +56 -23
  116. package/dist/src/commands/re-export-single.js +22 -6
  117. package/dist/src/commands/re-export-summary.d.ts +28 -0
  118. package/dist/src/commands/re-export-summary.js +36 -0
  119. package/dist/src/commands/re-export.js +37 -24
  120. package/dist/src/commands/rebase/index.js +17 -17
  121. package/dist/src/commands/run.js +16 -2
  122. package/dist/src/commands/status-check.d.ts +55 -16
  123. package/dist/src/commands/status-check.js +92 -37
  124. package/dist/src/commands/status-json.d.ts +22 -9
  125. package/dist/src/commands/status-json.js +47 -32
  126. package/dist/src/commands/status-output.js +3 -1
  127. package/dist/src/commands/status-scope.d.ts +37 -0
  128. package/dist/src/commands/status-scope.js +54 -0
  129. package/dist/src/commands/status.js +66 -23
  130. package/dist/src/commands/test-browser-preflight.js +19 -3
  131. package/dist/src/commands/test-build-furnace.d.ts +21 -0
  132. package/dist/src/commands/test-build-furnace.js +48 -0
  133. package/dist/src/commands/test-build-phase.js +47 -41
  134. package/dist/src/commands/test-checkjs.js +7 -0
  135. package/dist/src/commands/test-diagnose.js +7 -0
  136. package/dist/src/commands/test-modes.d.ts +16 -1
  137. package/dist/src/commands/test-modes.js +24 -1
  138. package/dist/src/commands/test-multi-verdict.d.ts +61 -0
  139. package/dist/src/commands/test-multi-verdict.js +111 -0
  140. package/dist/src/commands/test-orchestrate.d.ts +43 -0
  141. package/dist/src/commands/test-orchestrate.js +222 -0
  142. package/dist/src/commands/test-perf-scope.d.ts +29 -0
  143. package/dist/src/commands/test-perf-scope.js +83 -0
  144. package/dist/src/commands/test-prelock.d.ts +51 -0
  145. package/dist/src/commands/test-prelock.js +177 -0
  146. package/dist/src/commands/test-register.js +54 -10
  147. package/dist/src/commands/test-run.js +4 -1
  148. package/dist/src/commands/test-stale-gate.js +57 -27
  149. package/dist/src/commands/test-verdict.d.ts +10 -1
  150. package/dist/src/commands/test-verdict.js +28 -1
  151. package/dist/src/commands/test.d.ts +6 -1
  152. package/dist/src/commands/test.js +66 -60
  153. package/dist/src/commands/token-lint.d.ts +14 -0
  154. package/dist/src/commands/token-lint.js +111 -0
  155. package/dist/src/commands/token.js +10 -0
  156. package/dist/src/commands/verify-scope.d.ts +28 -0
  157. package/dist/src/commands/verify-scope.js +67 -0
  158. package/dist/src/commands/verify.d.ts +10 -2
  159. package/dist/src/commands/verify.js +59 -35
  160. package/dist/src/core/build-baseline-types.d.ts +46 -0
  161. package/dist/src/core/build-baseline-validate.js +19 -0
  162. package/dist/src/core/build-baseline.d.ts +5 -1
  163. package/dist/src/core/build-baseline.js +3 -1
  164. package/dist/src/core/build-overwrite-guard.d.ts +50 -0
  165. package/dist/src/core/build-overwrite-guard.js +84 -4
  166. package/dist/src/core/build-prepare.d.ts +7 -0
  167. package/dist/src/core/build-prepare.js +14 -8
  168. package/dist/src/core/build-run-record.d.ts +74 -0
  169. package/dist/src/core/build-run-record.js +136 -0
  170. package/dist/src/core/checkjs-support-heads.d.ts +41 -0
  171. package/dist/src/core/checkjs-support-heads.js +104 -0
  172. package/dist/src/core/config-paths.d.ts +2 -2
  173. package/dist/src/core/config-paths.js +8 -0
  174. package/dist/src/core/config-validate-check.d.ts +8 -0
  175. package/dist/src/core/config-validate-check.js +143 -0
  176. package/dist/src/core/config-validate-file-size.d.ts +9 -6
  177. package/dist/src/core/config-validate-file-size.js +91 -2
  178. package/dist/src/core/config-validate-history.d.ts +11 -0
  179. package/dist/src/core/config-validate-history.js +38 -0
  180. package/dist/src/core/config-validate-patch-policy.js +100 -0
  181. package/dist/src/core/config-validate-test-toolchains.js +7 -0
  182. package/dist/src/core/config-validate-token-scales.d.ts +6 -0
  183. package/dist/src/core/config-validate-token-scales.js +50 -0
  184. package/dist/src/core/config-validate.js +14 -5
  185. package/dist/src/core/config.d.ts +5 -0
  186. package/dist/src/core/config.js +16 -0
  187. package/dist/src/core/coverage-dispatch.d.ts +24 -0
  188. package/dist/src/core/coverage-dispatch.js +44 -0
  189. package/dist/src/core/coverage-extend.d.ts +70 -45
  190. package/dist/src/core/coverage-extend.js +199 -144
  191. package/dist/src/core/coverage-records.d.ts +97 -0
  192. package/dist/src/core/coverage-records.js +350 -0
  193. package/dist/src/core/destructive.d.ts +7 -4
  194. package/dist/src/core/destructive.js +10 -10
  195. package/dist/src/core/engine-session-lock.d.ts +10 -1
  196. package/dist/src/core/engine-session-lock.js +28 -8
  197. package/dist/src/core/export-moved-code-shape.d.ts +49 -11
  198. package/dist/src/core/export-moved-code-shape.js +135 -44
  199. package/dist/src/core/file-lock-queue.d.ts +67 -0
  200. package/dist/src/core/file-lock-queue.js +221 -0
  201. package/dist/src/core/file-lock.d.ts +4 -25
  202. package/dist/src/core/file-lock.js +105 -138
  203. package/dist/src/core/furnace-apply-output.d.ts +10 -0
  204. package/dist/src/core/furnace-apply-output.js +31 -1
  205. package/dist/src/core/furnace-apply.js +12 -2
  206. package/dist/src/core/furnace-composed-source.d.ts +76 -0
  207. package/dist/src/core/furnace-composed-source.js +167 -0
  208. package/dist/src/core/furnace-config-validate-exceptions.d.ts +9 -0
  209. package/dist/src/core/furnace-config-validate-exceptions.js +71 -0
  210. package/dist/src/core/furnace-config-validate.js +8 -0
  211. package/dist/src/core/furnace-css-fragments.d.ts +10 -0
  212. package/dist/src/core/furnace-css-fragments.js +26 -0
  213. package/dist/src/core/furnace-operation.d.ts +3 -3
  214. package/dist/src/core/furnace-registration-remove.d.ts +22 -1
  215. package/dist/src/core/furnace-registration-remove.js +53 -17
  216. package/dist/src/core/furnace-registration-rename.d.ts +26 -0
  217. package/dist/src/core/furnace-registration-rename.js +77 -0
  218. package/dist/src/core/furnace-registration.d.ts +14 -4
  219. package/dist/src/core/furnace-registration.js +44 -10
  220. package/dist/src/core/furnace-staleness.d.ts +7 -0
  221. package/dist/src/core/furnace-staleness.js +34 -24
  222. package/dist/src/core/furnace-validate-accessibility.d.ts +4 -2
  223. package/dist/src/core/furnace-validate-accessibility.js +10 -1
  224. package/dist/src/core/furnace-validate-exceptions.d.ts +40 -0
  225. package/dist/src/core/furnace-validate-exceptions.js +43 -0
  226. package/dist/src/core/furnace-validate-icon-button.d.ts +7 -0
  227. package/dist/src/core/furnace-validate-icon-button.js +49 -0
  228. package/dist/src/core/furnace-validate.js +1 -1
  229. package/dist/src/core/harness-port-fallback.d.ts +60 -0
  230. package/dist/src/core/harness-port-fallback.js +96 -0
  231. package/dist/src/core/history-log-file.d.ts +61 -0
  232. package/dist/src/core/history-log-file.js +157 -0
  233. package/dist/src/core/history-log.d.ts +2 -1
  234. package/dist/src/core/history-log.js +2 -1
  235. package/dist/src/core/json-preserving-edit.d.ts +22 -0
  236. package/dist/src/core/json-preserving-edit.js +199 -0
  237. package/dist/src/core/mach.js +8 -1
  238. package/dist/src/core/negative-control-diff.d.ts +39 -0
  239. package/dist/src/core/negative-control-diff.js +136 -0
  240. package/dist/src/core/negative-control.d.ts +138 -0
  241. package/dist/src/core/negative-control.js +356 -0
  242. package/dist/src/core/objdir-install-manifests.d.ts +73 -0
  243. package/dist/src/core/objdir-install-manifests.js +226 -0
  244. package/dist/src/core/patch-export-update.d.ts +2 -0
  245. package/dist/src/core/patch-export-update.js +1 -0
  246. package/dist/src/core/patch-export.js +3 -4
  247. package/dist/src/core/patch-lint-checkjs-any-import.d.ts +37 -0
  248. package/dist/src/core/patch-lint-checkjs-any-import.js +61 -0
  249. package/dist/src/core/patch-lint-checkjs.js +8 -11
  250. package/dist/src/core/patch-lint-code-lines.d.ts +59 -0
  251. package/dist/src/core/patch-lint-code-lines.js +105 -0
  252. package/dist/src/core/patch-lint-cross.js +12 -4
  253. package/dist/src/core/patch-lint-css.d.ts +8 -2
  254. package/dist/src/core/patch-lint-css.js +10 -4
  255. package/dist/src/core/patch-lint-file-size.d.ts +12 -5
  256. package/dist/src/core/patch-lint-file-size.js +22 -11
  257. package/dist/src/core/patch-lint-forward-registration-extended.d.ts +18 -0
  258. package/dist/src/core/patch-lint-forward-registration-extended.js +35 -10
  259. package/dist/src/core/patch-lint-forward-registration.js +10 -0
  260. package/dist/src/core/patch-lint-ignore.d.ts +152 -0
  261. package/dist/src/core/patch-lint-ignore.js +299 -0
  262. package/dist/src/core/patch-lint-size-thresholds.d.ts +52 -0
  263. package/dist/src/core/patch-lint-size-thresholds.js +110 -0
  264. package/dist/src/core/patch-lint-size.d.ts +117 -0
  265. package/dist/src/core/patch-lint-size.js +323 -0
  266. package/dist/src/core/patch-lint-staged-registration.d.ts +18 -1
  267. package/dist/src/core/patch-lint-staged-registration.js +20 -0
  268. package/dist/src/core/patch-lint-token-scale.d.ts +39 -0
  269. package/dist/src/core/patch-lint-token-scale.js +109 -0
  270. package/dist/src/core/patch-lint.d.ts +11 -83
  271. package/dist/src/core/patch-lint.js +64 -286
  272. package/dist/src/core/patch-lock.js +7 -6
  273. package/dist/src/core/patch-manifest-consistency.js +7 -6
  274. package/dist/src/core/patch-manifest-io.d.ts +6 -3
  275. package/dist/src/core/patch-manifest-io.js +19 -8
  276. package/dist/src/core/patch-manifest-validate.js +34 -2
  277. package/dist/src/core/patch-order-legal.d.ts +25 -0
  278. package/dist/src/core/patch-order-legal.js +83 -0
  279. package/dist/src/core/patch-policy-content.d.ts +40 -0
  280. package/dist/src/core/patch-policy-content.js +216 -0
  281. package/dist/src/core/patch-policy.d.ts +16 -3
  282. package/dist/src/core/patch-policy.js +16 -9
  283. package/dist/src/core/patch-retired.d.ts +35 -0
  284. package/dist/src/core/patch-retired.js +54 -0
  285. package/dist/src/core/patch-subset.d.ts +16 -0
  286. package/dist/src/core/patch-subset.js +66 -0
  287. package/dist/src/core/preflight-wait.d.ts +20 -0
  288. package/dist/src/core/preflight-wait.js +38 -0
  289. package/dist/src/core/rebase-from-version.d.ts +46 -0
  290. package/dist/src/core/rebase-from-version.js +71 -0
  291. package/dist/src/core/run-log.d.ts +20 -0
  292. package/dist/src/core/run-log.js +89 -3
  293. package/dist/src/core/run-profile.d.ts +7 -0
  294. package/dist/src/core/run-profile.js +7 -1
  295. package/dist/src/core/smoke-patterns.d.ts +5 -0
  296. package/dist/src/core/smoke-patterns.js +13 -0
  297. package/dist/src/core/test-harness-crash.d.ts +18 -1
  298. package/dist/src/core/test-harness-crash.js +6 -4
  299. package/dist/src/core/test-harness-signature.d.ts +17 -0
  300. package/dist/src/core/test-harness-signature.js +4 -0
  301. package/dist/src/core/test-run-completeness.d.ts +57 -0
  302. package/dist/src/core/test-run-completeness.js +144 -0
  303. package/dist/src/core/test-stale-check.d.ts +27 -1
  304. package/dist/src/core/test-stale-check.js +39 -49
  305. package/dist/src/core/test-stale-exempt.d.ts +40 -0
  306. package/dist/src/core/test-stale-exempt.js +70 -0
  307. package/dist/src/core/token-lint.d.ts +50 -0
  308. package/dist/src/core/token-lint.js +126 -0
  309. package/dist/src/core/tree-guard.js +3 -0
  310. package/dist/src/core/typecheck-shim.d.ts +1 -1
  311. package/dist/src/core/typecheck-shim.js +90 -2
  312. package/dist/src/index.d.ts +8 -3
  313. package/dist/src/index.js +3 -1
  314. package/dist/src/types/commands/index.d.ts +2 -2
  315. package/dist/src/types/commands/options.d.ts +148 -21
  316. package/dist/src/types/commands/patches.d.ts +75 -1
  317. package/dist/src/types/config.d.ts +192 -0
  318. package/dist/src/types/config.js +9 -0
  319. package/dist/src/types/furnace.d.ts +43 -1
  320. package/dist/src/types/furnace.js +7 -0
  321. package/dist/src/types/index.d.ts +1 -1
  322. package/dist/src/utils/options.d.ts +34 -6
  323. package/dist/src/utils/options.js +86 -5
  324. package/dist/src/utils/path-glob.d.ts +15 -0
  325. package/dist/src/utils/path-glob.js +47 -0
  326. package/dist/src/utils/regex.d.ts +15 -3
  327. package/dist/src/utils/regex.js +37 -16
  328. package/docs/configuration.md +282 -9
  329. package/docs/exit-codes.md +18 -1
  330. package/docs/furnace.md +97 -3
  331. package/docs/lifecycle-invariants.md +52 -19
  332. package/docs/machine-output.md +74 -12
  333. package/docs/patch-workflow.md +297 -22
  334. package/docs/run-logs.md +20 -1
  335. package/docs/testing.md +276 -20
  336. package/package.json +2 -2
@@ -3,46 +3,85 @@
3
3
  * a CI-enforceable non-zero exit: the default policy fails on any
4
4
  * classification outside {patch-backed, branding, furnace}, and
5
5
  * `--fail-on <class,...>` replaces that set for finer control.
6
+ * `--allow <class,...>` names classes that never fail, and
7
+ * `--fail-on-unknown` fails every present class that neither list names,
8
+ * so a gate keeps failing closed when FireForge adds a classification.
9
+ * `ownership-conflict` is a check class beside the file classifications:
10
+ * a path claimed by more than one patch, judged from the same rows as
11
+ * `--include-ownership`.
6
12
  *
7
13
  * Helper module consumed by status.ts, which sits at the max-lines budget.
8
14
  * No top-level registrar is exported and none is wanted.
9
15
  */
10
16
  import type { ClassifiedFile, FileClassification } from '../core/status-classify.js';
17
+ /**
18
+ * A class `--fail-on`/`--allow` can name: every file classification, plus
19
+ * `ownership-conflict`, which is judged from the ownership rows rather
20
+ * than from a file's classification.
21
+ */
22
+ export type StatusCheckClass = FileClassification | 'ownership-conflict';
11
23
  /** Resolved `--check`/`--fail-on` policy for one status invocation. */
12
24
  export interface StatusCheckPolicy {
13
25
  checkEnabled: boolean;
14
- failOn: readonly FileClassification[];
26
+ /** Classes that fail: `--fail-on` (or the default set) minus `--allow`. */
27
+ failOn: readonly StatusCheckClass[];
28
+ /** `--allow`: classes that never fail. Set only when the flag was given. */
29
+ allow?: readonly StatusCheckClass[];
30
+ /** `--fail-on-unknown`: a present class named by neither list fails. */
31
+ failOnUnknown?: boolean;
15
32
  }
16
33
  /**
17
- * Resolves the enforcement policy from the raw flags: `--fail-on` implies
18
- * `--check` and replaces the default set. Both are refused alongside the
19
- * modes that skip (or elide) classification.
34
+ * Resolves the enforcement policy from the raw flags. `--fail-on`,
35
+ * `--allow` and `--fail-on-unknown` each imply `--check`. `--fail-on`
36
+ * replaces the default set and `--allow` subtracts from it (naming one
37
+ * class in both is refused). `--fail-on-unknown` needs `--allow`: without
38
+ * an allow list every class the caller did not name, `patch-backed`
39
+ * included, would fail. All of them are refused alongside the modes that
40
+ * skip (or elide) classification.
20
41
  */
21
42
  export declare function resolveStatusCheckPolicy(options: {
22
43
  check?: boolean;
23
44
  failOn?: string;
45
+ allow?: string;
46
+ failOnUnknown?: boolean;
24
47
  raw?: boolean;
25
48
  unmanaged?: boolean;
26
49
  ownership?: boolean;
27
50
  testCoverage?: boolean;
28
51
  }): StatusCheckPolicy;
29
- /** One fail-set classification with a non-empty file list. */
52
+ /**
53
+ * True when the policy can fail on `ownership-conflict`, so the caller has
54
+ * to build the ownership rows even without `--include-ownership`.
55
+ */
56
+ export declare function policyNeedsOwnership(policy: StatusCheckPolicy): boolean;
57
+ /** One failing class with a non-empty file list. */
30
58
  export interface StatusCheckOffender {
31
- classification: FileClassification;
59
+ classification: StatusCheckClass;
32
60
  count: number;
33
61
  files: string[];
62
+ /**
63
+ * Set when the class failed only through `--fail-on-unknown`: neither
64
+ * `--fail-on` nor `--allow` named it.
65
+ */
66
+ unnamed?: true;
34
67
  }
35
68
  /**
36
- * Collects the fail-set classifications that have offending files, in
37
- * policy order with full file lists. Shared by the human `--check`
38
- * verdict and the `--json --summary` payload so the two views can never
39
- * disagree about what failed.
69
+ * Collects the failing classes that have offending files, with full file
70
+ * lists: the fail set in policy order, then (under `--fail-on-unknown`)
71
+ * every other present class neither list names, in first-seen order.
72
+ * Shared by the human `--check` verdict and the JSON `check` block so the
73
+ * two views can never disagree about what failed.
74
+ *
75
+ * @param classified - Classified worktree entries
76
+ * @param policy - Resolved policy
77
+ * @param ownershipConflicts - Paths claimed by more than one patch. Only
78
+ * read for the `ownership-conflict` class
40
79
  */
41
- export declare function collectStatusCheckOffenders(classified: readonly ClassifiedFile[], policy: StatusCheckPolicy): StatusCheckOffender[];
80
+ export declare function collectStatusCheckOffenders(classified: readonly ClassifiedFile[], policy: StatusCheckPolicy, ownershipConflicts?: readonly string[]): StatusCheckOffender[];
42
81
  /**
43
- * Throws a GeneralError (exit 1) when the policy is enabled and any of
44
- * its classifications is non-empty, naming each offending classification
45
- * with its first few files. A disabled policy or a fully tool-covered
46
- * tree passes silently.
82
+ * Throws a GeneralError (exit 1) when the policy is enabled and any
83
+ * failing class is non-empty, naming each offending class with its first
84
+ * few files. A disabled policy or a fully tool-covered tree passes
85
+ * silently.
47
86
  */
48
- export declare function runStatusCheck(classified: readonly ClassifiedFile[], policy: StatusCheckPolicy): void;
87
+ export declare function runStatusCheck(classified: readonly ClassifiedFile[], policy: StatusCheckPolicy, ownershipConflicts?: readonly string[]): void;
@@ -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
+ }