Skip to content

Read every bundled language through a tree-sitter parser; keep parses as files of their own; release 1.11.0 - #157

Merged
daniloc merged 40 commits into
mainfrom
beta-fixes-2
Oct 27, 2026
Merged

daniloc merged 40 commits into
mainfrom
beta-fixes-2

Conversation

@daniloc

@daniloc daniloc commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Every bundled language plugin now reads its text through a tree-sitter parser, and the parses that make this affordable are kept in the user's own cache, one file each. Merging this releases 1.11.0: the Release workflow publishes it to npm as latest.

What changes

  • Parsers for Python, Go, Rust and TypeScript/JavaScript (web-tree-sitter 0.27.1, Coherence's first runtime dependency).
    • A plugin names treeSitter { parser, queries, resolve }, and the core derives every reading of the language's text from one parse.
    • The hand-written reader slots (source, declares, topLevel, kept) are removed at plugin interface 4.
    • A plugin ejected from 1.10 is refused with its recovery first: eject again from this version.
    • Statements are read whole, across lines. Text inside strings and comments is never read as code.
  • TypeScript runs on Coherence's patched build of tree-sitter-typescript 0.23.2, since upstream has merged no grammar change since 2024-11.
    • The patch fixes import('x').T<U> and its kin, members and statements beginning with in, and a bare & in JSX.
    • Results over 17,106 PostHog files:
      • files with errors go from 191 to 52;
      • 85 declarations come back;
      • none is lost.
    • grammars/tree-sitter-typescript/build.sh builds both parsers with no network, from inputs checked by digest. It then compares stock and patched over upstream's corpus, Coherence's own corpus and this repository's source. CI's typescript-parsers job rebuilds them byte for byte.
    • The comparison holds over those inputs alone, and the docs say so. For a misread no corpus holds, the TypeScript plugin's @misread query catches a reserved word read as a name in any file Coherence reads, and orient says so.
  • Kept parses live in the user's cache ($XDG_CACHE_HOME/coherence/parses, else ~/.cache), never in a project.
    • Each is a file of its own, named by the text's digest, written once and never changed, so no process merges, locks or reloads.
    • The store is used only where its path is the user's alone, and never when it lies inside the repository being read.
    • Each parse and query stops after a fixed amount of work, so one huge generated file can't take the runtime down.
    • On PostHog's 19,902 Python files:
      • first warm-up: 32 s;
      • the next: 2.0 s;
      • mass: 3.4 s, where parsing every file takes 14.8 s.
  • plugin check gains three duties: answers agree, the language server agrees, and every plugin reads one reference project the same way. Also new: plugin shapes, plugin calibrate and plugin compare.
  • A dead-end alarm. spec --check, orient and the convergence reading name a component that keeps collecting defects across classes and rounds, and ask a human whether to redesign it. The journal is read across every worktree, and a defect must say where it lies. The alarm is built to help an agent that wants to work well notice a dead end; it is not a defense against an agent gaming its own records.
  • Kernel practices: "close a defect" now weighs a redesign at a dead end. "Keep state where it is true" is new, after Data is destiny.
  • Prereleases are cut by .github/workflows/prerelease.yml. A read-only build job checks the commit, packs the tarball, checks its parsers against parsers.sha256 and runs plugin check; only the release job can write.
  • Includes Rust: wasm-bindgen and UniFFI entrances, examples, and the lists slot #156 (Rust wasm-bindgen and UniFFI entrances), so Rust: wasm-bindgen and UniFFI entrances, examples, and the lists slot #156 can close once this lands.

Review

Eight rounds of blind review ran against this branch. The parse store collected 26 defects over the first rounds, each fixed at the site the reviewer found, until the dead-end alarm named it. It was then redesigned: first moved out of the project, then made one file per parse. The owner acknowledged the dead end as addressed by the redesign (ak-6ae550c5).

The last review (of 766fba1) found shallower faults, each fixed locally in 9887d0f:

  • the runtime's limit on a huge file;
  • a quadratic loop;
  • caches in checkouts and group-writable homes;
  • the alarm's accident paths;
  • the comparison's claim.

Known

  • A session start still stops on an unreadable file in its own journal; spec --check reports it instead (u-561ee937).
  • macOS access lists on the cache folder are not read.
  • Two flakes are recorded and unexplained: the leak guard's self-test (df-e8424adc) and a Rust overlay test (c-7e051032).
  • prerelease.yml's release job has not run yet.
  • TypeScript forms that still fail to parse, as with stock (docs/plugins.md):
    • ({ a }?: T) => U;
    • a parenthesized import type;
    • importOriginal<typeof import('x')>();
    • a method named in after another member.

Testing

  • npm test: 821 pass, 0 fail, 2 skipped (environmental).
  • spec --check: 0 problems. lexicon:check: 0 rejected names.
  • build.sh: both parsers rebuilt byte for byte; every difference from stock is intended.
  • Every changed bullet was refuted with a staged break that turned its test red on its own assertion, then run batched and alone: 20 of 20 batched, 19 of 19 alone on the final tree.

🤖 Generated with Claude Code

daniloc and others added 30 commits October 25, 2026 19:36
- Entrances: each pub method and constructor of a #[wasm_bindgen] impl,
  #[wasm_bindgen(start)], #[uniffi::export] fns and an exported impl's
  methods (a trait impl's all) are foreign exports, named Type::method; an
  extern block's imports, private, pub(crate), skipped and plain impls'
  methods are not. An example's main at Cargo's default paths is a new
  example rule.
- Lists: rustListOf fills the lists slot (a top-level const or static
  array: strings, tuples by their first, paths by their last segment), and
  mirrors.ts now reads a list through its language's lists slot, which no
  core module read before.
- Manifest: testFiles ^tests\.rs$; pkg beside Cargo.toml is wasm-pack
  output.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CodeQL's js/unvalidated-dynamic-method-call: the reader comes from a plugin's
syntax slot, chosen by the list file's language. A slot that is not a function
now falls back to the TypeScript reader instead of throwing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…from one parse

A plugin's manifest may name a tree-sitter parser and queries (treeSitter);
the core derives its source, declares and topLevel slots and its interfaces
table's import answers from the parse (src/plugins/syntax-trees.ts), and
refuses a hand-written reader of the same text beside it. The parser runtime
(web-tree-sitter 0.27.1, the package's first runtime dependency) starts once
per process, in the CLI and the test preload; every tree is freed.

Each file's parse is kept in .coherence/parses by content digest, and the
warm-up a session start launches pre-reads the project, so a whole-project
reading parses only what changed (PostHog mass 3.4 s; 14.8 s unkept).

Python moves first: python.scm and the parser from tree-sitter-python 0.25.0
replace its three hand-written readers, which disagreed about a from-import
split across lines (df-7ac38ee4).

Five invariants in src/plugins/Plugins.spec.md, each refuted and witnessed.
Experiment x-adaa7243.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A plugin's resolveModule may return a module, its folder and every file of
it, where the language's module is several files (a Go package, a SwiftPM
target). The core joins each imported name to the module's files that
declare it, and every file when the module is imported whole or no file
declares the name (src/economy/source.ts importsOf), so no plugin picks a
file of a module by a rule of its own.

Four plugins had four such rules, and every one missed a file its importer
used (d-cb7829df, c-38a1baae). Go drops its path#Name specifiers: its imports
now carry the names they use, and its packages resolve as modules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
parse-gap held defects in entrance rules, Python URL patterns, the shell
reader and Python's imports, which no one guard covers, so the first guard
any of them got stood for all and made df-7ac38ee4 read as its failure. It
is now a family (d-1ad22dfb), and df-53e501ca, misfiled there, is classed
silent-skip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A new duty, answers agree, runs over every file of each check project the
slots that answer one question of the same text: a name the source scan
declares is one topLevel finds at the top level; a type declaration where
interfaces and declares both say so; a name an interfaces import list renames
is one the source scan reads imported. An unknown kind is no answer.

Over a check project whose renamed import spans lines, Python's two
hand-written readers from before its parser fail it, naming the name
(df-7ac38ee4). Every bundled language plugin passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…beta.0

coherence plugin shapes counts every shape of import a project writes
through its parser beside the plugin's check projects, naming each no check
project holds. plugin check gains server agrees (every declaration the text
reading finds is a top-level symbol the language server reports, through a
new optional adapter method, topLevelSymbols) and reference reading (every
language plugin's check project maps one reference project, a two-file
store, a render using both, a main, and its readings must recover each
relation); coherence plugin calibrate and coherence plugin compare are the
readings of the same over a real project and across every plugin.

Python's check project now holds every import shape; every bundled check
project holds the reference project. Found on the way: Python's own plugin
check was failing on this branch (df-11317061, now guarded); three vias named
no test exactly (df-03839b92); a star-import reference Pyright reports only
sometimes (df-e2663a97).

A prerelease version publishes under its own dist-tag and is a GitHub
prerelease (release.yml). Version 1.11.0-beta.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The parse store refuses a link anywhere on its path, reads and appends
through no link, creates its temporary file exclusively, removes only its own
files, reads only files signed with a machine key, and runs one warm-up at a
time (df-7c50adfe: a repository committing .coherence/parses/python as a link
to ~/.ssh had the warm-up empty it; shipped in v1.11.0-beta.0, now deleted).
It flushes in batches, and one-line parses keep out of it.

A whole-module import reaches a reference name only where the importer
spells it; Rust's reference entry is its real main.rs. Plugin interface 3
brings the parser path, module imports, topLevelSymbols and the reference
map; a plugin written for 2 is told those duties are not held, never failed.
An extending plugin's source wrapper is honoured over a parser. plugin shapes
reads the check projects plugin check reads, by language, counting only
imports that resolve there. TypeScript reads re-exports as imports. The
release tag is the prerelease's leading letters, or next. Nits: parser paths
stay in the plugin folder, the fingerprint names the runtime, nested symbols
leave a flat server answer, the declared-names stamp is to the nanosecond, Go
reports every name a line declares. The docs point new-language authors at
the parser path and Python, not Go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…laims

docs/plugins.md's duty, manifest key, syntax slot, treeSitter key, capture
and adapter method tables are held to the lists the code holds; the
manifest, treeSitter and adapter lists are pinned to their types, so a
member added to a type fails the typecheck until the list, and the guide,
name it. Every plugin.json example validates at the interface version this
Coherence runs. The guide marks which bundled plugins read through a parser
and which by hand, and says which to start from; each mark is held to the
manifests. Holding them found the manifest table missing basisFiles and
moduleFolders, naming provides.language for provides, and the capture table
missing @import; each is fixed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On PostHog's registry each leaf's warm-up kept its own store, so mass and
status at the repository's top parsed every file again (16.4 s against
main's 3.9 s) and grew a log nothing folded in (df-266d6a61). Parses are now
kept at the registry's top (storeRootOf), a leaf's warm-up pre-reads the
top's files too, and the rewrite prunes by age, keeping anything seen in the
last 14 days, so one leaf's pass keeps the others'. Mass at the top after a
warm-up: 3.6 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cost held defects in unrelated places (the dead-trigger check paying for its
triggers, a parse store kept per registry leaf), which no one guard covers,
so the dead-trigger check's guard read four later defects as its failures.
It is now a family: each cost defect closes with its own guard or a decision.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Go plugin names a treeSitter block (tree-sitter-go 0.25.0's wasm,
go.scm, GO_RESOLVE, privateName ^\P{Lu}); its hand-written source,
declares, topLevel and interfaces readers are gone, and its entrances
read their own funcs and router imports. The core pairs a name used
through a qualifier (@use.qualifier, @use.name: st.Seal) with the
whole-module import that binds it (its alias, else the resolution's
boundName, else the specifier's last path element), so a Go import
still joins only the package file declaring the name it uses.
privateName is read with Unicode classes. The check project renames
one import, so plugin shapes finds the renamed shape held.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…om one parse

Rust joins Python on the parser path: plugins/rust names tree-sitter-rust
0.24.0's wasm and rust.scm (treeSitter), and the hand-written source,
declares and topLevel slots (RUST_SOURCE, rust-interfaces.ts,
rust-top-level.ts) and the interfaces table's import answers are gone.
Where an import leads stays code: RUST_RESOLVE in rust-source.ts.

Core: a query may capture @import.prefix, a path a group of imports
shares; the core joins every prefix over an import's specifier, outer
first, with the manifest's treeSitter.separator, so use a::{b::{c as d}}
reads a::b::c at any depth. The census places an import whose match
captures no statement in the statement a match of its own captures.

An inline mod's keyword prefixes the paths inside it, so use super::* in
a test module is read from that module: over PostHog's rust/, 692 imports
the line reader resolved one module too high are gone, 17 it missed are
read; declarations agree on all 15,683.

Rust's check project gains a renamed, globbed, multi-line and in-block
import. Four invariants restated or new, each refuted and witnessed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ading derived from one parse

The TypeScript plugin names a treeSitter parser: tree-sitter-tsx.wasm from
tree-sitter-typescript 0.23.2 reads every file of its pattern (the TSX grammar
reads each .ts file of PostHog's and Coherence's the TypeScript grammar reads,
but one vendored <any>row cast; the TypeScript grammar errs on 3,196 of
PostHog's 3,510 .tsx files), with typescript.scm. Its hand-written source,
declares and topLevel readers and its interfaces table's import and default
answers are gone; resolution stays code (TYPESCRIPT_RESOLVE).

Core, additive: @import.default (a default import, read as the name default)
and @default (the default export, declared or by name) captures; a capture
whose name starts with an underscore is the queries' own, for a predicate;
a declaration whose value the queries know wins over an unknown match of the
same name and line; a line that leaves brackets open is read again with them
closed; declarations carry isDefault, which the interface reading reads.
plugin eject copies a file that is no module byte for byte, so an ejected
parser plugin's .wasm is no longer corrupt.

Corpus comparison against beta-fixes' readers over Coherence (352 files) and
PostHog (15,507 files): every difference classed; the new reader is wrong
only past a grammar error (27 PostHog files) and for 10 local export renames
(d-b4fe6dee). The check project gains a barrel putting renamed, glob, whole
module and multi-line imports to the plugin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#156

# Conflicts:
#	src/plugins/Plugins.spec.md
#	src/plugins/registry.ts
#	src/plugins/syntax-trees.test.ts
#	src/plugins/syntax-trees.ts
…sitter

Every bundled language plugin now reads through a parser. The merge keeps
Go's qualified uses and Rust's prefixes beside TypeScript's default exports
and the queries' own underscore captures. The guide says so: the claims
marker names all four as parser-read, the capture and treeSitter tables
gain @import.prefix, @import.default, @default, @use.qualifier, @use.name,
separator and boundName, and it names what each grammar cannot read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Making cost a family (b78ee04) meant a later cost defect is no guard failure,
so the convergence test, which counted a repeat in cost, read none. The test
now repeats in overmatch, a declared class that keeps the guard rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…audits them

The parse store's fingerprint digested the parser and the queries alone,
while Rust's separator shapes the specifier a kept parse holds, and an edit
to how a tree is read kept its fingerprint (df-b9b2d3ef). Now:

- fingerprintOf digests every treeSitter input KEPT_FROM places as shaping a
  kept parse, a Record over the manifest's keys so a new key fails to compile
  until placed, plus the store format, the web-tree-sitter release and
  syntax-trees.ts's own source;
- a test holds each left-out key (privateName, resolve) to change no kept
  parse of any bundled plugin's check project, and syntax-trees.ts to import
  no other Coherence code that could shape a parse at run time;
- each warm-up re-parses a sample of eight kept texts; any difference
  discards the store whole, and orient says so for a week.

Also restores the Go bullet's kinds line, which the Rust merge dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- An old-style cast (<any>x) in a .ts file no longer hides every later
  declaration (df-42f25182). treeSitter gains an optional alternate
  parser, read with the same queries where the parser's tree errs and its
  own errs less; TypeScript names the TypeScript grammar beside TSX. Its
  queries name no JSX node so both compile: a JSX constant declares
  unknown. The guide no longer claims statements around a grammar error
  still read.
- The parse store signs its contents, not only its fingerprint, and each
  log line signs itself (df-aad5bf07). The forged-store test wrote a
  malformed body the shape check refused first, so it never tested the
  seal; its forgery is now well formed.
- A folder committed where the store's file goes, or a lock stamped in the
  future, no longer stops a warm-up; the stale-parses audit is signed and
  bounded, so a committed audit.json says nothing at a session start.
- A long-lived process reloads a store another process rewrote; the
  fingerprint digests the very bytes compiled; the import guard catches
  re-exports and dynamic imports; the guide's claims refutation is
  witnessed again against a break that still fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
beta.1 was cut from a branch that never took #153, so it put "use these
names" and the over-cap vocabulary back in adopters' session starts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Upstream has merged no grammar change since 0.23.2 (2024-11), and its
grammars left an error in 64 of PostHog's 5,832 frontend files, hiding 44
declarations in three of them (df-fead3a57). Both parsers are now built
from 0.23.2 with grammars/tree-sitter-typescript/coherence.patch:

- an import type is a primary type and takes type arguments, so
  import('x').T<U>, import('x').T[], keyof import('x').T and
  import('x').T<U>['k'] parse (upstream #322);
- the semicolon scanner reads identifier characters after in and
  instanceof, so a member named in_progress or in on the line after
  another no longer reads as the operator;
- a bare & is JSX text and a JSX string's text (Billing & Usage,
  to="?a=1&b=2"), and a character reference still reads as one.

Over PostHog's frontend one file still errs (an optional destructured
parameter in a function type, no declaration lost), the 44 are read, and
none is lost elsewhere. build.sh rebuilds both parsers byte for byte with
tree-sitter-cli 0.27.1 and Emscripten 6.0.13 (d-fd4e2e86).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Parse store (df-04fddaf1, df-8b32c22b):
- A log a repository commits costs a few signatures, not one per line:
  a line is checked only where its signature has a signature's length,
  the log is read a chunk at a time and no further once more than
  LOG_TORN_MAX lines fail, and a store or log over STORE_MAX_BYTES is
  never read. 17 reads over a planted 16 MB log took 4.4 s; now 4 ms.
- A folder or link at warming.lock no longer turns the warm-up off.
- A process's own append no longer hides another's discard.
- A pipe at the log's path no longer hangs a process.
- An empty key file is replaced, and the key is linked into place whole.
- Each claim is now held by a test: a forged log line, the alternate
  fingerprinted as a file of its own, and the cases above.
- Test files get their own XDG_CONFIG_HOME, so the suite never writes
  the developer's key.

Alternate parser (df-f1d6f9ce): chosen by the bytes its errors cover,
not by how many there are, so a file mid-edit with a cast and two typos
reads whole; compiled the first time a tree errs, not in every process.

TypeScript grammar patch (df-7f3e3885): a name ends at Unicode white
space, so `a\n in<NBSP>b` parses again as it did stock; a member named
instanceof, `in?:` and a class field `in = 2` end the line before them;
a bare & before a JSX string's quote leaves the quote. build.sh checks
emcc 6.0.13 and fails unless both parsers match the recorded digests.
Over 17,106 PostHog files: 191 erring files become 52, 85 declarations
come back, none lost, none newly erring.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rence

- The parse store touches its files only through StoreFolder, which
  refuses a link, a pipe, a folder or an oversized file and reads a log a
  chunk at a time. parsesFolder, the one place the store's path is made,
  is a chokepoint the compiler refuses outside the module
  (visibility-choked), and a totality oracle, reading syntax-trees.ts
  through the TypeScript plugin's own parser, fails on any node:fs use in
  store code outside the gateway.
- A store's generation, in its signed first line, is made only by
  rewrite; each log line names the generation it was appended under; a
  process reads again when the generation on disk is not the one it
  loaded. No append can hide another process's rewrite or discard. A
  parse-based test holds newGeneration to ParseStore.rewrite.
- The TypeScript parsers are pinned to the digests build.sh records, and
  build.sh builds only after tree-sitter-typescript's corpus and
  Coherence's own (each fix and each form it must leave alone, with its
  whole tree) pass in both dialects. CI's typescript-parsers job rebuilds
  them byte for byte in emsdk 6.0.13 on every pull request. The corpus
  fails e8f802e's patch and the over-eager scanner; TypeScript's own
  declaration files and node_modules' scripts failed neither.
- src/lifecycle/prerelease.ts is the one way to cut a branch prerelease:
  it refuses unless the version is a prerelease, every bundled plugin and
  mod that follows the package names it, the tree is clean, HEAD is
  pushed, origin/main is an ancestor of HEAD and the tag is free.

Each bullet refuted and witnessed batched and alone (d-78b9fc37).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
daniloc and others added 2 commits October 27, 2026 00:38
Each held defects in unrelated places no one guard covers: the project's-name
guard read the parse fingerprint's missing separator as its failure, and the
scope control guard read two parse-store defense defects as its failures. Each
defect of either now closes with its own guard or a decision (d-2b2898cf).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread src/adapters/go-source.test.ts Fixed
Comment thread src/lifecycle/hook.ts Fixed
Comment thread src/plugins/check.ts Fixed
Comment thread src/plugins/check.ts Fixed
Comment thread src/plugins/reference.test.ts Fixed
daniloc and others added 7 commits October 27, 2026 00:44
bench/interfaces/own-tree.ts asked TypeScript's declarations of a runtime it
never started and failed on PR #157's bench job; every bench script that reads
Coherence's source in-process now starts it first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A file nested thousands deep around an error no longer crashes the
  warm-up and mass: errorSpan walks the tree with its own stack, the
  warm-up reads each text on its own, and treeOf frees both trees on every
  path (df-6e6c2b80).
- The parse store: a long-lived reader reloads when the store's first line
  changes, so a store it cannot verify is read once; a log line is joined
  once, never copied per chunk; a discard seals an empty store under a new
  generation, so a reader of a log-only store sees it (df-1ebcefa2,
  df-2456bd98). A dead warm-up lock is removed only under a takeover lock
  one warm-up holds, and one no warm-up can read is taken over
  (df-ed777a88); files are created whole by link; an append refuses a file
  with a second name; a session start reads an audit only up to 4 KB.
- The warm-up reads only a project's regular source files, never through a
  link or a pipe, none over 16 MB (df-194f5620). A relative
  XDG_CONFIG_HOME is ignored, as the XDG spec says.
- The gateway and generation tests now catch createRequire, a built-in
  asked for by name, the store's folder spelled in a path, and an alias of
  ParseStore's folder; the store's link, O_NOFOLLOW and hard-link defenses
  are each held by a test (df-69c8ab4d, df-ff68d46c).
- The TSX grammar's bare & in a JSX string is a pattern of its own, so the &
  operator is an & node again (df-7cc06057). The corpus pins members named
  in$, in2, inü and ina and character references at their limits.
- build.sh fetches tree-sitter-cli 0.27.1, wasi-sdk 34 and binaryen 132 by
  digest and hands the compiler to the CLI, which downloaded it unchecked;
  the emcc check compiled nothing. It installs the parsers only once they
  match. CI's typescript-parsers job drops the emsdk container and verify
  waits on it (df-bf1275c5).
- prerelease.ts releases to the repository package.json names, through its
  remote, fetched with --prune and read by full refs; it refuses untracked
  files, a manifest a major bump left behind, unknown arguments and a
  missing notes file (df-089ac5a9).
- The plugin guide names what the patched grammar newly errs on, as tsc
  does, and where it recovers less (df-467db56c, d-6b0fbea9).
- The Scope bench holds the page under 5 MB (d-08e78ac9).

Each fix leaves its bug unrepresentable where the code allows it
(d-262d3cd3), held by a test that reads the code: every store file is
opened by StoreFolder's #openFile alone and each file system effect has
one method; no function of syntax-trees.ts calls itself; only seal makes a
generation, only get and seal set what a process loaded, only takeOver and
unlock remove the warm-up lock; every TypeScript operator is a node of its
own spelling; verify needs every CI job but the bench; every enumeration
of the project passes isFileOnDisk, a regular file or a link to one; the
prerelease script names no remote of its own. The kernel practice close
a defect now teaches it to every project that runs Coherence: name what
let the bug be written, fix it so the code cannot express it again, guard
that against any second way to write it (d-b12df633, d-354e6716).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…th resolved ones; add a dead-end alarm

Four rounds of blind review found about thirty defects in the parse store,
each round's local fixes adding surface the next broke: the store shared
the untrusted repository's namespace and was mutable shared state. This
round changes the representation instead (d-d1229127).

- Kept parses live in the user's cache ($XDG_CACHE_HOME/coherence/parses,
  else ~/.cache), filed by the parser's fingerprint and the text's digest,
  never in a project. A parse is written once, in a segment file written
  whole and never changed; a long-lived reader reads the segments others
  wrote since and reads again when one it read is gone; a warm-up merges
  more than eight segments, keeping what was read in fourteen days; one
  warm-up per project. The store is used only where the cache folder is
  the user's alone. Signing, generations, the log, the takeover lock,
  StoreFolder's hostile-folder defences and the code-reading tests that
  tried to rule out second doors are gone; every project, worktree and
  registry leaf shares one store. syntax-trees.ts: 1,206 lines at 887abd8
  to 925. Two bullets retired by decision (d-ab934293, d-8aa2df3f).
- The patched TypeScript grammar is held by its output, not by text: a
  stock-against-patched comparison over upstream's corpus, Coherence's,
  TypeScript's lib files and this repository's source, every node and
  every token by spelling, against intended-differences.json; it fails on
  all six rename variants a reviewer built. build.sh fetches and checks
  every input, then builds with no network (docker --network none in CI,
  sandbox-exec on macOS); both parsers rebuild byte for byte.
- Prereleases are cut by .github/workflows/prerelease.yml from a clean
  checkout of a pushed branch, targeting its resolved sha, never npm;
  prerelease.ts only checks.
- The dead-end alarm: spec --check, orient and the convergence reading
  name a component collecting defects across classes and rounds after its
  first guard, and print the escalation that asks a human whether to
  redesign; on this journal it names src/plugins alone. The journal is
  read from every worktree; a defect names its component. close a defect
  weighs a different design first where the alarm names the component.
- The kernel practice keep state where it is true, after "Data is
  destiny": one home per truth, chosen by who may write it, everything
  derived computed from it (d-fd38bffc).
- From the review of the previous round: the machine key and the user
  folder (always absolute, a key this user alone holds), prerelease
  versions read from HEAD, the plugin guide's corrected numbers, changed
  tracked links kept in a change, the store's hostile-input tests.
- Work orders for what this round found and left: versions written by the
  build (w-d112a3e4), refutations derived from run records (w-f50ec192),
  measured numbers in docs (w-7f9dbfb0), a question-counted structure
  budget (w-77d2fe8f), re-closing a defect whose guard was retired
  (w-fdc00207), practice delivery attributed to the subagent that ran a
  command (w-9c70b73f), the Journal tab across worktrees (w-4c76c5ea).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…parser alone

The four slots a plugin could fill to read its language's text by hand
(source, declares, topLevel, kept) are gone from the plugin interface,
pluginApi 4 (d-a59ee38f, w-8e253e9e). Nothing but test fixtures filled
them: the four bundled language plugins and mnemion's Swift plugin all
read through treeSitter.

- A plugin of any pluginApi version that fills one is refused, naming the
  slot and how to read the language through treeSitter; one that fills
  none (convex, quint, declared) loads as before (OLDEST_PLUGIN_API stays
  1, d-881c0b46).
- The parser-derived readings are asked through parsedOf; syntaxOf serves
  the five slots that remain (entrances, interfaces, notice, lists,
  boundaries), so the type checker refuses a read of a removed slot
  (d-581051d3). The bundled manifests name pluginApi 4 (d-cfc26b51).
- A test reads every non-test module of src and fails on any read of a
  hand-written slot, a cast among them; the fixtures that filled the slots
  read a made-up language through Python's parser (fixtures/parser-
  plugin.ts). Refuted by a kept read put back in keptModules.
- The plugin guide, its claims test and the bullets that described the
  slots say what is now true.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the patch cannot write; harden the dead-end alarm

The blind review of 2558929, fixed as one round (d-a5c35add): a check
never trusts input the thing it checks controls.

- The parse store keeps each kept parse as a file of its own, named by
  the text's hex digest under the parser's fingerprint, written whole
  under a temporary name and renamed into place, never written again, as
  git keeps a loose object. A reading opens only the files of the texts
  it reads and holds no store in memory; nothing merges, reloads or
  locks. A parse read is stamped at most once a day, and a warm-up sweeps,
  once a day, what no process has read in fourteen days, temporary files
  of writes that died, discarded stores and dead fingerprints. The store
  is used only where every folder and link on its path, as written and as
  followed, is the user's or the system's and none others may write
  unless sticky; each read checks the parse's folder and file. Two
  warm-ups at once share the work in orders of their own. PostHog's
  19,902 Python files: 32 s first warm-up, 2.0 s the next, mass 3.4 s.
  Two bullets retired (d-320d365d, d-e3e40b61); df-ef109f3e,
  df-d3eacab6, df-a05047c4, df-f1f4499c, df-fd915434 closed by guards.
- The TypeScript grammar's build reads upstream's corpus from the pinned
  clean tree (tree hash checked, replace objects ignored), refuses a patch
  touching anything but define-grammar.js and scanner.h, and keeps
  Coherence's expected trees in grammars/tree-sitter-typescript/corpus,
  outside the patch. compare.mjs holds the parsers to every declared
  expected tree, one region per named form; the member-named-in query
  covers exactly the names the scanner acts on, methods included. Both
  parsers rebuild byte for byte.
- The prerelease workflow splits into a read-only build job and a
  release job that alone may write; its smoke check runs plugin check
  from the installed tarball; a failed ls-remote stops the cut; its test
  is a lint of the file's shape and says so. The release job has not run.
- The dead-end alarm counts every defect after a component's first
  guard, open or closed, and names a component whose arrivals rise; an
  unclassified defect counts as a class of its own; --component must own
  the defect's files; only an acknowledgement carrying the owner's words
  restarts the count; one window; the journal across worktrees is read
  whole, deduplicated by bytes. A practice may fire on a defect's class:
  keep state where it is true fires on duplicated-fact.
- An ejected copy of a 1.10 plugin is told to eject again from this
  release; an interfaces table states facts alone, its readings coming
  from the parse; the text-scan test of the slots is retired for the
  registry's refusal (d-a1045837, d-e0d78a54).
- The src/plugins dead end, escalated (e-24d456c1), acknowledged by the
  owner: "the dead end was addressed through redesign" (ak-6ae550c5).
- Open: a leak-guard flake seen once under the full suite (df-e8424adc).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the alarm for a helpful agent

- Every parse and query of a text stops past a fixed count of the
  parser's own progress calls, the same on every machine: a text past it
  reads as matching nothing, is kept nowhere and orient names it. A 12 MB
  file of one-line statements aborted the parse runtime and with it every
  later parse; PostHog's largest source file uses a tenth of the bound. A
  parse cut short resets the parser, which would otherwise resume it.
- Import prefixes are spelled by walking a specifier's ancestors, not by
  scanning every prefix in the file: 16,000 grouped Rust uses, 14.6 s to
  245 ms.
- The parse store: a store inside the repository being read (a cache set
  inside a checkout) is not used for it; a folder only the user's own
  personal group may write counts as the user's (Debian and Ubuntu's
  default); orient says when the store is off and why; a parse too large
  to read back is never kept; a read stamp that cannot be written loses no
  parse; a sweep dated ahead is no sweep; temporary files are swept
  wherever the store writes; the warm-up reads no file whose real path
  leaves the project. Tests for the fingerprint's runtime and source, the
  path walked as written, sampling over the whole pass and the 32 MB bound.
- A misread alarm: a plugin's queries may capture @misread on a node valid
  source never holds; the TypeScript plugin's capture a reserved word read
  as a name outside import and export names, and orient names the word and
  its line. Coherence evaluates #not-parent-type? and refuses any other
  predicate tree-sitter leaves unevaluated. Over 171,579 files, error-free
  trees held no such name elsewhere.
- The grammar comparison's claim says it holds over its inputs alone; a
  corpus case no intended difference names must read as the stock parsers
  read it; in and instanceof before a parenthesis join the corpus. The
  prerelease smoke step checks each copy of the TypeScript parsers in the
  tarball against parsers.sha256.
- The dead-end alarm serves an agent that wants to work helpfully
  (d-9d919dd8): a defect must say where it lies; answering verbs resolve
  ids in every worktree; a retracted defect counts nowhere; paths are
  normalized before ownership; spec --check reports an unreadable journal
  file rather than crash; orient is silent with no dead end; reading every
  worktree's journal goes through an index in the user's cache (about
  300 ms to 105 ms here). A whole session start still stops on an
  unreadable file in its own journal (u-561ee937).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@daniloc daniloc changed the title Read every bundled language through a tree-sitter parser; harden the parse store (1.11.0-beta.3) Read every bundled language through a tree-sitter parser; keep parses as files of their own; release 1.11.0 Oct 27, 2026
…point

The ejected 1.10.1 fixture holds a second typescript.ts declaring
SERVER_BIN, so the chokepoint named by file name alone was not run in
CI's checks job.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@daniloc
daniloc merged commit 5a55b02 into main Oct 27, 2026
20 checks passed
@daniloc
daniloc deleted the beta-fixes-2 branch October 27, 2026 22:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants