Skip to main content

crucible

A self-contained, deterministic validation engine for OpenSpec v1.1 specifications.

CI PyPI Python versions License: Apache 2.0 mypy strict

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 the planning gate of SpecSmither, packaged as a standalone library with a stable API and a conformance suite that pins its output across every phase and layer.

Why

  • 🎯 Deterministic — pure scoring, no model calls. Reproducible in CI.
  • 📦 Self-contained — pure Python; only pydantic and pyyaml at runtime.
  • 🔬 Conformance-tested — a golden suite pins every phase and output layer.
  • 🎚️ Configurable — every threshold, tier weight, and check lives in config.
  • 🏷️ Typed — ships py.typed; passes mypy --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:
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, existingFiles) or their snake_case forms.

existingFiles is optional grep evidence for the file-provenance check — the set of paths that already exist in the real repository. It is tri-state: omit it and existence is judged strictly against what the spec creates; pass it (even empty) and it is unioned with those paths, so a brownfield file no ticket creates stops being reported as missing. The engine never touches the filesystem itself — you supply the set (crucible.compute_grep_candidates(spec) tells you which paths are worth probing).

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.

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

Behavior is pinned by a golden conformance corpus. The full corpus is kept local to the maintainer; the tests that ship and run in CI are the unit suites for the self-contained modules (file-provenance, concurrent-modification, cycle-analysis, creator-election, na-eligible), the format and UTF-16 suites, and the property and smoke tests — no fixtures needed. Every release is cut only after the full conformance corpus passes.

Status

0.3.0 — stable across every phase and all four output layers (structural · scoring · crossValidation · guidance).

Highlights of this release: file coordination is now a single static file-provenance model (four invariants over an existence set that an optional existingFiles context supplies — the engine stays filesystem-free), and concurrent-modification orders same-file writers by dependency reachability instead of wave collision. blueprint-coverage moved to ticket_decomposition, which rescales ticket_expansion scores. N/A eligibility became a shared allow-set (crucible.na_eligible). Two pure analyzers — cycle_analysis and creator_election — compute cycle-resolution and shared-file plans. The format gate is now strict (no coercion, null-rejecting optionals, UTF-16 string lengths). See CHANGELOG.md for the full list, including breaking renames in OperationName.

License

Apache 2.0 © Gabriel Augusto Gonçalves / blacksmithers

Metadata

Release files for crucible-forge 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for crucible-forge 0.3.0
File Size Uploaded
crucible_forge-0.3.0.tar.gz 86.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for crucible-forge 0.3.0
File Interpreter ABI Platform
crucible_forge-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 211.2 kB

Release files / crucible_forge-0.3.0.tar.gz

Download URL crucible_forge-0.3.0.tar.gz
Size 86.7 kB
Tags Source
SHA-256 checksum
How to use checksums
5c70f3694a4ed0c8b228f5524ac6c8104757054fa83f3a35017117de00de077a
BLAKE2b-256 checksum
How to use checksums
275692a987128990cbc59723d7e5d52c6cedde6d38c36e1e949d6a0791d2f55e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

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 21, 2026.

Transparency log

Release files / crucible_forge-0.3.0-py3-none-any.whl

Download URL crucible_forge-0.3.0-py3-none-any.whl
Size 124.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6913eb631e4cec489ad74f7a3dd83c7517e9d5436f53a8ae317ce3b751d90883
BLAKE2b-256 checksum
How to use checksums
d3b5a2af6cc14a8945d63aee0d8bfb2a3999678da0513ba7ea719732b561f89b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

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 21, 2026.

Transparency log

Release history Release notifications | RSS feed

0.4.0

2 release files

This release

0.3.0 This release

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page