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
data/lib/hecks/cli.rb
CHANGED
|
@@ -1,61 +1,116 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
# The `hecks` command an installed gem puts on the path: one router over the
|
|
3
|
-
# domain-operator tools, each also runnable from a checkout as `
|
|
3
|
+
# domain-operator tools, each also runnable from a checkout as `exe/hecks <name>`.
|
|
4
4
|
#
|
|
5
|
-
# Every subcommand's logic lives under `cli/`,
|
|
6
|
-
# over the same entry point, so the two can't drift apart.
|
|
5
|
+
# Every subcommand's logic lives under `cli/`, and `exe/hecks` routes to it.
|
|
7
6
|
#
|
|
8
7
|
# See `docs/decisions/0066-the-gem-ships-a-hecks-executable-and-dev-tooling-stays-in-the-repo.md`.
|
|
9
8
|
module CLI
|
|
10
|
-
# One subcommand
|
|
11
|
-
|
|
9
|
+
# One subcommand: a one-line summary, its usage line, the file that holds it and the
|
|
10
|
+
# call that runs it (given the argv after the name, its program name and its own name).
|
|
11
|
+
Command = Struct.new(:summary, :usage, :file, :run)
|
|
12
12
|
|
|
13
13
|
# Every subcommand, in the order the help lists them.
|
|
14
14
|
COMMANDS = {
|
|
15
15
|
"run" => Command.new(
|
|
16
16
|
"Dispatch a verb, or run a JSON step list, against a domain.",
|
|
17
|
-
"hecks run [domain] <verb [name=value …] | script.json | - | '{\"steps\":[…]}'>"
|
|
17
|
+
"hecks run [domain] <verb [name=value …] | script.json | - | '{\"steps\":[…]}'>",
|
|
18
|
+
"cli/run",
|
|
19
|
+
->(argv, program, _name) { Run.call(argv, program: program) }
|
|
18
20
|
),
|
|
19
21
|
"docs" => Command.new(
|
|
20
22
|
"Print a domain's usage document, projected from its bluebook.",
|
|
21
|
-
"hecks docs [domain-path] [aggregate]"
|
|
23
|
+
"hecks docs [domain-path] [aggregate]",
|
|
24
|
+
"cli/document",
|
|
25
|
+
->(argv, program, name) { Document.call(argv, projection: name.to_sym, program: program, root: Dir.pwd) }
|
|
22
26
|
),
|
|
23
27
|
"narrate" => Command.new(
|
|
24
28
|
"Print a domain read back in English, projected from its bluebook.",
|
|
25
|
-
"hecks narrate [domain-path] [aggregate]"
|
|
29
|
+
"hecks narrate [domain-path] [aggregate]",
|
|
30
|
+
"cli/document",
|
|
31
|
+
->(argv, program, name) { Document.call(argv, projection: name.to_sym, program: program, root: Dir.pwd) }
|
|
26
32
|
),
|
|
27
33
|
"ir" => Command.new(
|
|
28
34
|
"Print a booted domain's IR as JSON.",
|
|
29
|
-
"hecks ir <domain> [--translations] | hecks ir --meta"
|
|
35
|
+
"hecks ir <domain> [--translations] | hecks ir --meta",
|
|
36
|
+
"cli/ir",
|
|
37
|
+
->(argv, program, _name) { Ir.call(argv, program: program) }
|
|
30
38
|
),
|
|
31
39
|
"stores" => Command.new(
|
|
32
40
|
"Print every aggregate's current records as JSON.",
|
|
33
|
-
"hecks stores <domain>"
|
|
41
|
+
"hecks stores <domain>",
|
|
42
|
+
"cli/stores",
|
|
43
|
+
->(argv, program, _name) { Stores.call(argv, program: program) }
|
|
34
44
|
),
|
|
35
45
|
"model_check" => Command.new(
|
|
36
46
|
"Statically check a domain's IR for dead states and unreachable steps.",
|
|
37
|
-
"hecks model_check [--strict] [--profile client]
|
|
47
|
+
"hecks model_check [--strict] [--profile client] [--wait] [<domain> …]",
|
|
48
|
+
"cli/model_check",
|
|
49
|
+
->(argv, program, _name) { ModelCheck.call(argv, program: program, root: checkout_root) }
|
|
38
50
|
),
|
|
39
51
|
"smoke_test" => Command.new(
|
|
40
52
|
"Boot a domain and dispatch every declared command and report once.",
|
|
41
|
-
"hecks smoke_test [domain]"
|
|
53
|
+
"hecks smoke_test [domain]",
|
|
54
|
+
"cli/smoke_test",
|
|
55
|
+
->(argv, _program, _name) { SmokeTest.call(argv, root: Dir.pwd) }
|
|
42
56
|
),
|
|
43
57
|
"project_diagrams" => Command.new(
|
|
44
|
-
"Write a domain's Mermaid diagrams under ./docs/generated/diagrams
|
|
45
|
-
"hecks project_diagrams <domain-path> <ChapterName>"
|
|
58
|
+
"Write a domain's Mermaid diagrams under ./docs/generated/diagrams/ (positional form only).",
|
|
59
|
+
"hecks project_diagrams <domain-path> <ChapterName> (writes) | " \
|
|
60
|
+
"hecks project_diagrams domain=<path> chapter=<Name> (prints; writes nothing)",
|
|
61
|
+
"cli/project_diagrams",
|
|
62
|
+
->(argv, program, _name) { ProjectDiagrams.call(argv, program: program, root: Dir.pwd) }
|
|
46
63
|
),
|
|
47
64
|
"project_cli" => Command.new(
|
|
48
65
|
"Write a command-line launcher beside each domain, named after its bluebook.",
|
|
49
|
-
"hecks project_cli [domain-path …]"
|
|
66
|
+
"hecks project_cli [domain-path …]",
|
|
67
|
+
"cli/project_cli",
|
|
68
|
+
->(argv, program, _name) { ProjectCli.call(argv, program: program, root: Dir.pwd, remove_stale_bin: false) }
|
|
50
69
|
),
|
|
51
70
|
"mcp" => Command.new(
|
|
52
71
|
"Serve the MCP door over stdio (no authentication; stdio only).",
|
|
53
|
-
"hecks mcp [--stdio]"
|
|
72
|
+
"hecks mcp [--stdio]",
|
|
73
|
+
"cli/mcp",
|
|
74
|
+
->(argv, _program, _name) { Mcp.call(argv) }
|
|
54
75
|
)
|
|
55
76
|
}.freeze
|
|
56
77
|
|
|
57
78
|
HELP_FLAGS = ["--help", "-h"].freeze
|
|
58
79
|
|
|
80
|
+
# The launcher's flags that mean the same for every subcommand here: `--wait` (a subcommand
|
|
81
|
+
# exits with its own verdict, so waiting changes nothing) and `--confirm` (nothing here asks
|
|
82
|
+
# for one). Each may carry a Boolean word, as at the launcher.
|
|
83
|
+
GENERIC_FLAGS = ["--wait", "--confirm"].freeze
|
|
84
|
+
|
|
85
|
+
# A launcher word: `name=value`, where the name has no path characters.
|
|
86
|
+
NAME_VALUE = /\A[A-Za-z_][\w-]*=/
|
|
87
|
+
|
|
88
|
+
# The launcher's `name=value` spellings of a subcommand's positional arguments.
|
|
89
|
+
#
|
|
90
|
+
# `slots` lists, in positional order, the names that fill each place (`domain` before
|
|
91
|
+
# `aggregate`); `many` lets the first slot repeat and take a comma list; `exists` makes
|
|
92
|
+
# the first slot's values paths that must exist; `flags` and `options` are `--name`
|
|
93
|
+
# switches and `--name value` pairs; `ignored` names a launcher argument this form has no
|
|
94
|
+
# use for. A subcommand absent here (`run`, whose pairs are the verb's own, `mcp`, and
|
|
95
|
+
# `project_diagrams`, whose launcher form answers instead of writing) keeps its words.
|
|
96
|
+
LAUNCHER_FORMS = {
|
|
97
|
+
"docs" => { slots: [%w[domain], %w[aggregate]], exists: true },
|
|
98
|
+
"narrate" => { slots: [%w[domain], %w[aggregate]], exists: true },
|
|
99
|
+
"ir" => { slots: [%w[domain]], flags: %w[translations meta], exists: true },
|
|
100
|
+
"stores" => { slots: [%w[domain]], exists: true },
|
|
101
|
+
"model_check" => { slots: [%w[domains domain]], many: true, flags: %w[strict],
|
|
102
|
+
options: %w[profile], ignored: %w[run] },
|
|
103
|
+
"smoke_test" => { slots: [%w[subject domain]], ignored: %w[run] },
|
|
104
|
+
"project_cli" => { slots: [%w[domain]], many: true }
|
|
105
|
+
}.freeze
|
|
106
|
+
|
|
107
|
+
# Subcommands whose words are their own: `run`'s pairs and `mcp`'s flags are not the launcher's.
|
|
108
|
+
UNTOUCHED = %w[run mcp].freeze
|
|
109
|
+
|
|
110
|
+
# Subcommands whose `name=value` form is the launcher's own question, which answers
|
|
111
|
+
# differently from the positional form: `project_diagrams` writes files, the question prints.
|
|
112
|
+
LAUNCHER_ANSWERS = ["project_diagrams"].freeze
|
|
113
|
+
|
|
59
114
|
# Printed above the usage line by `overview`, on every path that reaches it
|
|
60
115
|
# (a bare `hecks`, `hecks --help`, and an unknown subcommand alike).
|
|
61
116
|
HERO = <<~BANNER.freeze
|
|
@@ -73,6 +128,15 @@ module Hecks
|
|
|
73
128
|
|
|
74
129
|
module_function
|
|
75
130
|
|
|
131
|
+
# The checkout this library is loaded from, which a corpus sweep needs; an installed gem has
|
|
132
|
+
# none.
|
|
133
|
+
#
|
|
134
|
+
# @return [String, nil] the repository root, or nil when `hecks.gemspec` is not beside `lib/`
|
|
135
|
+
def checkout_root
|
|
136
|
+
root = File.expand_path("../..", __dir__)
|
|
137
|
+
root if File.exist?(File.join(root, "hecks.gemspec"))
|
|
138
|
+
end
|
|
139
|
+
|
|
76
140
|
# Routes `argv` to its subcommand and runs it.
|
|
77
141
|
#
|
|
78
142
|
# A subcommand followed only by `--help`/`-h` prints its usage without
|
|
@@ -81,9 +145,12 @@ module Hecks
|
|
|
81
145
|
# @param argv [Array<String>] the command line, subcommand first
|
|
82
146
|
# @param out [#puts] where help goes
|
|
83
147
|
# @param err [#puts] where usage errors go
|
|
84
|
-
# @return [Integer] the process exit status for a
|
|
85
|
-
# subcommand that fails exits
|
|
148
|
+
# @return [Integer] the process exit status: 2 for a bad command, 1 for a bad word, the
|
|
149
|
+
# subcommand's own Integer when it returns one, else 0; a subcommand that fails exits
|
|
150
|
+
# the process itself
|
|
86
151
|
def start(argv, out: $stdout, err: $stderr)
|
|
152
|
+
Encoding.default_external = Encoding::UTF_8
|
|
153
|
+
Encoding.default_internal = Encoding::UTF_8
|
|
87
154
|
name, *rest = argv
|
|
88
155
|
if name.nil? || !COMMANDS.key?(name)
|
|
89
156
|
asked = HELP_FLAGS.include?(name) || name == "help"
|
|
@@ -92,12 +159,121 @@ module Hecks
|
|
|
92
159
|
return asked ? 0 : USAGE_STATUS
|
|
93
160
|
end
|
|
94
161
|
|
|
95
|
-
if
|
|
162
|
+
if help_asked?(name, rest)
|
|
96
163
|
help(COMMANDS.fetch(name), out)
|
|
97
|
-
|
|
98
|
-
|
|
164
|
+
return 0
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
words, problem = launcher_words(name, rest)
|
|
168
|
+
if problem
|
|
169
|
+
err.puts "hecks #{name}: #{problem}"
|
|
170
|
+
return 1
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
result = dispatch(name, words)
|
|
174
|
+
result.is_a?(Integer) ? result : 0
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Whether the words ask for a subcommand's usage: a lone flag for `run`, whose other words are
|
|
178
|
+
# the verb's own, and the flag anywhere for the rest.
|
|
179
|
+
# @api private
|
|
180
|
+
def help_asked?(name, rest)
|
|
181
|
+
return rest.length == 1 && HELP_FLAGS.include?(rest.first) if name == "run"
|
|
182
|
+
|
|
183
|
+
rest.any? { |word| HELP_FLAGS.include?(word) }
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Whether a command line is the launcher's own form of a subcommand that answers differently
|
|
187
|
+
# from its positional form, so the executable leaves it to the launcher.
|
|
188
|
+
#
|
|
189
|
+
# @param argv [Array<String>] the command line, subcommand first
|
|
190
|
+
# @return [Boolean]
|
|
191
|
+
def launcher_form?(argv)
|
|
192
|
+
LAUNCHER_ANSWERS.include?(argv.first) && argv.drop(1).any? { |word| word.match?(NAME_VALUE) }
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Rewrites the launcher's spellings into the subcommand's own: drops the generic flags and
|
|
196
|
+
# turns `name=value` words into the positionals and flags the subcommand reads.
|
|
197
|
+
#
|
|
198
|
+
# @param name [String] a key of `COMMANDS`
|
|
199
|
+
# @param rest [Array<String>] the words after the subcommand
|
|
200
|
+
# @return [Array(Array<String>, String)] the words to run with, and a refusal (nil when
|
|
201
|
+
# there is none)
|
|
202
|
+
def launcher_words(name, rest)
|
|
203
|
+
return [rest, nil] if UNTOUCHED.include?(name)
|
|
204
|
+
|
|
205
|
+
require_relative "doors/cli_door"
|
|
206
|
+
words = strip_generic(rest)
|
|
207
|
+
form = LAUNCHER_FORMS[name]
|
|
208
|
+
return [words, nil] unless form && words.any? { |word| word.match?(NAME_VALUE) }
|
|
209
|
+
|
|
210
|
+
slots, extra, refusal = bind_named_words(form, words)
|
|
211
|
+
return [nil, refusal] if refusal
|
|
212
|
+
|
|
213
|
+
missing = slots.first.find { |path| !File.exist?(path) } if form[:exists]
|
|
214
|
+
return [nil, "no such domain #{missing.inspect}"] if missing
|
|
215
|
+
|
|
216
|
+
[words.grep_v(NAME_VALUE) + extra + slots.flatten, nil]
|
|
217
|
+
rescue Runtime::TypeMismatch => e
|
|
218
|
+
[nil, e.message]
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Sorts the `name=value` words of `words` into the form's positional slots and its flags.
|
|
222
|
+
# @api private
|
|
223
|
+
#
|
|
224
|
+
# @param form [Hash] an entry of `LAUNCHER_FORMS`
|
|
225
|
+
# @param words [Array<String>] the words after the subcommand, generic flags removed
|
|
226
|
+
# @return [Array(Array<Array<String>>, Array<String>, String)] the slot contents, the extra
|
|
227
|
+
# flag words, and a refusal (nil when there is none)
|
|
228
|
+
def bind_named_words(form, words)
|
|
229
|
+
slots = Array.new(form[:slots].length) { [] }
|
|
230
|
+
extra = []
|
|
231
|
+
words.grep(NAME_VALUE).each do |word|
|
|
232
|
+
key, value = word.split("=", 2)
|
|
233
|
+
key = key.tr("-", "_")
|
|
234
|
+
slot = form[:slots].index { |names| names.include?(key) }
|
|
235
|
+
if slot
|
|
236
|
+
slots[slot].concat(form[:many] ? value.split(",") : [value])
|
|
237
|
+
elsif (refusal = bind_flag_or_option(form, key, value, extra))
|
|
238
|
+
return [nil, nil, refusal]
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
[slots, extra, nil]
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Appends the flag or option word for one `name=value` into `extra`.
|
|
245
|
+
# @api private
|
|
246
|
+
#
|
|
247
|
+
# @return [String, nil] a refusal when the form takes no such argument
|
|
248
|
+
def bind_flag_or_option(form, key, value, extra)
|
|
249
|
+
if Array(form[:flags]).include?(key)
|
|
250
|
+
extra << "--#{key.tr('_', '-')}" if Doors::CliDoor.boolean(value)
|
|
251
|
+
nil
|
|
252
|
+
elsif Array(form[:options]).include?(key)
|
|
253
|
+
extra.push("--#{key}", value)
|
|
254
|
+
nil
|
|
255
|
+
elsif !Array(form[:ignored]).include?(key)
|
|
256
|
+
known = form[:slots].flatten + Array(form[:flags]) + Array(form[:options])
|
|
257
|
+
"no argument #{key.inspect} — this verb takes #{known.sort.join(', ')}"
|
|
99
258
|
end
|
|
100
|
-
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Takes `--wait` and `--confirm`, each with an optional Boolean word, out of `words`.
|
|
262
|
+
# @api private
|
|
263
|
+
def strip_generic(words)
|
|
264
|
+
queue = words.dup
|
|
265
|
+
kept = []
|
|
266
|
+
until queue.empty?
|
|
267
|
+
word = queue.shift
|
|
268
|
+
flag, value = word.split("=", 2)
|
|
269
|
+
if GENERIC_FLAGS.include?(flag)
|
|
270
|
+
value ||= Doors::CliDoor::BOOLEAN_WORDS.key?(queue.first.to_s.downcase) ? queue.shift : "true"
|
|
271
|
+
Doors::CliDoor.boolean(value)
|
|
272
|
+
else
|
|
273
|
+
kept << word
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
kept
|
|
101
277
|
end
|
|
102
278
|
|
|
103
279
|
# Runs one known subcommand with the working directory as its root.
|
|
@@ -107,36 +283,9 @@ module Hecks
|
|
|
107
283
|
# @return [void]
|
|
108
284
|
# @raise [SystemExit] when the subcommand exits the process itself
|
|
109
285
|
def dispatch(name, argv)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
require_relative "cli/run"
|
|
114
|
-
Run.call(argv, program: program)
|
|
115
|
-
when "docs", "narrate"
|
|
116
|
-
require_relative "cli/document"
|
|
117
|
-
Document.call(argv, projection: name.to_sym, program: program, root: Dir.pwd)
|
|
118
|
-
when "ir"
|
|
119
|
-
require_relative "cli/ir"
|
|
120
|
-
Ir.call(argv, program: program)
|
|
121
|
-
when "stores"
|
|
122
|
-
require_relative "cli/stores"
|
|
123
|
-
Stores.call(argv, program: program)
|
|
124
|
-
when "model_check"
|
|
125
|
-
require_relative "cli/model_check"
|
|
126
|
-
ModelCheck.call(argv, program: program)
|
|
127
|
-
when "smoke_test"
|
|
128
|
-
require_relative "cli/smoke_test"
|
|
129
|
-
SmokeTest.call(argv, root: Dir.pwd)
|
|
130
|
-
when "project_diagrams"
|
|
131
|
-
require_relative "cli/project_diagrams"
|
|
132
|
-
ProjectDiagrams.call(argv, program: program, root: Dir.pwd)
|
|
133
|
-
when "project_cli"
|
|
134
|
-
require_relative "cli/project_cli"
|
|
135
|
-
ProjectCli.call(argv, program: program, root: Dir.pwd, remove_stale_bin: false)
|
|
136
|
-
when "mcp"
|
|
137
|
-
require_relative "cli/mcp"
|
|
138
|
-
Mcp.call(argv)
|
|
139
|
-
end
|
|
286
|
+
command = COMMANDS.fetch(name)
|
|
287
|
+
require_relative command.file
|
|
288
|
+
command.run.call(argv, "hecks #{name}", name)
|
|
140
289
|
end
|
|
141
290
|
|
|
142
291
|
# @api private
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
require_relative "bluebook/meta_validator"
|
|
2
|
+
require_relative "corpus"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
# Shared machinery for a codemod that migrates real `.bluebook` source: boot,
|
|
6
|
+
# find candidates, edit, then verify by diffing the re-booted IR before keeping it.
|
|
7
|
+
module Codemod
|
|
8
|
+
ROOT = File.expand_path("../..", __dir__)
|
|
9
|
+
|
|
10
|
+
EXAMPLE_ROOTS = Corpus.members(:example, root: ROOT).map(&:path)
|
|
11
|
+
META_FILES = (Dir.glob(File.join(ROOT, "lib/hecks/grammar/*.bluebook")) +
|
|
12
|
+
Dir.glob(File.join(ROOT, "lib/hecks/framework/bluebook/*.bluebook")) +
|
|
13
|
+
Dir.glob(File.join(ROOT, "lib/hecks/language/bluebook/**/*.bluebook"))).sort
|
|
14
|
+
|
|
15
|
+
# Boots the lightweight, in-memory path — a codemod only reads a chapter's
|
|
16
|
+
# own declared IR, never a stored record, so it has no reason to need Postgres.
|
|
17
|
+
PERSISTENCE_PORT = File.join(ROOT, "lib/hecks/ports/persistence.port")
|
|
18
|
+
EXTRACTION_PORT = File.join(ROOT, "lib/hecks/ports/extraction.port")
|
|
19
|
+
MEMORY_ADAPTER = File.join(ROOT, "lib/hecks/adapters/driven/memory.adapter")
|
|
20
|
+
PRISM_ADAPTER = File.join(ROOT, "lib/hecks/adapters/driven/prism.adapter")
|
|
21
|
+
|
|
22
|
+
def self.export_json(registry) = Hecks::Projector::Exporter.json(registry)
|
|
23
|
+
|
|
24
|
+
# Text that stands in for a file while a dry run judges an edit.
|
|
25
|
+
#
|
|
26
|
+
# `Kernel.load` evaluates the staged text in place of the file, under the file's own path, so
|
|
27
|
+
# a codemod can boot the edited bluebook without writing a tracked file: a dry run that is
|
|
28
|
+
# interrupted, or read by a parallel process, leaves the checkout exactly as it was.
|
|
29
|
+
module Shadow
|
|
30
|
+
# Prepended to `Kernel`'s singleton: loads staged text when there is any for the file.
|
|
31
|
+
module Loader
|
|
32
|
+
# @param file [String] the path being loaded
|
|
33
|
+
# @return [Boolean] true, as `Kernel.load` answers
|
|
34
|
+
def load(file, *)
|
|
35
|
+
text = Shadow.texts[file.to_s]
|
|
36
|
+
return super unless text
|
|
37
|
+
|
|
38
|
+
Hecks::Adapters::Prism::TREES[file.to_s] = ::Prism.parse(text).value
|
|
39
|
+
eval(text, TOPLEVEL_BINDING.dup, file.to_s, 1)
|
|
40
|
+
true
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
Kernel.singleton_class.prepend(Loader)
|
|
44
|
+
|
|
45
|
+
module_function
|
|
46
|
+
|
|
47
|
+
# @return [Hash{String => String}] the staged text by absolute path
|
|
48
|
+
def texts = (@texts ||= {})
|
|
49
|
+
|
|
50
|
+
# Stages `text` as `file`'s contents and drops the file's cached parse.
|
|
51
|
+
#
|
|
52
|
+
# @param file [String] the path the text stands in for
|
|
53
|
+
# @param text [String] the staged source
|
|
54
|
+
# @return [String] the text
|
|
55
|
+
def put(file, text)
|
|
56
|
+
Hecks::Adapters::Prism.forget(file)
|
|
57
|
+
texts[file] = text
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Stops standing in for `file`.
|
|
61
|
+
#
|
|
62
|
+
# @param file [String] the path to read from disk again
|
|
63
|
+
# @return [void]
|
|
64
|
+
def drop(file)
|
|
65
|
+
texts.delete(file)
|
|
66
|
+
Hecks::Adapters::Prism.forget(file)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @return [Boolean] whether any text is staged
|
|
70
|
+
def active? = !texts.empty?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Puts `text` where `file`'s next load will find it: on disk, or (a dry run) staged in memory.
|
|
74
|
+
#
|
|
75
|
+
# @param file [String] the bluebook's path
|
|
76
|
+
# @param text [String] the new source
|
|
77
|
+
# @param dry_run [Boolean] whether to leave the file untouched
|
|
78
|
+
# @return [void]
|
|
79
|
+
def self.stage(file, text, dry_run:)
|
|
80
|
+
dry_run ? Shadow.put(file, text) : File.write(file, text)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Undoes `stage`: rewrites the original, or stops staging.
|
|
84
|
+
#
|
|
85
|
+
# @param file [String] the bluebook's path
|
|
86
|
+
# @param original [String] the source as it was on disk
|
|
87
|
+
# @param dry_run [Boolean] whether the edit was only staged
|
|
88
|
+
# @return [void]
|
|
89
|
+
def self.unstage(file, original, dry_run:)
|
|
90
|
+
dry_run ? Shadow.drop(file) : File.write(file, original)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Forgets each path's cached AST first — a stale tree after editing a file
|
|
94
|
+
# misreports that file's own source locations.
|
|
95
|
+
def self.load_bluebook(path)
|
|
96
|
+
paths = if path.is_a?(Array)
|
|
97
|
+
path
|
|
98
|
+
elsif File.directory?(path)
|
|
99
|
+
Dir.glob(File.join(path, "*.bluebook"))
|
|
100
|
+
else
|
|
101
|
+
[path]
|
|
102
|
+
end
|
|
103
|
+
paths.each { |file| Hecks::Adapters::Prism.forget(file) }
|
|
104
|
+
|
|
105
|
+
registry = Hecks::Runtime::Registry.new
|
|
106
|
+
Hecks.with_registry(registry) do
|
|
107
|
+
Kernel.load(PERSISTENCE_PORT)
|
|
108
|
+
Kernel.load(EXTRACTION_PORT)
|
|
109
|
+
Kernel.load(MEMORY_ADAPTER)
|
|
110
|
+
Kernel.load(PRISM_ADAPTER)
|
|
111
|
+
Hecks::Bluebook::MetaValidator.defer { paths.each { |file| Kernel.load(file) } }
|
|
112
|
+
Hecks::Bluebook::MetaValidator.judge_deferred!(registry)
|
|
113
|
+
end
|
|
114
|
+
registry
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Forgets every tree, not just one — callers rarely know which of the
|
|
118
|
+
# meta-domain's several files they just edited.
|
|
119
|
+
def self.boot_meta
|
|
120
|
+
Hecks::Adapters::Prism.forget_all
|
|
121
|
+
Hecks::Bluebook::MetaValidator.instance_variable_set(:@grammar_registry, nil)
|
|
122
|
+
export_json(Hecks::Bluebook::MetaValidator.grammar_registry)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def self.meta_registry
|
|
126
|
+
Hecks::Adapters::Prism.forget_all
|
|
127
|
+
Hecks::Bluebook::MetaValidator.instance_variable_set(:@grammar_registry, nil)
|
|
128
|
+
Hecks::Bluebook::MetaValidator.grammar_registry
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Walks nested entities recursively too, not just each aggregate's own commands.
|
|
132
|
+
def self.each_command(registry)
|
|
133
|
+
registry.bluebooks.each_value do |chapter|
|
|
134
|
+
chapter.aggregates.each do |aggregate|
|
|
135
|
+
walk = lambda do |construct|
|
|
136
|
+
construct.commands.each { |command| yield construct, command }
|
|
137
|
+
construct.entities.each(&walk) if construct.respond_to?(:entities)
|
|
138
|
+
end
|
|
139
|
+
walk.call(aggregate)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def self.owner_attribute(construct, name)
|
|
145
|
+
construct.attributes.find { |attr| attr.name.to_s == name.to_s }
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Raises on a name collision between a value object and an entity — nothing
|
|
149
|
+
# in the DSL prevents two from sharing a `hecks_name`, so it can't be resolved silently.
|
|
150
|
+
def self.element_construct_for(construct, list_field)
|
|
151
|
+
list_attr = owner_attribute(construct, list_field)
|
|
152
|
+
return nil unless list_attr&.list?
|
|
153
|
+
|
|
154
|
+
value_objects = construct.respond_to?(:value_objects) ? construct.value_objects : []
|
|
155
|
+
entities = construct.respond_to?(:entities) ? construct.entities : []
|
|
156
|
+
matches = (value_objects + entities).select { |c| c.hecks_name.to_s == list_attr.type.to_s }
|
|
157
|
+
|
|
158
|
+
if matches.size > 1
|
|
159
|
+
raise "#{construct.hecks_name}##{list_field} names #{list_attr.type}, held by both a value " \
|
|
160
|
+
"object and an entity — ambiguous, cannot resolve which one the list holds"
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
matches.first
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Rescues StandardError only, so a genuine bug still raises rather than
|
|
167
|
+
# silently counting as an unsafe candidate to revert.
|
|
168
|
+
def self.safely
|
|
169
|
+
[yield, nil]
|
|
170
|
+
rescue StandardError => e
|
|
171
|
+
[nil, "#{e.class}: #{e.message}"]
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Runs one codemod rule across every example domain and the meta-domain,
|
|
175
|
+
# verifying each candidate edit via IR diff before keeping it.
|
|
176
|
+
class Runner
|
|
177
|
+
def initialize(find_candidates:, apply_candidate:, label:)
|
|
178
|
+
@find_candidates = find_candidates
|
|
179
|
+
@apply_candidate = apply_candidate
|
|
180
|
+
@label = label
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def run(dry_run: false)
|
|
184
|
+
results = { applied: [], skipped: [], clean: [] }
|
|
185
|
+
run_example_domains(results, dry_run)
|
|
186
|
+
run_meta_domain(results, dry_run)
|
|
187
|
+
results
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def report(results, dry_run:)
|
|
191
|
+
puts "== results (#{dry_run ? 'DRY RUN — nothing written' : 'applied'}) =="
|
|
192
|
+
puts "clean (no candidates): #{results[:clean].join(', ')}" unless results[:clean].empty?
|
|
193
|
+
results[:applied].each { |r| puts "APPLIED #{r[:file]}: #{r[:candidates].join(', ')}" }
|
|
194
|
+
results[:skipped].each { |r| puts "SKIPPED #{r[:file]} (#{r[:reason]}): #{Array(r[:candidates]).join(', ')}" }
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
private
|
|
198
|
+
|
|
199
|
+
def apply_many(text, candidates)
|
|
200
|
+
applied = []
|
|
201
|
+
candidates.each do |c|
|
|
202
|
+
text, changed = @apply_candidate.call(text, c)
|
|
203
|
+
applied << c if changed
|
|
204
|
+
end
|
|
205
|
+
[text, applied]
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Kept as one method: the write/verify/revert sequence is a single unit,
|
|
209
|
+
# and splitting it would scatter shared state for no readability gain.
|
|
210
|
+
# rubocop:disable-next Metrics/AbcSize
|
|
211
|
+
# rubocop:disable-next Metrics/BlockLength
|
|
212
|
+
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
213
|
+
# rubocop:disable-next Metrics/MethodLength
|
|
214
|
+
# rubocop:disable-next Metrics/PerceivedComplexity
|
|
215
|
+
def run_example_domains(results, dry_run)
|
|
216
|
+
Codemod::EXAMPLE_ROOTS.each do |domain_dir|
|
|
217
|
+
bluebook_files = Dir.glob(File.join(domain_dir, "bluebook", "*.bluebook"))
|
|
218
|
+
next if bluebook_files.empty?
|
|
219
|
+
|
|
220
|
+
before_json = Codemod.export_json(Codemod.load_bluebook(bluebook_files))
|
|
221
|
+
candidates = @find_candidates.call(Codemod.load_bluebook(bluebook_files))
|
|
222
|
+
|
|
223
|
+
if candidates.empty?
|
|
224
|
+
results[:clean] << domain_dir
|
|
225
|
+
next
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
live = bluebook_files.to_h { |file| [file, File.read(file)] }
|
|
229
|
+
applied_by_file = Hash.new { |hash, file| hash[file] = [] }
|
|
230
|
+
|
|
231
|
+
candidates.each do |candidate|
|
|
232
|
+
target_file = bluebook_files.find { |file| apply_many(live[file], [candidate]).last.any? }
|
|
233
|
+
unless target_file
|
|
234
|
+
results[:skipped] << { file: domain_dir, reason: "no matching source line", candidates: [@label.call(candidate)] }
|
|
235
|
+
next
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
original_text = live[target_file]
|
|
239
|
+
text, = apply_many(original_text, [candidate])
|
|
240
|
+
live[target_file] = text
|
|
241
|
+
Codemod.stage(target_file, text, dry_run: dry_run)
|
|
242
|
+
after_json, error = Codemod.safely do
|
|
243
|
+
Codemod.export_json(Codemod.load_bluebook(bluebook_files))
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
if after_json == before_json && !dry_run
|
|
247
|
+
applied_by_file[target_file] << candidate
|
|
248
|
+
elsif after_json == before_json
|
|
249
|
+
live[target_file] = original_text
|
|
250
|
+
Codemod.unstage(target_file, original_text, dry_run: dry_run)
|
|
251
|
+
Codemod.safely { Codemod.load_bluebook(bluebook_files) }
|
|
252
|
+
applied_by_file[target_file] << candidate
|
|
253
|
+
else
|
|
254
|
+
live[target_file] = original_text
|
|
255
|
+
Codemod.unstage(target_file, original_text, dry_run: dry_run)
|
|
256
|
+
Codemod.safely { Codemod.load_bluebook(bluebook_files) }
|
|
257
|
+
reason = error ? "reboot raised after edit (#{error}) — reverted" : "IR changed after edit — reverted"
|
|
258
|
+
results[:skipped] << { file: target_file, reason: reason, candidates: [@label.call(candidate)] }
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
applied_by_file.each do |file, applied|
|
|
263
|
+
results[:applied] << { file: file, candidates: applied.map(&@label) }
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Per-candidate, not batched — the meta-domain is one shared,
|
|
269
|
+
# self-dispatching registry (S14), so a single unsafe candidate could
|
|
270
|
+
# otherwise sink every other, genuinely safe one in the same run.
|
|
271
|
+
# rubocop:disable-next Metrics/AbcSize
|
|
272
|
+
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
273
|
+
# rubocop:disable-next Metrics/PerceivedComplexity
|
|
274
|
+
def run_meta_domain(results, dry_run)
|
|
275
|
+
before_meta = Codemod.boot_meta
|
|
276
|
+
meta_candidates = @find_candidates.call(Codemod.meta_registry)
|
|
277
|
+
|
|
278
|
+
if meta_candidates.empty?
|
|
279
|
+
results[:clean] << "meta-domain"
|
|
280
|
+
return
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
live_meta = Codemod::META_FILES.to_h { |f| [f, File.read(f)] }
|
|
284
|
+
applied_by_file = Hash.new { |h, k| h[k] = [] }
|
|
285
|
+
|
|
286
|
+
meta_candidates.each do |candidate|
|
|
287
|
+
target_file = Codemod::META_FILES.find { |f| apply_many(live_meta[f], [candidate]).last.any? }
|
|
288
|
+
unless target_file
|
|
289
|
+
results[:skipped] << { file: "meta-domain", reason: "no matching source line", candidates: [@label.call(candidate)] }
|
|
290
|
+
next
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
text, = apply_many(live_meta[target_file], [candidate])
|
|
294
|
+
original_text = live_meta[target_file]
|
|
295
|
+
live_meta[target_file] = text
|
|
296
|
+
Codemod::META_FILES.each { |f| Codemod.stage(f, live_meta[f], dry_run: dry_run) }
|
|
297
|
+
|
|
298
|
+
# A dry run stages the text in memory and reboots from it, then drops it — so the next
|
|
299
|
+
# candidate is judged against the true original state and no file is written.
|
|
300
|
+
after_meta, error = Codemod.safely { Codemod.boot_meta }
|
|
301
|
+
|
|
302
|
+
if after_meta == before_meta && !dry_run
|
|
303
|
+
applied_by_file[target_file] << candidate
|
|
304
|
+
elsif after_meta == before_meta # dry-run, safe — revert, but count as applied
|
|
305
|
+
live_meta[target_file] = original_text
|
|
306
|
+
Codemod::META_FILES.each { |f| Codemod.unstage(f, live_meta[f], dry_run: dry_run) }
|
|
307
|
+
Codemod.safely { Codemod.boot_meta }
|
|
308
|
+
applied_by_file[target_file] << candidate
|
|
309
|
+
else
|
|
310
|
+
live_meta[target_file] = original_text
|
|
311
|
+
Codemod::META_FILES.each { |f| Codemod.unstage(f, live_meta[f], dry_run: dry_run) }
|
|
312
|
+
Codemod.safely { Codemod.boot_meta } # resync memoized state to the reverted text
|
|
313
|
+
reason = error ? "reboot raised (#{error})" : "IR changed"
|
|
314
|
+
results[:skipped] << { file: "meta-domain", reason: reason, candidates: [@label.call(candidate)] }
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
applied_by_file.each do |file, candidates|
|
|
319
|
+
results[:applied] << { file: file, candidates: candidates.map(&@label) }
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
end
|