hecks 2.9.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (600) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +51 -6
  3. data/lib/hecks/adapters/driven/heki/journal.rb +1 -1
  4. data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
  5. data/lib/hecks/adapters/driven/lambda.rb +1 -1
  6. data/lib/hecks/adapters/driven/memory.rb +16 -3
  7. data/lib/hecks/adapters/driven/postgres/reconnect.rb +6 -2
  8. data/lib/hecks/adapters/driven/postgres/shared_connection.rb +180 -0
  9. data/lib/hecks/adapters/driven/postgres.rb +4 -2
  10. data/lib/hecks/adapters/driven/prism.rb +41 -8
  11. data/lib/hecks/adapters/driven/tenant_provisioner.rb +73 -16
  12. data/lib/hecks/behaviors/expectations.rb +1 -1
  13. data/lib/hecks/behaviors.rb +1 -1
  14. data/lib/hecks/bench/cli.rb +62 -0
  15. data/lib/hecks/bench/environment.rb +71 -0
  16. data/lib/hecks/bench/postgres_probe.rb +30 -0
  17. data/lib/hecks/bench/report.rb +76 -0
  18. data/lib/hecks/bench/ruby_runner.rb +93 -0
  19. data/lib/hecks/bench/run.rb +55 -0
  20. data/lib/hecks/bench/rust_runner.rb +101 -0
  21. data/lib/hecks/bench/stats.rb +46 -0
  22. data/lib/hecks/bench/suite.rb +98 -0
  23. data/lib/hecks/bench/workload.rb +89 -0
  24. data/lib/hecks/bench.rb +17 -0
  25. data/lib/hecks/bluebook/assembly/contracts.rb +2 -0
  26. data/lib/hecks/bluebook/attribute.rb +1 -1
  27. data/lib/hecks/bluebook/behaviour/aggregate.rb +30 -1
  28. data/lib/hecks/bluebook/behaviour/domain_port.rb +7 -0
  29. data/lib/hecks/bluebook/chapter.rb +6 -2
  30. data/lib/hecks/bluebook/domain_port.rb +36 -5
  31. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +10 -0
  32. data/lib/hecks/bluebook/dsl/bluebook_builder.rb +8 -0
  33. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -1
  34. data/lib/hecks/bluebook/dsl/domain_port_builder.rb +31 -3
  35. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +122 -3
  36. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +1 -1
  37. data/lib/hecks/bluebook/dsl/query_builder.rb +27 -1
  38. data/lib/hecks/bluebook/dsl/rule_reference.rb +1 -1
  39. data/lib/hecks/bluebook/dsl/world_builder.rb +29 -2
  40. data/lib/hecks/bluebook/expression/canonical_form.rb +1 -1
  41. data/lib/hecks/bluebook/expression/resolver.rb +3 -2
  42. data/lib/hecks/bluebook/hexagon.rb +13 -5
  43. data/lib/hecks/bluebook/meta_validator/reconstruction.rb +1 -0
  44. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +4 -1
  45. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +246 -0
  46. data/lib/hecks/bluebook/meta_validator.rb +9 -4
  47. data/lib/hecks/bluebook/model_check.rb +23 -0
  48. data/lib/hecks/bluebook/pattern_subset.rb +38 -0
  49. data/lib/hecks/bluebook/query.rb +20 -5
  50. data/lib/hecks/bluebook/smoke_test.rb +1 -1
  51. data/lib/hecks/canonical_json.rb +30 -0
  52. data/lib/hecks/chapters.rb +102 -0
  53. data/lib/hecks/cli/behaviors.rb +61 -0
  54. data/lib/hecks/cli/canonicalise.rb +25 -0
  55. data/lib/hecks/cli/check_engine_agreement.rb +32 -0
  56. data/lib/hecks/cli/check_era.rb +88 -0
  57. data/lib/hecks/cli/console.rb +66 -0
  58. data/lib/hecks/cli/deploy_template_diff.rb +40 -0
  59. data/lib/hecks/cli/doc_coverage.rb +32 -0
  60. data/lib/hecks/cli/document.rb +2 -2
  61. data/lib/hecks/cli/expression_projection.rb +41 -0
  62. data/lib/hecks/cli/follow.rb +88 -0
  63. data/lib/hecks/cli/history.rb +44 -0
  64. data/lib/hecks/cli/ir.rb +1 -1
  65. data/lib/hecks/cli/mcp.rb +3 -3
  66. data/lib/hecks/cli/model_check.rb +7 -4
  67. data/lib/hecks/cli/pattern_cases.rb +47 -0
  68. data/lib/hecks/cli/present.rb +39 -0
  69. data/lib/hecks/cli/project_cli.rb +224 -32
  70. data/lib/hecks/cli/project_diagrams.rb +4 -3
  71. data/lib/hecks/cli/project_glossary.rb +44 -0
  72. data/lib/hecks/cli/reference.rb +46 -0
  73. data/lib/hecks/cli/refresh_projections.rb +44 -0
  74. data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +150 -0
  75. data/lib/hecks/cli/release.rb +70 -0
  76. data/lib/hecks/cli/release_gem.rb +59 -0
  77. data/lib/hecks/cli/rspec_io_parallel_files.rb +137 -0
  78. data/lib/hecks/cli/rspec_shard_files.rb +66 -0
  79. data/lib/hecks/cli/run.rb +3 -3
  80. data/lib/hecks/cli/seed_semantics_corpus.rb +71 -0
  81. data/lib/hecks/cli/serve_query_ir_mcp.rb +30 -0
  82. data/lib/hecks/cli/shape.rb +70 -0
  83. data/lib/hecks/cli/smoke_http.rb +230 -0
  84. data/lib/hecks/cli/smoke_test.rb +5 -3
  85. data/lib/hecks/cli/spec_example.rb +27 -0
  86. data/lib/hecks/cli/statements.rb +32 -0
  87. data/lib/hecks/cli/stores.rb +4 -5
  88. data/lib/hecks/cli/stress_concurrency_specs.rb +177 -0
  89. data/lib/hecks/cli.rb +201 -52
  90. data/lib/hecks/codemod.rb +324 -0
  91. data/lib/hecks/corpus.rb +423 -0
  92. data/lib/hecks/deploy/bluebook/deploy.bluebook +591 -23
  93. data/lib/hecks/deploy/bluebook/deploy.hecksagon +41 -4
  94. data/lib/hecks/deploy/oidc.json +82 -2
  95. data/lib/hecks/doc/reference.rb +351 -0
  96. data/lib/hecks/doors/cli_door.rb +196 -0
  97. data/lib/hecks/doors/cli_runner.rb +297 -0
  98. data/lib/hecks/{facade → doors}/command_request.rb +1 -1
  99. data/lib/hecks/{facade → doors}/handle.rb +2 -2
  100. data/lib/hecks/{facade → doors}/json_door.rb +4 -4
  101. data/lib/hecks/doors/launcher_options.rb +129 -0
  102. data/lib/hecks/{cli → doors}/mcp_door.rb +7 -7
  103. data/lib/hecks/doors/mcp_door_scope.rb +97 -0
  104. data/lib/hecks/{facade/surface → doors/ruby_door}/aggregate_door.rb +8 -3
  105. data/lib/hecks/{facade/surface → doors/ruby_door}/chapter.rb +3 -3
  106. data/lib/hecks/{facade/surface.rb → doors/ruby_door.rb} +36 -5
  107. data/lib/hecks/doors.rb +61 -0
  108. data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +1 -1
  109. data/lib/hecks/embryonaut_bluebook.rb +1 -1
  110. data/lib/hecks/engine_agreement.rb +156 -0
  111. data/lib/hecks/facade.rb +24 -37
  112. data/lib/hecks/forms/app.rb +6 -6
  113. data/lib/hecks/forms/banking_presentation.rb +50 -0
  114. data/lib/hecks/forms/field_shape.rb +2 -1
  115. data/lib/hecks/forms/port_argument.rb +1 -1
  116. data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +393 -0
  117. data/lib/hecks/fuzzing/combination_miner.rb +86 -0
  118. data/lib/hecks/fuzzing/concurrent_dispatch.rb +214 -0
  119. data/lib/hecks/fuzzing/coverage_campaign.rb +143 -0
  120. data/lib/hecks/fuzzing/differential.rb +156 -0
  121. data/lib/hecks/fuzzing/domain_generator.rb +615 -0
  122. data/lib/hecks/fuzzing/era_boundary.rb +71 -0
  123. data/lib/hecks/fuzzing/form_census.rb +136 -0
  124. data/lib/hecks/fuzzing/generated_domain_check.rb +93 -0
  125. data/lib/hecks/fuzzing/invalid_value_generator.rb +64 -0
  126. data/lib/hecks/fuzzing/isolated_boot.rb +301 -0
  127. data/lib/hecks/fuzzing/nondeterministic.rb +51 -0
  128. data/lib/hecks/fuzzing/outside_world.rb +45 -0
  129. data/lib/hecks/fuzzing/persistence_parity.rb +102 -0
  130. data/lib/hecks/fuzzing/properties/corrections.rb +88 -0
  131. data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +314 -0
  132. data/lib/hecks/fuzzing/properties/guards.rb +190 -0
  133. data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +300 -0
  134. data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +93 -0
  135. data/lib/hecks/fuzzing/properties/outbox.rb +89 -0
  136. data/lib/hecks/fuzzing/properties/querying.rb +175 -0
  137. data/lib/hecks/fuzzing/properties.rb +162 -0
  138. data/lib/hecks/fuzzing/qa_settings.rb +123 -0
  139. data/lib/hecks/fuzzing/replay.rb +505 -0
  140. data/lib/hecks/fuzzing/rotation_priority.rb +48 -0
  141. data/lib/hecks/fuzzing/rust_gap_manifest.rb +108 -0
  142. data/lib/hecks/fuzzing/self_consistency.rb +339 -0
  143. data/lib/hecks/fuzzing/sequence_generator/adversary.rb +385 -0
  144. data/lib/hecks/fuzzing/sequence_generator/catalog.rb +132 -0
  145. data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +95 -0
  146. data/lib/hecks/fuzzing/sequence_generator/picker.rb +77 -0
  147. data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +109 -0
  148. data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +237 -0
  149. data/lib/hecks/fuzzing/sequence_generator.rb +189 -0
  150. data/lib/hecks/fuzzing/shrinker.rb +166 -0
  151. data/lib/hecks/fuzzing/structural_skips.rb +33 -0
  152. data/lib/hecks/fuzzing/sweep_depth.rb +43 -0
  153. data/lib/hecks/fuzzing/target_capabilities.rb +163 -0
  154. data/lib/hecks/fuzzing/value_generator.rb +216 -0
  155. data/lib/hecks/fuzzing.rb +23 -0
  156. data/lib/hecks/grammar/evolve.rb +336 -0
  157. data/lib/hecks/hecks/adapters/codebase/codemods.rb +45 -0
  158. data/lib/hecks/hecks/adapters/codebase/conformance.rb +71 -0
  159. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +202 -0
  160. data/lib/hecks/hecks/adapters/codebase/gem_registry.rb +80 -0
  161. data/lib/hecks/hecks/adapters/codebase/kernel_tables.rb +54 -0
  162. data/lib/hecks/hecks/adapters/codebase/language.rb +240 -0
  163. data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +102 -0
  164. data/lib/hecks/hecks/adapters/codebase/publishing.rb +142 -0
  165. data/lib/hecks/hecks/adapters/codebase/regeneration.rb +67 -0
  166. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +90 -0
  167. data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +83 -0
  168. data/lib/hecks/hecks/adapters/codebase/secret_vault.rb +36 -0
  169. data/lib/hecks/hecks/adapters/codebase/source_tree.adapter +7 -0
  170. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +189 -0
  171. data/lib/hecks/hecks/adapters/codebase/sqlite_fixture.rb +71 -0
  172. data/lib/hecks/hecks/adapters/codebase/style.rb +171 -0
  173. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +55 -0
  174. data/lib/hecks/hecks/adapters/codebase/test_suite.rb +165 -0
  175. data/lib/hecks/hecks/adapters/codebase/tree.rb +111 -0
  176. data/lib/hecks/hecks/adapters/console_capture.rb +79 -0
  177. data/lib/hecks/hecks/adapters/deploy_toolchain.adapter +7 -0
  178. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +108 -0
  179. data/lib/hecks/hecks/adapters/git.adapter +7 -0
  180. data/lib/hecks/hecks/adapters/git.rb +102 -0
  181. data/lib/hecks/hecks/adapters/host_http.adapter +7 -0
  182. data/lib/hecks/hecks/adapters/host_http.rb +65 -0
  183. data/lib/hecks/hecks/adapters/in_process_boot.adapter +7 -0
  184. data/lib/hecks/hecks/adapters/in_process_boot.rb +197 -0
  185. data/lib/hecks/hecks/adapters/in_process_operations.rb +202 -0
  186. data/lib/hecks/hecks/adapters/journal_store/changes.rb +143 -0
  187. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +145 -0
  188. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +132 -0
  189. data/lib/hecks/hecks/adapters/journal_store/examination.rb +135 -0
  190. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +163 -0
  191. data/lib/hecks/hecks/adapters/journal_store/readings.rb +187 -0
  192. data/lib/hecks/hecks/adapters/journal_store.adapter +7 -0
  193. data/lib/hecks/hecks/adapters/journal_store.rb +64 -0
  194. data/lib/hecks/hecks/adapters/local_files.adapter +7 -0
  195. data/lib/hecks/hecks/adapters/local_files.rb +46 -0
  196. data/lib/hecks/hecks/adapters/pg_admin.adapter +7 -0
  197. data/lib/hecks/hecks/adapters/pg_admin.rb +245 -0
  198. data/lib/hecks/hecks/adapters/process_pool.adapter +7 -0
  199. data/lib/hecks/hecks/adapters/process_pool.rb +119 -0
  200. data/lib/hecks/hecks/adapters/rust_toolchain.adapter +7 -0
  201. data/lib/hecks/hecks/adapters/rust_toolchain.rb +179 -0
  202. data/lib/hecks/hecks/adapters/rust_workspace.rb +150 -0
  203. data/lib/hecks/hecks/adapters/shell.adapter +7 -0
  204. data/lib/hecks/hecks/adapters/shell.rb +49 -0
  205. data/lib/hecks/hecks/adapters/terminal.adapter +7 -0
  206. data/lib/hecks/hecks/adapters/terminal.rb +74 -0
  207. data/lib/hecks/hecks/codebase.behaviors +172 -0
  208. data/lib/hecks/hecks/codebase.bluebook +2453 -0
  209. data/lib/hecks/hecks/context_map.hecksagon +13 -0
  210. data/lib/hecks/hecks/custodian.behaviors +179 -0
  211. data/lib/hecks/hecks/custodian.bluebook +1847 -0
  212. data/lib/hecks/hecks/deploy.behaviors +74 -0
  213. data/lib/hecks/hecks/deploy.world +12 -0
  214. data/lib/hecks/hecks/environments/memory.world +10 -0
  215. data/lib/hecks/hecks/governance.world +7 -0
  216. data/lib/hecks/hecks/hecks.behaviors +62 -0
  217. data/lib/hecks/hecks/hecks.bluebook +124 -0
  218. data/lib/hecks/hecks/hecks.hecksagon +390 -0
  219. data/lib/hecks/hecks/hecks.world +42 -0
  220. data/lib/hecks/kernel_capabilities.rb +194 -0
  221. data/lib/hecks/language/bluebook/bluebook.bluebook +11 -0
  222. data/lib/hecks/language/bluebook/query.bluebook +7 -0
  223. data/lib/hecks/language/bluebook/vocabulary.bluebook +4 -4
  224. data/lib/hecks/language/hecksagon/domain_port.bluebook +10 -0
  225. data/lib/hecks/language/hecksagon/hecksagon.bluebook +20 -0
  226. data/lib/hecks/mcp_stdio_guard.rb +2 -2
  227. data/lib/hecks/persistence_legacy_fixture/regenerate.rb +161 -0
  228. data/lib/hecks/persistence_legacy_fixture.rb +113 -0
  229. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +8 -5
  230. data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +4 -1
  231. data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +15 -2
  232. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +1 -1
  233. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +1 -1
  234. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +1 -1
  235. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +32 -15
  236. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +6 -3
  237. data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +227 -0
  238. data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +1 -1
  239. data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +43 -24
  240. data/lib/hecks/ports/persistence/plugins/era/translation.rb +1 -0
  241. data/lib/hecks/ports/persistence/state_codec.rb +24 -5
  242. data/lib/hecks/projection_files.rb +285 -0
  243. data/lib/hecks/projections/bootstrap_table.rb +1 -1
  244. data/lib/hecks/projections/deploy/fargate.rb +5 -5
  245. data/lib/hecks/projections/deploy/lambda.rb +43 -32
  246. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  247. data/lib/hecks/projections/deploy/preview/template.rb +1 -1
  248. data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +1 -1
  249. data/lib/hecks/projections/deploy/scripts/expected-era.header +1 -1
  250. data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +3 -3
  251. data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +1 -1
  252. data/lib/hecks/projections/deploy/shared.rb +1 -1
  253. data/lib/hecks/projections/diagrams.rb +3 -3
  254. data/lib/hecks/projections/model/deviations.rb +1 -1
  255. data/lib/hecks/projections/parser_table.rb +4 -4
  256. data/lib/hecks/projections/reference.rb +2 -3
  257. data/lib/hecks/projections/rust_vocabulary.rb +1 -1
  258. data/lib/hecks/projector/cli_projector.rb +106 -37
  259. data/lib/hecks/projector.rb +1 -1
  260. data/lib/hecks/quality_control/adapters/agent.adapter +7 -0
  261. data/lib/hecks/quality_control/adapters/agent.rb +58 -0
  262. data/lib/hecks/quality_control/adapters/angle_tools.adapter +7 -0
  263. data/lib/hecks/quality_control/adapters/angle_tools.rb +15 -0
  264. data/lib/hecks/quality_control/adapters/clearance_recorder.rb +36 -0
  265. data/lib/hecks/quality_control/adapters/clearance_tools.adapter +7 -0
  266. data/lib/hecks/quality_control/adapters/clearance_tools.rb +17 -0
  267. data/lib/hecks/quality_control/adapters/git_pr.adapter +7 -0
  268. data/lib/hecks/quality_control/adapters/git_pr.rb +226 -0
  269. data/lib/hecks/quality_control/adapters/github_checks.adapter +9 -0
  270. data/lib/hecks/quality_control/adapters/github_checks.rb +83 -0
  271. data/lib/hecks/quality_control/adapters/github_ci_webhook.rb +56 -0
  272. data/lib/hecks/quality_control/adapters/github_issues.adapter +7 -0
  273. data/lib/hecks/quality_control/adapters/github_issues.rb +49 -0
  274. data/lib/hecks/quality_control/adapters/qa_tool.rb +59 -0
  275. data/lib/hecks/quality_control/adapters/sweep_tools.adapter +7 -0
  276. data/lib/hecks/quality_control/adapters/sweep_tools.rb +51 -0
  277. data/lib/hecks/quality_control/adapters/target_tools.adapter +7 -0
  278. data/lib/hecks/quality_control/adapters/target_tools.rb +43 -0
  279. data/lib/hecks/quality_control/cli/child.rb +45 -0
  280. data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +45 -0
  281. data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +248 -0
  282. data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +163 -0
  283. data/lib/hecks/quality_control/cli/qa_generated_domains.rb +450 -0
  284. data/lib/hecks/quality_control/cli/qa_log_bug.rb +195 -0
  285. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +220 -0
  286. data/lib/hecks/quality_control/cli/qa_open_pr.rb +305 -0
  287. data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +154 -0
  288. data/lib/hecks/quality_control/cli/qa_postgres_role.rb +159 -0
  289. data/lib/hecks/quality_control/cli/qa_pr_check.rb +232 -0
  290. data/lib/hecks/quality_control/cli/qa_seed_angles.rb +142 -0
  291. data/lib/hecks/quality_control/cli/qa_seed_targets.rb +69 -0
  292. data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +280 -0
  293. data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +329 -0
  294. data/lib/hecks/quality_control/cli/qa_sweep/dials.rb +92 -0
  295. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +117 -0
  296. data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +175 -0
  297. data/lib/hecks/quality_control/cli/qa_sweep/release_mode.rb +60 -0
  298. data/lib/hecks/quality_control/cli/qa_sweep.rb +611 -0
  299. data/lib/hecks/quality_control/cli/qa_tick.rb +274 -0
  300. data/lib/hecks/quality_control/quality_control.bluebook +2224 -0
  301. data/lib/hecks/quality_control/quality_control.hecksagon +13 -0
  302. data/lib/hecks/quality_control/quality_control.ports.hecksagon +54 -0
  303. data/lib/hecks/query_ir.rb +346 -0
  304. data/lib/hecks/query_ir_mcp.rb +198 -0
  305. data/lib/hecks/release/runner/ci_publisher.rb +2 -2
  306. data/lib/hecks/release/runner/clean_tree.rb +42 -0
  307. data/lib/hecks/release/runner/console.rb +6 -0
  308. data/lib/hecks/release/runner/gem_publisher.rb +14 -7
  309. data/lib/hecks/release/runner/npm_publisher.rb +6 -36
  310. data/lib/hecks/release/runner/preflight.rb +26 -2
  311. data/lib/hecks/release/runner/published.rb +8 -26
  312. data/lib/hecks/release/runner/tagger.rb +30 -15
  313. data/lib/hecks/release/runner.rb +57 -6
  314. data/lib/hecks/router/namespace_installer.rb +3 -3
  315. data/lib/hecks/runtime/adapter_lookup.rb +124 -0
  316. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +4 -0
  317. data/lib/hecks/runtime/dependency_planning.rb +20 -2
  318. data/lib/hecks/runtime/dispatcher.rb +53 -3
  319. data/lib/hecks/runtime/interpreting.rb +3 -0
  320. data/lib/hecks/runtime/loader.rb +65 -12
  321. data/lib/hecks/runtime/outbox.rb +1 -1
  322. data/lib/hecks/runtime/policy_interpreter.rb +5 -2
  323. data/lib/hecks/runtime/port_operation_interpreter.rb +2 -10
  324. data/lib/hecks/runtime/query_interpreter.rb +77 -0
  325. data/lib/hecks/runtime/reaction_outcome.rb +89 -0
  326. data/lib/hecks/runtime/refusal_wording.rb +23 -1
  327. data/lib/hecks/runtime/registry/verification.rb +163 -4
  328. data/lib/hecks/runtime/registry.rb +57 -3
  329. data/lib/hecks/runtime/remote_dispatcher.rb +31 -2
  330. data/lib/hecks/runtime/value/coercion.rb +23 -135
  331. data/lib/hecks/runtime/value/field_checks.rb +172 -0
  332. data/lib/hecks/runtime/value.rb +4 -1
  333. data/lib/hecks/runtime.rb +20 -6
  334. data/lib/hecks/rust_build/conformance.rb +158 -0
  335. data/lib/hecks/rust_build/conformance_fuzz.rb +71 -0
  336. data/lib/hecks/rust_build/coverage.rb +218 -0
  337. data/lib/hecks/rust_build/native_build.rb +124 -0
  338. data/lib/hecks/rust_build/project_rust/root_mod.rb +120 -0
  339. data/lib/hecks/rust_build/project_rust.rb +332 -0
  340. data/lib/hecks/rust_build/wasm.rb +105 -0
  341. data/lib/hecks/rust_build/wasm_browser.rb +113 -0
  342. data/lib/hecks/rust_build.rb +144 -0
  343. data/lib/hecks/storehouse.rb +16 -16
  344. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +2 -2
  345. data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +1 -1
  346. data/lib/hecks/three_zero/forms.yml +127 -0
  347. data/lib/hecks/three_zero.rb +12 -0
  348. data/lib/hecks/tools/argument_gate_matrix.rb +316 -0
  349. data/lib/hecks/tools/comment_style.rb +1070 -0
  350. data/lib/hecks/tools/corpus_report.rb +87 -0
  351. data/lib/hecks/tools/deploy_recipe.rb +255 -0
  352. data/lib/hecks/tools/deploy_recipe_lint.rb +282 -0
  353. data/lib/hecks/tools/drop_implicit_append_fields.rb +95 -0
  354. data/lib/hecks/tools/era_projection_backfill.rb +94 -0
  355. data/lib/hecks/tools/era_reattest.rb +146 -0
  356. data/lib/hecks/tools/evolve_run.rb +189 -0
  357. data/lib/hecks/tools/fuzz_sweep.rb +373 -0
  358. data/lib/hecks/tools/heki_compaction.rb +100 -0
  359. data/lib/hecks/tools/hoist_local_givens.rb +246 -0
  360. data/lib/hecks/tools/journal_compaction.rb +89 -0
  361. data/lib/hecks/tools/oidc_manifests.rb +70 -0
  362. data/lib/hecks/tools/query_ir_run.rb +81 -0
  363. data/lib/hecks/tools/regeneration_run.rb +166 -0
  364. data/lib/hecks/tools/rust_comment_style.rb +642 -0
  365. data/lib/hecks/tools/sequence_script.rb +62 -0
  366. data/lib/hecks/tools/tail_merge.rb +117 -0
  367. data/lib/hecks/tools/tenant_provisioning.rb +142 -0
  368. data/lib/hecks/tools/translation_audit.rb +208 -0
  369. data/lib/hecks/tools/translation_scaffold.rb +131 -0
  370. data/lib/hecks/tools.rb +86 -0
  371. data/lib/hecks/version.rb +1 -1
  372. data/lib/hecks.rb +32 -15
  373. data/qa/settings.yml +66 -0
  374. data/rust/.cargo/config.toml +6 -0
  375. data/rust/.gitignore +2 -0
  376. data/rust/Cargo.lock +54 -0
  377. data/rust/Cargo.toml +61 -0
  378. data/rust/build/.cargo/config.toml +3 -0
  379. data/rust/build/.gitignore +1 -0
  380. data/rust/build/Cargo.lock +7 -0
  381. data/rust/build/Cargo.toml +19 -0
  382. data/rust/build/src/build_artifact.rs +47 -0
  383. data/rust/build/src/cargo_sync.rs +248 -0
  384. data/rust/build/src/json.rs +421 -0
  385. data/rust/build/src/lineage_pass.rs +285 -0
  386. data/rust/build/src/main.rs +51 -0
  387. data/rust/build/src/optional_pass.rs +150 -0
  388. data/rust/build/src/pipeline.rs +264 -0
  389. data/rust/build/src/reserved_names.rs +76 -0
  390. data/rust/build/src/resolve.rs +124 -0
  391. data/rust/build/src/root.rs +25 -0
  392. data/rust/build/src/sidecars.rs +73 -0
  393. data/rust/build/src/subprocess.rs +51 -0
  394. data/rust/build/src/tmp.rs +31 -0
  395. data/rust/codegen/.cargo/config.toml +3 -0
  396. data/rust/codegen/.gitignore +1 -0
  397. data/rust/codegen/Cargo.lock +7 -0
  398. data/rust/codegen/Cargo.toml +20 -0
  399. data/rust/codegen/src/attr.rs +32 -0
  400. data/rust/codegen/src/bridging.rs +481 -0
  401. data/rust/codegen/src/commands.rs +1226 -0
  402. data/rust/codegen/src/constraints.rs +109 -0
  403. data/rust/codegen/src/dependency_planning.rs +209 -0
  404. data/rust/codegen/src/domain_generator.rs +1288 -0
  405. data/rust/codegen/src/exemplar.rs +230 -0
  406. data/rust/codegen/src/expr_emitter.rs +87 -0
  407. data/rust/codegen/src/fielded.rs +192 -0
  408. data/rust/codegen/src/hecks_naming.rs +33 -0
  409. data/rust/codegen/src/json.rs +335 -0
  410. data/rust/codegen/src/json_codec.rs +697 -0
  411. data/rust/codegen/src/literal.rs +207 -0
  412. data/rust/codegen/src/main.rs +290 -0
  413. data/rust/codegen/src/manifest.rs +227 -0
  414. data/rust/codegen/src/mutations.rs +784 -0
  415. data/rust/codegen/src/naming.rs +286 -0
  416. data/rust/codegen/src/ports.rs +208 -0
  417. data/rust/codegen/src/prelude.rs +116 -0
  418. data/rust/codegen/src/queries.rs +786 -0
  419. data/rust/codegen/src/reactions.rs +532 -0
  420. data/rust/codegen/src/read_models.rs +697 -0
  421. data/rust/codegen/src/reference_specs.rs +40 -0
  422. data/rust/codegen/src/registry.rs +966 -0
  423. data/rust/codegen/src/reserved_names.rs +76 -0
  424. data/rust/codegen/src/shared.rs +93 -0
  425. data/rust/codegen/src/skip_reason.rs +25 -0
  426. data/rust/codegen/src/types.rs +362 -0
  427. data/rust/host/.cargo/config.toml +3 -0
  428. data/rust/host/.gitignore +1 -0
  429. data/rust/host/Cargo.lock +4297 -0
  430. data/rust/host/Cargo.toml +61 -0
  431. data/rust/host/HECKS_RELEASE +1 -0
  432. data/rust/host/rds-ca-bundle.pem +2736 -0
  433. data/rust/host/src/api.rs +1596 -0
  434. data/rust/host/src/approval.rs +652 -0
  435. data/rust/host/src/auth.rs +1287 -0
  436. data/rust/host/src/bin/expr_harness.rs +46 -0
  437. data/rust/host/src/bin/lineage_harness.rs +119 -0
  438. data/rust/host/src/bin/mint_harness.rs +135 -0
  439. data/rust/host/src/checkout.rs +265 -0
  440. data/rust/host/src/dispatch.rs +1443 -0
  441. data/rust/host/src/expr_json.rs +1117 -0
  442. data/rust/host/src/field_hints.rs +52 -0
  443. data/rust/host/src/ir.rs +733 -0
  444. data/rust/host/src/journal.rs +874 -0
  445. data/rust/host/src/lambda_client.rs +378 -0
  446. data/rust/host/src/log.rs +147 -0
  447. data/rust/host/src/main.rs +474 -0
  448. data/rust/host/src/mint.rs +1865 -0
  449. data/rust/host/src/payments/keystore.rs +330 -0
  450. data/rust/host/src/payments/tests.rs +1400 -0
  451. data/rust/host/src/payments.rs +767 -0
  452. data/rust/host/src/presentation.rs +698 -0
  453. data/rust/host/src/presentation_write.rs +1431 -0
  454. data/rust/host/src/query_step.rs +149 -0
  455. data/rust/host/src/rate_limit.rs +904 -0
  456. data/rust/host/src/reference_transform.rs +241 -0
  457. data/rust/host/src/reference_validate.rs +338 -0
  458. data/rust/host/src/resend.rs +296 -0
  459. data/rust/host/src/secrets.rs +106 -0
  460. data/rust/host/src/server.rs +663 -0
  461. data/rust/host/src/storage_shape.rs +175 -0
  462. data/rust/host/src/test_pg.rs +40 -0
  463. data/rust/host/src/ui_schema.rs +1351 -0
  464. data/rust/host/src/wasm_runner.rs +191 -0
  465. data/rust/host/src/web/newsletter/cooldown.rs +126 -0
  466. data/rust/host/src/web/newsletter/tests.rs +104 -0
  467. data/rust/host/src/web/newsletter.rs +310 -0
  468. data/rust/host/src/web/newsletter_send.rs +357 -0
  469. data/rust/host/src/web/registrations.rs +1505 -0
  470. data/rust/host/src/web.rs +2937 -0
  471. data/rust/lsp/.gitignore +1 -0
  472. data/rust/lsp/Cargo.lock +7 -0
  473. data/rust/lsp/Cargo.toml +16 -0
  474. data/rust/lsp/README.md +133 -0
  475. data/rust/lsp/src/diagnostics.rs +165 -0
  476. data/rust/lsp/src/json.rs +385 -0
  477. data/rust/lsp/src/main.rs +399 -0
  478. data/rust/lsp/src/outline.rs +177 -0
  479. data/rust/lsp/src/rpc.rs +67 -0
  480. data/rust/parser/.cargo/config.toml +5 -0
  481. data/rust/parser/.gitignore +1 -0
  482. data/rust/parser/Cargo.lock +7 -0
  483. data/rust/parser/Cargo.toml +21 -0
  484. data/rust/parser/src/build/closed_sets.rs +27 -0
  485. data/rust/parser/src/build/identity.rs +276 -0
  486. data/rust/parser/src/build/mod.rs +12 -0
  487. data/rust/parser/src/build/naming.rs +141 -0
  488. data/rust/parser/src/build/pattern_subset.rs +161 -0
  489. data/rust/parser/src/build/query_derive.rs +72 -0
  490. data/rust/parser/src/build/query_inference.rs +269 -0
  491. data/rust/parser/src/build/query_options.rs +100 -0
  492. data/rust/parser/src/build/read_model.rs +94 -0
  493. data/rust/parser/src/build/references.rs +32 -0
  494. data/rust/parser/src/canonical.rs +114 -0
  495. data/rust/parser/src/diag.rs +55 -0
  496. data/rust/parser/src/emit.rs +953 -0
  497. data/rust/parser/src/expr/ast_json.rs +147 -0
  498. data/rust/parser/src/expr/evaluator.rs +81 -0
  499. data/rust/parser/src/expr/mod.rs +84 -0
  500. data/rust/parser/src/expr/resolver.rs +497 -0
  501. data/rust/parser/src/ir.rs +387 -0
  502. data/rust/parser/src/keywords.rs +585 -0
  503. data/rust/parser/src/lex.rs +662 -0
  504. data/rust/parser/src/main.rs +252 -0
  505. data/rust/parser/src/parse/aggregate.rs +612 -0
  506. data/rust/parser/src/parse/chapter.rs +517 -0
  507. data/rust/parser/src/parse/command.rs +637 -0
  508. data/rust/parser/src/parse/domain_port.rs +127 -0
  509. data/rust/parser/src/parse/entity.rs +527 -0
  510. data/rust/parser/src/parse/file.rs +67 -0
  511. data/rust/parser/src/parse/hecksagon.rs +231 -0
  512. data/rust/parser/src/parse/lifecycle.rs +162 -0
  513. data/rust/parser/src/parse/mod.rs +1262 -0
  514. data/rust/parser/src/parse/policy.rs +87 -0
  515. data/rust/parser/src/parse/process_manager.rs +305 -0
  516. data/rust/parser/src/parse/query.rs +100 -0
  517. data/rust/parser/src/parse/read_model.rs +214 -0
  518. data/rust/parser/src/parse/value_object.rs +250 -0
  519. data/rust/parser/src/ruby_value.rs +422 -0
  520. data/rust/project/bridging.rb +257 -0
  521. data/rust/project/commands.rb +877 -0
  522. data/rust/project/constraints.rb +86 -0
  523. data/rust/project/dependency_planning.rb +178 -0
  524. data/rust/project/domain_generator.rb +980 -0
  525. data/rust/project/exemplar.rb +148 -0
  526. data/rust/project/expr_emitter.rb +72 -0
  527. data/rust/project/fielded.rb +141 -0
  528. data/rust/project/json_codec.rb +641 -0
  529. data/rust/project/mutations.rb +721 -0
  530. data/rust/project/naming.rb +205 -0
  531. data/rust/project/ports.rb +65 -0
  532. data/rust/project/queries.rb +493 -0
  533. data/rust/project/reactions.rb +317 -0
  534. data/rust/project/read_models.rb +690 -0
  535. data/rust/project/reference_specs.rb +27 -0
  536. data/rust/project/registry.rb +396 -0
  537. data/rust/project/skip_reason.rb +29 -0
  538. data/rust/project/types.rb +225 -0
  539. data/rust/project/write_if_changed.rb +83 -0
  540. data/rust/project.rb +30 -0
  541. data/rust/project_rust_pipeline.rb +383 -0
  542. data/rust/src/exemplar/commands.rs +279 -0
  543. data/rust/src/exemplar/constraints.rs +18 -0
  544. data/rust/src/exemplar/fielded.rs +280 -0
  545. data/rust/src/exemplar/json.rs +277 -0
  546. data/rust/src/exemplar/mod.rs +17 -0
  547. data/rust/src/exemplar/mutations.rs +80 -0
  548. data/rust/src/exemplar/queries.rs +33 -0
  549. data/rust/src/exemplar/reactions.rs +88 -0
  550. data/rust/src/exemplar/read_models.rs +48 -0
  551. data/rust/src/exemplar/registry.rs +166 -0
  552. data/rust/src/exemplar/types.rs +44 -0
  553. data/rust/src/kernel/attribute_shapes/composite.rs +88 -0
  554. data/rust/src/kernel/attribute_shapes/list.rs +18 -0
  555. data/rust/src/kernel/attribute_shapes/mod.rs +44 -0
  556. data/rust/src/kernel/attribute_shapes/optional.rs +9 -0
  557. data/rust/src/kernel/attribute_shapes/scalar.rs +39 -0
  558. data/rust/src/kernel/cli.rs +585 -0
  559. data/rust/src/kernel/dispatch.rs +1090 -0
  560. data/rust/src/kernel/expr.rs +458 -0
  561. data/rust/src/kernel/expression_operators/arithmetic.rs +128 -0
  562. data/rust/src/kernel/expression_operators/comparison.rs +48 -0
  563. data/rust/src/kernel/expression_operators/enumeration.rs +232 -0
  564. data/rust/src/kernel/expression_operators/logical.rs +13 -0
  565. data/rust/src/kernel/expression_operators/membership.rs +18 -0
  566. data/rust/src/kernel/expression_operators/mod.rs +68 -0
  567. data/rust/src/kernel/expression_operators/pattern_match.rs +73 -0
  568. data/rust/src/kernel/expression_operators/positional.rs +126 -0
  569. data/rust/src/kernel/expression_operators/presence.rs +101 -0
  570. data/rust/src/kernel/expression_operators/sign_test.rs +16 -0
  571. data/rust/src/kernel/expression_operators/sized.rs +35 -0
  572. data/rust/src/kernel/expression_operators/text.rs +79 -0
  573. data/rust/src/kernel/expression_operators/to_string.rs +18 -0
  574. data/rust/src/kernel/json.rs +779 -0
  575. data/rust/src/kernel/mod.rs +133 -0
  576. data/rust/src/kernel/named_query.rs +219 -0
  577. data/rust/src/kernel/orchestrate.rs +1810 -0
  578. data/rust/src/kernel/pattern.rs +307 -0
  579. data/rust/src/kernel/query_comparators.rs +206 -0
  580. data/rust/src/kernel/query_ordering.rs +103 -0
  581. data/rust/src/kernel/read_model.rs +914 -0
  582. data/rust/src/kernel/reference_lookup.rs +149 -0
  583. data/rust/src/kernel/repository.rs +468 -0
  584. data/rust/src/kernel/routing.rs +612 -0
  585. data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +118 -0
  586. data/rust/src/kernel/vocab/entity_dispatch_order.rs +114 -0
  587. data/rust/src/kernel/vocab/field_hint.rs +70 -0
  588. data/rust/src/kernel/vocab/mod.rs +20 -0
  589. data/rust/src/kernel/vocab/query_comparator.rs +65 -0
  590. data/rust/src/kernel/vocab/refusal_template.rs +1592 -0
  591. data/rust/src/lib.rs +8 -0
  592. data/rust/src/main.rs +16 -0
  593. data/rust/web/.gitignore +1 -0
  594. data/rust/web/Cargo.lock +119 -0
  595. data/rust/web/Cargo.toml +18 -0
  596. data/rust/web/src/lib.rs +8 -0
  597. metadata +477 -12
  598. data/lib/hecks/facade/cli_door.rb +0 -96
  599. data/lib/hecks/facade/cli_runner.rb +0 -127
  600. data/lib/hecks/mcp_door_scope.rb +0 -95
@@ -0,0 +1,13 @@
1
+ # The QualityControl chapter, bound so it boots standing alone (a fuzz or model_check boot of this
2
+ # directory). The QA ledger binds PostgresEra in qa/bluebook/quality_control.hecksagon, and the
3
+ # Hecks chapter binds it through its world's `default_adapter`; the ports every one of them shares
4
+ # are in quality_control.ports.hecksagon.
5
+ Hecks.hecksagon "QualityControl" do
6
+ uses_framework "Governance"
7
+ persisted_by "Memory"
8
+ end
9
+
10
+ # The anti-corruption layer for Governance, which the chapter's roles are checked against.
11
+ Hecks.hecksagon "Governance" do
12
+ persisted_by "Memory"
13
+ end
@@ -0,0 +1,54 @@
1
+ # The ports QualityControl declares, for every hecksagon that attaches the chapter (`Chapters`
2
+ # loads this file with it). Persistence is the attaching hecksagon's: the Hecks chapter binds
3
+ # every aggregate through its world's `default_adapter`, and the QA ledger binds its own
4
+ # (qa/bluebook/quality_control.hecksagon).
5
+ Hecks.hecksagon "QualityControl" do
6
+ # Bound by adapters/github_issues. The port has to be declared even where no adapter binds it,
7
+ # so FileWhenSubmitted has a target and model_check's deaf-policy check can see it.
8
+ QualityControl::Ticket.port "IssueTracker" do
9
+ asks "File", to: Ticket do
10
+ answers "IssueFiled"
11
+ refuses "IssueFilingRefused"
12
+ end
13
+
14
+ tells "Closed", to: Ticket do
15
+ emits "IssueClosedUpstream"
16
+ end
17
+ end
18
+
19
+ # Bound by adapters/github_checks. ClearOnPass and RefuseOnFail turn its answer into
20
+ # Clearance::Passed/Failed.
21
+ QualityControl::Clearance.port "CI" do
22
+ asks "Run", to: Clearance do
23
+ answers "SuitePassed"
24
+ refuses "SuiteFailed"
25
+ end
26
+ end
27
+
28
+ # Queries answered from outside the domain. The bluebook declares each question and the value
29
+ # object its answer takes and never says who answers it; each `answers_query` binds it to the
30
+ # `*Tools` port's adapter, which runs the script's own command and answers what it printed.
31
+ QualityControl::Target.port "TargetTools" do
32
+ answers_query "Seed"
33
+ answers_query "CheckGeneratedDomains"
34
+ answers_query "MineCombinations"
35
+ answers_query "JudgeNovelty"
36
+ answers_query "DiscoverExternalDomains"
37
+ end
38
+
39
+ QualityControl::Sweep.port "SweepTools" do
40
+ answers_query "Tick"
41
+ answers_query "Run"
42
+ answers_query "MigrateLedgerFromHeki"
43
+ answers_query "CreateLedgerRole"
44
+ answers_query "Race"
45
+ end
46
+
47
+ QualityControl::Angle.port "AngleTools" do
48
+ answers_query "Seed"
49
+ end
50
+
51
+ QualityControl::Clearance.port "ClearanceTools" do
52
+ answers_query "CheckPullRequests"
53
+ end
54
+ end
@@ -0,0 +1,346 @@
1
+ require_relative "bluebook"
2
+ require_relative "codemod"
3
+ require_relative "projections/model"
4
+ require_relative "fuzzing/properties"
5
+
6
+ module Hecks
7
+ # Shared core of `hecks ir_constructs` and `hecks serve_query_ir_mcp`.
8
+ # Returns structured data; formatting belongs to each front end.
9
+ module QueryIR
10
+ Codemod = Hecks::Codemod
11
+ Deviations = Hecks::Projections::Model::Deviations
12
+
13
+ # Mirrors the conformance spec's MODEL_CONSTRUCTS; kept here because a spec is not a library.
14
+ CONSTRUCTS = {
15
+ "Bluebook" => Hecks::Bluebook::Chapter,
16
+ "Aggregate" => Hecks::Bluebook::Aggregate,
17
+ "Command" => Hecks::Bluebook::Command,
18
+ "Entity" => Hecks::Bluebook::Entity,
19
+ "ValueObject" => Hecks::Bluebook::ValueObject,
20
+ "Policy" => Hecks::Bluebook::Policy,
21
+ "Query" => Hecks::Bluebook::Query,
22
+ "ReadModel" => Hecks::Bluebook::ReadModel,
23
+ "ProcessManager" => Hecks::Bluebook::ProcessManager
24
+ }.freeze
25
+
26
+ Rule = Struct.new(:kind, :description, :canonical, :location, keyword_init: true)
27
+
28
+ module_function
29
+
30
+ # Reads the fields the language declares for one construct kind, from the grammar itself.
31
+ #
32
+ # @param name [String] a `CONSTRUCTS` key, such as `"Aggregate"`
33
+ # @return [Array<Symbol>] the attribute names the self-hosted meta-domain
34
+ # grammar declares for `name`
35
+ def meta_declared(name)
36
+ Hecks::Bluebook::MetaValidator.grammar_registry
37
+ .bluebook("Bluebook").aggregate(name).attributes.map(&:name)
38
+ end
39
+
40
+ # The structural diff between what a Ruby IR class emits (`ir_spec.keys`) and what the
41
+ # self-hosted meta-domain declares, using the `Deviations` data the conformance spec reads.
42
+ # @param name [String] a `CONSTRUCTS` key, such as `"Aggregate"`
43
+ # @return [Hash{Symbol => Object}] `:name`, `:declared`, `:emitted`, plus
44
+ # `:missing_from_ruby` and `:unaccounted_in_ruby` — each an `Array<Symbol>`
45
+ # @raise [ArgumentError] if `name` is not a `CONSTRUCTS` key
46
+ def construct_diff(name)
47
+ klass = CONSTRUCTS.fetch(name) do
48
+ raise ArgumentError, "no such construct #{name.inspect} — known: #{CONSTRUCTS.keys.join(', ')}"
49
+ end
50
+ declared = meta_declared(name)
51
+ emitted = klass.ir_spec.keys
52
+
53
+ accounted = declared.reject { |field| Deviations.parent_ref?(field) } -
54
+ Deviations.judge_only(name) -
55
+ Deviations.folded(name).values.flatten -
56
+ Deviations.off_the_wire(name) -
57
+ Deviations.dynamic_tail(name) -
58
+ Deviations.unpacked(name).keys
59
+
60
+ unaccounted = emitted -
61
+ declared -
62
+ Deviations.contained(name) -
63
+ Deviations.folded(name).keys -
64
+ Deviations.computed(name) -
65
+ Deviations.unpacked(name).values.flatten
66
+
67
+ { name: name, declared: declared, emitted: emitted,
68
+ missing_from_ruby: accounted - emitted, unaccounted_in_ruby: unaccounted }
69
+ end
70
+
71
+ # Diffs one or every construct kind at once.
72
+ #
73
+ # @param names [Array<String>] `CONSTRUCTS` keys to diff, every construct when empty
74
+ # @return [Array<Hash>] one `construct_diff` result per name
75
+ def constructs(names = [])
76
+ targets = names.empty? ? CONSTRUCTS.keys : names
77
+ targets.map { |name| construct_diff(name) }
78
+ end
79
+
80
+ # Every given/ensures/invariant reachable from a booted registry, walked recursively.
81
+ #
82
+ # Object identity carries no signal: `MetaValidator.call` rebuilds the graph from flat rows,
83
+ # so a bare `given("x")` reference and its block declaration are distinct objects by then.
84
+ # @param registry [Runtime::Registry] the booted registry to walk
85
+ # @param chapter_name [String, nil] one chapter to walk, every booted chapter when nil
86
+ # @return [Array<Rule>]
87
+ def collect_rules(registry, chapter_name = nil)
88
+ rules = []
89
+ chapters = chapter_name ? [registry.bluebook(chapter_name)] : registry.bluebooks.values
90
+
91
+ chapters.each do |chapter|
92
+ chapter.aggregates.each do |aggregate|
93
+ walk_construct_rules(aggregate, aggregate.hecks_name, rules)
94
+ aggregate.value_objects.each do |vo|
95
+ vo.invariants.each do |rule|
96
+ rules << Rule.new(kind: "invariant", description: rule.description, canonical: rule.canonical,
97
+ location: "#{aggregate.hecks_name}::#{vo.hecks_name} (declared)")
98
+ end
99
+ end
100
+ end
101
+ end
102
+ rules
103
+ end
104
+
105
+ # Recursive walk behind `collect_rules`; appends to `rules` in place.
106
+ def walk_construct_rules(construct, path, rules)
107
+ (construct.respond_to?(:preconditions) ? construct.preconditions : []).each do |rule|
108
+ rules << Rule.new(kind: "given", description: rule.description, canonical: rule.canonical,
109
+ location: "#{path} (declared)")
110
+ end
111
+ (construct.respond_to?(:invariants) ? construct.invariants : []).each do |rule|
112
+ rules << Rule.new(kind: "invariant", description: rule.description, canonical: rule.canonical,
113
+ location: "#{path} (declared)")
114
+ end
115
+ construct.commands.each do |command|
116
+ command.givens.each do |rule|
117
+ rules << Rule.new(kind: "given", description: rule.description, canonical: rule.canonical,
118
+ location: "#{path}.#{command.hecks_name}")
119
+ end
120
+ command.ensures.each do |rule|
121
+ rules << Rule.new(kind: "ensures", description: rule.description, canonical: rule.canonical,
122
+ location: "#{path}.#{command.hecks_name}")
123
+ end
124
+ end
125
+ return unless construct.respond_to?(:entities)
126
+
127
+ construct.entities.each do |piece|
128
+ walk_construct_rules(piece, "#{path}.#{piece.hecks_name}", rules)
129
+ end
130
+ end
131
+ private_class_method :walk_construct_rules
132
+
133
+ # A rule's owner: the path a "(declared)" location names, or a command-level location
134
+ # with its trailing `.CommandName` stripped.
135
+ #
136
+ # @param location [String] a `Rule#location`
137
+ # @return [String] the owning construct's path, with `" (declared)"` or a trailing
138
+ # `.CommandName` segment stripped
139
+ def owner_of(location)
140
+ return location.sub(/ \(declared\)\z/, "") if location.end_with?(" (declared)")
141
+
142
+ location.rpartition(".").first
143
+ end
144
+
145
+ # Groups rules by (kind, description, canonical) and reports groups with more than one
146
+ # real declaration, counted by owner rather than object identity (see `collect_rules`).
147
+ #
148
+ # `domains: []` scans the self-hosted meta-domain only; `nil` adds every real example.
149
+ # @param domains [Array<String>, nil] domain root directories to scan; every real
150
+ # example (`Codemod::EXAMPLE_ROOTS`) when nil, none beyond the meta-domain when `[]`
151
+ # @param include_meta [Boolean] whether to also scan the self-hosted meta-domain
152
+ # @return [Array<Hash>] one entry per duplicate group: `:kind`, `:description`,
153
+ # `:canonical`, and `:locations` (`Array<String>`)
154
+ def duplicates(domains: nil, include_meta: true)
155
+ domains ||= Codemod::EXAMPLE_ROOTS
156
+ all_rules = []
157
+ all_rules.concat(collect_rules(Codemod.meta_registry)) if include_meta
158
+
159
+ domains.each do |domain_dir|
160
+ bluebook_files = Dir.glob(File.join(domain_dir, "bluebook", "*.bluebook"))
161
+ next if bluebook_files.empty?
162
+
163
+ registry = Codemod.load_bluebook(bluebook_files)
164
+ all_rules.concat(collect_rules(registry))
165
+ end
166
+
167
+ all_rules.group_by { |r| [r.kind, r.description, r.canonical] }
168
+ .map { |key, rules| [key, rules, declaration_count(rules)] }
169
+ .select { |_, _, count| count > 1 }
170
+ .map do |(kind, description, canonical), rules, _|
171
+ { kind: kind, description: description, canonical: canonical,
172
+ locations: rules.map(&:location) }
173
+ end
174
+ end
175
+
176
+ # A rule owned by a nested entity is also covered by a "(declared)" entry anywhere under the
177
+ # same root aggregate, matching the DSL's shared given pool.
178
+ #
179
+ # Known gap: a bare chapter-wide `given(desc)` reference still shows as its own "(declared)"
180
+ # owner, because the built IR cannot tell a reference from a declaration. Verify a flagged
181
+ # group by hand before treating it as fresh duplication.
182
+ # @param rules [Array<Rule>] one duplicate-key group's own rules
183
+ # @return [Integer] how many of `rules` are real, independent declarations
184
+ def declaration_count(rules)
185
+ declared = rules.select { |r| r.location.end_with?(" (declared)") }
186
+ declared_owners = declared.to_set { |r| owner_of(r.location) }
187
+ declared_given_roots = declared.select { |r| r.kind == "given" }
188
+ .to_set { |r| owner_of(r.location).split(".").first }
189
+
190
+ standalone = rules.reject do |r|
191
+ owner = owner_of(r.location)
192
+ next true if declared_owners.include?(owner)
193
+ next true if r.kind == "given" && owner.include?(".") && declared_given_roots.include?(owner.split(".").first)
194
+
195
+ false
196
+ end
197
+
198
+ declared_owners.size + standalone.size
199
+ end
200
+ private_class_method :declaration_count
201
+
202
+ # Renders `constructs` output as text, shared by `hecks ir_constructs` and `hecks
203
+ # serve_query_ir_mcp`.
204
+ #
205
+ # @param diffs [Array<Hash>] `constructs`' own output
206
+ # @return [String] the human-readable rendering
207
+ def format_constructs(diffs)
208
+ diffs.map do |diff|
209
+ lines = ["== #{diff[:name]} =="]
210
+ lines << " emits: #{diff[:emitted].join(', ')}"
211
+ lines << " declares: #{diff[:declared].join(', ')}"
212
+ if diff[:missing_from_ruby].empty? && diff[:unaccounted_in_ruby].empty?
213
+ lines << " clean — every declared field is emitted (or a named deviation), nothing emitted is undeclared"
214
+ else
215
+ unless diff[:missing_from_ruby].empty?
216
+ lines << " MISSING FROM RUBY (declared, not emitted, not a named deviation): " \
217
+ "#{diff[:missing_from_ruby].join(', ')}"
218
+ end
219
+ unless diff[:unaccounted_in_ruby].empty?
220
+ lines << " UNACCOUNTED IN RUBY (emitted, not declared, not a named deviation): " \
221
+ "#{diff[:unaccounted_in_ruby].join(', ')}"
222
+ end
223
+ end
224
+ lines.join("\n")
225
+ end.join("\n\n")
226
+ end
227
+
228
+ # The two `MetaValidator::Reconstruction` methods written by hand rather than driven by
229
+ # `Assembly::Contracts`; `impact_preview` checks touchpoint 4 only for these.
230
+ RECONSTRUCTION_METHODS = { "Aggregate" => :aggregate, "Entity" => :entity }.freeze
231
+
232
+ # Checks the six touchpoints of `.claude/skills/bluebook-construct-creator/SKILL.md` for a
233
+ # construct/field pair. Advisory, not a gate: a `false` can be a legitimate exemption
234
+ # (`Deviations`, `GUARANTEED_BY_CONSTRUCTION`, or the spec-only `META_DOMAIN_KNOWN_GAPS`).
235
+ #
236
+ # @param name [String] a `CONSTRUCTS` key, such as `"Aggregate"`
237
+ # @param field [String, Symbol] the declared field to check propagation for
238
+ # @return [Hash{Symbol => Object}] `:name`, `:field`, and `:touchpoints` — an
239
+ # `Array<Hash>` of `:touchpoint` (String) and `:present` (Boolean, or nil when the
240
+ # touchpoint does not apply to `name`)
241
+ # @raise [ArgumentError] if `name` is not a `CONSTRUCTS` key
242
+ def impact_preview(name, field)
243
+ CONSTRUCTS.fetch(name) { raise ArgumentError, "no such construct #{name.inspect} — known: #{CONSTRUCTS.keys.join(', ')}" }
244
+ field = field.to_s
245
+
246
+ {
247
+ name: name,
248
+ field: field,
249
+ touchpoints: [
250
+ { touchpoint: "meta-domain grammar declares it", present: meta_declared(name).map(&:to_s).include?(field) },
251
+ { touchpoint: "docs/resolution-rules/ names it", present: resolution_rule_mentions?(field) },
252
+ { touchpoint: "Assembly::Contracts consumes it", present: contract_consumes?(name, field) },
253
+ { touchpoint: "Reconstruction's hand-typed method reads it", present: reconstruction_reads?(name, field) },
254
+ { touchpoint: "fuzzer FEATURE_COVERAGE/GUARANTEED_BY_CONSTRUCTION claims it", present: fuzzer_claims?(name, field) },
255
+ { touchpoint: "Rust mirror (rust/parser/src/parse/*.rs) mentions it", present: rust_mentions?(field) }
256
+ ]
257
+ }
258
+ end
259
+
260
+ def resolution_rule_mentions?(field)
261
+ paths = Dir.glob(File.join(Codemod::ROOT, "docs/resolution-rules/*.md")) +
262
+ Dir.glob(File.join(Codemod::ROOT, "docs/implemented/resolution-rules/*.md"))
263
+ paths.any? { |path| File.read(path).include?(field) }
264
+ end
265
+ private_class_method :resolution_rule_mentions?
266
+
267
+ def contract_consumes?(name, field)
268
+ contract = Hecks::Bluebook::Assembly.contract(name)
269
+ contract.fields.key?(field.to_sym) || contract.derived.key?(field.to_sym)
270
+ rescue KeyError
271
+ false
272
+ end
273
+ private_class_method :contract_consumes?
274
+
275
+ # nil, not false, for constructs without a hand-typed method: the touchpoint does not apply.
276
+ def reconstruction_reads?(name, field)
277
+ method_name = RECONSTRUCTION_METHODS[name]
278
+ # rubocop:disable-next Style/ReturnNilInPredicateMethodDefinition -- nil vs
279
+ # false is a deliberate distinction here: nil means "not applicable" (no
280
+ # hand-typed method to check), false means "applicable, and it fails" —
281
+ # see the spec's own "not-applicable (nil), not false" example.
282
+ return nil unless method_name
283
+
284
+ file, start_line = Hecks::Bluebook::MetaValidator::Reconstruction.instance_method(method_name).source_location
285
+ lines = File.readlines(file)
286
+ def_line = lines[start_line - 1]
287
+ indent = def_line[/\A\s*/]
288
+
289
+ body = lines[start_line..].take_while do |line|
290
+ line.strip.empty? || line[/\A\s*/].size > indent.size || !line.lstrip.start_with?("def ")
291
+ end
292
+ body.join.include?("#{field}:")
293
+ end
294
+ private_class_method :reconstruction_reads?
295
+
296
+ def fuzzer_claims?(name, field)
297
+ key = "#{name}##{field}"
298
+ Hecks::Fuzzing::Properties::FEATURE_COVERAGE.values.flatten.include?(key) ||
299
+ Hecks::Fuzzing::Properties::GUARANTEED_BY_CONSTRUCTION.key?(key)
300
+ end
301
+ private_class_method :fuzzer_claims?
302
+
303
+ def rust_mentions?(field)
304
+ Dir.glob(File.join(Codemod::ROOT, "rust/parser/src/parse/*.rs")).any? { |path| File.read(path).include?(field) }
305
+ end
306
+ private_class_method :rust_mentions?
307
+
308
+ # Renders one field's touchpoint checklist as text.
309
+ #
310
+ # @param preview [Hash] `impact_preview`'s own output
311
+ # @return [String] the human-readable rendering
312
+ def format_impact_preview(preview)
313
+ lines = ["== #{preview[:name]}##{preview[:field]} =="]
314
+ preview[:touchpoints].each do |t|
315
+ mark = if t[:present].nil?
316
+ "n/a"
317
+ else
318
+ (t[:present] ? "yes" : "NOT YET")
319
+ end
320
+ lines << " [#{mark.rjust(7)}] #{t[:touchpoint]}"
321
+ end
322
+ done = preview[:touchpoints].count { |t| t[:present] == true }
323
+ total = preview[:touchpoints].count { |t| !t[:present].nil? }
324
+ lines << ""
325
+ lines << "#{done}/#{total} applicable touchpoint(s) show signs of this field — advisory, not a gate; " \
326
+ "a NOT YET can be a legitimate exemption (Deviations, GUARANTEED_BY_CONSTRUCTION, or a spec-only " \
327
+ "META_DOMAIN_KNOWN_GAPS entry this module deliberately never reads)."
328
+ lines.join("\n")
329
+ end
330
+
331
+ # Renders the duplicate-rule groups as text.
332
+ #
333
+ # @param groups [Array<Hash>] `duplicates`' own output
334
+ # @return [String] the human-readable rendering
335
+ def format_duplicates(groups)
336
+ return "no duplicate given/invariant/ensures rule found" if groups.empty?
337
+
338
+ body = groups.map do |group|
339
+ ["== #{group[:kind]}: #{group[:description].inspect} — #{group[:canonical]} ==",
340
+ *group[:locations].map { |loc| " #{loc}" }].join("\n")
341
+ end.join("\n\n")
342
+
343
+ "#{body}\n\n#{groups.size} duplicate group(s), #{groups.sum { |g| g[:locations].size }} declarations total"
344
+ end
345
+ end
346
+ end
@@ -0,0 +1,198 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "mcp_stdio_guard"
5
+ require_relative "query_ir"
6
+
7
+ module Hecks
8
+ # The MCP server that exposes `Hecks::QueryIR`'s queries as tools, over newline-delimited
9
+ # JSON-RPC.
10
+ #
11
+ # It is stdio only and unauthenticated (ADR 0062): {.start} refuses any other setup before it
12
+ # reads a request. The query logic lives in `Hecks::QueryIR`; this is only the protocol.
13
+ module QueryIrMcp
14
+ # The MCP protocol revision the server speaks.
15
+ PROTOCOL_VERSION = "2024-11-05"
16
+
17
+ # The three tools, as an MCP client lists them.
18
+ TOOLS = [
19
+ {
20
+ name: "query_ir_constructs",
21
+ description: "For a bluebook IR construct (Aggregate, Entity, Command, Query, ValueObject, Policy, " \
22
+ "ReadModel, ProcessManager, Bluebook) or all of them, the real structural diff between " \
23
+ "what the Ruby class emits and what the self-hosted meta-domain declares for it. Use " \
24
+ "this instead of reading entity.rb/entity.bluebook (or the equivalent pair for another " \
25
+ "construct) by hand to check whether a new IR field has been fully propagated.",
26
+ inputSchema: {
27
+ type: "object",
28
+ properties: {
29
+ names: {
30
+ type: "array",
31
+ items: { type: "string" },
32
+ description: "Construct names to check (e.g. [\"Entity\"]). Omit or leave empty for all constructs."
33
+ }
34
+ }
35
+ }
36
+ },
37
+ {
38
+ name: "query_ir_duplicates",
39
+ description: "Every given/ensures/invariant DECLARATION across the real corpus " \
40
+ "(banking/pizzas/compliance) and the self-hosted meta-domain, grouped by (kind, " \
41
+ "description, canonical predicate), reporting every group with more than one " \
42
+ "independently-declared rule (already-deduped references, which share the same " \
43
+ "underlying rule object, are excluded). Use this to find real corpus duplication " \
44
+ "before proposing a new 'declared once, referenced by name' resolution rule.",
45
+ inputSchema: {
46
+ type: "object",
47
+ properties: {
48
+ domains: {
49
+ type: "array",
50
+ items: { type: "string" },
51
+ description: "Real example domain directories to scan (e.g. [\"examples/banking\"]). Omit for " \
52
+ "every real example domain."
53
+ },
54
+ include_meta: {
55
+ type: "boolean",
56
+ description: "Include the self-hosted meta-domain (lib/hecks/language/bluebook). Defaults to true."
57
+ }
58
+ }
59
+ }
60
+ },
61
+ {
62
+ name: "query_ir_impact",
63
+ description: "For one construct/field pair, which of the six propagation touchpoints " \
64
+ ".claude/skills/bluebook-construct-creator/SKILL.md walks in prose already show signs " \
65
+ "of it, and which don't: the meta-domain grammar, docs/resolution-rules/, " \
66
+ "Assembly::Contracts, Reconstruction's hand-typed aggregate(row)/entity(row) (only " \
67
+ "applicable to Aggregate/Entity), the fuzzer's FEATURE_COVERAGE/" \
68
+ "GUARANTEED_BY_CONSTRUCTION, and the Rust mirror. Advisory, not a gate.",
69
+ inputSchema: {
70
+ type: "object",
71
+ properties: {
72
+ name: { type: "string", description: "Construct name (e.g. \"Aggregate\")." },
73
+ field: { type: "string", description: "The field name to check (e.g. \"preconditions\")." }
74
+ },
75
+ required: %w[name field]
76
+ }
77
+ }
78
+ ].freeze
79
+
80
+ module_function
81
+
82
+ # Checks the process is set up for stdio, prints the startup warning to stderr, and serves
83
+ # requests until stdin closes.
84
+ #
85
+ # @param argv [Array<String>] the arguments the guard checks (only `--stdio` is accepted)
86
+ # @param input [IO] where requests arrive
87
+ # @param output [IO] where responses leave
88
+ # @return [void]
89
+ # @raise [SystemExit] when the setup is not stdio, with the guard's status
90
+ def start(argv: [], input: $stdin, output: $stdout)
91
+ McpStdioGuard.start!(server: "hecks-query-ir", notes: notes, argv: argv, stdin: input, stdout: output)
92
+ serve(input: input, output: output)
93
+ end
94
+
95
+ # @return [Array<String>] what the startup warning says beyond the guard's common lines
96
+ def notes
97
+ ["No identity is asked for or checked; every tool answers every caller.",
98
+ "query_ir_duplicates loads .bluebook files under #{Storehouse::BOOT_ROOT} as Ruby."]
99
+ end
100
+
101
+ # Answers each request line until the input ends.
102
+ #
103
+ # @param input [#each_line] newline-delimited JSON-RPC requests
104
+ # @param output [#puts] where each response is written, one JSON object a line
105
+ # @return [void]
106
+ def serve(input:, output:)
107
+ output.sync = true if output.respond_to?(:sync=)
108
+ input.each_line do |line|
109
+ line = line.strip
110
+ next if line.empty?
111
+
112
+ request = parse(line, output)
113
+ respond(request, output) if request
114
+ end
115
+ end
116
+
117
+ # @param name [String] the tool
118
+ # @param arguments [Hash] its arguments as the client sent them
119
+ # @return [Hash] the MCP tool result
120
+ def call_tool(name, arguments)
121
+ case name
122
+ when "query_ir_constructs"
123
+ tool_result(QueryIR.format_constructs(QueryIR.constructs(Array(arguments["names"]))))
124
+ when "query_ir_duplicates" then tool_result(duplicates(arguments))
125
+ when "query_ir_impact"
126
+ tool_result(QueryIR.format_impact_preview(QueryIR.impact_preview(arguments["name"], arguments["field"])))
127
+ else
128
+ tool_result("no such tool: #{name.inspect} - known: #{TOOLS.map { |t| t[:name] }.join(', ')}", error: true)
129
+ end
130
+ rescue ArgumentError, Runtime::TypeMismatch => e
131
+ tool_result(e.message, error: true)
132
+ end
133
+
134
+ # @api private
135
+ def duplicates(arguments)
136
+ domains = confined_domains(arguments["domains"])
137
+ QueryIR.format_duplicates(QueryIR.duplicates(domains: domains, include_meta: arguments.fetch("include_meta", true)))
138
+ end
139
+
140
+ # `duplicates` loads every .bluebook under each directory as Ruby, so paths must stay in the
141
+ # storehouse boot root.
142
+ #
143
+ # @api private
144
+ def confined_domains(requested)
145
+ return nil if requested.nil? || requested.empty?
146
+
147
+ Array(requested).map { |dir| Storehouse.confine!(dir, "domains") }
148
+ end
149
+
150
+ # @api private
151
+ def tool_result(text, error: false)
152
+ { content: [{ type: "text", text: text }], isError: error }
153
+ end
154
+
155
+ # @api private
156
+ def parse(line, output)
157
+ JSON.parse(line)
158
+ rescue JSON::ParserError => e
159
+ error(output, nil, -32_700, "Parse error: #{e.message}")
160
+ nil
161
+ end
162
+
163
+ # @api private
164
+ def respond(request, output)
165
+ handle(request, output)
166
+ rescue StandardError => e
167
+ error(output, request["id"], -32_603, "Internal error: #{e.class}: #{e.message}")
168
+ end
169
+
170
+ # @api private
171
+ def handle(request, output)
172
+ id = request["id"]
173
+ params = request["params"] || {}
174
+ case request["method"]
175
+ when "initialize" then result(output, id, initialized)
176
+ when "notifications/initialized" then nil
177
+ when "tools/list" then result(output, id, { tools: TOOLS })
178
+ when "tools/call" then result(output, id, call_tool(params["name"], params["arguments"] || {}))
179
+ when "ping" then result(output, id, {})
180
+ else error(output, id, -32_601, "Method not found: #{request['method']}") unless id.nil?
181
+ end
182
+ end
183
+
184
+ # @api private
185
+ def initialized
186
+ { protocolVersion: PROTOCOL_VERSION, capabilities: { tools: {} },
187
+ serverInfo: { name: "hecks-query-ir", version: "1.0.0" } }
188
+ end
189
+
190
+ # @api private
191
+ def result(output, id, body) = output.puts(JSON.generate({ jsonrpc: "2.0", id: id, result: body }))
192
+
193
+ # @api private
194
+ def error(output, id, code, message)
195
+ output.puts(JSON.generate({ jsonrpc: "2.0", id: id, error: { code: code, message: message } }))
196
+ end
197
+ end
198
+ end
@@ -57,8 +57,8 @@ module Hecks
57
57
  def self.timeout_hint(version)
58
58
  "Timed out after #{LIMIT / 60} minutes waiting for CI to publish @hecks/client #{version}. " \
59
59
  "See the run: gh run list --workflow publish-client.yml. Re-run it: " \
60
- "gh workflow run publish-client.yml -f tag=v#{version}. Then finish with: bin/release --npm-only. " \
61
- "To publish from this machine instead: bin/release --npm-only --npm-local."
60
+ "gh workflow run publish-client.yml -f tag=v#{version}. Then finish with: hecks publish --npm-only --confirm. " \
61
+ "To publish from this machine instead: hecks publish --npm-only --npm-local --confirm."
62
62
  end
63
63
 
64
64
  private
@@ -0,0 +1,42 @@
1
+ require_relative "commands"
2
+
3
+ module Hecks
4
+ module Release
5
+ class Runner
6
+ # The check that what a release ships is exactly what is committed: nothing modified,
7
+ # untracked, or ignored under the paths the gem packages.
8
+ #
9
+ # `git status --porcelain` alone hides ignored files, and an ignored file under `lib/` or
10
+ # `rust/` is one a build of the gem could carry. Build output (`target/`) is ignored on
11
+ # purpose and never packaged, so it is not counted.
12
+ class CleanTree
13
+ # The repository paths the gem packages.
14
+ SHIPPED = %w[lib rust exe qa/settings.yml].freeze
15
+
16
+ # Ignored build output, which the gem leaves out.
17
+ BUILD_OUTPUT = %r{(\A|/)target/?\z}
18
+
19
+ # @param git [#read] runs a git command in the checkout and answers its output
20
+ def initialize(git:)
21
+ @git = git
22
+ end
23
+
24
+ # @return [Array<String>] each modified, untracked or ignored path under the packaged paths
25
+ def stray
26
+ lines = @git.read("status", "--porcelain", "--ignored", "--", *SHIPPED).lines.map(&:strip)
27
+ lines.reject(&:empty?).map { |line| line.sub(/\A\S+\s+/, "") }.grep_v(BUILD_OUTPUT)
28
+ end
29
+
30
+ # @return [void]
31
+ # @raise [Refusal] naming the first few stray paths
32
+ def check!
33
+ paths = stray
34
+ return if paths.empty?
35
+
36
+ raise Refusal, "#{paths.size} file(s) under #{SHIPPED.join(', ')} are modified, untracked or " \
37
+ "ignored (#{paths.first(5).join(', ')}); commit or delete them before releasing"
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -15,6 +15,12 @@ module Hecks
15
15
  @assume_yes = assume_yes
16
16
  end
17
17
 
18
+ # @return [IO] where progress goes
19
+ attr_reader :out
20
+
21
+ # @return [IO] where refusals and failures go
22
+ attr_reader :err
23
+
18
24
  # Prints a progress line.
19
25
  #
20
26
  # @param text [String] the line