Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .coherence/journal/04d60773-e8c7-4707-9a73-c0cf5e52af76.jsonl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ Then run `npx --no -- coherence query practice "adopt Coherence"`, work through
Ask me whether pull requests should fail on Coherence's findings or get a report comment, then run `npx --no -- coherence ci install --host github --mode fail` (or `--mode comment`) with `--write`.
```

Before asking you anything, your agent explains how Coherence works from
[docs/how-coherence-works.md](docs/how-coherence-works.md): one page, with a
diagram and the few words you will hear.

It needs Node 22.18 or newer on macOS (Apple Silicon) or Linux. The package
is [`@posthog/coherence`](https://www.npmjs.com/package/@posthog/coherence)
on npm, published from this repository with provenance; the dev dependency
Expand Down Expand Up @@ -635,6 +639,7 @@ nothing. The workflow can also be run by hand from the Actions tab.

## Further reading

- `docs/how-coherence-works.md`: one page on how Coherence works and the words it uses, for a person new to it.
- `Coherence.spec.md` and the `*.spec.md` file in each folder under `src/`: Coherence's own trust levels and invariants, in its own grammar.
- `docs/lexicon.json`: the settled vocabulary; `docs/lexicon.md`: why the lexicon is first-class, with the evidence.
- `docs/spec.md`, `docs/enforcement.md`, `docs/journal.md`, `docs/economy.md`, `docs/observation.md`, `docs/scope-shell.md`: how each part works.
Expand Down
69 changes: 69 additions & 0 deletions docs/how-coherence-works.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# How Coherence works

Agents write code faster than anyone can read it, and they cut corners where
you can't see them. Coherence keeps the few rules your project must never
break written down next to the code, checks them while the agent works, and
shows you which ones hold.

```text
you and your agent agree on a rule
│
â–¼
spec (src/billing/Billing.spec.md) the rule, why it matters,
│ and how it is checked
â–¼
hooks, on every edit ── bypassed? ──► the agent hears it in the same turn
│
run, and CI on every pull request ───► each rule's test, and every chokepoint again
│
â–¼
journal and Scope what was decided and found,
and a map of the project you can open
```

## The words you will hear

- **Requirement**: a rule the project must keep, written in a component's
spec with the reason it matters. For example: "every read of an invoice
checks that it belongs to the signed-in account." Nothing checks it yet.
- **Invariant**: a requirement whose enforcement has been seen to fire,
and whose ways of breaking (the checklist Coherence asks about each rule)
have been answered. Until then the rule is only written down; after, a
break shows up when its check runs.
- **Enforcement**: what checks the rule. Two kinds:
- a **chokepoint**: the one function every use of something must go
through, such as `invoices.forAccount()` in front of the invoice table.
Coherence asks the language server for every reference and flags any
that go around it, at the very edit that adds one. No test to write.
- a **totality oracle**: a test that checks the rule over a whole set, for
rules no single function can guard. It runs with `coherence run` and on
each pull request, not at every edit.
- **Refutation**: the proof that the enforcement works. The rule was broken
on purpose and the check went red. For a chokepoint, Coherence stages the
bypass itself without touching your files.
- **Entrance**: where work comes in from outside: a route, a command, a
queue consumer. A **spec gap** is an entrance whose callers are outside
your control or unknown, with no control traced on its way in and no
recorded reason that it needs none.
- **Journal**: the project's record of decisions and defects, so the reason
for a change outlives the chat it was made in.
- **Practice**: a method the project keeps, like adopting Coherence. Its
steps reach the agent when its commands run.

## Why a short set of invariants

You can't review everything an agent writes. You can name the handful of
rules that would hurt most if they broke: who may read whose data, money
that must add up, a boundary between two parts of the system. With those
as invariants, a break shows up while the agent works or on the pull
request, not in review, and Scope shows you which ones hold today.

During adoption your agent proposes these rules. For each it tells you the
rule, what goes wrong if it breaks, and how it would be checked. Confirm,
reword or drop each one; a few that matter beat many that don't.

## Questions to ask next

- "Which invariants does this project have, and which are broken?"
- "Show me the project in Scope."
- "Why did we decide that?" (the journal keeps the answer)
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@
"docs/enforcement.md",
"docs/spec.md",
"docs/plugins.md",
"plugins"
"plugins",
"docs/how-coherence-works.md"
],
"optionalDependencies": {
"pyright": "^1.1.414",
Expand Down
6 changes: 4 additions & 2 deletions src/lifecycle/Lifecycle.practice.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
when: explicit
step: confirm the hooks are wired as install would write them: coherence hooks --check --host <claude or codex>
leaves: hooks --check exits 0
step: before asking the user anything, tell them how Coherence works, in your own words and within one screen, from docs/how-coherence-works.md in the installed package: its diagram, what a requirement, an invariant, its enforcement and its refutation are, and what a short set of invariants buys them; say that you will propose the invariants for them to confirm, and invite their questions
step: write coherence.config.json where the project lives: at the repository root for a whole repository; in a monorepo, in the folder you adopt, after coherence adopt <folder> from the root when the repository keeps a registry (a root config with projects), writing only the keys the folder overrides; name, language (a list when the folder spans Python and TypeScript, the first the primary), the folders that are not this project's code or prose under ignore (vendored code, generated output, fixtures), and, where the project has tests, test with testMatch and testJson, or tests, one setup per runner with its own cwd; every key is in docs/config.md in the installed package
step: settle the vocabulary: coherence lexicon coverage, then declare the terms that carry the project's meaning (the practice settle a domain term)
step: to settle one part of the project first, name its folders or files: coherence lexicon coverage src/billing docs, and coherence lexicon draft src/billing for its unsettled candidates; each path is relative to the root or absolute inside it, and the config's ignore list still holds inside it
Expand All @@ -46,7 +47,7 @@
step: declare the components and where work enters them: the entry spec with its trust levels, one spec per component folder, then every entrance (the practice declare entrances); the grammar is in docs/spec.md in the installed package
step: give each real unit its own spec at whatever depth it sits, not one per top-level folder: run coherence mass, and for each folder it names as having no spec of its own, write its spec or record with decide why it belongs to the component above; repeat until mass names none you have not decided
leaves: mass names no folder without a spec or a decision
step: declare the few invariants that matter most (security, tenant isolation, data integrity), each through the practice declare a requirement; for a module boundary whose own code may use its internals, say which references the chokepoint governs (from: outside the component), and in a folder of a larger repository, where the boundary's callers live outside it, choose the references horizon in the config (a list of folders, or repository)
step: declare the few invariants that matter most (security, tenant isolation, data integrity), each through the practice declare a requirement, proposing each to the user first in plain words: the rule, what goes wrong for the project if it breaks, and how it would be checked; for a module boundary whose own code may use its internals, say which references the chokepoint governs (from: outside the component), and in a folder of a larger repository, where the boundary's callers live outside it, choose the references horizon in the config (a list of folders, or repository)
step: run, then witness each refutation (the practice witness a refutation); a broken chokepoint is a finding to report with its bypass sites, not a failure to hide; a requirement the code already breaks stays a requirement, with a defect per site and an escalation, and a test that cannot run here is recorded as unable
leaves: spec --check with 0 problems
step: baseline what the project already held: coherence lexicon baseline, and coherence scaffold control --baseline for entrances with no traced control
Expand All @@ -65,8 +66,9 @@
pitfall: a fresh adoption found its first requirements already false and its tests unable to run unattended, and had to improvise what to record (df-8ac95c4d)
pitfall: in a monorepo, a session started at the repository root took the whole repository as the project: another team's specs reached orient, their practices fired, and .coherence was written at the root (df-0b68c987)
pitfall: a chokepoint in a project nested below the repository top passed without searching the rest of the repository, where a product boundary's bypasses live; choose its references horizon (df-0d235229)
pitfall: the user was asked which invariants to declare before anything had said what an invariant is, or what Coherence does with one (df-729d4e68)
pitfall: a bullet inserted between another bullet and its refuted:, kinds: or checklist: lines took them, so that invariant fell back to a requirement while spec --check reported 0 problems; append a whole bullet after another's last line (df-f3826eaa)
learned: d-96eb6814, d-127ab8e4, d-a1095ef2, f658594
learned: d-96eb6814, d-127ab8e4, d-a1095ef2, f658594, df-729d4e68
reach: kernel
because: the setup prompt was a method kept in prose, carried out once and reported in a chat reply that no later session reads; as a practice each step is delivered where its commands run, and the enactment is the durable record of how the project was adopted
- settle a domain term: A term enters the project's lexicon as the right kind of thing (a concept, an alias, a property, or ordinary code) with the reason recorded, before code or specs use it.
Expand Down
8 changes: 8 additions & 0 deletions src/spec/practice.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { dirname, join } from "node:path";
import { after, describe, test } from "node:test";
import { commandMatches, firedBy, globMatches, parsePractices, type ToolUse } from "./practice.ts";
import { loadSpecModel, projectPractices } from "./model.ts";
import { COHERENCE_ROOT, kernelPractices } from "./practices.ts";
import { journalVerbs, type Io } from "../journal/cli.ts";
import { appendRecord, loadJournal } from "../journal/store.ts";
import type { Enactment } from "../journal/record.ts";
Expand Down Expand Up @@ -409,3 +410,10 @@ test("in an adopter, a kernel practice a Coherence release changed asks no amend
const floor = loadSpecModel(root, { runs: false }).problems.filter((p) => /is gone; a practice keeps what it taught/.test(p.message));
assert.deepEqual(floor.map((p) => p.message), [], "no floor gap is named for a kernel practice");
});

test("every doc a kernel practice sends an adopter to ships in the package", () => {
const shipped: string[] = JSON.parse(readFileSync(join(COHERENCE_ROOT, "package.json"), "utf8")).files;
const named = kernelPractices().flatMap((p) => p.steps.flatMap((s) => s.text.match(/\bdocs\/[\w./-]+\.md\b/g) ?? []));
assert.ok(named.length > 0, "the kernel practices name the docs an adopter reads");
assert.deepEqual([...new Set(named)].filter((doc) => !shipped.includes(doc)), []);
});