hecks 2.9.0 → 3.0.1

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 +13 -4
  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 +15 -8
  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 +15 -5
  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,662 @@
1
+ //! The lexer: strips comments and blank lines, then applies the shape gate.
2
+ //! Bare Ruby expressions, control flow and assignment are refused, never interpreted.
3
+
4
+ use crate::diag::Diagnostic;
5
+
6
+ #[derive(Debug, Clone, Copy)]
7
+ pub struct SourceLine<'a> {
8
+ pub number: usize,
9
+ pub text: &'a str,
10
+ }
11
+
12
+ /// Comment-stripped, blank-free lines with 1-based numbers; a `#` inside a string is kept.
13
+ pub fn lines(source: &str) -> Vec<SourceLine<'_>> {
14
+ source
15
+ .lines()
16
+ .enumerate()
17
+ .filter_map(|(idx, raw)| {
18
+ let stripped = strip_comment(raw);
19
+ let trimmed = stripped.trim();
20
+ if trimmed.is_empty() {
21
+ None
22
+ } else {
23
+ Some(SourceLine {
24
+ number: idx + 1,
25
+ text: trimmed,
26
+ })
27
+ }
28
+ })
29
+ .collect()
30
+ }
31
+
32
+ /// Joins lines continued by open brackets, a trailing `\`, or a trailing `,`.
33
+ ///
34
+ /// Keeps the input's line count (merged-away lines become empty) so diagnostics stay accurate.
35
+ /// Comments are stripped before joining, or one would swallow the lines merged after it.
36
+ pub fn join_continuations(source: &str) -> String {
37
+ let raw_lines: Vec<&str> = source.lines().collect();
38
+ let mut out: Vec<String> = vec![String::new(); raw_lines.len()];
39
+ let mut depth: i32 = 0;
40
+ let mut merge_target: Option<usize> = None;
41
+
42
+ for (idx, raw_line) in raw_lines.iter().enumerate() {
43
+ let mut text = strip_comment(raw_line).trim().to_string();
44
+
45
+ // A trailing `\` joins adjacent string literals; `ruby_value::read` concatenates them.
46
+ let backslash_continues = ends_with_bare_backslash(&text);
47
+ if backslash_continues {
48
+ text = text[..text.len() - 1].trim_end().to_string();
49
+ }
50
+
51
+ match merge_target {
52
+ Some(target) if target != idx => {
53
+ if !text.is_empty() {
54
+ if !out[target].is_empty() {
55
+ out[target].push(' ');
56
+ }
57
+ out[target].push_str(&text);
58
+ }
59
+ }
60
+ _ => {
61
+ out[idx].push_str(&text);
62
+ merge_target = Some(idx);
63
+ }
64
+ }
65
+
66
+ depth += bracket_delta(&text);
67
+
68
+ // A trailing bare comma continues the statement, as in Ruby.
69
+ let comma_continues = depth <= 0 && ends_with_bare_comma(&text);
70
+
71
+ if depth <= 0 {
72
+ depth = 0;
73
+ if !comma_continues && !backslash_continues {
74
+ merge_target = None;
75
+ }
76
+ }
77
+ }
78
+
79
+ out.join("\n")
80
+ }
81
+
82
+ fn ends_outside_quotes(text: &str) -> bool {
83
+ let mut quoting = false;
84
+ let mut escaping = false;
85
+ for ch in text.chars() {
86
+ if escaping {
87
+ escaping = false;
88
+ continue;
89
+ }
90
+ if quoting && ch == '\\' {
91
+ escaping = true;
92
+ continue;
93
+ }
94
+ if ch == '"' {
95
+ quoting = !quoting;
96
+ continue;
97
+ }
98
+ }
99
+ !quoting
100
+ }
101
+
102
+ fn ends_with_bare_backslash(text: &str) -> bool {
103
+ text.ends_with('\\') && ends_outside_quotes(text)
104
+ }
105
+
106
+ fn ends_with_bare_comma(text: &str) -> bool {
107
+ text.ends_with(',') && ends_outside_quotes(text)
108
+ }
109
+
110
+ /// Not bracket-type aware: mismatched bracket types never occur in real source.
111
+ fn bracket_delta(text: &str) -> i32 {
112
+ let mut depth = 0i32;
113
+ let mut quoting = false;
114
+ let mut escaping = false;
115
+ for ch in text.chars() {
116
+ if escaping {
117
+ escaping = false;
118
+ continue;
119
+ }
120
+ if quoting && ch == '\\' {
121
+ escaping = true;
122
+ continue;
123
+ }
124
+ if ch == '"' {
125
+ quoting = !quoting;
126
+ continue;
127
+ }
128
+ if quoting {
129
+ continue;
130
+ }
131
+ match ch {
132
+ '(' | '{' | '[' => depth += 1,
133
+ ')' | '}' | ']' => depth -= 1,
134
+ _ => {}
135
+ }
136
+ }
137
+ depth
138
+ }
139
+
140
+ fn strip_comment(line: &str) -> &str {
141
+ let mut quoting = false;
142
+ let mut escaping = false;
143
+ for (byte_idx, ch) in line.char_indices() {
144
+ if escaping {
145
+ escaping = false;
146
+ continue;
147
+ }
148
+ if quoting && ch == '\\' {
149
+ escaping = true;
150
+ continue;
151
+ }
152
+ if ch == '"' {
153
+ quoting = !quoting;
154
+ continue;
155
+ }
156
+ if ch == '#' && !quoting {
157
+ return &line[..byte_idx];
158
+ }
159
+ }
160
+ line
161
+ }
162
+
163
+ #[derive(Debug, Clone, PartialEq, Eq)]
164
+ pub enum Opener {
165
+ None,
166
+ DoBlock {
167
+ params: Option<String>,
168
+ },
169
+ BraceBlock {
170
+ body: String,
171
+ },
172
+ }
173
+
174
+ #[derive(Debug, Clone, PartialEq, Eq)]
175
+ pub struct Call {
176
+ pub word: String,
177
+ /// Raw text between the word and the opener; the argument gate tokenizes it.
178
+ pub args: String,
179
+ pub opener: Opener,
180
+ }
181
+
182
+ #[derive(Debug, Clone, PartialEq, Eq)]
183
+ pub enum LineShape {
184
+ End,
185
+ Call(Call),
186
+ }
187
+
188
+ /// Ruby keywords refused up front so the diagnostic names the real problem.
189
+ const FORBIDDEN_LEADING_WORDS: &[&str] = &[
190
+ "if", "unless", "while", "until", "case", "begin", "def", "class", "module", "return", "next",
191
+ "break", "redo", "retry", "yield", "lambda", "proc", "for", "loop",
192
+ ];
193
+
194
+ /// Only a file's top line is receiver-qualified; a nested `Hecks.x` must stay an error.
195
+ const FILE_RECEIVER_PREFIX: &str = "Hecks.";
196
+
197
+ pub fn classify<'a>(file: &str, line: &SourceLine<'a>) -> Result<LineShape, Diagnostic> {
198
+ let text = line
199
+ .text
200
+ .strip_prefix(FILE_RECEIVER_PREFIX)
201
+ .unwrap_or(line.text);
202
+
203
+ if text == "end" {
204
+ return Ok(LineShape::End);
205
+ }
206
+
207
+ let word_end = leading_identifier_end(text);
208
+ if word_end == 0 {
209
+ return Err(Diagnostic::new(
210
+ file,
211
+ line.number,
212
+ format!("'{text}' is not a word call — every line must be a keyword call or `end`"),
213
+ ));
214
+ }
215
+ let word = &text[..word_end];
216
+
217
+ if FORBIDDEN_LEADING_WORDS.contains(&word) {
218
+ return Err(Diagnostic::new(
219
+ file,
220
+ line.number,
221
+ format!(
222
+ "'{word}' is a bare Ruby control-flow/declaration form, which this parser never \
223
+ interprets — a bluebook may only use keyword calls this language declares"
224
+ ),
225
+ ));
226
+ }
227
+
228
+ let rest = text[word_end..].trim_start();
229
+
230
+ if let Some(top_level_eq) = find_top_level_assignment(rest) {
231
+ return Err(Diagnostic::new(
232
+ file,
233
+ line.number,
234
+ format!(
235
+ "'{}' looks like a local variable assignment at byte {top_level_eq} — this \
236
+ parser never interprets bare Ruby expressions or assignment",
237
+ text
238
+ ),
239
+ ));
240
+ }
241
+
242
+ let (args, opener) = split_opener(rest);
243
+ let args = strip_balanced_parens(args.trim());
244
+
245
+ Ok(LineShape::Call(Call {
246
+ word: word.to_string(),
247
+ args: args.to_string(),
248
+ opener,
249
+ }))
250
+ }
251
+
252
+ fn leading_identifier_end(text: &str) -> usize {
253
+ let mut end = 0;
254
+ for (idx, ch) in text.char_indices() {
255
+ if idx == 0 {
256
+ if ch.is_ascii_alphabetic() || ch == '_' {
257
+ end = idx + ch.len_utf8();
258
+ continue;
259
+ } else {
260
+ return 0;
261
+ }
262
+ }
263
+ if ch.is_ascii_alphanumeric() || ch == '_' {
264
+ end = idx + ch.len_utf8();
265
+ } else {
266
+ break;
267
+ }
268
+ }
269
+ end
270
+ }
271
+
272
+ /// A top-level (not inside quotes/braces/brackets/parens) bare `=` that
273
+ /// isn't part of a wider operator (`==`, `!=`, `<=`, `>=`, `=>`, `=~`) or a
274
+ /// `+=`/`-=`-style compound assignment, and isn't a hash-rocket. Returns
275
+ /// the byte offset of the first one found, if any.
276
+ fn find_top_level_assignment(text: &str) -> Option<usize> {
277
+ let bytes = text.as_bytes();
278
+ let mut depth: i32 = 0;
279
+ let mut quoting = false;
280
+ let mut escaping = false;
281
+
282
+ for (idx, ch) in text.char_indices() {
283
+ if escaping {
284
+ escaping = false;
285
+ continue;
286
+ }
287
+ if quoting && ch == '\\' {
288
+ escaping = true;
289
+ continue;
290
+ }
291
+ if ch == '"' {
292
+ quoting = !quoting;
293
+ continue;
294
+ }
295
+ if quoting {
296
+ continue;
297
+ }
298
+ match ch {
299
+ '{' | '[' | '(' => depth += 1,
300
+ '}' | ']' | ')' => depth -= 1,
301
+ '=' if depth == 0 => {
302
+ let prev = if idx == 0 {
303
+ None
304
+ } else {
305
+ Some(bytes[idx - 1] as char)
306
+ };
307
+ let next = text[idx + 1..].chars().next();
308
+ let is_wide_operator = matches!(
309
+ prev,
310
+ Some('=' | '!' | '<' | '>' | '+' | '-' | '*' | '/' | '~')
311
+ ) || matches!(next, Some('=' | '~' | '>'));
312
+ if !is_wide_operator {
313
+ return Some(idx);
314
+ }
315
+ }
316
+ _ => {}
317
+ }
318
+ }
319
+ None
320
+ }
321
+
322
+ fn split_opener(rest: &str) -> (String, Opener) {
323
+ if let Some((before, params)) = trailing_do(rest) {
324
+ return (before.to_string(), Opener::DoBlock { params });
325
+ }
326
+
327
+ if let Some(brace_start) = find_top_level_brace(rest) {
328
+ let (before, brace_and_after) = rest.split_at(brace_start);
329
+ if let Some((body, _after)) = matching_brace_body(brace_and_after) {
330
+ return (before.to_string(), Opener::BraceBlock { body });
331
+ }
332
+ // Unbalanced on this line: the body wraps across lines, so keep the whole tail.
333
+ return (
334
+ before.to_string(),
335
+ Opener::BraceBlock {
336
+ body: brace_and_after.to_string(),
337
+ },
338
+ );
339
+ }
340
+
341
+ (rest.to_string(), Opener::None)
342
+ }
343
+
344
+ fn trailing_do(rest: &str) -> Option<(&str, Option<String>)> {
345
+ let trimmed = rest.trim_end();
346
+ if let Some(before) = trimmed.strip_suffix(" do") {
347
+ return Some((before, None));
348
+ }
349
+ if trimmed.ends_with('|') {
350
+ let without_trailing_pipe = &trimmed[..trimmed.len() - 1];
351
+ if let Some(open_pipe) = without_trailing_pipe.rfind('|') {
352
+ let params = &without_trailing_pipe[open_pipe + 1..];
353
+ let before_pipe = trimmed[..open_pipe].trim_end();
354
+ if let Some(before) = before_pipe.strip_suffix(" do") {
355
+ return Some((before, Some(params.trim().to_string())));
356
+ }
357
+ }
358
+ }
359
+ if trimmed == "do" {
360
+ return Some(("", None));
361
+ }
362
+ None
363
+ }
364
+
365
+ /// A `{` inside parens or written as a hash literal argument is not a source-body opener.
366
+ fn find_top_level_brace(text: &str) -> Option<usize> {
367
+ let mut quoting = false;
368
+ let mut escaping = false;
369
+ let mut paren_depth: i32 = 0;
370
+ for (idx, ch) in text.char_indices() {
371
+ if escaping {
372
+ escaping = false;
373
+ continue;
374
+ }
375
+ if quoting && ch == '\\' {
376
+ escaping = true;
377
+ continue;
378
+ }
379
+ if ch == '"' {
380
+ quoting = !quoting;
381
+ continue;
382
+ }
383
+ if quoting {
384
+ continue;
385
+ }
386
+ match ch {
387
+ '(' => paren_depth += 1,
388
+ ')' => paren_depth -= 1,
389
+ '{' if paren_depth == 0 => {
390
+ if is_hash_literal_brace(&text[..idx]) {
391
+ continue;
392
+ }
393
+ return Some(idx);
394
+ }
395
+ _ => {}
396
+ }
397
+ }
398
+ None
399
+ }
400
+
401
+ /// A `{` after `:`, `,` or `>` opens a hash literal; `>` covers a parenless `:k => { ... }`.
402
+ fn is_hash_literal_brace(before: &str) -> bool {
403
+ matches!(
404
+ before.trim_end().chars().last(),
405
+ Some(':') | Some(',') | Some('>')
406
+ )
407
+ }
408
+
409
+ fn matching_brace_body(text: &str) -> Option<(String, &str)> {
410
+ let mut depth: i32 = 0;
411
+ let mut quoting = false;
412
+ let mut escaping = false;
413
+ let mut start_byte = None;
414
+
415
+ for (idx, ch) in text.char_indices() {
416
+ if escaping {
417
+ escaping = false;
418
+ continue;
419
+ }
420
+ if quoting && ch == '\\' {
421
+ escaping = true;
422
+ continue;
423
+ }
424
+ if ch == '"' {
425
+ quoting = !quoting;
426
+ continue;
427
+ }
428
+ if quoting {
429
+ continue;
430
+ }
431
+ if ch == '{' {
432
+ if depth == 0 {
433
+ start_byte = Some(idx + 1);
434
+ }
435
+ depth += 1;
436
+ } else if ch == '}' {
437
+ depth -= 1;
438
+ if depth == 0 {
439
+ let start = start_byte?;
440
+ let body = text[start..idx].trim().to_string();
441
+ let after = &text[idx + 1..];
442
+ return Some((body, after));
443
+ }
444
+ }
445
+ }
446
+ None
447
+ }
448
+
449
+ /// Splits `Pizzas::Order.port "X" do` into receiver and rest; legal only in a hecksagon body,
450
+ /// so it stays out of `classify`.
451
+ pub fn strip_aggregate_receiver(text: &str) -> Option<(&str, &str)> {
452
+ let bytes = text.as_bytes();
453
+ if bytes.is_empty() || !bytes[0].is_ascii_uppercase() {
454
+ return None;
455
+ }
456
+
457
+ let mut i = 0;
458
+ loop {
459
+ let start = i;
460
+ while i < bytes.len() && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'_') {
461
+ i += 1;
462
+ }
463
+ if i == start || !bytes[start].is_ascii_uppercase() {
464
+ return None;
465
+ }
466
+ if text[i..].starts_with("::") {
467
+ i += 2;
468
+ continue;
469
+ }
470
+ break;
471
+ }
472
+
473
+ let rest = text[i..].strip_prefix('.')?;
474
+ if rest.is_empty() {
475
+ return None;
476
+ }
477
+ Some((&text[..i], rest))
478
+ }
479
+
480
+ /// Captures a `do ... end` body as raw space-joined text, tracking nested `do` blocks textually.
481
+ pub fn capture_do_block_body<'a>(
482
+ file: &str,
483
+ lines: &[SourceLine<'a>],
484
+ pos: &mut usize,
485
+ ) -> Result<String, Diagnostic> {
486
+ let mut depth: i32 = 0;
487
+ let mut parts: Vec<&'a str> = Vec::new();
488
+
489
+ loop {
490
+ let line = *lines.get(*pos).ok_or_else(|| {
491
+ let last = lines.last().map(|l| l.number).unwrap_or(0);
492
+ Diagnostic::new(
493
+ file,
494
+ last,
495
+ "unexpected end of file — still inside a `do ... end` block".to_string(),
496
+ )
497
+ })?;
498
+ *pos += 1;
499
+
500
+ if line.text == "end" {
501
+ if depth == 0 {
502
+ return Ok(parts.join(" "));
503
+ }
504
+ depth -= 1;
505
+ parts.push(line.text);
506
+ continue;
507
+ }
508
+ if opens_a_do_block(line.text) {
509
+ depth += 1;
510
+ }
511
+ parts.push(line.text);
512
+ }
513
+ }
514
+
515
+ /// Textual check, not `classify`: raw source bodies are exempt from the word-call shape.
516
+ fn opens_a_do_block(text: &str) -> bool {
517
+ let trimmed = text.trim_end();
518
+ trimmed == "do"
519
+ || trimmed.ends_with(" do")
520
+ || (trimmed.ends_with('|') && trimmed.contains(" do "))
521
+ }
522
+
523
+ fn strip_balanced_parens(text: &str) -> &str {
524
+ if text.starts_with('(') && text.ends_with(')') && text.len() >= 2 {
525
+ let inner = &text[1..text.len() - 1];
526
+ // Strip only when the parens wrap the whole text.
527
+ let mut depth = 0i32;
528
+ let mut quoting = false;
529
+ let mut escaping = false;
530
+ for (idx, ch) in inner.char_indices() {
531
+ if escaping {
532
+ escaping = false;
533
+ continue;
534
+ }
535
+ if quoting && ch == '\\' {
536
+ escaping = true;
537
+ continue;
538
+ }
539
+ if ch == '"' {
540
+ quoting = !quoting;
541
+ continue;
542
+ }
543
+ if quoting {
544
+ continue;
545
+ }
546
+ match ch {
547
+ '(' => depth += 1,
548
+ ')' => {
549
+ depth -= 1;
550
+ if depth < 0 {
551
+ return text;
552
+ }
553
+ }
554
+ _ => {}
555
+ }
556
+ let _ = idx;
557
+ }
558
+ return inner.trim();
559
+ }
560
+ text
561
+ }
562
+
563
+ #[cfg(test)]
564
+ mod tests {
565
+ use super::*;
566
+
567
+ #[test]
568
+ fn does_not_strip_a_bare_lowercase_call() {
569
+ assert_eq!(
570
+ strip_aggregate_receiver("uses_framework \"Governance\""),
571
+ None
572
+ );
573
+ assert_eq!(strip_aggregate_receiver("subscribe \"Deposited\""), None);
574
+ }
575
+
576
+ #[test]
577
+ fn strips_comments_and_blanks() {
578
+ let source = "aggregate \"Pizza\" do # a comment\n\n attribute :name, String\nend\n";
579
+ let got = lines(source);
580
+ assert_eq!(got.len(), 3);
581
+ assert_eq!(got[0].text, "aggregate \"Pizza\" do");
582
+ assert_eq!(got[0].number, 1);
583
+ assert_eq!(got[1].text, "attribute :name, String");
584
+ assert_eq!(got[1].number, 3);
585
+ assert_eq!(got[2].text, "end");
586
+ }
587
+
588
+ #[test]
589
+ fn classifies_a_do_block_with_params() {
590
+ let line = SourceLine {
591
+ number: 1,
592
+ text: "on \"Deposited\" do |event|",
593
+ };
594
+ let shape = classify("f.bluebook", &line).unwrap();
595
+ assert_eq!(
596
+ shape,
597
+ LineShape::Call(Call {
598
+ word: "on".to_string(),
599
+ args: "\"Deposited\"".to_string(),
600
+ opener: Opener::DoBlock {
601
+ params: Some("event".to_string())
602
+ }
603
+ })
604
+ );
605
+ }
606
+
607
+ #[test]
608
+ fn classifies_a_brace_source_body() {
609
+ let line = SourceLine {
610
+ number: 1,
611
+ text: "identified_by { name.value }",
612
+ };
613
+ let shape = classify("f.bluebook", &line).unwrap();
614
+ assert_eq!(
615
+ shape,
616
+ LineShape::Call(Call {
617
+ word: "identified_by".to_string(),
618
+ args: "".to_string(),
619
+ opener: Opener::BraceBlock {
620
+ body: "name.value".to_string()
621
+ }
622
+ })
623
+ );
624
+ }
625
+
626
+ #[test]
627
+ fn captures_a_multi_line_do_block_body_as_raw_text() {
628
+ // governance.bluebook's `RoleAssignment`: multi-path `identified_by do ... end`.
629
+ let source = "identified_by do\n actor_id.value\n role_name.value\n starts_at.value\nend\nattribute :actor_id, IdentityId\n";
630
+ let got = lines(source);
631
+ // got[0] is the `do` line the caller already consumed, so start at index 1.
632
+ let mut pos = 1usize;
633
+ let body = capture_do_block_body("f.bluebook", &got, &mut pos).unwrap();
634
+ assert_eq!(body, "actor_id.value role_name.value starts_at.value");
635
+ assert_eq!(got[pos].text, "attribute :actor_id, IdentityId");
636
+ }
637
+
638
+ #[test]
639
+ fn refuses_local_assignment() {
640
+ let line = SourceLine {
641
+ number: 1,
642
+ text: "x = 1",
643
+ };
644
+ let err = classify("f.bluebook", &line).unwrap_err();
645
+ assert!(err.message.contains("assignment"));
646
+ }
647
+
648
+ #[test]
649
+ fn refuses_a_line_that_is_not_a_word_call() {
650
+ let line = SourceLine {
651
+ number: 1,
652
+ text: "\"just a string\"",
653
+ };
654
+ assert!(classify("f.bluebook", &line).is_err());
655
+ }
656
+
657
+ #[test]
658
+ fn a_backslash_inside_a_still_open_quoted_string_is_not_a_continuation() {
659
+ // An escaped backslash inside a still-open string is not a line continuation.
660
+ assert!(!ends_with_bare_backslash("template: \"a\\"));
661
+ }
662
+ }