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,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "git_landing"
4
+ require_relative "landed_branches"
5
+
6
+ module Hecks
7
+ module Adapters
8
+ # The `Leftovers` port's adapter: after a push, lists the worktrees of branches that landed and
9
+ # are still there, each with what a retirement would do. It removes nothing, so a session still
10
+ # working in one is never moved; a person or agent retires what the list says is safe.
11
+ #
12
+ # A branch counts only if `Landing` recorded it as landed, so a worktree that was merely opened
13
+ # is never named. What a retirement would do is `GitLanding::Tidy`'s own verdict, so the list
14
+ # and `Landing.retire!` cannot disagree.
15
+ class GitLeftovers
16
+ include GitWorktrees
17
+
18
+ # Accepts the arguments every driven adapter is built with; only the registry is kept.
19
+ #
20
+ # @param aggregate [Object, nil] unused
21
+ # @param settings [Hash] unused
22
+ # @param root [String, nil] unused
23
+ # @param registry [Runtime::Registry, nil] where the landed `Landing` rows are read
24
+ def initialize(aggregate: nil, settings: {}, root: nil, registry: nil)
25
+ @git = Git.new
26
+ @registry = registry
27
+ end
28
+
29
+ # @param held [Hash] the `SendTrain` record: `checkout` (any directory in the repository)
30
+ # @return [Hash{Symbol => Hash}] `report:` one line per such worktree, or that none is left
31
+ # @raise [ConsoleCapture::Failure] when the checkout is not a repository
32
+ def leftovers(**held)
33
+ settle(held)
34
+ lines = LandedBranches.of(@registry).filter_map { |branch| line(branch) }
35
+ { report: { value: lines.empty? ? "no landed worktree is left" : lines.join("\n") } }
36
+ end
37
+
38
+ private
39
+
40
+ def line(branch)
41
+ return unless worktree_on(branch)
42
+
43
+ "#{branch}: #{GitLanding::Tidy.new(@git, @checkout, branch: branch, onto: "main").verdict}"
44
+ rescue ConsoleCapture::Failure => e
45
+ "#{branch}: could not be checked (#{e.message})"
46
+ end
47
+ end
48
+ end
49
+ end
@@ -91,8 +91,8 @@ module Hecks
91
91
  lacking = count("#{branch_tip(branch)}..#{REMOTE_MAIN}")
92
92
  return if lacking.zero? || ancestor?(branch_tip(branch), branch_tip("main"))
93
93
 
94
- raise ConsoleCapture::Failure, "main is behind origin/main by #{lacking} commit#{"s" unless lacking == 1}: " \
95
- "pull main first (LinkedTree.pull_main!) and rebase the branch"
94
+ raise ConsoleCapture::Failure, "#{branch} lacks #{lacking} commit#{"s" unless lacking == 1} that origin/main has: " \
95
+ "pull main first (LinkedTree.pull_main!) and rebase the branch onto it"
96
96
  end
97
97
  end
98
98
  end
@@ -3,6 +3,7 @@
3
3
  require_relative "git"
4
4
  require_relative "console_capture"
5
5
  require_relative "push_size"
6
+ require_relative "red_flag_guard"
6
7
  require_relative "git_train/reading"
7
8
  require_relative "git_train/bench"
8
9
  require_relative "git_train/local_branch"
@@ -88,7 +89,9 @@ module Hecks
88
89
 
89
90
  # The commit the branch names as the boarding begins, read once the line is held.
90
91
  def boarded_commit(held)
91
- Carriage.new(@git, @checkout, branch: branch_of(held), remote: remote_of(held)).local_head
92
+ head = Carriage.new(@git, @checkout, branch: branch_of(held), remote: remote_of(held)).local_head
93
+ RedFlagGuard.new(read("rev-parse", "--path-format=absolute", "--git-common-dir")).boarding!(head)
94
+ head
92
95
  end
93
96
 
94
97
  # Brings the branch up to its remote and settles the shape ledger, then carries the settled
@@ -106,7 +109,7 @@ module Hecks
106
109
  def push_size(held)
107
110
  ahead = read("rev-list", "--count", "#{remote_of(held)}/#{branch_of(held)}..#{branch_of(held)}").to_i
108
111
  sentence = PushSize.sentence(ahead, PushSize.thresholds(@registry))
109
- sentence.empty? ? "pushing #{ahead} commits" : "pushing #{ahead} commits; #{sentence}"
112
+ [ahead, sentence.empty? ? "pushing #{ahead} commits" : "pushing #{ahead} commits; #{sentence}"]
110
113
  end
111
114
 
112
115
  def branch_of(held) = plain(held[:branch]) || "main"
@@ -115,11 +118,12 @@ module Hecks
115
118
 
116
119
  # The carriage's answer, its report opened with what the mint did and naming the boarded
117
120
  # commit (and the settled one when settlement moved the branch).
118
- def after_mint(minted, boarded, carried, size = nil)
121
+ def after_mint(minted, boarded, carried, size)
119
122
  answer, settled = carried
123
+ commits, said = size
120
124
  named = settled == boarded ? "boarded #{boarded[0, 9]}" : "boarded #{boarded[0, 9]}, settled to #{settled[0, 9]}"
121
- report = "#{"#{size}\n" if size}#{minted}, then #{named}: #{answer.dig(:report, :value)}"
122
- answer.merge(boarded: { value: boarded }, report: { value: report })
125
+ report = "#{said}\n#{minted}, then #{named}: #{answer.dig(:report, :value)}"
126
+ answer.merge(boarded: { value: boarded }, commits: { value: commits }, report: { value: report })
123
127
  end
124
128
 
125
129
  # A git hook takes no arguments, so the argument reaches the pre-push hook as git
@@ -5,11 +5,13 @@ require_relative "git_main_pull"
5
5
 
6
6
  module Hecks
7
7
  module Adapters
8
- # The `WorktreeAdding` port's adapter for `Worktree`: opens a linked worktree on a new branch cut from
8
+ # The `WorktreeAdding` port's adapter for `Worktree`: opens a linked worktree on a new branch
9
+ # cut from
9
10
  # `main` (pulled first, see {GitMainPull}), at the path named or under the checkout's
10
11
  # `.claude/worktrees`.
11
12
  #
12
- # It never checks out `main` in a worktree, never pushes, and never forces. A branch that already exists
13
+ # It never checks out `main` in a worktree, never pushes, and never forces. A branch that
14
+ # already exists
13
15
  # is declined by git, and the reason travels back on the record.
14
16
  class GitWorktreeAdd
15
17
  include GitWorktrees
@@ -26,10 +28,12 @@ module Hecks
26
28
 
27
29
  # Opens the worktree, then reads the sha it stands on.
28
30
  #
29
- # @param held [Hash] the `Worktree` record: `checkout` (a directory inside the repository; the current
31
+ # @param held [Hash] the `Worktree` record: `checkout` (a directory inside the repository; the
32
+ # current
30
33
  # directory when absent), `branch` (a new branch name), and `path` (optional)
31
34
  # @return [Hash{Symbol => Hash}] `sha:` the commit it stands on, `report:` where it is
32
- # @raise [ConsoleCapture::Failure] on a missing branch, a branch named main, or a directory outside a repository
35
+ # @raise [ConsoleCapture::Failure] on a missing branch, a branch named main, or a directory
36
+ # outside a repository
33
37
  def create(**held)
34
38
  repo = settle(held)
35
39
  branch = branch_named(held)
@@ -27,7 +27,8 @@ module Hecks
27
27
 
28
28
  # Opens a pull request from a branch onto a base, its title and body taken from the commits.
29
29
  #
30
- # @param held [Hash] the `PullRequest` record: `branch` (required), `base` (`main` when absent)
30
+ # @param held [Hash] the `PullRequest` record: `branch` (required), `base` (`main` when
31
+ # absent)
31
32
  # @return [Hash{Symbol => Hash}] `outcome:` (`opened`), `number:`, `url:` and `report:`
32
33
  # @raise [ConsoleCapture::Failure] when no branch is named, or GitHub refuses
33
34
  def create(**held)
@@ -16,7 +16,8 @@ module Hecks
16
16
  # the asks of `ModelCheckRun` and `Operation`, plus the `Follow` query.
17
17
  #
18
18
  # An ask is handed the whole record it was made on (its `held_state`), so each method reads the
19
- # record's fields, runs the existing `Hecks::Tools::*` entry point captured, and answers the text
19
+ # record's fields, runs the existing `Hecks::Tools::*` entry point captured, and answers the
20
+ # text
20
21
  # it printed. An answer records a success; a raise records a refusal, so a check that finds
21
22
  # something, or a run that fails, is refused with the report as its reason.
22
23
  module InProcessOperations
@@ -86,7 +86,7 @@ module Hecks
86
86
  return verdict.edge if verdict.verdict == "would_mint"
87
87
 
88
88
  raise Runtime::NotFound, "no translation edge leads #{latest[:label]} to #{label} " \
89
- "(the decision is #{verdict.verdict}) — run `hecks query era.scaffold_translation` first"
89
+ "(the decision is #{verdict.verdict}) — run `hecks era.scaffold_translation` first"
90
90
  end
91
91
 
92
92
  # The registered translations that leave the latest era.
@@ -11,7 +11,8 @@ module Hecks
11
11
  # it would remove.
12
12
  #
13
13
  # @param domain [Hash, String] the domain directory
14
- # @return [Hash] `text:` the edge file's text, or the sentence that says nothing is to be written
14
+ # @return [Hash] `text:` the edge file's text, or the sentence that says nothing is to be
15
+ # written
15
16
  # @raise [Runtime::NotFound] if the domain holds no eras, cannot be loaded, or its chain
16
17
  # cannot be composed
17
18
  def compose_translation(domain:)
@@ -4,7 +4,8 @@ module Hecks
4
4
  module Adapters
5
5
  class JournalStore
6
6
  module TranslationWrite
7
- # The edge for a shape that changed under an unchanged label: the scaffold's diff, written from
7
+ # The edge for a shape that changed under an unchanged label: the scaffold's diff, written
8
+ # from
8
9
  # the label to itself. Only `TranslationWrite` includes it.
9
10
  module ShapeEdge
10
11
  # A line of an edge file that still marks a field as undecided.
@@ -13,7 +14,8 @@ module Hecks
13
14
  private
14
15
 
15
16
  # A held label that equals the current one with a different projection: the edge is the
16
- # scaffold's diff, from the label to itself. A field the diff cannot decide refuses the write.
17
+ # scaffold's diff, from the label to itself. A field the diff cannot decide refuses the
18
+ # write.
17
19
  #
18
20
  # @raise [Runtime::WiringError] when the diff leaves a field or an aggregate to decide
19
21
  def shape_edge_plan(domain, latest, current)
@@ -16,7 +16,8 @@ module Hecks
16
16
  # `translations/` directory.
17
17
  module TranslationWrite
18
18
  # What a write would do: the file it writes and its text, the stale files it deletes, and
19
- # the shape labels the chain passes through. `message` alone answers when nothing is to be done.
19
+ # the shape labels the chain passes through. `message` alone answers when nothing is to be
20
+ # done.
20
21
  Plan = Struct.new(:message, :path, :text, :removals, :labels, keyword_init: true)
21
22
 
22
23
  include ShapeEdge
@@ -83,7 +84,8 @@ module Hecks
83
84
 
84
85
  # Writes the composed edge and deletes the stale ones; returns what each file held before.
85
86
  #
86
- # @return [Hash{String => String, nil}] each touched path with its earlier text, nil if absent
87
+ # @return [Hash{String => String, nil}] each touched path with its earlier text, nil if
88
+ # absent
87
89
  def write_translation!(plan)
88
90
  originals = [plan.path, *plan.removals].to_h { |path| [path, read_if_present(path)] }
89
91
  File.write(plan.path, plan.text)
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "started_work"
4
+
5
+ module Hecks
6
+ module Adapters
7
+ # The branches a booted registry holds a landed `Landing` row for. The `Leftovers` port reads
8
+ # them to know which worktrees came from a landing, so a branch that was only opened is never
9
+ # listed. It writes nothing.
10
+ module LandedBranches
11
+ module_function
12
+
13
+ # @param registry [Runtime::Registry, nil] the booted registry; nil reads no rows
14
+ # @return [Array<String>] each landed branch once, in the order its rows come
15
+ def of(registry)
16
+ aggregate = landing_aggregate(registry)
17
+ return [] unless aggregate
18
+
19
+ rows = registry.repository("Hecks", aggregate).all
20
+ rows.filter_map { |record| branch(record) }.uniq
21
+ end
22
+
23
+ # @return [Object, nil] the Landing aggregate, nil when no Hecks bluebook is booted
24
+ def landing_aggregate(registry)
25
+ bluebook = registry&.bluebook("Hecks")
26
+ bluebook&.aggregates&.find { |candidate| candidate.hecks_name == "Landing" }
27
+ end
28
+
29
+ # @return [String, nil] the row's branch, nil unless the row landed and names one
30
+ def branch(record)
31
+ return unless StartedWork.value(record, :status) == "landed"
32
+
33
+ name = StartedWork.value(record, :branch).to_s
34
+ name unless name.empty?
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,7 @@
1
+ # Required by name, not relative to this file: a fuzz or model check boots a copy of this directory,
2
+ # and the implementation must load once, from `lib/`, not from the copy.
3
+ require "hecks/hecks/adapters/main_boarding"
4
+
5
+ Hecks.adapter "MainBoarding" do
6
+ port "MainBoarding"
7
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "console_capture"
4
+ require_relative "main_integration"
5
+ require_relative "main_integration/repository"
6
+ require_relative "main_integration/range_guard"
7
+ require_relative "main_integration/train"
8
+ require_relative "main_integration/command_runner"
9
+
10
+ module Hecks
11
+ module Adapters
12
+ # The `MainBoarding` port's adapter for `Integration.Board`: pushes a verified commit of main
13
+ # by boarding the push train in the train's own copy, and answers the commit the remote holds.
14
+ #
15
+ # It refuses, naming why, a run that was not verified, a boarding while another run is being
16
+ # pushed or watched, and a range carrying an agent's attribution trailer, a release approval or
17
+ # a version change. A commit other than the head of local main is answered `superseded`: the
18
+ # run for the head pushes it. It never runs a plain push, a force, a skipped hook or the
19
+ # owner's override: the train's command line is fixed and the override is not handed on.
20
+ class MainBoarding
21
+ class << self
22
+ # @return [#call, nil] takes `(argv, chdir:)` and answers `[ok, output]`; the command
23
+ # runner when nil. A spec replaces it so nothing is pushed.
24
+ attr_accessor :runner
25
+
26
+ # @return [String, nil] the train's copy; `Train::ROOT` when nil
27
+ attr_accessor :train_root
28
+ end
29
+
30
+ # @param registry [Runtime::Registry, nil] the booted registry whose runs are read
31
+ # @param aggregate [Object, nil] unused
32
+ def initialize(registry: nil, aggregate: nil)
33
+ @registry = registry
34
+ end
35
+
36
+ # @param held [Hash] the `Integration` record: `landed` and `checkout`
37
+ # @return [Hash{Symbol => Hash}] `outcome:` (`pushed` or `superseded`), `pushed:`, `report:`
38
+ # @raise [ConsoleCapture::Failure] on any refusal named above, or when the train did not push
39
+ def embark(**held)
40
+ landed = plain(held[:landed]).to_s
41
+ refuse_unboardable!(landed)
42
+ repository = MainIntegration::Repository.new(checkout_of(held))
43
+ head = repository.main
44
+ return superseded(landed, head) unless head == landed
45
+
46
+ MainIntegration::RangeGuard.new(repository).check!(repository.remote_main, landed)
47
+ push(landed, checkout_of(held))
48
+ end
49
+
50
+ private
51
+
52
+ def checkout_of(held) = plain(held[:checkout]) || Dir.pwd
53
+
54
+ def push(landed, checkout)
55
+ train = MainIntegration::Train.new(runner, root: self.class.train_root || MainIntegration::Train::ROOT)
56
+ note = train.refresh
57
+ record = train.board(checkout)
58
+ pushed = [record["pushed"], record["boarded"], landed].compact.first
59
+ { outcome: { value: "pushed" }, pushed: { value: pushed.to_s },
60
+ report: { value: "#{landed[0, 10]} #{record["status"]} as #{pushed.to_s[0, 10]}; #{note}" } }
61
+ end
62
+
63
+ # The run must have come to boarding from verified (the saga moved it as the boarding
64
+ # began), and no other run may be being pushed or watched.
65
+ def refuse_unboardable!(landed)
66
+ runs = MainIntegration::Runs.new(@registry)
67
+ run = runs.find(landed) or refuse!("no integration run is keyed by #{landed[0, 10]}")
68
+ refuse!("#{run.short} is #{run.state}, not verified") unless run.state == "boarding"
69
+ other = runs.in_flight(except: landed).first
70
+ refuse!("#{other.short} is #{other.state}; one run boards at a time") if other
71
+ end
72
+
73
+ def refuse!(why) = raise(ConsoleCapture::Failure, "boarding refused: #{why}")
74
+
75
+ def superseded(landed, head)
76
+ report = "#{landed[0, 10]} is not the head of main (#{head[0, 10]}); the run for the head pushes it"
77
+ { outcome: { value: "superseded" }, report: { value: report } }
78
+ end
79
+
80
+ def runner = self.class.runner || MainIntegration::CommandRunner.new
81
+
82
+ def plain(argument) = argument.is_a?(Hash) ? argument[:value] : argument
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../git_rehearsal/spec_map"
4
+
5
+ module Hecks
6
+ module Adapters
7
+ class MainIntegration
8
+ # The checks a verification runs in a clean copy of a landed commit, each a launcher command
9
+ # run there: the quick gate, the specs the changes touch (the changed spec files and the
10
+ # specs `SpecMap` names for the other changes, in batches of at most `BATCH`), the command
11
+ # names, the CI pipeline and the generated corpus, the last two only compared. Nothing here
12
+ # runs the full spec suite.
13
+ class Checks
14
+ # The most spec files one launcher call runs, the local cap of the `test_scope` setting.
15
+ BATCH = 25
16
+
17
+ # The most spec files one verification runs; the rest run in CI.
18
+ LIMIT = 100
19
+
20
+ # The launcher, as the copy holds it.
21
+ LAUNCHER = "exe/hecks"
22
+
23
+ # The checks that do not depend on what changed, by name.
24
+ FIXED = {
25
+ "quick gate" => %w[gate_run.gate! stage=quick --wait],
26
+ "command names" => %w[conformance_run.check_command_names! --wait],
27
+ "ci pipeline" => %w[regeneration_run.project_ci_pipeline! check.value=true --wait],
28
+ "corpus" => %w[regeneration_run.regenerate_corpus! check.value=true --wait]
29
+ }.freeze
30
+
31
+ # @param directory [String] the clean copy the checks run in
32
+ # @param changed [Array<String>] the paths the landed range changed, relative to the copy
33
+ def initialize(directory, changed)
34
+ @directory = directory
35
+ @changed = changed
36
+ end
37
+
38
+ # @return [Array<Array(String, Array<String>)>] each check's name and its command line
39
+ def all
40
+ named = FIXED.map { |name, args| [name, [LAUNCHER, *args]] }
41
+ named.first(1) + spec_batches + named.drop(1)
42
+ end
43
+
44
+ # @return [Array<String>] the spec files the checks run, at most `LIMIT`
45
+ def specs
46
+ own = @changed.select { |path| path.end_with?("_spec.rb") && File.file?(File.join(@directory, path)) }
47
+ mapped = GitRehearsal::SpecMap.new(@directory).map(@changed, limit: LIMIT).specs
48
+ (own | mapped).first(LIMIT)
49
+ end
50
+
51
+ private
52
+
53
+ def spec_batches
54
+ batches = specs.each_slice(BATCH).to_a
55
+ batches.each_with_index.map do |batch, index|
56
+ ["specs #{index + 1}/#{batches.size}",
57
+ [LAUNCHER, "test_suite_run.run_spec_files!", "files.value=#{batch.join(" ")}", "--wait"]]
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hecks/vendoring/git_environment"
4
+ require_relative "../shell"
5
+
6
+ module Hecks
7
+ module Adapters
8
+ class MainIntegration
9
+ # Runs one command in a directory for an integration step, with the variables that would
10
+ # point it at another checkout, or let it push as the owner, left out. It answers whether
11
+ # the command ended well and what it printed.
12
+ class CommandRunner
13
+ # The variables of the calling process that are never handed on: Bundler's, which would
14
+ # run the command against the calling checkout, and the owner's push override.
15
+ DROPPED = %w[BUNDLE_GEMFILE BUNDLE_BIN_PATH RUBYOPT RUBYLIB HECKS_OWNER_PUSH].freeze
16
+
17
+ # @param argv [Array<String>] the command and its arguments
18
+ # @param chdir [String] the directory it runs in
19
+ # @return [Array(Boolean, String)] whether it ended with status zero, and what it printed:
20
+ # its standard output when it ended well, both streams when it did not
21
+ def call(argv, chdir:)
22
+ result = Shell.new.capture(*argv, env: environment, chdir: chdir)
23
+ [result.ok?, result.ok? ? result.out : "#{result.out}#{result.err}"]
24
+ end
25
+
26
+ private
27
+
28
+ def environment = Vendoring::GitEnvironment.clean.merge(DROPPED.to_h { |name| [name, nil] })
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../console_capture"
4
+
5
+ module Hecks
6
+ module Adapters
7
+ class MainIntegration
8
+ # What a boarding refuses to push, read from the range origin/main does not hold: a commit
9
+ # message with an agent's attribution trailer or a release approval, and a change to the
10
+ # version file, which is the owner's release decision. Each refusal names what it found.
11
+ class RangeGuard
12
+ # The message lines that stop a boarding, each with the name its refusal gives it.
13
+ FORBIDDEN_LINES = {
14
+ "a Co-Authored-By: Claude trailer" => /^Co-Authored-By:.*Claude/i,
15
+ "a Claude-Session trailer" => /^Claude-Session:/i,
16
+ "a Release-Approved-By line" => /^Release-Approved-By:/i
17
+ }.freeze
18
+
19
+ # The file whose change is a release, never boarded by an integration.
20
+ VERSION_FILE = "lib/hecks/version.rb"
21
+
22
+ # @param repository [Repository] the checkout the range is read in
23
+ def initialize(repository)
24
+ @repository = repository
25
+ end
26
+
27
+ # @param base [String] the commit the remote holds
28
+ # @param tip [String] the commit a boarding would push
29
+ # @return [void]
30
+ # @raise [ConsoleCapture::Failure] naming the first forbidden line or the version change
31
+ def check!(base, tip)
32
+ found = forbidden_line(@repository.messages(base, tip))
33
+ refuse!(tip, "carries #{found}") if found
34
+ refuse!(tip, "changes #{VERSION_FILE}, a release") if @repository.changed(base, tip).include?(VERSION_FILE)
35
+ end
36
+
37
+ private
38
+
39
+ def forbidden_line(messages)
40
+ FORBIDDEN_LINES.find { |_name, pattern| messages.any? { |message| message.match?(pattern) } }&.first
41
+ end
42
+
43
+ def refuse!(tip, why)
44
+ raise ConsoleCapture::Failure, "boarding refused: the range up to #{tip[0, 10]} #{why}"
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tmpdir"
4
+ require_relative "../git"
5
+ require_relative "../console_capture"
6
+
7
+ module Hecks
8
+ module Adapters
9
+ class MainIntegration
10
+ # The checkout a run landed in, as an integration step reads it: where local main and
11
+ # origin/main stand, what a range changed and says, and a clean detached copy of a commit.
12
+ class Repository
13
+ # The prefix of the scratch directory a clean copy is made in.
14
+ COPY_PREFIX = "hecks-main-integration"
15
+
16
+ # The files a development checkout keeps for itself and a clean copy must not hold.
17
+ LOCAL_FILES = "lib/**/*.local.{hecksagon,world}"
18
+
19
+ # @param checkout [String] a directory inside the repository
20
+ def initialize(checkout)
21
+ @checkout = checkout
22
+ @git = Git.new
23
+ end
24
+
25
+ # @param ref [String] a branch or commit
26
+ # @return [String, nil] the full commit it names, nil when it names none
27
+ def commit(ref)
28
+ done = @git.capture("rev-parse", "--verify", "--quiet", "#{ref}^{commit}", chdir: @checkout)
29
+ done.ok? ? done.out.strip : nil
30
+ end
31
+
32
+ # @return [String] the commit local main is at
33
+ # @raise [ConsoleCapture::Failure] when the checkout has no main
34
+ def main = commit("main") || raise(ConsoleCapture::Failure, "#{@checkout} has no main branch")
35
+
36
+ # @return [String] the commit origin/main is at, as last fetched; main's when none is
37
+ def remote_main = commit("origin/main") || main
38
+
39
+ # @param base [String] the commit the range starts after
40
+ # @param tip [String] the commit it ends at
41
+ # @return [Array<String>] the paths the range changed
42
+ def changed(base, tip) = read("diff", "--name-only", base, tip).split("\n").reject(&:empty?)
43
+
44
+ # @return [Array<String>] the message of every commit in the range
45
+ def messages(base, tip) = read("log", "--format=%B%x1e", "#{base}..#{tip}").split("\x1e").map(&:strip)
46
+
47
+ # Yields a clean detached copy of a commit, holding only what the commit tracks, and
48
+ # removes it once the block ends.
49
+ #
50
+ # @param tip [String] the commit to copy
51
+ # @yieldparam directory [String] the copy
52
+ # @return [Object] what the block answered
53
+ def clean_copy(tip)
54
+ Dir.mktmpdir(COPY_PREFIX) do |scratch|
55
+ directory = File.join(scratch, "main")
56
+ read("worktree", "add", "--quiet", "--detach", directory, tip)
57
+ begin
58
+ yield refuse_local_files!(directory)
59
+ ensure
60
+ @git.capture("worktree", "remove", "--force", directory, chdir: @checkout)
61
+ end
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def refuse_local_files!(directory)
68
+ found = Dir.glob(LOCAL_FILES, base: directory)
69
+ raise ConsoleCapture::Failure, "the clean copy holds local files: #{found.join(", ")}" unless found.empty?
70
+
71
+ directory
72
+ end
73
+
74
+ def read(*args)
75
+ done = @git.capture(*args, chdir: @checkout)
76
+ raise ConsoleCapture::Failure, "git #{args.first} in #{@checkout}: #{done.err.strip}" unless done.ok?
77
+
78
+ done.out
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hecks
4
+ module Adapters
5
+ class MainIntegration
6
+ # The open and finished runs of the MainIntegration saga, read from the booted registry:
7
+ # each landed commit, the stage its run is in and the checkout it landed in. Writes nothing.
8
+ class Runs
9
+ # The saga whose instances are read.
10
+ SAGA = "MainIntegration"
11
+
12
+ # The stages in which a run is being pushed or watched; only one run may be in them.
13
+ IN_FLIGHT = %w[boarding pushed ci_running].freeze
14
+
15
+ # One run: the landed commit it is keyed by, its stage, and the checkout it landed in.
16
+ Run = Struct.new(:landed, :state, :checkout) do
17
+ # @return [String] the commit's first ten characters, for a report
18
+ def short = landed[0, 10]
19
+ end
20
+
21
+ # @param registry [Runtime::Registry] the booted registry whose saga instances are read
22
+ def initialize(registry)
23
+ @registry = registry
24
+ end
25
+
26
+ # @return [Array<Run>] every run the saga remembers, in the order they started
27
+ def all
28
+ instances.map { |landed, instance| Run.new(landed.to_s, instance[:state].to_s, checkout_of(instance)) }
29
+ end
30
+
31
+ # @param landed [String] the landed commit
32
+ # @return [Run, nil] the run keyed by it
33
+ def find(landed) = all.find { |run| run.landed == landed.to_s }
34
+
35
+ # @param landed [String] the run to leave out
36
+ # @return [Array<Run>] the other runs that are being pushed or watched
37
+ def in_flight(except: nil) = all.select { |run| run.landed != except.to_s && IN_FLIGHT.include?(run.state) }
38
+
39
+ private
40
+
41
+ def instances = @registry.saga_instances[SAGA] || {}
42
+
43
+ def checkout_of(instance)
44
+ held = (instance[:memory] || {})[:checkout]
45
+ held = held[:value] if held.is_a?(Hash)
46
+ held.respond_to?(:value) ? held.value.to_s : held.to_s
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end