Skip to main content

assay

A self-contained, deterministic work-session completion gate.

CI PyPI Python versions License: Apache 2.0 mypy strict

pip install assay-forge  •  import assay


assay takes a work session (a ticket plus the agent's recorded acceptance checks, implementation-step completions, file changes, and test results), runs four independent coverage checks, and reports a completion gate — with no LLM, fully deterministic. Same input, same result, every time.

It is a faithful Python port of the SpecForge @specforge/assayforge engine, extracted as a standalone library, and is the work-session gate of SpecSmither (sitting beside the planning gate, crucible). The output is byte-equivalent to the reference TypeScript engine, verified by differential tests across single-check and full-lifecycle flow snapshots.

Why

  • 🎯 Deterministic — pure logic, no model calls. Reproducible in CI.
  • 📦 Self-contained — pure Python; only pydantic and pyyaml at runtime.
  • 🔬 Faithful — output matches the reference TS engine (differential-tested).
  • 🎚️ Configurable — every check and severity knob lives in config.
  • 🏷️ Typed — ships py.typed; passes mypy --strict.

Install

pip install assay-forge      # → import assay
# or
uv add assay-forge

Requires Python ≥ 3.11.

Quick start

from assay import assay, AssayContext, AssayOptions, deterministic_id_generator

context = AssayContext.model_validate(work_session_dict)

result = assay(context, AssayOptions(mode="action", action="check-imp-step"))

print(result.passed)        # overall gate: no finding has severity 'error'
print(result.next_step)     # in action mode: what to do next (+ guidance)

# Canonical camelCase JSON (matches the reference engine):
result.to_json_dict()

What it computes

assay(context, options) → AssayResult: four independent checks over the normalized work-session event tables, then a single gate passed = no finding has severity 'error'.

Check Reads
acceptance-coverage acceptanceChecks[] vs the ticket's acceptance criteria
implementation-coverage implStepCompletions[] vs the ticket's implementation steps
file-action-coverage fileChanges[] vs the ticket's planned files (+ justification states)
test-result-presence testResults[] vs the ticket's test specification (+ skip/failure justification)

Two modes:

  • action — computes next_step (the next thing the agent should do) with per-finding verbosity dispatch. Requires options.action.
  • check — computes a summary (pending_by_action, total_pending, blocking_issues).

Determinism is anchored by an injectable id generator (uuid_id_generator default, deterministic_id_generator for tests) and a meta block (engine_version, generated_at, config_hash).

Configuration

from assay import load_defaults, merge_config, config_hash

config = merge_config(load_defaults(), {"checks": {"test-result-presence": {"enabled": False}}})
context = AssayContext.model_validate({**work_session_dict, "config": config})

Severity of every check (and the per-justification severity knobs) is config-driven. load_defaults() returns the packaged AssayforgeConfig (data asset at assay/config/data/defaults.yml).

Public API

from assay import (
    assay, ENGINE_VERSION,                                  # entry point
    AssayContext, AssayOptions, AssayResult,                # core models
    uuid_id_generator, deterministic_id_generator,          # id generators
    load_defaults, load_config, merge_config, config_hash,  # config helpers
    AssayforgeConfigSchema, validate_config, HARDCODED_DEFAULTS,
    CHECK_REGISTRY, get_file_action_severity,               # checks
    resolve_next_step, applicable_actions, action_applies,  # next-step
    compose_finding_guidance, compose_next_step_guidance,   # guidance
    compose_check_summary, interpolate,
    OPERATIONS, OPERATION_NAMES, is_operation_name,         # operations vocab
    types,                                                  # full type namespace
)

Development

uv sync --all-extras --dev
uv run ruff check src tests
uv run mypy
uv run pytest                 # incl. differential vs the TS engine

Fidelity is verified against committed golden output captured from the reference engine (fixtures/golden/), so CI needs no Node tooling.

Releasing

assay-forge publishes to PyPI automatically via GitHub Actions (publish.yml) using Trusted Publishing (OIDC — no API tokens stored in the repo).

One-time PyPI setup (by a maintainer): add a pending publisher for the assay-forge project — owner blacksmithers, repository assay, workflow publish.yml, environment pypi.

Cut a release:

  1. Bump the version in pyproject.toml and src/assay/__init__.py, and add a CHANGELOG.md entry.
  2. Tag and push — the tag triggers the workflow (build → twine check → publish):
    git tag v0.1.0
    git push origin main --tags
    

The workflow can also be run manually from the Actions tab (workflow_dispatch). Build locally any time with uv build (artifacts in dist/).

Status

0.1.0 — a complete port, verified byte-for-byte against the reference TS assayforge engine across the single-check and full-lifecycle flow surfaces, both action and check modes.

License

Apache 2.0 © Gabriel Augusto Gonçalves / blacksmithers

Metadata

Release files for assay-forge 0.1.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 assay-forge 0.1.0
File Size Uploaded
assay_forge-0.1.0.tar.gz 40.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for assay-forge 0.1.0
File Interpreter ABI Platform
assay_forge-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 109.2 kB

Release files / assay_forge-0.1.0.tar.gz

Download URL assay_forge-0.1.0.tar.gz
Size 40.8 kB
Tags Source
SHA-256 checksum
How to use checksums
02b63fb20ace8d032e02e3e6b8e286b958b2e81b9dbbabc000388e908a88bdea
BLAKE2b-256 checksum
How to use checksums
3586466f087deff8f1506b3f6e9dcdc8bc6a9bd8a52f109fe7352eda6cf61699
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 Jun 26, 2026.

Transparency log

Release files / assay_forge-0.1.0-py3-none-any.whl

Download URL assay_forge-0.1.0-py3-none-any.whl
Size 68.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0657b40822c61c269a4148a55017c4b24d6e1ca936bea9348c6edcb30b254dd6
BLAKE2b-256 checksum
How to use checksums
0c70ef225540b3ff58d34f16f445fa97c8d7e188bfd721e56d5e76fe81edb36f
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 Jun 26, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

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