A self-contained, deterministic validation engine for OpenSpec v1.1 specifications.
pip install crucible-forge • import crucible
crucible takes a software specification (with its epics and tickets), scores
it against a fixed, configurable rubric, and reports a readiness gate — with
no LLM, fully deterministic. Same input, same score, every time.
It is a faithful Python port of the SpecForge @specforge/validator engine,
extracted as a standalone library, and is the planning gate of
SpecSmither. The output is
byte-equivalent to the reference TypeScript engine, verified by differential
tests across every phase and output layer.
Why
- 🎯 Deterministic — pure scoring, no model calls. Reproducible in CI.
- 📦 Self-contained — pure Python; only
pydanticandpyyamlat runtime. - 🔬 Faithful — output matches the reference TS validator (differential-tested).
- 🎚️ Configurable — every threshold, tier weight, and check lives in config.
- 🏷️ Typed — ships
py.typed; passesmypy --strict.
Install
pip install crucible-forge # → import crucible
# or
uv add crucible-forge
Requires Python ≥ 3.11.
Quick start
from crucible import validate, load_defaults
result = validate(spec, {
"phase": "planning_spec",
"config": load_defaults(), # optional — loaded by default
"returns": ["structural", "scoring", "guidance"],
})
if result.scoring and not result.scoring.skipped:
print(result.scoring.gate_result) # 'pass' | 'fail'
print(result.scoring.local_score) # e.g. 86.11
print(result.passed) # overall gate for the phase
# Canonical camelCase JSON (matches the reference engine):
result.to_json_dict()
spec may be a crucible.Specification model or a plain dict (camelCase,
the OpenSpec v1.1 shape). The context accepts the original keys
(phase, activeEntityId, config, returns) or their snake_case forms.
The model
A spec is a hierarchy:
Specification → Epic → Ticket (+ acceptance criteria, impl steps, tests, files, dependencies)
↘ ticket dependency DAG ↙
validate() walks this tree and produces up to four output layers:
| layer | what it reports |
|---|---|
structural |
schema issues — missing fields, format violations, duplicate orders, entity counts |
scoring |
the readiness score + gate (rubric tiers, weighted blocks, topology penalties, cascade floors) |
crossValidation |
cross-entity consistency — cycles, orphan/island tickets, file conflicts, wave coordination, blueprint coverage |
guidance |
human-readable, prioritized fix suggestions composed from the above |
Request the layers you want via returns; a single phase returns whatever it
computes, and phase: "all" defaults to ["scoring"].
Phases
validate() is driven by context["phase"]:
| phase | scope | needs activeEntityId |
|---|---|---|
planning_spec |
spec-level fields | |
epic_decomposition |
spec + the epic list | |
epic_expansion |
one epic's fields | ✓ |
ticket_decomposition |
tickets under one epic | |
ticket_expansion |
one ticket's fields | ✓ |
cross_validation |
full-tree consistency checks | |
all |
every phase, keyed under by_phase |
# Summative gate over the whole tree:
allr = validate(spec, {"phase": "all", "returns": ["scoring", "crossValidation"]})
planning_passed = (
allr.by_phase["planning_spec"].scoring
and allr.by_phase["planning_spec"].scoring.gate_result == "pass"
)
Structural-only
When you just need the schema check (no scoring/guidance):
from crucible import validate_structural, load_defaults
res = validate_structural(spec, load_defaults(), "planning_spec")
if not res.missing_fields and not res.invalid_fields:
... # schema-clean
Configuration
Every gate is config-driven. load_defaults() returns the packaged
ValidatorConfig (thresholds, tier weights, the 53-entry rubric thresholds,
topology penalties, cross-validation rules). Layer overrides with merge_config:
from crucible import load_defaults, merge_config
config = merge_config(load_defaults(), {"thresholds": {"global": 85}})
The scoring rubric (53 entries: 16 spec · 17 epic · 20 ticket) ships as a data
asset at crucible/guidance/rubric/data/rubric.json, generated verbatim from the
reference source.
Public API
from crucible import (
validate, validate_structural, # entry points
load_defaults, load_from_file, # config loading
load_partial_from_file, merge_config,
CONFIG_DEFAULTS, ValidatorConfigSchema,
PlanningConfigResolver, # project/spec config resolution
ValidatorInputError, # raised on bad context
Specification, Epic, Ticket, Blueprint, # OpenSpec models
models, types, # full model + result namespaces
)
Development
uv sync --all-extras --dev
uv run ruff check src tests
uv run mypy
uv run pytest # 125 tests, incl. differential vs the TS engine
Fidelity is verified against committed golden output captured from the reference engine, so CI needs no extra tooling.
Status
0.2.0 — a complete port, verified byte-for-byte against the current TS
validator across every phase and all four output layers (structural · scoring
· crossValidation · guidance). Tracks the reference engine through @specforge/validator
0.1.20: the epic/ticket expansion gate is ALL-PASS (every touched entity must
clear its threshold — a strong entity no longer masks a weak one; the mean is still
reported), entity-count findings embed the (epicId: …) in their message, and the
decomposition guidance advances via complete_planning_session. See
CHANGELOG.md.
License
Apache 2.0 © Gabriel Augusto Gonçalves / blacksmithers
Metadata
Release files for crucible-forge 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| crucible_forge-0.2.0.tar.gz | 70.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| crucible_forge-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 179.1 kB
Release files / crucible_forge-0.2.0.tar.gz
| Download URL | crucible_forge-0.2.0.tar.gz |
|---|---|
| Size | 70.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ed06e9c32b47205d88b0049d6d3e70015e42a0fc586b160fb2bd8d8bc0439449
|
|
BLAKE2b-256 checksum How to use checksums |
62eeb14ee591216e93a35dec06cd8062465bcebb5445bfc7c7e53be50601384c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 13, 2026.
Transparency logRelease files / crucible_forge-0.2.0-py3-none-any.whl
| Download URL | crucible_forge-0.2.0-py3-none-any.whl |
|---|---|
| Size | 108.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
06398d19001f655bcf93e9a2be61ddc890a2318dad3b9d2623994862988fc152
|
|
BLAKE2b-256 checksum How to use checksums |
4e7f298c00b8f36db0066fb1a48afc39373d0d01e727182b52e3e22364d8db4e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 13, 2026.
Transparency log