diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8bb46f7..5b14036 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -34,7 +34,7 @@ jobs:
shell: bash
run: |
cargo run --release -p probbit-cli -- demo --tasks 12 | cargo run --release -p probbit-cli -- decide --pretty
- cargo run --release -p probbit-cli -- demo --tasks 300 | cargo run --release -p probbit-cli -- decide --budget-ms 300
+ cargo run --release -p probbit-cli -- demo --tasks 300 | cargo run --release -p probbit-cli -- decide --budget-ms 1500
- name: processor spec sheet (probbit stats)
run: cargo run --release -p probbit-cli -- stats --pretty
- name: agent_router example
diff --git a/AGENTS.md b/AGENTS.md
index 5f1cea6..5490601 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -68,5 +68,5 @@ secrets). A command that does not print what this file says it prints is a bug i
## For contributors
-Build: `cargo build --release -p probbit-cli` (Rust 1.78+, no external crates). Test: `cargo test --release --workspace`.
+Build: `cargo build --release -p probbit-cli` (Rust 1.78+; default: `libm` for amplitude-register math; `nostd` adds `hashbrown` and `portable-atomic`). Test: `cargo test --release --workspace`.
Docs and metadata changes: keep every claim tied to a measurement in BENCHMARKS.md or a test. See CONTRIBUTING.md.
diff --git a/BENCHMARKS.md b/BENCHMARKS.md
index 3509cd3..94ec3a1 100644
--- a/BENCHMARKS.md
+++ b/BENCHMARKS.md
@@ -1385,8 +1385,7 @@ quantity; AgentDojo v1.1.1 to v1.2.2. `bench/agentdojo/experiments/rerun.sh` rec
does the same for E3 from `e3/tables/episodes-d.jsonl`.
**Reproductions.** REPRODUCE.md says how; a reproduction report (issue template) is the place to say what you got.
-Standing: the CI job `agentdojo-tables` recomputes these tables on GitHub's Ubuntu and macOS runners on every push and
-pull request and fails unless they are byte-identical to the committed ones (a machine that is not the author's, not a
+Standing: the CI job `agentdojo-tables` recomputes these tables on GitHub's Ubuntu and macOS runners on pushes to `main` and on pull requests and fails unless they are byte-identical to the committed ones (a machine that is not the author's, not a
person). By people outside this project: none yet.
## §15 The engine's own expectation and the exact trajectory (RFC 0009 engine side; 0.12.0; one registered run, 2026-10-22)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c799599..3def44a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,18 @@ All notable changes to this project are documented here. The format follows
## Unreleased
+## 0.14.1 - Unreleased
+
+### Fixed
+
+- Correct current dependency claims: the default build uses `libm` for amplitude-register math; `nostd` adds
+ `hashbrown` and `portable-atomic`. Offline source builds require cached dependencies.
+- Give the CI 300-task demo a 1500 ms budget (previously 300 ms) for slow runners; the same pipeline must still succeed.
+- Correct the OpenClaw plugin test count in the adapter guide to 160 (107 direct tests and 53 classifier cases).
+- Add docs-bundle regeneration to the release PR checklist, with `scripts/generate_llms_full.py` and its `--check` mode;
+ add the post-publication Pages front-door version and JSON-LD `softwareVersion` update on `gh-pages`.
+- Match the AgentDojo benchmark reproduction wording to CI: pushes to `main` and pull requests.
+
## 0.14.0 - 2026-10-31
0.14.0 makes the persona layer a library crate, `probbit-persona` (std, and `no_std` + `alloc` checked on
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 12207e2..207dbe5 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -6,7 +6,7 @@ rules that keep its claims honest.
## Build and test
```
-cargo build --release --workspace # no network needed: there are no external crates
+cargo build --release --workspace # offline once the dependencies below are cached
cargo test --release --workspace # unit, integration, regression and exact-oracle tests
cargo run --release -p probbit-cli -- demo --tasks 12 | cargo run --release -p probbit-cli -- decide --pretty
```
@@ -30,7 +30,9 @@ committed man page: `cargo run -p probbit-cli -- man > docs/probbit.1` (test `co
## Ground rules
-- **No external crates on the shipped path.** `cargo build` must keep working offline.
+- **Keep dependencies small.** The default build uses one third-party crate, `libm`, for amplitude-register math.
+ The `nostd` build adds `hashbrown` and `portable-atomic` (and their transitive dependencies).
+ `cargo build --offline` works with dependencies cached.
- **Every number in the docs points at something runnable**: a test, an example under
`examples/`, or a script under `bench/`. A change that moves a number updates the table it
lives in (`BENCHMARKS.md`, `USE-CASES.md`, `docs/probbit-ir-json.md`) with the machine, the load
diff --git a/README.md b/README.md
index 223f20f..bbb7ed9 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
A personality, mood and memory layer for AI agents, on an exact p-bit decision engine.
An AI persona that lives outside the model: every turn, simulated p-bits on a CPU settle the agent's stance under its
habits, give the exact odds, and log a replayable, verifiable record. The same engine plans under hard rules and
- refuses when it can't vouch. CLI, MCP server, OpenClaw plugin, npm, Python, wasm. Zero-dependency Rust.
+ refuses when it can't vouch. CLI, MCP server, OpenClaw plugin, npm, Python, wasm. Rust; one default third-party crate, libm.
@@ -32,7 +32,8 @@
curl -fsSL https://raw.githubusercontent.com/BitmapAsset/probbit/main/install.sh | sh
```
Linux and macOS, no sudo, the archive's SHA-256 checked first. Windows, npm and the from-source build: [Install](#install).
-One binary of 3 to 4 MB, zero third-party dependencies, nothing phones home. Agents: start at
+One binary of 3 to 4 MB; one third-party crate in the default build (`libm`, for amplitude-register math).
+The `nostd` build adds `hashbrown` and `portable-atomic`. Nothing phones home. Agents: start at
[For AI agents reading this](#for-ai-agents-reading-this), [AGENTS.md](AGENTS.md) and [llms.txt](llms.txt).
| **60 PII leaks → 0** | **180,540 plans, counted exactly** | **refused → partial → passed** |
@@ -267,7 +268,7 @@ odds with a tiny error bar (see [Known failure modes](BENCHMARKS.md#known-failur
A p-bit is a bit that is 0 or 1 with a tunable probability; coupled p-bits sample joint configurations. probbit is
quantum-inspired and entirely classical (see "Is this quantum?" below): it runs on the CPU you already have, needs no
-network and has zero external crates. The same problem lowers to the QUBO/Ising form a p-bit or annealer chip takes (`probbit-ir`), which is the hardware
+network at runtime. The default build uses `libm` for amplitude-register math; `nostd` adds `hashbrown` and `portable-atomic`. The same problem lowers to the QUBO/Ising form a p-bit or annealer chip takes (`probbit-ir`), which is the hardware
hand-off for later.
```
@@ -305,8 +306,8 @@ for Linux ARM64 and for x86_64 musl systems such as Alpine. The shell and PowerS
downloaded candidate starts successfully before replacing an existing binary; failed checksums leave it unchanged.
From source, with Rust 1.78 or later: `cargo install --git https://github.com/BitmapAsset/probbit probbit-cli`, or clone and
-`cargo build --release -p probbit-cli` (the binary lands in `target/release/probbit`). Nothing is downloaded after the clone:
-there are no external crates.
+`cargo build --release -p probbit-cli` (the binary lands in `target/release/probbit`). The default build downloads `libm` for amplitude-register math; `nostd` adds `hashbrown` and `portable-atomic`
+(and their transitive dependencies). With dependencies cached, builds work offline.
At a terminal, `install.sh` ends with probbit's own hero screen. Platform notes and the installer tests: [PORTABILITY.md](PORTABILITY.md).
@@ -349,8 +350,8 @@ $ probbit demo --tasks 300 --hard | probbit decide --budget-ms 1000 # "diagnos
(5 runs each on an Apple M4.) Every escalated task's odds are in the output; the decision is left to you. The budget is
wall-clock, so a slower machine needs a longer one; `--sweeps N --polish-sweeps M` gives the same output on every machine.
-Everything runs with stock stable Rust and **no external crates**, so `cargo build` needs no network (a fresh export builds
-`cargo build --release` in 7.82 s and every target in 26-28 s on the M4).
+Everything runs with stock stable Rust. The default build uses `libm` for amplitude-register math; `nostd` adds
+`hashbrown` and `portable-atomic`. An offline build needs these dependencies cached first.
## Inside the processor
@@ -830,7 +831,8 @@ probbit monitor --demo drives --open # synthetic goals/drive scenario
Select a channel to inspect its units, range, distribution, source and the engine's `why`. Hold an event while new ones
arrive, or follow latest. History means **recorded events, not seconds**. This is declared engine state, not thoughts or
CPU/GPU/memory usage. Replay verification, stale event age, held view, paused/retired strand and lost connection are distinct.
-Everything is embedded in the CLI: no dependencies, external assets, telemetry or Electron installation.
+The monitor UI is embedded in the CLI: no external assets, telemetry or Electron installation. The binary uses `libm`;
+`nostd` builds also use `hashbrown` and `portable-atomic`.
Several agents on one machine: `probbit monitor nova.strand atlas.strand --open` (or `--dir strands/ --open`) adds a switcher
rail, and the header above the face says which agent, on which host, in which lane and session the page shows. Those are
labels in a sidecar next to each strand (`nova.labels.json`), outside the strand and its digests: `probbit live ... --strand
diff --git a/RELEASING.md b/RELEASING.md
index 6b0641c..a56086f 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -22,7 +22,9 @@ the RFC). `probbit-cli/tests/readme_release.rs` fails when the README's current
`.github/workflows/bench.yml`, `scripts/bench/test_install_ps1.ps1` and `scripts/tests/test_install_contract.py`;
the title of `docs/CHEATSHEET.md`; the strand header example in `docs/persona.md` §5.7;
- `CHANGELOG.md`: `## X.Y.Z -
An AI persona that lives outside the model: every turn, simulated p-bits on a CPU settle the agent's stance under its
habits, give the exact odds, and log a replayable, verifiable record. The same engine plans under hard rules and
- refuses when it can't vouch. CLI, MCP server, OpenClaw plugin, npm, Python, wasm. Zero-dependency Rust.
+ refuses when it can't vouch. CLI, MCP server, OpenClaw plugin, npm, Python, wasm. Rust; one default third-party crate, libm.
@@ -44,7 +44,8 @@ Source: https://github.com/BitmapAsset/probbit/blob/main/README.md curl -fsSL https://raw.githubusercontent.com/BitmapAsset/probbit/main/install.sh | sh ``` Linux and macOS, no sudo, the archive's SHA-256 checked first. Windows, npm and the from-source build: [Install](#install). -One binary of 3 to 4 MB, zero third-party dependencies, nothing phones home. Agents: start at +One binary of 3 to 4 MB; one third-party crate in the default build (`libm`, for amplitude-register math). +The `nostd` build adds `hashbrown` and `portable-atomic`. Nothing phones home. Agents: start at [For AI agents reading this](#for-ai-agents-reading-this), [AGENTS.md](AGENTS.md) and [llms.txt](llms.txt). | **60 PII leaks → 0** | **180,540 plans, counted exactly** | **refused → partial → passed** | @@ -279,7 +280,7 @@ odds with a tiny error bar (see [Known failure modes](BENCHMARKS.md#known-failur A p-bit is a bit that is 0 or 1 with a tunable probability; coupled p-bits sample joint configurations. probbit is quantum-inspired and entirely classical (see "Is this quantum?" below): it runs on the CPU you already have, needs no -network and has zero external crates. The same problem lowers to the QUBO/Ising form a p-bit or annealer chip takes (`probbit-ir`), which is the hardware +network at runtime. The default build uses `libm` for amplitude-register math; `nostd` adds `hashbrown` and `portable-atomic`. The same problem lowers to the QUBO/Ising form a p-bit or annealer chip takes (`probbit-ir`), which is the hardware hand-off for later. ``` @@ -317,8 +318,8 @@ for Linux ARM64 and for x86_64 musl systems such as Alpine. The shell and PowerS downloaded candidate starts successfully before replacing an existing binary; failed checksums leave it unchanged. From source, with Rust 1.78 or later: `cargo install --git https://github.com/BitmapAsset/probbit probbit-cli`, or clone and -`cargo build --release -p probbit-cli` (the binary lands in `target/release/probbit`). Nothing is downloaded after the clone: -there are no external crates. +`cargo build --release -p probbit-cli` (the binary lands in `target/release/probbit`). The default build downloads `libm` for amplitude-register math; `nostd` adds `hashbrown` and `portable-atomic` +(and their transitive dependencies). With dependencies cached, builds work offline. At a terminal, `install.sh` ends with probbit's own hero screen. Platform notes and the installer tests: [PORTABILITY.md](PORTABILITY.md). @@ -361,8 +362,8 @@ $ probbit demo --tasks 300 --hard | probbit decide --budget-ms 1000 # "diagnos (5 runs each on an Apple M4.) Every escalated task's odds are in the output; the decision is left to you. The budget is wall-clock, so a slower machine needs a longer one; `--sweeps N --polish-sweeps M` gives the same output on every machine. -Everything runs with stock stable Rust and **no external crates**, so `cargo build` needs no network (a fresh export builds -`cargo build --release` in 7.82 s and every target in 26-28 s on the M4). +Everything runs with stock stable Rust. The default build uses `libm` for amplitude-register math; `nostd` adds +`hashbrown` and `portable-atomic`. An offline build needs these dependencies cached first. ## Inside the processor @@ -842,7 +843,8 @@ probbit monitor --demo drives --open # synthetic goals/drive scenario Select a channel to inspect its units, range, distribution, source and the engine's `why`. Hold an event while new ones arrive, or follow latest. History means **recorded events, not seconds**. This is declared engine state, not thoughts or CPU/GPU/memory usage. Replay verification, stale event age, held view, paused/retired strand and lost connection are distinct. -Everything is embedded in the CLI: no dependencies, external assets, telemetry or Electron installation. +The monitor UI is embedded in the CLI: no external assets, telemetry or Electron installation. The binary uses `libm`; +`nostd` builds also use `hashbrown` and `portable-atomic`. Several agents on one machine: `probbit monitor nova.strand atlas.strand --open` (or `--dir strands/ --open`) adds a switcher rail, and the header above the face says which agent, on which host, in which lane and session the page shows. Those are labels in a sidecar next to each strand (`nova.labels.json`), outside the strand and its digests: `probbit live ... --strand @@ -1092,6 +1094,7 @@ or Windows. Apache-2.0. + --- # FILE: docs/persona.md @@ -1116,7 +1119,8 @@ reads it as its instructions for how to write; an agent can read it to know its none, verify first, ask first). The model still writes every word. This document is normative for the file format, the compilation, the stance document and the state. It is implemented by -`probbit persona` (probbit-persona/src/persona.rs and yaml.rs, no dependency) and served on every surface with the same documents: the +`probbit persona` (probbit-persona/src/persona.rs and yaml.rs; the default engine uses `libm` for amplitude-register math, +`nostd` adds `hashbrown` and `portable-atomic`) and served on every surface with the same documents: the CLI, `probbit mcp` (tools `probbit_persona_init`, `probbit_persona_turn`), the Python wrapper (`probbit.persona_init`, `persona_turn`, `persona_replay`) and the browser module (probbit-wasm ops 4 and 5). `probbit live` (section 5.7, live.rs) keeps an individual running on a clock, with learning (section 2.8) and a replayable log; `probbit monitor` (section 5.8, monitor.rs) @@ -3231,6 +3235,7 @@ carry their own format numbers. The learning block (section 2.8) is additive: `p gives the documents it gave before the block existed. + --- # FILE: docs/agents.md @@ -3500,6 +3505,7 @@ What the shipped hosts write: | Python (`probbit.live_event(..., labels=)`, `StanceHost(..., labels=)`) and the LangChain, OpenAI Agents SDK and Vercel AI SDK examples | what the caller passes; the examples pass `host` = the framework's name | + --- # FILE: REPRODUCE.md @@ -3569,8 +3575,8 @@ differs. The committed files: | `tables/strands-verify.tsv` | `40a3124208edfbb3cb35d548df0f7825aa0851800c745c43f1df06af9d8620d3` | `git status --short tables` is empty after a reproduction. The CI job `agentdojo-tables` (`.github/workflows/ci.yml`) does -exactly this on GitHub's Ubuntu and macOS runners on every push and pull request, so the numbers are recomputed on machines -that are not the author's each time the repository changes. That is a standing machine reproduction; it is not a person. +exactly this on GitHub's Ubuntu and macOS runners on pushes to `main` and on pull requests, so the numbers are recomputed on machines +that are not the author's for those CI events. That is a standing machine reproduction; it is not a person. The E3 control (the if-statement arm of BENCHMARKS.md §14) recomputes the same way, from the same venv, in about ten seconds (no probbit binary needed): diff --git a/llms.txt b/llms.txt index e5e576a..987b082 100644 --- a/llms.txt +++ b/llms.txt @@ -1,6 +1,6 @@ # probbit -> probbit is a personality, mood and memory layer for AI agents, on an exact p-bit decision engine. A persona file plus a seed makes one individual; every turn, simulated p-bits on a CPU settle the agent's stance under its habits (hard rules), give exact odds, and append to a replayable strand that `probbit live verify` checks byte for byte. The same engine plans under hard rules (tasks to workers, tickets to agents), gives the odds for every part, and refuses when it cannot vouch. One zero-dependency Rust binary: CLI, MCP server (`probbit mcp`), OpenClaw plugin, npm, a Python wrapper, wasm. Offline, no API key. +> probbit is a personality, mood and memory layer for AI agents, on an exact p-bit decision engine. A persona file plus a seed makes one individual; every turn, simulated p-bits on a CPU settle the agent's stance under its habits (hard rules), give exact odds, and append to a replayable strand that `probbit live verify` checks byte for byte. The same engine plans under hard rules (tasks to workers, tickets to agents), gives the odds for every part, and refuses when it cannot vouch. One Rust binary (default: `libm` for amplitude-register math; `nostd` adds `hashbrown` and `portable-atomic`): CLI, MCP server (`probbit mcp`), OpenClaw plugin, npm, a Python wrapper, wasm. Offline, no API key. Install on Linux or macOS: `curl -fsSL https://raw.githubusercontent.com/BitmapAsset/probbit/main/install.sh | sh` (installs to `~/.local/bin`, SHA-256 checked). The full agent quickstart, with expected output for every step, is in AGENTS.md. diff --git a/npm/README.md b/npm/README.md index d025587..30226b1 100644 --- a/npm/README.md +++ b/npm/README.md @@ -72,7 +72,7 @@ Agent SDK adapters (Claude Code hooks, OpenAI Agents SDK, LangChain, Vercel AI S ## For AI agents reading this -- `probbit` is one offline binary (3 to 4 MB, zero dependencies): JSON in, JSON out, the exit code is the verdict +- `probbit` is one offline binary (3 to 4 MB; default: `libm` for amplitude-register math; `nostd` adds `hashbrown` and `portable-atomic`): JSON in, JSON out, the exit code is the verdict (0 answer, 1 infeasible or a strand that differs, 2 bad input, 3 refused). - Proof that it works here: the seven lines under "Install and try it", each with its expected output. - The full contract: [docs/agents.md](https://github.com/BitmapAsset/probbit/blob/main/docs/agents.md), @@ -88,8 +88,8 @@ Agent SDK adapters (Claude Code hooks, OpenAI Agents SDK, LangChain, Vercel AI S - **Is probbit the same as probit?** No. probbit (two b's) is a p-bit engine: a p-bit is a probabilistic bit, 0 or 1 with a probability you set, and coupled p-bits sample whole configurations together. It is unrelated to the probit model in statistics, to Power BI `.pbit` files and to any exchange or company of a similar name. -- **Does it need a GPU, a network or an API key?** No. It runs offline on any CPU as one binary with zero third-party - dependencies, and nothing phones home; only this npm install step downloads the binary. +- **Does it need a GPU, a network or an API key?** No. It runs offline on any CPU as one binary; the default build uses `libm` for amplitude-register math + (`nostd` adds `hashbrown` and `portable-atomic`). Nothing phones home; this npm install step downloads the binary. - **Can a stance be verified later?** Yes. `probbit live verify STRAND` replays the strand, checks every sha256 digest and names the first line that differs (exit 1). - **Does the engine experience its moods?** No. A mood is a declared state with exact numbers that replays byte for byte: a @@ -113,7 +113,7 @@ install step could not run (`--ignore-scripts`, offline), the first `probbit` ca | `PROBBIT_BINARY` | at install: copy this binary instead of downloading; at run time: run this binary | `probbit`'s exit code passes through unchanged: 0 answer (`exact`, `diagnostics_passed`, `partial`), 1 `infeasible` -(a proof), 2 bad input or flag, 3 `refused` / `declined`. No dependencies; Node 18 or later. License: Apache-2.0. +(a proof), 2 bad input or flag, 3 `refused` / `declined`. Node 18 or later; the binary uses `libm` (`nostd` adds `hashbrown` and `portable-atomic`). License: Apache-2.0. [Repository](https://github.com/BitmapAsset/probbit) · [front door](https://bitmapasset.github.io/probbit/) · diff --git a/npm/package.json b/npm/package.json index 52cfa66..9e9b9d3 100644 --- a/npm/package.json +++ b/npm/package.json @@ -2,7 +2,7 @@ "name": "probbit", "version": "0.14.0", "mcpName": "io.github.BitmapAsset/probbit", - "description": "A personality, mood and memory layer for AI agents on an exact p-bit decision engine: an AI persona outside the model, every stance computed on a CPU, rules kept by construction, replayable and verifiable. Installs the prebuilt CLI and MCP server (probbit mcp). Offline, zero dependencies.", + "description": "A personality, mood and memory layer for AI agents on an exact p-bit decision engine: an AI persona outside the model, every stance computed on a CPU, rules kept by construction, replayable and verifiable. Installs the prebuilt CLI and MCP server (probbit mcp). Offline at runtime; default build: libm for amplitude-register math; nostd adds hashbrown and portable-atomic.", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/probbit-cli/Cargo.toml b/probbit-cli/Cargo.toml index c039736..a767dda 100644 --- a/probbit-cli/Cargo.toml +++ b/probbit-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "probbit-cli" -description = "probbit: a personality, mood and memory layer for AI agents on an exact p-bit decision engine (plans under hard rules, gated odds, refusal), simulated on a CPU. CLI and MCP server; JSON in, JSON out, zero dependencies." +description = "probbit: a personality, mood and memory layer for AI agents on an exact p-bit decision engine (plans under hard rules, gated odds, refusal), simulated on a CPU. CLI and MCP server; JSON in, JSON out, default build: libm for amplitude-register math; nostd adds hashbrown and portable-atomic." version.workspace = true edition.workspace = true rust-version.workspace = true diff --git a/scripts/generate_llms_full.py b/scripts/generate_llms_full.py new file mode 100644 index 0000000..0ca2a26 --- /dev/null +++ b/scripts/generate_llms_full.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +"""Rebuild the model-readable docs bundle from its source pages (Python 3.9+).""" +import argparse +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +SOURCES = ("README.md", "docs/persona.md", "docs/agents.md", "REPRODUCE.md") + + +def render(): + text = "# probbit: full documentation for language models\n\n" + text += ("> The README, docs/persona.md, docs/agents.md and REPRODUCE.md of " + "https://github.com/BitmapAsset/probbit in one file. Index: llms.txt. " + "Generated from those four files; regenerate it whenever one of them changes.\n") + for name in SOURCES: + text += ("\n\n\n---\n\n# FILE: " + name + "\n\nSource: " + "https://github.com/BitmapAsset/probbit/blob/main/" + name + "\n\n") + text += (ROOT / name).read_text(encoding="utf-8").rstrip() + "\n" + return text + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--check", action="store_true", help="fail if llms-full.txt is stale") + args = parser.parse_args() + target = ROOT / "llms-full.txt" + expected = render() + if args.check: + if not target.exists() or target.read_text(encoding="utf-8") != expected: + parser.exit(1, "llms-full.txt is stale; run python3 scripts/generate_llms_full.py\n") + print("llms-full.txt matches its four source pages") + else: + target.write_text(expected, encoding="utf-8") + print("regenerated llms-full.txt from four source pages") + + +if __name__ == "__main__": + main()