CLI
penmark <verb> <genre> [options]. The genre is a required
positional, never a flag — every command knows up front which
genre it’s running, and a missing genre is an immediate error rather
than a subtle dispatch into the wrong defaults.
The verb set is still in flux; what’s wired today:
| Verb | Purpose |
|---|---|
solve | Solve every puzzle in a canonical dataset; report counts and timings. |
grade | Grade one puzzle against a profile and print the trace. |
grade-canon | Grade every puzzle in a canonical dataset against a profile; print difficulty distribution. |
generate | Full pipeline — scaffold + solve + derive + reduce. Wired for akari, sudoku, slitherlink. |
scaffold | Phase 1 only — wall/hole structural pass, no solver, no clues. |
enumerate | Walk uniquely-solvable puzzles of a given size for a genre. |
import | Read puzzlehound’s collated output and write canonical records to data/puzzles/<genre>.jsonl.gz. |
profile-init | Copy the embedded default profile to ~/.config/penmark/profiles/<genre>/ for editing. |
profile-eval | Run a profile against a canonical dataset; print the difficulty distribution. |
list-impls | Print the hand-maintained list of genres + algorithms. |
The verbs discover (capture specimens at every trial-search step)
and coverage (per-technique hit-count report against persisted
specimens) are described in the Enumerator chapter but
haven’t shipped as CLI verbs yet. The mining loop runs ad-hoc against
the enumerator until those land.
Config knobs are clap flags on the universal Cmd enum. There are
no per-genre CLI files — GenericCli is a single non-generic impl
that handles every command for every genre by dispatching through
the runtime &'static Genre handle (genre.default_gen_density,
genre.apply_cli_density, genre.parse_text, …). Adding a new
option means one clap field on Cmd::Generate (or wherever) plus
reading it from GenericCli::generate.
Parallelism is op-by-op: generate and the enumerator fan out
via rayon::par_iter across puzzles in the work queue; one puzzle’s
solve/grade is sequential. --threads=N is consistent where it
applies and ignored on single-puzzle ops.