bearout 0.2.0

A programmable contract engine for linked resources, documentation, and code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
# Bearout

Bearout is a deterministic repository contract engine. A repository keeps
prose and structured metadata together as resources; Bearout discovers
them, validates their shape, resolves their relationships, applies the
repository's own rules, and generates artifacts from the verified graph,
with provenance.

A *contract* here is a machine-checkable agreement about the resources in a
repository. It is not necessarily a legal contract.

> [!WARNING]
> Bearout is an experiment. Its bootstrap, resource envelope, shape
> vocabulary, Starlark ABI (version 0), diagnostic codes, and generated
> outputs are not stable yet. Do not build a compatibility promise on the
> current syntax.

## Two layers

- The **Rust kernel** owns discovery, parsing, graph construction,
  diagnostics, resource limits, path confinement, and filesystem writes.
- The **repository** owns every domain rule: schema identifiers and their
  JSON Schema shapes, validators, project checks, and generation plans in
  Starlark, and templates.

A schema identifier such as `example/decision-records/decision@1` belongs to
the repository that defines it. Nothing is registered with Bearout or
compiled into the binary. See [`docs/design.md`](docs/design.md) for the
responsibilities, phases, and boundaries.

## The bootstrap

`bearout.toml` is static and is the capability boundary of a project:

```toml
version = 1
entry = "bearout.star"

[resources]
roots = ["records"]

[rules]
root = "rules"          # `load()` resolves here; shapes live here

[templates]
root = "templates"

[outputs]
roots = ["generated"]   # the only places generation may write
license = "Apache-2.0"  # stamped into generated headers

[documents]             # optional; schema-less Markdown, read-only
roots = ["docs"]        # walked recursively for `*.md`
files = ["README.md"]   # named one by one

[fixtures]              # optional; contract fixture files for `bearout test`
files = ["contract-tests/log.test.toml"]

[limits]                # optional; see docs/design.md for which defaults are measured
ticks = 1000000
template_fuel = 2000000
```

Repository policy can register schemas, checks, and generators. It cannot
widen the roots the bootstrap grants. Resource, rules, templates, and
output roots are disjoint, none is the project root, and all filesystem
access goes through a capability opened on the project root. The document
grant is read-only and may overlap any of them.

## Repository policy

The entry module registers what the project uses:

```python
load("decision.star", "validate_decision")
load("log.star", "check_supersession_is_reciprocal")
load("decision-index.star", "plan_decision_index")

schema("example/decision-records/decision@1",
       shape = "decision.schema.toml", validate = validate_decision)
check("supersession-is-reciprocal", check_supersession_is_reciprocal)
generator("decision-index", plan_decision_index)
```

A validator receives one frozen resource view and returns findings; a check
receives the project view; a generator returns outputs:

```python
def validate_decision(resource):
    if resource["fields"]["status"] == "accepted" and not rulings(resource):
        return [error("an accepted record must carry a ruling", code = "rulings-required")]
    return []
```

`load()` resolves only beneath the rules root; escapes, symbolic links,
and cycles are rejected with the import chain. Every module is linted and
statically typechecked, and every evaluation runs under tick, heap, and
call-stack limits with cancellation. Scripts have no filesystem,
environment, network, clock, or random access. The full ABI is in
[`docs/starlark-abi.md`](docs/starlark-abi.md).

## Resources and shapes

A resource is Markdown with TOML front matter, or a header-only TOML file:

````markdown
+++
schema = "example/decision-records/decision@1"
id = "decision-0004"
title = "Records are numbered when merged"
status = "accepted"
date = "2026-09-02"
supersedes = ["decision-0002"]
+++

# Records are numbered when merged

## Context

### decision-0004-ruling-01

```toml bearout=ruling
id = "decision-0004-ruling-01"
text = "A record receives its sequence number when it is merged."
```
````

`schema`, `id`, and `refs` are the envelope keys the kernel owns. Every
other key is validated by the schema's shape, a JSON Schema 2020-12
document authored in TOML with a small `x-bearout` vocabulary:

```toml
"$schema" = "https://json-schema.org/draft/2020-12/schema"
type = "object"
additionalProperties = false
required = ["title", "status", "date"]

[properties.supersedes]
type = "array"
items = { type = "string" }
"x-bearout" = { ref = "example/decision-records/decision@1" }  # typed relation

["x-bearout"]
sections = ["Context"]                                          # required heading

["x-bearout".fragments.ruling]                                  # a fragment kind
type = "object"
required = ["id", "text"]
```

The vocabulary itself is validated: an unknown `x-bearout` key, a relation
on a non-string property, or a shape that declares an envelope key is an
error. Markdown bodies are parsed with Comrak; headings get GFM anchors,
fenced blocks tagged `bearout=<kind>` become typed fragments with
project-wide identifiers, and every relative link and `#anchor` is resolved.

## Schema-less documents

Ordinary Markdown files such as a README, governance notes, or design
documents carry no envelope and get no schema, identifier, or shape. An
explicit `[documents]` grant selects them: `roots` are walked recursively
for `*.md`, never following links or entering submodules, and `files` are
named one by one; nothing else is discovered, and a path that resource
discovery already claims is processed once, as a resource. Documents are
parsed with the same Comrak model as resource bodies: headings with GFM
anchors, explicit `<a id>` and `<a name>` anchors, links with their visible
text, and images with their alt text.

Every link and image of a resource or document is resolved against the
project tree: relative targets from the source's directory, `/`-prefixed
targets from the project root, `#fragment` within the source, and a
fragment on another Markdown file against that file's heading and explicit
anchors. A fragment on a Markdown file that is neither a resource nor a
selected document is reported rather than assumed valid; an image must
name an existing file; a broken reference is B011. Policy sees the
documents as `project["documents"]` and may report findings against a
document `path` and line. The [`document-references`](samples/document-references/)
sample shows the whole slice. Bearout assigns no meaning to any document
name or directory; which files matter is the repository's decision.

## Phases

bootstrap, discovery, parsing, structural validation, graph construction,
repository policy, generation planning, rendering, delivery. A resource that
fails parsing or structural validation is never passed to a validator, and
its identifiers still resolve so nothing cascades. Checks run only on an
error-free graph; generation runs only on an error-free project.

## Generation

Scripts never write files. A generator returns `output(template, path,
context)` entries; the kernel validates every path against the output
roots, renders every artifact into memory with MiniJinja in strict mode,
computes BLAKE3 digests, and only then delivers each file through an atomic
rename. `bearout-state.toml` records which outputs Bearout owns and their
provenance; `bearout generate --check` reports missing, stale, unowned,
orphaned, and re-owned outputs. A file the manifest does not own is never
overwritten, even when its bytes already match; orphans are removed only
when the manifest proves Bearout wrote them and they are unmodified. The
report's `outputs` list names delivered or verified files only when
generation succeeded. Third-party content is recorded in
[`NOTICE.md`](NOTICE.md).

## Commands

```sh
bearout check [path]                    # exit 0 clean, 1 findings, 2 fatal
bearout generate [path]                 # check, then deliver outputs
bearout generate --check [path]         # check, then verify committed outputs
bearout --format json check [path]      # one JSON report for every outcome
bearout check --index [path]            # check what a commit would record
bearout check --revision v1.2 [path]    # check one commit, tag, branch, or tree
bearout generate --check --index [path] # verify outputs as staged
bearout check --index --baseline HEAD   # and compare with an exact revision
bearout --allow-formatters check [path] # also run the declared formatters
bearout --allow-formatters format [path] # rewrite selected files in place
bearout test [path]                     # run the declared contract fixtures
bearout test --index [path]             # the suite, policy, and payloads as staged
bearout history range [path] --base REV # check the commits in REV..HEAD
bearout history message [path] --file .git/COMMIT_EDITMSG  # the commit-msg hook
```

Diagnostics use stable codes and forward-slash project-relative paths on
every platform; the catalog and its stability policy are in
[`docs/diagnostics.md`](docs/diagnostics.md).

## Sources

> [!WARNING]
> The Git-backed sources are experimental and require the `git`
> executable on `PATH`. Their flags, semantics, and report fields may
> change.

Without a selection, Bearout reads the live working directory through its
filesystem capability, as before; it makes no snapshot, so concurrent edits
are visible to a run. Two read-only sources read a Git tree instead, and
every input of the run comes from that tree and nothing else: the
bootstrap, the entry module and everything it loads, shapes, resources,
templates, `bearout-state.toml`, the generated outputs that
`generate --check` verifies, and the files that links resolve against. A
file that exists only in the working directory never satisfies a lookup in
a Git-backed run.

- `--index` reads the Git index of the repository that owns the project,
  from one private copy taken when the run starts: the tree a commit would
  record. Staged additions and modifications are present; unstaged
  modifications, untracked files, staged deletions, and intent-to-add
  entries are absent; a staged rename appears only at its destination. An
  unmerged index is a fatal outcome. In a partial-commit hook, Git's
  `GIT_INDEX_FILE` is honoured when it is a regular file directly inside
  the repository's own Git directory.
- `--revision <rev>` reads one commit, tag, branch, or tree object. The
  name is resolved exactly once, at the start; the resolved tree identity
  is recorded in the JSON report's `source` field and used for the whole
  run even if the branch moves meanwhile. An unknown name is a fatal
  outcome.

Either way the JSON report's `source` carries a deterministic `digest` of
the captured entries, equal for identical content from either source, so
a report can be tied to exactly what it examined. Git runs with a fixed
environment: variables that redirect the repository, its objects, or its
configuration are dropped, replacement objects and lazy fetching are
disabled, and nothing is fetched or written.

The project may sit below the repository root, including in a linked
worktree; only paths beneath the project are exposed, and a symbolic link
in a Git tree resolves only inside the tree it is read from. Submodules are
never entered. Blobs are read exactly as Git stores them, without
line-ending conversion or filters, and nothing is checked out.

Both sources are read-only: `check` and `generate --check` accept them,
`generate` without `--check` refuses them with exit code 2. Git support does
not make Bearout a security sandbox, and scripts do not learn which source
a run reads.

## Comparison

> [!WARNING]
> Comparison is experimental, like the Git-backed sources it builds on.

`--baseline <rev>` (library: `Options::baseline`) makes a run compare its
candidate, whatever source that is, with one exact Git revision of the
same repository. Nothing is inferred: no `HEAD`, parent, merge base, or
default branch. The name is resolved once, the candidate is checked as
usual, and the baseline is read-only historical evidence that is never
written and whose policy is never executed. The candidate's policy is the
only policy that runs: the baseline's own `bearout.toml` decides which
paths that revision classified as resources and documents, the
candidate's limits bound both sides, and the candidate's schemas and
shapes validate both. A revision without a `bearout.toml` is an empty
history; a baseline whose history the current policy cannot interpret is
reported on the baseline side, with `"side": "baseline"` in JSON and a
`baseline:` prefix in text, and fails the run.

Policy sees `project["comparison"]`, `None` without a baseline: the
historical `baseline` view with the same resource and document shapes as
the candidate, and `changes`, deterministic facts over the contract
surface (the bootstrap, the discovered resources, and the discovered
documents; not the whole repository) as `added`, `removed`, or `modified`
paths with each side's classification, digest, and size. A rename is a
removal plus an addition; resources pair through their ids. A check may
report against either side with `side="baseline"`, so a deleted record
can be named. What is immutable, when, and what may still change is the
repository's policy, never the kernel's; the
[`decision-records`](samples/decision-records/) sample shows one such
rule. The report carries the resolved baseline identity as `baseline`.

## Hygiene and formatting

> [!WARNING]
> The hygiene grant, the formatter declarations, the `format` command,
> and the related report fields are experimental.

Bearout natively enforces the byte-level hygiene every text file shares
and delegates syntax-aware formatting to programs the repository pins. An
explicit `[hygiene]` grant selects the files: `scope = "repository"` is
every file of the project as Git knows it (the captured index or revision
tree; for the working directory the tracked plus untracked, non-ignored
files), `scope = "declared"` is only the listed `roots` and `files`;
`exclude`, `binary`, and `text` refine any selection by path, and no
extension carries kernel meaning. A file is binary when declared so or
when a NUL byte occurs in its first 8 KiB; binary files are never checked
or rewritten.

```toml
[hygiene]
scope = "repository"
exclude = ["generated"]
binary = ["assets"]

[[formatters]]
name = "python"
command = ["ruff", "format", "--stdin-filename", "{path}", "-"]
extensions = ["py"]
support = ["ruff.toml"]
```

Text rules come from the `.editorconfig` files of the selected tree,
never from the live checkout during an index or revision check: `charset`
(`utf-8` or `utf-8-bom`), `end_of_line`, `insert_final_newline` (exactly
one final newline; an empty file is exempt and never changed), and
`trim_trailing_whitespace` (set it to `false` for Markdown hard breaks). A
value Bearout cannot enforce is reported, not guessed; the supported
subset is documented in [`docs/design.md`](docs/design.md), and complete
EditorConfig compatibility is not claimed.

A formatter is a strict byte transform: the selected file's exact bytes go
to the program on standard input, its standard output is the canonical
form, and a difference is one diagnostic. The program runs from an
argument vector with `{path}` replaced by the project-relative path, from
a private working directory holding only the declared `support` files
read from the selected tree, without color, sequentially, with bounded
streams and a timeout. Running formatters needs `--allow-formatters`,
because a formatter is a trusted host program outside Starlark's
capability model: Bearout confines what it sees, not what it can do, and
checking formatter declarations from untrusted authors is not a supported
security boundary. The formatter's version is an input to
reproducibility; run Bearout inside the environment that pins it, such as
`mise exec -- bearout --allow-formatters check`. Bearout never reads
`mise.toml` and installs nothing. General linting is deferred: linters
produce tool-specific findings that need a separate design.

`bearout format` rewrites selected files of the working directory after
computing every change, applying native normalization before the
formatter, replacing each file atomically with its permissions preserved
and only if it still holds the bytes that were read, and undoing
completed replacements if a later one fails. Nothing is created or
deleted, links are never followed, index and revision sources and
comparison baselines are never formatted, and `generate` never rewrites
sources.

## Contract fixtures

> [!WARNING]
> The fixture vocabulary, the `test` command, and the test report are
> experimental.

`bearout test` proves a repository's policy against controlled mutations
of the selected source without changing anything. An explicit
`[fixtures] files` grant names the fixture files one by one; nothing is
scanned for, and `check`, `generate`, and `format` never execute them.
Each fixture file holds named cases:

```toml
[[cases]]
name = "deleting a rejected record is caught against the unmodified log"
expect = "diagnostics"          # or "clean" or "fatal"
baseline = true                 # compare with the unmodified source
match = "exact"                 # the default; or "contains"

[[cases.mutations]]
delete = "records/decision-0005.md"

[[cases.diagnostics]]
code = "B015"
side = "baseline"
path = "records/decision-0005.md"
rule = "protected-record-deleted"
```

A case derives its candidate from the selected source by applying its
mutations in order through a read-only overlay: `write` replaces or
creates one regular file from inline `content` or a project-relative
`payload` file of the selected source, `delete` removes one regular file,
and `move` relocates one to a path that does not exist. Each path is
touched once per case and never above or beneath another touched path,
nothing beneath a file or through a symbolic link is touched, and every
conflict in any case is refused before the first case runs. Every
case starts from the same unchanged source; the working directory, the
index, Git objects, and the fixture files are never written.

`expect` names the outcome class: `clean` (no diagnostic at all),
`diagnostics`, or `fatal`, optionally with `fatal = "text"` the fatal
message must contain. Expected diagnostics are structured, never rendered
text: `code` is required, and `severity`, `path`, `line`, `side`
(`candidate` or `baseline`), the repository `rule`, and the exact
`message` are optional. They are matched as a multiset, so a repeated
diagnostic needs a repeated expectation. `match = "exact"`, the default,
also fails the case on any diagnostic it did not expect; `match =
"contains"` allows unrelated diagnostics. A contract diagnostic is test
data and fails a case only when unexpected.

With `baseline = true` the unmodified selected source is the comparison
baseline and the overlaid candidate is the candidate, with the Phase 3
authority: the candidate's bootstrap and policy interpret both sides, and
`project["comparison"]` holds the historical view and the change facts.
Policy sees an ordinary project and an ordinary comparison; nothing
exposes fixtures, mutations, or the overlay to Starlark.

The suite, payloads included, is read from the selected source before any
mutation is applied, so `--index` and `--revision <REV>` test exactly what
is staged or committed: an unstaged correction cannot hide a broken
staged fixture, and an untracked payload cannot satisfy an index fixture.
`limits.fixture_cases`, `limits.fixture_mutations`, and
`limits.fixture_bytes` bound a suite. A bootstrap that declares
formatters needs `--allow-formatters` before any case runs; nothing is
authorized silently. There is no `--baseline`: each case decides.

The text report prints one line per case and the details of each failed
case (the outcome mismatch, the fatal message, missing expectations,
unexpected diagnostics); `--format json` prints the test report, a
surface distinct from the contract report, with the source identity,
counts, and every case in suite order. Exit 0 when every case passed, 1
when a well-formed case did not match, 2 when the suite could not run: a
malformed fixture, an invalid mutation, a missing or linked payload, a
repeated case name, an exceeded limit, or a source that cannot be opened.
A broken suite is never reported as a passing one, and a project without
`[fixtures]` is a fatal outcome rather than an empty pass.

Mutation-style tests written in a scripting language, which copy a
repository, edit a file, run the checker, and grep its output, map onto
fixtures case by case: the copy becomes the overlay, the edit becomes a
`write`, `delete`, or `move` (a payload file holds a whole replacement),
the grep becomes a structured expectation, and a test that asserted a
crash becomes `expect = "fatal"`. Tests that mutate directories, run
shell commands, generate random edits, or inspect the checker's text are
outside the vocabulary and stay where they are. No compatibility with any
existing test suite is claimed; the
[`decision-records`](samples/decision-records/) sample shows the shape.

## History and commit policy

> [!WARNING]
> History checks, the `history` command, the history view, and the
> history report are experimental and require the `git` executable.

`bearout history` lets a repository enforce its own commit rules over
exact Git facts. The kernel captures commits, identities, messages,
parents, and changed paths; the repository writes the rules in Starlark.
Conventional Commits headers, allowed types and scopes, header length,
body separation, breaking-change footers, sign-off trailers that must
match the author, and merge or autosquash exemptions are all policy the
repository supplies; Bearout holds no Conventional Commits parser and no
DCO semantics, and it does not verify the legal truth of a sign-off.

```python
def commit_policy(history):
    findings = []
    for commit in history["commits"]:
        if commit["merge"]:
            continue                       # this repository's choice
        author = commit["author"]
        sign_off = "Signed-off-by: %s <%s>" % (author["name"], author["email"])
        if sign_off not in commit["message"].split("\n"):
            findings.append(error(
                "missing `%s`" % sign_off,
                commit = commit["key"],
                code = "sign-off",
            ))
    return findings

history_check("commit-policy", commit_policy)
```

`bearout history range [PATH] [--base REV] [--head REV]` checks the
commits reachable from the head (default `HEAD`) but not from the base:
Git's `base..head`, or everything reachable from the head without a
base. Both names are resolved exactly once and recorded with their full
identities; the base itself is excluded; merge commits are included and
policy decides whether they matter. Nothing is read from `BASE`, `HEAD`,
or any provider variable, and an all-zero base is not special: omit
`--base` for a new branch. Commits are exposed oldest first in a
deterministic topological order, the full object identity breaking ties
among simultaneously eligible commits. Each commit's changes are
relative to its first parent, or to the empty tree for a root commit,
without rename detection: a rename is a removal plus an addition whose
object identities policy may compare. Paths are repository-relative
facts, with a project-relative form when the path lies inside the
Bearout project.

`bearout history message [PATH] --file FILE` is the commit-msg hook path.
It reads exactly the named message file, which must be a regular,
non-linked file inside the repository's resolved Git directory (the
linked worktree's own directory in a worktree), bounded before it is
read, valid UTF-8, and free of NUL; comments, scissors lines, autosquash
prefixes, and blank lines reach policy exactly as Git supplied them. The
author's name and email are what Git would record, with no timestamp or
timezone, since Git would only invent the current clock for a pending
commit; the parents are `HEAD` and any merge in progress, established
only from a `HEAD` and a `MERGE_HEAD` Git can read and prove (an unborn
branch is recognized only as a symbolic `HEAD` to a branch that does not
exist yet); and the staged changes come from the same captured index
that supplies the policy. An empty message is an input to policy, and
message lines end at CRLF, LF, or a lone CR alike.

Authority is explicit: a range reads `bearout.toml`, the entry module,
and every loaded module from the resolved head's tree; a pending commit
reads them from the captured index. An unstaged policy edit cannot
change a commit-msg check, and the working tree cannot override a range
check. Only history checks run: no resource discovery, documents,
hygiene, ordinary checks, generators, formatters, or fixtures. Identities
are the commit object's own, without `.mailmap`, case folding, or any
inference that author and committer are one person; messages are
byte-exact. Signed commits parse with their signature headers kept out
of the message. Missing objects are never fetched, and a range that
reaches a shallow boundary is refused rather than described as complete
history.

A history finding targets a commit key from the view (`pending` for the
pending commit) with an optional message line, or nothing for a
range-wide finding; a commit target never combines with a resource,
path, or comparison side, and ordinary checks cannot target commits.
Accepted findings are B032 (error) and B033 (warning) in a distinct
history report, rendered as `commit <id>:<line>:B032[rule]: ...`,
`commit pending:...`, or `range:...`, with the registered check name as
the rule identity unless the finding carries its own `code`. Script
diagnostics sort first by path, then range-wide findings, then commit
findings in commit order; within a target by line, code, rule, and
message. Exit 0 with no finding, 1 with any finding, warnings included,
2 for invocation, Git, policy-loading, malformed-history, or limit
failures. An invalid revision or incomplete history is never a policy
finding.

`limits.history_commits`, `limits.history_changes`,
`limits.history_commit_bytes`, and `limits.history_bytes` bound a run.
Fixture cases may supply a synthetic pending message with
`[cases.history]` and expect findings by `commit`, so Conventional
Commit and sign-off policies are regression-tested without a Git
repository; the [`commit-policy`](samples/commit-policy/) sample shows a
complete policy with its fixtures.

A commit checker written in a scripting language, which runs `git log`
or `git show`, parses the text, and prints failures, maps onto this
model: the parsing becomes the history view, each rule becomes a branch
of the history check reading `subject`, `message`, `author`, `parents`,
and `changes`, each failure becomes `error(..., commit = key)`, and the
CI job becomes `bearout history range --base <base>` while the hook
becomes `bearout history message --file "$1"`. Rules that depend on
branch names, remote state, signature verification, or provider APIs
have no facts to read and stay where they are. No compatibility with any
existing checker is claimed.

## Samples

The repository's [`samples/`](https://github.com/malekpour/bearout/tree/main/samples)
directory holds nine complete projects, from three linked notes to a
spreadsheet-expression language whose Rust lexer, parser, and conformance
tests are generated from the resource graph and compiled in CI. The
[samples index](https://github.com/malekpour/bearout/blob/main/samples/README.md)
is the capability matrix. Every sample is checked, and its outputs
verified, by the test suite. Samples are not part of the crates.io package.

## Development

All required tools and versions are pinned in
[`mise.toml`](https://github.com/malekpour/bearout/blob/main/mise.toml).

```sh
mise run setup
mise run fmt
mise run check
```

See [`CONTRIBUTING.md`](CONTRIBUTING.md), [`docs/design.md`](docs/design.md),
and [`docs/technology-evaluation.md`](docs/technology-evaluation.md).

## Trust

Bearout is a capability-confined host with resource limits. It is not a
sandbox for hostile repositories; see [`SECURITY.md`](SECURITY.md).

## Licence

Licensed under the [Apache License 2.0](LICENSE).

Copyright 2026 Ali Malekpour.