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.
- checksums.yaml +4 -4
- data/lib/hecks/adapters/driven/postgres_era/lineage/chain_sql.rb +2 -1
- data/lib/hecks/adapters/driven/postgres_era/lineage/provisioning.rb +4 -2
- data/lib/hecks/adapters/driven/postgres_era/lineage/split_sql.rb +16 -8
- data/lib/hecks/adapters/driven/postgres_era/lineage/transform_sql.rb +7 -3
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/era_resolver.rb +2 -1
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/mint_request.rb +2 -1
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/refusals.rb +2 -1
- data/lib/hecks/adapters/driving/cli_runner/bang_rule.rb +3 -0
- data/lib/hecks/adapters/driving/cli_runner/completion_words.rb +2 -0
- data/lib/hecks/adapters/driving/cli_runner/read_context.rb +6 -3
- data/lib/hecks/adapters/driving/cli_runner/renamed_names.rb +2 -1
- data/lib/hecks/adapters/driving/cli_runner/resolution.rb +2 -2
- data/lib/hecks/adapters/driving/cli_runner/resume_words.rb +1 -0
- data/lib/hecks/adapters/driving/cli_runner/run_reference.rb +6 -3
- data/lib/hecks/adapters/driving/cli_runner/search_words.rb +2 -0
- data/lib/hecks/adapters/driving/cli_runner/tour_words.rb +1 -0
- data/lib/hecks/adapters/driving/cli_runner/verb_names.rb +2 -1
- data/lib/hecks/adapters/driving/cli_runner/verbosity.rb +2 -0
- data/lib/hecks/adapters/driving/cli_runner/word_help.rb +40 -0
- data/lib/hecks/adapters/driving/cli_runner.rb +2 -0
- data/lib/hecks/adapters/driving/mcp/progress_sink.rb +4 -2
- data/lib/hecks/adapters/driving/mcp.rb +2 -1
- data/lib/hecks/adapters/driving/mcp_scope/argument_policy.rb +2 -1
- data/lib/hecks/adapters/driving/ruby/aggregate_module/hooks.rb +28 -14
- data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +4 -2
- data/lib/hecks/assembly.rb +18 -4
- data/lib/hecks/behaviors/expectations/adapter_selection.rb +82 -0
- data/lib/hecks/behaviors/expectations/boot.rb +12 -5
- data/lib/hecks/bluebook/acknowledgements.rb +1 -1
- data/lib/hecks/bluebook/command.rb +3 -2
- data/lib/hecks/bluebook/declaration.rb +2 -1
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +9 -1
- data/lib/hecks/bluebook/dsl/bluebook_builder/metadata.rb +6 -4
- data/lib/hecks/bluebook/dsl/bluebook_builder/modules.rb +6 -3
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/correlation_keys.rb +2 -1
- data/lib/hecks/bluebook/dsl/command_builder/folded_sets.rb +4 -2
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +2 -1
- data/lib/hecks/bluebook/hecksagon.rb +6 -3
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +9 -2
- data/lib/hecks/bluebook/meta_validator.rb +1 -1
- data/lib/hecks/bluebook/model_check/aggregate_checks.rb +2 -1
- data/lib/hecks/bluebook/model_check/classification_checks.rb +51 -0
- data/lib/hecks/bluebook/model_check/context_checks.rb +2 -0
- data/lib/hecks/bluebook/model_check/redundant_relationships.rb +2 -1
- data/lib/hecks/ci_speed/adapters/shard_partitioner.adapter +7 -0
- data/lib/hecks/ci_speed/adapters/shard_partitioner.rb +66 -0
- data/lib/hecks/ci_speed/adapters/timings_source.adapter +7 -0
- data/lib/hecks/ci_speed/adapters/timings_source.rb +68 -0
- data/lib/hecks/ci_speed/ci_speed.bluebook +111 -0
- data/lib/hecks/ci_speed/ci_speed.ports.hecksagon +22 -0
- data/lib/hecks/ci_workflows/ci_workflows.bluebook +50 -6
- data/lib/hecks/coder/coder.bluebook +3 -1
- data/lib/hecks/command_names/index.rb +1 -1
- data/lib/hecks/command_names/resolution.rb +15 -5
- data/lib/hecks/composer/toolbox.rb +2 -1
- data/lib/hecks/corpus.rb +4 -0
- data/lib/hecks/doc/reference/readme.rb +1 -1
- data/lib/hecks/framework_bluebooks.rb +3 -2
- data/lib/hecks/fuzzing/differential.rb +1 -1
- data/lib/hecks/fuzzing/era_boundary.rb +1 -1
- data/lib/hecks/fuzzing/isolated_boot/postgres_scratch.rb +1 -1
- data/lib/hecks/fuzzing/persistence_parity.rb +1 -1
- data/lib/hecks/fuzzing/target_capabilities.rb +1 -1
- data/lib/hecks/hecks/adapters/ci_runs.adapter +7 -0
- data/lib/hecks/hecks/adapters/ci_runs.rb +80 -0
- data/lib/hecks/hecks/adapters/claude_agent_list.rb +43 -0
- data/lib/hecks/hecks/adapters/claude_agents.rb +50 -19
- data/lib/hecks/hecks/adapters/claude_jobs.rb +36 -0
- data/lib/hecks/hecks/adapters/codebase/corpus_queries.rb +2 -1
- data/lib/hecks/hecks/adapters/codebase/minitest_runs.rb +4 -2
- data/lib/hecks/hecks/adapters/codebase/run_options.rb +2 -1
- data/lib/hecks/hecks/adapters/codebase/test_suite.rb +3 -19
- data/lib/hecks/hecks/adapters/dispatching/brief_text.rb +5 -3
- data/lib/hecks/hecks/adapters/dispatching/launcher.rb +2 -1
- data/lib/hecks/hecks/adapters/dispatching/plan.rb +8 -4
- data/lib/hecks/hecks/adapters/dispatching/settings.rb +2 -1
- data/lib/hecks/hecks/adapters/dispatching/settler.rb +4 -2
- data/lib/hecks/hecks/adapters/dispatching.rb +6 -3
- data/lib/hecks/hecks/adapters/doctor_reading.rb +4 -2
- data/lib/hecks/hecks/adapters/git_landing/carried.rb +42 -0
- data/lib/hecks/hecks/adapters/git_landing/finisher.rb +2 -1
- data/lib/hecks/hecks/adapters/git_landing/flagged.rb +32 -0
- data/lib/hecks/hecks/adapters/git_landing/tidy.rb +27 -14
- data/lib/hecks/hecks/adapters/git_landing.rb +30 -13
- data/lib/hecks/hecks/adapters/git_leftovers.adapter +7 -0
- data/lib/hecks/hecks/adapters/git_leftovers.rb +49 -0
- data/lib/hecks/hecks/adapters/git_main_pull.rb +2 -2
- data/lib/hecks/hecks/adapters/git_train.rb +9 -5
- data/lib/hecks/hecks/adapters/git_worktree_add.rb +8 -4
- data/lib/hecks/hecks/adapters/github_pull_requests.rb +2 -1
- data/lib/hecks/hecks/adapters/in_process_operations.rb +2 -1
- data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +1 -1
- data/lib/hecks/hecks/adapters/journal_store/readings/composition.rb +2 -1
- data/lib/hecks/hecks/adapters/journal_store/translation_write/shape_edge.rb +4 -2
- data/lib/hecks/hecks/adapters/journal_store/translation_write.rb +4 -2
- data/lib/hecks/hecks/adapters/landed_branches.rb +38 -0
- data/lib/hecks/hecks/adapters/main_boarding.adapter +7 -0
- data/lib/hecks/hecks/adapters/main_boarding.rb +85 -0
- data/lib/hecks/hecks/adapters/main_integration/checks.rb +63 -0
- data/lib/hecks/hecks/adapters/main_integration/command_runner.rb +32 -0
- data/lib/hecks/hecks/adapters/main_integration/range_guard.rb +49 -0
- data/lib/hecks/hecks/adapters/main_integration/repository.rb +83 -0
- data/lib/hecks/hecks/adapters/main_integration/runs.rb +51 -0
- data/lib/hecks/hecks/adapters/main_integration/train.rb +74 -0
- data/lib/hecks/hecks/adapters/main_integration.adapter +7 -0
- data/lib/hecks/hecks/adapters/main_integration.rb +77 -0
- data/lib/hecks/hecks/adapters/main_verification.adapter +7 -0
- data/lib/hecks/hecks/adapters/main_verification.rb +89 -0
- data/lib/hecks/hecks/adapters/push_recorder.adapter +7 -0
- data/lib/hecks/hecks/adapters/push_recorder.rb +78 -0
- data/lib/hecks/hecks/adapters/red_flag_guard.rb +62 -0
- data/lib/hecks/hecks/adapters/red_flag_marker.rb +101 -0
- data/lib/hecks/hecks/adapters/red_flag_report.rb +59 -0
- data/lib/hecks/hecks/adapters/red_flag_survey.rb +65 -0
- data/lib/hecks/hecks/adapters/red_flagging.adapter +7 -0
- data/lib/hecks/hecks/adapters/red_flagging.rb +98 -0
- data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +6 -1
- data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +6 -3
- data/lib/hecks/hecks/adapters/settings_reading/event_tap_row.rb +33 -0
- data/lib/hecks/hecks/adapters/settings_reading.rb +3 -1
- data/lib/hecks/hecks/codebase.behaviors +0 -6
- data/lib/hecks/hecks/custodian.behaviors +22 -1
- data/lib/hecks/hecks/delegate/delegate.bluebook +128 -15
- data/lib/hecks/hecks/deliver/deliver.bluebook +450 -2
- data/lib/hecks/hecks/environments/memory.world +5 -0
- data/lib/hecks/hecks/hecks.contextmap +8 -0
- data/lib/hecks/hecks/hecks.hecksagon +106 -6
- data/lib/hecks/hecks/hecks.world +9 -1
- data/lib/hecks/hecks/migrate/migrate.bluebook +6 -1
- data/lib/hecks/hecks/redflag/redflag.bluebook +249 -0
- data/lib/hecks/hecks/test/test.bluebook +0 -19
- data/lib/hecks/language/adapter.bluebook +13 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +8 -9
- data/lib/hecks/language/sentence/sentence.bluebook +2 -0
- data/lib/hecks/pipeline/pipeline.bluebook +251 -0
- data/lib/hecks/pipeline/pipeline.hecksagon +17 -0
- data/lib/hecks/pipeline_board/adapters/cache.rb +43 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_fill.rb +56 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_git.rb +82 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_github/runs.rb +66 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_github.adapter +7 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_github.rb +70 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_jobs.adapter +7 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_jobs.rb +87 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_journal/chain.rb +40 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_journal/tap.rb +75 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_journal.adapter +7 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_journal.rb +83 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_stages/ci_tiles.rb +85 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_stages/fanout.rb +73 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_stages/gather.rb +57 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_stages/metrics.rb +36 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_stages/strip.rb +102 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_stages/tile_kit.rb +57 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_stages/work_tiles.rb +59 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_stages.adapter +7 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_stages.rb +84 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_tickets.adapter +7 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_tickets.rb +77 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_web/page.html +292 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_web/sequence.html +190 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_web/server.rb +105 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_web/snapshot.rb +57 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_web/stream.rb +59 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_web/wiring.html +536 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_web.adapter +7 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_web.rb +80 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_wiring/details.rb +85 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_wiring/graph.rb +120 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_wiring.adapter +7 -0
- data/lib/hecks/pipeline_board/adapters/pipeline_wiring.rb +36 -0
- data/lib/hecks/pipeline_board/adapters/probe.rb +48 -0
- data/lib/hecks/pipeline_board/pipeline_board.bluebook +144 -0
- data/lib/hecks/pipeline_board/pipeline_board.hecksagon +12 -0
- data/lib/hecks/pipeline_board/pipeline_board.ports.hecksagon +47 -0
- data/lib/hecks/ports/persistence/binding_policy.rb +2 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/compose.rb +4 -3
- data/lib/hecks/ports/persistence/repository_factory.rb +2 -1
- data/lib/hecks/projections/ci_pipeline/table.rb +2 -1
- data/lib/hecks/projections/deploy/lambda/stack/recipes.rb +2 -2
- data/lib/hecks/projections/undo.rb +2 -1
- data/lib/hecks/projector/cli_projector/command_help.rb +5 -4
- data/lib/hecks/projector/cli_projector/renames.rb +4 -2
- data/lib/hecks/projector/cli_projector/usage.rb +4 -3
- data/lib/hecks/projector/exporter/adapters.rb +3 -2
- data/lib/hecks/projector/narrate_projector/use_cases.rb +74 -0
- data/lib/hecks/projector/narrate_projector.rb +10 -3
- data/lib/hecks/quality_control/adapters/main_maintenance.rb +9 -5
- data/lib/hecks/quality_control/adapters/qa_auto_mint.rb +21 -2
- data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +2 -1
- data/lib/hecks/quality_control/cli/qa_fix_main_bug/publishing.rb +8 -4
- data/lib/hecks/quality_control/cli/qa_fix_main_bug.rb +10 -8
- data/lib/hecks/quality_control/cli/qa_generated_domains/promotion.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_main_gate/gate_run.rb +6 -3
- data/lib/hecks/quality_control/cli/qa_main_gate/logging.rb +8 -4
- data/lib/hecks/quality_control/cli/qa_main_gate/shell.rb +4 -2
- data/lib/hecks/quality_control/cli/qa_main_gate/sweeping.rb +8 -4
- data/lib/hecks/quality_control/cli/qa_main_gate/worktree.rb +3 -2
- data/lib/hecks/quality_control/cli/qa_main_gate.rb +10 -5
- data/lib/hecks/quality_control/cli/qa_sweep/all_mode/children.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_tick.rb +6 -3
- data/lib/hecks/quality_control/quality_control.bluebook +11 -11
- data/lib/hecks/quality_control/quality_control.ports.hecksagon +6 -3
- data/lib/hecks/query_ir/touchpoints.rb +2 -1
- data/lib/hecks/refused.rb +2 -1
- data/lib/hecks/release/approval.rb +4 -2
- data/lib/hecks/runtime/event_log.rb +27 -0
- data/lib/hecks/runtime/event_tap.rb +106 -0
- data/lib/hecks/runtime/loader/boot_steps.rb +1 -1
- data/lib/hecks/runtime/loader.rb +35 -19
- data/lib/hecks/runtime/registry/durability_warnings.rb +12 -5
- data/lib/hecks/runtime/registry/runtime_state.rb +4 -1
- data/lib/hecks/runtime/registry/saga_anchor.rb +54 -0
- data/lib/hecks/runtime/registry/saga_durability_rule.rb +66 -0
- data/lib/hecks/runtime/registry/saga_persistence.rb +58 -12
- data/lib/hecks/runtime/registry/verification.rb +7 -3
- data/lib/hecks/runtime/saga_interpreter/lifecycle.rb +2 -1
- data/lib/hecks/runtime/saga_interpreter.rb +1 -1
- data/lib/hecks/rust_format.rb +2 -1
- data/lib/hecks/shape_ledger/edge_hygiene.rb +10 -5
- data/lib/hecks/shape_ledger.rb +1 -1
- data/lib/hecks/storehouse/audit.rb +2 -1
- data/lib/hecks/storehouse/identity.rb +6 -3
- data/lib/hecks/storehouse.rb +2 -1
- data/lib/hecks/tickets/bluebook/tickets.bluebook +5 -4
- data/lib/hecks/tools/ci_workflows.rb +2 -1
- data/lib/hecks/tools/document.rb +12 -1
- data/lib/hecks/tools/downstream_model_check.rb +4 -2
- data/lib/hecks/tools/fuzz_sweep/shards.rb +9 -30
- data/lib/hecks/tools/model_check/examination.rb +25 -2
- data/lib/hecks/tools/move_disables.rb +4 -2
- data/lib/hecks/tools/regeneration_run/scratch.rb +2 -1
- data/lib/hecks/tools/rspec_shard_files.rb +12 -15
- data/lib/hecks/tools/sequence_script.rb +1 -1
- data/lib/hecks/tools/shard_planner.rb +65 -0
- data/lib/hecks/tools/stage_progress.rb +2 -1
- data/lib/hecks/tools/stress_concurrency_specs.rb +2 -1
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +8 -9
- data/lib/hecks.rb +5 -2
- data/prebuilt/{13c04bdc5970295b310f0a980d397ba722232c8da99cc1a0cdb383249921e871.marshal → 4b81646f769ae5b1e7457f9b11c48d8bc1093e96a1dd6fb61864065121ca6f01.marshal} +0 -0
- data/prebuilt/{e3857b77f47d9ecc1787b2e1f5136364f3cb3444ef4aea5479f3b98167affea2.marshal → 681890632e3f4a99646623dcae44af36b1bad66c515f5abf67a2460372b9c6fd.marshal} +0 -0
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/parser/src/emit.rs +5 -1
- data/rust/parser/src/ir.rs +2 -0
- data/rust/parser/src/keywords.rs +12 -0
- data/rust/parser/src/main.rs +1 -0
- data/rust/parser/src/parse/adapter.rs +1 -0
- data/rust/parser/src/phrases.rs +16 -0
- data/rust/parser/src/sentence/examples.rs +93 -0
- data/rust/parser/src/sentence/mod.rs +1 -0
- metadata +89 -5
- data/lib/hecks/tools/refresh_rspec_runtime_baseline.rb +0 -166
|
@@ -5,16 +5,18 @@ require_relative "shell"
|
|
|
5
5
|
require "time"
|
|
6
6
|
require_relative "console_capture"
|
|
7
7
|
require_relative "claude_transcript"
|
|
8
|
+
require_relative "claude_jobs"
|
|
9
|
+
require_relative "claude_agent_list"
|
|
8
10
|
require_relative "dispatching/settings"
|
|
9
11
|
|
|
10
12
|
module Hecks
|
|
11
13
|
module Adapters
|
|
12
|
-
# The `Agents` port's adapter for `AgentRun`: finds the directory a session will work in
|
|
13
|
-
# a background Claude session
|
|
14
|
-
#
|
|
14
|
+
# The `Agents` port's adapter for `AgentRun`: finds the directory a session will work in and the
|
|
15
|
+
# agents `claude` lists there, starts a background Claude session with the `claude` command,
|
|
16
|
+
# and reads the state its job is in.
|
|
15
17
|
#
|
|
16
|
-
# It decides nothing. Whether a brief is enough, whether an agent
|
|
17
|
-
#
|
|
18
|
+
# It decides nothing. Whether a brief is enough, whether an agent is listed and which states
|
|
19
|
+
# are new or mean a session has ended are the bluebook's; this only looks, starts and reads. The
|
|
18
20
|
# command is an argument list, never a string for a shell, and a `--` keeps a brief from being
|
|
19
21
|
# taken for a flag.
|
|
20
22
|
class ClaudeAgents
|
|
@@ -24,10 +26,14 @@ module Hecks
|
|
|
24
26
|
# A background start prints `backgrounded · <id>` first, then how to attach to the session.
|
|
25
27
|
SESSION_ID = /\Abackgrounded\W+([0-9A-Za-z][0-9A-Za-z_-]{5,})/
|
|
26
28
|
|
|
29
|
+
# How long a just-started session is given to fail before its job is read, in seconds.
|
|
30
|
+
SETTLE_SECONDS = 3
|
|
31
|
+
|
|
27
32
|
# Keeps the command to run, so a spec can point it at a stand-in.
|
|
28
33
|
#
|
|
29
34
|
# @param aggregate [Object, nil] unused
|
|
30
|
-
# @param settings [Hash] `command:` the program that starts sessions (default `claude`)
|
|
35
|
+
# @param settings [Hash] `command:` the program that starts sessions (default `claude`),
|
|
36
|
+
# `jobs:` the jobs directory, `settle:` the seconds a new session is given to fail
|
|
31
37
|
# @param root [String, nil] unused
|
|
32
38
|
# @param registry [Runtime::Registry, nil] whose Tickets world names the session environment
|
|
33
39
|
def initialize(aggregate: nil, settings: {}, root: nil, registry: nil)
|
|
@@ -35,18 +41,24 @@ module Hecks
|
|
|
35
41
|
@session = Dispatching::Settings.new(registry)
|
|
36
42
|
@shell = Shell.new
|
|
37
43
|
@transcript = ClaudeTranscript.new(**settings.slice(:projects))
|
|
44
|
+
@jobs = ClaudeJobs.new(directory: settings[:jobs])
|
|
45
|
+
@agents = ClaudeAgentList.new(@command, shell: @shell)
|
|
46
|
+
@settle = settings.fetch(:settle, SETTLE_SECONDS)
|
|
38
47
|
end
|
|
39
48
|
|
|
40
|
-
# Says the directory a session would work in is there
|
|
49
|
+
# Says the directory a session would work in is there, and whether the `claude` there lists
|
|
50
|
+
# the named agent. With no agent named the session runs `claude`'s default, so nothing is
|
|
51
|
+
# asked.
|
|
41
52
|
#
|
|
42
|
-
# @param held [Hash] the `AgentRun` record: `repo`
|
|
43
|
-
# @return [Hash{Symbol =>
|
|
44
|
-
#
|
|
53
|
+
# @param held [Hash] the `AgentRun` record: `repo`, and `agent` when one is named
|
|
54
|
+
# @return [Hash{Symbol => Object}] `report:` that the directory is there, `listed:` whether
|
|
55
|
+
# the agent is listed, and `note:` that in words, naming the agents that are
|
|
56
|
+
# @raise [ConsoleCapture::Failure] when it is not a directory, or `claude` names no agents
|
|
45
57
|
def locate(**held)
|
|
46
58
|
repo = plain(held[:repo])
|
|
47
59
|
raise ConsoleCapture::Failure, "#{repo} is not a directory" unless File.directory?(repo.to_s)
|
|
48
60
|
|
|
49
|
-
{ report: { value: "#{repo} is a directory" } }
|
|
61
|
+
{ report: { value: "#{repo} is a directory" }, **agent_listing(repo, plain(held[:agent])) }
|
|
50
62
|
end
|
|
51
63
|
|
|
52
64
|
# Starts a background session in the directory, with the brief as its first prompt.
|
|
@@ -64,15 +76,27 @@ module Hecks
|
|
|
64
76
|
{ session: { value: id } }
|
|
65
77
|
end
|
|
66
78
|
|
|
67
|
-
# Reads the state a started session
|
|
79
|
+
# Reads the state a started session's job is in, beside the state the run last recorded.
|
|
68
80
|
#
|
|
69
|
-
# @param held [Hash] the `AgentRun` record: `session`
|
|
70
|
-
# @return [Hash{Symbol => String}] `
|
|
71
|
-
#
|
|
72
|
-
# @raise [ConsoleCapture::Failure] when the
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
{
|
|
81
|
+
# @param held [Hash] the `AgentRun` record: `session`, `repo`, and the `condition` it holds
|
|
82
|
+
# @return [Hash{Symbol => String}] `state:` and `detail:` from the job, `previous:` the held
|
|
83
|
+
# condition (empty when none), and `checkout:` the run's directory with its trailing slash
|
|
84
|
+
# @raise [ConsoleCapture::Failure] when the job's state cannot be read
|
|
85
|
+
def read_job(**held)
|
|
86
|
+
job = @jobs.state(plain(held[:session]))
|
|
87
|
+
{ state: job["state"].to_s, detail: job["detail"].to_s, previous: plain(held[:condition]).to_s,
|
|
88
|
+
checkout: File.join(plain(held[:repo]).to_s, "") }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Waits the settling time, then reads the job as {#read_job} does: a session that dies at
|
|
92
|
+
# once has reported it by then.
|
|
93
|
+
#
|
|
94
|
+
# @param held [Hash] the `AgentRun` record, as for {#read_job}
|
|
95
|
+
# @return [Hash{Symbol => String}] what {#read_job} answers
|
|
96
|
+
# @raise [ConsoleCapture::Failure] when the job's state cannot be read
|
|
97
|
+
def await_job(**held)
|
|
98
|
+
sleep(@settle) if @settle.positive?
|
|
99
|
+
read_job(**held)
|
|
76
100
|
end
|
|
77
101
|
|
|
78
102
|
# Says whether a person replied in a session after its brief.
|
|
@@ -103,6 +127,13 @@ module Hecks
|
|
|
103
127
|
|
|
104
128
|
private
|
|
105
129
|
|
|
130
|
+
def agent_listing(repo, agent)
|
|
131
|
+
return { listed: true, note: "no agent named: the session runs the default agent" } unless agent
|
|
132
|
+
|
|
133
|
+
names = @agents.names(repo)
|
|
134
|
+
{ listed: names.include?(agent), note: "agent #{agent} is not available in #{repo}; available: #{names.join(", ")}" }
|
|
135
|
+
end
|
|
136
|
+
|
|
106
137
|
def listed(id)
|
|
107
138
|
listing = @shell.capture(@command, "agents", "--json", "--all")
|
|
108
139
|
raise ConsoleCapture::Failure, "#{@command} could not list sessions: #{listing.err.strip}" unless listing.ok?
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require_relative "console_capture"
|
|
5
|
+
|
|
6
|
+
module Hecks
|
|
7
|
+
module Adapters
|
|
8
|
+
# The state a background session's job keeps in `~/.claude/jobs/<session>/state.json`: the
|
|
9
|
+
# `state` it is in (working, blocked, done, failed, stopped) and the `detail` it gave, such as
|
|
10
|
+
# the question a blocked session waits on or why a failed one stopped.
|
|
11
|
+
#
|
|
12
|
+
# It reads and judges nothing: which states are new, and which mean a session ended, are the
|
|
13
|
+
# bluebook's.
|
|
14
|
+
class ClaudeJobs
|
|
15
|
+
# Where the jobs are kept unless a setting names another directory.
|
|
16
|
+
DIRECTORY = "~/.claude/jobs"
|
|
17
|
+
|
|
18
|
+
# @param directory [String, nil] the jobs directory (default `~/.claude/jobs`)
|
|
19
|
+
def initialize(directory: nil)
|
|
20
|
+
@directory = File.expand_path(directory || DIRECTORY)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @param session [String] the session's id, which names its job's directory
|
|
24
|
+
# @return [Hash{String => Object}] the job's state file, parsed
|
|
25
|
+
# @raise [ConsoleCapture::Failure] when the session names no job, or its state cannot be read
|
|
26
|
+
def state(session)
|
|
27
|
+
path = File.join(@directory, session.to_s, "state.json")
|
|
28
|
+
raise ConsoleCapture::Failure, "no job state at #{path}" if session.to_s.empty? || !File.file?(path)
|
|
29
|
+
|
|
30
|
+
JSON.parse(File.read(path))
|
|
31
|
+
rescue JSON::ParserError => e
|
|
32
|
+
raise ConsoleCapture::Failure, "the job state at #{path} cannot be read: #{e.message}"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Adapters
|
|
5
5
|
class SourceTree
|
|
6
|
-
# The `SourceTree` queries that read the corpus of domains: which have Rust, the order
|
|
6
|
+
# The `SourceTree` queries that read the corpus of domains: which have Rust, the order
|
|
7
|
+
# regeneration
|
|
7
8
|
# walks them in, and what the IR and its propagation touchpoints say. Each is answered through
|
|
8
9
|
# `corpus`, which the includer provides.
|
|
9
10
|
module CorpusQueries
|
|
@@ -5,7 +5,8 @@ require "open3"
|
|
|
5
5
|
module Hecks
|
|
6
6
|
module Adapters
|
|
7
7
|
module Codebase
|
|
8
|
-
# The minitest files of a spec directory. RSpec does not run them, so each one runs in its own
|
|
8
|
+
# The minitest files of a spec directory. RSpec does not run them, so each one runs in its own
|
|
9
|
+
# Ruby
|
|
9
10
|
# process, and its run summary is reported beside the RSpec output.
|
|
10
11
|
module MinitestRuns
|
|
11
12
|
# The summary line minitest prints, such as "3 runs, 5 assertions".
|
|
@@ -19,7 +20,8 @@ module Hecks
|
|
|
19
20
|
Dir.glob("#{TestSuite::SPEC_DIRECTORY}/**/*_test.rb", base: tree.root).sort
|
|
20
21
|
end
|
|
21
22
|
|
|
22
|
-
# Runs each file in its own Ruby process, from the folder, outside this process's bundle so
|
|
23
|
+
# Runs each file in its own Ruby process, from the folder, outside this process's bundle so
|
|
24
|
+
# the
|
|
23
25
|
# folder's own gems are the ones it loads.
|
|
24
26
|
#
|
|
25
27
|
# @param tree [Tree] the folder the files run in
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Adapters
|
|
5
5
|
module Codebase
|
|
6
|
-
# What a spec run's arguments become: the variables the specs read, and the flags a stress run
|
|
6
|
+
# What a spec run's arguments become: the variables the specs read, and the flags a stress run
|
|
7
|
+
# takes.
|
|
7
8
|
# A person names them in a request, so they never set them in a shell.
|
|
8
9
|
module RunOptions
|
|
9
10
|
module_function
|
|
@@ -20,11 +20,11 @@ module Hecks
|
|
|
20
20
|
# The tooling owns its options, so each ask runs one of the `Hecks::Tools` commands in this
|
|
21
21
|
# process from the checkout's root, except a single example (the `TestRunner` adapter runs it)
|
|
22
22
|
# and the persistence fixtures (the `SqliteFixture` adapter). A listing is a pure read.
|
|
23
|
-
#
|
|
24
|
-
#
|
|
23
|
+
# Regenerating the fixtures and writing the committed spec list change tracked files, so
|
|
24
|
+
# unconfirmed they report what they would do and write nothing.
|
|
25
25
|
module TestSuite
|
|
26
26
|
# Every operation this family carries out.
|
|
27
|
-
OPERATIONS = %w[
|
|
27
|
+
OPERATIONS = %w[run_spec_example run_spec_files run_impacted_specs run_suite run_changed
|
|
28
28
|
stress_concurrency run_cargo_tests clear_caches regenerate_legacy_fixtures seed_semantics_corpus
|
|
29
29
|
write_io_parallel_spec_list].freeze
|
|
30
30
|
|
|
@@ -38,9 +38,6 @@ module Hecks
|
|
|
38
38
|
# The tag filter the postgres job lists its specs by when none is named.
|
|
39
39
|
DEFAULT_TAGS = "--tag io --tag ~fuzzing"
|
|
40
40
|
|
|
41
|
-
# The committed files a baseline refresh writes.
|
|
42
|
-
BASELINES = %w[.github/rspec_runtime_baseline.log .github/postgres_io_runtime_baseline.log].freeze
|
|
43
|
-
|
|
44
41
|
module_function
|
|
45
42
|
|
|
46
43
|
# Loads the command a `Hecks::Tools` file defines, the first time an ask needs it: several
|
|
@@ -79,7 +76,6 @@ module Hecks
|
|
|
79
76
|
# @return [String] what the tool printed
|
|
80
77
|
def tooling(operation, args, tree, shell)
|
|
81
78
|
case operation
|
|
82
|
-
when "refresh_runtime_baseline" then refresh(args, tree)
|
|
83
79
|
when "stress_concurrency" then stress(args, tree)
|
|
84
80
|
when "regenerate_legacy_fixtures" then regenerate_fixtures(args, tree, shell)
|
|
85
81
|
when "seed_semantics_corpus" then seed(args, tree)
|
|
@@ -204,18 +200,6 @@ module Hecks
|
|
|
204
200
|
# @return [Array<String>] the tag filter, one word each
|
|
205
201
|
def tag_words(args) = (args[:tags] || DEFAULT_TAGS).split
|
|
206
202
|
|
|
207
|
-
# @param args [Hash] `workers` and `from_run`
|
|
208
|
-
# @param tree [Tree] the checkout whose baselines are rewritten
|
|
209
|
-
# @return [String] what was written, or (unconfirmed) what would be
|
|
210
|
-
# @raise [ConsoleCapture::Failure] when the refresh ends badly
|
|
211
|
-
def refresh(args, tree)
|
|
212
|
-
source = args[:from_run] ? ["--from-run", args[:from_run]] : [*args[:workers]&.to_s]
|
|
213
|
-
return answer { command("refresh_rspec_runtime_baseline").call(source, root: tree.root) } if args[:confirm] == true
|
|
214
|
-
|
|
215
|
-
"dry run, would #{args[:from_run] ? "read CI run #{args[:from_run]}'s timings" : "time a local run"} " \
|
|
216
|
-
"and rewrite #{BASELINES.join(", ")} (add --confirm)"
|
|
217
|
-
end
|
|
218
|
-
|
|
219
203
|
# @param args [Hash] `exclude`, `tags`, `write` and `confirm`
|
|
220
204
|
# @param tree [Tree] the checkout whose spec list is written
|
|
221
205
|
# @return [String] what was written, or (unconfirmed) how many files would be
|
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Adapters
|
|
5
5
|
class Dispatching
|
|
6
|
-
# The words a session is started with, laid out from a composed brief. Every sentence in it is
|
|
7
|
-
#
|
|
6
|
+
# The words a session is started with, laid out from a composed brief. Every sentence in it is
|
|
7
|
+
# the
|
|
8
|
+
# Coder bluebook's; this only orders the sections, fills in the row's key and lists the areas
|
|
9
|
+
# the
|
|
8
10
|
# caller knows.
|
|
9
11
|
class BriefText
|
|
10
12
|
# The sections of a brief after the outcome, in the order they are read.
|
|
11
|
-
SECTIONS = %w[rules verification workflow landing restraints report].freeze
|
|
13
|
+
SECTIONS = %w[rules review verification workflow landing restraints report].freeze
|
|
12
14
|
|
|
13
15
|
# @param brief [Hash] the composed brief: its attribute values by name
|
|
14
16
|
def initialize(brief)
|
|
@@ -20,7 +20,8 @@ module Hecks
|
|
|
20
20
|
# The JSON object a command prints after any log lines.
|
|
21
21
|
JSON_OBJECT = /\{.*\}/m
|
|
22
22
|
|
|
23
|
-
# @param repo [String] the checkout whose `exe/hecks` runs the commands and where sessions
|
|
23
|
+
# @param repo [String] the checkout whose `exe/hecks` runs the commands and where sessions
|
|
24
|
+
# work
|
|
24
25
|
# @param runner [#call, nil] runs an argument list in `repo` and answers a {Shell::Result}
|
|
25
26
|
def initialize(repo, runner: nil)
|
|
26
27
|
@repo = repo
|
|
@@ -5,11 +5,14 @@ require_relative "../area_overlap"
|
|
|
5
5
|
module Hecks
|
|
6
6
|
module Adapters
|
|
7
7
|
class Dispatching
|
|
8
|
-
# Which planned rows start a session now, and why each of the others waits. It decides from
|
|
8
|
+
# Which planned rows start a session now, and why each of the others waits. It decides from
|
|
9
|
+
# the
|
|
9
10
|
# rows it is given and starts nothing.
|
|
10
11
|
#
|
|
11
|
-
# A planned row waits when it sits in a frozen area, when its area overlaps a started row's or
|
|
12
|
-
#
|
|
12
|
+
# A planned row waits when it sits in a frozen area, when its area overlaps a started row's or
|
|
13
|
+
# an
|
|
14
|
+
# earlier chosen row's (by {AreaOverlap}), or when the machine is loaded or the cap is
|
|
15
|
+
# reached,
|
|
13
16
|
# which also hold back every row after it.
|
|
14
17
|
class Plan
|
|
15
18
|
# A row, as {Rows} reads it.
|
|
@@ -38,7 +41,8 @@ module Hecks
|
|
|
38
41
|
planned.each { |row| consider(row) }
|
|
39
42
|
end
|
|
40
43
|
|
|
41
|
-
# @return [Array<Row>] the started rows and the chosen ones: whose areas are not to be
|
|
44
|
+
# @return [Array<Row>] the started rows and the chosen ones: whose areas are not to be
|
|
45
|
+
# edited
|
|
42
46
|
def holders = @started + @chosen
|
|
43
47
|
|
|
44
48
|
private
|
|
@@ -53,7 +53,8 @@ module Hecks
|
|
|
53
53
|
# @return [Integer] the CPU count
|
|
54
54
|
attr_reader :cpus
|
|
55
55
|
|
|
56
|
-
# @return [Float] the load above which no session is started: the world's, else the CPU
|
|
56
|
+
# @return [Float] the load above which no session is started: the world's, else the CPU
|
|
57
|
+
# count
|
|
57
58
|
def max_load = (declared(MAX_LOAD)[:limit] || @cpus).to_f
|
|
58
59
|
|
|
59
60
|
# @return [Integer] the cap on started rows
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Adapters
|
|
5
5
|
class Dispatching
|
|
6
|
-
# Settles the started rows whose sessions have ended: asks each running session how it is,
|
|
7
|
-
#
|
|
6
|
+
# Settles the started rows whose sessions have ended: asks each running session how it is,
|
|
7
|
+
# then
|
|
8
|
+
# finishes the row of one that ended `done` and yields the row of one that ended any other
|
|
9
|
+
# way,
|
|
8
10
|
# with the condition as the reason. A session still working is left alone.
|
|
9
11
|
class Settler
|
|
10
12
|
# @param rows [Rows] where the started rows and the runs are read
|
|
@@ -9,7 +9,8 @@ require_relative "dispatching/settler"
|
|
|
9
9
|
|
|
10
10
|
module Hecks
|
|
11
11
|
module Adapters
|
|
12
|
-
# The `Dispatching` port's adapter for `Dispatch`: reads the Work queue and the machine, says
|
|
12
|
+
# The `Dispatching` port's adapter for `Dispatch`: reads the Work queue and the machine, says
|
|
13
|
+
# which
|
|
13
14
|
# planned rows would start a session and why each other waits, and with `confirm` starts them.
|
|
14
15
|
#
|
|
15
16
|
# It decides only what the Plan rules decide; whether a row may start is still the bluebook's
|
|
@@ -18,7 +19,8 @@ module Hecks
|
|
|
18
19
|
class Dispatching
|
|
19
20
|
# @param registry [Runtime::Registry, nil] the booted registry whose rows are read
|
|
20
21
|
# @param aggregate [Object, nil] unused
|
|
21
|
-
# @param settings [Hash] `load:`, `cpus:` and `runner:` stand in for the machine and the
|
|
22
|
+
# @param settings [Hash] `load:`, `cpus:` and `runner:` stand in for the machine and the
|
|
23
|
+
# launcher
|
|
22
24
|
# @param root [String, nil] unused
|
|
23
25
|
def initialize(registry: nil, aggregate: nil, settings: {}, root: nil)
|
|
24
26
|
@registry = registry
|
|
@@ -28,7 +30,8 @@ module Hecks
|
|
|
28
30
|
# Says which planned rows start now, and starts them when confirmed.
|
|
29
31
|
#
|
|
30
32
|
# @param held [Hash] the `Dispatch` record: `repo`, `budget_minutes`, `confirm`
|
|
31
|
-
# @return [Hash{Symbol => Hash}] `report:` the load, the rows started or that would be, and
|
|
33
|
+
# @return [Hash{Symbol => Hash}] `report:` the load, the rows started or that would be, and
|
|
34
|
+
# the
|
|
32
35
|
# rows left waiting with the reason
|
|
33
36
|
def choose(**held)
|
|
34
37
|
plan = plan_for
|
|
@@ -50,7 +50,8 @@ module Hecks
|
|
|
50
50
|
[directory, Dir.pwd].uniq.flat_map { |where| Dir[File.join(where, "*.local.{world,hecksagon}")] }.uniq
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
# The first line of a compile error, or nil when the file is valid Ruby. Compiling never runs
|
|
53
|
+
# The first line of a compile error, or nil when the file is valid Ruby. Compiling never runs
|
|
54
|
+
# it.
|
|
54
55
|
def syntax_error(path)
|
|
55
56
|
RubyVM::InstructionSequence.compile(File.read(path), path)
|
|
56
57
|
nil
|
|
@@ -124,7 +125,8 @@ module Hecks
|
|
|
124
125
|
row("store", :fail, "#{adapter} directory #{dir} is missing", "mkdir -p #{dir}")
|
|
125
126
|
end
|
|
126
127
|
|
|
127
|
-
# Postgres reachable, and the role the URL names exists; a read of the catalog, nothing
|
|
128
|
+
# Postgres reachable, and the role the URL names exists; a read of the catalog, nothing
|
|
129
|
+
# written.
|
|
128
130
|
def reach(url)
|
|
129
131
|
require "pg"
|
|
130
132
|
PG.connect(url, connect_timeout: 3) { |conn| role_row(conn, URI.parse(url).user) }
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Adapters
|
|
5
|
+
class GitLanding
|
|
6
|
+
# How a branch stands against the branch it was cut for: whether `onto` carries its commits,
|
|
7
|
+
# by
|
|
8
|
+
# id or by patch, and what a discard says it dropped.
|
|
9
|
+
#
|
|
10
|
+
# Mixed into `Tidy`, which supplies `@onto`, `@discarding`, `@checkout`, `count`, `ancestor?`
|
|
11
|
+
# and
|
|
12
|
+
# `read`.
|
|
13
|
+
module Carried
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def unmerged(tip)
|
|
17
|
+
return "it has no commit to verify" unless tip
|
|
18
|
+
return if @discarding
|
|
19
|
+
|
|
20
|
+
local = "refs/heads/#{@onto}"
|
|
21
|
+
return unless count("#{local}..#{tip}").positive? || !ancestor?(tip, local)
|
|
22
|
+
|
|
23
|
+
lacking = unmatched(local, tip)
|
|
24
|
+
"it has #{lacking} commit(s) not on #{@onto}" if lacking.positive?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# How many commits the branch adds that `onto` carries under no id and no patch: `git cherry`
|
|
28
|
+
# marks one `onto` has a patch-equivalent of with `-`, and one it lacks with `+`.
|
|
29
|
+
def unmatched(local, tip)
|
|
30
|
+
read("cherry", local, tip, dir: @checkout).lines.count { |line| line.start_with?("+") }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# What a discard says it dropped, so the commits can be found again by their tip.
|
|
34
|
+
def discarded_note(tip)
|
|
35
|
+
return unless @discarding
|
|
36
|
+
|
|
37
|
+
"it was at #{tip} with #{unmatched("refs/heads/#{@onto}", tip)} commit(s) not on #{@onto}"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -7,7 +7,8 @@ require_relative "../shell"
|
|
|
7
7
|
module Hecks
|
|
8
8
|
module Adapters
|
|
9
9
|
class GitLanding
|
|
10
|
-
# The steps before a landing that an agent used to run by hand: refuse unsaved work, rebase
|
|
10
|
+
# The steps before a landing that an agent used to run by hand: refuse unsaved work, rebase
|
|
11
|
+
# the
|
|
11
12
|
# branch onto local `main`, and run the quick gate in the branch's worktree.
|
|
12
13
|
#
|
|
13
14
|
# Each step refuses by name at the first failure, with the reason, and leaves the branch as it
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../red_flag_guard"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Adapters
|
|
7
|
+
class GitLanding
|
|
8
|
+
# What landing on main asks beyond any other landing: a base that is not behind origin, and
|
|
9
|
+
# no standing red flag unless the branch is a red row's fix. A fix that lands records its tip
|
|
10
|
+
# on the flag so the push train may board it. The including class answers `common_git_dir`,
|
|
11
|
+
# `@git` and `refuse_stale_base`.
|
|
12
|
+
module Flagged
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
# @return [Object] what the block answers, run only after main's checks pass
|
|
16
|
+
def on_main(branch, onto)
|
|
17
|
+
return yield unless onto == "main"
|
|
18
|
+
|
|
19
|
+
refuse_stale_base(branch)
|
|
20
|
+
git_dir = common_git_dir
|
|
21
|
+
guard = RedFlagGuard.new(git_dir)
|
|
22
|
+
guard.landing!(branch)
|
|
23
|
+
yield.tap { guard.landed(branch, tip(git_dir)) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Main's commit, read against the git directory: the worktree a finish lands from is gone
|
|
27
|
+
# by now.
|
|
28
|
+
def tip(git_dir) = @git.capture("--git-dir=#{git_dir}", "rev-parse", "main").out.strip
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../git_worktrees"
|
|
4
|
+
require_relative "carried"
|
|
4
5
|
|
|
5
6
|
module Hecks
|
|
6
7
|
module Adapters
|
|
@@ -10,11 +11,14 @@ module Hecks
|
|
|
10
11
|
# When in doubt it keeps both. It removes them only when the worktree is a linked one that
|
|
11
12
|
# nobody stands in, holds no file that is not tracked and clean (ignored files and build
|
|
12
13
|
# output count, so a local `.env` is never lost), and its branch tip, verified once, is
|
|
13
|
-
# contained in local `onto
|
|
14
|
-
#
|
|
14
|
+
# contained in local `onto` or every commit it adds has a patch-equivalent there (a rebase or
|
|
15
|
+
# a cherry-pick carries a commit under another id). The worktree goes without `--force`, and
|
|
16
|
+
# the branch goes only while it still names the verified tip, so a commit made in between is
|
|
17
|
+
# never dropped.
|
|
15
18
|
# `main`, `stable`, `edge`, the target and a branch another worktree holds are never deleted.
|
|
16
19
|
class Tidy
|
|
17
20
|
include GitWorktrees
|
|
21
|
+
include Carried
|
|
18
22
|
|
|
19
23
|
# Branches no tidy deletes, whatever they contain.
|
|
20
24
|
PROTECTED = %w[main stable edge].freeze
|
|
@@ -26,14 +30,17 @@ module Hecks
|
|
|
26
30
|
# @param checkout [String] the repository's top level
|
|
27
31
|
# @param branch [String] the landed branch
|
|
28
32
|
# @param onto [String] the branch it was landed on
|
|
29
|
-
# @param
|
|
30
|
-
|
|
33
|
+
# @param mode [Symbol] `:land` tidies after a landing, `:retire` removes the worktree named
|
|
34
|
+
# by `checkout`, `:discard` does that although `onto` lacks the branch's commits; every
|
|
35
|
+
# other guard holds, and the sentence names the tip it was at
|
|
36
|
+
def initialize(git, checkout, branch:, onto:, mode: :land)
|
|
31
37
|
@git = git
|
|
32
38
|
@checkout = checkout
|
|
33
39
|
@from = checkout
|
|
34
40
|
@branch = ref(branch)
|
|
35
41
|
@onto = ref(onto)
|
|
36
|
-
@retiring =
|
|
42
|
+
@retiring = mode != :land
|
|
43
|
+
@discarding = mode == :discard
|
|
37
44
|
end
|
|
38
45
|
|
|
39
46
|
# @return [String] a sentence saying what was removed, or why nothing was
|
|
@@ -49,7 +56,21 @@ module Hecks
|
|
|
49
56
|
tree = worktree_on(@branch)
|
|
50
57
|
tip = tree && branch_tip(@branch)
|
|
51
58
|
why = reason_to_keep(tree, tip)
|
|
52
|
-
|
|
59
|
+
return [true, why] if why
|
|
60
|
+
|
|
61
|
+
note = discarded_note(tip)
|
|
62
|
+
kept, said = drop(tree.path, tip)
|
|
63
|
+
[kept, kept || !@discarding ? said : "#{said}; #{note}"]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# What `retire` would do, said and not done: it removes nothing and takes no lock.
|
|
67
|
+
#
|
|
68
|
+
# @return [String] "would retire it", or "would keep it: " and why
|
|
69
|
+
def verdict
|
|
70
|
+
@checkout = primary.path
|
|
71
|
+
tree = worktree_on(@branch)
|
|
72
|
+
why = reason_to_keep(tree, tree && branch_tip(@branch))
|
|
73
|
+
why ? "would keep it: #{why}" : "would retire it"
|
|
53
74
|
end
|
|
54
75
|
|
|
55
76
|
private
|
|
@@ -89,14 +110,6 @@ module Hecks
|
|
|
89
110
|
lines.size > NAMED ? "#{shown} and #{lines.size - NAMED} more" : shown
|
|
90
111
|
end
|
|
91
112
|
|
|
92
|
-
def unmerged(tip)
|
|
93
|
-
return "it has no commit to verify" unless tip
|
|
94
|
-
|
|
95
|
-
local = "refs/heads/#{@onto}"
|
|
96
|
-
behind = count("#{local}..#{tip}")
|
|
97
|
-
"it has #{behind} commit(s) not on #{@onto}" if behind.positive? || !ancestor?(tip, local)
|
|
98
|
-
end
|
|
99
|
-
|
|
100
113
|
def drop(path, tip)
|
|
101
114
|
return [true, "#{@branch} moved after it was checked"] unless branch_tip(@branch) == tip
|
|
102
115
|
|
|
@@ -7,6 +7,7 @@ require_relative "git_landing/tidy"
|
|
|
7
7
|
require_relative "git_landing/rivals"
|
|
8
8
|
require_relative "git_landing/finisher"
|
|
9
9
|
require_relative "git_landing/lineup"
|
|
10
|
+
require_relative "git_landing/flagged"
|
|
10
11
|
require_relative "../../indifferent_key"
|
|
11
12
|
|
|
12
13
|
module Hecks
|
|
@@ -19,6 +20,7 @@ module Hecks
|
|
|
19
20
|
class GitLanding
|
|
20
21
|
include GitWorktrees
|
|
21
22
|
include GitMainPull
|
|
23
|
+
include Flagged
|
|
22
24
|
|
|
23
25
|
# Accepts the arguments every driven adapter is built with; only the registry and the
|
|
24
26
|
# overrides are kept.
|
|
@@ -96,16 +98,20 @@ module Hecks
|
|
|
96
98
|
# @raise [ConsoleCapture::Failure] when anything makes keeping it the safe answer: the primary
|
|
97
99
|
# worktree, one the process stands in, any file not tracked and clean (ignored included), a
|
|
98
100
|
# branch with commits `onto` lacks or a protected branch, or git refusing the removal
|
|
99
|
-
def remove(**held)
|
|
100
|
-
settle(held)
|
|
101
|
-
branch = plain(held[:branch]) || current_branch
|
|
102
|
-
onto = plain(held[:onto]) || "main"
|
|
103
|
-
gone = @checkout
|
|
104
|
-
kept, said = Tidy.new(@git, @checkout, branch: branch, onto: onto, retiring: true).retire
|
|
105
|
-
raise ConsoleCapture::Failure, "#{gone} is kept: #{said}" if kept
|
|
101
|
+
def remove(**held) = take_down(held, :retire, "retired")
|
|
106
102
|
|
|
107
|
-
|
|
108
|
-
|
|
103
|
+
# Removes a linked worktree and deletes its branch although `onto` lacks the branch's commits.
|
|
104
|
+
#
|
|
105
|
+
# Every other guard of `remove` holds: the primary worktree, one the process stands in, any
|
|
106
|
+
# file not tracked and clean, a protected branch and a branch that moved are all refused. The
|
|
107
|
+
# answer names the tip the branch was at and how many commits `onto` lacked, so they can be
|
|
108
|
+
# found again, and the reason the caller gave.
|
|
109
|
+
#
|
|
110
|
+
# @param held [Hash] the `Landing` record: `checkout`, `branch`, `onto` and `reason`
|
|
111
|
+
# @return [Hash{Symbol => Hash}] `outcome:` (`discarded`), `checkout:`, `branch:`, `report:`
|
|
112
|
+
# @raise [ConsoleCapture::Failure] when anything makes keeping it the safe answer
|
|
113
|
+
def drop_branch(**held)
|
|
114
|
+
take_down(held, :discard, "discarded") { |said| "#{said}; reason: #{plain(held[:reason])}" }
|
|
109
115
|
end
|
|
110
116
|
|
|
111
117
|
# The branches whose commits are not on a branch, or that hold uncommitted work.
|
|
@@ -124,6 +130,17 @@ module Hecks
|
|
|
124
130
|
|
|
125
131
|
private
|
|
126
132
|
|
|
133
|
+
def take_down(held, mode, outcome)
|
|
134
|
+
settle(held)
|
|
135
|
+
branch = plain(held[:branch]) || current_branch
|
|
136
|
+
gone = @checkout
|
|
137
|
+
kept, said = Tidy.new(@git, @checkout, branch: branch, onto: plain(held[:onto]) || "main", mode: mode).retire
|
|
138
|
+
raise ConsoleCapture::Failure, "#{gone} is kept: #{said}" if kept
|
|
139
|
+
|
|
140
|
+
{ outcome: { value: outcome }, checkout: { value: gone }, branch: { value: branch },
|
|
141
|
+
report: { value: block_given? ? yield(said) : said } }
|
|
142
|
+
end
|
|
143
|
+
|
|
127
144
|
# One landing from another worktree, as the train makes it for a waiting branch.
|
|
128
145
|
def land_for(checkout, branch, onto)
|
|
129
146
|
mine = @checkout
|
|
@@ -135,9 +152,8 @@ module Hecks
|
|
|
135
152
|
|
|
136
153
|
def land_alone(branch, onto, note)
|
|
137
154
|
refuse_unsaved(branch)
|
|
138
|
-
refuse_stale_base(branch) if onto == "main"
|
|
139
155
|
refuse_rival(branch, onto)
|
|
140
|
-
said = tidy(Merger.new(@git, @checkout, branch: branch, onto: onto).land, branch, onto)
|
|
156
|
+
said = on_main(branch, onto) { tidy(Merger.new(@git, @checkout, branch: branch, onto: onto).land, branch, onto) }
|
|
141
157
|
note ? said.merge(report: { value: "#{said[:report][:value]}; #{note}" }) : said
|
|
142
158
|
end
|
|
143
159
|
|
|
@@ -159,11 +175,12 @@ module Hecks
|
|
|
159
175
|
landed.merge(report: { value: "#{landed[:report][:value]}; #{said}" })
|
|
160
176
|
end
|
|
161
177
|
|
|
162
|
-
# A finish runs inside the worktree it retires, so the process leaves it first; a worktree
|
|
178
|
+
# A finish runs inside the worktree it retires, so the process leaves it first; a worktree
|
|
179
|
+
# that
|
|
163
180
|
# holds ignored files is kept and said so.
|
|
164
181
|
def retire_from_primary(branch, onto)
|
|
165
182
|
Dir.chdir(primary.path) do
|
|
166
|
-
kept, said = Tidy.new(@git, @checkout, branch: branch, onto: onto,
|
|
183
|
+
kept, said = Tidy.new(@git, @checkout, branch: branch, onto: onto, mode: :retire).retire
|
|
167
184
|
kept ? "kept #{branch}: #{said}" : said
|
|
168
185
|
end
|
|
169
186
|
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/git_leftovers"
|
|
4
|
+
|
|
5
|
+
Hecks.adapter "GitLeftovers" do
|
|
6
|
+
port "Leftovers"
|
|
7
|
+
end
|