@shrkcrft/cli 0.1.0-alpha.30 → 0.1.0-alpha.31

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 (510) hide show
  1. package/dist/asset-preview/apply-asset-preview.js +1 -1
  2. package/dist/authoring/authoring-kit.d.ts +9 -1
  3. package/dist/authoring/authoring-kit.d.ts.map +1 -1
  4. package/dist/authoring/authoring-kit.js +26 -13
  5. package/dist/bootstrap/unlinked-workspace-dependency.d.ts +11 -0
  6. package/dist/bootstrap/unlinked-workspace-dependency.d.ts.map +1 -0
  7. package/dist/bootstrap/unlinked-workspace-dependency.js +99 -0
  8. package/dist/command-registry.d.ts +100 -1
  9. package/dist/command-registry.d.ts.map +1 -1
  10. package/dist/command-registry.js +130 -3
  11. package/dist/commands/api-diff.command.d.ts.map +1 -1
  12. package/dist/commands/api-diff.command.js +12 -0
  13. package/dist/commands/apply.command.d.ts.map +1 -1
  14. package/dist/commands/apply.command.js +3 -0
  15. package/dist/commands/arch.command.d.ts.map +1 -1
  16. package/dist/commands/arch.command.js +26 -7
  17. package/dist/commands/architecture.command.d.ts.map +1 -1
  18. package/dist/commands/architecture.command.js +8 -2
  19. package/dist/commands/baseline.command.d.ts +54 -3
  20. package/dist/commands/baseline.command.d.ts.map +1 -1
  21. package/dist/commands/baseline.command.js +582 -74
  22. package/dist/commands/biome.command.d.ts.map +1 -1
  23. package/dist/commands/biome.command.js +20 -0
  24. package/dist/commands/boundaries.command.d.ts +9 -1
  25. package/dist/commands/boundaries.command.d.ts.map +1 -1
  26. package/dist/commands/boundaries.command.js +88 -20
  27. package/dist/commands/bundle.command.d.ts.map +1 -1
  28. package/dist/commands/bundle.command.js +57 -11
  29. package/dist/commands/cache-align.command.d.ts.map +1 -1
  30. package/dist/commands/cache-align.command.js +3 -1
  31. package/dist/commands/changelog-data.d.ts.map +1 -1
  32. package/dist/commands/changelog-data.js +138 -0
  33. package/dist/commands/changes.command.d.ts.map +1 -1
  34. package/dist/commands/changes.command.js +15 -0
  35. package/dist/commands/check.command.d.ts.map +1 -1
  36. package/dist/commands/check.command.js +1241 -322
  37. package/dist/commands/checks.command.d.ts.map +1 -1
  38. package/dist/commands/checks.command.js +188 -40
  39. package/dist/commands/ci.command.d.ts.map +1 -1
  40. package/dist/commands/ci.command.js +35 -2
  41. package/dist/commands/code-intel.command.d.ts.map +1 -1
  42. package/dist/commands/code-intel.command.js +3 -0
  43. package/dist/commands/command-catalog.d.ts +23 -1
  44. package/dist/commands/command-catalog.d.ts.map +1 -1
  45. package/dist/commands/command-catalog.js +152 -35
  46. package/dist/commands/commands.command.d.ts +21 -3
  47. package/dist/commands/commands.command.d.ts.map +1 -1
  48. package/dist/commands/commands.command.js +172 -27
  49. package/dist/commands/compress.command.d.ts.map +1 -1
  50. package/dist/commands/compress.command.js +4 -0
  51. package/dist/commands/constructs.command.d.ts.map +1 -1
  52. package/dist/commands/constructs.command.js +50 -12
  53. package/dist/commands/context.command.d.ts.map +1 -1
  54. package/dist/commands/context.command.js +40 -5
  55. package/dist/commands/contract-templates.command.d.ts.map +1 -1
  56. package/dist/commands/contract-templates.command.js +9 -1
  57. package/dist/commands/contract.command.d.ts.map +1 -1
  58. package/dist/commands/contract.command.js +35 -0
  59. package/dist/commands/conventions.command.d.ts.map +1 -1
  60. package/dist/commands/conventions.command.js +189 -29
  61. package/dist/commands/coverage.command.d.ts.map +1 -1
  62. package/dist/commands/coverage.command.js +9 -0
  63. package/dist/commands/daily.commands.d.ts +0 -1
  64. package/dist/commands/daily.commands.d.ts.map +1 -1
  65. package/dist/commands/daily.commands.js +12 -76
  66. package/dist/commands/dashboard.command.d.ts.map +1 -1
  67. package/dist/commands/dashboard.command.js +10 -0
  68. package/dist/commands/dev.command.d.ts.map +1 -1
  69. package/dist/commands/dev.command.js +45 -2
  70. package/dist/commands/diagnostics.command.d.ts +0 -2
  71. package/dist/commands/diagnostics.command.d.ts.map +1 -1
  72. package/dist/commands/diagnostics.command.js +5 -78
  73. package/dist/commands/diff-check.command.d.ts +6 -4
  74. package/dist/commands/diff-check.command.d.ts.map +1 -1
  75. package/dist/commands/diff-check.command.js +190 -103
  76. package/dist/commands/docs-references.command.d.ts +13 -0
  77. package/dist/commands/docs-references.command.d.ts.map +1 -1
  78. package/dist/commands/docs-references.command.js +269 -72
  79. package/dist/commands/doctor.command.d.ts.map +1 -1
  80. package/dist/commands/doctor.command.js +58 -4
  81. package/dist/commands/drift.command.d.ts.map +1 -1
  82. package/dist/commands/drift.command.js +17 -4
  83. package/dist/commands/eslint.command.d.ts.map +1 -1
  84. package/dist/commands/eslint.command.js +20 -0
  85. package/dist/commands/export.command.d.ts.map +1 -1
  86. package/dist/commands/export.command.js +23 -1
  87. package/dist/commands/feedback-dispatch.command.d.ts.map +1 -1
  88. package/dist/commands/feedback-dispatch.command.js +29 -0
  89. package/dist/commands/feedback.command.d.ts.map +1 -1
  90. package/dist/commands/feedback.command.js +6 -1
  91. package/dist/commands/finish.command.d.ts.map +1 -1
  92. package/dist/commands/finish.command.js +32 -8
  93. package/dist/commands/fix.command.d.ts.map +1 -1
  94. package/dist/commands/fix.command.js +19 -0
  95. package/dist/commands/gate.command.d.ts +6 -1
  96. package/dist/commands/gate.command.d.ts.map +1 -1
  97. package/dist/commands/gate.command.js +50 -124
  98. package/dist/commands/gates.command.d.ts +52 -0
  99. package/dist/commands/gates.command.d.ts.map +1 -1
  100. package/dist/commands/gates.command.js +811 -185
  101. package/dist/commands/gen.command.d.ts.map +1 -1
  102. package/dist/commands/gen.command.js +4 -0
  103. package/dist/commands/generated.command.d.ts +33 -3
  104. package/dist/commands/generated.command.d.ts.map +1 -1
  105. package/dist/commands/generated.command.js +499 -147
  106. package/dist/commands/graph-code-subverbs.d.ts +15 -0
  107. package/dist/commands/graph-code-subverbs.d.ts.map +1 -1
  108. package/dist/commands/graph-code-subverbs.js +198 -24
  109. package/dist/commands/graph.command.d.ts.map +1 -1
  110. package/dist/commands/graph.command.js +79 -37
  111. package/dist/commands/help.command.d.ts +30 -6
  112. package/dist/commands/help.command.d.ts.map +1 -1
  113. package/dist/commands/help.command.js +323 -199
  114. package/dist/commands/helper.command.d.ts +1 -0
  115. package/dist/commands/helper.command.d.ts.map +1 -1
  116. package/dist/commands/helper.command.js +195 -56
  117. package/dist/commands/impact.command.d.ts.map +1 -1
  118. package/dist/commands/impact.command.js +115 -12
  119. package/dist/commands/infer.command.d.ts.map +1 -1
  120. package/dist/commands/infer.command.js +20 -45
  121. package/dist/commands/ingest.command.d.ts +0 -1
  122. package/dist/commands/ingest.command.d.ts.map +1 -1
  123. package/dist/commands/ingest.command.js +25 -41
  124. package/dist/commands/knowledge-author.command.d.ts +0 -3
  125. package/dist/commands/knowledge-author.command.d.ts.map +1 -1
  126. package/dist/commands/knowledge-author.command.js +10 -23
  127. package/dist/commands/knowledge.command.d.ts.map +1 -1
  128. package/dist/commands/knowledge.command.js +469 -129
  129. package/dist/commands/languages.command.d.ts.map +1 -1
  130. package/dist/commands/languages.command.js +27 -0
  131. package/dist/commands/lint.command.d.ts.map +1 -1
  132. package/dist/commands/lint.command.js +3 -0
  133. package/dist/commands/mcp.command.d.ts +18 -0
  134. package/dist/commands/mcp.command.d.ts.map +1 -1
  135. package/dist/commands/mcp.command.js +33 -8
  136. package/dist/commands/onboard.command.d.ts.map +1 -1
  137. package/dist/commands/onboard.command.js +20 -0
  138. package/dist/commands/owners.command.d.ts.map +1 -1
  139. package/dist/commands/owners.command.js +14 -2
  140. package/dist/commands/packs-new.d.ts +13 -1
  141. package/dist/commands/packs-new.d.ts.map +1 -1
  142. package/dist/commands/packs-new.js +500 -335
  143. package/dist/commands/packs.command.d.ts.map +1 -1
  144. package/dist/commands/packs.command.js +378 -36
  145. package/dist/commands/paths.command.d.ts.map +1 -1
  146. package/dist/commands/paths.command.js +9 -1
  147. package/dist/commands/pipelines.command.d.ts.map +1 -1
  148. package/dist/commands/pipelines.command.js +7 -1
  149. package/dist/commands/plan-check.command.d.ts.map +1 -1
  150. package/dist/commands/plan-check.command.js +3 -0
  151. package/dist/commands/plan.command.d.ts.map +1 -1
  152. package/dist/commands/plan.command.js +3 -0
  153. package/dist/commands/playbooks.command.d.ts.map +1 -1
  154. package/dist/commands/playbooks.command.js +7 -2
  155. package/dist/commands/policy-lint.command.d.ts +19 -2
  156. package/dist/commands/policy-lint.command.d.ts.map +1 -1
  157. package/dist/commands/policy-lint.command.js +224 -65
  158. package/dist/commands/policy.command.d.ts.map +1 -1
  159. package/dist/commands/policy.command.js +6 -1
  160. package/dist/commands/pr.command.d.ts.map +1 -1
  161. package/dist/commands/pr.command.js +14 -0
  162. package/dist/commands/presets.command.d.ts.map +1 -1
  163. package/dist/commands/presets.command.js +12 -27
  164. package/dist/commands/profiles.command.d.ts.map +1 -1
  165. package/dist/commands/profiles.command.js +94 -26
  166. package/dist/commands/provenance.command.js +1 -1
  167. package/dist/commands/quality.command.d.ts.map +1 -1
  168. package/dist/commands/quality.command.js +129 -33
  169. package/dist/commands/recommend.command.d.ts +20 -4
  170. package/dist/commands/recommend.command.d.ts.map +1 -1
  171. package/dist/commands/recommend.command.js +172 -278
  172. package/dist/commands/registrations.command.d.ts.map +1 -1
  173. package/dist/commands/registrations.command.js +138 -13
  174. package/dist/commands/registry-lifecycle-run.d.ts +14 -0
  175. package/dist/commands/registry-lifecycle-run.d.ts.map +1 -0
  176. package/dist/commands/registry-lifecycle-run.js +336 -0
  177. package/dist/commands/registry.command.d.ts.map +1 -1
  178. package/dist/commands/registry.command.js +235 -62
  179. package/dist/commands/release.command.d.ts.map +1 -1
  180. package/dist/commands/release.command.js +99 -20
  181. package/dist/commands/report.command.d.ts.map +1 -1
  182. package/dist/commands/report.command.js +38 -2
  183. package/dist/commands/reuse-coverage.command.d.ts +23 -0
  184. package/dist/commands/reuse-coverage.command.d.ts.map +1 -0
  185. package/dist/commands/reuse-coverage.command.js +536 -0
  186. package/dist/commands/reuse.command.d.ts +3 -18
  187. package/dist/commands/reuse.command.d.ts.map +1 -1
  188. package/dist/commands/reuse.command.js +395 -244
  189. package/dist/commands/review.command.d.ts.map +1 -1
  190. package/dist/commands/review.command.js +33 -1
  191. package/dist/commands/rounds.command.d.ts.map +1 -1
  192. package/dist/commands/rounds.command.js +5 -0
  193. package/dist/commands/safety.command.d.ts.map +1 -1
  194. package/dist/commands/safety.command.js +9 -0
  195. package/dist/commands/scaffolds.command.d.ts.map +1 -1
  196. package/dist/commands/scaffolds.command.js +97 -25
  197. package/dist/commands/search.command.d.ts +0 -8
  198. package/dist/commands/search.command.d.ts.map +1 -1
  199. package/dist/commands/search.command.js +136 -28
  200. package/dist/commands/self-config-xrefs.command.d.ts +24 -0
  201. package/dist/commands/self-config-xrefs.command.d.ts.map +1 -0
  202. package/dist/commands/self-config-xrefs.command.js +170 -0
  203. package/dist/commands/self-config.command.d.ts.map +1 -1
  204. package/dist/commands/self-config.command.js +261 -65
  205. package/dist/commands/smart-context.command.d.ts.map +1 -1
  206. package/dist/commands/smart-context.command.js +39 -3
  207. package/dist/commands/spec.command.d.ts.map +1 -1
  208. package/dist/commands/spec.command.js +3 -0
  209. package/dist/commands/stats.command.d.ts.map +1 -1
  210. package/dist/commands/stats.command.js +3 -0
  211. package/dist/commands/surface.command.d.ts +6 -1
  212. package/dist/commands/surface.command.d.ts.map +1 -1
  213. package/dist/commands/surface.command.js +256 -54
  214. package/dist/commands/task-context.command.js +1 -1
  215. package/dist/commands/task.command.d.ts.map +1 -1
  216. package/dist/commands/task.command.js +22 -1
  217. package/dist/commands/templates.command.d.ts +4 -0
  218. package/dist/commands/templates.command.d.ts.map +1 -1
  219. package/dist/commands/templates.command.js +109 -22
  220. package/dist/commands/test.command.d.ts.map +1 -1
  221. package/dist/commands/test.command.js +161 -33
  222. package/dist/commands/tests.command.d.ts.map +1 -1
  223. package/dist/commands/tests.command.js +29 -3
  224. package/dist/commands/trace.command.d.ts.map +1 -1
  225. package/dist/commands/trace.command.js +29 -4
  226. package/dist/commands/watch.command.d.ts.map +1 -1
  227. package/dist/commands/watch.command.js +3 -0
  228. package/dist/commands/why.command.d.ts.map +1 -1
  229. package/dist/commands/why.command.js +3 -0
  230. package/dist/commands/wiring.command.d.ts +18 -4
  231. package/dist/commands/wiring.command.d.ts.map +1 -1
  232. package/dist/commands/wiring.command.js +360 -73
  233. package/dist/dashboard/code-intelligence-data.js +4 -4
  234. package/dist/diff/deleted-orphan-coverage.d.ts +32 -0
  235. package/dist/diff/deleted-orphan-coverage.d.ts.map +1 -0
  236. package/dist/diff/deleted-orphan-coverage.js +107 -0
  237. package/dist/diff/deleted-orphan-scope-notes.d.ts +23 -0
  238. package/dist/diff/deleted-orphan-scope-notes.d.ts.map +1 -0
  239. package/dist/diff/deleted-orphan-scope-notes.js +49 -0
  240. package/dist/diff/deleted-orphans.d.ts +32 -0
  241. package/dist/diff/deleted-orphans.d.ts.map +1 -1
  242. package/dist/diff/deleted-orphans.js +42 -2
  243. package/dist/dispatch/closest-match.d.ts +15 -0
  244. package/dist/dispatch/closest-match.d.ts.map +1 -0
  245. package/dist/dispatch/closest-match.js +47 -0
  246. package/dist/dispatch/global-flags.d.ts +94 -0
  247. package/dist/dispatch/global-flags.d.ts.map +1 -0
  248. package/dist/dispatch/global-flags.js +123 -0
  249. package/dist/dispatch/guard-invocation-input.d.ts +24 -0
  250. package/dist/dispatch/guard-invocation-input.d.ts.map +1 -0
  251. package/dist/dispatch/guard-invocation-input.js +1 -0
  252. package/dist/dispatch/guard-invocation.d.ts +33 -0
  253. package/dist/dispatch/guard-invocation.d.ts.map +1 -0
  254. package/dist/dispatch/guard-invocation.js +183 -0
  255. package/dist/dispatch/help-intercept.d.ts +22 -0
  256. package/dist/dispatch/help-intercept.d.ts.map +1 -0
  257. package/dist/dispatch/help-intercept.js +42 -0
  258. package/dist/dispatch/i-declared-invocation.d.ts +17 -0
  259. package/dist/dispatch/i-declared-invocation.d.ts.map +1 -0
  260. package/dist/dispatch/i-declared-invocation.js +1 -0
  261. package/dist/dispatch/i-unknown-flag-refusal-input.d.ts +28 -0
  262. package/dist/dispatch/i-unknown-flag-refusal-input.d.ts.map +1 -0
  263. package/dist/dispatch/i-unknown-flag-refusal-input.js +1 -0
  264. package/dist/dispatch/invocation-rejection-kind.d.ts +15 -0
  265. package/dist/dispatch/invocation-rejection-kind.d.ts.map +1 -0
  266. package/dist/dispatch/invocation-rejection-kind.js +15 -0
  267. package/dist/dispatch/invocation-rejection.d.ts +18 -0
  268. package/dist/dispatch/invocation-rejection.d.ts.map +1 -0
  269. package/dist/dispatch/invocation-rejection.js +1 -0
  270. package/dist/dispatch/judge-invocation.d.ts +24 -0
  271. package/dist/dispatch/judge-invocation.d.ts.map +1 -0
  272. package/dist/dispatch/judge-invocation.js +50 -0
  273. package/dist/dispatch/positional-mode.d.ts +18 -0
  274. package/dist/dispatch/positional-mode.d.ts.map +1 -0
  275. package/dist/dispatch/positional-mode.js +18 -0
  276. package/dist/dispatch/read-tracking-map.d.ts +28 -0
  277. package/dist/dispatch/read-tracking-map.d.ts.map +1 -0
  278. package/dist/dispatch/read-tracking-map.js +58 -0
  279. package/dist/dispatch/refuse-sibling-valve-flags.d.ts +30 -0
  280. package/dist/dispatch/refuse-sibling-valve-flags.d.ts.map +1 -0
  281. package/dist/dispatch/refuse-sibling-valve-flags.js +65 -0
  282. package/dist/dispatch/subverb-spec.d.ts +26 -0
  283. package/dist/dispatch/subverb-spec.d.ts.map +1 -0
  284. package/dist/dispatch/subverb-spec.js +1 -0
  285. package/dist/dispatch/undocumented-flag-reads.d.ts +22 -0
  286. package/dist/dispatch/undocumented-flag-reads.d.ts.map +1 -0
  287. package/dist/dispatch/undocumented-flag-reads.js +283 -0
  288. package/dist/dispatch/unknown-flag-refusal-mode.d.ts +13 -0
  289. package/dist/dispatch/unknown-flag-refusal-mode.d.ts.map +1 -0
  290. package/dist/dispatch/unknown-flag-refusal-mode.js +13 -0
  291. package/dist/dispatch/unknown-flag-refusal.d.ts +22 -0
  292. package/dist/dispatch/unknown-flag-refusal.d.ts.map +1 -0
  293. package/dist/dispatch/unknown-flag-refusal.js +57 -0
  294. package/dist/dispatch/unread-flags.d.ts +152 -0
  295. package/dist/dispatch/unread-flags.d.ts.map +1 -0
  296. package/dist/dispatch/unread-flags.js +295 -0
  297. package/dist/dispatch/verdict-valve-flag.d.ts +21 -0
  298. package/dist/dispatch/verdict-valve-flag.d.ts.map +1 -0
  299. package/dist/dispatch/verdict-valve-flag.js +21 -0
  300. package/dist/dispatch/walk-declared-subverbs.d.ts +14 -0
  301. package/dist/dispatch/walk-declared-subverbs.d.ts.map +1 -0
  302. package/dist/dispatch/walk-declared-subverbs.js +28 -0
  303. package/dist/exit-codes.d.ts +41 -3
  304. package/dist/exit-codes.d.ts.map +1 -1
  305. package/dist/exit-codes.js +153 -14
  306. package/dist/finish/run-finish.d.ts +72 -5
  307. package/dist/finish/run-finish.d.ts.map +1 -1
  308. package/dist/finish/run-finish.js +453 -104
  309. package/dist/gates/accepted-empty-note.d.ts +10 -0
  310. package/dist/gates/accepted-empty-note.d.ts.map +1 -0
  311. package/dist/gates/accepted-empty-note.js +11 -0
  312. package/dist/gates/allow-empty.d.ts +25 -0
  313. package/dist/gates/allow-empty.d.ts.map +1 -0
  314. package/dist/gates/allow-empty.js +27 -0
  315. package/dist/gates/asset-doctor-failing-units.d.ts +16 -0
  316. package/dist/gates/asset-doctor-failing-units.d.ts.map +1 -0
  317. package/dist/gates/asset-doctor-failing-units.js +14 -0
  318. package/dist/gates/asset-doctor-failure-line.d.ts +16 -0
  319. package/dist/gates/asset-doctor-failure-line.d.ts.map +1 -0
  320. package/dist/gates/asset-doctor-failure-line.js +28 -0
  321. package/dist/gates/empty-rule-advice-lines.d.ts +15 -0
  322. package/dist/gates/empty-rule-advice-lines.d.ts.map +1 -0
  323. package/dist/gates/empty-rule-advice-lines.js +20 -0
  324. package/dist/gates/gate-envelope.d.ts +85 -8
  325. package/dist/gates/gate-envelope.d.ts.map +1 -1
  326. package/dist/gates/gate-envelope.js +56 -7
  327. package/dist/gates/gate-rule-globs.d.ts +37 -3
  328. package/dist/gates/gate-rule-globs.d.ts.map +1 -1
  329. package/dist/gates/gate-rule-globs.js +78 -40
  330. package/dist/gates/gate-rule-view.d.ts +17 -4
  331. package/dist/gates/gate-rule-view.d.ts.map +1 -1
  332. package/dist/gates/gate-rule-view.js +105 -90
  333. package/dist/gates/i-coverage-dead-glob.d.ts +19 -0
  334. package/dist/gates/i-coverage-dead-glob.d.ts.map +1 -0
  335. package/dist/gates/i-coverage-dead-glob.js +1 -0
  336. package/dist/gates/i-coverage-negation.d.ts +13 -0
  337. package/dist/gates/i-coverage-negation.d.ts.map +1 -0
  338. package/dist/gates/i-coverage-negation.js +1 -0
  339. package/dist/gates/i-registration-query-settle-input.d.ts +18 -0
  340. package/dist/gates/i-registration-query-settle-input.d.ts.map +1 -0
  341. package/dist/gates/i-registration-query-settle-input.js +1 -0
  342. package/dist/gates/i-unit-state-note-row.d.ts +16 -0
  343. package/dist/gates/i-unit-state-note-row.d.ts.map +1 -0
  344. package/dist/gates/i-unit-state-note-row.js +1 -0
  345. package/dist/gates/i-unit-state-notes.d.ts +32 -0
  346. package/dist/gates/i-unit-state-notes.d.ts.map +1 -0
  347. package/dist/gates/i-unit-state-notes.js +1 -0
  348. package/dist/gates/incomplete-registry-inventory.d.ts +19 -0
  349. package/dist/gates/incomplete-registry-inventory.d.ts.map +1 -0
  350. package/dist/gates/incomplete-registry-inventory.js +63 -0
  351. package/dist/gates/measure-registration-roles.d.ts +10 -0
  352. package/dist/gates/measure-registration-roles.d.ts.map +1 -0
  353. package/dist/gates/measure-registration-roles.js +9 -0
  354. package/dist/gates/plane-verdict.d.ts +14 -0
  355. package/dist/gates/plane-verdict.d.ts.map +1 -0
  356. package/dist/gates/plane-verdict.js +22 -0
  357. package/dist/gates/qualify-clean-for-units.d.ts +12 -0
  358. package/dist/gates/qualify-clean-for-units.d.ts.map +1 -0
  359. package/dist/gates/qualify-clean-for-units.js +18 -0
  360. package/dist/gates/registration-graph-verdict.d.ts +34 -0
  361. package/dist/gates/registration-graph-verdict.d.ts.map +1 -0
  362. package/dist/gates/registration-graph-verdict.js +70 -0
  363. package/dist/gates/registration-roles.d.ts +9 -0
  364. package/dist/gates/registration-roles.d.ts.map +1 -0
  365. package/dist/gates/registration-roles.js +1 -0
  366. package/dist/gates/registry-liveness.d.ts +11 -0
  367. package/dist/gates/registry-liveness.d.ts.map +1 -0
  368. package/dist/gates/registry-liveness.js +13 -0
  369. package/dist/gates/rule-coverage.d.ts +187 -4
  370. package/dist/gates/rule-coverage.d.ts.map +1 -1
  371. package/dist/gates/rule-coverage.js +501 -49
  372. package/dist/gates/run-gate-planes.d.ts +14 -0
  373. package/dist/gates/run-gate-planes.d.ts.map +1 -1
  374. package/dist/gates/run-gate-planes.js +172 -30
  375. package/dist/gates/scaffold-selftest.d.ts +56 -0
  376. package/dist/gates/scaffold-selftest.d.ts.map +1 -0
  377. package/dist/gates/scaffold-selftest.js +119 -0
  378. package/dist/gates/seam-rejected-rules.d.ts +28 -0
  379. package/dist/gates/seam-rejected-rules.d.ts.map +1 -0
  380. package/dist/gates/seam-rejected-rules.js +95 -0
  381. package/dist/gates/self-test-check.d.ts +29 -0
  382. package/dist/gates/self-test-check.d.ts.map +1 -0
  383. package/dist/gates/self-test-check.js +1 -0
  384. package/dist/gates/self-test-subject.d.ts +28 -0
  385. package/dist/gates/self-test-subject.d.ts.map +1 -0
  386. package/dist/gates/self-test-subject.js +1 -0
  387. package/dist/gates/self-test.d.ts +20 -0
  388. package/dist/gates/self-test.d.ts.map +1 -0
  389. package/dist/gates/self-test.js +82 -0
  390. package/dist/gates/settle-verdict.d.ts +14 -0
  391. package/dist/gates/settle-verdict.d.ts.map +1 -0
  392. package/dist/gates/settle-verdict.js +13 -0
  393. package/dist/gates/settled-verdict.d.ts +8 -0
  394. package/dist/gates/settled-verdict.d.ts.map +1 -0
  395. package/dist/gates/settled-verdict.js +1 -0
  396. package/dist/gates/template-registry-coverage.d.ts +18 -0
  397. package/dist/gates/template-registry-coverage.d.ts.map +1 -0
  398. package/dist/gates/template-registry-coverage.js +24 -0
  399. package/dist/gates/unit-state-notes.d.ts +40 -0
  400. package/dist/gates/unit-state-notes.d.ts.map +1 -0
  401. package/dist/gates/unit-state-notes.js +71 -0
  402. package/dist/gates/verdict-line.d.ts +18 -0
  403. package/dist/gates/verdict-line.d.ts.map +1 -0
  404. package/dist/gates/verdict-line.js +40 -0
  405. package/dist/graph/graph-reuse-lookup.d.ts +10 -0
  406. package/dist/graph/graph-reuse-lookup.d.ts.map +1 -0
  407. package/dist/graph/graph-reuse-lookup.js +41 -0
  408. package/dist/graph/index-behind-hint.d.ts +11 -0
  409. package/dist/graph/index-behind-hint.d.ts.map +1 -0
  410. package/dist/graph/index-behind-hint.js +27 -0
  411. package/dist/knowledge/knowledge-stale-gate-flags.d.ts +3 -0
  412. package/dist/knowledge/knowledge-stale-gate-flags.d.ts.map +1 -0
  413. package/dist/knowledge/knowledge-stale-gate-flags.js +1 -0
  414. package/dist/knowledge/knowledge-stale-gate-input.d.ts +3 -0
  415. package/dist/knowledge/knowledge-stale-gate-input.d.ts.map +1 -0
  416. package/dist/knowledge/knowledge-stale-gate-input.js +1 -0
  417. package/dist/knowledge/knowledge-stale-gate-result.d.ts +3 -0
  418. package/dist/knowledge/knowledge-stale-gate-result.d.ts.map +1 -0
  419. package/dist/knowledge/knowledge-stale-gate-result.js +1 -0
  420. package/dist/knowledge/knowledge-stale-gate.d.ts +10 -0
  421. package/dist/knowledge/knowledge-stale-gate.d.ts.map +1 -0
  422. package/dist/knowledge/knowledge-stale-gate.js +9 -0
  423. package/dist/main.d.ts.map +1 -1
  424. package/dist/main.js +200 -83
  425. package/dist/output/failure-hints.d.ts.map +1 -1
  426. package/dist/output/failure-hints.js +11 -9
  427. package/dist/output/rejected-entries-note.d.ts +57 -0
  428. package/dist/output/rejected-entries-note.d.ts.map +1 -0
  429. package/dist/output/rejected-entries-note.js +103 -0
  430. package/dist/output/stdout-is-pipe.d.ts +13 -0
  431. package/dist/output/stdout-is-pipe.d.ts.map +1 -0
  432. package/dist/output/stdout-is-pipe.js +21 -0
  433. package/dist/packs/stale-build-warning.d.ts +11 -0
  434. package/dist/packs/stale-build-warning.d.ts.map +1 -0
  435. package/dist/packs/stale-build-warning.js +23 -0
  436. package/dist/quality/run-quality.d.ts +123 -0
  437. package/dist/quality/run-quality.d.ts.map +1 -0
  438. package/dist/quality/run-quality.js +442 -0
  439. package/dist/schemas/json-schemas.d.ts +184 -3
  440. package/dist/schemas/json-schemas.d.ts.map +1 -1
  441. package/dist/schemas/json-schemas.js +113 -2
  442. package/dist/shrk.d.ts +3 -0
  443. package/dist/shrk.d.ts.map +1 -0
  444. package/dist/shrk.js +30 -0
  445. package/dist/surface/audience-applicability.d.ts +26 -0
  446. package/dist/surface/audience-applicability.d.ts.map +1 -0
  447. package/dist/surface/audience-applicability.js +60 -0
  448. package/dist/surface/catalog-command-safety.d.ts +18 -0
  449. package/dist/surface/catalog-command-safety.d.ts.map +1 -0
  450. package/dist/surface/catalog-command-safety.js +62 -0
  451. package/dist/surface/cli-command-resolver.d.ts +21 -0
  452. package/dist/surface/cli-command-resolver.d.ts.map +1 -0
  453. package/dist/surface/cli-command-resolver.js +87 -0
  454. package/dist/surface/command-dispatch-kind.d.ts +20 -0
  455. package/dist/surface/command-dispatch-kind.d.ts.map +1 -0
  456. package/dist/surface/command-dispatch-kind.js +20 -0
  457. package/dist/surface/command-index-entry.d.ts +40 -0
  458. package/dist/surface/command-index-entry.d.ts.map +1 -0
  459. package/dist/surface/command-index-entry.js +1 -0
  460. package/dist/surface/command-index.d.ts +77 -0
  461. package/dist/surface/command-index.d.ts.map +1 -0
  462. package/dist/surface/command-index.js +323 -0
  463. package/dist/surface/i-command-index.d.ts +22 -0
  464. package/dist/surface/i-command-index.d.ts.map +1 -0
  465. package/dist/surface/i-command-index.js +1 -0
  466. package/dist/surface/i-command-string-context.d.ts +20 -0
  467. package/dist/surface/i-command-string-context.d.ts.map +1 -0
  468. package/dist/surface/i-command-string-context.js +1 -0
  469. package/dist/surface/i-surface-denial.d.ts +13 -0
  470. package/dist/surface/i-surface-denial.d.ts.map +1 -0
  471. package/dist/surface/i-surface-denial.js +1 -0
  472. package/dist/surface/load-surface-context.d.ts +17 -1
  473. package/dist/surface/load-surface-context.d.ts.map +1 -1
  474. package/dist/surface/load-surface-context.js +48 -10
  475. package/dist/surface/not-enabled-error.d.ts +38 -4
  476. package/dist/surface/not-enabled-error.d.ts.map +1 -1
  477. package/dist/surface/not-enabled-error.js +96 -6
  478. package/dist/surface/profiles.d.ts +7 -0
  479. package/dist/surface/profiles.d.ts.map +1 -1
  480. package/dist/surface/resolve-command-string.d.ts +10 -0
  481. package/dist/surface/resolve-command-string.d.ts.map +1 -0
  482. package/dist/surface/resolve-command-string.js +572 -0
  483. package/dist/surface/surface-config-writer.d.ts +7 -2
  484. package/dist/surface/surface-config-writer.d.ts.map +1 -1
  485. package/dist/surface/surface-config-writer.js +25 -6
  486. package/dist/surface/surface-layer.d.ts +11 -0
  487. package/dist/surface/surface-layer.d.ts.map +1 -0
  488. package/dist/surface/surface-layer.js +11 -0
  489. package/dist/surface/surface-refusal-reason.d.ts +16 -0
  490. package/dist/surface/surface-refusal-reason.d.ts.map +1 -0
  491. package/dist/surface/surface-refusal-reason.js +16 -0
  492. package/dist/surface/surface-selector.d.ts +26 -0
  493. package/dist/surface/surface-selector.d.ts.map +1 -0
  494. package/dist/surface/surface-selector.js +42 -0
  495. package/dist/surface/surface-summary.d.ts +61 -18
  496. package/dist/surface/surface-summary.d.ts.map +1 -1
  497. package/dist/surface/surface-summary.js +147 -60
  498. package/dist/surface/tier.d.ts +85 -22
  499. package/dist/surface/tier.d.ts.map +1 -1
  500. package/dist/surface/tier.js +132 -30
  501. package/dist/usage/usage-log.d.ts +6 -1
  502. package/dist/usage/usage-log.d.ts.map +1 -1
  503. package/dist/usage/usage-log.js +7 -2
  504. package/dist/validation/run-validation-loop.d.ts +9 -0
  505. package/dist/validation/run-validation-loop.d.ts.map +1 -1
  506. package/dist/validation/run-validation-loop.js +14 -11
  507. package/dist/validation/typecheck-emitted.d.ts +8 -9
  508. package/dist/validation/typecheck-emitted.d.ts.map +1 -1
  509. package/dist/validation/typecheck-emitted.js +17 -97
  510. package/package.json +34 -34
@@ -17,35 +17,47 @@
17
17
  import { spawnSync } from 'node:child_process';
18
18
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
19
19
  import * as nodePath from 'node:path';
20
- import { resolveSourceGlobs, failsWhenEmpty } from '@shrkcrft/core';
21
- import { baselineCount, baselineFails, computeBaselineFromExtractor, diffBaseline, matchesAny, } from '@shrkcrft/boundaries';
20
+ import { coverageShortfall, failsWhenEmpty, formatEmptyRuleAdvice, normalizeRuleList, normalizeWiringSource, resolveSourceGlobs, ruleAssertsEmptyOutput, settleRuleEmptiness, unitStateLists, UnitLivenessState, } from '@shrkcrft/core';
21
+ import { settleVerdict } from "../gates/settle-verdict.js";
22
+ import { baselineCount, baselineFails, ceilingValue, clearFileReadCache, computeBaselineFromExtractor, diffBaseline, evaluateCeiling, globListSelects, planeScanExcludeDirs, readGlobListUnits, readScopeCoverage, readScopeHasUnread, settleGlobLists, sourceLivenessRequest, } from '@shrkcrft/boundaries';
22
23
  import { resolveChangedFiles, resolveProjectConfig } from '@shrkcrft/inspector';
23
24
  import { flagBool, flagString, resolveCwd, } from "../command-registry.js";
25
+ import { PositionalMode } from "../dispatch/positional-mode.js";
24
26
  import { ExitCode } from "../exit-codes.js";
25
27
  import { asJson, header, kv } from "../output/format-output.js";
26
28
  import { buildGateEnvelope } from "../gates/gate-envelope.js";
29
+ import { verdictLine } from "../gates/verdict-line.js";
30
+ import { acceptedEmptyNote } from "../gates/accepted-empty-note.js";
31
+ import { emptyRuleAdviceLines } from "../gates/empty-rule-advice-lines.js";
32
+ import { qualifyCleanForUnits } from "../gates/qualify-clean-for-units.js";
33
+ import { unitStateNotes } from "../gates/unit-state-notes.js";
34
+ import { planeVerdictForExit } from "../gates/plane-verdict.js";
35
+ import { seamRejectedRules } from "../gates/seam-rejected-rules.js";
27
36
  const SCHEMA = 'sharkcraft.baseline/v1';
28
37
  const DEFAULT_TIMEOUT_MS = 60_000;
29
38
  async function loadBaselines(cwd) {
30
39
  const loaded = await resolveProjectConfig(cwd);
31
40
  if (!loaded.ok)
32
41
  return { ok: false, message: loaded.error.message };
33
- const rel = nodePath.relative(cwd, loaded.value.sharkcraftDir).split(nodePath.sep).join('/');
34
42
  return {
35
43
  ok: true,
36
44
  value: {
37
45
  rules: loaded.value.config.baselines ?? [],
38
46
  planeDiagnostics: loaded.value.planeDiagnostics,
39
- sharkcraftDirRel: rel && !rel.startsWith('..') ? rel : '',
47
+ rejected: seamRejectedRules(loaded.value, ['baseline']),
48
+ // THE plane scan scope (one entry, or none).
49
+ sharkcraftDirRel: planeScanExcludeDirs(cwd, loaded.value.sharkcraftDir)[0] ?? '',
40
50
  },
41
51
  };
42
52
  }
43
53
  /** Narrow to `--id`, refusing an unknown id rather than silently selecting nothing. */
44
- function selectRules(rules, id) {
54
+ function selectRules(rules, id,
55
+ /** Ids that are declared but did not load (merge-seam rejections) — selectable by a verdict verb. */
56
+ extraKnown = []) {
45
57
  if (!id)
46
58
  return { ok: true, rules };
47
59
  const wanted = id.split(',').map((s) => s.trim()).filter(Boolean);
48
- const known = new Set(rules.map((r) => r.id));
60
+ const known = new Set([...rules.map((r) => r.id), ...extraKnown]);
49
61
  const unknown = wanted.filter((w) => !known.has(w));
50
62
  if (unknown.length > 0) {
51
63
  return {
@@ -59,15 +71,16 @@ function computeCurrent(cwd, rule, excludeDirs) {
59
71
  if (rule.compute.kind === 'extractor') {
60
72
  const source = rule.compute.source;
61
73
  if (!source)
62
- return { text: '', error: 'compute.kind "extractor" but no `source` declared' };
74
+ return { text: '', error: 'compute.kind "extractor" but no `source` declared', unitCount: 0 };
63
75
  const res = computeBaselineFromExtractor(cwd, source, excludeDirs);
76
+ const readScope = res.unread.length > 0 ? { readScope: { read: res.filesScanned, unread: res.unread } } : {};
64
77
  return res.error
65
- ? { text: '', error: res.error, filesScanned: res.filesScanned }
66
- : { text: res.text, filesScanned: res.filesScanned };
78
+ ? { text: '', error: res.error, filesScanned: res.filesScanned, unitCount: 0 }
79
+ : { text: res.text, filesScanned: res.filesScanned, unitCount: res.ids.length, ...readScope };
67
80
  }
68
81
  const run = rule.compute.run;
69
82
  if (!run || run.trim() === '') {
70
- return { text: '', error: 'compute.kind "command" but no `run` declared' };
83
+ return { text: '', error: 'compute.kind "command" but no `run` declared', unitCount: 0 };
71
84
  }
72
85
  const child = spawnSync(run, {
73
86
  cwd,
@@ -76,18 +89,135 @@ function computeCurrent(cwd, rule, excludeDirs) {
76
89
  timeout: rule.compute.timeoutMs ?? DEFAULT_TIMEOUT_MS,
77
90
  maxBuffer: 16 * 1024 * 1024,
78
91
  });
79
- if (child.error)
80
- return { text: '', error: `compute command failed to start: ${child.error.message}` };
92
+ // The command may rewrite files anywhere: drop every memoised tree read, so
93
+ // a read-cache window (`withFileReadCache`, e.g. `shrk quality`'s plane
94
+ // section) never serves a pre-spawn snapshot after it.
95
+ clearFileReadCache();
96
+ if (child.error) {
97
+ return { text: '', error: `compute command failed to start: ${child.error.message}`, unitCount: 0 };
98
+ }
81
99
  if (child.status !== 0) {
82
100
  const tail = String(child.stderr ?? '').trim().split('\n').slice(-3).join(' | ');
83
101
  return {
84
102
  text: '',
85
103
  error: `compute command exited ${child.status ?? 'null'}${tail ? ` — ${tail}` : ''}`,
104
+ unitCount: 0,
86
105
  };
87
106
  }
88
- return { text: String(child.stdout ?? '') };
107
+ const text = String(child.stdout ?? '');
108
+ return { text, unitCount: text.trim() === '' ? 0 : 1 };
89
109
  }
90
- export function evaluateBaselineRule(cwd, rule, excludeDirs, changedFiles) {
110
+ /** A ledger whose recompute and committed side are both empty, in its words. */
111
+ const LEDGER_EMPTY = 'the recompute produced no entries (and the committed baseline is empty too)';
112
+ /** A ceiling over a measurement that measured nothing, in its words. */
113
+ const CEILING_EMPTY = 'the compute produced nothing — a ceiling over an empty measurement proves nothing';
114
+ /** A bless (`baseline update`) whose recompute measured nothing, in its words. */
115
+ const UPDATE_EMPTY = 'the recompute produced no entries';
116
+ /**
117
+ * THE glob lists of a baseline's INPUTS (round 13): an extractor compute's
118
+ * `source` (`compute.source.files`, and `to.files` for an import-edges fence)
119
+ * and the rule's `watchFiles` — with their `expectEmpty` markers. `baseline
120
+ * check` settles it (`settleGlobLists`) and `gates coverage` builds the same
121
+ * request, so the two read one baseline's units alike. `primaryLists` names
122
+ * the lists whose liveness decides the rule's emptiness — a command compute
123
+ * has none (its `watchFiles` is a footprint, not an input it reads).
124
+ */
125
+ export function baselineLivenessRequest(cwd, rule, excludeDirs) {
126
+ const source = rule.compute.kind === 'extractor' ? rule.compute.source : undefined;
127
+ const sources = sourceLivenessRequest(cwd, source !== undefined ? [{ label: 'compute.source', source }] : [], excludeDirs, rule.id);
128
+ const watch = rule.watchFiles ?? [];
129
+ return {
130
+ request: {
131
+ subject: rule.id,
132
+ lists: [
133
+ ...sources.lists,
134
+ ...(watch.length > 0
135
+ ? [
136
+ {
137
+ list: 'watchFiles',
138
+ ...(sources.lists.length > 0 ? { label: 'watchFiles' } : {}),
139
+ globs: watch,
140
+ units: readGlobListUnits(cwd, watch, new Set(excludeDirs)),
141
+ },
142
+ ]
143
+ : []),
144
+ ],
145
+ marks: [...sources.marks, ...(rule.expectEmptyUnits ?? [])],
146
+ },
147
+ primaryLists: source !== undefined ? ['compute.source.files'] : [],
148
+ };
149
+ }
150
+ /** The unit fields every outcome carries when the rule's inputs were settled. */
151
+ function unitFieldsOf(liveness) {
152
+ if (liveness === undefined)
153
+ return {};
154
+ const nonLive = liveness.units.filter((u) => u.state !== UnitLivenessState.Live);
155
+ return {
156
+ ...(liveness.acceptance !== undefined ? { unitAcceptance: liveness.acceptance } : {}),
157
+ ...(liveness.dead.length + liveness.intendedEmpty.length + liveness.wentLive.length > 0
158
+ ? { units: unitStateLists(liveness) }
159
+ : {}),
160
+ ...(nonLive.length > 0 ? { unitLiveness: nonLive } : {}),
161
+ };
162
+ }
163
+ /**
164
+ * THE rule-emptiness settle of a baseline whose compute measured nothing
165
+ * (round 13): the ledger's empty recompute, the ceiling's empty measurement
166
+ * (P1: decided on the extractor's UNIT count, never its text — an extractor
167
+ * serialises an empty set as `[]`, so the old text test could never fire for
168
+ * one) and `baseline update`'s refusal all ask it. A fence (`expectEmpty:
169
+ * true`) is accepted only over live or intended-empty inputs; over a dead
170
+ * input it is Stale (it proves nothing).
171
+ */
172
+ function settleBaselineEmptiness(rule, computed, liveness, primaryLists, reason) {
173
+ return settleRuleEmptiness({
174
+ subject: rule.id,
175
+ unitLabel: 'entries',
176
+ filesMatched: computed.filesScanned ?? 0,
177
+ unitsMatched: 0,
178
+ unread: false,
179
+ liveness,
180
+ primaryLists,
181
+ assertsEmptyOutput: ruleAssertsEmptyOutput(rule),
182
+ failOnEmpty: failsWhenEmpty(rule),
183
+ noFilesReason: reason,
184
+ noUnitsReason: reason,
185
+ });
186
+ }
187
+ /**
188
+ * The engine entry's idempotent normalisation (round 13): the extractor
189
+ * source's markable lists and `watchFiles` into plain lists plus their
190
+ * `expectEmptyUnits`. A malformed marker is an errored rule, never a crash.
191
+ */
192
+ function normalizeBaselineRule(rule) {
193
+ const source = rule.compute.source !== undefined ? normalizeWiringSource(rule.compute.source) : undefined;
194
+ if (source !== undefined && !source.ok)
195
+ return { ok: false, error: `compute.source ${source.error.message}` };
196
+ const withSource = source !== undefined ? { ...rule, compute: { ...rule.compute, source: source.value } } : rule;
197
+ const watch = normalizeRuleList(withSource, 'watchFiles');
198
+ if (!watch.ok)
199
+ return { ok: false, error: `watchFiles ${watch.error.message}` };
200
+ return { ok: true, rule: watch.value };
201
+ }
202
+ export function evaluateBaselineRule(cwd, authored, excludeDirs, changedFiles) {
203
+ const normalized = normalizeBaselineRule(authored);
204
+ if (!normalized.ok) {
205
+ return { rule: authored, status: 'error', committedCount: 0, currentCount: 0, error: normalized.error };
206
+ }
207
+ const rule = normalized.rule;
208
+ // The rule's input globs, settled with their markers (round 13) — only when
209
+ // a unit is marked (its acceptance / went-live line rides on the outcome) or
210
+ // the compute measured nothing (what the empty rule is decided from).
211
+ const marked = (rule.expectEmptyUnits?.length ?? 0) > 0 || (rule.compute.source?.expectEmptyUnits?.length ?? 0) > 0;
212
+ let input;
213
+ const inputOf = () => {
214
+ if (input === undefined) {
215
+ const built = baselineLivenessRequest(cwd, rule, excludeDirs);
216
+ input = { liveness: settleGlobLists(built.request), primaryLists: built.primaryLists };
217
+ }
218
+ return input;
219
+ };
220
+ const markedFields = () => marked ? unitFieldsOf(inputOf().liveness) : {};
91
221
  // --changed-only is honest about what it CANNOT scope: a command compute with
92
222
  // no `watchFiles` has no file footprint, so it is reported as skipped rather
93
223
  // than quietly passing.
@@ -106,7 +236,9 @@ export function evaluateBaselineRule(cwd, rule, excludeDirs, changedFiles) {
106
236
  skipReason: 'command compute with no `watchFiles` cannot be scoped to a changeset',
107
237
  };
108
238
  }
109
- if (!changedFiles.some((f) => matchesAny(f, globs))) {
239
+ // One list (watchFiles, else the extractor's files): a changed file the
240
+ // list EXCLUDES cannot move the value, so it does not select the rule.
241
+ if (!changedFiles.some((f) => globListSelects(f, globs))) {
110
242
  return {
111
243
  rule,
112
244
  status: 'skipped',
@@ -116,6 +248,64 @@ export function evaluateBaselineRule(cwd, rule, excludeDirs, changedFiles) {
116
248
  };
117
249
  }
118
250
  }
251
+ // A ceiling rule pins a NUMBER declared in the config, so it has no committed
252
+ // artifact to read and no entry diff to compute — the comparison is the whole
253
+ // check. Branching here (rather than in a parallel engine) keeps `baseline
254
+ // check`, `gates check` and `quality` running the identical evaluation.
255
+ if (rule.mode === 'ceiling') {
256
+ const computed = computeCurrent(cwd, rule, excludeDirs);
257
+ if (computed.error) {
258
+ return { rule, status: 'error', committedCount: 0, currentCount: 0, error: computed.error, ...markedFields() };
259
+ }
260
+ const value = ceilingValue(rule, computed.text);
261
+ // A compute that MEASURED nothing measured nothing. Reporting `0 ≤ 200` as
262
+ // a pass is how a broken extractor greens a ratchet forever. Decided on the
263
+ // UNIT count (round 13, P1 — an extractor serialises its empty set as `[]`,
264
+ // so the old `text.trim() === ''` test could never fire for one) through
265
+ // THE rule-emptiness settle, the one `gates coverage` reads too.
266
+ if (computed.unitCount === 0 && !readScopeHasUnread(computed.readScope)) {
267
+ const { liveness, primaryLists } = inputOf();
268
+ const emptiness = settleBaselineEmptiness(rule, computed, liveness, primaryLists, CEILING_EMPTY);
269
+ if (emptiness.skipped) {
270
+ return {
271
+ rule,
272
+ status: emptiness.fails ? 'failed' : 'skipped',
273
+ current: computed.text,
274
+ committedCount: rule.ceiling ?? 0,
275
+ currentCount: 0,
276
+ skipReason: emptiness.skipReason ?? CEILING_EMPTY,
277
+ ...unitFieldsOf(liveness),
278
+ };
279
+ }
280
+ // Accepted: the rule asserts an empty output (`expectEmpty: true` is
281
+ // honoured on a ceiling) or every input glob is intended-empty. The
282
+ // ceiling is still judged — on the measured 0.
283
+ const judged = evaluateCeiling(rule, value);
284
+ return {
285
+ rule,
286
+ status: judged.failed ? 'failed' : 'passed',
287
+ current: computed.text,
288
+ committedCount: judged.ceiling,
289
+ currentCount: judged.value,
290
+ ceiling: judged,
291
+ ...(!judged.failed && emptiness.coverage !== undefined ? { emptyCoverage: emptiness.coverage } : {}),
292
+ ...unitFieldsOf(liveness),
293
+ };
294
+ }
295
+ const verdict = evaluateCeiling(rule, value);
296
+ return {
297
+ rule,
298
+ status: verdict.failed ? 'failed' : 'passed',
299
+ current: computed.text,
300
+ committedCount: verdict.ceiling,
301
+ currentCount: verdict.value,
302
+ ceiling: verdict,
303
+ // An unread file makes the measurement a lower bound: over the ceiling
304
+ // is still a real failure, under it is not verified.
305
+ ...(computed.readScope ? { readScope: computed.readScope } : {}),
306
+ ...markedFields(),
307
+ };
308
+ }
119
309
  const abs = nodePath.resolve(cwd, rule.baseline);
120
310
  if (!existsSync(abs)) {
121
311
  // No artifact yet. `check` must still fail (nothing to compare against),
@@ -157,11 +347,12 @@ export function evaluateBaselineRule(cwd, rule, excludeDirs, changedFiles) {
157
347
  // to prevent. But this only holds when the COMMITTED side is empty too: if
158
348
  // the baseline has entries and the recompute has none, that is real drift
159
349
  // (everything vanished) and must be reported as such, not swallowed as a skip.
350
+ const readScope = computed.readScope ? { readScope: computed.readScope } : {};
160
351
  if (currentCount === 0 && committedCount === 0) {
161
- // A fence ASSERTS emptiness, so for it the empty case is the verified pass
162
- // rather than "nothing was checked" otherwise the rule could never be
163
- // green and could not gate anything.
164
- if (rule.expectEmpty === true) {
352
+ // An empty recompute over a file the reader could not read is not "matched
353
+ // nothing": PARTIAL (the coverage names the file), never failOnEmpty's
354
+ // failure and never a pass.
355
+ if (readScopeHasUnread(computed.readScope)) {
165
356
  return {
166
357
  rule,
167
358
  status: 'passed',
@@ -169,16 +360,40 @@ export function evaluateBaselineRule(cwd, rule, excludeDirs, changedFiles) {
169
360
  current: computed.text,
170
361
  committedCount,
171
362
  currentCount,
363
+ ...readScope,
364
+ ...markedFields(),
365
+ };
366
+ }
367
+ // THE rule-emptiness settle (round 13). A FENCE (`expectEmpty: true`)
368
+ // asserts the empty OUTPUT, so over live (or intended-empty) inputs its
369
+ // empty set is the verified pass — otherwise the rule could never be green.
370
+ // Over a DEAD input it proves nothing: Stale, and failOnEmpty's 1 (V2 f1d:
371
+ // it used to be accepted). An extractor whose every source glob is marked
372
+ // `expectEmpty` and matched no file is intended-empty, accepted. Anything
373
+ // else is the loud skip it always was.
374
+ const { liveness, primaryLists } = inputOf();
375
+ const emptiness = settleBaselineEmptiness(rule, computed, liveness, primaryLists, LEDGER_EMPTY);
376
+ if (!emptiness.skipped) {
377
+ return {
378
+ rule,
379
+ status: 'passed',
380
+ committed,
381
+ current: computed.text,
382
+ committedCount,
383
+ currentCount,
384
+ ...(emptiness.coverage !== undefined ? { emptyCoverage: emptiness.coverage } : {}),
385
+ ...unitFieldsOf(liveness),
172
386
  };
173
387
  }
174
388
  return {
175
389
  rule,
176
- status: failsWhenEmpty(rule) ? 'failed' : 'skipped',
390
+ status: emptiness.fails ? 'failed' : 'skipped',
177
391
  committed,
178
392
  current: computed.text,
179
393
  committedCount,
180
394
  currentCount,
181
- skipReason: 'the recompute produced no entries (and the committed baseline is empty too)',
395
+ skipReason: emptiness.skipReason ?? LEDGER_EMPTY,
396
+ ...unitFieldsOf(liveness),
182
397
  };
183
398
  }
184
399
  const diff = diffBaseline(rule, committed, computed.text);
@@ -193,8 +408,52 @@ export function evaluateBaselineRule(cwd, rule, excludeDirs, changedFiles) {
193
408
  // A total wipe is far more often a broken compute than a real emptying —
194
409
  // say so next to the diff so it is not blessed by reflex.
195
410
  ...(currentCount === 0 ? { emptyCompute: true } : {}),
411
+ ...readScope,
412
+ ...markedFields(),
196
413
  };
197
414
  }
415
+ /**
416
+ * What one baseline rule examined — shared by `baseline check` and the
417
+ * aggregate (`gates check`, `quality`), so both report the same scope.
418
+ *
419
+ * A ledger examines the union of its committed and recomputed entries; a
420
+ * ceiling examines one measurement. A fence (`expectEmpty`) over an empty set
421
+ * is the asserted state, so its empty scope is ACCEPTED by that field — and
422
+ * printed as accepted, never silent. A skipped or errored rule examined nothing.
423
+ *
424
+ * An extractor compute that matched a file the reader did not read folds it
425
+ * in through `readScopeCoverage`, the one rule every plane uses: the record
426
+ * becomes `examined N of M files`, naming the unread file.
427
+ */
428
+ export function baselineCoverage(o) {
429
+ return readScopeCoverage(planeBaselineCoverage(o), o.readScope);
430
+ }
431
+ function planeBaselineCoverage(o) {
432
+ // The accepted empty (round 13): the fence's asserted empty set, or an
433
+ // intended-empty input — the rule-emptiness settle's own record, one wording
434
+ // on every surface (`the rule asserts an empty set` / `asserted empty — …`).
435
+ if (o.emptyCoverage !== undefined)
436
+ return o.emptyCoverage;
437
+ if (o.ceiling !== undefined)
438
+ return { unit: 'measurements', expected: 1, examined: 1 };
439
+ if (o.skipReason !== undefined) {
440
+ return { unit: 'entries', expected: 0, examined: 0, reason: o.skipReason };
441
+ }
442
+ if (o.status === 'error') {
443
+ return { unit: 'entries', expected: 0, examined: 0, reason: 'the baseline could not be evaluated' };
444
+ }
445
+ const size = Math.max(o.committedCount, o.currentCount);
446
+ return { unit: 'entries', expected: size, examined: size };
447
+ }
448
+ /**
449
+ * What this rule pins, for display. A ledger names its committed file; a
450
+ * ceiling names the number in the config, because that IS its committed value.
451
+ */
452
+ function pinLabel(rule) {
453
+ return rule.mode === 'ceiling'
454
+ ? `ceiling ${rule.ceiling ?? 0} (sharkcraft.config.ts)`
455
+ : (rule.baseline ?? '(no artifact)');
456
+ }
198
457
  function hintFor(rule) {
199
458
  return rule.hint ?? `review the diff, then bless it with \`shrk baseline update --id ${rule.id}\``;
200
459
  }
@@ -202,9 +461,11 @@ function outcomeJson(o) {
202
461
  return {
203
462
  id: o.rule.id,
204
463
  ...(o.rule.description ? { description: o.rule.description } : {}),
205
- baseline: o.rule.baseline,
464
+ ...(o.rule.baseline ? { baseline: o.rule.baseline } : {}),
465
+ mode: o.rule.mode ?? 'ledger',
206
466
  severity: o.rule.severity ?? 'error',
207
- direction: o.rule.direction ?? 'two-way',
467
+ direction: o.rule.direction ?? (o.rule.mode === 'ceiling' ? 'at-most' : 'two-way'),
468
+ ...(o.ceiling ? { ceiling: o.ceiling } : {}),
208
469
  status: o.status,
209
470
  committedCount: o.committedCount,
210
471
  currentCount: o.currentCount,
@@ -241,7 +502,13 @@ async function prepare(args, opts) {
241
502
  process.stderr.write(`Could not load config: ${loaded.message}\n Run \`shrk doctor\` for details.\n`);
242
503
  return { ok: false, code: ExitCode.UsageError };
243
504
  }
244
- const selected = selectRules(loaded.value.rules, flagString(args, 'id') ?? undefined);
505
+ const idFlag = flagString(args, 'id') ?? undefined;
506
+ // A verdict verb may select a rejected baseline by id (its errored row); a
507
+ // verb that acts on a rule (update / diff / explain) cannot — there is none.
508
+ const rejectedKnown = opts.rejectedKnown === true ? loaded.value.rejected.map((r) => r.id) : [];
509
+ const selected = selectRules(loaded.value.rules, idFlag, rejectedKnown);
510
+ const wantedIds = idFlag ? idFlag.split(',').map((s) => s.trim()).filter(Boolean) : undefined;
511
+ const rejected = wantedIds ? loaded.value.rejected.filter((r) => wantedIds.includes(r.id)) : loaded.value.rejected;
245
512
  if (!selected.ok) {
246
513
  process.stderr.write(selected.message + '\n');
247
514
  return { ok: false, code: ExitCode.UsageError };
@@ -265,19 +532,53 @@ async function prepare(args, opts) {
265
532
  excludeDirs: loaded.value.sharkcraftDirRel ? [loaded.value.sharkcraftDirRel] : [],
266
533
  ...(changedFiles !== undefined ? { changedFiles } : {}),
267
534
  planeDiagnostics: loaded.value.planeDiagnostics,
535
+ rejected,
268
536
  };
269
537
  }
270
- /** The "no baselines declared" landing, shared by every subverb. */
271
- function writeNoRules(json) {
538
+ /** ` ✗ <id> REJECTED <why>` a pack rule the merge seam refused (declared, never run). */
539
+ function writeRejected(rejected) {
540
+ for (const r of rejected)
541
+ process.stdout.write(` ✗ ${r.id} REJECTED — ${r.error ?? 'failed validation'}\n`);
542
+ }
543
+ /**
544
+ * The "no baselines declared" landing, shared by every subverb. A VERDICT verb
545
+ * passes its name so its JSON still carries the settled `gate` envelope —
546
+ * nothing declared is `2`, never a pass.
547
+ */
548
+ function writeNoRules(json, verb) {
549
+ // A VERDICT verb settles first and renders second, in text AND JSON: nothing
550
+ // declared proposes 0 and the run coverage (expected 0) settles it to 2. The
551
+ // exit comes from the envelope — never a hard-coded code — so text, JSON and
552
+ // gate.exit cannot disagree. List / explain subverbs stay informational.
553
+ const gate = verb !== undefined
554
+ ? buildGateEnvelope(verb, ExitCode.VerifiedPass, [], {
555
+ unit: 'baselines',
556
+ expected: 0,
557
+ examined: 0,
558
+ reason: 'no baselines[] declared',
559
+ })
560
+ : undefined;
561
+ const exit = gate?.exit ?? ExitCode.NotVerified;
272
562
  if (json) {
273
- process.stdout.write(asJson({ schema: SCHEMA, results: [], evaluated: 0, verdict: 'not-verified' }) + '\n');
274
- return ExitCode.NotVerified;
563
+ process.stdout.write(asJson({
564
+ schema: SCHEMA,
565
+ results: [],
566
+ evaluated: 0,
567
+ verdict: gate ? planeVerdictForExit(gate.exit) : 'not-verified',
568
+ ...(gate ? { exitCode: gate.exit, gate } : {}),
569
+ }) + '\n');
570
+ return exit;
275
571
  }
276
572
  process.stdout.write(header('Baselines'));
277
573
  process.stdout.write(' No baselines declared. Add `baselines[]` to sharkcraft.config.ts to replace a\n' +
278
574
  ' hand-rolled "committed file + recompute script + drift test" trio with one\n' +
279
575
  ' two-way engine (see docs/baseline-drift.md).\n');
280
- return ExitCode.NotVerified;
576
+ if (gate) {
577
+ const line = verdictLine(gate, 'Nothing declared — accepted.');
578
+ if (line)
579
+ process.stdout.write(`\n${line}\n`);
580
+ }
581
+ return exit;
281
582
  }
282
583
  export const baselineListCommand = {
283
584
  name: 'list',
@@ -289,11 +590,13 @@ export const baselineListCommand = {
289
590
  if (!prep.ok)
290
591
  return prep.code;
291
592
  const json = flagBool(args, 'json');
292
- if (prep.all.length === 0)
593
+ if (prep.all.length === 0 && prep.rejected.length === 0)
293
594
  return writeNoRules(json);
294
595
  if (json) {
295
596
  process.stdout.write(asJson({
296
597
  schema: SCHEMA,
598
+ // Declared by a pack, refused by the merge seam: never computed.
599
+ rejected: prep.rejected.map((r) => ({ id: r.id, error: r.error ?? null })),
297
600
  baselines: prep.all.map((r) => ({
298
601
  id: r.id,
299
602
  description: r.description ?? null,
@@ -301,7 +604,9 @@ export const baselineListCommand = {
301
604
  compute: r.compute.kind,
302
605
  direction: r.direction ?? 'two-way',
303
606
  keyBy: r.keyBy ?? null,
304
- failOnEmpty: r.failOnEmpty === true,
607
+ // The EFFECTIVE failOnEmpty (round 13) — THE authority, never the
608
+ // raw field: an error rule fails on empty by default.
609
+ failOnEmpty: failsWhenEmpty(r),
305
610
  })),
306
611
  diagnostics: prep.planeDiagnostics,
307
612
  }) + '\n');
@@ -312,10 +617,14 @@ export const baselineListCommand = {
312
617
  process.stdout.write(` • ${r.id} → ${r.baseline}\n`);
313
618
  process.stdout.write(` compute ${r.compute.kind}${r.compute.kind === 'command' ? ` (${r.compute.run})` : ''}` +
314
619
  ` · direction ${r.direction ?? 'two-way'}${r.keyBy ? ` · keyBy ${r.keyBy}` : ''}` +
315
- `${r.failOnEmpty ? ' · failOnEmpty' : ''}\n`);
620
+ `${failsWhenEmpty(r) ? ' · failOnEmpty' : ''}\n`);
316
621
  if (r.description)
317
622
  process.stdout.write(` ${r.description}\n`);
318
623
  }
624
+ if (prep.rejected.length > 0) {
625
+ process.stdout.write(`\n rejected at the pack-plane merge seam — never computed (${prep.rejected.length}):\n`);
626
+ writeRejected(prep.rejected);
627
+ }
319
628
  for (const d of prep.planeDiagnostics)
320
629
  process.stdout.write(` ! ${d}\n`);
321
630
  return ExitCode.VerifiedPass;
@@ -327,52 +636,87 @@ export const baselineCheckCommand = {
327
636
  usage: 'shrk baseline check [--id <ids>] [--changed-only] [--since <ref>] [--json]',
328
637
  booleanFlags: new Set(['json', 'changed-only']),
329
638
  async run(args) {
330
- const prep = await prepare(args, { changedAware: true });
639
+ const prep = await prepare(args, { changedAware: true, rejectedKnown: true });
331
640
  if (!prep.ok)
332
641
  return prep.code;
333
642
  const json = flagBool(args, 'json');
334
- if (prep.rules.length === 0)
335
- return writeNoRules(json);
643
+ // A pack baseline the merge seam rejected is a declared baseline that was
644
+ // never computed — an errored row, exit 1 (round 12 review, R12-X1).
645
+ const rejected = prep.rejected;
646
+ if (prep.rules.length === 0 && rejected.length === 0)
647
+ return writeNoRules(json, 'baseline check');
336
648
  const outcomes = prep.rules.map((r) => evaluateBaselineRule(prep.cwd, r, prep.excludeDirs, prep.changedFiles));
337
649
  const failed = outcomes.filter((o) => o.status === 'failed' || (o.status === 'error' && (o.rule.severity ?? 'error') === 'error'));
338
650
  const evaluated = outcomes.filter((o) => o.status !== 'skipped').length;
339
651
  // 0 only when a NON-EMPTY scope was actually compared; 2 when nothing was.
340
652
  // A skipped baseline is "partially verified", never a green 0.
341
653
  const skippedCount = outcomes.filter((o) => o.status === 'skipped').length;
342
- const exit = failed.length > 0
654
+ const proposed = failed.length > 0 || rejected.length > 0
343
655
  ? ExitCode.Failure
344
656
  : evaluated === 0 || skippedCount > 0
345
657
  ? ExitCode.NotVerified
346
658
  : ExitCode.VerifiedPass;
659
+ // Settle first, render second: one envelope for text AND JSON, and the ✓
660
+ // line is printed only from the settled verdict.
661
+ const unexamined = [
662
+ ...outcomes.filter((o) => o.status === 'skipped' || o.status === 'error').map((o) => o.rule.id),
663
+ ...rejected.map((r) => r.id),
664
+ ];
665
+ const env = buildGateEnvelope('baseline check', proposed, [...outcomes.map((o) => ({
666
+ id: o.rule.id,
667
+ type: 'baseline',
668
+ status: o.status,
669
+ severity: o.rule.severity ?? 'error',
670
+ counts: { committed: o.committedCount, current: o.currentCount },
671
+ violations: [
672
+ ...(o.diff?.added ?? []).map((id) => ({ id, message: 'added', hint: hintFor(o.rule) })),
673
+ ...(o.diff?.removed ?? []).map((id) => ({ id, message: 'removed', hint: hintFor(o.rule) })),
674
+ ],
675
+ ...(o.skipReason ? { skipReason: o.skipReason } : {}),
676
+ ...(o.error ? { error: o.error } : {}),
677
+ coverage: baselineCoverage(o),
678
+ // The rule's `expectEmpty` acceptance and unit lines (round 13), folded
679
+ // into the envelope's one settle.
680
+ ...(o.unitAcceptance !== undefined ? { unitAcceptance: o.unitAcceptance } : {}),
681
+ ...(o.units !== undefined ? { units: o.units } : {}),
682
+ })), ...rejected], {
683
+ unit: 'baselines',
684
+ expected: outcomes.length + rejected.length,
685
+ examined: outcomes.length + rejected.length - unexamined.length,
686
+ ...(unexamined.length > 0 ? { unexamined, reason: 'compared nothing or could not run' } : {}),
687
+ });
688
+ const exit = env.exit;
347
689
  if (json) {
348
690
  process.stdout.write(asJson({
349
691
  schema: SCHEMA,
350
692
  results: outcomes.map(outcomeJson),
693
+ // Pack baselines the merge seam refused — errored rows in `gate.rules`.
694
+ rejected: rejected.map((r) => ({ id: r.id, error: r.error ?? null })),
351
695
  evaluated,
352
- skipped: outcomes.filter((o) => o.status === 'skipped').length,
353
- verdict: failed.length > 0 ? 'errors' : evaluated === 0 ? 'not-verified' : 'pass',
696
+ skipped: skippedCount,
697
+ verdict: exit === ExitCode.Failure ? 'errors' : exit === ExitCode.VerifiedPass ? 'pass' : 'not-verified',
354
698
  diagnostics: prep.planeDiagnostics,
355
- gate: buildGateEnvelope('baseline check', exit, outcomes.map((o) => ({
356
- id: o.rule.id,
357
- type: 'baseline',
358
- status: o.status,
359
- severity: o.rule.severity ?? 'error',
360
- counts: { committed: o.committedCount, current: o.currentCount },
361
- violations: [
362
- ...(o.diff?.added ?? []).map((id) => ({ id, message: 'added', hint: hintFor(o.rule) })),
363
- ...(o.diff?.removed ?? []).map((id) => ({ id, message: 'removed', hint: hintFor(o.rule) })),
364
- ],
365
- ...(o.skipReason ? { skipReason: o.skipReason } : {}),
366
- ...(o.error ? { error: o.error } : {}),
367
- }))),
699
+ exitCode: exit,
700
+ gate: env,
368
701
  }) + '\n');
369
702
  return exit;
370
703
  }
371
704
  process.stdout.write(header('Baseline drift'));
372
- process.stdout.write(kv('evaluated', `${evaluated} of ${prep.rules.length}`) + '\n');
705
+ // Round 13 (K6): the printed count is the envelope's (`gate.evaluated`), which
706
+ // never counts a rule accepted as intended-empty — it is named apart.
707
+ process.stdout.write(kv('evaluated', `${env.evaluated} of ${prep.rules.length + rejected.length}${acceptedEmptyNote(env.acceptedEmpty)}` +
708
+ (rejected.length > 0 ? ` (${rejected.length} rejected at the pack-plane merge seam — NOT evaluated)` : '')) + '\n');
709
+ writeRejected(rejected);
373
710
  for (const o of outcomes) {
374
711
  if (o.status === 'passed') {
375
- process.stdout.write(` ✓ ${o.rule.id} (${o.currentCount} entries, no drift)\n`);
712
+ // Rendered from the SETTLED rule: a pass over part of its scope is
713
+ // `partial`, never a ✓ (the keystone emitter pattern, step 5).
714
+ const settledRule = env.rules.find((x) => x.id === o.rule.id);
715
+ process.stdout.write(settledRule?.status === 'partial'
716
+ ? ` ~ ${o.rule.id} PARTIAL — ${settledRule.shortfall ?? 'part of its scope was not examined'}\n`
717
+ : o.ceiling
718
+ ? ` ✓ ${o.rule.id} (${o.ceiling.value} ${o.ceiling.direction} ${o.ceiling.ceiling} — ${o.ceiling.slack} to spare)\n`
719
+ : ` ✓ ${o.rule.id} (${o.currentCount} entries, no drift)\n`);
376
720
  continue;
377
721
  }
378
722
  if (o.status === 'skipped') {
@@ -387,6 +731,24 @@ export const baselineCheckCommand = {
387
731
  }
388
732
  continue;
389
733
  }
734
+ // A rule that measured nothing and FAILS on it (failOnEmpty, or a fence
735
+ // over a dead input) has no diff — its reason is the only true line. It is
736
+ // never rendered as DRIFT with a bless hint (round 13, P1): blessing it
737
+ // would commit the empty measurement the loud skip refused.
738
+ if (o.status === 'failed' && o.skipReason !== undefined) {
739
+ process.stdout.write(` ✗ ${o.rule.id} FAILED — ${o.skipReason}\n`);
740
+ if (!ruleAssertsEmptyOutput(o.rule)) {
741
+ process.stdout.write(` → ${formatEmptyRuleAdvice({ fails: true })}\n`);
742
+ }
743
+ continue;
744
+ }
745
+ if (o.ceiling) {
746
+ const over = -o.ceiling.slack;
747
+ process.stdout.write(` ✗ ${o.rule.id} OVER CEILING — ${o.ceiling.value} is ${over} ` +
748
+ `${o.ceiling.direction === 'at-most' ? 'above' : 'below'} the limit of ${o.ceiling.ceiling}\n` +
749
+ ` lower the measurement, or raise the ceiling deliberately: \`shrk baseline update --id ${o.rule.id}\`\n`);
750
+ continue;
751
+ }
390
752
  const added = o.diff?.added.length ?? 0;
391
753
  const removed = o.diff?.removed.length ?? 0;
392
754
  process.stdout.write(` ✗ ${o.rule.id} DRIFT — ${added} added, ${removed} removed ` +
@@ -399,12 +761,29 @@ export const baselineCheckCommand = {
399
761
  }
400
762
  for (const d of prep.planeDiagnostics)
401
763
  process.stdout.write(` ! ${d}\n`);
402
- if (exit === ExitCode.NotVerified) {
403
- process.stdout.write('\nNothing was compared this is NOT a pass. Every selected baseline was skipped.\n');
404
- }
405
- else if (exit === ExitCode.VerifiedPass) {
406
- process.stdout.write('\nEvery baseline matches its committed artifact. ✓\n');
764
+ // THE empty-rule advice (round 13) for a SOFT skip: a failing one is
765
+ // advised inline (above); a rule that matched nothing and does not fail on
766
+ // it was told nothing.
767
+ for (const a of emptyRuleAdviceLines(outcomes.filter((o) => o.status === 'skipped' && o.skipReason !== undefined).map(() => ({ fails: false })))) {
768
+ process.stdout.write(` ${a}.\n`);
407
769
  }
770
+ // THE shared unit-state block (round 13, K2): a dead input glob — a dead
771
+ // import-edges `to.files` under an accepted fence included — and a LOCAL
772
+ // expectEmpty marker whose target appeared withhold the ✓ (exit
773
+ // unchanged); a pack marker is INFO.
774
+ const unitNotes = unitStateNotes(outcomes.map((o) => ({
775
+ id: o.rule.id,
776
+ ...(o.unitLiveness !== undefined ? { unitLiveness: o.unitLiveness } : {}),
777
+ reportedEmpty: o.status === 'skipped' || o.skipReason !== undefined,
778
+ })));
779
+ process.stdout.write(unitNotes.text);
780
+ const line = verdictLine(env, qualifyCleanForUnits(outcomes.some((o) => o.ceiling)
781
+ ? 'Every baseline is within its pinned value. ✓'
782
+ : 'Every baseline matches its committed artifact. ✓', unitNotes), proposed === ExitCode.NotVerified && evaluated === 0
783
+ ? 'Nothing was compared — this is NOT a pass. Every selected baseline was skipped.'
784
+ : undefined);
785
+ if (line)
786
+ process.stdout.write(`\n${line}\n`);
408
787
  return exit;
409
788
  },
410
789
  };
@@ -429,7 +808,13 @@ export const baselineDiffCommand = {
429
808
  for (const o of outcomes) {
430
809
  const added = o.diff?.added.length ?? 0;
431
810
  const removed = o.diff?.removed.length ?? 0;
432
- process.stdout.write(`\n${o.rule.id} (${o.rule.baseline}) ${o.status === 'error' ? `! ${o.error}` : `+${added} / -${removed}`}\n`);
811
+ process.stdout.write(`\n${o.rule.id} (${pinLabel(o.rule)}) ` +
812
+ (o.status === 'error'
813
+ ? `! ${o.error}`
814
+ : o.ceiling
815
+ ? `${o.ceiling.value} vs ${o.ceiling.direction} ${o.ceiling.ceiling} (slack ${o.ceiling.slack})`
816
+ : `+${added} / -${removed}`) +
817
+ '\n');
433
818
  writeDiff(o, 100);
434
819
  }
435
820
  return ExitCode.VerifiedPass;
@@ -450,19 +835,101 @@ export const baselineUpdateCommand = {
450
835
  return writeNoRules(json);
451
836
  const written = [];
452
837
  const errors = [];
838
+ // A ceiling's pinned value lives in `sharkcraft.config.ts`, so blessing it
839
+ // is a config edit, not a file write. `update` prints the exact one-line
840
+ // change instead of touching the config — the same explicit, reviewable
841
+ // bless, without this command growing the ability to rewrite the rules it
842
+ // is enforcing.
843
+ const reblessed = [];
844
+ // A value recomputed from an incomplete read (a file over the read cap)
845
+ // misses whatever the unread file holds. Blessing it would commit a number
846
+ // the loud-skip rule forbids reporting, and the next `baseline check`
847
+ // would be NOT VERIFIED anyway. So the rule is refused and named, and the
848
+ // run settles NOT VERIFIED (2), never "wrote … (exit 0)".
849
+ const unverified = [];
850
+ const unverifiedCoverage = [];
851
+ // Round 13 (K9): ceilings whose measurement is an EMPTY one the settle does
852
+ // not accept — the loud skip `baseline check` reports, never a proposal.
853
+ const emptyCeilings = [];
453
854
  for (const rule of prep.rules) {
454
855
  const computed = computeCurrent(prep.cwd, rule, prep.excludeDirs);
455
856
  if (computed.error) {
456
857
  errors.push({ id: rule.id, error: computed.error });
457
858
  continue;
458
859
  }
459
- if (computed.text.trim() === '' && failsWhenEmpty(rule)) {
460
- errors.push({
860
+ if (readScopeHasUnread(computed.readScope)) {
861
+ const record = {
862
+ ...readScopeCoverage({ unit: 'baseline entries', expected: 1, examined: 1 }, computed.readScope),
863
+ subject: rule.id,
864
+ };
865
+ unverifiedCoverage.push(record);
866
+ unverified.push({
867
+ id: rule.id,
868
+ shortfall: coverageShortfall(record) ?? '',
869
+ unread: (computed.readScope?.unread ?? []).map((u) => u.path),
870
+ });
871
+ continue;
872
+ }
873
+ if (rule.mode === 'ceiling') {
874
+ // K9: never propose `ceiling: <measured>` over an EMPTY measurement.
875
+ // THE rule-emptiness settle `baseline check` reads decides it (on the
876
+ // extractor's UNIT count): an accepted empty (a fence, an intended-
877
+ // empty input) is judged like any value; a stale one is the loud skip
878
+ // — 2 NOT VERIFIED, or 1 under failOnEmpty — never `ceiling: 0`.
879
+ if (computed.unitCount === 0) {
880
+ const input = baselineLivenessRequest(prep.cwd, rule, prep.excludeDirs);
881
+ const emptiness = settleBaselineEmptiness(rule, computed, settleGlobLists(input.request), input.primaryLists, CEILING_EMPTY);
882
+ if (emptiness.skipped) {
883
+ const reason = emptiness.skipReason ?? CEILING_EMPTY;
884
+ if (emptiness.fails) {
885
+ errors.push({
886
+ id: rule.id,
887
+ error: `refusing to propose a ceiling from an EMPTY measurement for a \`failOnEmpty\` rule — ${reason} — fix the compute first`,
888
+ });
889
+ }
890
+ else {
891
+ unverifiedCoverage.push({ unit: 'measurements', expected: 1, examined: 0, subject: rule.id, reason });
892
+ emptyCeilings.push({ id: rule.id, reason });
893
+ }
894
+ continue;
895
+ }
896
+ }
897
+ const value = ceilingValue(rule, computed.text);
898
+ reblessed.push({
461
899
  id: rule.id,
462
- error: 'refusing to write an EMPTY baseline for a `failOnEmpty` rule fix the compute first',
900
+ from: rule.ceiling ?? 0,
901
+ to: value,
902
+ edit: `ceiling: ${value}, // was ${rule.ceiling ?? 0}`,
463
903
  });
464
904
  continue;
465
905
  }
906
+ // An EMPTY bless is decided by the compute's UNIT count and THE
907
+ // rule-emptiness settle `baseline check` reads (round 13, P1) — never by
908
+ // its text: an extractor serialises its empty set as `[]`, so the old
909
+ // `text.trim() === ''` test never refused one, and a failOnEmpty ledger
910
+ // over a dead selector was blessed empty. A fence's asserted-empty output
911
+ // over live inputs, or an intended-empty input, is blessable; a stale
912
+ // empty (a dead or unmarked selector, files that yielded nothing) of a
913
+ // failOnEmpty rule is refused, naming why.
914
+ if (computed.unitCount === 0) {
915
+ const input = baselineLivenessRequest(prep.cwd, rule, prep.excludeDirs);
916
+ const liveness = settleGlobLists(input.request);
917
+ // Name the dead input the settle found (its own `.dead` lines), so the
918
+ // refusal says WHICH selector to fix.
919
+ const deadInputs = liveness.dead.map((u) => `${u.label}: ${u.deadReason ?? 'matched 0 files'}`);
920
+ const reason = deadInputs.length > 0
921
+ ? `${UPDATE_EMPTY} — its input selector matched nothing (${deadInputs.join('; ')})`
922
+ : UPDATE_EMPTY;
923
+ const emptiness = settleBaselineEmptiness(rule, computed, liveness, input.primaryLists, reason);
924
+ if (emptiness.skipped && emptiness.fails) {
925
+ errors.push({
926
+ id: rule.id,
927
+ error: 'refusing to write an EMPTY baseline for a `failOnEmpty` rule — ' +
928
+ `${emptiness.skipReason ?? reason} — fix the compute first`,
929
+ });
930
+ continue;
931
+ }
932
+ }
466
933
  const abs = nodePath.resolve(prep.cwd, rule.baseline);
467
934
  const previous = existsSync(abs) ? readFileSync(abs, 'utf8') : undefined;
468
935
  const changed = previous !== computed.text;
@@ -472,20 +939,56 @@ export const baselineUpdateCommand = {
472
939
  }
473
940
  written.push({ id: rule.id, path: rule.baseline, bytes: computed.text.length, changed });
474
941
  }
942
+ const settled = settleVerdict(errors.length > 0 ? ExitCode.Failure : ExitCode.VerifiedPass, unverifiedCoverage);
943
+ const exit = settled.exit;
475
944
  if (json) {
476
- process.stdout.write(asJson({ schema: SCHEMA, dryRun, written, errors }) + '\n');
477
- return errors.length > 0 ? ExitCode.Failure : ExitCode.VerifiedPass;
945
+ process.stdout.write(asJson({
946
+ schema: SCHEMA,
947
+ dryRun,
948
+ written,
949
+ reblessed,
950
+ errors,
951
+ ...(unverified.length > 0 ? { unverified } : {}),
952
+ // K9: a ceiling over an empty measurement is the loud skip, never a
953
+ // proposal — named here with the reason `baseline check` prints.
954
+ ...(emptyCeilings.length > 0 ? { skipped: emptyCeilings } : {}),
955
+ ...(unverified.length > 0 || emptyCeilings.length > 0 ? { shortfalls: settled.shortfalls } : {}),
956
+ exitCode: exit,
957
+ }) + '\n');
958
+ return exit;
478
959
  }
479
960
  process.stdout.write(header(dryRun ? 'Baseline update (dry run)' : 'Baseline update'));
480
961
  for (const w of written) {
481
962
  process.stdout.write(` ${w.changed ? (dryRun ? 'would write' : 'wrote') : 'unchanged '} ${w.path} (${w.bytes} bytes)\n`);
482
963
  }
964
+ for (const r of reblessed) {
965
+ const verb = r.from === r.to ? 'unchanged ' : 'edit config';
966
+ process.stdout.write(` ${verb} ${r.id} (ceiling ${r.from} → ${r.to})\n`);
967
+ if (r.from !== r.to)
968
+ process.stdout.write(` ${r.edit}\n`);
969
+ }
483
970
  for (const e of errors)
484
971
  process.stdout.write(` ! ${e.id}: ${e.error}\n`);
972
+ for (const u of unverified) {
973
+ process.stdout.write(` ! ${u.id}: not written — computed from an incomplete read (${u.unread.join(', ')})\n`);
974
+ }
975
+ for (const s of emptyCeilings) {
976
+ process.stdout.write(` – ${s.id}: no ceiling proposed — ${s.reason}\n`);
977
+ }
978
+ if (reblessed.some((r) => r.from !== r.to)) {
979
+ process.stdout.write('\nA ceiling lives in sharkcraft.config.ts — apply the line above by hand so raising it stays a reviewed diff.\n');
980
+ }
485
981
  if (written.some((w) => w.changed) && !dryRun) {
486
982
  process.stdout.write('\nReview the diff before committing — this is the bless step.\n');
487
983
  }
488
- return errors.length > 0 ? ExitCode.Failure : ExitCode.VerifiedPass;
984
+ const line = verdictLine(settled, '', unverified.length > 0
985
+ ? 'Nothing was blessed for the baseline(s) above: their value came from an incomplete read.'
986
+ : emptyCeilings.length > 0
987
+ ? 'No ceiling was proposed for the baseline(s) above: their measurement was empty — fix the compute, or mark a planned input { pattern, expectEmpty: true }.'
988
+ : undefined);
989
+ if (line)
990
+ process.stdout.write(`\n${line}\n`);
991
+ return exit;
489
992
  },
490
993
  };
491
994
  export const baselineExplainCommand = {
@@ -520,18 +1023,21 @@ export const baselineExplainCommand = {
520
1023
  process.stdout.write(header(`Baseline: ${rule.id}`));
521
1024
  if (rule.description)
522
1025
  process.stdout.write(` ${rule.description}\n`);
523
- process.stdout.write(kv('committed', rule.baseline) + '\n');
1026
+ process.stdout.write(kv('committed', pinLabel(rule)) + '\n');
524
1027
  process.stdout.write(kv('compute', rule.compute.kind === 'command'
525
1028
  ? `command · ${rule.compute.run}`
526
1029
  : `extractor · ${rule.compute.source?.extract ?? 'sugar'}` +
527
1030
  (rule.compute.source?.$use ? ` (via $use:${rule.compute.source.$use})` : '')) + '\n');
528
- process.stdout.write(kv('direction', rule.direction ?? 'two-way') + '\n');
1031
+ process.stdout.write(kv('direction', rule.direction ?? (rule.mode === 'ceiling' ? 'at-most' : 'two-way')) + '\n');
529
1032
  process.stdout.write(kv('canonical', outcome.diff?.canonical ?? rule.compute.canonical ?? 'auto') + '\n');
530
1033
  if (rule.keyBy)
531
1034
  process.stdout.write(kv('keyBy', rule.keyBy) + '\n');
532
- process.stdout.write(kv('entries', outcome.missingBaseline
533
- ? `committed (none yet) → ${outcome.currentCount} now`
534
- : `${outcome.committedCount} committed ${outcome.currentCount} now`) + '\n');
1035
+ process.stdout.write(kv(outcome.ceiling ? 'measured' : 'entries', outcome.ceiling
1036
+ ? `${outcome.ceiling.value} ${outcome.ceiling.direction} ${outcome.ceiling.ceiling}` +
1037
+ ` (${outcome.ceiling.slack >= 0 ? `${outcome.ceiling.slack} to spare` : `${-outcome.ceiling.slack} over`})`
1038
+ : outcome.missingBaseline
1039
+ ? `committed (none yet) → ${outcome.currentCount} now`
1040
+ : `${outcome.committedCount} committed → ${outcome.currentCount} now`) + '\n');
535
1041
  process.stdout.write(kv('status', outcome.status) + '\n');
536
1042
  if (outcome.error)
537
1043
  process.stdout.write(` ! ${outcome.error}\n`);
@@ -548,11 +1054,13 @@ export const baselineCommand = {
548
1054
  name: 'baseline',
549
1055
  description: 'Committed-baseline drift engine: recompute a ledger/digest/allow-list and fail on drift in BOTH directions. Read-only except `update`.',
550
1056
  usage: 'shrk baseline list | check | diff | update | explain --id <id>',
1057
+ // Every subverb is a registered trie child; any other bare token is refused
1058
+ // by the dispatcher guard (closest match named) before this body runs — the
1059
+ // one unknown-subcommand authority. Only a bare `shrk baseline` lands here.
1060
+ positionals: PositionalMode.None,
551
1061
  booleanFlags: new Set(['json', 'changed-only', 'dry-run']),
552
- async run(args) {
553
- const sub = args.positional[0];
554
- process.stderr.write((sub ? `Unknown subcommand "${sub}". ` : '') +
555
- 'Usage: shrk baseline list | check [--id X] | diff | update | explain --id <id>\n');
1062
+ async run() {
1063
+ process.stderr.write('Usage: shrk baseline list | check [--id X] | diff | update | explain --id <id>\n');
556
1064
  return ExitCode.UsageError;
557
1065
  },
558
1066
  };