@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
@@ -7,7 +7,7 @@
7
7
  * shrk generated list # every declared artifact set
8
8
  * shrk generated check [--id X] # regen → temp dir → diff BOTH ways + headers
9
9
  * [--headers-only] # header contract only; never spawns
10
- * shrk generated update [--id X] # run regen in place (the bless step)
10
+ * shrk generated update [--id X] # regen → temp dir → write the ALIGNED files in place (bless)
11
11
  * shrk generated explain --id X # what it will run + what it currently sees
12
12
  *
13
13
  * The build compiles a hand-edited generated file perfectly happily; only
@@ -17,65 +17,43 @@
17
17
  * fully useful and never spawns, so packs can still ship it.
18
18
  */
19
19
  import { spawnSync } from 'node:child_process';
20
- import { mkdtempSync, readFileSync, readdirSync, rmSync, statSync } from 'node:fs';
20
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
21
21
  import * as nodeOs from 'node:os';
22
22
  import * as nodePath from 'node:path';
23
- import { failsWhenEmpty } from '@shrkcrft/core';
24
- import { checkProvenanceHeaders, compareGeneratedTrees, scanGeneratedFiles, } from '@shrkcrft/boundaries';
23
+ import { failsWhenEmpty, formatEmptyRuleAdvice, normalizeRuleList, RuleEmptiness, settleRuleEmptiness, unitStateLists, UnitLivenessState, } from '@shrkcrft/core';
24
+ import { checkProvenanceHeaders, clearFileReadCache, compareGeneratedTrees, globListSelects, globListWalkGlobs, planeScanExcludeDirs, readGlobListLiveness, readRegenTree, readScopeCoverage, readScopeHasUnread, scanGeneratedFiles, } from '@shrkcrft/boundaries';
25
25
  import { resolveProjectConfig } from '@shrkcrft/inspector';
26
26
  import { flagBool, flagString, resolveCwd, } from "../command-registry.js";
27
27
  import { ExitCode } from "../exit-codes.js";
28
28
  import { asJson, header, kv } from "../output/format-output.js";
29
29
  import { buildGateEnvelope } from "../gates/gate-envelope.js";
30
+ import { verdictLine } from "../gates/verdict-line.js";
31
+ import { acceptedEmptyNote } from "../gates/accepted-empty-note.js";
32
+ import { emptyRuleAdviceLines } from "../gates/empty-rule-advice-lines.js";
33
+ import { qualifyCleanForUnits } from "../gates/qualify-clean-for-units.js";
34
+ import { unitStateNotes } from "../gates/unit-state-notes.js";
35
+ import { planeVerdictForExit } from "../gates/plane-verdict.js";
36
+ import { seamRejectedRules } from "../gates/seam-rejected-rules.js";
30
37
  const SCHEMA = 'sharkcraft.generated-drift/v1';
31
38
  const DEFAULT_TIMEOUT_MS = 120_000;
32
- /** Cap on the temp tree read — a runaway regen must not be read into memory whole. */
33
- const MAX_REGEN_FILE_BYTES = 2_000_000;
34
39
  async function loadRules(cwd) {
35
40
  const loaded = await resolveProjectConfig(cwd);
36
41
  if (!loaded.ok)
37
42
  return { ok: false, message: loaded.error.message };
38
- const rel = nodePath.relative(cwd, loaded.value.sharkcraftDir).split(nodePath.sep).join('/');
39
43
  return {
40
44
  ok: true,
41
45
  value: {
42
46
  rules: loaded.value.config.generatedArtifacts ?? [],
43
47
  planeDiagnostics: loaded.value.planeDiagnostics,
44
- excludeDirs: rel && !rel.startsWith('..') ? [rel] : [],
48
+ excludeDirs: planeScanExcludeDirs(cwd, loaded.value.sharkcraftDir),
49
+ rejected: seamRejectedRules(loaded.value, ['generated']),
45
50
  },
46
51
  };
47
52
  }
48
- /** Read a regenerated temp tree into path→content, relative to `root`. */
49
- function readTree(root) {
50
- const out = new Map();
51
- const visit = (abs) => {
52
- let entries;
53
- try {
54
- entries = readdirSync(abs, { withFileTypes: true });
55
- }
56
- catch {
57
- return;
58
- }
59
- for (const e of entries) {
60
- const child = nodePath.join(abs, e.name);
61
- if (e.isDirectory()) {
62
- visit(child);
63
- continue;
64
- }
65
- if (!e.isFile())
66
- continue;
67
- try {
68
- if (statSync(child).size > MAX_REGEN_FILE_BYTES)
69
- continue;
70
- out.set(nodePath.relative(root, child).split(nodePath.sep).join('/'), readFileSync(child, 'utf8'));
71
- }
72
- catch {
73
- // unreadable — skip
74
- }
75
- }
76
- };
77
- visit(root);
78
- return out;
53
+ /** ` ✗ <id> REJECTED — <why>` — a pack rule the merge seam refused (declared, never run). */
54
+ function writeRejected(rejected) {
55
+ for (const r of rejected)
56
+ process.stdout.write(` ✗ ${r.id} REJECTED ${r.error ?? 'failed validation'}\n`);
79
57
  }
80
58
  /** Number of trailing path SEGMENTS `a` and `b` share (0 when none). */
81
59
  function sharedSuffixSegments(a, b) {
@@ -97,17 +75,21 @@ function sharedSuffixSegments(a, b) {
97
75
  * files. Ties break lexically so the mapping is deterministic, each committed
98
76
  * path is claimed at most once, and anything unmatched keeps its temp-relative
99
77
  * key and surfaces as `only-regenerated` rather than disappearing.
78
+ *
79
+ * Returns temp path → key, so the outputs the reader read AND the ones it did
80
+ * not (over the regen cap) key onto the same committed path through ONE
81
+ * alignment.
100
82
  */
101
- function alignToCommitted(temp, committed) {
102
- const committedPaths = [...committed.keys()].sort();
83
+ function alignKeys(tempPaths, committedPaths) {
84
+ const targets = [...new Set(committedPaths)].sort();
103
85
  const claimed = new Set();
104
86
  const out = new Map();
105
87
  // Best-match first: a temp path with a longer shared suffix has the stronger
106
88
  // claim on a committed path, so resolve those before the weaker ones.
107
- const scored = [...temp.keys()]
89
+ const scored = [...tempPaths]
108
90
  .map((tempPath) => {
109
91
  let best;
110
- for (const c of committedPaths) {
92
+ for (const c of targets) {
111
93
  const score = sharedSuffixSegments(tempPath, c);
112
94
  if (score > 0 && (best === undefined || score > best.score))
113
95
  best = { path: c, score };
@@ -116,13 +98,12 @@ function alignToCommitted(temp, committed) {
116
98
  })
117
99
  .sort((a, b) => (b.best?.score ?? 0) - (a.best?.score ?? 0) || a.tempPath.localeCompare(b.tempPath));
118
100
  for (const { tempPath, best } of scored) {
119
- const content = temp.get(tempPath);
120
101
  if (best && !claimed.has(best.path)) {
121
102
  claimed.add(best.path);
122
- out.set(best.path, content);
103
+ out.set(tempPath, best.path);
123
104
  }
124
105
  else {
125
- out.set(tempPath, content);
106
+ out.set(tempPath, tempPath);
126
107
  }
127
108
  }
128
109
  return out;
@@ -136,7 +117,7 @@ function alignToCommitted(temp, committed) {
136
117
  * (and later diffed against) only its own files, so writer A's output can never
137
118
  * be reported as writer B's stale committed file.
138
119
  */
139
- function runOneRegen(cwd, command, committed, timeoutMs) {
120
+ function runOneRegen(cwd, command, committed, timeoutMs, unreadCommitted = []) {
140
121
  const tmp = mkdtempSync(nodePath.join(nodeOs.tmpdir(), 'shrk-generated-'));
141
122
  try {
142
123
  const child = spawnSync(command.split('{TMP}').join(tmp), {
@@ -146,17 +127,28 @@ function runOneRegen(cwd, command, committed, timeoutMs) {
146
127
  timeout: timeoutMs,
147
128
  maxBuffer: 16 * 1024 * 1024,
148
129
  });
130
+ // A spawned command may rewrite files anywhere: drop every memoised tree
131
+ // read, so a read-cache window (`withFileReadCache`, e.g. `shrk quality`'s
132
+ // plane section) never serves a pre-spawn snapshot after it.
133
+ clearFileReadCache();
149
134
  if (child.error)
150
135
  return { error: `regen failed to start: ${child.error.message}` };
151
136
  if (child.status !== 0) {
152
137
  const tail = String(child.stderr ?? '').trim().split('\n').slice(-3).join(' | ');
153
138
  return { error: `regen exited ${child.status ?? 'null'}${tail ? ` — ${tail}` : ''}` };
154
139
  }
155
- const tree = readTree(tmp);
156
- if (tree.size === 0) {
140
+ const tree = readRegenTree(tmp);
141
+ if (tree.files.size === 0 && tree.unread.length === 0) {
157
142
  return { error: 'regen wrote no files into {TMP} — the command probably ignores the output path' };
158
143
  }
159
- return { files: alignToCommitted(tree, committed) };
144
+ // ONE alignment for the read and the unread outputs, against the committed
145
+ // slice AND the committed files the reader could not read, so an over-cap
146
+ // output keys onto the same committed path a read one would.
147
+ const keys = alignKeys([...tree.files.keys(), ...tree.unread.map((u) => u.path)], [...committed.keys(), ...unreadCommitted]);
148
+ const files = new Map();
149
+ for (const [p, content] of tree.files)
150
+ files.set(keys.get(p) ?? p, content);
151
+ return { files, unread: tree.unread.map((u) => ({ ...u, path: keys.get(u.path) ?? u.path })) };
160
152
  }
161
153
  finally {
162
154
  rmSync(tmp, { recursive: true, force: true });
@@ -182,20 +174,100 @@ function regenUnitsOf(rule, scan) {
182
174
  // glob is not compared against output no generator produces.
183
175
  return [{ label: rule.id, command: rule.regen, committed: scan.checkable, timeoutMs: fallback }];
184
176
  }
185
- export function evaluateGeneratedRule(cwd, rule, excludeDirs, headersOnly) {
177
+ export function evaluateGeneratedRule(cwd, authored, excludeDirs, headersOnly) {
178
+ // The engine entry normalises idempotently (round 13): a loaded rule comes
179
+ // back as the SAME object; a hand-built `{ pattern, expectEmpty }` entry is a
180
+ // glob plus a marker; a malformed entry is a misconfigured rule, never a crash.
181
+ const normalized = normalizeRuleList(authored, 'generatedGlob');
182
+ if (!normalized.ok) {
183
+ return {
184
+ rule: authored,
185
+ status: 'error',
186
+ committedCount: 0,
187
+ provenance: [],
188
+ driftChecked: false,
189
+ handMaintained: [],
190
+ writers: [],
191
+ error: `generatedGlob ${normalized.error.message}`,
192
+ };
193
+ }
194
+ const rule = normalized.value;
186
195
  const scan = scanGeneratedFiles(cwd, rule, excludeDirs);
187
196
  const severity = rule.severity ?? 'error';
188
- if (scan.generated.size === 0) {
189
- const failed = failsWhenEmpty(rule);
197
+ // A matched file the reader could not read (over the read cap) was never
198
+ // header-checked or compared. It rides on every outcome below, so the
199
+ // coverage names it; it is left out of the drift diff, where its absence
200
+ // from the committed side would otherwise read as `only-regenerated`.
201
+ const unreadScope = readScopeHasUnread(scan.readScope) ? { readScope: scan.readScope } : {};
202
+ const unreadPaths = new Set(scan.readScope.unread.map((u) => u.path));
203
+ // `generatedGlob` settled with its `expectEmpty` markers (round 13) — needed
204
+ // when a unit is marked (its acceptance / went-live line rides on the
205
+ // outcome) or when the rule matched nothing (what the empty rule is decided
206
+ // from). An unmarked rule that matched files pays nothing extra.
207
+ const live = (rule.expectEmptyUnits?.length ?? 0) > 0 || scan.generated.size === 0
208
+ ? readGlobListLiveness(cwd, 'generatedGlob', rule.generatedGlob, rule.expectEmptyUnits ?? [], {
209
+ subject: rule.id,
210
+ excludeDirs: new Set(excludeDirs),
211
+ })
212
+ : undefined;
213
+ const nonLive = live?.liveness.units.filter((u) => u.state !== UnitLivenessState.Live) ?? [];
214
+ const unitFields = {
215
+ ...(live?.liveness.acceptance !== undefined ? { unitAcceptance: live.liveness.acceptance } : {}),
216
+ ...(live !== undefined &&
217
+ live.liveness.dead.length + live.liveness.intendedEmpty.length + live.liveness.wentLive.length > 0
218
+ ? { units: unitStateLists(live.liveness) }
219
+ : {}),
220
+ ...(nonLive.length > 0 ? { unitLiveness: nonLive } : {}),
221
+ };
222
+ if (scan.generated.size === 0 && unreadPaths.size > 0) {
223
+ // Every committed file it matched is unread: not "0 files matched", so
224
+ // PARTIAL, never failOnEmpty's failure and never a pass.
190
225
  return {
191
226
  rule,
192
- status: failed ? 'failed' : 'skipped',
227
+ status: 'passed',
193
228
  committedCount: 0,
194
229
  provenance: [],
195
230
  driftChecked: false,
196
231
  handMaintained: [],
197
232
  writers: [],
198
- skipReason: `0 files matched generatedGlob (${rule.generatedGlob.join(', ')})`,
233
+ ...unreadScope,
234
+ ...unitFields,
235
+ };
236
+ }
237
+ if (scan.generated.size === 0 && live !== undefined) {
238
+ // THE rule-emptiness settle (round 13, `settleRuleEmptiness`): every
239
+ // inclusion glob of `generatedGlob` marked `expectEmpty` and no file
240
+ // matched → a generated tree that does not exist yet, accepted and printed.
241
+ // Anything else is the loud skip (failOnEmpty's 1, else 2).
242
+ const noFiles = `0 files matched generatedGlob (${rule.generatedGlob.join(', ')})`;
243
+ const emptiness = settleRuleEmptiness({
244
+ subject: rule.id,
245
+ unitLabel: 'generated files',
246
+ filesMatched: 0,
247
+ unitsMatched: 0,
248
+ unread: false,
249
+ liveness: live.liveness,
250
+ primaryLists: ['generatedGlob'],
251
+ failOnEmpty: failsWhenEmpty(rule),
252
+ noFilesReason: noFiles,
253
+ noUnitsReason: noFiles,
254
+ });
255
+ const empty = {
256
+ rule,
257
+ committedCount: 0,
258
+ provenance: [],
259
+ driftChecked: false,
260
+ handMaintained: [],
261
+ writers: [],
262
+ ...unitFields,
263
+ };
264
+ if (emptiness.state === RuleEmptiness.IntendedEmpty && emptiness.coverage !== undefined) {
265
+ return { ...empty, status: 'passed', emptyCoverage: emptiness.coverage };
266
+ }
267
+ return {
268
+ ...empty,
269
+ status: emptiness.fails ? 'failed' : 'skipped',
270
+ skipReason: emptiness.skipReason ?? noFiles,
199
271
  };
200
272
  }
201
273
  // Headers + byte checks run over the CHECKABLE set — a hand-maintained bless
@@ -214,26 +286,45 @@ export function evaluateGeneratedRule(cwd, rule, excludeDirs, headersOnly) {
214
286
  handMaintained: scan.handMaintained,
215
287
  writers: [],
216
288
  error: headers.error,
289
+ ...unreadScope,
217
290
  };
218
291
  }
219
292
  const units = headersOnly ? [] : regenUnitsOf(rule, scan);
220
293
  const differences = [];
221
294
  const writers = [];
222
295
  const errors = [];
296
+ // Regen outputs the reader did not read (over the regen cap) that key onto a
297
+ // COMMITTED file: never byte-compared, so they join the rule's read scope
298
+ // and its coverage names them. Never dropped.
299
+ const uncomparable = [];
223
300
  let committedCompared = 0;
224
301
  let regeneratedCount = 0;
225
302
  for (const unit of units) {
226
- const regen = runOneRegen(cwd, unit.command, unit.committed, unit.timeoutMs);
303
+ const regen = runOneRegen(cwd, unit.command, unit.committed, unit.timeoutMs, [...unreadPaths]);
227
304
  if (regen.error) {
228
305
  errors.push(units.length > 1 ? `${unit.label}: ${regen.error}` : regen.error);
229
306
  writers.push({ label: unit.label, committedCount: unit.committed.size, differences: 0, error: regen.error });
230
307
  continue;
231
308
  }
309
+ const regenUnread = regen.unread ?? [];
310
+ const keyedOnCommitted = (p) => unit.committed.has(p) || unreadPaths.has(p);
311
+ const unitUncomparable = regenUnread.filter((u) => keyedOnCommitted(u.path));
312
+ uncomparable.push(...unitUncomparable);
313
+ const skip = new Set([...unreadPaths, ...unitUncomparable.map((u) => u.path)]);
232
314
  const diff = compareGeneratedTrees(unit.committed, regen.files, rule.compare ?? 'bytes');
233
- differences.push(...diff.differences);
234
- committedCompared += diff.committedCount;
235
- regeneratedCount += diff.regeneratedCount;
236
- writers.push({ label: unit.label, committedCount: unit.committed.size, differences: diff.differences.length });
315
+ // A path whose committed or regenerated side was never read cannot be
316
+ // compared: it is in the coverage gap, not the diff. An unread output
317
+ // keyed onto NOTHING committed is drift its path alone proves.
318
+ const compared = [
319
+ ...diff.differences.filter((d) => !skip.has(d.file)),
320
+ ...regenUnread
321
+ .filter((u) => !keyedOnCommitted(u.path))
322
+ .map((u) => ({ file: u.path, kind: 'only-regenerated' })),
323
+ ];
324
+ differences.push(...compared);
325
+ committedCompared += diff.committedCount - unitUncomparable.filter((u) => unit.committed.has(u.path)).length;
326
+ regeneratedCount += diff.regeneratedCount + regenUnread.length;
327
+ writers.push({ label: unit.label, committedCount: unit.committed.size, differences: compared.length });
237
328
  }
238
329
  const ranAnyWriter = units.length > 0 && errors.length < units.length;
239
330
  const treeDiff = ranAnyWriter
@@ -249,6 +340,17 @@ export function evaluateGeneratedRule(cwd, rule, excludeDirs, headersOnly) {
249
340
  : drifted || (hardFindings.length > 0 && severity === 'error')
250
341
  ? 'failed'
251
342
  : 'passed';
343
+ // The rule's read scope: the committed files the reader could not read, plus
344
+ // each uncomparable regen output keyed onto a READ committed file (read, but
345
+ // never compared, so it moves from examined to unexamined).
346
+ const extra = new Map();
347
+ for (const u of uncomparable)
348
+ if (!unreadPaths.has(u.path) && !extra.has(u.path))
349
+ extra.set(u.path, u);
350
+ const finalScope = {
351
+ read: scan.readScope.read - extra.size,
352
+ unread: [...scan.readScope.unread, ...extra.values()].sort((a, b) => a.path.localeCompare(b.path)),
353
+ };
252
354
  return {
253
355
  rule,
254
356
  status,
@@ -259,8 +361,53 @@ export function evaluateGeneratedRule(cwd, rule, excludeDirs, headersOnly) {
259
361
  driftChecked: units.length > 0 && errors.length === 0,
260
362
  handMaintained: scan.handMaintained,
261
363
  writers: writers.length > 1 ? writers : [],
364
+ ...(readScopeHasUnread(finalScope) ? { readScope: finalScope } : {}),
365
+ // The acceptance of a marked planned glob and the unit lines ride on a
366
+ // connected rule too (round 13), not only on an empty one.
367
+ ...unitFields,
262
368
  };
263
369
  }
370
+ /**
371
+ * What one generated-artifact rule examined — shared by `generated check` and
372
+ * the aggregate (`gates check`, `quality`), so both report the same scope.
373
+ *
374
+ * `driftSkipped` is the aggregate's `--no-spawn` case: headers and
375
+ * classification ran, the regen diff did not, so no file's CONTENTS were
376
+ * verified. (`generated check --headers-only` is a narrowing the caller asked
377
+ * for, not a gap.)
378
+ */
379
+ export function generatedCoverage(o, driftSkipped = false) {
380
+ // An intended-empty rule (round 13: every `generatedGlob` inclusion glob
381
+ // marked `expectEmpty`, no file matched) is covered by its acceptance — the
382
+ // settle's own record, printed at exit 0.
383
+ if (o.emptyCoverage !== undefined)
384
+ return o.emptyCoverage;
385
+ // A file in scope the reader could not read folds in through the one rule
386
+ // every plane uses (`readScopeCoverage`): `examined N of M files`, named.
387
+ return readScopeCoverage(planeGeneratedCoverage(o, driftSkipped), o.readScope);
388
+ }
389
+ function planeGeneratedCoverage(o, driftSkipped) {
390
+ if (o.skipReason !== undefined) {
391
+ return { unit: 'generated files', expected: 0, examined: 0, reason: o.skipReason };
392
+ }
393
+ if (o.status === 'error') {
394
+ return {
395
+ unit: 'generated files',
396
+ expected: o.committedCount,
397
+ examined: 0,
398
+ reason: 'the rule could not be evaluated',
399
+ };
400
+ }
401
+ if (driftSkipped) {
402
+ return {
403
+ unit: 'generated files',
404
+ expected: o.committedCount,
405
+ examined: 0,
406
+ reason: 'regen drift check skipped by --no-spawn',
407
+ };
408
+ }
409
+ return { unit: 'generated files', expected: o.committedCount, examined: o.committedCount };
410
+ }
264
411
  export function generatedHintFor(rule) {
265
412
  return (rule.hint ??
266
413
  (rule.regen || rule.sources
@@ -284,7 +431,9 @@ function outcomeJson(o) {
284
431
  hint: generatedHintFor(o.rule),
285
432
  };
286
433
  }
287
- async function prepare(args) {
434
+ async function prepare(args,
435
+ /** `rejectedKnown`: a verdict verb may select a rejected rule by `--id` (its errored row). */
436
+ opts = {}) {
288
437
  const cwd = resolveCwd(args);
289
438
  const json = flagBool(args, 'json');
290
439
  const loaded = await loadRules(cwd);
@@ -297,15 +446,20 @@ async function prepare(args) {
297
446
  }
298
447
  const id = flagString(args, 'id');
299
448
  let rules = loaded.value.rules;
449
+ let rejected = loaded.value.rejected;
300
450
  if (id) {
301
451
  const wanted = id.split(',').map((s) => s.trim()).filter(Boolean);
302
- const known = new Set(rules.map((r) => r.id));
452
+ const known = new Set([
453
+ ...rules.map((r) => r.id),
454
+ ...(opts.rejectedKnown === true ? rejected.map((r) => r.id) : []),
455
+ ]);
303
456
  const unknown = wanted.filter((w) => !known.has(w));
304
457
  if (unknown.length > 0) {
305
458
  process.stderr.write(`Unknown generated-artifact id(s): ${unknown.join(', ')}. Declared: ${[...known].join(', ') || '(none)'}\n`);
306
459
  return { ok: false, code: ExitCode.UsageError };
307
460
  }
308
461
  rules = rules.filter((r) => wanted.includes(r.id));
462
+ rejected = rejected.filter((r) => wanted.includes(r.id));
309
463
  }
310
464
  return {
311
465
  ok: true,
@@ -314,18 +468,47 @@ async function prepare(args) {
314
468
  all: loaded.value.rules,
315
469
  excludeDirs: loaded.value.excludeDirs,
316
470
  planeDiagnostics: loaded.value.planeDiagnostics,
471
+ rejected,
317
472
  };
318
473
  }
319
- function writeNoRules(json) {
474
+ /**
475
+ * The "no rules declared" landing. A VERDICT verb passes its name so its JSON
476
+ * still carries the settled `gate` envelope — nothing declared is `2`.
477
+ */
478
+ function writeNoRules(json, verb) {
479
+ // A VERDICT verb settles first and renders second, in text AND JSON: nothing
480
+ // declared proposes 0 and the run coverage (expected 0) settles it to 2. The
481
+ // exit comes from the envelope — never a hard-coded code — so text, JSON and
482
+ // gate.exit cannot disagree. List / explain subverbs stay informational.
483
+ const gate = verb !== undefined
484
+ ? buildGateEnvelope(verb, ExitCode.VerifiedPass, [], {
485
+ unit: 'generated-artifact rules',
486
+ expected: 0,
487
+ examined: 0,
488
+ reason: 'no generatedArtifacts[] declared',
489
+ })
490
+ : undefined;
491
+ const exit = gate?.exit ?? ExitCode.NotVerified;
320
492
  if (json) {
321
- process.stdout.write(asJson({ schema: SCHEMA, results: [], evaluated: 0, verdict: 'not-verified' }) + '\n');
322
- return ExitCode.NotVerified;
493
+ process.stdout.write(asJson({
494
+ schema: SCHEMA,
495
+ results: [],
496
+ evaluated: 0,
497
+ verdict: gate ? planeVerdictForExit(gate.exit) : 'not-verified',
498
+ ...(gate ? { exitCode: gate.exit, gate } : {}),
499
+ }) + '\n');
500
+ return exit;
323
501
  }
324
502
  process.stdout.write(header('Generated artifacts'));
325
503
  process.stdout.write(' No generated-artifact rules declared. Add `generatedArtifacts[]` to\n' +
326
504
  ' sharkcraft.config.ts to catch hand-edited generated files and missing\n' +
327
505
  ' "do not edit" headers (see docs/generated-drift.md).\n');
328
- return ExitCode.NotVerified;
506
+ if (gate) {
507
+ const line = verdictLine(gate, 'Nothing declared — accepted.');
508
+ if (line)
509
+ process.stdout.write(`\n${line}\n`);
510
+ }
511
+ return exit;
329
512
  }
330
513
  export const generatedListCommand = {
331
514
  name: 'list',
@@ -337,11 +520,13 @@ export const generatedListCommand = {
337
520
  if (!prep.ok)
338
521
  return prep.code;
339
522
  const json = flagBool(args, 'json');
340
- if (prep.all.length === 0)
523
+ if (prep.all.length === 0 && prep.rejected.length === 0)
341
524
  return writeNoRules(json);
342
525
  if (json) {
343
526
  process.stdout.write(asJson({
344
527
  schema: SCHEMA,
528
+ // Declared by a pack, refused by the merge seam: never checked.
529
+ rejected: prep.rejected.map((r) => ({ id: r.id, error: r.error ?? null })),
345
530
  rules: prep.all.map((r) => ({
346
531
  id: r.id,
347
532
  description: r.description ?? null,
@@ -352,7 +537,9 @@ export const generatedListCommand = {
352
537
  handMaintainedMarker: r.handMaintainedMarker ?? null,
353
538
  compare: r.compare ?? 'bytes',
354
539
  provenanceHeader: r.provenanceHeader ?? null,
355
- failOnEmpty: r.failOnEmpty === true,
540
+ // The EFFECTIVE failOnEmpty (round 13) — THE authority, never the
541
+ // raw field: an error rule fails on empty by default.
542
+ failOnEmpty: failsWhenEmpty(r),
356
543
  })),
357
544
  diagnostics: prep.planeDiagnostics,
358
545
  }) + '\n');
@@ -384,6 +571,10 @@ export const generatedListCommand = {
384
571
  if (r.description)
385
572
  process.stdout.write(` ${r.description}\n`);
386
573
  }
574
+ if (prep.rejected.length > 0) {
575
+ process.stdout.write(`\n rejected at the pack-plane merge seam — never checked (${prep.rejected.length}):\n`);
576
+ writeRejected(prep.rejected);
577
+ }
387
578
  for (const d of prep.planeDiagnostics)
388
579
  process.stdout.write(` ! ${d}\n`);
389
580
  return ExitCode.VerifiedPass;
@@ -395,63 +586,92 @@ export const generatedCheckCommand = {
395
586
  usage: 'shrk generated check [--id <ids>] [--headers-only] [--json]',
396
587
  booleanFlags: new Set(['json', 'headers-only']),
397
588
  async run(args) {
398
- const prep = await prepare(args);
589
+ const prep = await prepare(args, { rejectedKnown: true });
399
590
  if (!prep.ok)
400
591
  return prep.code;
401
592
  const json = flagBool(args, 'json');
402
593
  const headersOnly = flagBool(args, 'headers-only');
403
- if (prep.rules.length === 0)
404
- return writeNoRules(json);
594
+ // A pack rule the merge seam rejected is a declared rule that was never
595
+ // checked — an errored row, exit 1 (round 12 review, R12-X1).
596
+ const rejected = prep.rejected;
597
+ if (prep.rules.length === 0 && rejected.length === 0)
598
+ return writeNoRules(json, 'generated check');
405
599
  const outcomes = prep.rules.map((r) => evaluateGeneratedRule(prep.cwd, r, prep.excludeDirs, headersOnly));
406
600
  const failed = outcomes.filter((o) => o.status === 'failed' || (o.status === 'error' && (o.rule.severity ?? 'error') === 'error'));
407
601
  const evaluated = outcomes.filter((o) => o.status !== 'skipped').length;
408
602
  const skippedCount = outcomes.length - evaluated;
409
- const exit = failed.length > 0
603
+ const proposed = failed.length > 0 || rejected.length > 0
410
604
  ? ExitCode.Failure
411
605
  : evaluated === 0 || skippedCount > 0
412
606
  ? ExitCode.NotVerified
413
607
  : ExitCode.VerifiedPass;
608
+ // Settle first, render second: one envelope for text AND JSON, and the ✓
609
+ // line is printed only from the settled verdict. `--headers-only` is a
610
+ // narrowing the caller asked for, so it is not a coverage gap here.
611
+ const unexamined = [
612
+ ...outcomes.filter((o) => o.status === 'skipped' || o.status === 'error').map((o) => o.rule.id),
613
+ ...rejected.map((r) => r.id),
614
+ ];
615
+ const env = buildGateEnvelope('generated check', proposed, [...outcomes.map((o) => ({
616
+ id: o.rule.id,
617
+ type: 'generated',
618
+ status: o.status,
619
+ severity: o.rule.severity ?? 'error',
620
+ counts: {
621
+ files: o.committedCount,
622
+ differences: o.treeDiff?.differences.length ?? 0,
623
+ provenance: o.provenance.length,
624
+ },
625
+ violations: [
626
+ ...(o.treeDiff?.differences ?? []).map((d) => ({
627
+ id: d.file,
628
+ file: d.file,
629
+ message: d.kind,
630
+ hint: generatedHintFor(o.rule),
631
+ })),
632
+ ...o.provenance.map((f) => ({
633
+ id: f.file,
634
+ file: f.file,
635
+ message: f.message,
636
+ hint: generatedHintFor(o.rule),
637
+ })),
638
+ ],
639
+ ...(o.skipReason ? { skipReason: o.skipReason } : {}),
640
+ ...(o.error ? { error: o.error } : {}),
641
+ coverage: generatedCoverage(o),
642
+ // The rule's `expectEmpty` acceptance and unit lines (round 13), folded
643
+ // into the envelope's one settle.
644
+ ...(o.unitAcceptance !== undefined ? { unitAcceptance: o.unitAcceptance } : {}),
645
+ ...(o.units !== undefined ? { units: o.units } : {}),
646
+ })), ...rejected], {
647
+ unit: 'generated-artifact rules',
648
+ expected: outcomes.length + rejected.length,
649
+ examined: outcomes.length + rejected.length - unexamined.length,
650
+ ...(unexamined.length > 0 ? { unexamined, reason: 'checked nothing or could not run' } : {}),
651
+ });
652
+ const exit = env.exit;
414
653
  if (json) {
415
654
  process.stdout.write(asJson({
416
655
  schema: SCHEMA,
417
656
  headersOnly,
418
657
  results: outcomes.map(outcomeJson),
658
+ // Pack rules the merge seam refused — errored rows in `gate.rules`.
659
+ rejected: rejected.map((r) => ({ id: r.id, error: r.error ?? null })),
419
660
  evaluated,
420
661
  skipped: outcomes.filter((o) => o.status === 'skipped').length,
421
- verdict: failed.length > 0 ? 'errors' : evaluated === 0 ? 'not-verified' : 'pass',
662
+ verdict: exit === ExitCode.Failure ? 'errors' : exit === ExitCode.VerifiedPass ? 'pass' : 'not-verified',
422
663
  diagnostics: prep.planeDiagnostics,
423
- gate: buildGateEnvelope('generated check', exit, outcomes.map((o) => ({
424
- id: o.rule.id,
425
- type: 'generated',
426
- status: o.status,
427
- severity: o.rule.severity ?? 'error',
428
- counts: {
429
- files: o.committedCount,
430
- differences: o.treeDiff?.differences.length ?? 0,
431
- provenance: o.provenance.length,
432
- },
433
- violations: [
434
- ...(o.treeDiff?.differences ?? []).map((d) => ({
435
- id: d.file,
436
- file: d.file,
437
- message: d.kind,
438
- hint: generatedHintFor(o.rule),
439
- })),
440
- ...o.provenance.map((f) => ({
441
- id: f.file,
442
- file: f.file,
443
- message: f.message,
444
- hint: generatedHintFor(o.rule),
445
- })),
446
- ],
447
- ...(o.skipReason ? { skipReason: o.skipReason } : {}),
448
- ...(o.error ? { error: o.error } : {}),
449
- }))),
664
+ exitCode: exit,
665
+ gate: env,
450
666
  }) + '\n');
451
667
  return exit;
452
668
  }
453
669
  process.stdout.write(header('Generated-artifact drift'));
454
- process.stdout.write(kv('evaluated', `${evaluated} of ${prep.rules.length}`) + '\n');
670
+ // Round 13 (K6): the printed count is the envelope's (`gate.evaluated`), which
671
+ // never counts a rule accepted as intended-empty — it is named apart.
672
+ process.stdout.write(kv('evaluated', `${env.evaluated} of ${prep.rules.length + rejected.length}${acceptedEmptyNote(env.acceptedEmpty)}` +
673
+ (rejected.length > 0 ? ` (${rejected.length} rejected at the pack-plane merge seam — NOT evaluated)` : '')) + '\n');
674
+ writeRejected(rejected);
455
675
  if (headersOnly)
456
676
  process.stdout.write(kv('scope', 'headers only — no regen was run') + '\n');
457
677
  for (const o of outcomes) {
@@ -463,8 +683,22 @@ export const generatedCheckCommand = {
463
683
  process.stdout.write(` ! ${o.rule.id} ${o.error}\n`);
464
684
  continue;
465
685
  }
686
+ // A rule that matched nothing and FAILS on it (failOnEmpty) produced no
687
+ // diff and no regen output — its skip reason is the only true line. It is
688
+ // never rendered as drift with a bless hint (round 13).
689
+ if (o.status === 'failed' && o.skipReason !== undefined) {
690
+ process.stdout.write(` ✗ ${o.rule.id} FAILED — ${o.skipReason}\n`);
691
+ process.stdout.write(` → ${formatEmptyRuleAdvice({ fails: true })}\n`);
692
+ continue;
693
+ }
466
694
  const diffs = o.treeDiff?.differences ?? [];
467
- if (o.status === 'passed') {
695
+ // Rendered from the SETTLED rule: a pass over part of its scope is
696
+ // `partial`, never a ✓ (the keystone emitter pattern, step 5).
697
+ const settledRule = env.rules.find((x) => x.id === o.rule.id);
698
+ if (o.status === 'passed' && settledRule?.status === 'partial') {
699
+ process.stdout.write(` ~ ${o.rule.id} PARTIAL — ${settledRule.shortfall ?? 'part of its scope was not examined'}\n`);
700
+ }
701
+ else if (o.status === 'passed') {
468
702
  const exempt = o.handMaintained.length > 0 ? `, ${o.handMaintained.length} hand-maintained` : '';
469
703
  process.stdout.write(` ✓ ${o.rule.id} (${o.committedCount} files${exempt}` +
470
704
  `${o.driftChecked ? ', byte-identical to a fresh regen' : ', headers only'})\n`);
@@ -499,18 +733,118 @@ export const generatedCheckCommand = {
499
733
  }
500
734
  for (const d of prep.planeDiagnostics)
501
735
  process.stdout.write(` ! ${d}\n`);
502
- if (exit === ExitCode.NotVerified) {
503
- process.stdout.write('\nNothing was checked this is NOT a pass. Every rule matched 0 files.\n');
504
- }
505
- else if (exit === ExitCode.VerifiedPass) {
506
- process.stdout.write('\nEvery generated artifact matches its source. ✓\n');
736
+ // THE empty-rule advice (round 13) for a SOFT skip: a failing one is
737
+ // advised inline (above); a rule that matched nothing and does not fail on
738
+ // it was told nothing.
739
+ for (const a of emptyRuleAdviceLines(outcomes.filter((o) => o.status === 'skipped' && o.skipReason !== undefined).map(() => ({ fails: false })))) {
740
+ process.stdout.write(` ${a}.\n`);
507
741
  }
742
+ // THE shared unit-state block (round 13, K2): a dead `generatedGlob` of a
743
+ // rule that still matched, and a LOCAL expectEmpty marker whose target
744
+ // appeared, withhold the ✓ (exit unchanged); a pack marker is INFO.
745
+ const unitNotes = unitStateNotes(outcomes.map((o) => ({
746
+ id: o.rule.id,
747
+ ...(o.unitLiveness !== undefined ? { unitLiveness: o.unitLiveness } : {}),
748
+ reportedEmpty: o.status === 'skipped' || o.skipReason !== undefined,
749
+ })));
750
+ process.stdout.write(unitNotes.text);
751
+ const line = verdictLine(env, qualifyCleanForUnits('Every generated artifact matches its source. ✓', unitNotes), proposed === ExitCode.NotVerified && evaluated === 0
752
+ ? 'Nothing was checked — this is NOT a pass. Every rule matched 0 files.'
753
+ : undefined);
754
+ if (line)
755
+ process.stdout.write(`\n${line}\n`);
508
756
  return exit;
509
757
  },
510
758
  };
759
+ const EMPTY_WRITES = { written: [], unchanged: [], created: [], notPlaced: [], noLongerProduced: [] };
760
+ /** The leading literal directory of a glob (`docs/schemas/*.json` → `docs/schemas`). */
761
+ function globStaticDir(glob) {
762
+ const out = [];
763
+ for (const seg of glob.split('/').slice(0, -1)) {
764
+ if (/[*?[\]{}]/.test(seg))
765
+ break;
766
+ out.push(seg);
767
+ }
768
+ return out.length > 0 ? out.join('/') : undefined;
769
+ }
770
+ /**
771
+ * Where a regen unit's NEW files belong: the single directory its committed
772
+ * slice lives in, or — for a first generation with nothing committed yet — the
773
+ * single literal directory its INCLUSION globs name. Ambiguous → undefined,
774
+ * and the new file is reported instead of guessed.
775
+ *
776
+ * Only an inclusion glob says where files live: a negation
777
+ * (`!gen/**\/*.hand.ts`) names what is carved OUT, and read raw its leading
778
+ * `!gen` was a second "directory" that made every first generation ambiguous.
779
+ */
780
+ function sliceTargetDir(committed, globs) {
781
+ const dirs = new Set([...committed.keys()].map((p) => nodePath.posix.dirname(p)));
782
+ if (dirs.size === 1)
783
+ return [...dirs][0];
784
+ if (dirs.size > 1)
785
+ return undefined;
786
+ const statics = new Set(globListWalkGlobs(globs)
787
+ .map(globStaticDir)
788
+ .filter((d) => d !== undefined));
789
+ return statics.size === 1 ? [...statics][0] : undefined;
790
+ }
791
+ function writeOut(cwd, rel, content) {
792
+ const abs = nodePath.join(cwd, rel);
793
+ mkdirSync(nodePath.dirname(abs), { recursive: true });
794
+ writeFileSync(abs, content, 'utf8');
795
+ }
796
+ /**
797
+ * Write a regen unit's ALIGNED output over the committed tree.
798
+ *
799
+ * The keys come from {@link runOneRegen} — the same alignment `generated check`
800
+ * diffs against — so "which committed file does this output replace" has one
801
+ * answer for both verbs. Output that aligns to nothing is a new file: it is
802
+ * written only where the rule's own glob says generated files live, never at a
803
+ * bare temp-relative path (which is how a flat regen used to land at the repo
804
+ * root). A committed file the regen stopped producing is reported, not
805
+ * deleted: removing tracked files is a decision for a human.
806
+ */
807
+ function writeAligned(cwd, rule, scan, committed, files) {
808
+ const written = [];
809
+ const unchanged = [];
810
+ const created = [];
811
+ const notPlaced = [];
812
+ const blessed = new Set(scan.handMaintained);
813
+ const targetDir = sliceTargetDir(committed, rule.generatedGlob);
814
+ for (const [key, content] of files) {
815
+ if (committed.has(key)) {
816
+ if (committed.get(key) === content)
817
+ unchanged.push(key);
818
+ else {
819
+ writeOut(cwd, key, content);
820
+ written.push(key);
821
+ }
822
+ continue;
823
+ }
824
+ const candidates = [key, ...(targetDir ? [nodePath.posix.join(targetDir, key)] : [])];
825
+ // A new output is placed only where `generatedGlob` SELECTS it — never on
826
+ // a path the list excludes, which the next check would not cover.
827
+ const dest = candidates.find((c) => globListSelects(c, rule.generatedGlob) && !blessed.has(c));
828
+ if (!dest) {
829
+ notPlaced.push(key);
830
+ continue;
831
+ }
832
+ writeOut(cwd, dest, content);
833
+ created.push(dest);
834
+ }
835
+ const produced = new Set([...written, ...unchanged]);
836
+ const noLongerProduced = [...committed.keys()].filter((p) => !produced.has(p)).sort();
837
+ return {
838
+ written: written.sort(),
839
+ unchanged: unchanged.sort(),
840
+ created: created.sort(),
841
+ notPlaced: notPlaced.sort(),
842
+ noLongerProduced,
843
+ };
844
+ }
511
845
  export const generatedUpdateCommand = {
512
846
  name: 'update',
513
- description: 'Run the declared regen command in place the one-command bless step after an intentional source change. Writes files.',
847
+ description: 'The bless step after an intentional source change: regenerate into a temp dir with the SAME regen + path alignment `generated check` uses, then write the aligned files over their committed paths. Writes files; never deletes a committed file.',
514
848
  usage: 'shrk generated update [--id <ids>] [--json]',
515
849
  booleanFlags: new Set(['json']),
516
850
  async run(args) {
@@ -522,56 +856,74 @@ export const generatedUpdateCommand = {
522
856
  return writeNoRules(json);
523
857
  const results = [];
524
858
  for (const rule of prep.rules) {
525
- // A multi-writer rule blesses by running EVERY writer regenerating one
526
- // slice and calling the artifact updated is how a mixed tree drifts.
527
- const commands = rule.sources && rule.sources.length > 0
528
- ? rule.sources.map((src, i) => ({
529
- label: `${rule.id}/${src.id ?? i}`,
530
- command: src.regen,
531
- timeoutMs: src.timeoutMs ?? rule.timeoutMs ?? DEFAULT_TIMEOUT_MS,
532
- }))
533
- : rule.regen
534
- ? [{ label: rule.id, command: rule.regen, timeoutMs: rule.timeoutMs ?? DEFAULT_TIMEOUT_MS }]
535
- : [];
536
- if (commands.length === 0) {
537
- results.push({ id: rule.id, ran: false, error: 'header-only rule — nothing to regenerate' });
859
+ // `{TMP}` means the same thing here as in `check`. Update used to
860
+ // substitute the project root instead, so a flat regen such as
861
+ // `schemas emit --out {TMP}` wrote every file at the repo ROOT while
862
+ // `check` compared against docs/schemas/ — two answers to "where does
863
+ // the output land". Regenerating through runOneRegen makes it one.
864
+ // A multi-writer rule blesses EVERY writer's slice, never just one.
865
+ const scan = scanGeneratedFiles(prep.cwd, rule, prep.excludeDirs);
866
+ const units = regenUnitsOf(rule, scan);
867
+ if (units.length === 0) {
868
+ results.push({ id: rule.id, ran: false, error: 'header-only rule nothing to regenerate', ...EMPTY_WRITES });
538
869
  continue;
539
870
  }
540
- for (const { label, command, timeoutMs } of commands) {
541
- // `{TMP}` is the CHECK contract; `update` writes in place, so it is
542
- // substituted with the project root and the regen writes its real output.
543
- const child = spawnSync(command.split('{TMP}').join(prep.cwd), {
544
- cwd: prep.cwd,
545
- shell: true,
546
- encoding: 'utf8',
547
- timeout: timeoutMs,
548
- maxBuffer: 16 * 1024 * 1024,
549
- stdio: json ? 'pipe' : 'inherit',
550
- });
551
- if (child.error) {
552
- results.push({ id: label, ran: false, error: child.error.message });
553
- }
554
- else if (child.status !== 0) {
555
- results.push({ id: label, ran: true, error: `exited ${child.status ?? 'null'}` });
556
- }
557
- else {
558
- results.push({ id: label, ran: true });
871
+ for (const unit of units) {
872
+ const regen = runOneRegen(prep.cwd, unit.command, unit.committed, unit.timeoutMs);
873
+ if (regen.error || !regen.files) {
874
+ results.push({ id: unit.label, ran: true, error: regen.error ?? 'regen produced no files', ...EMPTY_WRITES });
875
+ continue;
559
876
  }
877
+ // An output the reader did not read (over the regen cap) is never
878
+ // written: it is listed, and the bless is reported incomplete (1).
879
+ // Its committed counterpart is not "no longer produced".
880
+ const unreadKeys = new Set((regen.unread ?? []).map((u) => u.path));
881
+ const aligned = writeAligned(prep.cwd, rule, scan, unit.committed, regen.files);
882
+ results.push({
883
+ id: unit.label,
884
+ ran: true,
885
+ ...aligned,
886
+ noLongerProduced: aligned.noLongerProduced.filter((p) => !unreadKeys.has(p)),
887
+ ...(unreadKeys.size > 0 ? { unreadOutputs: [...unreadKeys].sort() } : {}),
888
+ });
560
889
  }
561
890
  }
562
891
  const failed = results.filter((r) => r.error !== undefined);
892
+ // The bless is incomplete when output could not be placed, a committed
893
+ // file is no longer produced, or an output was too large to read:
894
+ // `generated check` would still fail, so exiting 0 here would claim a sync
895
+ // that did not happen.
896
+ const outOfSync = results.filter((r) => r.notPlaced.length > 0 || r.noLongerProduced.length > 0 || (r.unreadOutputs?.length ?? 0) > 0);
897
+ const exit = failed.length > 0 || outOfSync.length > 0 ? ExitCode.Failure : ExitCode.VerifiedPass;
563
898
  if (json) {
564
- process.stdout.write(asJson({ schema: SCHEMA, results }) + '\n');
565
- return failed.length > 0 ? ExitCode.Failure : ExitCode.VerifiedPass;
899
+ process.stdout.write(asJson({ schema: SCHEMA, inSync: failed.length === 0 && outOfSync.length === 0, results, exitCode: exit }) + '\n');
900
+ return exit;
566
901
  }
567
902
  process.stdout.write(header('Generated update'));
568
903
  for (const r of results) {
569
- process.stdout.write(` ${r.error ? '!' : '✓'} ${r.id}${r.error ? ` — ${r.error}` : ''}\n`);
570
- }
571
- if (failed.length === 0) {
572
- process.stdout.write('\nRegenerated. Review `git diff` before committing.\n');
904
+ if (r.error) {
905
+ process.stdout.write(` ! ${r.id} — ${r.error}\n`);
906
+ continue;
907
+ }
908
+ process.stdout.write(` ✓ ${r.id} — ${r.written.length} rewritten, ${r.created.length} new, ${r.unchanged.length} unchanged\n`);
909
+ for (const f of r.written)
910
+ process.stdout.write(` ~ ${f}\n`);
911
+ for (const f of r.created)
912
+ process.stdout.write(` + ${f}\n`);
913
+ for (const f of r.notPlaced) {
914
+ process.stdout.write(` ! not written: ${f} — maps to no committed file and lies outside generatedGlob\n`);
915
+ }
916
+ for (const f of r.unreadOutputs ?? []) {
917
+ process.stdout.write(` ! not written: ${f} — over the regen read cap, so it was never read\n`);
918
+ }
919
+ for (const f of r.noLongerProduced) {
920
+ process.stdout.write(` ? no longer produced (left in place — delete it if that is intended): ${f}\n`);
921
+ }
573
922
  }
574
- return failed.length > 0 ? ExitCode.Failure : ExitCode.VerifiedPass;
923
+ process.stdout.write(exit === ExitCode.VerifiedPass
924
+ ? '\nRegenerated. Review `git diff` before committing.\n'
925
+ : '\nNot fully blessed — `generated check` will still report the files above.\n');
926
+ return exit;
575
927
  },
576
928
  };
577
929
  export const generatedExplainCommand = {