Skip to content

Getting started

The core is GPU-free. The entire loop — synthesize, generate, gate, repair, bind — runs against deterministic stubs on any machine, and the whole test suite executes that way. That is not a demo mode bolted on afterwards; it is what proves the plugin boundary actually holds.

Terminal window
pip install prompt-crafter
pcraft --help

Or as a Node launcher that forwards to the same Python and inherits its exit code:

Terminal window
npm install -g @mcptoolshop/prompt-crafter

The distribution is prompt-crafterpcraft and prompt-craft are both taken on PyPI — while the import package and the command stay pcraft. For development from a clone:

Terminal window
python -m venv .venv
. .venv/Scripts/activate # POSIX: source .venv/bin/activate
pip install -e ".[dev]"

Requires Python 3.11+. CI runs 3.11 and 3.13 on the core + [dev]. The [image] extra is not claimed on 3.11. The core’s only runtime dependency is pydantic.

Terminal window
pcraft demo
pcraft list
pcraft validate
pcraft doctor

demo runs synth → generate → gate → repair → bind end to end with a stub generator and a scripted verifier, and writes a provenance receipt. list and validate open a contract store without generating. doctor reports python, extras, and whether the store loads. No GPU, no network, no model downloads. --contracts-dir points at a tree that is not the shipped demo.

Terminal window
pcraft gate hero.png

Read the exit code, not just the text. The human-readable transcript and the process exit are different objects on purpose:

exitmeaning
0the gate ran and every required atom passed
1bad arguments, or a contract that does not parse
2it ran, and a required atom failed
3it ran, and the result is unconfirmed — the human band
4it could not run — unreadable input, or no required tier available

If you script anything around this tool, branch on 4 separately. “I could not check” is not a pass and it is not a failure, and treating it as either is how a gate becomes decorative.

Terminal window
pcraft replay records/hero.json

Every bound asset writes a replayable receipt pinning the contract hash, the compiled synthesizer id, the generator id with its seed and sampler, the verifier id and version, and the full per-atom gate transcript.

Terminal window
pip install -e ".[image]" # torch / diffusers — the real generator and verifiers
pip install -e ".[synth]" # DSPy + a hosted LM — the real synthesizer

Neither is needed to run, test, or evaluate the core. Local generate() on a 5090 has been run here (2026-08-18, ashen-reaver, OpenPose + identity plate). The frame is orcish; grip, sigil, and bracer did not land. A Cloud recipe (pcraft recipe) has been submitted live (2026-08-18). A live GEPA compile ran 2026-08-18 on local Ollama hermes3:8b (not 600B) via compile_synthesizer and an external gate_metric. The CLI will not invent one (STATE_COMPILE_NEEDS_GATE). bind --no-mock is the live door when [image] is installed; missing extras are DEP_IMAGE_MISSING. --mock stays the GPU-free scaffold.

Terminal window
python verify.py --installed

Six legs: version coherence, lint, typecheck, the suite, the suite again under -O, and a package build.

The -O pass is not ceremony — assert is stripped under -O, so a check written as an assert silently disappears in optimized mode. Every refusal in this codebase raises, and that second pass is what proves it.

Version coherence compares the installed distribution’s version against the one pyproject.toml declares, and refuses when they differ. An editable install’s metadata is not regenerated when pyproject.toml changes, and package_version() falls back to the tree’s literal only when the distribution is missing entirely — so stale metadata is found and the wrong version is returned silently. That happened twice here. It runs first, because an environment lying about its version should not be discovered after a full suite and two builds.

The gate also lints and typechecks itself. That sounds obvious and was not true until v0.4.0: the legs covered src and tests and skipped the file defining them.

The run closes by naming its own scope:

VERIFY OK -- checked: version coherence, lint, typecheck, suite, suite under -O, build
NOT CHECKED -- dependency audit. CI runs pip-audit as a separate step, so a green
verify.py is not yet a green CI.

A bare OK implies a scope this gate does not have. “Could not check” must never read as “checked clean” — the same rule the CI workflow applies to its own skipped entries.

Terminal window
python verify.py --installed --audit

Off by default, and not out of squeamishness about the network: running it makes the gate time-varying, so an unchanged tree passes today and fails tomorrow when an advisory publishes. That is right for CI and wrong for a release gate.

It reports three outcomes rather than two, because two would have shipped a gate that is red forever:

outcomebehaviour
advisory with a published fixfails — there is a move available
advisory with no published fixreported, does not fail
could not audit at allreported loudest

The third row is the one that would otherwise pass silently. With [image] installed, torch is a local CUDA build that is not on PyPI, so the auditor cannot see the largest dependency in the tree at all — and a report saying “no vulnerabilities” would be printing “could not check” as “checked clean”. Every run also names the extras it resolved against, because the verdict depends on that set: [synth] surfaces an advisory [dev] does not. A passing run carrying any caveat prints QUALIFIED rather than a bare OK.