hecks 7.0.0 → 7.1.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 (257) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/adapters/driven/postgres_era/lineage/chain_sql.rb +2 -1
  3. data/lib/hecks/adapters/driven/postgres_era/lineage/provisioning.rb +4 -2
  4. data/lib/hecks/adapters/driven/postgres_era/lineage/split_sql.rb +16 -8
  5. data/lib/hecks/adapters/driven/postgres_era/lineage/transform_sql.rb +7 -3
  6. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/era_resolver.rb +2 -1
  7. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/mint_request.rb +2 -1
  8. data/lib/hecks/adapters/driven/postgres_era/lineage_manager/refusals.rb +2 -1
  9. data/lib/hecks/adapters/driving/cli_runner/bang_rule.rb +3 -0
  10. data/lib/hecks/adapters/driving/cli_runner/completion_words.rb +2 -0
  11. data/lib/hecks/adapters/driving/cli_runner/read_context.rb +6 -3
  12. data/lib/hecks/adapters/driving/cli_runner/renamed_names.rb +2 -1
  13. data/lib/hecks/adapters/driving/cli_runner/resolution.rb +2 -2
  14. data/lib/hecks/adapters/driving/cli_runner/resume_words.rb +1 -0
  15. data/lib/hecks/adapters/driving/cli_runner/run_reference.rb +6 -3
  16. data/lib/hecks/adapters/driving/cli_runner/search_words.rb +2 -0
  17. data/lib/hecks/adapters/driving/cli_runner/tour_words.rb +1 -0
  18. data/lib/hecks/adapters/driving/cli_runner/verb_names.rb +2 -1
  19. data/lib/hecks/adapters/driving/cli_runner/verbosity.rb +2 -0
  20. data/lib/hecks/adapters/driving/cli_runner/word_help.rb +40 -0
  21. data/lib/hecks/adapters/driving/cli_runner.rb +2 -0
  22. data/lib/hecks/adapters/driving/mcp/progress_sink.rb +4 -2
  23. data/lib/hecks/adapters/driving/mcp.rb +2 -1
  24. data/lib/hecks/adapters/driving/mcp_scope/argument_policy.rb +2 -1
  25. data/lib/hecks/adapters/driving/ruby/aggregate_module/hooks.rb +28 -14
  26. data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +4 -2
  27. data/lib/hecks/assembly.rb +18 -4
  28. data/lib/hecks/behaviors/expectations/adapter_selection.rb +82 -0
  29. data/lib/hecks/behaviors/expectations/boot.rb +12 -5
  30. data/lib/hecks/bluebook/acknowledgements.rb +1 -1
  31. data/lib/hecks/bluebook/command.rb +3 -2
  32. data/lib/hecks/bluebook/declaration.rb +2 -1
  33. data/lib/hecks/bluebook/dsl/adapter_builder.rb +9 -1
  34. data/lib/hecks/bluebook/dsl/bluebook_builder/metadata.rb +6 -4
  35. data/lib/hecks/bluebook/dsl/bluebook_builder/modules.rb +6 -3
  36. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/correlation_keys.rb +2 -1
  37. data/lib/hecks/bluebook/dsl/command_builder/folded_sets.rb +4 -2
  38. data/lib/hecks/bluebook/dsl/generic_dispatch.rb +2 -1
  39. data/lib/hecks/bluebook/hecksagon.rb +6 -3
  40. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +9 -2
  41. data/lib/hecks/bluebook/meta_validator.rb +1 -1
  42. data/lib/hecks/bluebook/model_check/aggregate_checks.rb +2 -1
  43. data/lib/hecks/bluebook/model_check/classification_checks.rb +51 -0
  44. data/lib/hecks/bluebook/model_check/context_checks.rb +2 -0
  45. data/lib/hecks/bluebook/model_check/redundant_relationships.rb +2 -1
  46. data/lib/hecks/ci_speed/adapters/shard_partitioner.adapter +7 -0
  47. data/lib/hecks/ci_speed/adapters/shard_partitioner.rb +66 -0
  48. data/lib/hecks/ci_speed/adapters/timings_source.adapter +7 -0
  49. data/lib/hecks/ci_speed/adapters/timings_source.rb +68 -0
  50. data/lib/hecks/ci_speed/ci_speed.bluebook +111 -0
  51. data/lib/hecks/ci_speed/ci_speed.ports.hecksagon +22 -0
  52. data/lib/hecks/ci_workflows/ci_workflows.bluebook +50 -6
  53. data/lib/hecks/coder/coder.bluebook +3 -1
  54. data/lib/hecks/command_names/index.rb +1 -1
  55. data/lib/hecks/command_names/resolution.rb +15 -5
  56. data/lib/hecks/composer/toolbox.rb +2 -1
  57. data/lib/hecks/corpus.rb +4 -0
  58. data/lib/hecks/doc/reference/readme.rb +1 -1
  59. data/lib/hecks/framework_bluebooks.rb +3 -2
  60. data/lib/hecks/fuzzing/differential.rb +1 -1
  61. data/lib/hecks/fuzzing/era_boundary.rb +1 -1
  62. data/lib/hecks/fuzzing/isolated_boot/postgres_scratch.rb +1 -1
  63. data/lib/hecks/fuzzing/persistence_parity.rb +1 -1
  64. data/lib/hecks/fuzzing/target_capabilities.rb +1 -1
  65. data/lib/hecks/hecks/adapters/ci_runs.adapter +7 -0
  66. data/lib/hecks/hecks/adapters/ci_runs.rb +80 -0
  67. data/lib/hecks/hecks/adapters/claude_agent_list.rb +43 -0
  68. data/lib/hecks/hecks/adapters/claude_agents.rb +50 -19
  69. data/lib/hecks/hecks/adapters/claude_jobs.rb +36 -0
  70. data/lib/hecks/hecks/adapters/codebase/corpus_queries.rb +2 -1
  71. data/lib/hecks/hecks/adapters/codebase/minitest_runs.rb +4 -2
  72. data/lib/hecks/hecks/adapters/codebase/run_options.rb +2 -1
  73. data/lib/hecks/hecks/adapters/codebase/test_suite.rb +3 -19
  74. data/lib/hecks/hecks/adapters/dispatching/brief_text.rb +5 -3
  75. data/lib/hecks/hecks/adapters/dispatching/launcher.rb +2 -1
  76. data/lib/hecks/hecks/adapters/dispatching/plan.rb +8 -4
  77. data/lib/hecks/hecks/adapters/dispatching/settings.rb +2 -1
  78. data/lib/hecks/hecks/adapters/dispatching/settler.rb +4 -2
  79. data/lib/hecks/hecks/adapters/dispatching.rb +6 -3
  80. data/lib/hecks/hecks/adapters/doctor_reading.rb +4 -2
  81. data/lib/hecks/hecks/adapters/git_landing/carried.rb +42 -0
  82. data/lib/hecks/hecks/adapters/git_landing/finisher.rb +2 -1
  83. data/lib/hecks/hecks/adapters/git_landing/flagged.rb +32 -0
  84. data/lib/hecks/hecks/adapters/git_landing/tidy.rb +27 -14
  85. data/lib/hecks/hecks/adapters/git_landing.rb +30 -13
  86. data/lib/hecks/hecks/adapters/git_leftovers.adapter +7 -0
  87. data/lib/hecks/hecks/adapters/git_leftovers.rb +49 -0
  88. data/lib/hecks/hecks/adapters/git_main_pull.rb +2 -2
  89. data/lib/hecks/hecks/adapters/git_train.rb +9 -5
  90. data/lib/hecks/hecks/adapters/git_worktree_add.rb +8 -4
  91. data/lib/hecks/hecks/adapters/github_pull_requests.rb +2 -1
  92. data/lib/hecks/hecks/adapters/in_process_operations.rb +2 -1
  93. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +1 -1
  94. data/lib/hecks/hecks/adapters/journal_store/readings/composition.rb +2 -1
  95. data/lib/hecks/hecks/adapters/journal_store/translation_write/shape_edge.rb +4 -2
  96. data/lib/hecks/hecks/adapters/journal_store/translation_write.rb +4 -2
  97. data/lib/hecks/hecks/adapters/landed_branches.rb +38 -0
  98. data/lib/hecks/hecks/adapters/main_boarding.adapter +7 -0
  99. data/lib/hecks/hecks/adapters/main_boarding.rb +85 -0
  100. data/lib/hecks/hecks/adapters/main_integration/checks.rb +63 -0
  101. data/lib/hecks/hecks/adapters/main_integration/command_runner.rb +32 -0
  102. data/lib/hecks/hecks/adapters/main_integration/range_guard.rb +49 -0
  103. data/lib/hecks/hecks/adapters/main_integration/repository.rb +83 -0
  104. data/lib/hecks/hecks/adapters/main_integration/runs.rb +51 -0
  105. data/lib/hecks/hecks/adapters/main_integration/train.rb +74 -0
  106. data/lib/hecks/hecks/adapters/main_integration.adapter +7 -0
  107. data/lib/hecks/hecks/adapters/main_integration.rb +77 -0
  108. data/lib/hecks/hecks/adapters/main_verification.adapter +7 -0
  109. data/lib/hecks/hecks/adapters/main_verification.rb +89 -0
  110. data/lib/hecks/hecks/adapters/push_recorder.adapter +7 -0
  111. data/lib/hecks/hecks/adapters/push_recorder.rb +78 -0
  112. data/lib/hecks/hecks/adapters/red_flag_guard.rb +62 -0
  113. data/lib/hecks/hecks/adapters/red_flag_marker.rb +101 -0
  114. data/lib/hecks/hecks/adapters/red_flag_report.rb +59 -0
  115. data/lib/hecks/hecks/adapters/red_flag_survey.rb +65 -0
  116. data/lib/hecks/hecks/adapters/red_flagging.adapter +7 -0
  117. data/lib/hecks/hecks/adapters/red_flagging.rb +98 -0
  118. data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +6 -1
  119. data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +6 -3
  120. data/lib/hecks/hecks/adapters/settings_reading/event_tap_row.rb +33 -0
  121. data/lib/hecks/hecks/adapters/settings_reading.rb +3 -1
  122. data/lib/hecks/hecks/codebase.behaviors +0 -6
  123. data/lib/hecks/hecks/custodian.behaviors +22 -1
  124. data/lib/hecks/hecks/delegate/delegate.bluebook +128 -15
  125. data/lib/hecks/hecks/deliver/deliver.bluebook +450 -2
  126. data/lib/hecks/hecks/environments/memory.world +5 -0
  127. data/lib/hecks/hecks/hecks.contextmap +8 -0
  128. data/lib/hecks/hecks/hecks.hecksagon +106 -6
  129. data/lib/hecks/hecks/hecks.world +9 -1
  130. data/lib/hecks/hecks/migrate/migrate.bluebook +6 -1
  131. data/lib/hecks/hecks/redflag/redflag.bluebook +249 -0
  132. data/lib/hecks/hecks/test/test.bluebook +0 -19
  133. data/lib/hecks/language/adapter.bluebook +13 -0
  134. data/lib/hecks/language/bluebook/vocabulary.bluebook +8 -9
  135. data/lib/hecks/language/sentence/sentence.bluebook +2 -0
  136. data/lib/hecks/pipeline/pipeline.bluebook +251 -0
  137. data/lib/hecks/pipeline/pipeline.hecksagon +17 -0
  138. data/lib/hecks/pipeline_board/adapters/cache.rb +43 -0
  139. data/lib/hecks/pipeline_board/adapters/pipeline_fill.rb +56 -0
  140. data/lib/hecks/pipeline_board/adapters/pipeline_git.rb +82 -0
  141. data/lib/hecks/pipeline_board/adapters/pipeline_github/runs.rb +66 -0
  142. data/lib/hecks/pipeline_board/adapters/pipeline_github.adapter +7 -0
  143. data/lib/hecks/pipeline_board/adapters/pipeline_github.rb +70 -0
  144. data/lib/hecks/pipeline_board/adapters/pipeline_jobs.adapter +7 -0
  145. data/lib/hecks/pipeline_board/adapters/pipeline_jobs.rb +87 -0
  146. data/lib/hecks/pipeline_board/adapters/pipeline_journal/chain.rb +40 -0
  147. data/lib/hecks/pipeline_board/adapters/pipeline_journal/tap.rb +75 -0
  148. data/lib/hecks/pipeline_board/adapters/pipeline_journal.adapter +7 -0
  149. data/lib/hecks/pipeline_board/adapters/pipeline_journal.rb +83 -0
  150. data/lib/hecks/pipeline_board/adapters/pipeline_stages/ci_tiles.rb +85 -0
  151. data/lib/hecks/pipeline_board/adapters/pipeline_stages/fanout.rb +73 -0
  152. data/lib/hecks/pipeline_board/adapters/pipeline_stages/gather.rb +57 -0
  153. data/lib/hecks/pipeline_board/adapters/pipeline_stages/metrics.rb +36 -0
  154. data/lib/hecks/pipeline_board/adapters/pipeline_stages/strip.rb +102 -0
  155. data/lib/hecks/pipeline_board/adapters/pipeline_stages/tile_kit.rb +57 -0
  156. data/lib/hecks/pipeline_board/adapters/pipeline_stages/work_tiles.rb +59 -0
  157. data/lib/hecks/pipeline_board/adapters/pipeline_stages.adapter +7 -0
  158. data/lib/hecks/pipeline_board/adapters/pipeline_stages.rb +84 -0
  159. data/lib/hecks/pipeline_board/adapters/pipeline_tickets.adapter +7 -0
  160. data/lib/hecks/pipeline_board/adapters/pipeline_tickets.rb +77 -0
  161. data/lib/hecks/pipeline_board/adapters/pipeline_web/page.html +292 -0
  162. data/lib/hecks/pipeline_board/adapters/pipeline_web/sequence.html +190 -0
  163. data/lib/hecks/pipeline_board/adapters/pipeline_web/server.rb +105 -0
  164. data/lib/hecks/pipeline_board/adapters/pipeline_web/snapshot.rb +57 -0
  165. data/lib/hecks/pipeline_board/adapters/pipeline_web/stream.rb +59 -0
  166. data/lib/hecks/pipeline_board/adapters/pipeline_web/wiring.html +536 -0
  167. data/lib/hecks/pipeline_board/adapters/pipeline_web.adapter +7 -0
  168. data/lib/hecks/pipeline_board/adapters/pipeline_web.rb +80 -0
  169. data/lib/hecks/pipeline_board/adapters/pipeline_wiring/details.rb +85 -0
  170. data/lib/hecks/pipeline_board/adapters/pipeline_wiring/graph.rb +120 -0
  171. data/lib/hecks/pipeline_board/adapters/pipeline_wiring.adapter +7 -0
  172. data/lib/hecks/pipeline_board/adapters/pipeline_wiring.rb +36 -0
  173. data/lib/hecks/pipeline_board/adapters/probe.rb +48 -0
  174. data/lib/hecks/pipeline_board/pipeline_board.bluebook +144 -0
  175. data/lib/hecks/pipeline_board/pipeline_board.hecksagon +12 -0
  176. data/lib/hecks/pipeline_board/pipeline_board.ports.hecksagon +47 -0
  177. data/lib/hecks/ports/persistence/binding_policy.rb +2 -1
  178. data/lib/hecks/ports/persistence/plugins/era/translation/compose.rb +4 -3
  179. data/lib/hecks/ports/persistence/repository_factory.rb +2 -1
  180. data/lib/hecks/projections/ci_pipeline/table.rb +2 -1
  181. data/lib/hecks/projections/deploy/lambda/stack/recipes.rb +2 -2
  182. data/lib/hecks/projections/undo.rb +2 -1
  183. data/lib/hecks/projector/cli_projector/command_help.rb +5 -4
  184. data/lib/hecks/projector/cli_projector/renames.rb +4 -2
  185. data/lib/hecks/projector/cli_projector/usage.rb +4 -3
  186. data/lib/hecks/projector/exporter/adapters.rb +3 -2
  187. data/lib/hecks/projector/narrate_projector/use_cases.rb +74 -0
  188. data/lib/hecks/projector/narrate_projector.rb +10 -3
  189. data/lib/hecks/quality_control/adapters/main_maintenance.rb +9 -5
  190. data/lib/hecks/quality_control/adapters/qa_auto_mint.rb +21 -2
  191. data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +1 -1
  192. data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +2 -1
  193. data/lib/hecks/quality_control/cli/qa_fix_main_bug/publishing.rb +8 -4
  194. data/lib/hecks/quality_control/cli/qa_fix_main_bug.rb +10 -8
  195. data/lib/hecks/quality_control/cli/qa_generated_domains/promotion.rb +2 -2
  196. data/lib/hecks/quality_control/cli/qa_main_gate/gate_run.rb +6 -3
  197. data/lib/hecks/quality_control/cli/qa_main_gate/logging.rb +8 -4
  198. data/lib/hecks/quality_control/cli/qa_main_gate/shell.rb +4 -2
  199. data/lib/hecks/quality_control/cli/qa_main_gate/sweeping.rb +8 -4
  200. data/lib/hecks/quality_control/cli/qa_main_gate/worktree.rb +3 -2
  201. data/lib/hecks/quality_control/cli/qa_main_gate.rb +10 -5
  202. data/lib/hecks/quality_control/cli/qa_sweep/all_mode/children.rb +1 -1
  203. data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +1 -1
  204. data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
  205. data/lib/hecks/quality_control/cli/qa_tick.rb +6 -3
  206. data/lib/hecks/quality_control/quality_control.bluebook +11 -11
  207. data/lib/hecks/quality_control/quality_control.ports.hecksagon +6 -3
  208. data/lib/hecks/query_ir/touchpoints.rb +2 -1
  209. data/lib/hecks/refused.rb +2 -1
  210. data/lib/hecks/release/approval.rb +4 -2
  211. data/lib/hecks/runtime/event_log.rb +27 -0
  212. data/lib/hecks/runtime/event_tap.rb +106 -0
  213. data/lib/hecks/runtime/loader/boot_steps.rb +1 -1
  214. data/lib/hecks/runtime/loader.rb +35 -19
  215. data/lib/hecks/runtime/registry/durability_warnings.rb +12 -5
  216. data/lib/hecks/runtime/registry/runtime_state.rb +4 -1
  217. data/lib/hecks/runtime/registry/saga_anchor.rb +54 -0
  218. data/lib/hecks/runtime/registry/saga_durability_rule.rb +66 -0
  219. data/lib/hecks/runtime/registry/saga_persistence.rb +58 -12
  220. data/lib/hecks/runtime/registry/verification.rb +7 -3
  221. data/lib/hecks/runtime/saga_interpreter/lifecycle.rb +2 -1
  222. data/lib/hecks/runtime/saga_interpreter.rb +1 -1
  223. data/lib/hecks/rust_format.rb +2 -1
  224. data/lib/hecks/shape_ledger/edge_hygiene.rb +10 -5
  225. data/lib/hecks/shape_ledger.rb +1 -1
  226. data/lib/hecks/storehouse/audit.rb +2 -1
  227. data/lib/hecks/storehouse/identity.rb +6 -3
  228. data/lib/hecks/storehouse.rb +2 -1
  229. data/lib/hecks/tickets/bluebook/tickets.bluebook +5 -4
  230. data/lib/hecks/tools/ci_workflows.rb +2 -1
  231. data/lib/hecks/tools/document.rb +12 -1
  232. data/lib/hecks/tools/downstream_model_check.rb +4 -2
  233. data/lib/hecks/tools/fuzz_sweep/shards.rb +9 -30
  234. data/lib/hecks/tools/model_check/examination.rb +25 -2
  235. data/lib/hecks/tools/move_disables.rb +4 -2
  236. data/lib/hecks/tools/regeneration_run/scratch.rb +2 -1
  237. data/lib/hecks/tools/rspec_shard_files.rb +12 -15
  238. data/lib/hecks/tools/sequence_script.rb +1 -1
  239. data/lib/hecks/tools/shard_planner.rb +65 -0
  240. data/lib/hecks/tools/stage_progress.rb +2 -1
  241. data/lib/hecks/tools/stress_concurrency_specs.rb +2 -1
  242. data/lib/hecks/version.rb +1 -1
  243. data/lib/hecks/vocabulary.rb +8 -9
  244. data/lib/hecks.rb +5 -2
  245. data/prebuilt/{13c04bdc5970295b310f0a980d397ba722232c8da99cc1a0cdb383249921e871.marshal → 4b81646f769ae5b1e7457f9b11c48d8bc1093e96a1dd6fb61864065121ca6f01.marshal} +0 -0
  246. data/prebuilt/{e3857b77f47d9ecc1787b2e1f5136364f3cb3444ef4aea5479f3b98167affea2.marshal → 681890632e3f4a99646623dcae44af36b1bad66c515f5abf67a2460372b9c6fd.marshal} +0 -0
  247. data/rust/host/HECKS_RELEASE +1 -1
  248. data/rust/parser/src/emit.rs +5 -1
  249. data/rust/parser/src/ir.rs +2 -0
  250. data/rust/parser/src/keywords.rs +12 -0
  251. data/rust/parser/src/main.rs +1 -0
  252. data/rust/parser/src/parse/adapter.rs +1 -0
  253. data/rust/parser/src/phrases.rs +16 -0
  254. data/rust/parser/src/sentence/examples.rs +93 -0
  255. data/rust/parser/src/sentence/mod.rs +1 -0
  256. metadata +89 -5
  257. data/lib/hecks/tools/refresh_rspec_runtime_baseline.rb +0 -166
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hecks
4
+ module Adapters
5
+ module PipelineBoard
6
+ # What a node declares, for the sidebar: an aggregate's description, commands with their goals
7
+ # and events, lifecycle and queries; a port's operations; an adapter's port. Read from the IR
8
+ # and never from a store.
9
+ class Details
10
+ # @param registry [Runtime::Registry] a booted registry
11
+ def initialize(registry)
12
+ @registry = registry
13
+ end
14
+
15
+ # @param node [Hash] a graph node
16
+ # @return [Hash] the node with its `details`
17
+ def enrich(node)
18
+ node.merge(details: send(node[:kind], node))
19
+ end
20
+
21
+ private
22
+
23
+ def aggregate(node)
24
+ book = @registry.bluebooks[node[:domain]]
25
+ held = book&.aggregates&.find { |candidate| candidate.hecks_name == node[:label] }
26
+ return {} unless held
27
+
28
+ { description: held.description.to_s, vision: vision(book, node), commands: commands(held),
29
+ lifecycle: lifecycle(held), queries: queries(held) }
30
+ end
31
+
32
+ def vision(book, node)
33
+ return "" unless node[:context] && book.respond_to?(:modules)
34
+
35
+ row = book.modules.find { |held| held.name.to_s == node[:context] }
36
+ row ? row.vision.to_s : ""
37
+ end
38
+
39
+ def commands(held)
40
+ held.commands.map do |command|
41
+ { name: name_of(command), goal: command.goal.to_s, emits: Array(command.emits).map(&:to_s) }
42
+ end
43
+ end
44
+
45
+ def lifecycle(held)
46
+ cycle = held.lifecycle
47
+ return nil unless cycle
48
+
49
+ { field: cycle.field.to_s, default: cycle.default.to_s, states: Array(cycle.states).map(&:to_s) }
50
+ end
51
+
52
+ def queries(held) = held.queries.map { |query| { name: name_of(query), description: query.description.to_s } }
53
+
54
+ def port(node)
55
+ declared = declared_ports.find { |held| held.name.to_s == node[:label] }
56
+ return {} unless declared
57
+
58
+ { operations: declared.operations.map { |operation| operation_of(operation) },
59
+ answers: Array(declared.answered_queries).map(&:to_s) }
60
+ end
61
+
62
+ # Every operation port an aggregate of any booted bluebook declares.
63
+ def declared_ports
64
+ aggregates = @registry.bluebooks.each_value.flat_map(&:aggregates)
65
+ aggregates.flat_map { |held| held.respond_to?(:ports) ? held.ports : [] }
66
+ end
67
+
68
+ def operation_of(operation)
69
+ { name: name_of(operation), direction: operation.direction.to_s, answers: operation.answers.to_s,
70
+ refuses: operation.refuses.to_s, emits: Array(operation.emits).map(&:to_s) }
71
+ end
72
+
73
+ def adapter(node)
74
+ held = @registry.adapters[node[:label]]
75
+ return {} unless held
76
+
77
+ { port: (held.respond_to?(:port_name) ? held.port_name : held.port).to_s,
78
+ fields: (held.respond_to?(:fields) ? Array(held.fields) : []).map(&:to_s) }
79
+ end
80
+
81
+ def name_of(item) = (item.respond_to?(:hecks_name) ? item.hecks_name : item.name).to_s
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hecks
4
+ module Adapters
5
+ module PipelineBoard
6
+ # The wiring of a booted registry as a graph: an aggregate is a node, a policy is an edge from
7
+ # the aggregate whose event it hears to the aggregate whose command it reaches, an `ask` goes
8
+ # to a port node, and a port goes to the adapter bound to it. Only what is wired is drawn, so
9
+ # an aggregate no policy or port touches is left out.
10
+ class Graph
11
+ # @param registry [Runtime::Registry] a booted registry
12
+ def initialize(registry)
13
+ @registry = registry
14
+ @nodes = {}
15
+ @edges = []
16
+ end
17
+
18
+ # @return [Hash] `nodes` and `edges`, each a Hash of plain strings
19
+ def to_h
20
+ policy_edges
21
+ adapter_edges
22
+ details = Details.new(@registry)
23
+ { nodes: @nodes.values.map { |node| details.enrich(node) }, edges: @edges.uniq }
24
+ end
25
+
26
+ private
27
+
28
+ def policy_edges
29
+ @registry.bluebooks.each_value do |bluebook|
30
+ bluebook.policies.each { |policy| edge_for(bluebook.name.to_s, policy) }
31
+ end
32
+ end
33
+
34
+ def edge_for(domain, policy)
35
+ from = aggregate_id(domain, policy.event_qualifier.to_s)
36
+ target = policy.reaches.to_s
37
+ return if from.empty? || target.empty?
38
+
39
+ to = target.start_with?("ask :") ? nil : destination(domain, target)
40
+ link(from, to, policy.name.to_s, to&.start_with?("port:") ? "ask" : "policy")
41
+ end
42
+
43
+ # The node a reach lands on: the port when the verb is a port operation, else the aggregate.
44
+ def destination(domain, verb)
45
+ parts = path_of(verb)
46
+ return aggregate_id(domain, parts.first) if parts.size == 1
47
+ return "port:#{parts.last}" if port?(parts.last)
48
+ return aggregate_id(parts.first, parts[1]) if @registry.bluebooks.key?(parts.first)
49
+
50
+ aggregate_id(domain, parts.first)
51
+ end
52
+
53
+ # The `Domain::Aggregate::Part` segments of a verb, without its command.
54
+ def path_of(verb)
55
+ owner = verb.split(".").first
56
+ owner.to_s.split("::")
57
+ end
58
+
59
+ def port?(name) = port_names.include?(name.to_s)
60
+
61
+ # Every port name: the plain ports the registry holds and the operation ports each
62
+ # aggregate declares.
63
+ def port_names
64
+ @port_names ||= @registry.ports.keys.map(&:to_s) + @registry.bluebooks.each_value.flat_map { |book| owned_ports(book) }
65
+ end
66
+
67
+ def owned_ports(bluebook)
68
+ bluebook.aggregates.flat_map { |aggregate| declared_ports(aggregate) }
69
+ end
70
+
71
+ def declared_ports(aggregate)
72
+ aggregate.respond_to?(:ports) ? aggregate.ports.map { |port| port.name.to_s } : []
73
+ end
74
+
75
+ def aggregate_id(domain, aggregate) = aggregate.to_s.empty? ? "" : "#{domain}::#{aggregate.split("::").last}"
76
+
77
+ def link(from, to, label, kind)
78
+ return unless to
79
+
80
+ origin = node(from, nil)
81
+ node(to, origin)
82
+ @edges << { from: from, to: to, label: label, kind: kind }
83
+ end
84
+
85
+ # An aggregate belongs to its own domain and to the module its bluebook declares it in,
86
+ # when there is one; a port to the domain and module of the aggregate that asks through it.
87
+ def node(id, asking)
88
+ port = id.start_with?("port:")
89
+ label = id.split("::").last.sub("port:", "")
90
+ domain = port ? asking[:domain] : id.split("::").first
91
+ context = port ? asking[:context] : module_of(domain, label)
92
+ @nodes[id] ||= { id: id, kind: port ? "port" : "aggregate", domain: domain, label: label, context: context }
93
+ end
94
+
95
+ def module_of(domain, aggregate)
96
+ book = @registry.bluebooks[domain]
97
+ return nil unless book.respond_to?(:modules)
98
+
99
+ row = book.modules.find { |held| held.aggregate.to_s == aggregate }
100
+ row && row.name.to_s
101
+ end
102
+
103
+ def adapter_edges
104
+ @registry.adapters.each_value do |adapter|
105
+ port = adapter.respond_to?(:port_name) ? adapter.port_name : adapter.port
106
+ next unless @nodes.key?("port:#{port}")
107
+
108
+ bind(port, adapter.name.to_s)
109
+ end
110
+ end
111
+
112
+ def bind(port, adapter)
113
+ id = "adapter:#{adapter}"
114
+ @nodes[id] = { id: id, kind: "adapter", domain: "", label: adapter }
115
+ @edges << { from: "port:#{port}", to: id, label: "", kind: "adapter" }
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,7 @@
1
+ # Binds the `Wiring` port to `PipelineWiring`, which answers `Wiring` from the booted registry.
2
+ # Required by name, not relative, so a copy of this directory shares the one implementation loaded.
3
+ require "hecks/pipeline_board/adapters/pipeline_wiring"
4
+
5
+ Hecks.adapter "PipelineWiring" do
6
+ port "Wiring"
7
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "probe"
5
+ require_relative "pipeline_wiring/details"
6
+ require_relative "pipeline_wiring/graph"
7
+
8
+ module Hecks
9
+ module Adapters
10
+ # The `Wiring` port's adapter: answers `Wiring` with what the loaded bluebooks wire together,
11
+ # for the diagram: which aggregate's events a policy listens to, which aggregate's command it
12
+ # reaches, which port an `ask` goes out through, and which adapter answers each port. It reads
13
+ # the booted registry and nothing else, and changes nothing.
14
+ class PipelineWiring
15
+ # @param registry [Runtime::Registry, nil] the booted registry whose bluebooks are read
16
+ # @param aggregate [Object, nil] unused
17
+ # @param settings [Hash] unused
18
+ # @param root [String, nil] unused
19
+ def initialize(registry: nil, aggregate: nil, settings: {}, root: nil)
20
+ @registry = registry
21
+ end
22
+
23
+ # @return [Hash{Symbol => String}] `text:` the graph as JSON: `nodes` and `edges`
24
+ # @raise [PipelineBoard::Unavailable] when no registry is booted here
25
+ def wiring = { text: JSON.generate(graph) }
26
+
27
+ # @return [Hash] `nodes` (aggregates, ports, adapters) and `edges` (policy, ask, adapter)
28
+ # @raise [PipelineBoard::Unavailable] when no registry is booted here
29
+ def graph
30
+ raise PipelineBoard::Unavailable, "no domain is booted here to read the wiring from" unless @registry
31
+
32
+ @graph ||= PipelineBoard::Graph.new(@registry).to_h
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "open3"
5
+ require "timeout"
6
+
7
+ module Hecks
8
+ module Adapters
9
+ # What the PipelineBoard readers share: the reason a read failed, the outside command they run,
10
+ # and the short cache that keeps a polling page from hammering it.
11
+ module PipelineBoard
12
+ # A read that could not be answered. The message is the reason a stage shows after `unknown:`.
13
+ class Unavailable < StandardError; end
14
+
15
+ # Runs one outside command for a reader and answers its standard output.
16
+ module Probe
17
+ TIMEOUT_SECONDS = 25
18
+
19
+ # @param command [Array<String>] the program and its arguments, never a shell string
20
+ # @param chdir [String, nil] where it runs
21
+ # @return [String] standard output
22
+ # @raise [Unavailable] when the program is missing, times out or exits non-zero
23
+ def self.run(*command, chdir: nil, seconds: TIMEOUT_SECONDS)
24
+ out, err, status = Timeout.timeout(seconds) { Open3.capture3(*command, chdir: chdir || Dir.pwd) }
25
+ raise Unavailable, "#{command.first} #{command[1]}: #{reason(err)}" unless status.success?
26
+
27
+ out
28
+ rescue Errno::ENOENT then raise Unavailable, "#{command.first} is not installed"
29
+ rescue Timeout::Error then raise Unavailable, "#{command.first} did not answer in #{seconds}s"
30
+ end
31
+
32
+ # @return [String] the first line the command wrote to standard error
33
+ def self.reason(err)
34
+ first = err.lines.first
35
+ first.to_s.strip
36
+ end
37
+
38
+ # @return [Object] the JSON the command printed
39
+ # @raise [Unavailable] when the output is not JSON
40
+ def self.json(*command, chdir: nil)
41
+ JSON.parse(run(*command, chdir: chdir))
42
+ rescue JSON::ParserError => e
43
+ raise Unavailable, "#{command.first} printed no JSON: #{e.message[0, 60]}"
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,144 @@
1
+ # PipelineBoard: the pipeline from coding to release, read live and shown on one local page. The
2
+ # read side is six queries, each answered by an adapter behind a port: GitHub for the CI runs, Git
3
+ # for local main and the train, Tickets for the Work items, Jobs for the sessions, Journal for the
4
+ # events the tooling recorded. The bluebook names the questions and never fetches, shells out or
5
+ # opens a file.
6
+ #
7
+ # The one command is `Serve`. It opens the page on 127.0.0.1 and answers `GET /` and
8
+ # `GET /api/state` until it is stopped; no command is reachable from the page, so the board only
9
+ # reads. A read that fails answers `unknown: <reason>` for its stage and leaves the others alone.
10
+ #
11
+ # exe/hecks PipelineBoard.serve! port.value=4747 --wait
12
+
13
+ Hecks.bluebook "PipelineBoard" do
14
+ vision "The pipeline from a Work row to a release is read live, stage by stage, and shown with the real events it emitted."
15
+
16
+ supporting
17
+
18
+ # The board. One run of it is one server opened on a local port, until it is stopped.
19
+ aggregate "PipelineBoard" do
20
+ description "The pipeline's stages, CI runs, work, sessions and events as they stand, and one server that shows them."
21
+
22
+ identified_by :run
23
+
24
+ attribute :run, RunKey
25
+ attribute :port, Port, optional: true
26
+ attribute :last, Reason, optional: true
27
+
28
+ value_object "RunKey" do
29
+ attribute :value, String, matches: :present
30
+ end
31
+
32
+ # A local port the page is served on.
33
+ value_object "Port" do
34
+ attribute :value, Integer, default: 4747
35
+
36
+ invariant("a port is 1024 to 65535") { value >= 1024 && value <= 65535 }
37
+ end
38
+
39
+ # Why a server ended or was refused.
40
+ value_object "Reason" do
41
+ attribute :value, String
42
+ end
43
+
44
+ # A JSON document: the results of a query, or `unknown: <reason>` for a read that failed.
45
+ value_object "Document" do
46
+ attribute :text, String
47
+ end
48
+
49
+ # How many results a query answers, at most.
50
+ value_object "Limit" do
51
+ attribute :value, Integer, default: 20
52
+ end
53
+
54
+ lifecycle :status, default: "requested" do
55
+ mark :failure, "refused"
56
+ mark :terminal, "closed", "refused"
57
+ transition "Close" => "closed", from: "requested"
58
+ transition "Refuse" => "refused", from: "requested"
59
+ end
60
+
61
+ command "Serve" do
62
+ role "Maintainer"
63
+ goal "Show the pipeline on a page at 127.0.0.1 until stopped; the page reads and writes nothing"
64
+
65
+ attribute :run, RunKey, sets: true
66
+ attribute :port, Port, default: 4747, sets: true
67
+
68
+ emits ServeRequested
69
+ end
70
+
71
+ command "Close" do
72
+ role "System"
73
+ goal "Record that the server ended"
74
+
75
+ reference_to PipelineBoard
76
+ attribute :last, Reason, sets: true
77
+
78
+ emits BoardClosed
79
+ end
80
+
81
+ command "Refuse" do
82
+ role "System"
83
+ goal "Record that the server could not open, and why"
84
+
85
+ reference_to PipelineBoard
86
+ attribute :last, Reason, sets: true
87
+
88
+ emits BoardRefused
89
+ end
90
+
91
+ query "Stages" do
92
+ description "One entry per stage, left to right: Work items, Sessions, Local main, Train, CI, Promote, Release, each with its state, count and a one-line detail."
93
+ returns Document
94
+ end
95
+
96
+ query "CiRuns" do
97
+ description "The last CI runs on main, oldest first: sha, started, wall minutes, conclusion and how many jobs failed."
98
+ attribute :limit, Limit, optional: true
99
+ returns Document
100
+ end
101
+
102
+ query "SlowestJobs" do
103
+ description "The jobs of the last completed CI run, slowest first, in minutes."
104
+ attribute :limit, Limit, optional: true
105
+ returns Document
106
+ end
107
+
108
+ query "OpenWork" do
109
+ description "The Work items that are not done: slug, status and area."
110
+ returns Document
111
+ end
112
+
113
+ query "Sessions" do
114
+ description "The busy and the recent dispatched sessions, with the job's own state."
115
+ returns Document
116
+ end
117
+
118
+ query "Wiring" do
119
+ description "What the loaded bluebooks wire together: aggregates, the policies between them, the ports an ask goes out through and the adapters that answer them."
120
+ returns Document
121
+ end
122
+
123
+ query "Events" do
124
+ description "The most recent events the tooling recorded, newest first: time, aggregate, event, and the policy that reacted where the journal holds one."
125
+ attribute :limit, Limit, optional: true
126
+ returns Document
127
+ end
128
+ end
129
+
130
+ policy "ListenWhenServed" do
131
+ on "PipelineBoard.ServeRequested"
132
+ ask :listen, with: { port: :port }
133
+ end
134
+
135
+ policy "CloseWhenEnded" do
136
+ on "PipelineBoard.ServerEnded"
137
+ trigger PipelineBoard::Close, with: { last: :last }
138
+ end
139
+
140
+ policy "RefuseWhenRefused" do
141
+ on "PipelineBoard.ServerRefused"
142
+ trigger PipelineBoard::Refuse, with: { last: :last }
143
+ end
144
+ end
@@ -0,0 +1,12 @@
1
+ # The PipelineBoard bluebook, bound so it boots standing alone (a behaviors or model_check boot of
2
+ # this directory). The ports it shares with every attaching hecksagon are in
3
+ # pipeline_board.ports.hecksagon.
4
+ Hecks.hecksagon "PipelineBoard" do
5
+ attaches "Governance"
6
+ persisted_by "Memory"
7
+ end
8
+
9
+ # The anti-corruption layer for Governance, which the bluebook's roles are checked against.
10
+ Hecks.hecksagon "Governance" do
11
+ persisted_by "Memory"
12
+ end
@@ -0,0 +1,47 @@
1
+ # The ports PipelineBoard declares, for every hecksagon that attaches the bluebook. Each names the
2
+ # question it asks of the outside and never says how it is answered; an adapter binds each. Every
3
+ # port but `Web` only reads.
4
+ Hecks.hecksagon "PipelineBoard" do
5
+ attaches "Governance"
6
+
7
+ # CI as GitHub reports it, read with the `gh` CLI.
8
+ PipelineBoard::PipelineBoard.port "GitHub" do
9
+ answers_query "CiRuns"
10
+ answers_query "SlowestJobs"
11
+ end
12
+
13
+ # The Work items, read through the Tickets domain.
14
+ PipelineBoard::PipelineBoard.port "Tickets" do
15
+ answers_query "OpenWork"
16
+ end
17
+
18
+ # The dispatched sessions: each job's state file and `claude agents --json`.
19
+ PipelineBoard::PipelineBoard.port "Jobs" do
20
+ answers_query "Sessions"
21
+ end
22
+
23
+ # The events the tooling recorded, read from the journal store without writing to it.
24
+ PipelineBoard::PipelineBoard.port "Journal" do
25
+ answers_query "Events"
26
+ end
27
+
28
+ # The wiring of the booted domains, read from the registry, for the diagram.
29
+ PipelineBoard::PipelineBoard.port "Wiring" do
30
+ answers_query "Wiring"
31
+ end
32
+
33
+ # Every stage, composed from the readings above and from this checkout's git state.
34
+ PipelineBoard::PipelineBoard.port "Pipeline" do
35
+ answers_query "Stages"
36
+ end
37
+
38
+ # The page. The adapter opens a server on 127.0.0.1 and answers `GET /` and `GET /api/state`;
39
+ # it is the only effect the board has.
40
+ PipelineBoard::PipelineBoard.port "Web" do
41
+ asks "Listen", to: PipelineBoard do
42
+ attribute :port, Port
43
+ answers "ServerEnded"
44
+ refuses "ServerRefused"
45
+ end
46
+ end
47
+ end
@@ -80,7 +80,8 @@ module Hecks
80
80
  # @return [Bluebook::Bind] the authoritative bind
81
81
  # @raise [Runtime::WiringError] if the roleless count is not one, or a role is present
82
82
  def authoritative_among(bindings, domain, aggregate)
83
- # The same adapter declared again, by a hecksagon that another domain also attaches, is one
83
+ # The same adapter declared again, by a hecksagon that another domain also attaches, is
84
+ # one
84
85
  # binding, not an ambiguous second one.
85
86
  authoritative = bindings.select { |bind| bind.role.nil? || bind.role.empty? }.uniq(&:adapter)
86
87
  raise ambiguous_binding(domain, aggregate, authoritative) unless authoritative.size == 1
@@ -32,7 +32,6 @@ module Hecks
32
32
  # its one step is the edge from that label to itself, when such an edge exists.
33
33
  #
34
34
  # @return [Array<Bluebook::Translation>] the edges in order; empty when none leads `from` to `to`
35
- # and `from` is `to`
36
35
  # @raise [Runtime::WiringError] when a step has no edge, has two, or the walk loops
37
36
  def chain(translations, domain:, from:, to:)
38
37
  return self_edges(translations, domain, from) if from == to
@@ -48,7 +47,8 @@ module Hecks
48
47
  steps
49
48
  end
50
49
 
51
- # @return [Bluebook::Translation] the one edge from the first step's origin to the last step's target
50
+ # @return [Bluebook::Translation] the one edge from the first step's origin to the last step's
51
+ # target
52
52
  # @raise [Runtime::WiringError] when a step's rules cannot be composed into one edge
53
53
  def compose(domain:, from:, to:, chain:)
54
54
  refuse_unrenderable!(chain)
@@ -102,7 +102,8 @@ module Hecks
102
102
  # Follows each aggregate from its held name to its current one, and finds the one step that
103
103
  # carries field rules for each origin.
104
104
  #
105
- # @return [Array(Hash, Array<String>, Hash)] origins keyed by current name, every retired name,
105
+ # @return [Array(Hash, Array<String>, Hash)] origins keyed by current name, every retired
106
+ # name,
106
107
  # and the aggregate carrying field rules for each origin
107
108
  def track_chain(chain)
108
109
  state = { origins: {}, retired: [], ruled: {} }
@@ -55,7 +55,8 @@ module Hecks
55
55
  settings.merge(boot_marks(registry, domain))
56
56
  end
57
57
 
58
- # The per-domain marks the boot gate left on the registry, merged into an adapter's settings.
58
+ # The per-domain marks the boot gate left on the registry, merged into an adapter's
59
+ # settings.
59
60
  #
60
61
  # @return [Hash] the domain, resolved era, superseding era and whether the shape is unminted
61
62
  def boot_marks(registry, domain)
@@ -41,7 +41,8 @@ module Hecks
41
41
  "#{claimed.map(&:name).join(", ")}; a project has one set of workflow rows"
42
42
  end
43
43
 
44
- # @return [Array<Bluebook::Declaration>] the project's bluebooks that declare `Workflow` rows
44
+ # @return [Array<Bluebook::Declaration>] the project's bluebooks that declare `Workflow`
45
+ # rows
45
46
  def self.holders(registry)
46
47
  clients = registry.bluebooks.values.reject { |bluebook| bluebook.name == BLUEBOOK }
47
48
  clients.select { |bluebook| rows_of(bluebook, MARKER).any? }
@@ -26,11 +26,11 @@ module Hecks
26
26
 
27
27
  # `hecks ask` answers the scaffold as text, and refuses (exit 1) where the script
28
28
  # aborted.
29
- SCAFFOLD = Translation.new(verb: "scaffold-translation", script: "exe/hecks query era.scaffold_translation",
29
+ SCAFFOLD = Translation.new(verb: "scaffold-translation", script: "exe/hecks era.scaffold_translation",
30
30
  extra_args: "", cwd: "$(ROOT)", run_prefix: "HECKS_ENVIRONMENT=memory ruby",
31
31
  db_env_blind: true, domain_arg: "domain=$(DOMAIN) ")
32
32
  # A refused audit is a refused query: `hecks ask` exits 1, so the target fails too.
33
- AUDIT = Translation.new(verb: "translation-audit", script: "exe/hecks query era.audit_translation",
33
+ AUDIT = Translation.new(verb: "translation-audit", script: "exe/hecks era.audit_translation",
34
34
  extra_args: "", cwd: "$(ROOT)", run_prefix: "HECKS_ENVIRONMENT=memory ruby",
35
35
  db_env_blind: true, domain_arg: "domain=$(DOMAIN) ")
36
36
  # An app-owned one-time migration script instead of a hecks tool:
@@ -15,7 +15,8 @@ module Hecks
15
15
  module_function
16
16
 
17
17
  # @param bluebook [Bluebook::Declaration] the bluebook holding the process managers
18
- # @param options [Hash{Symbol => Object}] `command:`, the command's verb form, e.g. "Account.Debit"
18
+ # @param options [Hash{Symbol => Object}] `command:`, the command's verb form, e.g.
19
+ # "Account.Debit"
19
20
  # @return [Array<String>] one sentence: the undoing command, or that none is declared
20
21
  def call(bluebook:, options: {})
21
22
  command = options.fetch(:command)
@@ -5,9 +5,9 @@ module Hecks
5
5
  module CommandHelp
6
6
  # Four independent blocks, concatenated in fixed order: meta, invocation,
7
7
  # arguments, refusals.
8
- def command_help(program, name, spec, ask: false)
8
+ def command_help(program, name, spec, ask: false, qualified: false)
9
9
  out = command_help_meta_lines(name, spec)
10
- out.concat(command_help_invocation_lines(program, name, spec, ask))
10
+ out.concat(command_help_invocation_lines(program, name, spec, [ask, qualified]))
11
11
  out.concat(command_help_argument_lines(spec))
12
12
  out.concat(command_help_refusal_lines(spec))
13
13
  out.join("\n")
@@ -28,8 +28,9 @@ module Hecks
28
28
  end
29
29
 
30
30
  # The example call with every argument as `path=…`.
31
- def command_help_invocation_lines(program, name, spec, ask)
32
- invocation = ask ? "#{program} query #{name}" : "#{program} #{name}!"
31
+ def command_help_invocation_lines(program, name, spec, asking)
32
+ ask, qualified = asking
33
+ invocation = ask ? [program, ("query" if qualified), name].compact.join(" ") : "#{program} #{name}!"
33
34
  ["", " #{invocation}#{spec[:arguments].map { |a| " #{a[:path]}=…" }.join}", ""]
34
35
  end
35
36
 
@@ -3,7 +3,8 @@ module Hecks
3
3
  module CliProjector
4
4
  # The launcher's rename table: the old command names a bluebook's `launcher` setting lists as
5
5
  # `renamed`, each mapped to the command it became. An old spelling resolves to the new command
6
- # and the launcher prints the new spelling once; an entry may also translate old argument names.
6
+ # and the launcher prints the new spelling once; an entry may also translate old argument
7
+ # names.
7
8
  #
8
9
  # The table resolves through 8.0; its removal is recorded in docs/compatibility.md.
9
10
  module Renames
@@ -13,7 +14,8 @@ module Hecks
13
14
  # @param commands [Hash{String => Hash}] the projected commands, keyed by snake-form name
14
15
  # @param renamed [Hash{String => String, Hash}, nil] old name to new name, or to a hash
15
16
  # with `to:` and `arguments:` (old argument name to new)
16
- # @return [Hash{Symbol => Hash{String => String}}] `:command` and `:question`, each mapping an
17
+ # @return [Hash{Symbol => Hash{String => String}}] `:command` and `:question`, each mapping
18
+ # an
17
19
  # old spelling to the name it became
18
20
  # @raise [Bluebook::DSL::Malformed] if an entry names no projected command
19
21
  def renamed_aliases(commands, questions, renamed)
@@ -25,7 +25,7 @@ module Hecks
25
25
  return unless only
26
26
 
27
27
  spec = help_spec(only, commands, questions, options[:ask])
28
- command_help(program, verb_form(spec, spec[:short]), spec, ask: options[:ask]) if spec
28
+ command_help(program, verb_form(spec, spec[:short]), spec, ask: options[:ask], qualified: options[:query_word]) if spec
29
29
  end
30
30
 
31
31
  # `ask` picks the namespace when both hold the name; without it a
@@ -58,11 +58,12 @@ module Hecks
58
58
  module_name ? module_header(bluebook, module_name, program) : usage_header(bluebook, program)
59
59
  end
60
60
 
61
- # The bluebook's vision, the release of the gem this launcher runs, and the two ways to call it.
61
+ # The bluebook's vision, the release of the gem this launcher runs, and the two ways to call
62
+ # it.
62
63
  def usage_header(bluebook, program)
63
64
  ["#{bluebook.name} — #{bluebook.vision}", " hecks #{Hecks::VERSION}", "",
64
65
  " #{program} <command>! [name=value …] do something",
65
- " #{program} query <query> [name=value …] read something", ""]
66
+ " #{program} <query> [name=value …] read something, written bare", ""]
66
67
  end
67
68
 
68
69
  # The pointers after the tables: `--help`, `--all`, `--maintainer` and an example call.