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
|
@@ -981,6 +981,99 @@ Hecks.bluebook "CiSpeed" do
|
|
|
981
981
|
end
|
|
982
982
|
end
|
|
983
983
|
|
|
984
|
+
# How one family of sharded CI jobs (the rspec shards, the postgres io shards) splits its files
|
|
985
|
+
# among its legs. A plan job asks for the split once; every leg reads that plan, so all legs
|
|
986
|
+
# agree. The split is balanced by the seconds the newest successful main run recorded for each
|
|
987
|
+
# file, and by file size when no such run can be read. The chain, never deeper than three:
|
|
988
|
+
#
|
|
989
|
+
# ShardPlanRequested -> TimingsFetched -> ShardPlanned (source timings)
|
|
990
|
+
# ShardPlanRequested -> TimingsUnavailable -> ShardPlanned (source size)
|
|
991
|
+
#
|
|
992
|
+
# The `TimingsSource` port finds the previous run's timings and the `Planner` port splits; the
|
|
993
|
+
# bluebook says neither how. Run the plan job's command line as
|
|
994
|
+
#
|
|
995
|
+
# exe/hecks run lib/hecks/ci_speed '{"steps":[{"verb":"CiSpeed::ShardPlan.Request","args":{
|
|
996
|
+
# "name":{"value":"rspec"},"shards":{"value":8},"items_from":{"value":"glob:spec/**/*_spec.rb"},
|
|
997
|
+
# "timings_from":{"value":"main"},"out_dir":{"value":"tmp/shard-plan"}}}]}'
|
|
998
|
+
aggregate "ShardPlan" do
|
|
999
|
+
description "One family's split of its files among its shard legs, and where the timings behind it came from."
|
|
1000
|
+
|
|
1001
|
+
identified_by :name
|
|
1002
|
+
|
|
1003
|
+
attribute :name, PlanName
|
|
1004
|
+
attribute :shards, ShardCount, optional: true
|
|
1005
|
+
# `glob:<pattern>` splits the files a pattern finds, `list:<path>` the lines of a file.
|
|
1006
|
+
attribute :items_from, ItemsFrom, optional: true
|
|
1007
|
+
# A directory of timing logs, or `main` for the newest successful CI run on main.
|
|
1008
|
+
attribute :timings_from, TimingsFrom, optional: true
|
|
1009
|
+
# Where the plan is written: `group-<n>.txt` per leg, `runtimes.log` and `plan.json`.
|
|
1010
|
+
attribute :out_dir, OutDir, optional: true
|
|
1011
|
+
# The log one leg timed its own files into, kept as an artifact for the next plan.
|
|
1012
|
+
attribute :recorded_log, OutDir, optional: true
|
|
1013
|
+
|
|
1014
|
+
# The family: `rspec`, `postgres_io`.
|
|
1015
|
+
value_object "PlanName" do
|
|
1016
|
+
attribute :value, String, pattern: '^[a-z][a-z0-9_]*$'
|
|
1017
|
+
end
|
|
1018
|
+
|
|
1019
|
+
value_object "ShardCount" do
|
|
1020
|
+
attribute :value, Integer, default: 1
|
|
1021
|
+
invariant("a family has at least one shard") { value >= 1 }
|
|
1022
|
+
end
|
|
1023
|
+
|
|
1024
|
+
value_object "ItemsFrom" do
|
|
1025
|
+
attribute :value, String, pattern: '^(glob|list):[^ \t\n]+$'
|
|
1026
|
+
end
|
|
1027
|
+
|
|
1028
|
+
value_object "TimingsFrom" do
|
|
1029
|
+
attribute :value, String, pattern: '^[^ \t\n]+$'
|
|
1030
|
+
end
|
|
1031
|
+
|
|
1032
|
+
value_object "OutDir" do
|
|
1033
|
+
attribute :value, String, pattern: '^[^ \t\n]+$'
|
|
1034
|
+
end
|
|
1035
|
+
|
|
1036
|
+
# Where a plan's prices came from: the recorded seconds, or file size.
|
|
1037
|
+
value_object "PlanSource" do
|
|
1038
|
+
attribute :value, String, pattern: '^(timings|size)$'
|
|
1039
|
+
end
|
|
1040
|
+
|
|
1041
|
+
# Lines of `path:seconds`, as the shards' runtime loggers write them.
|
|
1042
|
+
value_object "Timings" do
|
|
1043
|
+
attribute :value, String, default: ""
|
|
1044
|
+
end
|
|
1045
|
+
|
|
1046
|
+
command "Request" do
|
|
1047
|
+
goal "Ask for a family's files to be split among its shards, from the last run's timings when they can be read"
|
|
1048
|
+
|
|
1049
|
+
attribute :name, PlanName
|
|
1050
|
+
attribute :shards, ShardCount
|
|
1051
|
+
attribute :items_from, ItemsFrom
|
|
1052
|
+
attribute :timings_from, TimingsFrom
|
|
1053
|
+
attribute :out_dir, OutDir
|
|
1054
|
+
|
|
1055
|
+
sets :name
|
|
1056
|
+
sets :shards
|
|
1057
|
+
sets :items_from
|
|
1058
|
+
sets :timings_from
|
|
1059
|
+
sets :out_dir
|
|
1060
|
+
|
|
1061
|
+
emits "ShardPlanRequested"
|
|
1062
|
+
end
|
|
1063
|
+
|
|
1064
|
+
command "RecordTimings" do
|
|
1065
|
+
goal "Say which log one shard leg timed its own files into, so the next plan can read it"
|
|
1066
|
+
|
|
1067
|
+
attribute :name, PlanName
|
|
1068
|
+
attribute :recorded_log, OutDir
|
|
1069
|
+
|
|
1070
|
+
sets :name
|
|
1071
|
+
sets :recorded_log
|
|
1072
|
+
|
|
1073
|
+
emits "TimingsRecorded"
|
|
1074
|
+
end
|
|
1075
|
+
end
|
|
1076
|
+
|
|
984
1077
|
# An experiment runs as soon as it is asked for: the port runs it and answers with the numbers.
|
|
985
1078
|
policy "MeasureWhenRequested" do
|
|
986
1079
|
on "Experiment.ExperimentRequested"
|
|
@@ -1070,4 +1163,22 @@ Hecks.bluebook "CiSpeed" do
|
|
|
1070
1163
|
on "Budget.MissRunWitnessed"
|
|
1071
1164
|
trigger Budget::Complete, with: { budget: :name }
|
|
1072
1165
|
end
|
|
1166
|
+
|
|
1167
|
+
# A plan is asked for, and the first thing to do is look for the previous run's timings.
|
|
1168
|
+
policy "FetchTimingsWhenPlanRequested" do
|
|
1169
|
+
on "ShardPlan.ShardPlanRequested"
|
|
1170
|
+
ask :fetch, with: { name: :name }
|
|
1171
|
+
end
|
|
1172
|
+
|
|
1173
|
+
policy "PlanFromFetchedTimings" do
|
|
1174
|
+
on "ShardPlan.TimingsFetched"
|
|
1175
|
+
ask :partition, with: { name: :name, source: { value: "timings" }, timings: :timings }
|
|
1176
|
+
end
|
|
1177
|
+
|
|
1178
|
+
# The fallback: timings that cannot be read are no reason to fail the run, so the files are
|
|
1179
|
+
# split by size instead.
|
|
1180
|
+
policy "PlanBySizeWhenTimingsUnavailable" do
|
|
1181
|
+
on "ShardPlan.TimingsUnavailable"
|
|
1182
|
+
ask :partition, with: { name: :name, source: { value: "size" }, timings: { value: "" } }
|
|
1183
|
+
end
|
|
1073
1184
|
end
|
|
@@ -18,4 +18,26 @@ Hecks.hecksagon "CiSpeed" do
|
|
|
18
18
|
refuses "ExperimentRefused"
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
|
+
|
|
22
|
+
# Finds the timings the newest successful main run recorded for a family's files. The adapter
|
|
23
|
+
# reads a directory of logs or downloads them through `gh`, and refuses when it finds none; the
|
|
24
|
+
# bluebook never says which.
|
|
25
|
+
CiSpeed::ShardPlan.port "TimingsSource" do
|
|
26
|
+
asks "Fetch", to: ShardPlan do
|
|
27
|
+
attribute :name, PlanName
|
|
28
|
+
answers "TimingsFetched"
|
|
29
|
+
refuses "TimingsUnavailable"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Splits a family's files among its shards by price (timings, or size) and writes the plan.
|
|
34
|
+
CiSpeed::ShardPlan.port "Planner" do
|
|
35
|
+
asks "Partition", to: ShardPlan do
|
|
36
|
+
attribute :name, PlanName
|
|
37
|
+
attribute :source, PlanSource
|
|
38
|
+
attribute :timings, Timings
|
|
39
|
+
answers "ShardPlanned"
|
|
40
|
+
refuses "PlanRefused"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
21
43
|
end
|
|
@@ -64,6 +64,9 @@ Hecks.bluebook "CiWorkflows" do
|
|
|
64
64
|
|
|
65
65
|
member workflow: "ci-rspec.yml", name: "ci_attestation_rspec", runner: "ubuntu-latest", timeout_minutes: 10,
|
|
66
66
|
note: "Separate job because a job-level `if:` cannot read a step output from its own job.\nLets `rspec_shard` skip wholesale when a local run attested this tree.\nSafe to skip on push: `rspec_shard` never reads this result on that trigger."
|
|
67
|
+
member workflow: "ci-rspec.yml", name: "shard_plan", runner: "ubuntu-latest", timeout_minutes: 10,
|
|
68
|
+
condition: "always() && (needs.ci_attestation_rspec.result != 'success' || needs.ci_attestation_rspec.outputs.attested != 'true')",
|
|
69
|
+
note: "Splits the spec files among the shards once, so every leg reads the same plan. It runs the\nCiSpeed bluebook's ShardPlan: the seconds the newest successful run on main recorded for each file\nbalance the legs, and file size does when that run cannot be read. The events of the plan, and the\npolicy that reacted to each, are in the job summary and the shard-plan-trace-rspec artifact."
|
|
67
70
|
member workflow: "ci-rspec.yml", name: "rspec_shard", runner: "ubuntu-latest", timeout_minutes: 20,
|
|
68
71
|
condition: "always() && (needs.ci_attestation_rspec.result != 'success' || needs.ci_attestation_rspec.outputs.attested != 'true')",
|
|
69
72
|
shard_total: 8, shard_axis: "group",
|
|
@@ -75,7 +78,9 @@ Hecks.bluebook "CiWorkflows" do
|
|
|
75
78
|
attribute :job, String
|
|
76
79
|
attribute :needs, String
|
|
77
80
|
|
|
81
|
+
member workflow: "ci-rspec.yml", job: "shard_plan", needs: "ci_attestation_rspec"
|
|
78
82
|
member workflow: "ci-rspec.yml", job: "rspec_shard", needs: "ci_attestation_rspec"
|
|
83
|
+
member workflow: "ci-rspec.yml", job: "rspec_shard", needs: "shard_plan"
|
|
79
84
|
end
|
|
80
85
|
|
|
81
86
|
value_object "Output" do
|
|
@@ -93,6 +98,9 @@ Hecks.bluebook "CiWorkflows" do
|
|
|
93
98
|
attribute :job, String
|
|
94
99
|
attribute :key, String
|
|
95
100
|
attribute :value, String
|
|
101
|
+
|
|
102
|
+
member workflow: "ci-rspec.yml", job: "shard_plan", key: "GH_TOKEN", value: "${{ github.token }}"
|
|
103
|
+
member workflow: "ci-rspec.yml", job: "rspec_shard", key: "GH_TOKEN", value: "${{ github.token }}"
|
|
96
104
|
end
|
|
97
105
|
|
|
98
106
|
value_object "Step" do
|
|
@@ -110,6 +118,25 @@ Hecks.bluebook "CiWorkflows" do
|
|
|
110
118
|
member workflow: "ci-rspec.yml", job: "ci_attestation_rspec", name: "attest", id: "attest",
|
|
111
119
|
uses: "./.github/actions/check-attestation"
|
|
112
120
|
|
|
121
|
+
member workflow: "ci-rspec.yml", job: "shard_plan", name: "checkout", uses: "actions/checkout@v4"
|
|
122
|
+
member workflow: "ci-rspec.yml", job: "shard_plan", name: "ruby", uses: "./.github/actions/setup-ruby",
|
|
123
|
+
note: "The plan boots the CiSpeed bluebook and splits spec files; it builds no Rust."
|
|
124
|
+
member workflow: "ci-rspec.yml", job: "shard_plan", name: "environment",
|
|
125
|
+
uses: "./.github/actions/hecks-environment"
|
|
126
|
+
member workflow: "ci-rspec.yml", job: "shard_plan", name: "plan",
|
|
127
|
+
title: "Plan the shards: ShardPlan.Request, from the last main run's timings or file size",
|
|
128
|
+
run: "mkdir -p tmp/shard-plan-trace\ncat > tmp/shard-plan-steps.json <<'JSON'\n{\"steps\":[{\"verb\":\"CiSpeed::ShardPlan.Request\",\"args\":{\"name\":{\"value\":\"rspec\"},\"shards\":{\"value\":8},\"items_from\":{\"value\":\"glob:spec/**/*_spec.rb\"},\"timings_from\":{\"value\":\"main\"},\"out_dir\":{\"value\":\"tmp/plan\"}}}]}\nJSON\nbundle exec exe/hecks Operation.run! subject.value=lib/hecks/ci_speed script.value=tmp/shard-plan-steps.json --wait \\\n > tmp/shard-plan-trace/run.json \\\n || echo \"::warning::the shard plan failed; every leg splits by file size\"\n",
|
|
129
|
+
note: "`shards` is the rspec_shard matrix's group count. A plan that cannot be made leaves tmp/plan empty and\neach leg then splits by file size on its own, so a missing plan never fails the run."
|
|
130
|
+
member workflow: "ci-rspec.yml", job: "shard_plan", name: "trace",
|
|
131
|
+
title: "Publish the plan's events and the policies that reacted",
|
|
132
|
+
condition: "always()",
|
|
133
|
+
run: "jq -r -f .github/shard-plan-trace.jq tmp/shard-plan-trace/run.json \\\n | tee tmp/shard-plan-trace/trace.md >> \"$GITHUB_STEP_SUMMARY\" \\\n || echo \"::warning::no shard plan trace to publish\"\n"
|
|
134
|
+
member workflow: "ci-rspec.yml", job: "shard_plan", name: "upload_plan",
|
|
135
|
+
title: "Upload the plan every leg reads",
|
|
136
|
+
condition: "always()", uses: "actions/upload-artifact@v4"
|
|
137
|
+
member workflow: "ci-rspec.yml", job: "shard_plan", name: "upload_trace",
|
|
138
|
+
title: "Upload the plan's event trace",
|
|
139
|
+
condition: "always()", uses: "actions/upload-artifact@v4"
|
|
113
140
|
member workflow: "ci-rspec.yml", job: "rspec_shard", name: "checkout", uses: "actions/checkout@v4"
|
|
114
141
|
member workflow: "ci-rspec.yml", job: "rspec_shard", name: "ruby", uses: "./.github/actions/setup-ruby"
|
|
115
142
|
member workflow: "ci-rspec.yml", job: "rspec_shard", name: "zsh",
|
|
@@ -121,18 +148,22 @@ Hecks.bluebook "CiWorkflows" do
|
|
|
121
148
|
member workflow: "ci-rspec.yml", job: "rspec_shard", name: "generated_rust",
|
|
122
149
|
uses: "./.github/actions/generated-rust",
|
|
123
150
|
note: "rust/src/generated is committed, so the whole tree is in the checkout and this step only checks it.\nA shard reads only the domains named below (rust_build_kernel_input_spec); the input names them and\nchanges nothing. Specs that read the whole tree are tagged `generated_all` and run in the drift job,\nwhich regenerates every domain."
|
|
151
|
+
member workflow: "ci-rspec.yml", job: "rspec_shard", name: "download_plan",
|
|
152
|
+
title: "Read the plan",
|
|
153
|
+
run: "mkdir -p tmp/plan\ngh run download ${{ github.run_id }} --name shard-plan-rspec --dir tmp/plan \\\n || echo \"::warning::no shard plan to read; this leg splits by file size\"\n",
|
|
154
|
+
note: "Every leg reads the one plan the shard_plan job made, so the legs agree on the partition.\nA plan that cannot be read is no reason to fail: the leg splits by file size on its own."
|
|
124
155
|
member workflow: "ci-rspec.yml", job: "rspec_shard", name: "file_list",
|
|
125
156
|
title: "Determine this shard's own file list",
|
|
126
|
-
run: "
|
|
127
|
-
note: "`io: true` specs run elsewhere: rspec_rust_io (cargo/wasm) and rspec_postgres_io
|
|
157
|
+
run: "if [ -s \"tmp/plan/group-${{ matrix.group }}.txt\" ]; then\n cp \"tmp/plan/group-${{ matrix.group }}.txt\" tmp/rspec_shard_files.txt\nelse\n bundle exec exe/hecks test_suite_run.shard_specs group=${{ matrix.group }} groups=%{shard_total} \\\n > tmp/rspec_shard_files.txt\nfi\n",
|
|
158
|
+
note: "`io: true` specs run elsewhere: rspec_rust_io (cargo/wasm) and rspec_postgres_io."
|
|
128
159
|
member workflow: "ci-rspec.yml", job: "rspec_shard", name: "parallel",
|
|
129
160
|
title: "rspec: parallel (this shard's own file list only)",
|
|
130
|
-
run: "
|
|
131
|
-
note: "
|
|
161
|
+
run: "if [ -s tmp/plan/runtimes.log ]; then\n GROUPING=(--group-by runtime --runtime-log tmp/plan/runtimes.log --allowed-missing 100)\nelse\n GROUPING=(--group-by filesize)\nfi\nFILES=()\nwhile IFS= read -r line; do FILES+=(\"$line\"); done < tmp/rspec_shard_files.txt\nbundle exec parallel_rspec \"${FILES[@]}\" \"${GROUPING[@]}\" \\\n -o \"--tag ~io --tag ~fuzzing --tag ~generated_all --profile 15 --format documentation --format ParallelTests::RSpec::RuntimeLogger --out tmp/runtime_rspec.log\"\n",
|
|
162
|
+
note: "Grouping by runtime keeps one worker from drawing the slowest files; the plan's runtimes.log\nprices every file, so none is missing. Without a plan the workers group by file size."
|
|
132
163
|
member workflow: "ci-rspec.yml", job: "rspec_shard", name: "upload_log",
|
|
133
|
-
title: "Upload this shard's runtime log (
|
|
164
|
+
title: "Upload this shard's runtime log (the next run's plan reads it)",
|
|
134
165
|
condition: "always()", uses: "actions/upload-artifact@v4",
|
|
135
|
-
note: "Artifact only, never restored:
|
|
166
|
+
note: "Artifact only, never restored by this run: the shard_plan job of the next run downloads\nthe artifacts of the newest successful run on main and balances the legs by these seconds."
|
|
136
167
|
end
|
|
137
168
|
|
|
138
169
|
value_object "StepInput" do
|
|
@@ -146,6 +177,19 @@ Hecks.bluebook "CiWorkflows" do
|
|
|
146
177
|
member workflow: "ci-rspec.yml", job: "ci_attestation_rspec", step: "attest", key: "hmac-key",
|
|
147
178
|
value: "${{ secrets.HECKS_CI_ATTEST_KEY }}"
|
|
148
179
|
|
|
180
|
+
member workflow: "ci-rspec.yml", job: "shard_plan", step: "upload_plan", key: "name", value: "shard-plan-rspec"
|
|
181
|
+
member workflow: "ci-rspec.yml", job: "shard_plan", step: "upload_plan", key: "path", value: "tmp/plan"
|
|
182
|
+
member workflow: "ci-rspec.yml", job: "shard_plan", step: "upload_plan", key: "retention-days", value: "14"
|
|
183
|
+
member workflow: "ci-rspec.yml", job: "shard_plan", step: "upload_plan", key: "if-no-files-found",
|
|
184
|
+
value: "ignore"
|
|
185
|
+
member workflow: "ci-rspec.yml", job: "shard_plan", step: "upload_trace", key: "name",
|
|
186
|
+
value: "shard-plan-trace-rspec"
|
|
187
|
+
member workflow: "ci-rspec.yml", job: "shard_plan", step: "upload_trace", key: "path",
|
|
188
|
+
value: "tmp/shard-plan-trace"
|
|
189
|
+
member workflow: "ci-rspec.yml", job: "shard_plan", step: "upload_trace", key: "retention-days", value: "14"
|
|
190
|
+
member workflow: "ci-rspec.yml", job: "shard_plan", step: "upload_trace", key: "if-no-files-found",
|
|
191
|
+
value: "ignore"
|
|
192
|
+
|
|
149
193
|
member workflow: "ci-rspec.yml", job: "rspec_shard", step: "environment", key: "database",
|
|
150
194
|
value: "${{ secrets.HECKS_JOURNAL_DATABASE }}"
|
|
151
195
|
member workflow: "ci-rspec.yml", job: "rspec_shard", step: "generated_rust", key: "domains",
|
|
@@ -301,6 +301,7 @@ Hecks.bluebook "Coder" do
|
|
|
301
301
|
attribute :detail, BriefDetail, optional: true
|
|
302
302
|
attribute :area, list_of(AreaWord), optional: true
|
|
303
303
|
attribute :rules, BriefRules
|
|
304
|
+
attribute :review, BriefRules, optional: true
|
|
304
305
|
attribute :verification, BriefVerification
|
|
305
306
|
attribute :workflow, BriefRules, optional: true
|
|
306
307
|
attribute :landing, BriefRules, optional: true
|
|
@@ -355,10 +356,11 @@ Hecks.bluebook "Coder" do
|
|
|
355
356
|
attribute :area, list_of(AreaWord), optional: true, sets: true
|
|
356
357
|
|
|
357
358
|
sets :rules, to: "Map the whole picture before acting. Act on everything within the area. Stop only for (a) a risk of losing data, (b) an action the owner alone may take: a push, a mint, a release, deleting something that is not yours, or (c) a change outside the area. When stopped, report what was stuck first, then finish everything that does not depend on the stuck part."
|
|
359
|
+
sets :review, to: "Before you edit anything, have your plan reviewed. Write the plan: what you will build, which aggregates, commands, policies, ports, adapters and files it touches, and how it will be checked. Give it to the architect agent (the Agent tool, subagent type architect) and act on its verdict. Sound: build it. Sound with changes: revise the plan as its findings say, then build it. Rethink: edit nothing, hand the row back with exe/hecks tickets Work.yield! to={work} reason.value=\"architect: rethink. <its verdict and first finding>\" --wait, and end. Quote the verdict and each finding you acted on or set aside, with your reason, in your report."
|
|
358
360
|
sets :verification, to: "Run named specs only through the launcher: exe/hecks test_suite_run.run_spec_example! file.value=<spec> \"example.value= \" --wait. Do not run the full suite: it runs only in CI. Run at most {limit} spec files per call locally (test_suite_run.run_spec_files!), never a whole family. Never use git stash. Keep the quick gate green (exe/hecks gate_run.gate! stage=quick --wait) and RuboCop strict (methods at most 10 lines). Run the quick gate, specs and builds each as ONE blocking command with an explicit Bash timeout of up to 600000 ms; never as a background command with a monitor or poll loop. Run exe/hecks style_run.check_comments! paths=<one file> --wait for each Ruby file you touch."
|
|
359
361
|
sets :workflow, to: "Pull main first, always: fetch origin, bring local main up to date, then cut the branch. Work in your own worktree named for the row: step 1 is exe/hecks LinkedTree.add! branch.value={work} --wait, which pulls main (a fast-forward, or a rebase of local main's unpushed commits when both moved; it refuses by name when the checkout is dirty) and cuts the branch from it. A session the harness started in a worktree (EnterWorktree cuts from origin/main, not local main) checks git merge-base --is-ancestor origin/main HEAD and rebases onto local main if not, after exe/hecks LinkedTree.pull_main! --wait. Rebase onto local main before landing: local main can hold work that is not pushed, and landing refuses a branch whose base is behind origin/main. Put no Claude attribution trailer in a commit message: no Co-Authored-By line and no Claude-Session line. A worktree that holds ignored files is kept after landing and reported: remove it by hand."
|
|
360
362
|
sets :landing, to: "Regenerate only if a bluebook, hecksagon, Vocabulary row or generator changed: exe/hecks regeneration_run.regenerate_corpus! --confirm --wait, then --check. Run exe/hecks conformance_run.check_shape_ledger! --wait; a purely additive move uses --confirm --additive, any other move: stop and report it. Land with exe/hecks landing.finish! --confirm: one step that rebases onto local main, runs the quick gate, lands through the queue and removes the worktree and branch. It refuses by name at the first failure (unsaved work, conflict, gate red, queue busy): fix that and run it again. If landing refuses because a landing is in flight, run it again once; it queues. Never poll or sleep on a lock, and never run a lock file by hand."
|
|
361
|
-
sets :restraints, to: "Do not push. Never set HECKS_OWNER_PUSH and never use --no-verify: main is pushed only by boarding the push train. Do not mint an era.
|
|
363
|
+
sets :restraints, to: "Do not push. Never set HECKS_OWNER_PUSH and never use --no-verify: main is pushed only by boarding the push train. Do not mint an era. A red main raises a red flag (the RedFlagRaised event; exe/hecks RedFlag.status says whether one stands, and `exe/hecks Operation.follow domain.value=Hecks aggregate.value=RedFlag from_now=true wait=600` is the one line that listens, and it returns when RedFlagRaised or RedFlagCleared is recorded). On RedFlagRaised stop feature work and check in with exe/hecks RedFlag.check_in! session.value=<you> taking.value=<a red-* Work row or a cause> (exe/hecks tickets Work.undone lists the rows); Landing and the push train then refuse everything except the fix of a red-* row, by name (main is red: flag <id>), and a red-* row's fix goes out through the push train. Resume on RedFlagCleared, when CI on main is green again. Do not run the full spec suite locally; it runs in CI. Do not write to a live database (tickets.db, agent_run.db or any other under ~/.claude): use Memory or a temp file in specs. Never touch a local database that may hold client data."
|
|
362
364
|
sets :report, to: "End with a report: what changed, each with file:line; the spec counts (examples and failures); the shape-ledger action you took; and the sha you landed, or why nothing landed."
|
|
363
365
|
sets :coordination, to: "COORDINATION: other sessions are working in these areas. Do not edit them:"
|
|
364
366
|
sets :freeze, to: "FREEZE: the persisted shape of these may not move. If your change moves one, commit and hold, do not land, and report 'ready, held for the freeze':"
|
|
@@ -15,13 +15,15 @@ module Hecks
|
|
|
15
15
|
|
|
16
16
|
# @param scope [Hash] one scope of `CliRunner.name_index`
|
|
17
17
|
# @param typed [String] the word, with its `!` if it has one
|
|
18
|
-
# @param asking [Boolean] whether `query` came before it
|
|
19
|
-
|
|
18
|
+
# @param asking [Boolean] whether `query` (or `ask`) came before it
|
|
19
|
+
# @param old_spelling [Boolean] whether the word before it was `query`, the old spelling
|
|
20
|
+
def initialize(scope, typed, asking, old_spelling: false)
|
|
20
21
|
@scope = scope
|
|
21
22
|
@cli = { names: { command: scope["command"], question: scope["question"] } }
|
|
22
23
|
@typed = typed.chomp("!")
|
|
23
24
|
@bang = typed.end_with?("!")
|
|
24
25
|
@asking = asking
|
|
26
|
+
@old_spelling = old_spelling
|
|
25
27
|
@name = RUNNER.canonical_name(@cli, @typed)
|
|
26
28
|
end
|
|
27
29
|
|
|
@@ -29,7 +31,7 @@ module Hecks
|
|
|
29
31
|
def problem
|
|
30
32
|
return unknown unless command || question
|
|
31
33
|
|
|
32
|
-
bare_command || banged_query || query_of_command
|
|
34
|
+
bare_command || banged_query || query_of_command || query_word
|
|
33
35
|
end
|
|
34
36
|
|
|
35
37
|
private
|
|
@@ -47,7 +49,15 @@ module Hecks
|
|
|
47
49
|
def banged_query
|
|
48
50
|
return unless @bang && !@asking && !command && question
|
|
49
51
|
|
|
50
|
-
Problem.new("#{@typed}! is a query and reads; write it without the !",
|
|
52
|
+
Problem.new("#{@typed}! is a query and reads; write it without the !", question.to_s)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# A query is written bare (ADR 0163 rule 6); `query` stays only where a command shares the
|
|
56
|
+
# name.
|
|
57
|
+
def query_word
|
|
58
|
+
return unless @old_spelling && question && !command
|
|
59
|
+
|
|
60
|
+
Problem.new("query is the old spelling; write #{@typed} bare", question.to_s)
|
|
51
61
|
end
|
|
52
62
|
|
|
53
63
|
def query_of_command
|
|
@@ -74,7 +84,7 @@ module Hecks
|
|
|
74
84
|
def listed(near)
|
|
75
85
|
return "#{@scope["command"][near]}!" if @scope["command"][near]
|
|
76
86
|
|
|
77
|
-
|
|
87
|
+
@scope["question"][near].to_s
|
|
78
88
|
end
|
|
79
89
|
end
|
|
80
90
|
end
|
|
@@ -157,7 +157,8 @@ module Hecks
|
|
|
157
157
|
Hecks.boot(File.join(Parse::ROOT, "lib/hecks/hecks"), install_driving: false, environment: "memory")
|
|
158
158
|
end
|
|
159
159
|
|
|
160
|
-
# A safe-listed command writes through the runner only with its bang; the list names bare
|
|
160
|
+
# A safe-listed command writes through the runner only with its bang; the list names bare
|
|
161
|
+
# commands.
|
|
161
162
|
def writing(argv)
|
|
162
163
|
return argv unless argv.first && SAFE_COMMANDS.include?(argv.first)
|
|
163
164
|
|
data/lib/hecks/corpus.rb
CHANGED
|
@@ -28,6 +28,10 @@ module Hecks
|
|
|
28
28
|
# The Coder and Fixer domains keep their bluebook beside their hecksagon in their own folder.
|
|
29
29
|
coder: "lib/hecks/coder",
|
|
30
30
|
fixer: "lib/hecks/fixer",
|
|
31
|
+
# The PipelineBoard domain keeps its bluebook beside its hecksagon and adapters.
|
|
32
|
+
board: "lib/hecks/pipeline_board",
|
|
33
|
+
# The Pipeline domain: one passage per change, moved by the other domains' events.
|
|
34
|
+
pipeline: "lib/hecks/pipeline",
|
|
31
35
|
# A package vendored via `attaches ... from: :vendor` — nested inside the
|
|
32
36
|
# consuming example's own checkout, not this gem's own framework/bluebook/.
|
|
33
37
|
vendored: "examples/*/vendor/embryonaut_bluebooks/*"
|
|
@@ -44,7 +44,7 @@ module Hecks
|
|
|
44
44
|
def diagram_showcase(root)
|
|
45
45
|
lifecycle = File.read(File.join(root, "docs/generated/diagrams/pizzas/Order_lifecycle.mmd")).strip
|
|
46
46
|
<<~MARKDOWN.strip
|
|
47
|
-
`hecks
|
|
47
|
+
`hecks introspection.project_diagrams` reads a booted domain's own declaration and draws it as Mermaid — `<Name>_lifecycle.mmd`, `relationships.mmd`, `dispatch.mmd`, `roles.mmd`, `ports.mmd`, `read_models.mmd`, `<Name>_surface.mmd` (what a command does, and what it writes), `<Name>_saga.mmd`, and `frameworks.mmd`. Nothing hand-drawn — the same reason a domain is data at all. Order's own lifecycle, straight off the bluebook above:
|
|
48
48
|
|
|
49
49
|
```mermaid
|
|
50
50
|
#{lifecycle}
|
|
@@ -7,7 +7,7 @@ require_relative "sentence_form"
|
|
|
7
7
|
module Hecks
|
|
8
8
|
# The bluebooks the gem carries that a hecksagon can attach by name (ADR 0080): the language
|
|
9
9
|
# declared in itself, Expression, Tenancy, Deploy, Site, Tickets, Composer, QualityControl,
|
|
10
|
-
# Fixer, Coder, Bulk and
|
|
10
|
+
# Fixer, Coder, Bulk, CiPipeline, PipelineBoard and Pipeline.
|
|
11
11
|
# Framework members stay in `Framework`; `table` and `attach!` find a name across both.
|
|
12
12
|
#
|
|
13
13
|
# A bluebook is named by the `Hecks.bluebook "Name"` header of its files, and may span several.
|
|
@@ -19,7 +19,8 @@ module Hecks
|
|
|
19
19
|
GLOBS = %w[language/**/*.bluebook grammar/expression.bluebook tenancy/bluebook/*.bluebook
|
|
20
20
|
deploy/bluebook/*.bluebook site/bluebook/*.bluebook rails_app/bluebook/*.bluebook tickets/bluebook/*.bluebook
|
|
21
21
|
composer/bluebook/*.bluebook quality_control/*.bluebook fixer/*.bluebook coder/*.bluebook
|
|
22
|
-
bulk/bluebook/*.bluebook ci_pipeline/*.bluebook
|
|
22
|
+
bulk/bluebook/*.bluebook ci_pipeline/*.bluebook pipeline_board/*.bluebook
|
|
23
|
+
pipeline/*.bluebook].freeze
|
|
23
24
|
|
|
24
25
|
# A `Hecks.bluebook "Name"` header line, capturing the bluebook's name.
|
|
25
26
|
BLUEBOOK_HEADER = /\A\s*Hecks\.bluebook\s+"([^"]+)"/
|
|
@@ -11,7 +11,7 @@ require_relative "differential/wire_comparison"
|
|
|
11
11
|
module Hecks
|
|
12
12
|
module Fuzzing
|
|
13
13
|
# The Ruby-vs-Rust comparison of one generated sequence, shared by the QualityControl sweep
|
|
14
|
-
# (`hecks quality_control
|
|
14
|
+
# (`hecks quality_control sweep.run`) and `hecks quality_control
|
|
15
15
|
# target.check_generated_domains --rust`.
|
|
16
16
|
#
|
|
17
17
|
# `differ` is duck-typed (the `RustConformanceHelpers` comparison helpers plus a
|
|
@@ -9,7 +9,7 @@ module Hecks
|
|
|
9
9
|
|
|
10
10
|
# `kind:` on a `checked: false` result distinguishes a target with no
|
|
11
11
|
# lineage to audit (`:not_applicable`) from an audit that failed to
|
|
12
|
-
# run (`:error`) — `hecks quality_control
|
|
12
|
+
# run (`:error`) — `hecks quality_control sweep.run` holds the first and surfaces the
|
|
13
13
|
# second as a finding, instead of treating both as a clean audit.
|
|
14
14
|
def diverged_ancestor_writes(domain_path)
|
|
15
15
|
registry, directory = load_registry(domain_path)
|
|
@@ -51,7 +51,7 @@ module Hecks
|
|
|
51
51
|
# `:postgres_era` is the only mode that exercises era/lineage-bound SQL; plain
|
|
52
52
|
# `:postgres` never touches that machinery. Unlike `:postgres`, there's no shared
|
|
53
53
|
# scratch constant here — `database:`/`schema:` are required keyword args because
|
|
54
|
-
# the caller (`hecks quality_control
|
|
54
|
+
# the caller (`hecks quality_control sweep.run --persistence-parity`) owns that
|
|
55
55
|
# database's lifecycle.
|
|
56
56
|
def rebind_to_postgres_era!(copy, database:, schema:, disposable: true)
|
|
57
57
|
require "pg"
|
|
@@ -11,7 +11,7 @@ module Hecks
|
|
|
11
11
|
|
|
12
12
|
# Replays `steps` on both adapters and lists where the results differ.
|
|
13
13
|
#
|
|
14
|
-
# Compares the same six fields as `hecks quality_control
|
|
14
|
+
# Compares the same six fields as `hecks quality_control sweep.run`'s Ruby-vs-Rust mode.
|
|
15
15
|
# Both results are JSON-round-tripped so plain data compares equal on both sides.
|
|
16
16
|
#
|
|
17
17
|
# @param left [Symbol] an `IsolatedBoot` adapter: `:memory`, `:sqlite`, `:postgres`,
|
|
@@ -41,7 +41,7 @@ module Hecks
|
|
|
41
41
|
wasm_front: %w[rust]
|
|
42
42
|
}.freeze
|
|
43
43
|
|
|
44
|
-
# The modes `hecks quality_control
|
|
44
|
+
# The modes `hecks quality_control sweep.run` can run; one absent here is refused at
|
|
45
45
|
# start, not resolved.
|
|
46
46
|
RUNNABLE_MODES = %i[differential ruby_only self_consistency properties_in_differential
|
|
47
47
|
structural_skip_report adapter_parity_sqlite persistence_parity
|
|
@@ -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/ci_runs"
|
|
4
|
+
|
|
5
|
+
Hecks.adapter "CiRuns" do
|
|
6
|
+
port "CiRuns"
|
|
7
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require_relative "console_capture"
|
|
5
|
+
require_relative "main_integration/command_runner"
|
|
6
|
+
|
|
7
|
+
module Hecks
|
|
8
|
+
module Adapters
|
|
9
|
+
# The `CiRuns` port's adapter for `Integration.Observe`: reads, through `gh`, the CI run of a
|
|
10
|
+
# push of main at a landed commit. It answers `running` while there is no run yet or it has not
|
|
11
|
+
# completed, `passed` when it succeeded, and `failed` with the jobs that did not succeed.
|
|
12
|
+
class CiRuns
|
|
13
|
+
# The workflow whose run is read, as GitHub names it.
|
|
14
|
+
WORKFLOW = "CI"
|
|
15
|
+
|
|
16
|
+
# Job conclusions that are not a failure.
|
|
17
|
+
CLEAN = %w[success skipped neutral].freeze
|
|
18
|
+
|
|
19
|
+
class << self
|
|
20
|
+
# @return [#call, nil] takes `(argv, chdir:)` and answers `[ok, output]`; the command
|
|
21
|
+
# runner when nil. A spec replaces it with a fake GitHub.
|
|
22
|
+
attr_accessor :runner
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @param aggregate [Object, nil] unused
|
|
26
|
+
# @param settings [Hash] unused
|
|
27
|
+
# @param root [String, nil] unused
|
|
28
|
+
def initialize(aggregate: nil, settings: {}, root: nil); end
|
|
29
|
+
|
|
30
|
+
# @param held [Hash] the `Integration` record: `landed` and `checkout`
|
|
31
|
+
# @return [Hash{Symbol => Hash}] `outcome:` (`running`, `passed` or `failed`) and `report:`
|
|
32
|
+
# @raise [ConsoleCapture::Failure] when `gh` cannot be read
|
|
33
|
+
def watch(**held)
|
|
34
|
+
landed = plain(held[:landed]).to_s
|
|
35
|
+
@checkout = plain(held[:checkout]) || Dir.pwd
|
|
36
|
+
run = latest_run(landed)
|
|
37
|
+
return answer("running", "#{landed[0, 10]}: no #{WORKFLOW} run yet") unless run
|
|
38
|
+
return answer("running", "#{landed[0, 10]}: #{WORKFLOW} run #{run["databaseId"]} is #{run["status"]}") unless
|
|
39
|
+
run["status"] == "completed"
|
|
40
|
+
|
|
41
|
+
verdict(landed, run)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def latest_run(landed)
|
|
47
|
+
gh("run", "list", "--workflow", WORKFLOW, "--branch", "main", "--event", "push", "--commit", landed,
|
|
48
|
+
"--json", "databaseId,status,conclusion", "--limit", "1").first
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def verdict(landed, run)
|
|
52
|
+
id = run["databaseId"]
|
|
53
|
+
return answer("passed", "#{landed[0, 10]}: #{WORKFLOW} run #{id} passed") if run["conclusion"] == "success"
|
|
54
|
+
|
|
55
|
+
answer("failed", "#{landed[0, 10]}: #{WORKFLOW} run #{id} #{run["conclusion"]}; failed jobs: #{failed_jobs(id)}")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def failed_jobs(id)
|
|
59
|
+
jobs = gh("run", "view", id.to_s, "--json", "jobs").fetch("jobs", [])
|
|
60
|
+
names = jobs.reject { |job| CLEAN.include?(job["conclusion"].to_s) }.map { |job| job["name"] }
|
|
61
|
+
names.empty? ? "none named" : names.join(", ")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def gh(*args)
|
|
65
|
+
ok, output = runner.call(["gh", *args], chdir: @checkout)
|
|
66
|
+
raise ConsoleCapture::Failure, "gh #{args.first(2).join(" ")} failed: #{output.to_s.strip}" unless ok
|
|
67
|
+
|
|
68
|
+
JSON.parse(output)
|
|
69
|
+
rescue JSON::ParserError
|
|
70
|
+
raise ConsoleCapture::Failure, "gh #{args.first(2).join(" ")} answered no JSON"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def answer(outcome, report) = { outcome: { value: outcome }, report: { value: report }, checkout: { value: @checkout } }
|
|
74
|
+
|
|
75
|
+
def runner = self.class.runner || MainIntegration::CommandRunner.new
|
|
76
|
+
|
|
77
|
+
def plain(argument) = argument.is_a?(Hash) ? argument[:value] : argument
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "shell"
|
|
4
|
+
require_relative "console_capture"
|
|
5
|
+
|
|
6
|
+
module Hecks
|
|
7
|
+
module Adapters
|
|
8
|
+
# The agent types the `claude` command can start in a directory: the built-in ones, the user's
|
|
9
|
+
# and the directory's own.
|
|
10
|
+
#
|
|
11
|
+
# `claude` has no listing of its own, but it names every available agent when asked for one it
|
|
12
|
+
# does not have, and then stops before it starts anything. The list is read from that answer,
|
|
13
|
+
# in the directory a session would work in, so the directory's own agents are counted. An
|
|
14
|
+
# answer that names no list is refused with what `claude` said, so a reworded message is seen at
|
|
15
|
+
# once, not taken for an empty list.
|
|
16
|
+
class ClaudeAgentList
|
|
17
|
+
# An agent name no one gives an agent, asked for only to make `claude` name the real ones.
|
|
18
|
+
PROBE = "hecks-probe-not-an-agent"
|
|
19
|
+
|
|
20
|
+
# How `claude` names the agents it has, after refusing one it has not.
|
|
21
|
+
AVAILABLE = /Available agents:\s*(.+)$/
|
|
22
|
+
|
|
23
|
+
# @param command [String] the program that starts sessions
|
|
24
|
+
# @param shell [Shell] runs it
|
|
25
|
+
def initialize(command, shell: Shell.new)
|
|
26
|
+
@command = command
|
|
27
|
+
@shell = shell
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @param directory [String] where a session would work
|
|
31
|
+
# @return [Array<String>] the agent names `claude` lists there
|
|
32
|
+
# @raise [ConsoleCapture::Failure] when its answer names no list
|
|
33
|
+
def names(directory)
|
|
34
|
+
result = @shell.capture(@command, "--agent", PROBE, "-p", "list the agents", chdir: directory)
|
|
35
|
+
said = "#{result.out}\n#{result.err}"
|
|
36
|
+
listed = said[AVAILABLE, 1]
|
|
37
|
+
raise ConsoleCapture::Failure, "#{@command} listed no agents: #{said.strip[0, 300]}" unless listed
|
|
38
|
+
|
|
39
|
+
listed.split(",").map(&:strip).reject(&:empty?)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|