zendev-proposal
zendev-proposal is a stateless validator and indexer for repositories that
store durable design proposals as Markdown. Git history remains the lifecycle
record, YAML frontmatter remains structured proposal metadata, templates remain
the required-section authority, and the committed JSON index remains a derived
projection.
The tool owns repository mechanics:
- safe YAML frontmatter parsing with duplicate-key rejection
- JSON Schema validation
- filename, number, title, H1, and Executive Summary consistency
- required H2 sections derived from repository templates
- frontmatter-bearing drafts with either the formal or a dedicated draft schema
- proposal relation integrity and inverse index edges
- optional Git-backed deletion, number-reuse, initial-state, and transition checks
- deterministic index checking and explicit writes
- stable human and JSON diagnostics
Project terminology, normative semantics, process authority, and acceptance decisions stay in each proposal repository.
Install this tool independently of the commit workflow:
$ uv add --dev zendev-proposal
For an ad hoc run, use uvx --from zendev-proposal zendev-proposal ....
The complete zendev distribution exposes the same application through its
unified command:
$ uvx --from zendev zendev proposal --help
Python API
from zendev.proposal import load_config, validate_repository
config = load_config("proposal.toml")
result = validate_repository(config)
if not result.ok:
for diagnostic in result.diagnostics:
print(diagnostic)
Repositories using prek can install the published hook directly:
[[repos]]
repo = "https://github.com/zendev-lab/zendev"
rev = "v0.2.0"
hooks = [
{ id = "zendev-proposal" },
]
Commands
Run commands from the proposal repository root or pass an explicit config:
$ zendev-proposal check [--config proposal.toml] [--base-ref REF] [--json]
$ zendev-proposal index [--config proposal.toml] (--check | --write) [--json]
check uses PROPOSAL_BASE_REF when --base-ref is absent. History validation
is disabled when neither is present. An explicitly requested ref must exist
locally; the tool fails closed instead of silently skipping lifecycle checks.
Exit codes have stable meanings:
0: repository is valid and the requested index operation succeeded1: proposal documents, graph, history, or committed index are invalid2: the tool could not load its configuration, schema, templates, or Git ref
check and index --check never write. index --write validates proposal
documents before replacing the configured index.
Policy file
The root policy is versioned TOML. This example models numbered VEP documents, lightweight pre-VEP drafts, a proposal graph, and Git lifecycle transitions:
version = 1
[proposal]
prefix = "VEP"
number_field = "vep"
title_field = "title"
type_field = "type"
status_field = "status"
documents_dir = "veps"
schema = "schemas/vep.schema.json"
index = "veps-index.json"
number_width = 4
metadata_title = "plain"
filename_slug_pattern = "[a-z0-9]+(?:-[a-z0-9]+)*"
[drafts]
directory = "drafts"
schema = "schemas/draft.schema.json"
marker = "> Pre-VEP design draft. Non-normative."
pre_proposal = true
[templates]
Technical = "templates/technical.md"
Process = "templates/process.md"
Informational = "templates/informational.md"
[summary]
prefix = "**Executive Summary:**"
minimum_sentences = 2
maximum_sentences = 4
[graph]
fields = ["requires", "amends", "supersedes"]
requires_field = "requires"
amends_field = "amends"
supersedes_field = "supersedes"
accepted_status = "Accepted"
superseded_status = "Superseded"
[history]
initial_status = "Draft"
protect_records = true
bootstrap_numbers = [0]
[history.transitions]
Draft = ["Draft", "Review", "Withdrawn"]
Review = ["Review", "Draft", "Accepted", "Rejected", "Withdrawn"]
Accepted = ["Accepted", "Superseded"]
Rejected = ["Rejected"]
Withdrawn = ["Withdrawn"]
Superseded = ["Superseded"]
[index]
version = 1
entries_key = "veps"
include_drafts = false
[[index.fields]]
name = "vep"
source = "metadata"
key = "vep"
[[index.fields]]
name = "id"
source = "identifier"
[[index.fields]]
name = "path"
source = "path"
[[index.fields]]
name = "requires"
source = "metadata"
key = "requires"
[[index.fields]]
name = "required_by"
source = "inverse"
key = "requires"
All configured paths must remain under the repository root. The schema and
template files must already exist. Unknown policy keys are rejected so a typo
cannot silently disable a check. The index may be absent before the first
explicit index --write.
Title modes
metadata_title = "plain" keeps the identifier out of metadata and constructs
the formal H1 as # VEP-0001: Title. metadata_title = "prefixed" expects the
metadata title itself to start with VEP-0001: and requires the H1 to equal that
title. The second mode supports existing processes that treat the display title
as a single frontmatter field.
Draft policy
Omit [drafts] when the repository does not use proposal drafts. When it is
configured, drafts use YAML frontmatter. drafts.schema selects a dedicated
schema; if omitted, drafts use the formal proposal schema. A draft must not
assign a proposal number, its H1 must match its metadata title, and an optional
marker must immediately follow that H1.
pre_proposal = true models non-normative exploration before the proposal
lifecycle. It additionally forbids status metadata, status sections, and
concrete proposal IDs in the body. A lifecycle draft instead uses
pre_proposal = false and must have status: Draft.
require_summary = true applies the configured Executive Summary policy to
drafts. Set index.include_drafts = true only when drafts belong in the
committed index. These independent settings cover the actual VEP pre-proposal
and SEP lifecycle-draft contracts without teaching the shared tool their
repository-specific schemas.
Templates
Each key under [templates] is a value accepted by the configured proposal
type field. Every H2 heading in that template is required in documents of that
type. Headings inside fenced code blocks are ignored. This keeps the executable
section policy in the template instead of duplicating it in Python.
Graph policy
Every configured graph field rejects duplicate, self, and missing-target edges.
Integer references and canonical strings such as VEP-0001 are normalized to
the same identifier.
The optional role fields add semantic checks:
requires_field: rejects cycles and prevents an accepted proposal from transitively requiring a non-accepted proposalamends_field: requires amendment targets to remain acceptedsupersedes_field: validates coordinated supersession and exactly one accepted forward superseder
Index fields with source = "inverse" derive reverse edges without storing two
writable authorities in proposal frontmatter.
History policy and waivers
When a base ref is supplied, the tool verifies that formal records were not
deleted, proposal numbers were not reused under another path, new proposals
begin at initial_status, and existing statuses follow the transition table.
Bootstrap proposal numbers may bypass the initial-status rule. A repository can also preserve a narrow historical exception without hard-coding it into the shared tool:
[[history.waivers]]
path = "proposals/VEP-0001-example.md"
from_status = "Accepted"
to_status = "Draft"
reason = "One-time bootstrap correction recorded by Process VEP-0000."
Waivers apply only to the exact path and transition. Their required reason is a review record, not a second lifecycle state.
Index fields
Index fields are emitted in configuration order. Supported sources are:
metadata: copy the configured frontmatterkeyidentifier: emit the formatted prefix and numberpath: emit the repository-relative Markdown pathinverse: derive sources that point to this proposal through relationkey
Documents sort by number, followed by unnumbered frontmatter drafts sorted by path. JSON uses UTF-8, two-space indentation, and one trailing newline.
JSON diagnostics
--json emits one object to standard output:
{
"command": "check",
"diagnostics": [
{
"code": "proposal.index.drift",
"hint": "Run `zendev-proposal index --write` and commit the result.",
"line": null,
"message": "committed proposal index is missing or out of date",
"path": "veps-index.json"
}
],
"ok": false,
"schema_version": 1,
"summary": {
"drafts": 3,
"formal_proposals": 1,
"index": "drifted"
}
}
Diagnostic codes and JSON keys are the automation contract. Human messages may be made more actionable without requiring consumers to parse prose.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file zendev_proposal-0.2.0-py3-none-any.whl.
File metadata
- Download URL: zendev_proposal-0.2.0-py3-none-any.whl
- Upload date:
- Size: 23.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b3eab019262bec65f48baed76a21c6064c7ce621ab0bccb0d4cb129dba45bb7c
|
|
| MD5 |
b14a700d7bfbd020c61cfec552947b6e
|
|
| BLAKE2b-256 |
4d6651f90591fba9d0ec85011fb397b7d67b2cdc14f1c885e5d2353e5e1c2b49
|