avouch 0.1.23 → 0.1.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,18 +30,31 @@ Read in this order:
30
30
 
31
31
  ## Adopting Avouch
32
32
 
33
- Start with step 0, then do the two steps, in this order.
33
+ Start with step 0, then do the three steps, in this order.
34
34
 
35
- 0. At the top level of the git repository, run `npx -y avouch init`. It installs the plugin for Claude Code and for Codex (see "Claude Code and Codex plugin") and adds the Avouch instructions for coding agents. The instructions tell an agent to read the rules with `avouch query`, not with a text tool, and to run `avouch check` and `avouch gate docs` after a change. There are two modes: `--append` adds the instructions to `AGENTS.md` and `CLAUDE.md`, if they exist; `--print` writes them to the terminal, and you add them yourself. Without a flag, `init` asks in a terminal and prints otherwise. `--dry-run` shows what would change and writes nothing. Then run `/avouch:scan` in Claude Code (`$avouch:scan` in Codex), which drafts `avouch.config.json`, a first ontology and the document review ledger. See "init" in "Command line".
35
+ 0. At the top level of the git repository, run `npx -y avouch init`. It installs the plugin for Claude Code and for Codex (see "Claude Code and Codex plugin") and adds the Avouch instructions for coding agents. The instructions tell an agent to read the rules with `avouch query`, not with a text tool, and to run `avouch check` and `avouch gate docs` after a change. There are two modes: `--append` adds the instructions to `AGENTS.md` (it creates the file when it is missing) and to `CLAUDE.md` if it exists; `--print` writes them to the terminal, and you add them yourself. Without a flag, `init` asks in a terminal and prints otherwise. `--dry-run` shows what would change and writes nothing. Then run `/avouch:scan` in Claude Code (`$avouch:scan` in Codex), which drafts `avouch.config.json`, a first ontology and the document review ledger. See "init" in "Command line".
36
36
  1. Every project: keep a document review ledger, and run `avouch gate docs` in CI. See "Document review ledger (gate docs)". The gate works with any Markdown style. It guarantees that each changed section of a design document has a recorded review decision. It does not guarantee that the decision is complete: it does not check that a person read the section, that `claims` lists every claim, or that the claimed places say what the section says.
37
37
  2. For the structured checks, choose one path. The checks are: the field lists and catalogs of R2, and R3, R5, R6, R8, R9 and R10 (SPEC section 5). Each needs a fact from the project's documents (SPEC section 6). R7 needs execution evidence, which the built-in adapter does not read.
38
38
  - (a) Write the documents to the Markdown convention, `docs/markdown-convention.md`, and use the built-in adapter. `avouch facts` checks the documents without an ontology.
39
- - (b) Write your own adapter: `docs/adapter-guide.md` sections 1 to 6. The grammar of the documents is then free. An AI agent writes it in a new session, not in the session that writes the ontology, with the prompt in "For AI agents" of that guide.
39
+ - (b) Write your own adapter: `docs/adapter-guide.md` sections 1 to 6 and 8. The grammar of the documents is then free. An AI agent writes it in a new session, not in the session that writes the ontology, with the prompt in "For AI agents" of that guide.
40
+ 3. Runtime consistency: compare the runtime with the documents. See "Runtime consistency on the command line".
41
+ - The project's own test harness records one `RuntimeCase` (SPEC section 9) for each command test: the action, the inputs (`bindings`, `actor`, `scope`, `now`), the `snapshot` that the decision reads, the result that the runtime reported (`reported`), and the expected result (`expected`).
42
+ - Take the expected result from the text of the documents. Never take it from what the code does: then the test compares the code with itself.
43
+ - Run `avouch reconcile`, then `avouch gate verify` with a baseline. Make the baseline with a script, from the output of `avouch gate verify --json`; do not edit it by hand. People write only the `reason` of each `exempt` entry.
44
+ - Do not put a known difference between the code and the documents in the baseline. Keep it as a separate test that asserts the full current behavior, and remove the test when the difference is fixed.
45
+ - Any language can do this: write the cases as JSONL and run the two commands. A TypeScript harness can instead call `reconcile(ontology, cases)` and `verifyCoverage(ontology, rows, baseline)` from the package root in the same process. Both paths give the same result (without `--check`). You do not need TypeScript.
40
46
 
41
47
  Avouch does not widen the built-in grammar for one project.
42
48
 
43
49
  A rule whose fact is not available is switched off in the profile, with a reason: `"rules": {"R8": {"disabled": "<reason>"}}` (in `profile` of the Markdown configuration; `docs/adapter-guide.md` section 5). The checker prints the reason. Without the switch, the rule reports `fact_missing` for the missing fact. The built-in adapter switches R3 and R9 off by itself when their facts are not set, and R7 always (`docs/adapter-guide.md` section 7).
44
50
 
51
+ Dependency boundary:
52
+
53
+ - Pin an exact version of `avouch` in a test or development package, and run its local bin (for example `npm exec avouch` in that package). Step 0 runs `npx` once; the gate runs the pinned version. In a repository without a root `package.json`, `npx avouch` gets the newest version, not the pinned one.
54
+ - Production code does not import `avouch`. When production code needs data from the ontology (for example a registry of command permissions), generate files from the ontology, and add a test that fails when a generated file is stale.
55
+ - Upgrade `avouch` in its own pull request, and run the full gate in it.
56
+ - `avouch check` with an adapter checks the ontology against the documents only. It is one part of the project's gate, not the whole gate. The `check` stamp of `avouch query` is `not_run` unless you give `--check` or `--adapter` (see "Query").
57
+
45
58
  ## Layout
46
59
 
47
60
  | Path | Content |
@@ -107,7 +120,7 @@ An `--adapter` path that ends in `.json` is not loaded as a module: it is the co
107
120
  avouch init [--append | --print] [--plugin | --no-plugin] [--dry-run]
108
121
  ```
109
122
 
110
- `avouch init` prepares a project for Avouch. Run it inside a git repository; it works on the git top level. Step 1 is the plugin, for Claude Code and then for Codex. With `--plugin`, it runs `claude plugin marketplace add githonllc/avouch` and `claude plugin install avouch@avouch`, then `codex plugin marketplace add githonllc/avouch` and `codex plugin add avouch@avouch` (it does not check if the plugin is already installed, and runs the commands again on each run). Without a flag, it asks in a terminal for each host and only prints the commands otherwise. When `claude` or `codex` is not on `PATH`, it prints the two commands of that host. Codex runs the hook only after you approve it. `--no-plugin` skips the step. Step 2 is the instructions for coding agents (`templates/agents-block.md`). `--append` adds them, between the lines `<!-- BEGIN avouch -->` and `<!-- END avouch -->`, to `AGENTS.md` and `CLAUDE.md` at the top level. It never creates a file: when neither file exists, it prints the instructions. It replaces an earlier block in place, keeps all other bytes and the line endings (CRLF or LF) of the file, and a second run changes nothing. A file that does not have exactly one BEGIN line and then one END line is an error, and the file is not changed. It skips `CLAUDE.md` when the file has the line `@AGENTS.md` and `AGENTS.md` exists. `--print` writes the instructions to stdout. Without a flag, it asks in a terminal and prints otherwise. `--dry-run` runs no command and writes no file; it prints the commands and, for each file, `append`, `replace` or `unchanged`. Step 3 tells you to run `/avouch:scan` in Claude Code. Exit code 0 means done (also a print and a dry run); 1 means a plugin command failed or a file could not be changed; 2 means invalid arguments or no git repository.
123
+ `avouch init` prepares a project for Avouch. Run it inside a git repository; it works on the git top level. Step 1 is the plugin, for Claude Code and then for Codex. With `--plugin`, it runs `claude plugin marketplace add githonllc/avouch` and `claude plugin install avouch@avouch`, then `codex plugin marketplace add githonllc/avouch` and `codex plugin add avouch@avouch` (it does not check if the plugin is already installed, and runs the commands again on each run). Without a flag, it asks in a terminal for each host and only prints the commands otherwise. When `claude` or `codex` is not on `PATH`, it prints the two commands of that host. Codex runs the hook only after you approve it. `--no-plugin` skips the step. Step 2 is the instructions for coding agents (`templates/agents-block.md`). `--append` adds them, between the lines `<!-- BEGIN avouch -->` and `<!-- END avouch -->`, to `AGENTS.md` and `CLAUDE.md` at the top level. It creates `AGENTS.md` when it is missing, but it never creates `CLAUDE.md`. The reason: Claude Code reads `CLAUDE.md`, and reads `AGENTS.md` only when there is no `CLAUDE.md` or through the line `@AGENTS.md` in `CLAUDE.md`; Codex reads only `AGENTS.md`. It replaces an earlier block in place, keeps all other bytes and the line endings (CRLF or LF) of the file, and a second run changes nothing. A file that does not have exactly one BEGIN line and then one END line is an error, and the file is not changed. It skips `CLAUDE.md` when the file has the line `@AGENTS.md`, because `CLAUDE.md` then imports the instructions from `AGENTS.md`. `--print` writes the instructions to stdout. Without a flag, it asks in a terminal and prints otherwise. `--dry-run` runs no command and writes no file; it prints the commands and, for each file, `create`, `append`, `replace` or `unchanged`. Step 3 tells you to run `/avouch:scan` in Claude Code. Exit code 0 means done (also a print and a dry run); 1 means a plugin command failed or a file could not be changed; 2 means invalid arguments or no git repository.
111
124
 
112
125
  ### facts
113
126
 
package/dist/cli/init.js CHANGED
@@ -198,45 +198,43 @@ async function plugin(opts, dryRun) {
198
198
  async function instructions(top, opts, dryRun) {
199
199
  console.log("Step 2: the Avouch instructions for coding agents (AGENTS.md, CLAUDE.md).");
200
200
  const exists = (name) => existsSync(join(top, name)) && statSync(join(top, name)).isFile();
201
+ // AGENTS.md is always a target and is created when it is missing (Codex reads only AGENTS.md); CLAUDE.md is never created.
201
202
  const files = [];
202
203
  for (const name of TARGETS) {
203
- if (!exists(name))
204
+ if (name === "CLAUDE.md" && !exists(name))
204
205
  continue;
205
- if (name === "CLAUDE.md" && exists("AGENTS.md") && readFileSync(join(top, name), "utf8").split(/\r?\n/).some((l) => l.trim() === "@AGENTS.md")) {
206
+ if (name === "CLAUDE.md" && readFileSync(join(top, name), "utf8").split(/\r?\n/).some((l) => l.trim() === "@AGENTS.md")) {
206
207
  console.log("CLAUDE.md is skipped: it has the line `@AGENTS.md`, so it imports the instructions from AGENTS.md.");
207
208
  continue;
208
209
  }
209
210
  files.push(name);
210
211
  }
212
+ const label = (name) => (exists(name) ? name : `${name} (new file)`);
211
213
  let append = opts.has("--append");
212
- if (!append && !opts.has("--print") && files.length > 0) {
214
+ if (!append && !opts.has("--print")) {
213
215
  if (dryRun) {
214
216
  console.log("Dry run without --append or --print: this is the plan for --append (init asks in a terminal and prints otherwise).");
215
217
  append = true;
216
218
  }
217
219
  else if (isTty())
218
- append = await ask(`Append the Avouch instructions to ${files.join(", ")}?`);
220
+ append = await ask(`Append the Avouch instructions to ${files.map(label).join(", ")}?`);
219
221
  }
220
222
  if (!append) {
221
223
  printBlock();
222
224
  return true;
223
225
  }
224
- if (files.length === 0) {
225
- console.log(`No file to append to: AGENTS.md and CLAUDE.md do not exist at ${top}, and init never creates a file.`);
226
- printBlock();
227
- return true;
228
- }
229
226
  let ok = true;
230
227
  for (const name of files) {
231
228
  const path = join(top, name);
232
- const plan = planFile(name, readFileSync(path));
229
+ const created = !exists(name);
230
+ const plan = planFile(name, created ? Buffer.alloc(0) : readFileSync(path));
233
231
  if ("error" in plan) {
234
232
  ok = false;
235
233
  console.error(`FAIL: ${plan.error}`);
236
234
  continue;
237
235
  }
238
236
  if (dryRun) {
239
- console.log(`Dry run: ${name}: ${plan.action === "unchanged" ? "unchanged" : `would ${plan.action === "append" ? "append the block" : "replace the block"}`}`);
237
+ console.log(`Dry run: ${name}: ${created ? "would create the file with the block" : plan.action === "unchanged" ? "unchanged" : `would ${plan.action === "append" ? "append the block" : "replace the block"}`}`);
240
238
  continue;
241
239
  }
242
240
  if (plan.action === "unchanged") {
@@ -245,7 +243,7 @@ async function instructions(top, opts, dryRun) {
245
243
  }
246
244
  try {
247
245
  writeFileSync(path, plan.bytes);
248
- console.log(`${name}: ${plan.action === "append" ? "appended the block" : "replaced the block"}`);
246
+ console.log(`${name}: ${created ? "created the file with the block" : plan.action === "append" ? "appended the block" : "replaced the block"}`);
249
247
  }
250
248
  catch (error) {
251
249
  ok = false;
@@ -185,3 +185,14 @@ Without `--check` and without `--adapter`, the check stamp of `avouch query` sta
185
185
  - the generic adapter also reports R6 `fact_missing facts.evidence`.
186
186
 
187
187
  The R3 and R9 findings are the same, because the configuration sets `facts.stateMachines`, `facts.transitionTriggers` and `facts.scenarios`. So the generic adapter does not catch three mutations: `lib-r6-none-but-written`, `lib-r7-effect-unwitnessed` and `lib-r7-undeclared-store`. It catches the other 25, `lib-r3-transition-binding`, `lib-r6-link-sweep-missing` and `lib-r9-unknown-scenario` included. With the evidence ids kept, the clean toy fails with three violations: R1 for each of the two evidence ids, and R6 `fact_missing`.
188
+
189
+ ## 8. Test your adapter with mutations
190
+
191
+ A clean PASS can prove nothing. An adapter that returns too few facts, or a rule that never fires, also gives PASS. A mutation test shows that each check can fail.
192
+
193
+ - For each rule that you turn on, keep a few mutated copies of the ontology or of the documents. Each copy changes one thing, and must fail with exactly its expected rule: that rule, and no other rule.
194
+ - Pin the result in a snapshot, so that a change in the adapter or in Avouch that changes a finding shows in the diff.
195
+ - Give each waiver (`knownSourceGaps`: `conflict` and `proposed`) and each disabled rule (`{ disabled: "<reason>" }`) a reason.
196
+ - Where the documents decide, track that no `unknown` or `unspecified` is left in the ontology: for example `permission: unknown` (R10 `permission_unknown`) or a condition without an expression (R12 `condition_unspecified`). Do not waive these when the text gives the answer; write the value. `avouch query list gaps` lists the waivers.
197
+
198
+ The model is the toy library: each file in `examples/library/mutations/` is a JSON-pointer patch of the ontology (`src/patch.ts`) with `expect: R<n>`, and `test/library.test.ts` checks that the clean ontology passes and that each mutation fails with exactly its expected rule. Mutations of the documents follow the same pattern: change one source text, run the adapter, and expect one rule.
@@ -3,7 +3,7 @@
3
3
  This document is the grammar of the built-in Markdown adapter (`src/markdown.ts`). The adapter reads facts only in the form that this document describes. A project has two ways to use Avouch with Markdown documents:
4
4
 
5
5
  1. Write the documents to this convention. Then `avouch check` runs the built-in adapter, with a JSON configuration (`docs/adapter-guide.md` section 7).
6
- 2. Write an own adapter. Then the grammar of the documents is free. See `docs/adapter-guide.md` sections 1 to 6; an AI agent uses the prompt in "For AI agents" of that guide.
6
+ 2. Write an own adapter. Then the grammar of the documents is free. See `docs/adapter-guide.md` sections 1 to 6 and 8; an AI agent uses the prompt in "For AI agents" of that guide.
7
7
 
8
8
  An AI that reformats documents can use this document as the target form. Write each fact as one block, as the templates in sections 3 to 10 show.
9
9
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "avouch",
3
- "version": "0.1.23",
3
+ "version": "0.1.24",
4
4
  "description": "Avouch: an ontology spec whose every claim cites its source, with a checker and a CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Githon LLC",