hecks

The gem ships a hecks executable, and development tooling stays in the repo

Status: Accepted — implemented in 2.8.0. Date: 2026-09-27. Step 1 (the syntax-boot cache and the Storehouse log live under Hecks::CacheDir) and step 2 (exe/hecks, and a packaged file list without the repository-only tooling) both shipped. The open items below still stand.

Context

gem install hecks today delivers lib/ and nothing else. hecks.gemspec:25 builds spec.files from lib/**/* and the gemspec declares no executables. Every command an operator would run lives in the unshipped bin/ directory, and the README’s own quickstart tells the reader to clone (README.md:655).

Some code under lib/ reaches paths that exist only in a checkout (docs/wayfinder/review-followup/tickets/01-research-what-escapes-the-gem.md).

Group Kind Installed-gem behavior
Syntax-boot cache (lib/hecks/bluebook/meta_validator/syntax_boot.rb) runtime, on every parse CACHE_DIR (line 169) is <gem dir>/tmp/hecks_syntax_boot_cache; a read-only gem directory silently disables the cache
Storehouse log (lib/hecks/storehouse.rb) runtime, required by lib/hecks.rb:33 LOG_ROOT (line 105) is <gem dir>/tmp/storehouse; appends fail silently on a read-only install
Fuzzing (lib/hecks/fuzzing/) dev tooling QaSettings.load raises without qa/settings.yml; the rust/Cargo.toml read raises ENOENT
Bench (lib/hecks/bench/) dev tooling cargo build in a missing rust/ raises
Corpus, codemod, query IR, grammar evolve, doc reference dev tooling globs over examples/, qa/, spec/, rust/ come back empty or raise; codemod and grammar evolve rewrite shipped lib/ bluebooks in place

Two findings shape the decision. The first two rows are runtime writes into the gem directory on a normal install, a defect independent of any split. Everything else that breaks is already dev tooling and is not required by lib/hecks.rb, so there is a clean seam. The one exception is require_relative "hecks/corpus" at lib/hecks.rb:36.

bin/ holds about 86 scripts in four buckets (docs/wayfinder/review-followup/tickets/04-distribution-shape.md): about 27 domain-operator tools, 7 deploy and target projectors, about 31 language-development and self-hosting scripts, and about 20 QA, CI and release scripts. Most operator scripts start with require "hecks" from lib (bin/run:27), so they port easily. Hecks::Facade::CliRunner.call(runtime:, argv:, program:) (lib/hecks/facade/cli_runner.rb:48) is an IO-free, domain-scoped runner that bin/run and bin/project_cli already wrap. Nothing dispatches tool subcommands.

Dependents pin hecks with ~> ranges, and domain images build from a git tag, so whatever lib/hecks.rb requires must stay in the runtime gem and tag builds must keep working.

Decision

Two steps, in this order.

  1. Stop writing into the gem directory. Move the syntax-boot cache (CACHE_DIR) and the Storehouse log (LOG_ROOT) to the system temp directory or the XDG cache directory. HECKS_SYNTAX_BOOT_CACHE=off keeps working. This step does not depend on step 2 and ships first.
  2. Ship a hecks executable and trim the package.
    • Add exe/hecks and list it in spec.executables. It is a thin router over the domain-operator scripts: run, docs, narrate, ir, stores, model_check, smoke_test, project_diagrams, project_cli, and mcp for the MCP door. The domain-scoped part wraps Hecks::Facade::CliRunner.
    • Keep the dev-tooling directories out of spec.files.
    • Make the require "hecks/corpus" at lib/hecks.rb:36 lazy, so the constant loads on first use instead of at boot.

Dev tooling stays in the repo and is documented as clone-only: fuzzing, bench, corpus, codemod, query IR, grammar evolve, the doc reference generator and the QA scripts. A separate hecks-dev gem is revisited only if those tools gain users outside this repository.

The runtime gem keeps everything lib/hecks.rb requires, including storehouse and mcp_stdio_guard, so existing ~> pins keep resolving. Building a domain image from a git tag keeps working because nothing in the tag layout moves.

Consequences

Alternatives considered

Open items

These are the maintainer questions from the distribution ticket that are still unanswered.

Addendum (2026-09-30): Facade is now Doors

Hecks::Facade is now Hecks::Doors, and this text’s Hecks::Facade::CliRunner is Hecks::Doors::CliRunner (in lib/hecks/doors/cli_runner.rb). Surface is Doors::RubyDoor, and the MCP door (Hecks::Doors::McpDoor, with its McpDoorScope) moved from lib/hecks/cli/ to sit beside the others. The boot keyword install_facade: is install_doors:. The old constants and keyword still work for one release and warn; generated launchers pick up the new names when regenerated with hecks project_cli.