Byspec
Documentation

CLI reference

The byspec binary is a thin shell over the @byspec-dev/core library; every command is one library call plus formatting and an exit code. byspec <command> --help prints each command's flags.

text
parse [specs...]             Parse spec files and print their criteria
lint [specs...]              Clarity gate: EARS conformance, ambiguity scoring, optional LLM rewrite
verify [specs...]            Verify a repository against the acceptance criteria in its spec
report [run_id]              Render a saved run (default: the last run)
export [run_id]              Write a self-verifying evidence packet (tar.gz) for a saved run, or verify one
ledger verify | show | keygen
                             Inspect the hash-chained verdict ledger; generate a signing key
import <file>                Convert a Kiro requirements.md or Spec Kit spec.md into a Byspec spec
prepare                      Run the sandbox.prepare command from byspec.config.yaml before verifying
init                         Write byspec.config.yaml, specs/README.md, .byspec/.gitignore and an example spec
mcp                          Run the Byspec MCP server over stdio (tools: lint, verify, explain_failure)

Global flags: --repo <path> (default: the git top level of the current directory, or the current directory outside git), --config <path>, --provider bedrock|anthropic|mock, --format text|json|md|github|agent, --quiet, --verbose, --version. Spec arguments resolve relative to the current directory; paths inside checks resolve relative to --repo, where .byspec/ is created and where fixtures/mock-provider/ is looked up. When no spec is given, config.spec_globs is used.

CommandPurposeFlags
byspec initWrite byspec.config.yaml, specs/README.md, .byspec/.gitignore, an example spec. Refuses to overwrite.--force, --agents
byspec parse [specs...]Parse and print criteria.--format json prints { criteria, count }
byspec lint [specs...]Clarity gate: EARS conformance and ambiguity scoring.--min-score <n>, --rewrite
byspec verify [specs...]Run verification.--changed, --base <ref>, --head <ref>, --only <ids>, --no-cache, --no-judge, --fail-on-unverifiable, --frozen-spec, --out <path>, --no-ledger
byspec report [run_id]Render a saved run (default: the last).`--format jsonmdgithubagent`
byspec export [run_id]Write a self-verifying evidence packet, or verify one.--out <file>, --verify <file>
byspec ledger verifyRecompute every hash and prev_hash link; verify signatures.--require-signatures
byspec ledger keygenGenerate an ed25519 signing key pair for the ledger.--force
byspec ledger showPrint entries.--criterion <id>, --last <n>
byspec import <file>Convert a Kiro requirements.md or Spec Kit spec.md into BSF.--from kiro|speckit, --out <path>, --format json
byspec mcpRun the MCP stdio server.--list-tools, --root <dirs...> (directories the tools may operate in; a repo argument outside them is refused)

byspec verify flags#

FlagMeaning
--changedOnly criteria touched by the diff (base...head); everything else is SKIPPED.
--base <ref>Diff base. Default: the merge base with run.base_ref; no merge base is an error (exit 3).
--head <ref>Diff head. Default: the working tree, including uncommitted and untracked files.
--only <ids>Comma-separated criterion ids to verify.
--no-cacheBypass the check and judge caches.
--no-judgeNever call the model; judge criteria become UNVERIFIABLE.
--fail-on-unverifiableExit 2 when any criterion is UNVERIFIABLE.
--frozen-specExit at least 1 when a spec file is in the diff (with --changed).
--out <path>Also write the JSON run report to this file.
--no-ledgerRecord nothing in the ledger (what cloud sandboxes use).
--repo <path>Repository root (global; see above).
--config <path>Config file (global; default byspec.config.yaml in the repository).
--provider <kind>bedrock, anthropic or mock (global; overrides provider.kind).
--format <format>text, json, md, github or agent (global).
--quiet / --verboseErrors only, or debug output including the cause chain of an error (global).

Errors print a hint: line for the common causes (no AWS credentials, judge model not enabled, shallow clone without a merge base, ledger lock held, missing mock fixture); --verbose adds the full cause chain.

With --format json, stdout carries exactly one JSON document; diagnostics go to stderr. The verify document is the Run and validates against packages/core/schema/run.schema.json. When GITHUB_STEP_SUMMARY is set, verify appends the GitHub report to it.

Lint rules#

byspec lint scores each criterion from 100 with deductions per rule and prints one line per violation (ABC-001 warning L05_VAGUE vague term: quickly). Errors (L01 id, L02 ears present, L03 declared pattern matches detected pattern, L04 contains "shall", L10 judge has a rubric) always exit 2; so does a document score below --min-score (default 80). Warnings cover vague terms (L05: fast, easy, robust, correctly, …), empty verify (L06), compound requirements with two "shall"s (L07), comparatives without a number (L08), and judge-only verification (L09, info). --rewrite asks the model for EARS-conformant rewrites and writes them to a sibling <name>.rewrite.md; the original file is never modified.

Exit codes#

CodeMeaning
0No FAIL; no UNVERIFIABLE when --fail-on-unverifiable
1At least one FAIL, or --frozen-spec with a spec file in the diff
2UNVERIFIABLE present with --fail-on-unverifiable; or lint below --min-score or with errors
3Config, spec parse, or spec schema error (nothing ran)
4Provider error that prevented the run from completing (auth, region, quota)

Source: README.md § CLI

Configuration#

byspec.config.yaml at the repository root. Every key has a default; an empty file is valid.

yaml
version: 1
spec_globs: ["specs/**/*.spec.md"]
test_command: "pnpm vitest run"
provider:
  kind: bedrock            # bedrock | anthropic | mock
  region: us-east-1
  model: "us.anthropic.claude-sonnet-4-5-20250929-v1:0"   # judge + rewrite
  fast_model: "us.anthropic.claude-haiku-4-5-20251001-v1:0" # reserved for v2
judge:
  min_confidence: 0.7
  max_evidence_bytes: 20000
lint:
  min_score: 80
  vague_terms: []          # appended to the defaults; replace_vague_terms: true replaces them
run:
  check_timeout_s: 120
  parallel: 4              # concurrency for file/grep/symbol/schema checks
  base_ref: origin/main    # merge base for --changed (no merge base is an error, exit 3)
ledger:
  path: .byspec/ledger.jsonl
  store: jsonl             # jsonl | none (none records nothing; same as --no-ledger)
  signer: none             # none | ed25519-file | kms
  key: .byspec/ledger-key.pem   # ed25519-file: private key; the public key sits next to it as .pub
  kms_key_id: ""           # kms: key ARN or alias (used by the cloud control plane)
  trusted_keys: []         # public keys verification may trust: [{ key_id, fingerprint }]; keygen appends

Environment overrides: BYSPEC_PROVIDER, BYSPEC_MODEL_ID, BYSPEC_REGION (or AWS_REGION), BYSPEC_CONFIG. Precedence: CLI flags over environment over file over defaults. The config schema has no field for credentials.

Providers#

  • bedrock (default): @aws-sdk/client-bedrock-runtime Converse with a forced tool for structured output. Credentials come from the default AWS credential chain only. Verify the model ids are enabled in your region before relying on them.
  • anthropic: the Anthropic API with a forced tool. ANTHROPIC_API_KEY from the environment only.
  • mock: deterministic, no network. Judge prompts get PASS at confidence 0.95 unless a fixture at fixtures/mock-provider/<sha256 of the user message>.json says otherwise; every prompt is recorded to .byspec/cache/mock-prompts/. Used by tests and by the required CI check.

Judge calls are cached in .byspec/cache/judge/ by rubric, evidence hashes, model id, and prompt version. --no-cache bypasses both the judge cache and the deterministic check cache.

Source: README.md § Configuration

Working state#

.byspec/
├── ledger.jsonl           # append-only, hash-chained; commit it
├── last-run.json          # the last Run
├── runs/<run_id>.json
├── evidence/<run_id>/<criterion_id>/<n>-<kind>.txt   # mode 0600; gitignored
└── cache/                 # gitignored

Each ledger entry records the run, spec hash, commit, criterion, actor (human, agent, or ci, with the tool when known), verdict, per-check status with evidence hashes, judge summary, and prev_hash/hash. byspec ledger verify recomputes the chain and reports the first bad entry. Set BYSPEC_ACTOR=agent:claude-code (or ci:github-actions) to record the actor explicitly; otherwise it is detected from well-known environment variables and defaults to human.

Signed ledgers#

A hash chain proves order and integrity; a signature proves who wrote it. Entries can carry a signature over their hash, kept outside the hashed payload so signing never changes a hash.

sh
byspec ledger keygen                       # writes .byspec/ledger-key.pem (0600) and .byspec/ledger-key.pub,
                                           # and records the key's fingerprint under ledger.trusted_keys

Then set ledger.signer: ed25519-file in byspec.config.yaml. Every verify run signs its entries with the private key; a missing key fails the run with exit 3 rather than writing unsigned entries. Commit the .pub file and the trusted_keys entry next to the ledger and keep the .pem out of git.

sh
byspec ledger verify                       # hashes, links, and every signature that is present
byspec ledger verify --require-signatures  # also fails on unsigned entries (exit 1)

Verification looks up public keys by the entry's key_id among .byspec/*.pub, so a chain signed by several keys over time still verifies. Only keys whose fingerprint (sha256 of the DER public key) appears in ledger.trusted_keys count; a .pub that is not listed is ignored with a warning, so a rogue key committed alongside a re-signed ledger cannot satisfy --require-signatures. ledger.signer: kms (asymmetric ECDSA P-256 in AWS KMS, kms_key_id) is what the cloud control plane uses for tenant ledgers; the CLI does not sign with KMS locally. ledger.store: none, or --no-ledger, runs verification without touching a ledger at all, which is how the cloud sandbox runs the CLI while the control plane owns the chain.

Evidence packets#

sh
byspec export                              # byspec-<run_id>.tar.gz for the last run
byspec export <run_id> --out audit.tar.gz
byspec export --verify audit.tar.gz        # "packet ok" and exit 0, or the problems and exit 1

A packet is a tar.gz containing run.json (the Run exactly as saved), ledger.jsonl (this run's entries, in order, with signatures), evidence/<criterion>/<n>-<kind>.txt, spec/<file> for every spec file the run parsed, and manifest.json written last with the sha256 and size of every other file, the run's ledger sub-chain summary, and the public keys needed to verify signatures. A packet is therefore self-verifying: --verify recomputes every hash, re-links the sub-chain, and checks each signature offline, with no access to the repository or to AWS. Packets produced by the cloud control plane (Section "Cloud control plane") have the same layout and verify with the same command.

Source: README.md § Working state

Path resolution and output discipline#

Binary: byspec. Global flags: --repo <path> (default cwd), --config <path> (default byspec.config.yaml in repo), --provider bedrock|anthropic|mock (overrides config), --format text|json (default text), --quiet, --verbose.

Path resolution: spec file arguments resolve relative to the current working directory; every path inside a check or evidence item resolves relative to --repo; .byspec/ is created under --repo. This is why Section 21 commands pass a spec from fixtures/specs/ with --repo fixtures/repos/<name>.

Commands#

CommandPurposeNotable flags
byspec initWrite byspec.config.yaml, specs/README.md, .byspec/.gitignore, and an example spec. Refuses to overwrite.--force, --agents (Section 22.3)
byspec import <file>Convert a Kiro requirements.md or Spec Kit spec.md into BSF (Section 22.2).--from kiro|speckit, --out <path>, --format json
byspec parse <spec...>Parse and print criteria (used by tests and by other tools).--format json prints { criteria: Criterion[], count }
byspec lint <spec...>Clarity gate.--min-score N, --rewrite, --format json
byspec verify <spec...>Run verification.--changed, --base <ref>, --head <ref>, --only <ids>, --no-cache, --no-judge, --fail-on-unverifiable, --frozen-spec, --out <path>, --format agent
byspec mcpRun the MCP stdio server (Section 22.4).--list-tools
byspec report [run_id]Render a saved run (default last).`--format jsonmdgithubagent`
byspec ledger verifyRecompute the chain.
byspec ledger showPrint entries.--criterion <id>, --last N

<spec...> defaults to config.spec_globs when omitted.

Text output#

verify prints one line per criterion: PASS RUN-004 Nonzero exit on any FAIL (3 checks, 1.2s) with FAIL lines followed by an indented reason and the failing check's first 5 evidence lines. A summary line closes: 12 pass, 1 fail, 2 unverifiable, 4 skipped → exit 1. Colors only when stdout is a TTY.

JSON output#

--format json prints exactly one JSON document to stdout and nothing else on stdout (diagnostics go to stderr). The document for verify is the Run (Section 5.7) and validates against packages/core/schema/run.schema.json, which is generated from the zod schema at build time (zod-to-json-schema) and committed.

Exit codes#

CodeMeaning
0No FAIL; no UNVERIFIABLE when --fail-on-unverifiable
1At least one FAIL
2No FAIL, but at least one UNVERIFIABLE and --fail-on-unverifiable set; or lint below --min-score or with errors
3Config, spec parse, or spec schema error (nothing ran)
4Provider error that prevented the run from completing (auth, region, quota)

Verdict precedence for the exit code: FAIL beats UNVERIFIABLE. With --frozen-spec, a run whose report has spec_changed_in_diff: true exits with at least 1 (Section 22.5).


Source: docs/byspec-implementation-spec.md § 12. CLI