hecks 2.9.0 → 3.0.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/exe/hecks +51 -6
- data/lib/hecks/adapters/driven/heki/journal.rb +1 -1
- data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
- data/lib/hecks/adapters/driven/lambda.rb +1 -1
- data/lib/hecks/adapters/driven/memory.rb +16 -3
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +6 -2
- data/lib/hecks/adapters/driven/postgres/shared_connection.rb +180 -0
- data/lib/hecks/adapters/driven/postgres.rb +4 -2
- data/lib/hecks/adapters/driven/prism.rb +41 -8
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +73 -16
- data/lib/hecks/behaviors/expectations.rb +1 -1
- data/lib/hecks/behaviors.rb +1 -1
- data/lib/hecks/bench/cli.rb +62 -0
- data/lib/hecks/bench/environment.rb +71 -0
- data/lib/hecks/bench/postgres_probe.rb +30 -0
- data/lib/hecks/bench/report.rb +76 -0
- data/lib/hecks/bench/ruby_runner.rb +93 -0
- data/lib/hecks/bench/run.rb +55 -0
- data/lib/hecks/bench/rust_runner.rb +101 -0
- data/lib/hecks/bench/stats.rb +46 -0
- data/lib/hecks/bench/suite.rb +98 -0
- data/lib/hecks/bench/workload.rb +89 -0
- data/lib/hecks/bench.rb +17 -0
- data/lib/hecks/bluebook/assembly/contracts.rb +2 -0
- data/lib/hecks/bluebook/attribute.rb +1 -1
- data/lib/hecks/bluebook/behaviour/aggregate.rb +30 -1
- data/lib/hecks/bluebook/behaviour/domain_port.rb +7 -0
- data/lib/hecks/bluebook/chapter.rb +6 -2
- data/lib/hecks/bluebook/domain_port.rb +36 -5
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +10 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +8 -0
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -1
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +31 -3
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +122 -3
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +1 -1
- data/lib/hecks/bluebook/dsl/query_builder.rb +27 -1
- data/lib/hecks/bluebook/dsl/rule_reference.rb +1 -1
- data/lib/hecks/bluebook/dsl/world_builder.rb +29 -2
- data/lib/hecks/bluebook/expression/canonical_form.rb +1 -1
- data/lib/hecks/bluebook/expression/resolver.rb +3 -2
- data/lib/hecks/bluebook/hexagon.rb +13 -5
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +1 -0
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +4 -1
- data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +246 -0
- data/lib/hecks/bluebook/meta_validator.rb +9 -4
- data/lib/hecks/bluebook/model_check.rb +23 -0
- data/lib/hecks/bluebook/pattern_subset.rb +38 -0
- data/lib/hecks/bluebook/query.rb +20 -5
- data/lib/hecks/bluebook/smoke_test.rb +1 -1
- data/lib/hecks/canonical_json.rb +30 -0
- data/lib/hecks/chapters.rb +102 -0
- data/lib/hecks/cli/behaviors.rb +61 -0
- data/lib/hecks/cli/canonicalise.rb +25 -0
- data/lib/hecks/cli/check_engine_agreement.rb +32 -0
- data/lib/hecks/cli/check_era.rb +88 -0
- data/lib/hecks/cli/console.rb +66 -0
- data/lib/hecks/cli/deploy_template_diff.rb +40 -0
- data/lib/hecks/cli/doc_coverage.rb +32 -0
- data/lib/hecks/cli/document.rb +2 -2
- data/lib/hecks/cli/expression_projection.rb +41 -0
- data/lib/hecks/cli/follow.rb +88 -0
- data/lib/hecks/cli/history.rb +44 -0
- data/lib/hecks/cli/ir.rb +1 -1
- data/lib/hecks/cli/mcp.rb +3 -3
- data/lib/hecks/cli/model_check.rb +7 -4
- data/lib/hecks/cli/pattern_cases.rb +47 -0
- data/lib/hecks/cli/present.rb +39 -0
- data/lib/hecks/cli/project_cli.rb +224 -32
- data/lib/hecks/cli/project_diagrams.rb +4 -3
- data/lib/hecks/cli/project_glossary.rb +44 -0
- data/lib/hecks/cli/reference.rb +46 -0
- data/lib/hecks/cli/refresh_projections.rb +44 -0
- data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +150 -0
- data/lib/hecks/cli/release.rb +70 -0
- data/lib/hecks/cli/release_gem.rb +59 -0
- data/lib/hecks/cli/rspec_io_parallel_files.rb +137 -0
- data/lib/hecks/cli/rspec_shard_files.rb +66 -0
- data/lib/hecks/cli/run.rb +3 -3
- data/lib/hecks/cli/seed_semantics_corpus.rb +71 -0
- data/lib/hecks/cli/serve_query_ir_mcp.rb +30 -0
- data/lib/hecks/cli/shape.rb +70 -0
- data/lib/hecks/cli/smoke_http.rb +230 -0
- data/lib/hecks/cli/smoke_test.rb +5 -3
- data/lib/hecks/cli/spec_example.rb +27 -0
- data/lib/hecks/cli/statements.rb +32 -0
- data/lib/hecks/cli/stores.rb +4 -5
- data/lib/hecks/cli/stress_concurrency_specs.rb +177 -0
- data/lib/hecks/cli.rb +201 -52
- data/lib/hecks/codemod.rb +324 -0
- data/lib/hecks/corpus.rb +423 -0
- data/lib/hecks/deploy/bluebook/deploy.bluebook +591 -23
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +41 -4
- data/lib/hecks/deploy/oidc.json +82 -2
- data/lib/hecks/doc/reference.rb +351 -0
- data/lib/hecks/doors/cli_door.rb +196 -0
- data/lib/hecks/doors/cli_runner.rb +297 -0
- data/lib/hecks/{facade → doors}/command_request.rb +1 -1
- data/lib/hecks/{facade → doors}/handle.rb +2 -2
- data/lib/hecks/{facade → doors}/json_door.rb +4 -4
- data/lib/hecks/doors/launcher_options.rb +129 -0
- data/lib/hecks/{cli → doors}/mcp_door.rb +7 -7
- data/lib/hecks/doors/mcp_door_scope.rb +97 -0
- data/lib/hecks/{facade/surface → doors/ruby_door}/aggregate_door.rb +8 -3
- data/lib/hecks/{facade/surface → doors/ruby_door}/chapter.rb +3 -3
- data/lib/hecks/{facade/surface.rb → doors/ruby_door.rb} +36 -5
- data/lib/hecks/doors.rb +61 -0
- data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +1 -1
- data/lib/hecks/embryonaut_bluebook.rb +1 -1
- data/lib/hecks/engine_agreement.rb +156 -0
- data/lib/hecks/facade.rb +24 -37
- data/lib/hecks/forms/app.rb +6 -6
- data/lib/hecks/forms/banking_presentation.rb +50 -0
- data/lib/hecks/forms/field_shape.rb +2 -1
- data/lib/hecks/forms/port_argument.rb +1 -1
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +393 -0
- data/lib/hecks/fuzzing/combination_miner.rb +86 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +214 -0
- data/lib/hecks/fuzzing/coverage_campaign.rb +143 -0
- data/lib/hecks/fuzzing/differential.rb +156 -0
- data/lib/hecks/fuzzing/domain_generator.rb +615 -0
- data/lib/hecks/fuzzing/era_boundary.rb +71 -0
- data/lib/hecks/fuzzing/form_census.rb +136 -0
- data/lib/hecks/fuzzing/generated_domain_check.rb +93 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +64 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +301 -0
- data/lib/hecks/fuzzing/nondeterministic.rb +51 -0
- data/lib/hecks/fuzzing/outside_world.rb +45 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +102 -0
- data/lib/hecks/fuzzing/properties/corrections.rb +88 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +314 -0
- data/lib/hecks/fuzzing/properties/guards.rb +190 -0
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +300 -0
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +93 -0
- data/lib/hecks/fuzzing/properties/outbox.rb +89 -0
- data/lib/hecks/fuzzing/properties/querying.rb +175 -0
- data/lib/hecks/fuzzing/properties.rb +162 -0
- data/lib/hecks/fuzzing/qa_settings.rb +123 -0
- data/lib/hecks/fuzzing/replay.rb +505 -0
- data/lib/hecks/fuzzing/rotation_priority.rb +48 -0
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +108 -0
- data/lib/hecks/fuzzing/self_consistency.rb +339 -0
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +385 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +132 -0
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +95 -0
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +77 -0
- data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +109 -0
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +237 -0
- data/lib/hecks/fuzzing/sequence_generator.rb +189 -0
- data/lib/hecks/fuzzing/shrinker.rb +166 -0
- data/lib/hecks/fuzzing/structural_skips.rb +33 -0
- data/lib/hecks/fuzzing/sweep_depth.rb +43 -0
- data/lib/hecks/fuzzing/target_capabilities.rb +163 -0
- data/lib/hecks/fuzzing/value_generator.rb +216 -0
- data/lib/hecks/fuzzing.rb +23 -0
- data/lib/hecks/grammar/evolve.rb +336 -0
- data/lib/hecks/hecks/adapters/codebase/codemods.rb +45 -0
- data/lib/hecks/hecks/adapters/codebase/conformance.rb +71 -0
- data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +202 -0
- data/lib/hecks/hecks/adapters/codebase/gem_registry.rb +80 -0
- data/lib/hecks/hecks/adapters/codebase/kernel_tables.rb +54 -0
- data/lib/hecks/hecks/adapters/codebase/language.rb +240 -0
- data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +102 -0
- data/lib/hecks/hecks/adapters/codebase/publishing.rb +142 -0
- data/lib/hecks/hecks/adapters/codebase/regeneration.rb +67 -0
- data/lib/hecks/hecks/adapters/codebase/release_facts.rb +90 -0
- data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +83 -0
- data/lib/hecks/hecks/adapters/codebase/secret_vault.rb +36 -0
- data/lib/hecks/hecks/adapters/codebase/source_tree.adapter +7 -0
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +189 -0
- data/lib/hecks/hecks/adapters/codebase/sqlite_fixture.rb +71 -0
- data/lib/hecks/hecks/adapters/codebase/style.rb +171 -0
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +55 -0
- data/lib/hecks/hecks/adapters/codebase/test_suite.rb +165 -0
- data/lib/hecks/hecks/adapters/codebase/tree.rb +111 -0
- data/lib/hecks/hecks/adapters/console_capture.rb +79 -0
- data/lib/hecks/hecks/adapters/deploy_toolchain.adapter +7 -0
- data/lib/hecks/hecks/adapters/deploy_toolchain.rb +108 -0
- data/lib/hecks/hecks/adapters/git.adapter +7 -0
- data/lib/hecks/hecks/adapters/git.rb +102 -0
- data/lib/hecks/hecks/adapters/host_http.adapter +7 -0
- data/lib/hecks/hecks/adapters/host_http.rb +65 -0
- data/lib/hecks/hecks/adapters/in_process_boot.adapter +7 -0
- data/lib/hecks/hecks/adapters/in_process_boot.rb +197 -0
- data/lib/hecks/hecks/adapters/in_process_operations.rb +202 -0
- data/lib/hecks/hecks/adapters/journal_store/changes.rb +143 -0
- data/lib/hecks/hecks/adapters/journal_store/compaction.rb +145 -0
- data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +132 -0
- data/lib/hecks/hecks/adapters/journal_store/examination.rb +135 -0
- data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +163 -0
- data/lib/hecks/hecks/adapters/journal_store/readings.rb +187 -0
- data/lib/hecks/hecks/adapters/journal_store.adapter +7 -0
- data/lib/hecks/hecks/adapters/journal_store.rb +64 -0
- data/lib/hecks/hecks/adapters/local_files.adapter +7 -0
- data/lib/hecks/hecks/adapters/local_files.rb +46 -0
- data/lib/hecks/hecks/adapters/pg_admin.adapter +7 -0
- data/lib/hecks/hecks/adapters/pg_admin.rb +245 -0
- data/lib/hecks/hecks/adapters/process_pool.adapter +7 -0
- data/lib/hecks/hecks/adapters/process_pool.rb +119 -0
- data/lib/hecks/hecks/adapters/rust_toolchain.adapter +7 -0
- data/lib/hecks/hecks/adapters/rust_toolchain.rb +179 -0
- data/lib/hecks/hecks/adapters/rust_workspace.rb +150 -0
- data/lib/hecks/hecks/adapters/shell.adapter +7 -0
- data/lib/hecks/hecks/adapters/shell.rb +49 -0
- data/lib/hecks/hecks/adapters/terminal.adapter +7 -0
- data/lib/hecks/hecks/adapters/terminal.rb +74 -0
- data/lib/hecks/hecks/codebase.behaviors +172 -0
- data/lib/hecks/hecks/codebase.bluebook +2453 -0
- data/lib/hecks/hecks/context_map.hecksagon +13 -0
- data/lib/hecks/hecks/custodian.behaviors +179 -0
- data/lib/hecks/hecks/custodian.bluebook +1847 -0
- data/lib/hecks/hecks/deploy.behaviors +74 -0
- data/lib/hecks/hecks/deploy.world +12 -0
- data/lib/hecks/hecks/environments/memory.world +10 -0
- data/lib/hecks/hecks/governance.world +7 -0
- data/lib/hecks/hecks/hecks.behaviors +62 -0
- data/lib/hecks/hecks/hecks.bluebook +124 -0
- data/lib/hecks/hecks/hecks.hecksagon +390 -0
- data/lib/hecks/hecks/hecks.world +42 -0
- data/lib/hecks/kernel_capabilities.rb +194 -0
- data/lib/hecks/language/bluebook/bluebook.bluebook +11 -0
- data/lib/hecks/language/bluebook/query.bluebook +7 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +4 -4
- data/lib/hecks/language/hecksagon/domain_port.bluebook +10 -0
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +20 -0
- data/lib/hecks/mcp_stdio_guard.rb +2 -2
- data/lib/hecks/persistence_legacy_fixture/regenerate.rb +161 -0
- data/lib/hecks/persistence_legacy_fixture.rb +113 -0
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +8 -5
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +4 -1
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +15 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +32 -15
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +6 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +227 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +43 -24
- data/lib/hecks/ports/persistence/plugins/era/translation.rb +1 -0
- data/lib/hecks/ports/persistence/state_codec.rb +24 -5
- data/lib/hecks/projection_files.rb +285 -0
- data/lib/hecks/projections/bootstrap_table.rb +1 -1
- data/lib/hecks/projections/deploy/fargate.rb +5 -5
- data/lib/hecks/projections/deploy/lambda.rb +43 -32
- data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
- data/lib/hecks/projections/deploy/preview/template.rb +1 -1
- data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +1 -1
- data/lib/hecks/projections/deploy/scripts/expected-era.header +1 -1
- data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +3 -3
- data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +1 -1
- data/lib/hecks/projections/deploy/shared.rb +1 -1
- data/lib/hecks/projections/diagrams.rb +3 -3
- data/lib/hecks/projections/model/deviations.rb +1 -1
- data/lib/hecks/projections/parser_table.rb +4 -4
- data/lib/hecks/projections/reference.rb +2 -3
- data/lib/hecks/projections/rust_vocabulary.rb +1 -1
- data/lib/hecks/projector/cli_projector.rb +106 -37
- data/lib/hecks/projector.rb +1 -1
- data/lib/hecks/quality_control/adapters/agent.adapter +7 -0
- data/lib/hecks/quality_control/adapters/agent.rb +58 -0
- data/lib/hecks/quality_control/adapters/angle_tools.adapter +7 -0
- data/lib/hecks/quality_control/adapters/angle_tools.rb +15 -0
- data/lib/hecks/quality_control/adapters/clearance_recorder.rb +36 -0
- data/lib/hecks/quality_control/adapters/clearance_tools.adapter +7 -0
- data/lib/hecks/quality_control/adapters/clearance_tools.rb +17 -0
- data/lib/hecks/quality_control/adapters/git_pr.adapter +7 -0
- data/lib/hecks/quality_control/adapters/git_pr.rb +226 -0
- data/lib/hecks/quality_control/adapters/github_checks.adapter +9 -0
- data/lib/hecks/quality_control/adapters/github_checks.rb +83 -0
- data/lib/hecks/quality_control/adapters/github_ci_webhook.rb +56 -0
- data/lib/hecks/quality_control/adapters/github_issues.adapter +7 -0
- data/lib/hecks/quality_control/adapters/github_issues.rb +49 -0
- data/lib/hecks/quality_control/adapters/qa_tool.rb +59 -0
- data/lib/hecks/quality_control/adapters/sweep_tools.adapter +7 -0
- data/lib/hecks/quality_control/adapters/sweep_tools.rb +51 -0
- data/lib/hecks/quality_control/adapters/target_tools.adapter +7 -0
- data/lib/hecks/quality_control/adapters/target_tools.rb +43 -0
- data/lib/hecks/quality_control/cli/child.rb +45 -0
- data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +45 -0
- data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +248 -0
- data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +163 -0
- data/lib/hecks/quality_control/cli/qa_generated_domains.rb +450 -0
- data/lib/hecks/quality_control/cli/qa_log_bug.rb +195 -0
- data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +220 -0
- data/lib/hecks/quality_control/cli/qa_open_pr.rb +305 -0
- data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +154 -0
- data/lib/hecks/quality_control/cli/qa_postgres_role.rb +159 -0
- data/lib/hecks/quality_control/cli/qa_pr_check.rb +232 -0
- data/lib/hecks/quality_control/cli/qa_seed_angles.rb +142 -0
- data/lib/hecks/quality_control/cli/qa_seed_targets.rb +69 -0
- data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +280 -0
- data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +329 -0
- data/lib/hecks/quality_control/cli/qa_sweep/dials.rb +92 -0
- data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +117 -0
- data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +175 -0
- data/lib/hecks/quality_control/cli/qa_sweep/release_mode.rb +60 -0
- data/lib/hecks/quality_control/cli/qa_sweep.rb +611 -0
- data/lib/hecks/quality_control/cli/qa_tick.rb +274 -0
- data/lib/hecks/quality_control/quality_control.bluebook +2224 -0
- data/lib/hecks/quality_control/quality_control.hecksagon +13 -0
- data/lib/hecks/quality_control/quality_control.ports.hecksagon +54 -0
- data/lib/hecks/query_ir.rb +346 -0
- data/lib/hecks/query_ir_mcp.rb +198 -0
- data/lib/hecks/release/runner/ci_publisher.rb +2 -2
- data/lib/hecks/release/runner/clean_tree.rb +42 -0
- data/lib/hecks/release/runner/console.rb +6 -0
- data/lib/hecks/release/runner/gem_publisher.rb +14 -7
- data/lib/hecks/release/runner/npm_publisher.rb +6 -36
- data/lib/hecks/release/runner/preflight.rb +26 -2
- data/lib/hecks/release/runner/published.rb +8 -26
- data/lib/hecks/release/runner/tagger.rb +30 -15
- data/lib/hecks/release/runner.rb +57 -6
- data/lib/hecks/router/namespace_installer.rb +3 -3
- data/lib/hecks/runtime/adapter_lookup.rb +124 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +4 -0
- data/lib/hecks/runtime/dependency_planning.rb +20 -2
- data/lib/hecks/runtime/dispatcher.rb +53 -3
- data/lib/hecks/runtime/interpreting.rb +3 -0
- data/lib/hecks/runtime/loader.rb +65 -12
- data/lib/hecks/runtime/outbox.rb +1 -1
- data/lib/hecks/runtime/policy_interpreter.rb +5 -2
- data/lib/hecks/runtime/port_operation_interpreter.rb +2 -10
- data/lib/hecks/runtime/query_interpreter.rb +77 -0
- data/lib/hecks/runtime/reaction_outcome.rb +89 -0
- data/lib/hecks/runtime/refusal_wording.rb +23 -1
- data/lib/hecks/runtime/registry/verification.rb +163 -4
- data/lib/hecks/runtime/registry.rb +57 -3
- data/lib/hecks/runtime/remote_dispatcher.rb +31 -2
- data/lib/hecks/runtime/value/coercion.rb +23 -135
- data/lib/hecks/runtime/value/field_checks.rb +172 -0
- data/lib/hecks/runtime/value.rb +4 -1
- data/lib/hecks/runtime.rb +20 -6
- data/lib/hecks/rust_build/conformance.rb +158 -0
- data/lib/hecks/rust_build/conformance_fuzz.rb +71 -0
- data/lib/hecks/rust_build/coverage.rb +218 -0
- data/lib/hecks/rust_build/native_build.rb +124 -0
- data/lib/hecks/rust_build/project_rust/root_mod.rb +120 -0
- data/lib/hecks/rust_build/project_rust.rb +332 -0
- data/lib/hecks/rust_build/wasm.rb +105 -0
- data/lib/hecks/rust_build/wasm_browser.rb +113 -0
- data/lib/hecks/rust_build.rb +144 -0
- data/lib/hecks/storehouse.rb +16 -16
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +2 -2
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +1 -1
- data/lib/hecks/three_zero/forms.yml +127 -0
- data/lib/hecks/three_zero.rb +12 -0
- data/lib/hecks/tools/argument_gate_matrix.rb +316 -0
- data/lib/hecks/tools/comment_style.rb +1070 -0
- data/lib/hecks/tools/corpus_report.rb +87 -0
- data/lib/hecks/tools/deploy_recipe.rb +255 -0
- data/lib/hecks/tools/deploy_recipe_lint.rb +282 -0
- data/lib/hecks/tools/drop_implicit_append_fields.rb +95 -0
- data/lib/hecks/tools/era_projection_backfill.rb +94 -0
- data/lib/hecks/tools/era_reattest.rb +146 -0
- data/lib/hecks/tools/evolve_run.rb +189 -0
- data/lib/hecks/tools/fuzz_sweep.rb +373 -0
- data/lib/hecks/tools/heki_compaction.rb +100 -0
- data/lib/hecks/tools/hoist_local_givens.rb +246 -0
- data/lib/hecks/tools/journal_compaction.rb +89 -0
- data/lib/hecks/tools/oidc_manifests.rb +70 -0
- data/lib/hecks/tools/query_ir_run.rb +81 -0
- data/lib/hecks/tools/regeneration_run.rb +166 -0
- data/lib/hecks/tools/rust_comment_style.rb +642 -0
- data/lib/hecks/tools/sequence_script.rb +62 -0
- data/lib/hecks/tools/tail_merge.rb +117 -0
- data/lib/hecks/tools/tenant_provisioning.rb +142 -0
- data/lib/hecks/tools/translation_audit.rb +208 -0
- data/lib/hecks/tools/translation_scaffold.rb +131 -0
- data/lib/hecks/tools.rb +86 -0
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks.rb +32 -15
- data/qa/settings.yml +66 -0
- data/rust/.cargo/config.toml +6 -0
- data/rust/.gitignore +2 -0
- data/rust/Cargo.lock +54 -0
- data/rust/Cargo.toml +61 -0
- data/rust/build/.cargo/config.toml +3 -0
- data/rust/build/.gitignore +1 -0
- data/rust/build/Cargo.lock +7 -0
- data/rust/build/Cargo.toml +19 -0
- data/rust/build/src/build_artifact.rs +47 -0
- data/rust/build/src/cargo_sync.rs +248 -0
- data/rust/build/src/json.rs +421 -0
- data/rust/build/src/lineage_pass.rs +285 -0
- data/rust/build/src/main.rs +51 -0
- data/rust/build/src/optional_pass.rs +150 -0
- data/rust/build/src/pipeline.rs +264 -0
- data/rust/build/src/reserved_names.rs +76 -0
- data/rust/build/src/resolve.rs +124 -0
- data/rust/build/src/root.rs +25 -0
- data/rust/build/src/sidecars.rs +73 -0
- data/rust/build/src/subprocess.rs +51 -0
- data/rust/build/src/tmp.rs +31 -0
- data/rust/codegen/.cargo/config.toml +3 -0
- data/rust/codegen/.gitignore +1 -0
- data/rust/codegen/Cargo.lock +7 -0
- data/rust/codegen/Cargo.toml +20 -0
- data/rust/codegen/src/attr.rs +32 -0
- data/rust/codegen/src/bridging.rs +481 -0
- data/rust/codegen/src/commands.rs +1226 -0
- data/rust/codegen/src/constraints.rs +109 -0
- data/rust/codegen/src/dependency_planning.rs +209 -0
- data/rust/codegen/src/domain_generator.rs +1288 -0
- data/rust/codegen/src/exemplar.rs +230 -0
- data/rust/codegen/src/expr_emitter.rs +87 -0
- data/rust/codegen/src/fielded.rs +192 -0
- data/rust/codegen/src/hecks_naming.rs +33 -0
- data/rust/codegen/src/json.rs +335 -0
- data/rust/codegen/src/json_codec.rs +697 -0
- data/rust/codegen/src/literal.rs +207 -0
- data/rust/codegen/src/main.rs +290 -0
- data/rust/codegen/src/manifest.rs +227 -0
- data/rust/codegen/src/mutations.rs +784 -0
- data/rust/codegen/src/naming.rs +286 -0
- data/rust/codegen/src/ports.rs +208 -0
- data/rust/codegen/src/prelude.rs +116 -0
- data/rust/codegen/src/queries.rs +786 -0
- data/rust/codegen/src/reactions.rs +532 -0
- data/rust/codegen/src/read_models.rs +697 -0
- data/rust/codegen/src/reference_specs.rs +40 -0
- data/rust/codegen/src/registry.rs +966 -0
- data/rust/codegen/src/reserved_names.rs +76 -0
- data/rust/codegen/src/shared.rs +93 -0
- data/rust/codegen/src/skip_reason.rs +25 -0
- data/rust/codegen/src/types.rs +362 -0
- data/rust/host/.cargo/config.toml +3 -0
- data/rust/host/.gitignore +1 -0
- data/rust/host/Cargo.lock +4297 -0
- data/rust/host/Cargo.toml +61 -0
- data/rust/host/HECKS_RELEASE +1 -0
- data/rust/host/rds-ca-bundle.pem +2736 -0
- data/rust/host/src/api.rs +1596 -0
- data/rust/host/src/approval.rs +652 -0
- data/rust/host/src/auth.rs +1287 -0
- data/rust/host/src/bin/expr_harness.rs +46 -0
- data/rust/host/src/bin/lineage_harness.rs +119 -0
- data/rust/host/src/bin/mint_harness.rs +135 -0
- data/rust/host/src/checkout.rs +265 -0
- data/rust/host/src/dispatch.rs +1443 -0
- data/rust/host/src/expr_json.rs +1117 -0
- data/rust/host/src/field_hints.rs +52 -0
- data/rust/host/src/ir.rs +733 -0
- data/rust/host/src/journal.rs +874 -0
- data/rust/host/src/lambda_client.rs +378 -0
- data/rust/host/src/log.rs +147 -0
- data/rust/host/src/main.rs +474 -0
- data/rust/host/src/mint.rs +1865 -0
- data/rust/host/src/payments/keystore.rs +330 -0
- data/rust/host/src/payments/tests.rs +1400 -0
- data/rust/host/src/payments.rs +767 -0
- data/rust/host/src/presentation.rs +698 -0
- data/rust/host/src/presentation_write.rs +1431 -0
- data/rust/host/src/query_step.rs +149 -0
- data/rust/host/src/rate_limit.rs +904 -0
- data/rust/host/src/reference_transform.rs +241 -0
- data/rust/host/src/reference_validate.rs +338 -0
- data/rust/host/src/resend.rs +296 -0
- data/rust/host/src/secrets.rs +106 -0
- data/rust/host/src/server.rs +663 -0
- data/rust/host/src/storage_shape.rs +175 -0
- data/rust/host/src/test_pg.rs +40 -0
- data/rust/host/src/ui_schema.rs +1351 -0
- data/rust/host/src/wasm_runner.rs +191 -0
- data/rust/host/src/web/newsletter/cooldown.rs +126 -0
- data/rust/host/src/web/newsletter/tests.rs +104 -0
- data/rust/host/src/web/newsletter.rs +310 -0
- data/rust/host/src/web/newsletter_send.rs +357 -0
- data/rust/host/src/web/registrations.rs +1505 -0
- data/rust/host/src/web.rs +2937 -0
- data/rust/lsp/.gitignore +1 -0
- data/rust/lsp/Cargo.lock +7 -0
- data/rust/lsp/Cargo.toml +16 -0
- data/rust/lsp/README.md +133 -0
- data/rust/lsp/src/diagnostics.rs +165 -0
- data/rust/lsp/src/json.rs +385 -0
- data/rust/lsp/src/main.rs +399 -0
- data/rust/lsp/src/outline.rs +177 -0
- data/rust/lsp/src/rpc.rs +67 -0
- data/rust/parser/.cargo/config.toml +5 -0
- data/rust/parser/.gitignore +1 -0
- data/rust/parser/Cargo.lock +7 -0
- data/rust/parser/Cargo.toml +21 -0
- data/rust/parser/src/build/closed_sets.rs +27 -0
- data/rust/parser/src/build/identity.rs +276 -0
- data/rust/parser/src/build/mod.rs +12 -0
- data/rust/parser/src/build/naming.rs +141 -0
- data/rust/parser/src/build/pattern_subset.rs +161 -0
- data/rust/parser/src/build/query_derive.rs +72 -0
- data/rust/parser/src/build/query_inference.rs +269 -0
- data/rust/parser/src/build/query_options.rs +100 -0
- data/rust/parser/src/build/read_model.rs +94 -0
- data/rust/parser/src/build/references.rs +32 -0
- data/rust/parser/src/canonical.rs +114 -0
- data/rust/parser/src/diag.rs +55 -0
- data/rust/parser/src/emit.rs +953 -0
- data/rust/parser/src/expr/ast_json.rs +147 -0
- data/rust/parser/src/expr/evaluator.rs +81 -0
- data/rust/parser/src/expr/mod.rs +84 -0
- data/rust/parser/src/expr/resolver.rs +497 -0
- data/rust/parser/src/ir.rs +387 -0
- data/rust/parser/src/keywords.rs +585 -0
- data/rust/parser/src/lex.rs +662 -0
- data/rust/parser/src/main.rs +252 -0
- data/rust/parser/src/parse/aggregate.rs +612 -0
- data/rust/parser/src/parse/chapter.rs +517 -0
- data/rust/parser/src/parse/command.rs +637 -0
- data/rust/parser/src/parse/domain_port.rs +127 -0
- data/rust/parser/src/parse/entity.rs +527 -0
- data/rust/parser/src/parse/file.rs +67 -0
- data/rust/parser/src/parse/hecksagon.rs +231 -0
- data/rust/parser/src/parse/lifecycle.rs +162 -0
- data/rust/parser/src/parse/mod.rs +1262 -0
- data/rust/parser/src/parse/policy.rs +87 -0
- data/rust/parser/src/parse/process_manager.rs +305 -0
- data/rust/parser/src/parse/query.rs +100 -0
- data/rust/parser/src/parse/read_model.rs +214 -0
- data/rust/parser/src/parse/value_object.rs +250 -0
- data/rust/parser/src/ruby_value.rs +422 -0
- data/rust/project/bridging.rb +257 -0
- data/rust/project/commands.rb +877 -0
- data/rust/project/constraints.rb +86 -0
- data/rust/project/dependency_planning.rb +178 -0
- data/rust/project/domain_generator.rb +980 -0
- data/rust/project/exemplar.rb +148 -0
- data/rust/project/expr_emitter.rb +72 -0
- data/rust/project/fielded.rb +141 -0
- data/rust/project/json_codec.rb +641 -0
- data/rust/project/mutations.rb +721 -0
- data/rust/project/naming.rb +205 -0
- data/rust/project/ports.rb +65 -0
- data/rust/project/queries.rb +493 -0
- data/rust/project/reactions.rb +317 -0
- data/rust/project/read_models.rb +690 -0
- data/rust/project/reference_specs.rb +27 -0
- data/rust/project/registry.rb +396 -0
- data/rust/project/skip_reason.rb +29 -0
- data/rust/project/types.rb +225 -0
- data/rust/project/write_if_changed.rb +83 -0
- data/rust/project.rb +30 -0
- data/rust/project_rust_pipeline.rb +383 -0
- data/rust/src/exemplar/commands.rs +279 -0
- data/rust/src/exemplar/constraints.rs +18 -0
- data/rust/src/exemplar/fielded.rs +280 -0
- data/rust/src/exemplar/json.rs +277 -0
- data/rust/src/exemplar/mod.rs +17 -0
- data/rust/src/exemplar/mutations.rs +80 -0
- data/rust/src/exemplar/queries.rs +33 -0
- data/rust/src/exemplar/reactions.rs +88 -0
- data/rust/src/exemplar/read_models.rs +48 -0
- data/rust/src/exemplar/registry.rs +166 -0
- data/rust/src/exemplar/types.rs +44 -0
- data/rust/src/kernel/attribute_shapes/composite.rs +88 -0
- data/rust/src/kernel/attribute_shapes/list.rs +18 -0
- data/rust/src/kernel/attribute_shapes/mod.rs +44 -0
- data/rust/src/kernel/attribute_shapes/optional.rs +9 -0
- data/rust/src/kernel/attribute_shapes/scalar.rs +39 -0
- data/rust/src/kernel/cli.rs +585 -0
- data/rust/src/kernel/dispatch.rs +1090 -0
- data/rust/src/kernel/expr.rs +458 -0
- data/rust/src/kernel/expression_operators/arithmetic.rs +128 -0
- data/rust/src/kernel/expression_operators/comparison.rs +48 -0
- data/rust/src/kernel/expression_operators/enumeration.rs +232 -0
- data/rust/src/kernel/expression_operators/logical.rs +13 -0
- data/rust/src/kernel/expression_operators/membership.rs +18 -0
- data/rust/src/kernel/expression_operators/mod.rs +68 -0
- data/rust/src/kernel/expression_operators/pattern_match.rs +73 -0
- data/rust/src/kernel/expression_operators/positional.rs +126 -0
- data/rust/src/kernel/expression_operators/presence.rs +101 -0
- data/rust/src/kernel/expression_operators/sign_test.rs +16 -0
- data/rust/src/kernel/expression_operators/sized.rs +35 -0
- data/rust/src/kernel/expression_operators/text.rs +79 -0
- data/rust/src/kernel/expression_operators/to_string.rs +18 -0
- data/rust/src/kernel/json.rs +779 -0
- data/rust/src/kernel/mod.rs +133 -0
- data/rust/src/kernel/named_query.rs +219 -0
- data/rust/src/kernel/orchestrate.rs +1810 -0
- data/rust/src/kernel/pattern.rs +307 -0
- data/rust/src/kernel/query_comparators.rs +206 -0
- data/rust/src/kernel/query_ordering.rs +103 -0
- data/rust/src/kernel/read_model.rs +914 -0
- data/rust/src/kernel/reference_lookup.rs +149 -0
- data/rust/src/kernel/repository.rs +468 -0
- data/rust/src/kernel/routing.rs +612 -0
- data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +118 -0
- data/rust/src/kernel/vocab/entity_dispatch_order.rs +114 -0
- data/rust/src/kernel/vocab/field_hint.rs +70 -0
- data/rust/src/kernel/vocab/mod.rs +20 -0
- data/rust/src/kernel/vocab/query_comparator.rs +65 -0
- data/rust/src/kernel/vocab/refusal_template.rs +1592 -0
- data/rust/src/lib.rs +8 -0
- data/rust/src/main.rs +16 -0
- data/rust/web/.gitignore +1 -0
- data/rust/web/Cargo.lock +119 -0
- data/rust/web/Cargo.toml +18 -0
- data/rust/web/src/lib.rs +8 -0
- metadata +477 -12
- data/lib/hecks/facade/cli_door.rb +0 -96
- data/lib/hecks/facade/cli_runner.rb +0 -127
- data/lib/hecks/mcp_door_scope.rb +0 -95
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Binds the `Agent` port to `Agent`; `hecks quality_control mine_combinations` asks it to write candidate domains.
|
|
2
|
+
# Required by name, not relative, so a copy of this directory shares the one implementation loaded.
|
|
3
|
+
require "hecks/quality_control/adapters/agent"
|
|
4
|
+
|
|
5
|
+
Hecks.adapter "Agent" do
|
|
6
|
+
port "Agent"
|
|
7
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
require "shellwords"
|
|
5
|
+
|
|
6
|
+
module Hecks
|
|
7
|
+
module Adapters
|
|
8
|
+
# The `Agent` port's adapter: hands a prompt to an agent on the command line and lets it work
|
|
9
|
+
# in the checkout, the way `hecks quality_control mine_combinations` asks one for candidate
|
|
10
|
+
# domains.
|
|
11
|
+
#
|
|
12
|
+
# Not the interviewer behind the framework's `agent` port (`ClaudeCode`), which reads one
|
|
13
|
+
# JSON answer back: this agent edits files, so the only answer is whether it finished.
|
|
14
|
+
class Agent
|
|
15
|
+
# An agent that could not start, or ended with a failure.
|
|
16
|
+
class Failed < StandardError; end
|
|
17
|
+
|
|
18
|
+
# The command run when neither the caller nor `QA_MINER_AGENT` names one.
|
|
19
|
+
DEFAULT_COMMAND = ["claude", "-p", "--permission-mode", "acceptEdits",
|
|
20
|
+
"--allowedTools", "Read,Glob,Grep,Write,Edit"].freeze
|
|
21
|
+
|
|
22
|
+
# @param aggregate [Object, nil] unused
|
|
23
|
+
# @param settings [Hash] unused
|
|
24
|
+
# @param root [String, nil] unused
|
|
25
|
+
def initialize(aggregate: nil, settings: {}, root: nil); end
|
|
26
|
+
|
|
27
|
+
# The command an ask runs.
|
|
28
|
+
#
|
|
29
|
+
# @param command [String, Array<String>, nil] a command line, or its words
|
|
30
|
+
# @return [Array<String>] `command` as words; `QA_MINER_AGENT` else `DEFAULT_COMMAND` when nil
|
|
31
|
+
def command_for(command = nil)
|
|
32
|
+
command ||= ENV.fetch("QA_MINER_AGENT", nil)
|
|
33
|
+
return DEFAULT_COMMAND unless command
|
|
34
|
+
|
|
35
|
+
command.is_a?(String) ? Shellwords.split(command) : command
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Runs the agent with `prompt` on its standard input.
|
|
39
|
+
#
|
|
40
|
+
# @param prompt [String] what the agent is asked to do
|
|
41
|
+
# @param command [String, Array<String>, nil] the agent's command; see `command_for`
|
|
42
|
+
# @param chdir [String] the directory the agent works in
|
|
43
|
+
# @param log [String, nil] a file the agent's output is appended to
|
|
44
|
+
# @return [String] the agent's output
|
|
45
|
+
# @raise [Failed] when the agent cannot start or exits non-zero
|
|
46
|
+
def ask(prompt:, chdir:, command: nil, log: nil)
|
|
47
|
+
words = command_for(command)
|
|
48
|
+
output, status = Open3.capture2e(*words, stdin_data: prompt, chdir: chdir)
|
|
49
|
+
File.open(log, "a") { |file| file.puts(output) } if log
|
|
50
|
+
return output if status.success?
|
|
51
|
+
|
|
52
|
+
raise Failed, "agent exited #{status.exitstatus}: #{output.lines.last(5).join.strip}"
|
|
53
|
+
rescue SystemCallError => e
|
|
54
|
+
raise Failed, "agent could not start (#{words.first}): #{e.message}"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Binds the `AngleTools` port to `AngleTools`, which answers `Angle`'s query: the seed of the backlog.
|
|
2
|
+
# Required by name, not relative, so a copy of this directory shares the one implementation loaded.
|
|
3
|
+
require "hecks/quality_control/adapters/angle_tools"
|
|
4
|
+
|
|
5
|
+
Hecks.adapter "AngleTools" do
|
|
6
|
+
port "AngleTools"
|
|
7
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "qa_tool"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Adapters
|
|
7
|
+
# The `AngleTools` port's adapter: answers `Angle`'s query by running the seed of the backlog.
|
|
8
|
+
class AngleTools < QaTool
|
|
9
|
+
# @return [Hash] `text:` each starting lead's outcome: proposed, or already on file
|
|
10
|
+
def seed
|
|
11
|
+
run_command("qa_seed_angles")
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module QA
|
|
5
|
+
# Records what CI said about one commit as a `QualityControl::Clearance`.
|
|
6
|
+
module ClearanceRecorder
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# Conclusions that count as green; same vocabulary as `GithubChecks::PASSING`.
|
|
10
|
+
# A `check_suite` conclusion aggregates one GitHub App's runs only, so callers decide
|
|
11
|
+
# which suites may pass a commit.
|
|
12
|
+
PASSING = %w[success neutral skipped].freeze
|
|
13
|
+
|
|
14
|
+
# Returns the Clearance for `commit`, starting one if none exists.
|
|
15
|
+
# `AlreadyExists` from a redelivered webhook racing another start is swallowed.
|
|
16
|
+
def ensure_started(commit)
|
|
17
|
+
QualityControl::Clearance.find(commit) ||
|
|
18
|
+
QualityControl::Clearance.start!(commit: { value: commit })
|
|
19
|
+
rescue Hecks::Runtime::AlreadyExists
|
|
20
|
+
QualityControl::Clearance.find(commit)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Starts and settles the Clearance for `commit`; the first verdict recorded stands.
|
|
24
|
+
# An already-settled record is returned unchanged, so redelivery cannot raise
|
|
25
|
+
# `LifecycleRefused`.
|
|
26
|
+
#
|
|
27
|
+
# @return the settled Clearance
|
|
28
|
+
def record(commit:, passed:, summary:)
|
|
29
|
+
cleared = ensure_started(commit)
|
|
30
|
+
return cleared if cleared.status != "running"
|
|
31
|
+
|
|
32
|
+
passed ? cleared.passed!(summary: { value: summary }) : cleared.failed!(refusal: { value: summary })
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Binds the `ClearanceTools` port to `ClearanceTools`, which answers `Clearance`'s query: the PR check.
|
|
2
|
+
# Required by name, not relative, so a copy of this directory shares the one implementation loaded.
|
|
3
|
+
require "hecks/quality_control/adapters/clearance_tools"
|
|
4
|
+
|
|
5
|
+
Hecks.adapter "ClearanceTools" do
|
|
6
|
+
port "ClearanceTools"
|
|
7
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "qa_tool"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Adapters
|
|
7
|
+
# The `ClearanceTools` port's adapter: answers `Clearance`'s query by running the PR check. A
|
|
8
|
+
# newly red PR ends the check with status 2, which is an answer; anything else it calls an
|
|
9
|
+
# error is refused with its report.
|
|
10
|
+
class ClearanceTools < QaTool
|
|
11
|
+
# @return [Hash] `text:` what the check found for each open PR the ledger tracks
|
|
12
|
+
def check_pull_requests
|
|
13
|
+
run_command("qa_pr_check", answers: [0, 2])
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Binds the `GitPr` port to `GitPr`; the ledger's scripts ask it for every `git` and `gh` call.
|
|
2
|
+
# Required by name, not relative, so a copy of this directory shares the one implementation loaded.
|
|
3
|
+
require "hecks/quality_control/adapters/git_pr"
|
|
4
|
+
|
|
5
|
+
Hecks.adapter "GitPr" do
|
|
6
|
+
port "GitPr"
|
|
7
|
+
end
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module Hecks
|
|
7
|
+
module Adapters
|
|
8
|
+
# The `GitPr` port's adapter: the `git` and `gh` calls behind a pull request the QA practice
|
|
9
|
+
# opens or checks, and the two rules that need the outside world to answer.
|
|
10
|
+
#
|
|
11
|
+
# The rules a ledger record can answer (the branch prefix, the bug being fixed, the angle
|
|
12
|
+
# being under investigation) are `given`s on `Patch.Open` and `Improvement.Open`. The two
|
|
13
|
+
# here need the repository or a count of today's pull requests: the per-day cap and the fix
|
|
14
|
+
# commit being an ancestor of `HEAD`.
|
|
15
|
+
class GitPr
|
|
16
|
+
# A rule the adapter enforces refused the request; nothing was opened.
|
|
17
|
+
class Refusal < StandardError; end
|
|
18
|
+
|
|
19
|
+
# `git` or `gh` could not do what was asked, whatever the request.
|
|
20
|
+
class CommandFailed < StandardError; end
|
|
21
|
+
|
|
22
|
+
# A commit sha as it may be handed to `git` as a revision.
|
|
23
|
+
SHA_PATTERN = /\A[0-9a-fA-F]{7,40}\z/
|
|
24
|
+
|
|
25
|
+
# The remote a branch is pushed to and `gh pr create --head` reads it from.
|
|
26
|
+
REMOTE = "origin"
|
|
27
|
+
|
|
28
|
+
# What `gh pr view` says when the branch simply has no pull request.
|
|
29
|
+
NO_PR = /no pull requests? found/i
|
|
30
|
+
|
|
31
|
+
# The fields `gh pr view` is asked for when a PR is opened or looked up by branch.
|
|
32
|
+
VIEW_FIELDS = "number,url,headRefName,headRefOid,title,state"
|
|
33
|
+
|
|
34
|
+
# @param aggregate [Object, nil] unused
|
|
35
|
+
# @param settings [Hash] unused
|
|
36
|
+
# @param root [String, nil] unused
|
|
37
|
+
# @param repo_dir [String] the checkout `git` and `gh` run in; `QA_REPO_DIR` picks one so a
|
|
38
|
+
# spec can use a throwaway repository and a fake `gh`
|
|
39
|
+
def initialize(aggregate: nil, settings: {}, root: nil, repo_dir: ENV.fetch("QA_REPO_DIR", Dir.pwd))
|
|
40
|
+
@repo_dir = repo_dir
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @return [String] the checkout's current branch
|
|
44
|
+
# @raise [Refusal] when the directory is not a git checkout
|
|
45
|
+
def branch
|
|
46
|
+
out, _err, status = git("rev-parse", "--abbrev-ref", "HEAD")
|
|
47
|
+
raise Refusal, "not a git checkout at #{@repo_dir}" unless status.success?
|
|
48
|
+
|
|
49
|
+
out
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @return [String] the commit `HEAD` names
|
|
53
|
+
def head = git("rev-parse", "HEAD").first
|
|
54
|
+
|
|
55
|
+
# Refuses a checkout with uncommitted work, since a PR opened from it would not carry it.
|
|
56
|
+
#
|
|
57
|
+
# @return [void]
|
|
58
|
+
# @raise [Refusal] when `git status` cannot be read or reports changes
|
|
59
|
+
def assert_clean_tree!
|
|
60
|
+
dirty, _err, status = git("status", "--porcelain")
|
|
61
|
+
raise Refusal, "could not read git status at #{@repo_dir}" unless status.success?
|
|
62
|
+
return if dirty.empty?
|
|
63
|
+
|
|
64
|
+
raise Refusal, "the working tree at #{@repo_dir} is dirty — commit or discard first:\n#{dirty}"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Refuses one more pull request than the day's cap allows; a cap of zero is no cap.
|
|
68
|
+
#
|
|
69
|
+
# @param opened_today [Integer] how many PRs the ledger recorded since local midnight
|
|
70
|
+
# @param cap [Integer] `PR_CAP_PER_DAY`
|
|
71
|
+
# @return [void]
|
|
72
|
+
# @raise [Refusal] when `opened_today` has reached a positive `cap`
|
|
73
|
+
def assert_under_daily_cap!(opened_today:, cap:)
|
|
74
|
+
return unless cap.positive? && opened_today >= cap
|
|
75
|
+
|
|
76
|
+
raise Refusal, "#{opened_today} PR(s) already opened since local midnight, and " \
|
|
77
|
+
"QualityControlDials::PR_CAP_PER_DAY is #{cap} — leave this as an open Bug/Angle " \
|
|
78
|
+
"and open it tomorrow, or raise the dial in qa/settings.yml"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Refuses a fix commit that is not part of the branch being opened.
|
|
82
|
+
#
|
|
83
|
+
# @param commit [String] the commit the ledger says fixes the bug
|
|
84
|
+
# @param owner [String] what the commit belongs to, worded into the refusal (`"BUG#1"`)
|
|
85
|
+
# @return [void]
|
|
86
|
+
# @raise [Refusal] when `commit` is not a sha or not an ancestor of `HEAD`
|
|
87
|
+
def assert_ancestor!(commit:, owner:)
|
|
88
|
+
assert_sha!(commit, owner)
|
|
89
|
+
_out, _err, status = git("merge-base", "--is-ancestor", "--", commit, "HEAD")
|
|
90
|
+
return if status.success?
|
|
91
|
+
|
|
92
|
+
raise Refusal, "#{owner}'s own fix commit #{commit[0, 7]} is not an ancestor of HEAD " \
|
|
93
|
+
"(#{head[0, 7]}) on #{branch} — the PR must carry the commit the ledger says fixes it"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Refuses a fix commit that the pushed branch does not carry: `gh pr create --head` opens
|
|
97
|
+
# the pull request from the remote branch, not from the local checkout.
|
|
98
|
+
#
|
|
99
|
+
# @param branch [String] the head branch
|
|
100
|
+
# @param commit [String] the commit the ledger says fixes the bug
|
|
101
|
+
# @param owner [String] what the commit belongs to, worded into the refusal
|
|
102
|
+
# @return [void]
|
|
103
|
+
# @raise [Refusal] when the branch is not on the remote, or its tip does not carry `commit`
|
|
104
|
+
def assert_pushed!(branch:, commit:, owner:)
|
|
105
|
+
assert_sha!(commit, owner)
|
|
106
|
+
out, err, status = git("ls-remote", "--heads", REMOTE, "refs/heads/#{branch}")
|
|
107
|
+
raise Refusal, "could not read #{REMOTE} for #{branch}: #{err}" unless status.success?
|
|
108
|
+
|
|
109
|
+
tip = out.split.first.to_s
|
|
110
|
+
raise Refusal, "#{branch} is not pushed to #{REMOTE} — push it before opening the PR" if tip.empty?
|
|
111
|
+
|
|
112
|
+
assert_carries!(tip, commit, owner, "the pushed #{REMOTE}/#{branch}")
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Refuses a pull request whose head does not carry the fix commit.
|
|
116
|
+
#
|
|
117
|
+
# @param head [String] the PR's head commit, as `gh` reports it
|
|
118
|
+
# @param commit [String] the commit the ledger says fixes the bug
|
|
119
|
+
# @param owner [String] what the commit belongs to, worded into the refusal
|
|
120
|
+
# @return [void]
|
|
121
|
+
# @raise [Refusal] when `head` is not a sha, or does not carry `commit`
|
|
122
|
+
def assert_pr_head!(head:, commit:, owner:)
|
|
123
|
+
assert_sha!(commit, owner)
|
|
124
|
+
assert_sha!(head, "the PR head")
|
|
125
|
+
assert_carries!(head, commit, owner, "the PR head")
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# The open PR for a branch.
|
|
129
|
+
#
|
|
130
|
+
# @param branch [String] the head branch
|
|
131
|
+
# @return [Hash{Symbol => Object}, nil] `gh pr view`'s fields, or nil when no PR is open
|
|
132
|
+
# @raise [CommandFailed] when `gh` failed for any reason but the branch having no PR
|
|
133
|
+
def open_pull_request(branch)
|
|
134
|
+
out, err, status = gh("pr", "view", branch, "--json", VIEW_FIELDS)
|
|
135
|
+
unless status.success?
|
|
136
|
+
return nil if err.match?(NO_PR)
|
|
137
|
+
|
|
138
|
+
raise CommandFailed, "gh pr view #{branch} failed — #{err.empty? ? out : err}"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
view = JSON.parse(out, symbolize_names: true)
|
|
142
|
+
view[:state] == "OPEN" ? view : nil
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Opens a PR from `branch`.
|
|
146
|
+
#
|
|
147
|
+
# @param branch [String] the head branch
|
|
148
|
+
# @param title [String] the PR's title
|
|
149
|
+
# @param body [String] the PR's description
|
|
150
|
+
# @param draft [Boolean] open it as a draft
|
|
151
|
+
# @return [String] what `gh pr create` printed, the PR's URL
|
|
152
|
+
# @raise [CommandFailed] when `gh` refuses
|
|
153
|
+
def create_pull_request(branch:, title:, body:, draft: false)
|
|
154
|
+
args = ["pr", "create", "--head", branch, "--title", title, "--body", body]
|
|
155
|
+
args << "--draft" if draft
|
|
156
|
+
out, err, status = gh(*args)
|
|
157
|
+
raise CommandFailed, "gh pr create failed — #{err.empty? ? out : err}" unless status.success?
|
|
158
|
+
|
|
159
|
+
out
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Queues a squash merge for when the PR's checks pass.
|
|
163
|
+
#
|
|
164
|
+
# @param number [Integer] the PR's number
|
|
165
|
+
# @return [void]
|
|
166
|
+
# @raise [CommandFailed] when `gh` refuses
|
|
167
|
+
def merge_when_green(number)
|
|
168
|
+
_out, err, status = gh("pr", "merge", number.to_s, "--auto", "--squash")
|
|
169
|
+
raise CommandFailed, "gh pr merge --auto failed — #{err}" unless status.success?
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# A tracked PR's state and head commit, read live: a push mints a new commit to clear.
|
|
173
|
+
#
|
|
174
|
+
# @param number [Integer] the PR's number
|
|
175
|
+
# @return [Hash{Symbol => Object}] `state`, `headRefOid` and `url`
|
|
176
|
+
# @raise [CommandFailed] when `gh` cannot read it
|
|
177
|
+
def pull_request(number)
|
|
178
|
+
parse(gh("pr", "view", number.to_s, "--json", "state,headRefOid,url"), "gh pr view failed")
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# A tracked PR's checks, as `gh` reports them.
|
|
182
|
+
#
|
|
183
|
+
# @param number [Integer] the PR's number
|
|
184
|
+
# @return [Array<Hash{Symbol => Object}>] each check's `name`, `state`, `bucket`, `workflow`
|
|
185
|
+
# @raise [CommandFailed] when `gh` cannot read them
|
|
186
|
+
def checks(number)
|
|
187
|
+
parse(gh("pr", "checks", number.to_s, "--json", "name,state,bucket,workflow"), "gh pr checks failed")
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
private
|
|
191
|
+
|
|
192
|
+
def assert_sha!(value, owner)
|
|
193
|
+
return if value.to_s.match?(SHA_PATTERN)
|
|
194
|
+
|
|
195
|
+
raise Refusal, "#{owner}'s commit #{value.to_s.inspect} does not look like a sha"
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def assert_carries!(tip, commit, owner, label)
|
|
199
|
+
_out, _err, status = git("merge-base", "--is-ancestor", "--", commit, tip)
|
|
200
|
+
return if status.success?
|
|
201
|
+
|
|
202
|
+
raise Refusal, "#{owner}'s own fix commit #{commit[0, 7]} is not an ancestor of #{label} " \
|
|
203
|
+
"(#{tip[0, 7]}) — the PR must carry the commit the ledger says fixes it"
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def parse(result, failure)
|
|
207
|
+
out, err, status = result
|
|
208
|
+
raise CommandFailed, "#{failure} — #{err.strip.empty? ? out.strip : err.strip}" unless status.success?
|
|
209
|
+
|
|
210
|
+
JSON.parse(out, symbolize_names: true)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def git(*) = run_tool("git", *)
|
|
214
|
+
|
|
215
|
+
def gh(*) = run_tool("gh", *)
|
|
216
|
+
|
|
217
|
+
def run_tool(tool, *)
|
|
218
|
+
out, err, status = Open3.capture3(tool, *, chdir: @repo_dir)
|
|
219
|
+
[out.strip, err.strip, status]
|
|
220
|
+
rescue Errno::ENOENT
|
|
221
|
+
raise Refusal, "#{tool} is not installed or not on PATH (or #{@repo_dir} is missing) — " \
|
|
222
|
+
"the practice cannot open or check a pull request without it"
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Binds the `CI` port to `GithubChecks` for any boot that loads the QualityControl chapter.
|
|
2
|
+
# The implementation is required by name, not relative to this file, because the `.adapter` glob
|
|
3
|
+
# never loads the sibling `.rb` and a copy of this directory (a fuzz boot) must share the one
|
|
4
|
+
# implementation already loaded.
|
|
5
|
+
require "hecks/quality_control/adapters/github_checks"
|
|
6
|
+
|
|
7
|
+
Hecks.adapter "GithubChecks" do
|
|
8
|
+
port "CI"
|
|
9
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module Hecks
|
|
7
|
+
module Adapters
|
|
8
|
+
# The `CI` port adapter: answers `Clearance.CI.Run` from GitHub's check runs for a commit.
|
|
9
|
+
# Raises when no checks exist, any are still running, or any failed.
|
|
10
|
+
class GithubChecks
|
|
11
|
+
# All keywords are unused; the adapter takes no per-boot configuration.
|
|
12
|
+
def initialize(aggregate: nil, settings: {}, root: nil); end
|
|
13
|
+
|
|
14
|
+
# Check-run conclusions that count as green; `skipped` covers path-filtered workflows.
|
|
15
|
+
PASSING = %w[success neutral skipped].freeze
|
|
16
|
+
|
|
17
|
+
# A commit sha as GitHub accepts it in an API path.
|
|
18
|
+
SHA_PATTERN = /\A[0-9a-fA-F]{7,40}\z/
|
|
19
|
+
|
|
20
|
+
# Check runs asked for per page; GitHub's maximum.
|
|
21
|
+
PER_PAGE = 100
|
|
22
|
+
|
|
23
|
+
# Asks GitHub by commit sha, matching `Clearance`'s identity, so it works after the
|
|
24
|
+
# branch is gone. `{owner}/{repo}` is a `gh api` template resolved from the git remote.
|
|
25
|
+
#
|
|
26
|
+
# @param commit [Hash, String] a materialized value object (`{value: sha}`) or a bare sha
|
|
27
|
+
# @return [Hash] `{ summary: { value: ... } }` when every check is green
|
|
28
|
+
# @raise [RuntimeError] when the sha is malformed, `gh` is missing or fails, no checks
|
|
29
|
+
# exist, any are incomplete, or any failed
|
|
30
|
+
def run(commit:, **)
|
|
31
|
+
sha = sha_of(commit)
|
|
32
|
+
raise "not a commit sha: #{sha.inspect}" unless sha.match?(SHA_PATTERN)
|
|
33
|
+
|
|
34
|
+
runs = check_runs(sha)
|
|
35
|
+
|
|
36
|
+
raise "gh reports no checks at all against #{sha}" if runs.empty?
|
|
37
|
+
|
|
38
|
+
# Refuse rather than answer on an incomplete run; asking again once it settles is safe.
|
|
39
|
+
incomplete = runs.reject { |run| run["status"] == "completed" }
|
|
40
|
+
raise "checks against #{sha} are still running — asked before they settled" if incomplete.any?
|
|
41
|
+
|
|
42
|
+
failing = runs.reject { |run| PASSING.include?(run["conclusion"]) }
|
|
43
|
+
return { summary: { value: "#{runs.length} checks, all green (#{sha[0, 7]})" } } if failing.empty?
|
|
44
|
+
|
|
45
|
+
raise "#{failing.length} of #{runs.length} checks failed against #{sha[0, 7]}: " \
|
|
46
|
+
"#{failing.map { |run| run['name'] }.join(', ')}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
# Accepts a materialized value hash with symbol or string keys, or a bare string.
|
|
52
|
+
def sha_of(commit)
|
|
53
|
+
return commit if commit.is_a?(String)
|
|
54
|
+
|
|
55
|
+
(commit.key?(:value) ? commit[:value] : commit["value"]).to_s
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Every page of check runs: a red run past the first page must still be seen.
|
|
59
|
+
def check_runs(sha)
|
|
60
|
+
runs = []
|
|
61
|
+
page = 1
|
|
62
|
+
loop do
|
|
63
|
+
batch = check_run_page(sha, page)["check_runs"] || []
|
|
64
|
+
runs.concat(batch)
|
|
65
|
+
break if batch.length < PER_PAGE
|
|
66
|
+
|
|
67
|
+
page += 1
|
|
68
|
+
end
|
|
69
|
+
runs
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def check_run_page(sha, page)
|
|
73
|
+
path = "repos/{owner}/{repo}/commits/#{sha}/check-runs?per_page=#{PER_PAGE}&page=#{page}"
|
|
74
|
+
out, err, status = Open3.capture3("gh", "api", path)
|
|
75
|
+
raise "gh api check-runs failed for #{sha}: #{err.strip.empty? ? out.strip : err.strip}" unless status.success?
|
|
76
|
+
|
|
77
|
+
JSON.parse(out)
|
|
78
|
+
rescue Errno::ENOENT
|
|
79
|
+
raise "gh is not installed or not on PATH — cannot read check runs for #{sha}"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "hecks/adapters/driving/github_webhook"
|
|
4
|
+
require_relative "clearance_recorder"
|
|
5
|
+
|
|
6
|
+
module Hecks
|
|
7
|
+
module Adapters
|
|
8
|
+
module Driving
|
|
9
|
+
# Records GitHub's `check_suite` webhook as a `QualityControl::Clearance`.
|
|
10
|
+
# GitHub sends one `check_suite` per GitHub App for a commit, so one suite is not the
|
|
11
|
+
# commit's verdict. A passing suite clears the commit only when it belongs to `CI_APP_SLUG`
|
|
12
|
+
# (Actions, which runs the gate); a passing suite from any other app is acknowledged and
|
|
13
|
+
# ignored. A failing suite from any app holds the commit red, and the first verdict stands.
|
|
14
|
+
class GithubCiWebhook < GithubWebhook
|
|
15
|
+
SHA_PATTERN = /\A[0-9a-fA-F]{7,40}\z/
|
|
16
|
+
|
|
17
|
+
# The GitHub App whose passing suite counts as the commit being green.
|
|
18
|
+
CI_APP_SLUG = "github-actions"
|
|
19
|
+
|
|
20
|
+
def handle_event(event, action, payload)
|
|
21
|
+
return ignored("not a check_suite event (got #{event.inspect})") unless event == "check_suite"
|
|
22
|
+
# `requested`/`rerequested` mean the suite has not finished, so there is no verdict yet.
|
|
23
|
+
return ignored("check_suite not yet completed (action=#{action.inspect})") unless action == "completed"
|
|
24
|
+
|
|
25
|
+
suite = payload["check_suite"] || {}
|
|
26
|
+
sha = suite["head_sha"].to_s
|
|
27
|
+
|
|
28
|
+
unless sha.match?(SHA_PATTERN)
|
|
29
|
+
return [422, { error: "MalformedCommit", message: "check_suite.head_sha #{sha.inspect} does not look like a sha" }]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
settle(sha, suite)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def ignored(reason) = [200, { ok: true, ignored: reason }]
|
|
38
|
+
|
|
39
|
+
def settle(sha, suite)
|
|
40
|
+
conclusion = suite["conclusion"].to_s
|
|
41
|
+
passed = Hecks::QA::ClearanceRecorder::PASSING.include?(conclusion)
|
|
42
|
+
app = suite.dig("app", "slug").to_s
|
|
43
|
+
if passed && app != CI_APP_SLUG
|
|
44
|
+
return ignored("passing check_suite from app #{app.inspect}, not #{CI_APP_SLUG.inspect}")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
summary = "check_suite #{suite['id']} conclusion=#{conclusion} for #{sha[0, 7]} — via webhook"
|
|
48
|
+
|
|
49
|
+
cleared = Hecks::QA::ClearanceRecorder.record(commit: sha, passed: passed, summary: summary)
|
|
50
|
+
|
|
51
|
+
[200, { ok: true, commit: sha, status: cleared.status, summary: summary }]
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Binds the `IssueTracker` port to `GithubIssues` for any boot that loads the chapter.
|
|
2
|
+
# Required by name, not relative, so a copy of this directory shares the one implementation loaded.
|
|
3
|
+
require "hecks/quality_control/adapters/github_issues"
|
|
4
|
+
|
|
5
|
+
Hecks.adapter "GithubIssues" do
|
|
6
|
+
port "IssueTracker"
|
|
7
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Adapters
|
|
7
|
+
# The `IssueTracker` port's adapter: files a `QualityControl::Ticket` as a GitHub issue.
|
|
8
|
+
# Raises when `gh` refuses, which the runtime records as the ticket's refusal.
|
|
9
|
+
class GithubIssues
|
|
10
|
+
# @param aggregate [Object, nil] unused
|
|
11
|
+
# @param settings [Hash] unused
|
|
12
|
+
# @param root [String, nil] unused
|
|
13
|
+
def initialize(aggregate: nil, settings: {}, root: nil); end
|
|
14
|
+
|
|
15
|
+
# Files the ticket the runtime hands over (its whole held state) with `gh issue create`.
|
|
16
|
+
#
|
|
17
|
+
# @param held [Hash] the ticket's fields; `repository`, `title` and `body` are materialized
|
|
18
|
+
# value objects (`{value: ...}`) or bare strings, and `pull_request` names a proposed fix
|
|
19
|
+
# @return [Hash{Symbol => Hash}] `number:` and `url:` of the issue, in the shape the
|
|
20
|
+
# ticket's `Filed` command takes
|
|
21
|
+
# @raise [RuntimeError] when `gh` exits non-zero or prints no issue URL
|
|
22
|
+
def file(**held)
|
|
23
|
+
repository = plain(held[:repository])
|
|
24
|
+
proposed = plain(held[:pull_request]).to_s
|
|
25
|
+
body = plain(held[:body]).to_s
|
|
26
|
+
body += "\n\nA fix is proposed in #{proposed}" unless proposed.empty?
|
|
27
|
+
|
|
28
|
+
out, err, status = Open3.capture3("gh", "issue", "create", "--repo", repository,
|
|
29
|
+
"--title", plain(held[:title]).to_s, "--body", body)
|
|
30
|
+
raise "gh issue create failed — #{err.strip.empty? ? out.strip : err.strip}" unless status.success?
|
|
31
|
+
|
|
32
|
+
url = out.strip.lines.last.to_s.strip
|
|
33
|
+
number = url[%r{/issues/(\d+)\z}, 1]
|
|
34
|
+
raise "gh issue create printed no issue URL: #{out.strip.inspect}" unless number
|
|
35
|
+
|
|
36
|
+
{ number: { value: number.to_i }, url: { value: url } }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
# A materialized value object (`{value: x}`, symbol or string keys) or the value itself.
|
|
42
|
+
def plain(field)
|
|
43
|
+
return field unless field.is_a?(Hash)
|
|
44
|
+
|
|
45
|
+
field.key?(:value) ? field[:value] : field["value"]
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
require "shellwords"
|
|
5
|
+
require "hecks/runtime/errors"
|
|
6
|
+
require_relative "../cli/child"
|
|
7
|
+
|
|
8
|
+
module Hecks
|
|
9
|
+
module Adapters
|
|
10
|
+
# The base of the adapters that answer the QA scripts' queries (`SweepTools`,
|
|
11
|
+
# `ClearanceTools`, `AngleTools`, `TargetTools`): each starts one command of
|
|
12
|
+
# `Hecks::QualityControlCli` as its own OS process and answers what it printed.
|
|
13
|
+
#
|
|
14
|
+
# A child, not this process: the commands boot the ledger, fork, spawn and open Postgres
|
|
15
|
+
# connections of their own, and a crash or an `exit` in one must end only that run. The answer
|
|
16
|
+
# is the command's output, unchanged, so the launcher shows the bytes the script prints.
|
|
17
|
+
class QaTool
|
|
18
|
+
# A command ended in an error. It is a refusal, since the launcher and the ledger's policies
|
|
19
|
+
# treat it as one, but not a `given`'s: no description of the command's guards quotes it, so
|
|
20
|
+
# the fuzzer's guard checks must not read it as one.
|
|
21
|
+
class ToolRefused < Runtime::GivenNotMet; end
|
|
22
|
+
|
|
23
|
+
# @param aggregate [Object, nil] unused
|
|
24
|
+
# @param settings [Hash] unused
|
|
25
|
+
# @param root [String, nil] the checkout the commands run in; this repository when nil
|
|
26
|
+
def initialize(aggregate: nil, settings: {}, root: nil)
|
|
27
|
+
@root = root || File.expand_path("../../../..", __dir__)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
# Runs one command and answers its output.
|
|
33
|
+
#
|
|
34
|
+
# @param command [String] a key of `QualityControlCli::Child::COMMANDS`
|
|
35
|
+
# @param args [Array<String>] the command's own arguments
|
|
36
|
+
# @param answers [Array<Integer>] the exit statuses that are an answer: a finding, or a
|
|
37
|
+
# judgment, is one, an operational error is not
|
|
38
|
+
# @return [Hash] `text:` what the command printed, stdout and stderr in
|
|
39
|
+
# order
|
|
40
|
+
# @raise [ToolRefused] with what the command printed, when it ended with another
|
|
41
|
+
# status
|
|
42
|
+
def run_command(command, *, answers: [0])
|
|
43
|
+
output, status = Open3.capture2e(*QualityControlCli::Child.argv(@root, command, *), chdir: @root)
|
|
44
|
+
return { text: output } if answers.include?(status.exitstatus)
|
|
45
|
+
|
|
46
|
+
raise ToolRefused, "#{output.strip}\n(#{command} ended with status #{status.exitstatus.inspect})"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @param value [Hash, String, nil] a value object's materialized `{ value: "..." }`, or plain
|
|
50
|
+
# text
|
|
51
|
+
# @return [String, nil] the text, nil when absent
|
|
52
|
+
def plain(value) = value.is_a?(Hash) ? value[:value] : value
|
|
53
|
+
|
|
54
|
+
# @param value [Hash, String, nil] words in the command's own flag syntax
|
|
55
|
+
# @return [Array<String>] the words, split as a shell would
|
|
56
|
+
def words(value) = Shellwords.split(plain(value).to_s)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Binds the `SweepTools` port to `SweepTools`, which answers `Sweep`'s queries: a tick, a sweep, the Heki migration, the ledger's role and a racer.
|
|
2
|
+
# Required by name, not relative, so a copy of this directory shares the one implementation loaded.
|
|
3
|
+
require "hecks/quality_control/adapters/sweep_tools"
|
|
4
|
+
|
|
5
|
+
Hecks.adapter "SweepTools" do
|
|
6
|
+
port "SweepTools"
|
|
7
|
+
end
|