@shrkcrft/cli 0.1.0-alpha.29 → 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 +76 -0
  20. package/dist/commands/baseline.command.d.ts.map +1 -1
  21. package/dist/commands/baseline.command.js +599 -75
  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 +164 -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 +1260 -324
  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 +8 -1
  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 +20 -0
  77. package/dist/commands/docs-references.command.d.ts.map +1 -0
  78. package/dist/commands/docs-references.command.js +520 -0
  79. package/dist/commands/doctor.command.d.ts.map +1 -1
  80. package/dist/commands/doctor.command.js +60 -5
  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 +54 -0
  99. package/dist/commands/gates.command.d.ts.map +1 -1
  100. package/dist/commands/gates.command.js +1314 -64
  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 +62 -0
  104. package/dist/commands/generated.command.d.ts.map +1 -1
  105. package/dist/commands/generated.command.js +639 -156
  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 +203 -25
  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 +362 -74
  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 +67 -0
  328. package/dist/gates/gate-rule-globs.d.ts.map +1 -0
  329. package/dist/gates/gate-rule-globs.js +139 -0
  330. package/dist/gates/gate-rule-view.d.ts +25 -6
  331. package/dist/gates/gate-rule-view.d.ts.map +1 -1
  332. package/dist/gates/gate-rule-view.js +105 -77
  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 +225 -4
  370. package/dist/gates/rule-coverage.d.ts.map +1 -1
  371. package/dist/gates/rule-coverage.js +610 -43
  372. package/dist/gates/run-gate-planes.d.ts +58 -0
  373. package/dist/gates/run-gate-planes.d.ts.map +1 -0
  374. package/dist/gates/run-gate-planes.js +403 -0
  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 +204 -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 { 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
- function evaluateRule(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.
@@ -95,7 +225,7 @@ function evaluateRule(cwd, rule, excludeDirs, changedFiles) {
95
225
  const globs = rule.watchFiles && rule.watchFiles.length > 0
96
226
  ? rule.watchFiles
97
227
  : rule.compute.kind === 'extractor'
98
- ? (rule.compute.source?.files ?? [])
228
+ ? (resolveSourceGlobs(rule.compute.source ?? { files: [] }))
99
229
  : undefined;
100
230
  if (globs === undefined) {
101
231
  return {
@@ -106,7 +236,9 @@ function evaluateRule(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 @@ function evaluateRule(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,15 +347,53 @@ function evaluateRule(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) {
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)) {
356
+ return {
357
+ rule,
358
+ status: 'passed',
359
+ committed,
360
+ current: computed.text,
361
+ committedCount,
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),
386
+ };
387
+ }
161
388
  return {
162
389
  rule,
163
- status: failsWhenEmpty(rule) ? 'failed' : 'skipped',
390
+ status: emptiness.fails ? 'failed' : 'skipped',
164
391
  committed,
165
392
  current: computed.text,
166
393
  committedCount,
167
394
  currentCount,
168
- skipReason: 'the recompute produced no entries (and the committed baseline is empty too)',
395
+ skipReason: emptiness.skipReason ?? LEDGER_EMPTY,
396
+ ...unitFieldsOf(liveness),
169
397
  };
170
398
  }
171
399
  const diff = diffBaseline(rule, committed, computed.text);
@@ -180,8 +408,52 @@ function evaluateRule(cwd, rule, excludeDirs, changedFiles) {
180
408
  // A total wipe is far more often a broken compute than a real emptying —
181
409
  // say so next to the diff so it is not blessed by reflex.
182
410
  ...(currentCount === 0 ? { emptyCompute: true } : {}),
411
+ ...readScope,
412
+ ...markedFields(),
183
413
  };
184
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
+ }
185
457
  function hintFor(rule) {
186
458
  return rule.hint ?? `review the diff, then bless it with \`shrk baseline update --id ${rule.id}\``;
187
459
  }
@@ -189,9 +461,11 @@ function outcomeJson(o) {
189
461
  return {
190
462
  id: o.rule.id,
191
463
  ...(o.rule.description ? { description: o.rule.description } : {}),
192
- baseline: o.rule.baseline,
464
+ ...(o.rule.baseline ? { baseline: o.rule.baseline } : {}),
465
+ mode: o.rule.mode ?? 'ledger',
193
466
  severity: o.rule.severity ?? 'error',
194
- 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 } : {}),
195
469
  status: o.status,
196
470
  committedCount: o.committedCount,
197
471
  currentCount: o.currentCount,
@@ -228,7 +502,13 @@ async function prepare(args, opts) {
228
502
  process.stderr.write(`Could not load config: ${loaded.message}\n Run \`shrk doctor\` for details.\n`);
229
503
  return { ok: false, code: ExitCode.UsageError };
230
504
  }
231
- 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;
232
512
  if (!selected.ok) {
233
513
  process.stderr.write(selected.message + '\n');
234
514
  return { ok: false, code: ExitCode.UsageError };
@@ -252,19 +532,53 @@ async function prepare(args, opts) {
252
532
  excludeDirs: loaded.value.sharkcraftDirRel ? [loaded.value.sharkcraftDirRel] : [],
253
533
  ...(changedFiles !== undefined ? { changedFiles } : {}),
254
534
  planeDiagnostics: loaded.value.planeDiagnostics,
535
+ rejected,
255
536
  };
256
537
  }
257
- /** The "no baselines declared" landing, shared by every subverb. */
258
- 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;
259
562
  if (json) {
260
- process.stdout.write(asJson({ schema: SCHEMA, results: [], evaluated: 0, verdict: 'not-verified' }) + '\n');
261
- 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;
262
571
  }
263
572
  process.stdout.write(header('Baselines'));
264
573
  process.stdout.write(' No baselines declared. Add `baselines[]` to sharkcraft.config.ts to replace a\n' +
265
574
  ' hand-rolled "committed file + recompute script + drift test" trio with one\n' +
266
575
  ' two-way engine (see docs/baseline-drift.md).\n');
267
- 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;
268
582
  }
269
583
  export const baselineListCommand = {
270
584
  name: 'list',
@@ -276,11 +590,13 @@ export const baselineListCommand = {
276
590
  if (!prep.ok)
277
591
  return prep.code;
278
592
  const json = flagBool(args, 'json');
279
- if (prep.all.length === 0)
593
+ if (prep.all.length === 0 && prep.rejected.length === 0)
280
594
  return writeNoRules(json);
281
595
  if (json) {
282
596
  process.stdout.write(asJson({
283
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 })),
284
600
  baselines: prep.all.map((r) => ({
285
601
  id: r.id,
286
602
  description: r.description ?? null,
@@ -288,7 +604,9 @@ export const baselineListCommand = {
288
604
  compute: r.compute.kind,
289
605
  direction: r.direction ?? 'two-way',
290
606
  keyBy: r.keyBy ?? null,
291
- 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),
292
610
  })),
293
611
  diagnostics: prep.planeDiagnostics,
294
612
  }) + '\n');
@@ -299,10 +617,14 @@ export const baselineListCommand = {
299
617
  process.stdout.write(` • ${r.id} → ${r.baseline}\n`);
300
618
  process.stdout.write(` compute ${r.compute.kind}${r.compute.kind === 'command' ? ` (${r.compute.run})` : ''}` +
301
619
  ` · direction ${r.direction ?? 'two-way'}${r.keyBy ? ` · keyBy ${r.keyBy}` : ''}` +
302
- `${r.failOnEmpty ? ' · failOnEmpty' : ''}\n`);
620
+ `${failsWhenEmpty(r) ? ' · failOnEmpty' : ''}\n`);
303
621
  if (r.description)
304
622
  process.stdout.write(` ${r.description}\n`);
305
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
+ }
306
628
  for (const d of prep.planeDiagnostics)
307
629
  process.stdout.write(` ! ${d}\n`);
308
630
  return ExitCode.VerifiedPass;
@@ -314,52 +636,87 @@ export const baselineCheckCommand = {
314
636
  usage: 'shrk baseline check [--id <ids>] [--changed-only] [--since <ref>] [--json]',
315
637
  booleanFlags: new Set(['json', 'changed-only']),
316
638
  async run(args) {
317
- const prep = await prepare(args, { changedAware: true });
639
+ const prep = await prepare(args, { changedAware: true, rejectedKnown: true });
318
640
  if (!prep.ok)
319
641
  return prep.code;
320
642
  const json = flagBool(args, 'json');
321
- if (prep.rules.length === 0)
322
- return writeNoRules(json);
323
- const outcomes = prep.rules.map((r) => evaluateRule(prep.cwd, r, prep.excludeDirs, prep.changedFiles));
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');
648
+ const outcomes = prep.rules.map((r) => evaluateBaselineRule(prep.cwd, r, prep.excludeDirs, prep.changedFiles));
324
649
  const failed = outcomes.filter((o) => o.status === 'failed' || (o.status === 'error' && (o.rule.severity ?? 'error') === 'error'));
325
650
  const evaluated = outcomes.filter((o) => o.status !== 'skipped').length;
326
651
  // 0 only when a NON-EMPTY scope was actually compared; 2 when nothing was.
327
652
  // A skipped baseline is "partially verified", never a green 0.
328
653
  const skippedCount = outcomes.filter((o) => o.status === 'skipped').length;
329
- const exit = failed.length > 0
654
+ const proposed = failed.length > 0 || rejected.length > 0
330
655
  ? ExitCode.Failure
331
656
  : evaluated === 0 || skippedCount > 0
332
657
  ? ExitCode.NotVerified
333
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;
334
689
  if (json) {
335
690
  process.stdout.write(asJson({
336
691
  schema: SCHEMA,
337
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 })),
338
695
  evaluated,
339
- skipped: outcomes.filter((o) => o.status === 'skipped').length,
340
- 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',
341
698
  diagnostics: prep.planeDiagnostics,
342
- gate: buildGateEnvelope('baseline check', exit, outcomes.map((o) => ({
343
- id: o.rule.id,
344
- type: 'baseline',
345
- status: o.status,
346
- severity: o.rule.severity ?? 'error',
347
- counts: { committed: o.committedCount, current: o.currentCount },
348
- violations: [
349
- ...(o.diff?.added ?? []).map((id) => ({ id, message: 'added', hint: hintFor(o.rule) })),
350
- ...(o.diff?.removed ?? []).map((id) => ({ id, message: 'removed', hint: hintFor(o.rule) })),
351
- ],
352
- ...(o.skipReason ? { skipReason: o.skipReason } : {}),
353
- ...(o.error ? { error: o.error } : {}),
354
- }))),
699
+ exitCode: exit,
700
+ gate: env,
355
701
  }) + '\n');
356
702
  return exit;
357
703
  }
358
704
  process.stdout.write(header('Baseline drift'));
359
- 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);
360
710
  for (const o of outcomes) {
361
711
  if (o.status === 'passed') {
362
- 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`);
363
720
  continue;
364
721
  }
365
722
  if (o.status === 'skipped') {
@@ -374,6 +731,24 @@ export const baselineCheckCommand = {
374
731
  }
375
732
  continue;
376
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
+ }
377
752
  const added = o.diff?.added.length ?? 0;
378
753
  const removed = o.diff?.removed.length ?? 0;
379
754
  process.stdout.write(` ✗ ${o.rule.id} DRIFT — ${added} added, ${removed} removed ` +
@@ -386,12 +761,29 @@ export const baselineCheckCommand = {
386
761
  }
387
762
  for (const d of prep.planeDiagnostics)
388
763
  process.stdout.write(` ! ${d}\n`);
389
- if (exit === ExitCode.NotVerified) {
390
- process.stdout.write('\nNothing was compared this is NOT a pass. Every selected baseline was skipped.\n');
391
- }
392
- else if (exit === ExitCode.VerifiedPass) {
393
- 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`);
394
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`);
395
787
  return exit;
396
788
  },
397
789
  };
@@ -407,7 +799,7 @@ export const baselineDiffCommand = {
407
799
  const json = flagBool(args, 'json');
408
800
  if (prep.rules.length === 0)
409
801
  return writeNoRules(json);
410
- const outcomes = prep.rules.map((r) => evaluateRule(prep.cwd, r, prep.excludeDirs, undefined));
802
+ const outcomes = prep.rules.map((r) => evaluateBaselineRule(prep.cwd, r, prep.excludeDirs, undefined));
411
803
  if (json) {
412
804
  process.stdout.write(asJson({ schema: SCHEMA, results: outcomes.map(outcomeJson), inspection: true }) + '\n');
413
805
  return ExitCode.VerifiedPass;
@@ -416,7 +808,13 @@ export const baselineDiffCommand = {
416
808
  for (const o of outcomes) {
417
809
  const added = o.diff?.added.length ?? 0;
418
810
  const removed = o.diff?.removed.length ?? 0;
419
- 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');
420
818
  writeDiff(o, 100);
421
819
  }
422
820
  return ExitCode.VerifiedPass;
@@ -437,19 +835,101 @@ export const baselineUpdateCommand = {
437
835
  return writeNoRules(json);
438
836
  const written = [];
439
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 = [];
440
854
  for (const rule of prep.rules) {
441
855
  const computed = computeCurrent(prep.cwd, rule, prep.excludeDirs);
442
856
  if (computed.error) {
443
857
  errors.push({ id: rule.id, error: computed.error });
444
858
  continue;
445
859
  }
446
- if (computed.text.trim() === '' && failsWhenEmpty(rule)) {
447
- 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({
448
899
  id: rule.id,
449
- 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}`,
450
903
  });
451
904
  continue;
452
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
+ }
453
933
  const abs = nodePath.resolve(prep.cwd, rule.baseline);
454
934
  const previous = existsSync(abs) ? readFileSync(abs, 'utf8') : undefined;
455
935
  const changed = previous !== computed.text;
@@ -459,20 +939,56 @@ export const baselineUpdateCommand = {
459
939
  }
460
940
  written.push({ id: rule.id, path: rule.baseline, bytes: computed.text.length, changed });
461
941
  }
942
+ const settled = settleVerdict(errors.length > 0 ? ExitCode.Failure : ExitCode.VerifiedPass, unverifiedCoverage);
943
+ const exit = settled.exit;
462
944
  if (json) {
463
- process.stdout.write(asJson({ schema: SCHEMA, dryRun, written, errors }) + '\n');
464
- 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;
465
959
  }
466
960
  process.stdout.write(header(dryRun ? 'Baseline update (dry run)' : 'Baseline update'));
467
961
  for (const w of written) {
468
962
  process.stdout.write(` ${w.changed ? (dryRun ? 'would write' : 'wrote') : 'unchanged '} ${w.path} (${w.bytes} bytes)\n`);
469
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
+ }
470
970
  for (const e of errors)
471
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
+ }
472
981
  if (written.some((w) => w.changed) && !dryRun) {
473
982
  process.stdout.write('\nReview the diff before committing — this is the bless step.\n');
474
983
  }
475
- 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;
476
992
  },
477
993
  };
478
994
  export const baselineExplainCommand = {
@@ -494,7 +1010,7 @@ export const baselineExplainCommand = {
494
1010
  process.stderr.write(`No baseline "${id}". Declared: ${prep.all.map((r) => r.id).join(', ') || '(none)'}\n`);
495
1011
  return ExitCode.UsageError;
496
1012
  }
497
- const outcome = evaluateRule(prep.cwd, rule, prep.excludeDirs, undefined);
1013
+ const outcome = evaluateBaselineRule(prep.cwd, rule, prep.excludeDirs, undefined);
498
1014
  if (flagBool(args, 'json')) {
499
1015
  process.stdout.write(asJson({
500
1016
  schema: 'sharkcraft.baseline-explain/v1',
@@ -507,15 +1023,21 @@ export const baselineExplainCommand = {
507
1023
  process.stdout.write(header(`Baseline: ${rule.id}`));
508
1024
  if (rule.description)
509
1025
  process.stdout.write(` ${rule.description}\n`);
510
- process.stdout.write(kv('committed', rule.baseline) + '\n');
511
- process.stdout.write(kv('compute', rule.compute.kind === 'command' ? `command · ${rule.compute.run}` : `extractor · ${rule.compute.source?.extract ?? 'sugar'}`) + '\n');
512
- process.stdout.write(kv('direction', rule.direction ?? 'two-way') + '\n');
1026
+ process.stdout.write(kv('committed', pinLabel(rule)) + '\n');
1027
+ process.stdout.write(kv('compute', rule.compute.kind === 'command'
1028
+ ? `command · ${rule.compute.run}`
1029
+ : `extractor · ${rule.compute.source?.extract ?? 'sugar'}` +
1030
+ (rule.compute.source?.$use ? ` (via $use:${rule.compute.source.$use})` : '')) + '\n');
1031
+ process.stdout.write(kv('direction', rule.direction ?? (rule.mode === 'ceiling' ? 'at-most' : 'two-way')) + '\n');
513
1032
  process.stdout.write(kv('canonical', outcome.diff?.canonical ?? rule.compute.canonical ?? 'auto') + '\n');
514
1033
  if (rule.keyBy)
515
1034
  process.stdout.write(kv('keyBy', rule.keyBy) + '\n');
516
- process.stdout.write(kv('entries', outcome.missingBaseline
517
- ? `committed (none yet) → ${outcome.currentCount} now`
518
- : `${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');
519
1041
  process.stdout.write(kv('status', outcome.status) + '\n');
520
1042
  if (outcome.error)
521
1043
  process.stdout.write(` ! ${outcome.error}\n`);
@@ -532,11 +1054,13 @@ export const baselineCommand = {
532
1054
  name: 'baseline',
533
1055
  description: 'Committed-baseline drift engine: recompute a ledger/digest/allow-list and fail on drift in BOTH directions. Read-only except `update`.',
534
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,
535
1061
  booleanFlags: new Set(['json', 'changed-only', 'dry-run']),
536
- async run(args) {
537
- const sub = args.positional[0];
538
- process.stderr.write((sub ? `Unknown subcommand "${sub}". ` : '') +
539
- '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');
540
1064
  return ExitCode.UsageError;
541
1065
  },
542
1066
  };