CEQA Preflight is an early-stage, local-first command-line tool for checking the technical readiness of CEQA Submit filing packages.
It is designed to help planners, clerks, and consultants catch objective,
correctable package issues before State Clearinghouse review. It produces
source-cited advisory findings and human-review reminders; it will not submit
documents, modify originals, or determine legal sufficiency. The default
check path makes no network requests. A separate, opt-in ai command group
(ADR 0002) sends document text to a
configured model provider to draft manifest fields, explain findings, and
draft corrections; it never produces a finding and is never invoked by
check.
Status: In build
CEQA Preflight is pre-alpha software. The repository includes safe local package loading, bounded PDF
technical inspection, a source-cited declarative rule engine, a common
technical rule pack (readability, searchable text, flattened forms, structure
tags, file hygiene), and a synthetic-package generator for demos and pilot
calibration. NOD- and NOE-specific rules are currently
experimental: they run only with --include-experimental while documented
official-source review, practitioner review, tests, and a permissioned pilot
are completed.
- Local directory or ZIP input.
- NOD and NOE package checks.
- Deterministic PDF and metadata checks.
- Accessible HTML and JSON reports.
- No hosted document storage, portal scraping, or AI-driven legal analysis.
- Opt-in, model-backed drafting and explanation under ADR 0002: the model structures input and narrates cited sources; only the deterministic rule engine produces a finding, and legal-sufficiency questions are refused.
uv sync --all-groups
uv run ceqa-preflight --version
uv run ceqa-preflight --help
uv run ceqa-preflight init ./my-package --filing-type NOE
uv run ceqa-preflight init ./my-package --filing-type NOE --from-package
uv run ceqa-preflight check ./my-package --filing-type NOE --format html --output ./reports
uv run ceqa-preflight check ./my-package --filing-type NOE --include-experimental
uv run ceqa-preflight check ./pkg-a ./pkg-b --filing-type NOE --format json --output ./reports
uv run ceqa-preflight check ./my-package --filing-type NOE --format checklist
uv run ceqa-preflight synth ./demo-package --filing-type NOE --defect scanned
uv run ceqa-preflight rules list --filing-type NOE
uv run ceqa-preflight rules list --format json
uv run ceqa-preflight pilot init ./pilot-evidence
uv run ceqa-preflight pilot summarize --reviews ./pilot-evidence/finding-review.csv --baseline ./pilot-evidence/manual-baseline.csv
Without a local checkout, run the CLI directly from a clone with
uv: uvx --from /path/to/ceqa-preflight ceqa-preflight --help, or install it with pipx install /path/to/ceqa-preflight. No package registry release exists yet.
The check command reads one or more directories or ZIP packages locally,
never uploads or alters their contents, and can emit console, JSON,
self-contained HTML, or printable sign-off checklist advisory reports.
Checking several packages at once prints a per-package roll-up summary. Add
--manifest package.yaml to enable explicit primary form and
document-category checks when experimental rules are opted into (single
package only), and --rules / --exclude-rules to select specific rule
identifiers. The default run includes active technical checks only. Add
--log-format json for minimal, package-content-free operational events on
stderr, including inspection progress counts for large packages.
Every report states its own scope. Any rule that applies to the filing type but
did not run — because it is experimental and --include-experimental was not
given, because --rules or --exclude-rules removed it, or because it has been
withdrawn — is listed by identifier, with the reason and the way to run it, in
all four report formats. A report with no failures therefore always says whether
it covered every applicable check or only some of them.
The synth command generates plainly fictional synthetic packages, optionally
seeded with objective defects (scanned pages, fillable forms, encrypted or
truncated PDFs, duplicates, and more) for demos, regression tests, and pilot
reviewer calibration. See examples/ for a generated
package and its HTML report.
The pilot commands support the permissioned evaluation protocol with opaque
IDs and controlled labels only; they do not read filing packages or accept
free-text reviewer notes.
The ai command group (ADR 0002) is the
only part of the tool that talks to a model provider, and nothing else invokes
it. Install the extra (pip install 'ceqa-preflight[ai]', or [ai-bedrock]
for Amazon Bedrock), put the credential in the environment
(ANTHROPIC_API_KEY, or the AWS credential chain plus AWS_REGION), and opt
in per command. The default model is claude-sonnet-5; --provider,
--model, CEQA_PREFLIGHT_AI_PROVIDER, and CEQA_PREFLIGHT_AI_MODEL change
it. Every ai command states the data flow before it runs: the text it sends
leaves the machine for the duration of the request, and the provider's terms
apply to it. It never writes that text to a log.
uv run ceqa-preflight ai extract ./my-package --filing-type NOE --write-manifest ./my-package/package.yaml
uv run ceqa-preflight ai extract ./my-package --filing-type NOE --format json --output ./reports/extraction.json
ai extract reads each PDF's text layer through the same bounded,
process-isolated path check uses and asks the model to copy out the facts a
manifest carries: what kind of document it is, the project title, lead agency,
county, city, SCH number, exemption status and citation, and so on. Every value
must come with a verbatim quote from the document; the tool verifies the quote
against the text and withholds any value whose quote does not verify. A field
the text does not state is unknown. A scanned, image-only PDF is reported as
having no text layer and is not sent anywhere. The result is a draft
manifest for a person to review and correct; only check --manifest on the
reviewed manifest produces findings. The model structures input. It never
decides anything, and the rule engine never sees its output directly.
uv run ceqa-preflight check ./my-package --filing-type NOE --format json --output ./reports
uv run ceqa-preflight ai explain ./reports/report.json
uv run ceqa-preflight ai draft-fix ./reports/report.json --rules PDF-003,PDF-007
uv run ceqa-preflight ai ask ./reports/report.json "What does PDF-003 mean?"
ai explain and ai draft-fix read a JSON report written by check and,
for each failure, warning, or manual-review item, ask the model for a
plain-language explanation (or a numbered correction draft) in which every
claim cites a passage of the official source the rule cites and quotes it
verbatim. The passages come from corpus/, the committed,
hashed text of those sources; a verifier checks every quote against it and
checks every sentence for determination language before anything is shown.
Claims that fail are withheld and counted. A self-cited rule (FILE-004,
FILE-005) is explained from the project's own reasoning and says so.
ai ask answers questions about the findings in a report. Any form of "is
this legally sufficient", "will it be accepted", "is this exemption valid",
or "did the agency comply", in English or Spanish, direct or indirect, is
refused before the model runs and redirected to the objective findings and
to qualified review; the model is instructed to refuse as well, and its
answers pass the same verifier. The refusal suite in evals/
has zero tolerance. None of this output is a finding; check alone produces
findings, and its output is unchanged by the ai commands.
check exits 0 when no automated failure was found (warnings and
manual-review items may still exist), 1 when at least one failure finding
was produced, and 2 on input or internal rule errors. With multiple
packages, the worst exit code wins.
Exit code 0 is not a statement that every applicable check ran: skipped
checks do not change it. Read the not_run list, or the "check(s) not run"
count in the summary line, before treating a 0 as a complete result.
CEQA Preflight is an independent open-source project. It is not affiliated with, endorsed by, or operated by the State of California, the Governor's Office of Land Use and Climate Innovation, the State Clearinghouse, CEQA Submit, or CEQAnet. See DISCLAIMER.md.
make verify
This project is developed with AI-assisted tooling. Every change, whether
AI-assisted or not, must pass the same review, tests, and make verify gate
before merge; AI-assisted development measurement is tracked in the
responsible technology audits.
See CONTRIBUTING.md, SECURITY.md, and the architecture decisions (ADR 0001, ADR 0002). The project also documents its pilot protocol, pilot partner kit, accessibility boundaries, and threat model.
The command-line interface and JSON report schema are not yet stable. No
GitHub Release or package publication has been made; version 0.1.0 is the
pre-release development baseline, not a promise of production readiness.
Breaking changes may occur before the first tagged release. See
CHANGELOG.md and docs/ROADMAP.md.
This project follows the vendored Portfolio Standards. “Applies” means an automated or documented control exists; release-only evidence is collected before a tagged release.
| Standard | State | Evidence / scope |
|---|---|---|
| Responsible-Tech Framework | Applies | Responsible technology audits, threat model, and data card |
| Code Quality | Applies | Makefile gates (ruff, mypy --strict, pytest with a 90% branch-coverage floor, and complexity <= 10), uv.lock, and .python-version |
| Security & Supply-Chain | Applies | Security policy; bandit, pip-audit, gitleaks, and CodeQL in CI; SHA-pinned actions and a committed lockfile |
| CI/CD | Applies | SHA-pinned, permission-scoped workflows; CI runs the same make verify gate as local development |
| Release & Versioning | Applies — pre-release gap | The signed-tag release workflow is committed; the first public tag and its release evidence are still pending |
| Observability | N/A (no hosted telemetry) | Stateless local CLI; opt-in JSON operational events only, with no package contents |
| Performance | N/A (no service SLO) | No hosted service; bounded PDF/ZIP parsing is covered by the threat model |
| Accessibility | Applies | Accessibility boundaries; release review pending the first tag |
| Internationalization | Applies — pre-release gap | Scope and release gate; current English-only reports must gain reviewed EN/ES catalogs before a public tag |
| AI Evaluation | Applies | ADR 0002; the committed evals/ harnesses (legal-sufficiency refusal, real-filing extraction vs. CEQAnet metadata, citation grounding) with provenance-stamped results; a test rejects any result file without provenance |
| Documentation | Applies | README, CONTRIBUTING.md, CHANGELOG.md, CITATION.cff, and the definition of done |
| Quality & Metrics | Applies | Metrics ledger in the roadmap; make verify is the merge gate |
| AI Development Measurement | Applies | docs/ROADMAP.md declares AI-DEV-MEASUREMENT: APPLIES; the baseline is recorded in the responsible technology audits |
| Incident Response | Applies — local CLI scope | Security, privacy, and data-exposure incidents remain in scope even though there is no hosted service |
| Data Governance | Applies | The local filing package data card defines the processing and retention boundary |
The central standards register is maintained separately and must be updated when this repository is published.