rename docs/ to harness/, and reserve docs/ for the reader

docs/content-model.md opens with "Engine specification". It is also where the
rule lives that a leading underscore makes a file unaddressable — and the human
who owns this site did not know that rule, because nothing in this repository is
addressed to an author. Twelve documents named docs/ while being exclusively
about building the parser is a signpost pointing at the wrong room.

Naming the directory for its audience makes the gap visible instead of hiding it.
docs/ is now reserved and deliberately absent: an empty docs/ is an honest
statement that end-user documentation does not exist, where docs/ full of parser
specs was a claim that it did.

HARNESS.md stays at the root. Root holds the three entry points — README.md for a
human, CLAUDE.md for an agent, HARNESS.md for whoever maintains the machine — and
harness/README.md is the map of the directory, so moving the guide inside would
have collided with it for nothing.

Mechanical and wide: 100 path references across 24 files. Every verify.sh gate
that names a doc by path, the directory lists the dangling-path and ADR-number
gates scan, surface.sh's output target, the Makefile, CLAUDE.md's read order, the
skill, four commands, and two Go package comments. A first pass with a shell loop
silently edited only four files and the rest still said docs/; the fix was to
write the file list out and check the remaining count was zero rather than trust
the loop's exit status.

No rule, threshold, gate or obligation moved — this is a rename, and the gates
demonstrated it twice: they stayed green on the new paths, and the ADR-number gate
caught ADR-0082 before the entry existed.

Deferred, both on the human's call: the end-user documentation site itself, which
wants its own decision about where it lives and whether its claims are gated; and
moving examples/ under docs/, since demo-site is a live site root that verify.sh,
the coverage test and make demo all point at, and moving it would couple a rename
to a design nobody has made.

31 files, +146/-106.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-02 20:11:06 +06:00
co-authored by Claude Opus 5
parent 9349c54d2e
commit ec6e9838f0
31 changed files with 146 additions and 106 deletions
+2 -2
View File
@@ -2,10 +2,10 @@
description: Record an architectural decision in six lines description: Record an architectural decision in six lines
--- ---
Append an ADR to `docs/decisions.md` using the exact format at the top of that file. Append an ADR to `harness/decisions.md` using the exact format at the top of that file.
First, before writing anything: find the doc that owns this topic via the ownership table in First, before writing anything: find the doc that owns this topic via the ownership table in
`docs/README.md` and read it. If it already carries the rule, do not write an ADR — amend that doc `harness/README.md` and read it. If it already carries the rule, do not write an ADR — amend that doc
and say that is what you did. An ADR that restates an existing doc is a duplicate, not a decision. and say that is what you did. An ADR that restates an existing doc is a duplicate, not a decision.
Rules: Rules:
+1 -1
View File
@@ -16,7 +16,7 @@ Look for:
- Tests that assert on private helpers, or that need a mock to exist. - Tests that assert on private helpers, or that need a mock to exist.
- Dead code, commented-out code, `TODO`s older than the feature that introduced them. - Dead code, commented-out code, `TODO`s older than the feature that introduced them.
- Comments that narrate control flow. - Comments that narrate control flow.
- Anything in `docs/` describing code that no longer exists — excluding `ideas/` and - Anything in `harness/` describing code that no longer exists — excluding `ideas/` and
`reference/`, which are never audited against the code and must not be opened here. `reference/`, which are never audited against the code and must not be opened here.
Report as a table: item, location, why it has not earned its place, LOC recovered if deleted, Report as a table: item, location, why it has not earned its place, LOC recovered if deleted,
+1 -1
View File
@@ -3,7 +3,7 @@ description: Check the architecture invariants against the code, not against the
--- ---
`verify.sh` checks what a grep can check. This pass checks the nine invariants in `verify.sh` checks what a grep can check. This pass checks the nine invariants in
`docs/architecture.md`, which need reading the code. Run at arc boundaries, before a freeze, and `harness/architecture.md`, which need reading the code. Run at arc boundaries, before a freeze, and
before the first deploy. before the first deploy.
For each invariant, answer **held / violated / not yet applicable**, with a file and line when For each invariant, answer **held / violated / not yet applicable**, with a file and line when
+3 -3
View File
@@ -5,12 +5,12 @@ description: Reconcile the docs against the actual code and report drift
Reconcile documentation with reality. The code is the truth; the docs are the suspects. Reconcile documentation with reality. The code is the truth; the docs are the suspects.
1. Inventory the actual Go files, their line counts, and the non-stdlib dependencies in `go.mod`. 1. Inventory the actual Go files, their line counts, and the non-stdlib dependencies in `go.mod`.
2. Compare against `docs/state.md`: inventory rows, counters, latent items. 2. Compare against `harness/state.md`: inventory rows, counters, latent items.
Recount the counters **from the code** — number of render transforms, routing cases, views, Recount the counters **from the code** — number of render transforms, routing cases, views,
output formats, extensions — rather than trusting the recorded numbers. output formats, extensions — rather than trusting the recorded numbers.
3. Check `docs/architecture.md` STATUS lines: has a primitive become real, or is one described 3. Check `harness/architecture.md` STATUS lines: has a primitive become real, or is one described
as live when it is not built? as live when it is not built?
4. Check `docs/content-model.md` against what the parser actually accepts. It describes today's 4. Check `harness/content-model.md` against what the parser actually accepts. It describes today's
behaviour and carries no markers, so every sentence in it is a claim to test. Frontmatter fields behaviour and carries no markers, so every sentence in it is a claim to test. Frontmatter fields
the code reads but the doc omits are drift; fields the doc promises but the code ignores are worse the code reads but the doc omits are drift; fields the doc promises but the code ignores are worse
drift. Worst, and the kind that has actually shipped: a subcommand, flag or gate the doc describes drift. Worst, and the kind that has actually shipped: a subcommand, flag or gate the doc describes
+11 -11
View File
@@ -19,7 +19,7 @@ are the first and the last.
| the site root | Out of reach: it lives in its own repo (ADR-0011). A disk-contract change ships a **written migration step** the author runs, plus a note on whether any bundle key or URL moves. Never claim to have migrated files you cannot see. | | the site root | Out of reach: it lives in its own repo (ADR-0011). A disk-contract change ships a **written migration step** the author runs, plus a note on whether any bundle key or URL moves. Never claim to have migrated files you cannot see. |
| code | The engine, plus a test for the new behaviour. | | code | The engine, plus a test for the new behaviour. |
| the theme | Out of reach, like the site root (ADR-0023). This repo ships a **contract extension** in | the theme | Out of reach, like the site root (ADR-0023). This repo ships a **contract extension** in
`docs/theme-contract.md` plus a written note of what a theme must do — never the theme itself, and never `harness/theme-contract.md` plus a written note of what a theme must do — never the theme itself, and never
a layout or markup decision dressed as an engine feature. | a layout or markup decision dressed as an engine feature. |
| fixtures and emitted output | Fixture sites in `testdata/`, the embedded default templates (a reference implementation of the contract), and anything reading a field or path shape you altered. | | fixtures and emitted output | Fixture sites in `testdata/`, the embedded default templates (a reference implementation of the contract), and anything reading a field or path shape you altered. |
| harness docs | Every doc, ADR, marker or example that assumes the old form. An ADR that mandates what you just made optional is not stale, it is **contradicted** — supersede it, do not quietly reword it. | | harness docs | Every doc, ADR, marker or example that assumes the old form. An ADR that mandates what you just made optional is not stale, it is **contradicted** — supersede it, do not quietly reword it. |
@@ -28,15 +28,15 @@ a layout or markup decision dressed as an engine feature. |
## 1. Clarify ## 1. Clarify
Read `docs/state.md`, then `docs/README.md` — the map, always, before deciding what else to open. Read `harness/state.md`, then `harness/README.md` — the map, always, before deciding what else to open.
**A handoff is not evidence.** If `.scratch/continue.md` says something is pending, waiting, or unfinished, **A handoff is not evidence.** If `.scratch/continue.md` says something is pending, waiting, or unfinished,
run `git log --oneline -- <the path it names>` before planning from it. The file is uncommitted, so no gate run `git log --oneline -- <the path it names>` before planning from it. The file is uncommitted, so no gate
compares it to anything, and it is written *during* a change whose commit can still move underneath it. The compares it to anything, and it is written *during* a change whose commit can still move underneath it. The
log is the only record of what landed. Offering the human work that is already in `HEAD` wastes the turn log is the only record of what landed. Offering the human work that is already in `HEAD` wastes the turn
they asked the question in. they asked the question in.
For code, read the compressed form before the source: `docs/surface.md` locates every declaration, For code, read the compressed form before the source: `harness/surface.md` locates every declaration,
`go doc` gives a package's surface, a test states its contract. Disciplines: `docs/context-economy.md`. `go doc` gives a package's surface, a test states its contract. Disciplines: `harness/context-economy.md`.
Use its topic-ownership table to list the docs owning anything this change asserts a rule about, Use its topic-ownership table to list the docs owning anything this change asserts a rule about,
and read those. That list is a floor: skipping it is how a rule gets written twice and the second and read those. That list is a floor: skipping it is how a rule gets written twice and the second
copy contradicts the first. copy contradicts the first.
@@ -96,7 +96,7 @@ If the plan reveals a trunk, say so instead of planning and offer the leaf.
- Smallest code satisfying the success criteria. Nothing for the version after next. - Smallest code satisfying the success criteria. Nothing for the version after next.
- Only the planned files. No renames, reordering, reformatting beyond `gofmt`, or tidying of - Only the planned files. No renames, reordering, reformatting beyond `gofmt`, or tidying of
neighbouring code. neighbouring code.
- Something wrong outside the plan? One line on the Latent list in `docs/state.md`, keep moving. - Something wrong outside the plan? One line on the Latent list in `harness/state.md`, keep moving.
That is the whole mechanism; use it instead of a drive-by fix. That is the whole mechanism; use it instead of a drive-by fix.
- Reuse the existing shape before inventing one. Copying eight lines twice beats an abstraction at - Reuse the existing shape before inventing one. Copying eight lines twice beats an abstraction at
use two; the third use is when it becomes a design. use two; the third use is when it becomes a design.
@@ -123,12 +123,12 @@ not run something, say which and why.
## 5. Document ## 5. Document
Same change, not later. Triggers: `docs/README.md`. Walk the propagation table above — every surface Same change, not later. Triggers: `harness/README.md`. Walk the propagation table above — every surface
done or explicitly n/a. Minimum: done or explicitly n/a. Minimum:
- `docs/state.md`: inventory rows, counters, latent items added/removed — in *this* commit, never a - `harness/state.md`: inventory rows, counters, latent items added/removed — in *this* commit, never a
trailing one, which is what `verify.sh` compares (ADR-0057). trailing one, which is what `verify.sh` compares (ADR-0057).
- `docs/decisions.md`: an ADR if the choice is expensive to reverse. Six lines. First open the doc - `harness/decisions.md`: an ADR if the choice is expensive to reverse. Six lines. First open the doc
that owns the topic — if it already carries the rule, amend it there; an ADR restating an that owns the topic — if it already carries the rule, amend it there; an ADR restating an
existing doc is a duplicate, not a decision. existing doc is a duplicate, not a decision.
- Any other doc **only if the change made it wrong.** Never state one fact in two docs. - Any other doc **only if the change made it wrong.** Never state one fact in two docs.
@@ -139,7 +139,7 @@ Doc edits are surgical too. Prefer deleting a stale line to appending a correcte
elsewhere in the old terms: elsewhere in the old terms:
``` ```
grep -rn '<old form>' docs CLAUDE.md HARNESS.md ideas reference .claude scripts grep -rn '<old form>' harness CLAUDE.md HARNESS.md ideas reference .claude scripts
``` ```
`verify.sh` fails on dangling file paths, ADR numbers and `CLAUDE.md` section refs. It cannot detect `verify.sh` fails on dangling file paths, ADR numbers and `CLAUDE.md` section refs. It cannot detect
@@ -216,7 +216,7 @@ Swept: the old form you grepped for after a rename, or "n/a"
| Renaming "for clarity" mid-feature | Drive-by refactor | Latent list | | Renaming "for clarity" mid-feature | Drive-by refactor | Latent list |
| Explaining why the boundary can bend here | It cannot (ADR-0003) | Stop and ask | | Explaining why the boundary can bend here | It cannot (ADR-0003) | Stop and ask |
| Plan grew while implementing | Scope drift | Stop, re-plan, continue | | Plan grew while implementing | Scope drift | Stop, re-plan, continue |
| Writing a rule without reading its owning doc | Guessing where you could look | `docs/README.md` topic table, then amend that doc | | Writing a rule without reading its owning doc | Guessing where you could look | `harness/README.md` topic table, then amend that doc |
| Quietly doing what was asked against a recorded decision | The conflict was real and you hid it | Quote the line, give both paths, wait | | Quietly doing what was asked against a recorded decision | The conflict was real and you hid it | Quote the line, give both paths, wait |
| Changed the rule in code, left the docs describing the old one | Propagation stopped at the first surface | Walk all four surfaces | | Changed the rule in code, left the docs describing the old one | Propagation stopped at the first surface | Walk all four surfaces |
| Made a field optional, left the ADR mandating it | Contradicted, not stale | Supersede the ADR | | Made a field optional, left the ADR mandating it | Contradicted, not stale | Supersede the ADR |
@@ -225,7 +225,7 @@ Swept: the old form you grepped for after a rename, or "n/a"
| Ending a turn with the work only in the working tree | The one copy is the one that gets lost | Commit before reporting | | Ending a turn with the work only in the working tree | The one copy is the one that gets lost | Commit before reporting |
| Offering work a handoff calls pending | The file states intent; only the log states what landed | `git log -- <path>` before trusting it | | Offering work a handoff calls pending | The file states intent; only the log states what landed | `git log -- <path>` before trusting it |
| Ticking the handoff during Document | The commit can still move under it | Reconcile it after the commit exists | | Ticking the handoff during Document | The commit can still move under it | Reconcile it after the commit exists |
| Opening a file to find out what is in it | The generated surface already answers it | `docs/surface.md`, then read the range | | Opening a file to find out what is in it | The generated surface already answers it | `harness/surface.md`, then read the range |
| Grepping for callers before changing a signature | The compiler enumerates them exactly | Change it, then `go build ./...` | | Grepping for callers before changing a signature | The compiler enumerates them exactly | Change it, then `go build ./...` |
| A shorter report that dropped a caveat, case or number | Truncation wearing compression's clothes | Restore it; cut words, never findings | | A shorter report that dropped a caveat, case or number | Truncation wearing compression's clothes | Restore it; cut words, never findings |
| One commit per file, or per doc touched | Shredding a single revertible unit | Bundle what would be undone together | | One commit per file, or per doc touched | Shredding a single revertible unit | Bundle what would be undone together |
+16 -16
View File
@@ -12,27 +12,27 @@ as aesthetic, comprehensibility by one person.
## 1. Read order (do not skip, do not exceed) ## 1. Read order (do not skip, do not exceed)
1. This file. 1. This file.
2. `docs/state.md` — what exists **right now**, plus the earn-it counters. 2. `harness/state.md` — what exists **right now**, plus the earn-it counters.
3. `docs/README.md` — always. The map: how you find which doc owns your topic. Not "exceeding". 3. `harness/README.md` — always. The map: how you find which doc owns your topic. Not "exceeding".
4. `.scratch/continue.md`**after** the harness above, never instead of it, and only when picking work up 4. `.scratch/continue.md`**after** the harness above, never instead of it, and only when picking work up
rather than answering a named request. A *temporary* handoff: uncommitted, ungated, discardable, holding rather than answering a named request. A *temporary* handoff: uncommitted, ungated, discardable, holding
the continuation point and nothing else. Anything it calls pending is a claim about the past — check it the continuation point and nothing else. Anything it calls pending is a claim about the past — check it
against `git log` first, and where they differ the log wins the way the code wins over `state.md`. against `git log` first, and where they differ the log wins the way the code wins over `state.md`.
5. Every doc owning a topic your change asserts a rule about (ownership table in `docs/README.md`). 5. Every doc owning a topic your change asserts a rule about (ownership table in `harness/README.md`).
6. Only the source files you will edit, plus their direct callers. 6. Only the source files you will edit, plus their direct callers.
No reading the repo "for context", no speculative greps. Where `docs/state.md` and the code No reading the repo "for context", no speculative greps. Where `harness/state.md` and the code
disagree, the code wins — say so, fix the doc in Document. disagree, the code wins — say so, fix the doc in Document.
**The ceiling has a floor.** "Read less" governs breadth, never the doc that owns what you are **The ceiling has a floor.** "Read less" governs breadth, never the doc that owns what you are
writing. Before stating a rule, contract, threshold, or gate, read its owning doc; if it already writing. Before stating a rule, contract, threshold, or gate, read its owning doc; if it already
says it, amend there instead of restating elsewhere. says it, amend there instead of restating elsewhere.
**Read the compressed form first.** `docs/surface.md` (generated, gated) says where every declaration **Read the compressed form first.** `harness/surface.md` (generated, gated) says where every declaration
lives; `go doc ./internal/<pkg>` gives a package's surface; a test states a contract in a fifth of the lives; `go doc ./internal/<pkg>` gives a package's surface; a test states a contract in a fifth of the
lines that implement it. Locate with `grep -n`, then read that range — a whole file is for the doc that lines that implement it. Locate with `grep -n`, then read that range — a whole file is for the doc that
owns a rule you are asserting, or code you are about to rewrite. Every discipline, and the floor none of owns a rule you are asserting, or code you are about to rewrite. Every discipline, and the floor none of
them may cross: `docs/context-economy.md`. A saving that buys a guess is not a saving. them may cross: `harness/context-economy.md`. A saving that buys a guess is not a saving.
`ideas/` and `reference/` are out of context by default, indexes included. Open one only when the `ideas/` and `reference/` are out of context by default, indexes included. Open one only when the
human names it. Never sweep, never list, never cite unasked. Storage, not background. human names it. Never sweep, never list, never cite unasked. Storage, not background.
@@ -40,7 +40,7 @@ human names it. Never sweep, never list, never cite unasked. Storage, not backgr
## 2. The primitives — everything reduces to one ## 2. The primitives — everything reduces to one
**Bundle · Stage · Query · View · Interaction · Effect** (+ bundle-as-program). **Bundle · Stage · Query · View · Interaction · Effect** (+ bundle-as-program).
Definitions and STATUS: `docs/architecture.md`. Definitions and STATUS: `harness/architecture.md`.
Name the primitive before writing code. If it reduces to none it is a **trunk** (wants a permanent Name the primitive before writing code. If it reduces to none it is a **trunk** (wants a permanent
service or a core-model change): stop, say so in a paragraph, propose the leaf, wait. service or a core-model change): stop, say so in a paragraph, propose the leaf, wait.
@@ -49,21 +49,21 @@ service or a core-model change): stop, say so in a paragraph, propose the leaf,
browser (CSS, and only then JS). Build it at the outermost layer that can do the job: a line-breaking or browser (CSS, and only then JS). Build it at the outermost layer that can do the job: a line-breaking or
spacing problem CSS solves is not an engine feature, and code that edits an author's text to fix how it spacing problem CSS solves is not an engine feature, and code that edits an author's text to fix how it
*looks* is at the wrong layer by definition (ADR-0045 — a whole feature was deleted for this). Layers and *looks* is at the wrong layer by definition (ADR-0045 — a whole feature was deleted for this). Layers and
the test: `docs/architecture.md`. the test: `harness/architecture.md`.
## 3. Hard rules ## 3. Hard rules
1. **No abstraction before its second concrete use** — pipeline, resolver, interface, generic, config 1. **No abstraction before its second concrete use** — pipeline, resolver, interface, generic, config
knob, registry. The counters table in `docs/state.md` holds every threshold and is the only place knob, registry. The counters table in `harness/state.md` holds every threshold and is the only place
they are written down: read them, increment them, never anticipate them. they are written down: read them, increment them, never anticipate them.
2. **No new dependency** without an ADR and human approval. Allowlist: 2. **No new dependency** without an ADR and human approval. Allowlist:
`scripts/allowed-deps.txt`. Stdlib first, always. `scripts/allowed-deps.txt`. Stdlib first, always.
3. **Surgical diffs.** Only the lines the feature needs. No renames, no reformatting beyond 3. **Surgical diffs.** Only the lines the feature needs. No renames, no reformatting beyond
`gofmt`, no "while I was in there". Spotted something bad? Latent list in `docs/state.md`. `gofmt`, no "while I was in there". Spotted something bad? Latent list in `harness/state.md`.
4. **The untrusted boundary is absolute.** Anything not from the site root (comments, webmentions, 4. **The untrusted boundary is absolute.** Anything not from the site root (comments, webmentions,
form input) never reaches shortcode or template evaluation. Crossing it needs a plan callout. form input) never reaches shortcode or template evaluation. Crossing it needs a plan callout.
5. **Permalinks are permanent.** A published URL never changes meaning; renames add aliases and 5. **Permalinks are permanent.** A published URL never changes meaning; renames add aliases and
permanent redirects. The path shape is decided (ADR-0008) and written in `docs/content-model.md` permanent redirects. The path shape is decided (ADR-0008) and written in `harness/content-model.md`
read it before emitting a URL, and never invent a second shape. read it before emitting a URL, and never invent a second shape.
6. **No speculative anything**: no unused parameters, no `interface{}` for flexibility, no "we 6. **No speculative anything**: no unused parameters, no `interface{}` for flexibility, no "we
might want to" comments, no one-field options structs, no plugin registry before its counter is due, no might want to" comments, no one-field options structs, no plugin registry before its counter is due, no
@@ -96,14 +96,14 @@ Procedure: `.claude/skills/feature-loop/SKILL.md`. The gates people skip:
those. Batched, up front, each with a **bold** default so silence answers. There is no cap: a request those. Batched, up front, each with a **bold** default so silence answers. There is no cap: a request
carrying six real forks gets six questions, and splitting them across turns to look brisk wastes more of carrying six real forks gets six questions, and splitting them across turns to look brisk wastes more of
the human's time than asking once. Never about naming, formatting, the human's time than asking once. Never about naming, formatting,
or anything `docs/conventions.md` decides. None to ask? State assumptions in one line and move on. or anything `harness/conventions.md` decides. None to ask? State assumptions in one line and move on.
**Verify.** `./scripts/verify.sh --quiet` green, plus one piece of feature-specific evidence you actually **Verify.** `./scripts/verify.sh --quiet` green, plus one piece of feature-specific evidence you actually
ran (golden file, `curl`, test name, benchmark). Never report success from reading your own diff. ran (golden file, `curl`, test name, benchmark). Never report success from reading your own diff.
"Should work" is not a result. "Should work" is not a result.
**Commit.** Work lands in git without being asked, every time, before you report. What one commit **Commit.** Work lands in git without being asked, every time, before you report. What one commit
contains is `docs/conventions.md` "Git": the changes that would be reverted together, bundled contains is `harness/conventions.md` "Git": the changes that would be reverted together, bundled
together. Never `git push`, and never `--no-verify` without saying why in the body. Asymmetry is the together. Never `git push`, and never `--no-verify` without saying why in the body. Asymmetry is the
whole reason — a commit is one `git revert` away from undone, while work that only ever existed in whole reason — a commit is one `git revert` away from undone, while work that only ever existed in
the working tree is not recoverable at all. the working tree is not recoverable at all.
@@ -119,8 +119,8 @@ exists; before it, an unannounced commit is a surprise in someone else's reposit
- [ ] Plan's success criteria demonstrated with real output. - [ ] Plan's success criteria demonstrated with real output.
- [ ] `./scripts/verify.sh` green. - [ ] `./scripts/verify.sh` green.
- [ ] Diff contains nothing outside the planned files. - [ ] Diff contains nothing outside the planned files.
- [ ] `docs/state.md` updated (inventory, counters, latent items, verified-at line). - [ ] `harness/state.md` updated (inventory, counters, latent items, verified-at line).
- [ ] ADR in `docs/decisions.md` if a load-bearing choice was made. - [ ] ADR in `harness/decisions.md` if a load-bearing choice was made.
- [ ] Committed — one revertible unit per commit, announced if this session did not ask for it. - [ ] Committed — one revertible unit per commit, announced if this session did not ask for it.
- [ ] Report states LOC delta, what is now earned, what you deliberately did not do. - [ ] Report states LOC delta, what is now earned, what you deliberately did not do.
@@ -138,5 +138,5 @@ Stopping early costs one message. Guessing costs a refactor.
Go stdlib idiom, `net/http` + `html/template`, no framework. `%w` wrapping at package boundaries Go stdlib idiom, `net/http` + `html/template`, no framework. `%w` wrapping at package boundaries
only. `log/slog`. Table-driven tests, golden files in `testdata/`. Explicit wiring in one file, no only. `log/slog`. Table-driven tests, golden files in `testdata/`. Explicit wiring in one file, no
`init()`. Size thresholds live in `scripts/budgets.env` only. Full rules: `docs/conventions.md` `init()`. Size thresholds live in `scripts/budgets.env` only. Full rules: `harness/conventions.md`
read them, do not ask. read them, do not ask.
+25 -22
View File
@@ -4,9 +4,12 @@ Scaffolding that makes an agent build `khosra` the way you want: minimally, surg
before code, docs that stay true. before code, docs that stay true.
`README.md` is a two-line signpost — human here, agent to `CLAUDE.md`. `CLAUDE.md` is the `README.md` is a two-line signpost — human here, agent to `CLAUDE.md`. `CLAUDE.md` is the
constitution (always loaded). `docs/` is what the agent needs to build the constitution (always loaded). `harness/` is what the agent needs to build the
engine — anything in it may be pulled into context on demand. `ideas/` and `reference/` sit outside engine — anything in it may be pulled into context on demand. It is named for its audience, not its
`docs/` deliberately: storage, opened only when you name a file, swept by nothing. format: `docs/` is reserved for documentation written for whoever *uses* khosra, and is absent because
that does not exist yet (ADR-0082). An empty `docs/` says so honestly; `docs/` full of parser
specifications said the opposite, and cost its own author a rule he did not know was written down. `ideas/` and `reference/` sit outside
`harness/` deliberately: storage, opened only when you name a file, swept by nothing.
`.claude/` holds the feature-loop skill (`skills/feature-loop/`), the commands, and `launch.json`. `.claude/` holds the feature-loop skill (`skills/feature-loop/`), the commands, and `launch.json`.
`scripts/` holds the gate. `.scratch/continue.md` is a **temporary** handoff — uncommitted, ungated, `scripts/` holds the gate. `.scratch/continue.md` is a **temporary** handoff — uncommitted, ungated,
discardable — holding the continuation point and the findings worth carrying that no doc owns. It is read discardable — holding the continuation point and the findings worth carrying that no doc owns. It is read
@@ -37,7 +40,7 @@ Occasional maintenance, by you:
- "Park this" → the agent writes `ideas/<slug>.md`, resumable cold, and indexes it. Name the file - "Park this" → the agent writes `ideas/<slug>.md`, resumable cold, and indexes it. Name the file
later to pick the thread up; it reads these only when named. `ideas/exploration.md` is the same idea for later to pick the thread up; it reads these only when named. `ideas/exploration.md` is the same idea for
engine features nobody has asked for — a catalog, deliberately outside `docs/` so it adds no weight to the engine features nobody has asked for — a catalog, deliberately outside `harness/` so it adds no weight to the
working set. working set.
- `/audit` every ~5 features — finds abstractions that never earned their keep. - `/audit` every ~5 features — finds abstractions that never earned their keep.
- `/invariants` at arc boundaries, before a freeze, before the first deploy — checks the nine - `/invariants` at arc boundaries, before a freeze, before the first deploy — checks the nine
@@ -54,7 +57,7 @@ split the difference into a compromise nobody picked.
**Three surfaces, one of them yours to edit here.** Engine source lives in this repo; content lives in **Three surfaces, one of them yours to edit here.** Engine source lives in this repo; content lives in
the site root (ADR-0011); the theme is a third surface with its own owner (ADR-0023). A request that spans the site root (ADR-0011); the theme is a third surface with its own owner (ADR-0023). A request that spans
engine and theme produces a *contract extension* in `docs/theme-contract.md` plus a note of what the theme engine and theme produces a *contract extension* in `harness/theme-contract.md` plus a note of what the theme
must do — not the theme. `verify.sh` fails if the embedded reference theme changes without the contract doc must do — not the theme. `verify.sh` fails if the embedded reference theme changes without the contract doc
changing, because in practice those two drift together — and it fails on a `<script>` tag in that theme, changing, because in practice those two drift together — and it fails on a `<script>` tag in that theme,
because a reference theme that grows taste stops being a reference (ADR-0026). because a reference theme that grows taste stops being a reference (ADR-0026).
@@ -68,7 +71,7 @@ without a case there is a feature the demo does not show, and the build says so
a site root and there is nothing else to run. a site root and there is nothing else to run.
**The agent names the layer as well as the primitive.** Content on disk, engine, theme, browser — and it builds **The agent names the layer as well as the primitive.** Content on disk, engine, theme, browser — and it builds
at the outermost layer that can do the job (`CLAUDE.md` §2, `docs/architecture.md`). This exists because a whole at the outermost layer that can do the job (`CLAUDE.md` §2, `harness/architecture.md`). This exists because a whole
feature was built at the wrong one: widow prevention as a Markdown transform that inserted a non-breaking space feature was built at the wrong one: widow prevention as a Markdown transform that inserted a non-breaking space
into an author's prose. It worked and it had tests; `text-wrap: pretty` does it better with no bytes in the into an author's prose. It worked and it had tests; `text-wrap: pretty` does it better with no bytes in the
content, so the feature was deleted (ADR-0045). If a feature only rearranges how something looks, expect the content, so the feature was deleted (ADR-0045). If a feature only rearranges how something looks, expect the
@@ -81,9 +84,9 @@ history belongs, not here. Read a core raise as evidence something belongs in `i
it as evidence the number was small. it as evidence the number was small.
**Context is a budget, and parts of it are mechanical.** The limit on this project is how much **Context is a budget, and parts of it are mechanical.** The limit on this project is how much
work fits in a session, so `docs/context-economy.md` holds the reading, searching and reporting work fits in a session, so `harness/context-economy.md` holds the reading, searching and reporting
disciplines — read the compressed form first, batch calls, script anything repeatable, never pay twice disciplines — read the compressed form first, batch calls, script anything repeatable, never pay twice
for the same bytes (ADR-0053). These are enforced rather than trusted: `docs/surface.md` is for the same bytes (ADR-0053). These are enforced rather than trusted: `harness/surface.md` is
regenerated and staged by the pre-commit hook and independently compared by `verify.sh`, so the map of regenerated and staged by the pre-commit hook and independently compared by `verify.sh`, so the map of
the code cannot mislead — an order of magnitude smaller than the source it stands for — and no one has to the code cannot mislead — an order of magnitude smaller than the source it stands for — and no one has to
remember to run it; remember to run it;
@@ -125,23 +128,23 @@ wanting to emit HTML still renders a theme template instead (ADR-0036).
In order, cheapest first: In order, cheapest first:
1. `./scripts/verify.sh` — one command, tells you whether the thing is still coherent and whether 1. `./scripts/verify.sh` — one command, tells you whether the thing is still coherent and whether
`docs/state.md` has fallen behind the code. `harness/state.md` has fallen behind the code.
2. `docs/state.md` — what exists, the earn-it counters, the latent list. This is the only doc that 2. `harness/state.md` — what exists, the earn-it counters, the latent list. This is the only doc that
describes the present, and `verify.sh` compares its last commit against the last `.go` one rather than describes the present, and `verify.sh` compares its last commit against the last `.go` one rather than
trusting a sha written by hand (ADR-0057). Its companion `docs/surface.md` is generated: trusting a sha written by hand (ADR-0057). Its companion `harness/surface.md` is generated:
every declaration and its line, so you can find your way around without opening anything. every declaration and its line, so you can find your way around without opening anything.
3. `git log --oneline` — one revertible unit per commit, each body saying *why* (`conventions.md`). This is the 3. `git log --oneline` — one revertible unit per commit, each body saying *why* (`conventions.md`). This is the
real map of how the code got here. real map of how the code got here.
4. `/refresh-docs` — reconciles every doc against the actual code and reports drift, which is exactly the 4. `/refresh-docs` — reconciles every doc against the actual code and reports drift, which is exactly the
question you have after a year. question you have after a year.
5. `docs/decisions.md` — the ADR log, when you hit something and think "why on earth is it like this". 5. `harness/decisions.md` — the ADR log, when you hit something and think "why on earth is it like this".
Each entry names the observation that would overturn it, so you can tell a stale decision from a Each entry names the observation that would overturn it, so you can tell a stale decision from a
deliberate one. deliberate one.
6. `docs/toolchain.md` — if something is broken rather than merely unfamiliar. It records what this was 6. `harness/toolchain.md` — if something is broken rather than merely unfamiliar. It records what this was
built against and which agent-tooling contracts it assumes, so a tooling change is diagnosable instead built against and which agent-tooling contracts it assumes, so a tooling change is diagnosable instead
of looking like a harness bug. of looking like a harness bug.
Then `docs/README.md` for whichever topic you are actually here for. Then `harness/README.md` for whichever topic you are actually here for.
## Why the pieces exist ## Why the pieces exist
@@ -150,7 +153,7 @@ empty (ADR-0070). Four counters had to be re-scoped the first time anything test
sentence about what had been wrongly included — so the sentence is now required up front. A counter that sentence about what had been wrongly included — so the sentence is now required up front. A counter that
cannot name an exclusion is measuring a symptom. cannot name an exclusion is measuring a symptom.
**Counters in `docs/state.md`** turn "no abstraction before its second use" into arithmetic. Every **Counters in `harness/state.md`** turn "no abstraction before its second use" into arithmetic. Every
threshold lives in that one table and nowhere else. The agent cannot argue a pipeline into existence threshold lives in that one table and nowhere else. The agent cannot argue a pipeline into existence
one transform early — it writes the next one inline and lets the count force the extraction. Most one transform early — it writes the next one inline and lets the count force the extraction. Most
load-bearing mechanism here, and the one with least machine enforcement, which is why `verify.sh` load-bearing mechanism here, and the one with least machine enforcement, which is why `verify.sh`
@@ -183,7 +186,7 @@ is the engine's own job rather than the gate's. The gate skips `ideas/`, `refere
diffed, the theme contract and this file are coupled to what they describe. `.scratch/continue.md` can have diffed, the theme contract and this file are coupled to what they describe. `.scratch/continue.md` can have
none of that: it is gitignored, so there is no commit to compare it against. Two things stand in for the none of that: it is gitignored, so there is no commit to compare it against. Two things stand in for the
gate it cannot have — it holds the plan and the carried findings but never what landed, and the read order gate it cannot have — it holds the plan and the carried findings but never what landed, and the read order
puts `git log` first with the handoff read against it (`docs/README.md`). The one check that *is* mechanical puts `git log` first with the handoff read against it (`harness/README.md`). The one check that *is* mechanical
now runs: a `.scratch/` path named in a doc must exist, guarded on the directory being present so a fresh now runs: a `.scratch/` path named in a doc must exist, guarded on the directory being present so a fresh
clone with no handoff still passes. A handoff that recorded what landed was believed for a whole session — clone with no handoff still passes. A handoff that recorded what landed was believed for a whole session —
it said a doc trim awaited a yes while the commit being amended around it had already done the trim. it said a doc trim awaited a yes while the commit being amended around it had already done the trim.
@@ -217,7 +220,7 @@ Softer signals stay advisory: `fmt.Errorf` without `%w`, nesting past 4, exporte
and `any` in an **exported** signature — only exported, because ADR-0002 mandates an open page object, and `any` in an **exported** signature — only exported, because ADR-0002 mandates an open page object,
so unexported code reading frontmatter takes `any` legitimately and forever. so unexported code reading frontmatter takes `any` legitimately and forever.
**STATUS markers in `docs/architecture.md`** give the target shape *and* what is legal today, so **STATUS markers in `harness/architecture.md`** give the target shape *and* what is legal today, so
the agent can read the endgame without building toward it. They are the only markers of that kind left: the agent can read the endgame without building toward it. They are the only markers of that kind left:
`content-model.md`'s `[spec]` sections were deleted rather than given a stricter "do not build from this" `content-model.md`'s `[spec]` sections were deleted rather than given a stricter "do not build from this"
rule, because a marker inside a doc the agent already has open still gets read. Those shapes moved to rule, because a marker inside a doc the agent already has open still gets read. Those shapes moved to
@@ -243,30 +246,30 @@ that describe it, in the same change.**
`internal/ext/…`, and `verify.sh` states that as one rule rather than a list of the pairs that happen to `internal/ext/…`, and `verify.sh` states that as one rule rather than a list of the pairs that happen to
exist today (ADR-0069) — so a core package added next month cannot quietly import a feature. A feature exist today (ADR-0069) — so a core package added next month cannot quietly import a feature. A feature
importing its sibling fails the same rule. importing its sibling fails the same rule.
- `verify.sh` fails when a `.go` file changes without `docs/state.md` changing, warns when `state.md`'s last - `verify.sh` fails when a `.go` file changes without `harness/state.md` changing, warns when `state.md`'s last
commit is older than the last `.go` one — the two together mean the doc ships inside the change, never in a commit is older than the last `.go` one — the two together mean the doc ships inside the change, never in a
commit trailing it (ADR-0057) — and fails when `cmd/` or commit trailing it (ADR-0057) — and fails when `cmd/` or
`internal/` code changes without a `_test.go` changing — behaviour ships with a test. A comment-only or `internal/` code changes without a `_test.go` changing — behaviour ships with a test. A comment-only or
`gofmt`-only diff is exempt: it ships no behaviour, and failing it would only teach you `--no-verify`. `gofmt`-only diff is exempt: it ships no behaviour, and failing it would only teach you `--no-verify`.
- The gate is not optional. `scripts/hooks/pre-commit` runs it on every commit; enable once per clone - The gate is not optional. `scripts/hooks/pre-commit` runs it on every commit; enable once per clone
with `git config core.hooksPath scripts/hooks`. `--no-verify` bypasses it, and the commit body should with `git config core.hooksPath scripts/hooks`. `--no-verify` bypasses it, and the commit body should
say why. The hook also regenerates `docs/surface.md` and stages it, so a generated file is never say why. The hook also regenerates `harness/surface.md` and stages it, so a generated file is never
something you have to remember — and it refuses a commit with unstaged `.go` changes, because what it something you have to remember — and it refuses a commit with unstaged `.go` changes, because what it
generated describes the working tree rather than what you are committing. generated describes the working tree rather than what you are committing.
- `docs/decisions.md` registers every ADR number ever used, entries and withdrawals alike, so a citation - `harness/decisions.md` registers every ADR number ever used, entries and withdrawals alike, so a citation
can resolve to a decision or to a deferral but never to nothing. can resolve to a decision or to a deferral but never to nothing.
- `./scripts/verify.sh --list` names every gate that exists. A doc claiming enforcement is checkable - `./scripts/verify.sh --list` names every gate that exists. A doc claiming enforcement is checkable
against it in one command, and `/refresh-docs` checks it in both directions — a claimed gate that is against it in one command, and `/refresh-docs` checks it in both directions — a claimed gate that is
missing, and a real gate nothing explains. Asserting a mechanism before it exists is the drift that missing, and a real gate nothing explains. Asserting a mechanism before it exists is the drift that
reads as enforcement and is decoration; `CLAUDE.md` rule 8 forbids it. reads as enforcement and is decoration; `CLAUDE.md` rule 8 forbids it.
- `docs/README.md` carries three tables: topic → doc to **read before asserting a rule**, change → - `harness/README.md` carries three tables: topic → doc to **read before asserting a rule**, change →
doc to **update after making one**, and an authority table naming the one home of every value. doc to **update after making one**, and an authority table naming the one home of every value.
A new mechanism adds a row; nothing outside a value's home may restate it. A number written twice A new mechanism adds a row; nothing outside a value's home may restate it. A number written twice
eventually disagrees with itself. eventually disagrees with itself.
## Open ## Open
- No ADR gate blocks Arc 1. One question remains in `docs/state.md`: the language suffix on the first - No ADR gate blocks Arc 1. One question remains in `harness/state.md`: the language suffix on the first
content file. content file.
- `go.mod` does not exist yet — `go mod init` belongs to the first feature, and the module path is - `go.mod` does not exist yet — `go mod init` belongs to the first feature, and the module path is
still unchosen. still unchosen.
+1 -1
View File
@@ -38,7 +38,7 @@ verify: ## everything the harness enforces; must be green before a commit
quiet: ## the same gates, printing only what needs acting on quiet: ## the same gates, printing only what needs acting on
./scripts/verify.sh --quiet ./scripts/verify.sh --quiet
surface: ## regenerate docs/surface.md, the compressed map of the code surface: ## regenerate harness/surface.md, the compressed map of the code
./scripts/surface.sh --write ./scripts/surface.sh --write
fmt: ## format all Go source fmt: ## format all Go source
+7 -3
View File
@@ -1,6 +1,10 @@
# Doc map # Harness doc map
Read the one you need. Do not read them all. The docs an agent reads to build the engine. Read the one you need. Do not read them all.
This directory was `docs/` until ADR-0082 renamed it: `docs/` is reserved for documentation written for
whoever *uses* khosra, which does not exist yet. Everything here is engine- and agent-facing — a
specification, a rule, or a record — and none of it is written for an author.
| Doc | Contains | Mutability | | Doc | Contains | Mutability |
|---|---|---| |---|---|---|
@@ -16,7 +20,7 @@ Read the one you need. Do not read them all.
| `context-economy.md` | How the agent spends context: what to read, in what form, and the floor no saving may cross | When a technique or a gate for one changes | | `context-economy.md` | How the agent spends context: what to read, in what form, and the floor no saving may cross | When a technique or a gate for one changes |
| `surface.md` | **Generated.** Every top-level declaration with its line — read before opening source | Never by hand; `make surface` | | `surface.md` | **Generated.** Every top-level declaration with its line — read before opening source | Never by hand; `make surface` |
Sibling folders sit **outside** `docs/` because they are storage, not working material: Sibling folders sit **outside** `harness/` because they are storage, not working material:
`../ideas/` (parked ideas, any topic, and `exploration.md` — the catalog of engine features nobody has asked `../ideas/` (parked ideas, any topic, and `exploration.md` — the catalog of engine features nobody has asked
for yet) and `../reference/` (durable facts from conversation). Write there when asked to park something; for yet) and `../reference/` (durable facts from conversation). Write there when asked to park something;
open a file only when the human names it. Nothing sweeps them. open a file only when the human names it. Nothing sweeps them.
@@ -24,7 +24,7 @@ In order of cost, stop at the first that answers the question:
| Question | Cheapest answer | | Question | Cheapest answer |
|---|---| |---|---|
| where does X live, what is in this package | `docs/surface.md` (generated; `make surface`) | | where does X live, what is in this package | `harness/surface.md` (generated; `make surface`) |
| what does this package offer callers | `go doc ./internal/<pkg>` | | what does this package offer callers | `go doc ./internal/<pkg>` |
| what does this behave like | its `_test.go` — a table states in 20 lines what 200 implement | | what does this behave like | its `_test.go` — a table states in 20 lines what 200 implement |
| why is it like this | `decisions.md`, `state.md` — never the code | | why is it like this | `decisions.md`, `state.md` — never the code |
@@ -77,7 +77,7 @@ assumption, or a caveat — a report that omits the caveat is not shorter, it is
## What is enforced, and what is not ## What is enforced, and what is not
Three of these are mechanical. `scripts/hooks/pre-commit` regenerates `docs/surface.md` and stages it, Three of these are mechanical. `scripts/hooks/pre-commit` regenerates `harness/surface.md` and stages it,
so no commit can carry a stale one, and `verify.sh` compares independently for the clone that never set so no commit can carry a stale one, and `verify.sh` compares independently for the clone that never set
`core.hooksPath`. `CLAUDE_LOC_MAX` bounds the one file billed on every turn. `--quiet` exists to be `core.hooksPath`. `CLAUDE_LOC_MAX` bounds the one file billed on every turn. `--quiet` exists to be
used, and the hook uses it. used, and the hook uses it.
+34 -5
View File
@@ -244,7 +244,7 @@ Date: 2026-07-28 · Status: accepted
Decision: engine source, content, and theme are three separate surfaces with three separate owners. This Decision: engine source, content, and theme are three separate surfaces with three separate owners. This
repository holds the engine and is bound to the **theme contract** — the data available to templates, the repository holds the engine and is bound to the **theme contract** — the data available to templates, the
template and block names it looks for, the helpers it provides, the URLs it emits — recorded in template and block names it looks for, the helpers it provides, the URLs it emits — recorded in
`docs/theme-contract.md`. It is not bound to any theme's markup, layout, or styling. Where a theme comes `harness/theme-contract.md`. It is not bound to any theme's markup, layout, or styling. Where a theme comes
from (a directory in the site root, its own repo checked out into place) is deliberately outside this from (a directory in the site root, its own repo checked out into place) is deliberately outside this
repo's concern: the engine knows a path and a contract. repo's concern: the engine knows a path and a contract.
Why: a request that reads as one feature is often split — "supporting files listed in a sidebar" is a Why: a request that reads as one feature is often split — "supporting files listed in a sidebar" is a
@@ -287,7 +287,7 @@ contract executable: a bare site root renders, and a golden-file test through it
regressions. It is deliberately not a design: legibility only, no branding, no visual opinions, and it regressions. It is deliberately not a design: legibility only, no branding, no visual opinions, and it
demonstrates every contract feature and nothing more. demonstrates every contract feature and nothing more.
Why: a contract nobody implements is a contract nobody has tested. Without a reference theme, the first Why: a contract nobody implements is a contract nobody has tested. Without a reference theme, the first
real theme discovers the contract's gaps, and `docs/theme-contract.md` stays aspirational. It also means real theme discovers the contract's gaps, and `harness/theme-contract.md` stays aspirational. It also means
someone can point the binary at a folder of Markdown and see a site, which is the whole promise. someone can point the binary at a folder of Markdown and see a site, which is the whole promise.
Consequence: cheap — templates and CSS are not Go, so they cost nothing against `CORE_LOC_MAX`, which is Consequence: cheap — templates and CSS are not Go, so they cost nothing against `CORE_LOC_MAX`, which is
the same incentive that pushes presentation out of the core. Expensive — the reference theme is a the same incentive that pushes presentation out of the core. Expensive — the reference theme is a
@@ -794,10 +794,10 @@ tweak to this one.
## ADR-0053 — Context economy is a doc with a floor, plus three gates ## ADR-0053 — Context economy is a doc with a floor, plus three gates
Date: 2026-08-01 · Status: accepted (adopts `ideas/token-conservation.md`) Date: 2026-08-01 · Status: accepted (adopts `ideas/token-conservation.md`)
Decision: `docs/context-economy.md` owns how the agent spends context — read the compressed form first, Decision: `harness/context-economy.md` owns how the agent spends context — read the compressed form first,
fewer turns before fewer bytes, script anything repeatable, shrink output at the source, never pay twice fewer turns before fewer bytes, script anything repeatable, shrink output at the source, never pay twice
for the same bytes. Three parts are mechanical rather than remembered: `scripts/surface.sh` generates for the same bytes. Three parts are mechanical rather than remembered: `scripts/surface.sh` generates
`docs/surface.md`, the pre-commit hook regenerates and stages it while `verify.sh` compares it `harness/surface.md`, the pre-commit hook regenerates and stages it while `verify.sh` compares it
independently, `CLAUDE_LOC_MAX=150` bounds the file re-sent every turn, and `verify.sh --quiet` prints independently, `CLAUDE_LOC_MAX=150` bounds the file re-sent every turn, and `verify.sh --quiet` prints
only what needs acting on (the hook uses it). Generated artifacts are produced by the hook, never by only what needs acting on (the hook uses it). Generated artifacts are produced by the hook, never by
memory; the hook refuses a commit with unstaged `.go` changes, since what it generated describes the memory; the hook refuses a commit with unstaged `.go` changes, since what it generated describes the
@@ -887,7 +887,7 @@ one pointer, and `web.Handler` takes an accessor for it instead of two arguments
## ADR-0057 — `state.md`'s currency is compared, not declared ## ADR-0057 — `state.md`'s currency is compared, not declared
Date: 2026-08-01 · Status: accepted (retires the `verified against` sha the harness carried from day one) Date: 2026-08-01 · Status: accepted (retires the `verified against` sha the harness carried from day one)
Decision: `verify.sh` decides whether `docs/state.md` is current by comparing the last commit that touched Decision: `verify.sh` decides whether `harness/state.md` is current by comparing the last commit that touched
it against the last commit that touched a `.go` file — current when the doc's commit is the same or newer. it against the last commit that touched a `.go` file — current when the doc's commit is the same or newer.
The hand-written `**Verified against:** <sha>` line is deleted, and no commit exists solely to write one. The hand-written `**Verified against:** <sha>` line is deleted, and no commit exists solely to write one.
Why: the sha could only ever be wrong. A commit cannot name itself, so the line had to be written *after* Why: the sha could only ever be wrong. A commit cannot name itself, so the line had to be written *after*
@@ -1412,3 +1412,32 @@ A broken template serves its own source rather than 404ing, because a promised a
worse than one answering unrendered (ADR-0029). worse than one answering unrendered (ADR-0029).
Revisit if: a feature needs a path *prefix* rather than exact paths, or two features claim the same path — Revisit if: a feature needs a path *prefix* rather than exact paths, or two features claim the same path —
neither is expressible today, and both would want the resolver rather than the mux. neither is expressible today, and both would want the resolver rather than the mux.
## ADR-0082 — `docs/` becomes `harness/`, and `docs/` is reserved for the reader
Date: 2026-08-02 · Status: accepted
Decision: the twelve engine- and agent-facing documents move from `docs/` to `harness/`. `docs/` is
reserved for documentation written for whoever *uses* khosra and is deliberately left absent until it
exists. `HARNESS.md` stays at the repository root: root holds the three entry points — `README.md` for a
human, `CLAUDE.md` for an agent, `HARNESS.md` for whoever maintains the machine — and `harness/README.md`
is the map of the directory, so moving the guide inside would collide with it for no gain.
Why: `docs/content-model.md` opens with "Engine specification" and is where the rule that a leading
underscore makes a file unaddressable is written. The human, who owns this site, did not know that rule —
because nothing in this repository is addressed to an author. Twelve documents named `docs/` while being
exclusively about building the parser is a signpost pointing at the wrong room, and the name is the part
that misleads: an author looking for how to write a post opens `docs/` and finds a specification for the
person implementing it.
Naming the directory for its audience makes the gap visible instead of hiding it. An empty `docs/` is an
honest statement that end-user documentation does not exist; `docs/` full of engine specs was a claim that
it did.
Consequence: mechanical and wide — 100 path references across 24 files, every gate in `verify.sh` that
names a doc by path, `surface.sh`'s output target, the pre-commit hook, `CLAUDE.md`'s read order, and the
directory lists the dangling-path and ADR-number gates scan. No rule, threshold, gate or obligation
changed: this is a rename, and the gates proved it by staying green with the new paths and by catching this
very ADR's number before it existed.
An end-user documentation site is planned and deliberately not built here: it wants its own decision about
where it lives and whether its claims are gated, and the human deferred both. `examples/demo-site` was
also considered for a move under `docs/` and deferred with it, since it is a live site root that
`verify.sh`, the coverage test and `make demo` all point at — moving it would couple a rename to a design
nobody has made.
Revisit if: `docs/` is still empty when the first person other than its author tries to use this engine, at
which point the absence has stopped being honest and become neglect.
+6 -2
View File
@@ -7,7 +7,11 @@ If this file disagrees with the code, the code is right and this file is a bug.
## Inventory ## Inventory
Line counts are **not** here: `docs/surface.md` is generated from the code and carries per-file sizes The engine- and agent-facing docs live in `harness/`, not `docs/` (ADR-0082). `docs/` is reserved for
documentation aimed at whoever uses khosra and does not exist yet — an end-user documentation site is
planned, with its home and whether its claims are gated both undecided.
Line counts are **not** here: `harness/surface.md` is generated from the code and carries per-file sizes
plus every declaration's location (ADR-0053). This column drifted on six files before it was removed, plus every declaration's location (ADR-0053). This column drifted on six files before it was removed,
which is what a number written in two places always does. What each file is *for* is the fact this which is what a number written in two places always does. What each file is *for* is the fact this
table owns. table owns.
@@ -166,7 +170,7 @@ Every ADR in `decisions.md` is accepted; none is open or proposed.
Working plan lives in `.scratch/continue.md`, which is deliberately not committed — `git log` is the Working plan lives in `.scratch/continue.md`, which is deliberately not committed — `git log` is the
record of what actually landed, and the handoff is read *against* the log rather than instead of it record of what actually landed, and the handoff is read *against* the log rather than instead of it
(`docs/README.md`). If that file is absent, read the log and rebuild the plan from it. (`harness/README.md`). If that file is absent, read the log and rebuild the plan from it.
## Arc retro log ## Arc retro log
@@ -206,7 +206,7 @@ Only with a declared `base`: without one there is no feed to point at.
The engine supplies facts it alone can produce — which bundles exist, what a picture measures, where a The engine supplies facts it alone can produce — which bundles exist, what a picture measures, where a
permalink points, what a month is called in Bengali. Everything about how those facts *look* is yours, and the permalink points, what a month is called in Bengali. Everything about how those facts *look* is yours, and the
engine is audited against that line (ADR-0046, `docs/architecture.md`). Two consequences worth knowing: engine is audited against that line (ADR-0046, `harness/architecture.md`). Two consequences worth knowing:
- Where a listing offers more than one shape of the same data — `.Items` and `.Groups` — pick one. The engine - Where a listing offers more than one shape of the same data — `.Items` and `.Groups` — pick one. The engine
is not telling you which. is not telling you which.
+5 -5
View File
@@ -11,8 +11,8 @@ re-derivation** — the whole justification for writing it while the context is
| an engine feature you might build (webmentions, gemtext, galleries) | `ideas/exploration.md` catalog line, `/leaf` for a verdict | | an engine feature you might build (webmentions, gemtext, galleries) | `ideas/exploration.md` catalog line, `/leaf` for a verdict |
| that same feature, but with a discussion worth not repeating | an idea file here, linked from the catalog line | | that same feature, but with a discussion worth not repeating | an idea file here, linked from the catalog line |
| the harness, agent workflow, token cost, tooling, process | here — `ideas/exploration.md` is engine-only | | the harness, agent workflow, token cost, tooling, process | here — `ideas/exploration.md` is engine-only |
| a decision already made | `docs/decisions.md` | | a decision already made | `harness/decisions.md` |
| a known code flaw deliberately unfixed | Latent list in `docs/state.md` | | a known code flaw deliberately unfixed | Latent list in `harness/state.md` |
| a durable fact, number, or link | `reference/` | | a durable fact, number, or link | `reference/` |
## Format ## Format
@@ -43,7 +43,7 @@ you, not the agent; `verify.sh` keeps it honest without reading it into context.
- [specs-as-secondary-artifacts.md](specs-as-secondary-artifacts.md) — optional per-feature specs, derived by default, plus the named-test convention. **parked** - [specs-as-secondary-artifacts.md](specs-as-secondary-artifacts.md) — optional per-feature specs, derived by default, plus the named-test convention. **parked**
- [engine-design-review.md](engine-design-review.md) — open design decisions for a multi-type site; items graduate to ADRs one at a time. **parked** - [engine-design-review.md](engine-design-review.md) — open design decisions for a multi-type site; items graduate to ADRs one at a time. **parked**
- [exploration.md](exploration.md) — the catalog of engine features nobody has asked for, with leaf/trunk verdicts. **catalog** - [exploration.md](exploration.md) — the catalog of engine features nobody has asked for, with leaf/trunk verdicts. **catalog**
- [token-conservation.md](token-conservation.md) — cut agent token cost without losing output quality. **adopted → `docs/context-economy.md`**, except the subagent question - [token-conservation.md](token-conservation.md) — cut agent token cost without losing output quality. **adopted → `harness/context-economy.md`**, except the subagent question
It lives here rather than in `docs/` because a catalogue of things nobody has asked for is storage, not It lives here rather than in `harness/` because a catalogue of things nobody has asked for is storage, not
working material: keeping it out of `docs/` keeps it out of the set an agent may pull in on demand. working material: keeping it out of `harness/` keeps it out of the set an agent may pull in on demand.
+1 -1
View File
@@ -75,7 +75,7 @@ sneakernet and QR offline distribution.
## Shapes recorded before anything asked for them ## Shapes recorded before anything asked for them
Moved out of `docs/content-model.md`, which now describes only what the parser accepts. Each is Moved out of `harness/content-model.md`, which now describes only what the parser accepts. Each is
intent, not a plan: the shape is decided by whatever eventually builds it, and being written here is not intent, not a plan: the shape is decided by whatever eventually builds it, and being written here is not
permission. permission.
+1 -1
View File
@@ -22,7 +22,7 @@ implemented — flowing upward into the harness where it touches anything the ha
- Tests are mandatory for `cmd/` and `internal/` changes but nothing ties a test to the rule it verifies. - Tests are mandatory for `cmd/` and `internal/` changes but nothing ties a test to the rule it verifies.
`content-model.md` says a bundle key excludes language, and that a bundle supplying both `about.md` `content-model.md` says a bundle key excludes language, and that a bundle supplying both `about.md`
and `about.en.md` is rejected — nothing checks a test exists for either. and `about.en.md` is rejected — nothing checks a test exists for either.
- One fact, one place is enforced throughout (`docs/README.md` authority table). Specs must not become a - One fact, one place is enforced throughout (`harness/README.md` authority table). Specs must not become a
fourth copy of decisions. fourth copy of decisions.
## The non-overlapping slot ## The non-overlapping slot
+1 -1
View File
@@ -1,6 +1,6 @@
# Token conservation # Token conservation
Status: adopted → `docs/context-economy.md` + ADR-0053 (2026-08-01). All six mechanisms landed, plus Status: adopted → `harness/context-economy.md` + ADR-0053 (2026-08-01). All six mechanisms landed, plus
the compressed-form, machine-discovery and quiet-output disciplines the human added. The open question the compressed-form, machine-discovery and quiet-output disciplines the human added. The open question
below is the only part still undecided; it is also recorded in `state.md` "Open questions". below is the only part still undecided; it is also recorded in `state.md` "Open questions".
Raised: 2026-07-28 Raised: 2026-07-28
+1 -1
View File
@@ -2,7 +2,7 @@
// nothing about HTTP. // nothing about HTTP.
// //
// The embedded templates and stylesheet are the reference theme (ADR-0026) — a demonstration of // The embedded templates and stylesheet are the reference theme (ADR-0026) — a demonstration of
// docs/theme-contract.md, not a design. Fields a template may rely on are listed there. // harness/theme-contract.md, not a design. Fields a template may rely on are listed there.
package render package render
import ( import (
+1 -1
View File
@@ -1,6 +1,6 @@
// The types in this file are the theme contract in Go: what a template receives, nothing about how it is // The types in this file are the theme contract in Go: what a template receives, nothing about how it is
// produced. Split from render.go when that file crossed the size warning — the seam was already here, since // produced. Split from render.go when that file crossed the size warning — the seam was already here, since
// docs/theme-contract.md describes exactly this and nothing else. // harness/theme-contract.md describes exactly this and nothing else.
package render package render
+2 -2
View File
@@ -3,8 +3,8 @@
Durable information surfaced in conversation that would otherwise be lost: measured numbers, external Durable information surfaced in conversation that would otherwise be lost: measured numbers, external
constraints, how a tool actually behaves, links worth having. One file per subject. constraints, how a tool actually behaves, links worth having. One file per subject.
Not decisions (`docs/decisions.md`), not proposals (`ideas/`), not the present state of the code Not decisions (`harness/decisions.md`), not proposals (`ideas/`), not the present state of the code
(`docs/state.md`). A fact only true today belongs in `state.md`; a rule belongs in the doc owning the (`harness/state.md`). A fact only true today belongs in `state.md`; a rule belongs in the doc owning the
topic. topic.
Each file states **how the fact was established** — measured, read from a spec, or asserted — so a Each file states **how the fact was established** — measured, read from a spec, or asserted — so a
+1 -1
View File
@@ -1,5 +1,5 @@
# Non-stdlib dependency allowlist. One module path per line; # starts a comment. # Non-stdlib dependency allowlist. One module path per line; # starts a comment.
# Adding a line requires an ADR in docs/decisions.md. Stdlib first, always. # Adding a line requires an ADR in harness/decisions.md. Stdlib first, always.
# Only direct requirements are checked here; the total module count is capped by DEPS_MAX. # Only direct requirements are checked here; the total module count is capped by DEPS_MAX.
# Infrastructure clients (Redis, S3, search) are dependencies like any other and get no exemption. # Infrastructure clients (Redis, S3, search) are dependencies like any other and get no exemption.
+1 -1
View File
@@ -35,4 +35,4 @@ CLAUDE_LOC_MAX=150 # CLAUDE.md only — the one file billed on every turn (A
# CLAUDE_LOC_MAX is a different kind of budget: every other ceiling here bills once, when someone reads # CLAUDE_LOC_MAX is a different kind of budget: every other ceiling here bills once, when someone reads
# the code, while CLAUDE.md is re-sent on every turn of every session. 150 leaves ~20 lines of headroom # the code, while CLAUDE.md is re-sent on every turn of every session. 150 leaves ~20 lines of headroom
# over the current file. Reaching it means moving detail to the doc that owns the topic, never deleting # over the current file. Reaching it means moving detail to the doc that owns the topic, never deleting
# a rule to fit — `docs/README.md`'s compression contract governs which of the two you are doing. # a rule to fit — `harness/README.md`'s compression contract governs which of the two you are doing.
+4 -4
View File
@@ -3,7 +3,7 @@
# with the line to jump to. # with the line to jump to.
# #
# Usage: scripts/surface.sh print it # Usage: scripts/surface.sh print it
# scripts/surface.sh --write regenerate docs/surface.md (`make surface`) # scripts/surface.sh --write regenerate harness/surface.md (`make surface`)
# #
# Why this exists: reading a 400-line file to learn what is in it costs forty times what reading its # Why this exists: reading a 400-line file to learn what is in it costs forty times what reading its
# surface costs, and the answer to "where does X live" is almost never in the bodies. `go doc` already # surface costs, and the answer to "where does X live" is almost never in the bodies. `go doc` already
@@ -11,7 +11,7 @@
# lives — nothing outside cmd/ has many exported names by design. # lives — nothing outside cmd/ has many exported names by design.
# #
# Generated, never hand-edited, and gated: verify.sh regenerates and compares, so a stale surface fails # Generated, never hand-edited, and gated: verify.sh regenerates and compares, so a stale surface fails
# the build rather than misleading a reader. Purposes and intent stay in docs/state.md — this file is # the build rather than misleading a reader. Purposes and intent stay in harness/state.md — this file is
# mechanical and says only what is there. # mechanical and says only what is there.
set -uo pipefail set -uo pipefail
cd "$(dirname "$0")/.." || exit 1 cd "$(dirname "$0")/.." || exit 1
@@ -48,8 +48,8 @@ emit() {
} }
if [ "${1:-}" = "--write" ]; then if [ "${1:-}" = "--write" ]; then
emit >docs/surface.md emit >harness/surface.md
printf 'wrote docs/surface.md (%s lines)\n' "$(wc -l <docs/surface.md | tr -d ' ')" printf 'wrote harness/surface.md (%s lines)\n' "$(wc -l <harness/surface.md | tr -d ' ')"
else else
emit emit
fi fi
+18 -18
View File
@@ -51,8 +51,8 @@ if [ -d .git ] && command -v git >/dev/null 2>&1; then
if [ -z "$changed" ]; then if [ -z "$changed" ]; then
pass "working tree clean — nothing to couple" pass "working tree clean — nothing to couple"
else else
if echo "$changed" | grep -qE '\.go$' && ! echo "$changed" | grep -qx 'docs/state.md'; then if echo "$changed" | grep -qE '\.go$' && ! echo "$changed" | grep -qx 'harness/state.md'; then
bad "*.go changed but docs/state.md did not — inventory, counters and latent items move with the code" bad "*.go changed but harness/state.md did not — inventory, counters and latent items move with the code"
else else
pass "code/state.md coupling" pass "code/state.md coupling"
fi fi
@@ -77,8 +77,8 @@ if [ -d .git ] && command -v git >/dev/null 2>&1; then
pass "code/test coupling" pass "code/test coupling"
fi fi
if echo "$changed" | grep -qE '^internal/.*templates/.*\.html$' && ! echo "$changed" | grep -qx 'docs/theme-contract.md'; then if echo "$changed" | grep -qE '^internal/.*templates/.*\.html$' && ! echo "$changed" | grep -qx 'harness/theme-contract.md'; then
bad "embedded templates changed but docs/theme-contract.md did not — they drift together (ADR-0023)" bad "embedded templates changed but harness/theme-contract.md did not — they drift together (ADR-0023)"
else else
pass "templates/theme-contract coupling" pass "templates/theme-contract coupling"
fi fi
@@ -133,28 +133,28 @@ if [ -d .git ] && command -v git >/dev/null 2>&1; then
# Dangling references. Every one of these found a real stale pointer when run by hand. # Dangling references. Every one of these found a real stale pointer when run by hand.
# `.scratch/` is included only when it exists: it is gitignored, so on a fresh clone the handoff is # `.scratch/` is included only when it exists: it is gitignored, so on a fresh clone the handoff is
# legitimately absent (docs/README.md says "if present") and checking it there would fail correct # legitimately absent (harness/README.md says "if present") and checking it there would fail correct
# work. Once the directory is present, a named file missing from it is a stale pointer like any # work. Once the directory is present, a named file missing from it is a stale pointer like any
# other — which is how state.md's pointer to a deleted build-queue outlived it by two commits. That # other — which is how state.md's pointer to a deleted build-queue outlived it by two commits. That
# path is named without backticks on purpose: this gate reads its own file, so the example would be a # path is named without backticks on purpose: this gate reads its own file, so the example would be a
# reference, and the gate would flag the comment explaining it. # reference, and the gate would flag the comment explaining it.
scratchpat="" scratchpat=""
[ -d .scratch ] && scratchpat='|\.scratch' [ -d .scratch ] && scratchpat='|\.scratch'
refs=$(grep -rhoE "\`(docs|scripts|ideas|reference|\.claude$scratchpat)/[A-Za-z0-9_./-]+\`" \ refs=$(grep -rhoE "\`(harness|scripts|ideas|reference|\.claude$scratchpat)/[A-Za-z0-9_./-]+\`" \
docs CLAUDE.md HARNESS.md ideas reference .claude scripts 2>/dev/null | tr -d '`' | sort -u) harness CLAUDE.md HARNESS.md ideas reference .claude scripts 2>/dev/null | tr -d '`' | sort -u)
dangling="" dangling=""
for f in $refs; do [ -e "$f" ] || dangling="$dangling $f"; done for f in $refs; do [ -e "$f" ] || dangling="$dangling $f"; done
[ -n "$dangling" ] && bad "reference to a path that does not exist:$dangling" [ -n "$dangling" ] && bad "reference to a path that does not exist:$dangling"
adrs=$(grep -rhoE 'ADR-[0-9]{4}' docs CLAUDE.md HARNESS.md ideas reference .claude scripts cmd internal 2>/dev/null | sort -u) adrs=$(grep -rhoE 'ADR-[0-9]{4}' harness CLAUDE.md HARNESS.md ideas reference .claude scripts cmd internal 2>/dev/null | sort -u)
missingadr="" missingadr=""
for a in $adrs; do for a in $adrs; do
# the log registers every number ever used — as an entry, or in the withdrawn line # the log registers every number ever used — as an entry, or in the withdrawn line
grep -q "$a" docs/decisions.md 2>/dev/null || missingadr="$missingadr $a" grep -q "$a" harness/decisions.md 2>/dev/null || missingadr="$missingadr $a"
done done
[ -n "$missingadr" ] && bad "reference to an ADR with no entry in decisions.md:$missingadr" [ -n "$missingadr" ] && bad "reference to an ADR with no entry in decisions.md:$missingadr"
secs=$(grep -rhoE 'CLAUDE\.md`? §[0-9]+' docs HARNESS.md ideas reference .claude scripts 2>/dev/null | secs=$(grep -rhoE 'CLAUDE\.md`? §[0-9]+' harness HARNESS.md ideas reference .claude scripts 2>/dev/null |
grep -oE '§[0-9]+' | tr -d '§' | sort -u) grep -oE '§[0-9]+' | tr -d '§' | sort -u)
missingsec="" missingsec=""
for s in $secs; do for s in $secs; do
@@ -183,12 +183,12 @@ if [ -d .git ] && command -v git >/dev/null 2>&1; then
# doc update had to trail the code in a commit of its own, and folding the two together then left the # doc update had to trail the code in a commit of its own, and folding the two together then left the
# sha naming a commit that no longer existed (ADR-0057). # sha naming a commit that no longer existed (ADR-0057).
lastgo=$(git log -1 --format=%H -- '*.go' 2>/dev/null) lastgo=$(git log -1 --format=%H -- '*.go' 2>/dev/null)
laststate=$(git log -1 --format=%H -- docs/state.md 2>/dev/null) laststate=$(git log -1 --format=%H -- harness/state.md 2>/dev/null)
if [ -n "$lastgo" ]; then if [ -n "$lastgo" ]; then
if [ -n "$laststate" ] && git merge-base --is-ancestor "$lastgo" "$laststate" 2>/dev/null; then if [ -n "$laststate" ] && git merge-base --is-ancestor "$lastgo" "$laststate" 2>/dev/null; then
pass "docs/state.md is current with the code" pass "harness/state.md is current with the code"
else else
note "docs/state.md was last updated in ${laststate:-no commit}, older than the .go change in $(git log -1 --format=%h -- '*.go')" note "harness/state.md was last updated in ${laststate:-no commit}, older than the .go change in $(git log -1 --format=%h -- '*.go')"
fi fi
fi fi
# Every counter row says what does *not* count (ADR-0070). Four counters were re-scoped on first contact, # Every counter row says what does *not* count (ADR-0070). Four counters were re-scoped on first contact,
@@ -199,7 +199,7 @@ if [ -d .git ] && command -v git >/dev/null 2>&1; then
name=$2; gsub(/^[ \t]+|[ \t]+$/, "", name); name=$2; gsub(/^[ \t]+|[ \t]+$/, "", name);
last=$6; gsub(/^[ \t]+|[ \t]+$/, "", last); last=$6; gsub(/^[ \t]+|[ \t]+$/, "", last);
if (last == "") print name if (last == "") print name
} on && !/^\|/{on=0}' docs/state.md) } on && !/^\|/{on=0}' harness/state.md)
if [ -n "$counters" ]; then if [ -n "$counters" ]; then
bad "counter row with no 'does not count' (ADR-0070): $(echo "$counters" | tr '\n' ';')" bad "counter row with no 'does not count' (ADR-0070): $(echo "$counters" | tr '\n' ';')"
else else
@@ -209,10 +209,10 @@ if [ -d .git ] && command -v git >/dev/null 2>&1; then
# The generated surface is only worth reading if it cannot be wrong. Regenerate and compare rather # The generated surface is only worth reading if it cannot be wrong. Regenerate and compare rather
# than trusting that whoever added a function also ran the script. # than trusting that whoever added a function also ran the script.
if [ -x scripts/surface.sh ]; then if [ -x scripts/surface.sh ]; then
if ! scripts/surface.sh | cmp -s - docs/surface.md; then if ! scripts/surface.sh | cmp -s - harness/surface.md; then
bad "docs/surface.md is stale — run 'make surface'" bad "harness/surface.md is stale — run 'make surface'"
else else
pass "docs/surface.md matches the code" pass "harness/surface.md matches the code"
fi fi
fi fi
@@ -489,6 +489,6 @@ deadexp=$(echo "$gofiles" | grep '^\./internal/' | grep -v '_test\.go$' | xargs
done) done)
[ -n "$deadexp" ] && note "exported but referenced once — unexport or delete: $deadexp" [ -n "$deadexp" ] && note "exported but referenced once — unexport or delete: $deadexp"
todos=$(echo "$gofiles" | xargs grep -c 'TODO\|FIXME\|XXX' 2>/dev/null | awk -F: '{s+=$2} END{print s+0}') todos=$(echo "$gofiles" | xargs grep -c 'TODO\|FIXME\|XXX' 2>/dev/null | awk -F: '{s+=$2} END{print s+0}')
[ "${todos:-0}" -gt 0 ] && note "$todos TODO/FIXME markers (latent items belong in docs/state.md)" [ "${todos:-0}" -gt 0 ] && note "$todos TODO/FIXME markers (latent items belong in harness/state.md)"
result_and_exit result_and_exit