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
---
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
`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.
Rules:
+1 -1
View File
@@ -16,7 +16,7 @@ Look for:
- 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.
- 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.
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
`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.
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.
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,
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?
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
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
+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. |
| 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
`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. |
| 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. |
@@ -28,15 +28,15 @@ a layout or markup decision dressed as an engine feature. |
## 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,
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
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.
For code, read the compressed form before the source: `docs/surface.md` locates every declaration,
`go doc` gives a package's surface, a test states its contract. Disciplines: `docs/context-economy.md`.
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: `harness/context-economy.md`.
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
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.
- Only the planned files. No renames, reordering, reformatting beyond `gofmt`, or tidying of
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.
- 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.
@@ -123,12 +123,12 @@ not run something, say which and why.
## 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:
- `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).
- `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
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.
@@ -139,7 +139,7 @@ Doc edits are surgical too. Prefer deleting a stale line to appending a correcte
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
@@ -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 |
| Explaining why the boundary can bend here | It cannot (ADR-0003) | Stop and ask |
| 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 |
| 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 |
@@ -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 |
| 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 |
| 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 ./...` |
| 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 |
+16 -16
View File
@@ -12,27 +12,27 @@ as aesthetic, comprehensibility by one person.
## 1. Read order (do not skip, do not exceed)
1. This file.
2. `docs/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".
2. `harness/state.md` — what exists **right now**, plus the earn-it counters.
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
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
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.
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.
**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
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
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
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
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
**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
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
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
the test: `docs/architecture.md`.
the test: `harness/architecture.md`.
## 3. Hard rules
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.
2. **No new dependency** without an ADR and human approval. Allowlist:
`scripts/allowed-deps.txt`. Stdlib first, always.
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,
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
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.
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
@@ -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
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,
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
ran (golden file, `curl`, test name, benchmark). Never report success from reading your own diff.
"Should work" is not a result.
**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
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.
@@ -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.
- [ ] `./scripts/verify.sh` green.
- [ ] Diff contains nothing outside the planned files.
- [ ] `docs/state.md` updated (inventory, counters, latent items, verified-at line).
- [ ] ADR in `docs/decisions.md` if a load-bearing choice was made.
- [ ] `harness/state.md` updated (inventory, counters, latent items, verified-at line).
- [ ] 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.
- [ ] 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
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.
+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.
`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
engine — anything in it may be pulled into context on demand. `ideas/` and `reference/` sit outside
`docs/` deliberately: storage, opened only when you name a file, swept by nothing.
constitution (always loaded). `harness/` is what the agent needs to build the
engine — anything in it may be pulled into context on demand. It is named for its audience, not its
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`.
`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
@@ -37,7 +40,7 @@ Occasional maintenance, by you:
- "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
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.
- `/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
@@ -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
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
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).
@@ -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.
**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
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
@@ -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.
**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
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
the code cannot mislead — an order of magnitude smaller than the source it stands for — and no one has to
remember to run it;
@@ -125,23 +128,23 @@ wanting to emit HTML still renders a theme template instead (ADR-0036).
In order, cheapest first:
1. `./scripts/verify.sh` — one command, tells you whether the thing is still coherent and whether
`docs/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
`harness/state.md` has fallen behind the code.
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
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.
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.
4. `/refresh-docs` — reconciles every doc against the actual code and reports drift, which is exactly the
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
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
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
@@ -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
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
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`
@@ -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
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
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
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.
@@ -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,
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:
`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
@@ -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
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.
- `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 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
`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
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
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.
- `./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
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.
- `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.
A new mechanism adds a row; nothing outside a value's home may restate it. A number written twice
eventually disagrees with itself.
## 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.
- `go.mod` does not exist yet — `go mod init` belongs to the first feature, and the module path is
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
./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
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 |
|---|---|---|
@@ -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 |
| `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
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.
@@ -24,7 +24,7 @@ In order of cost, stop at the first that answers the question:
| 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 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 |
@@ -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
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
`core.hooksPath`. `CLAUDE_LOC_MAX` bounds the one file billed on every turn. `--quiet` exists to be
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
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
`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
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
@@ -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
demonstrates every contract feature and nothing more.
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.
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
@@ -794,10 +794,10 @@ tweak to this one.
## ADR-0053 — Context economy is a doc with a floor, plus three gates
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
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
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
@@ -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
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.
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*
@@ -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).
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.
## 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
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,
which is what a number written in two places always does. What each file is *for* is the fact this
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
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
@@ -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
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
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 |
| 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 |
| a decision already made | `docs/decisions.md` |
| a known code flaw deliberately unfixed | Latent list in `docs/state.md` |
| a decision already made | `harness/decisions.md` |
| a known code flaw deliberately unfixed | Latent list in `harness/state.md` |
| a durable fact, number, or link | `reference/` |
## 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**
- [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**
- [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
working material: keeping it out of `docs/` keeps it out of the set an agent may pull in on demand.
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 `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
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
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.
`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.
- 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.
## The non-overlapping slot
+1 -1
View File
@@ -1,6 +1,6 @@
# 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
below is the only part still undecided; it is also recorded in `state.md` "Open questions".
Raised: 2026-07-28
+1 -1
View File
@@ -2,7 +2,7 @@
// nothing about HTTP.
//
// 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
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
// 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
+2 -2
View File
@@ -3,8 +3,8 @@
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.
Not decisions (`docs/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
Not decisions (`harness/decisions.md`), not proposals (`ideas/`), not the present state of the code
(`harness/state.md`). A fact only true today belongs in `state.md`; a rule belongs in the doc owning the
topic.
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.
# 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.
# 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
# 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
# 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.
#
# 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
# 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.
#
# 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.
set -uo pipefail
cd "$(dirname "$0")/.." || exit 1
@@ -48,8 +48,8 @@ emit() {
}
if [ "${1:-}" = "--write" ]; then
emit >docs/surface.md
printf 'wrote docs/surface.md (%s lines)\n' "$(wc -l <docs/surface.md | tr -d ' ')"
emit >harness/surface.md
printf 'wrote harness/surface.md (%s lines)\n' "$(wc -l <harness/surface.md | tr -d ' ')"
else
emit
fi
+18 -18
View File
@@ -51,8 +51,8 @@ if [ -d .git ] && command -v git >/dev/null 2>&1; then
if [ -z "$changed" ]; then
pass "working tree clean — nothing to couple"
else
if echo "$changed" | grep -qE '\.go$' && ! echo "$changed" | grep -qx 'docs/state.md'; then
bad "*.go changed but docs/state.md did not — inventory, counters and latent items move with the code"
if echo "$changed" | grep -qE '\.go$' && ! echo "$changed" | grep -qx 'harness/state.md'; then
bad "*.go changed but harness/state.md did not — inventory, counters and latent items move with the code"
else
pass "code/state.md coupling"
fi
@@ -77,8 +77,8 @@ if [ -d .git ] && command -v git >/dev/null 2>&1; then
pass "code/test coupling"
fi
if echo "$changed" | grep -qE '^internal/.*templates/.*\.html$' && ! echo "$changed" | grep -qx 'docs/theme-contract.md'; then
bad "embedded templates changed but docs/theme-contract.md did not — they drift together (ADR-0023)"
if echo "$changed" | grep -qE '^internal/.*templates/.*\.html$' && ! echo "$changed" | grep -qx 'harness/theme-contract.md'; then
bad "embedded templates changed but harness/theme-contract.md did not — they drift together (ADR-0023)"
else
pass "templates/theme-contract coupling"
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.
# `.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
# 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
# reference, and the gate would flag the comment explaining it.
scratchpat=""
[ -d .scratch ] && scratchpat='|\.scratch'
refs=$(grep -rhoE "\`(docs|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)
refs=$(grep -rhoE "\`(harness|scripts|ideas|reference|\.claude$scratchpat)/[A-Za-z0-9_./-]+\`" \
harness CLAUDE.md HARNESS.md ideas reference .claude scripts 2>/dev/null | tr -d '`' | sort -u)
dangling=""
for f in $refs; do [ -e "$f" ] || dangling="$dangling $f"; done
[ -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=""
for a in $adrs; do
# 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
[ -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)
missingsec=""
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
# sha naming a commit that no longer existed (ADR-0057).
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 "$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
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
# 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);
last=$6; gsub(/^[ \t]+|[ \t]+$/, "", last);
if (last == "") print name
} on && !/^\|/{on=0}' docs/state.md)
} on && !/^\|/{on=0}' harness/state.md)
if [ -n "$counters" ]; then
bad "counter row with no 'does not count' (ADR-0070): $(echo "$counters" | tr '\n' ';')"
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
# than trusting that whoever added a function also ran the script.
if [ -x scripts/surface.sh ]; then
if ! scripts/surface.sh | cmp -s - docs/surface.md; then
bad "docs/surface.md is stale — run 'make surface'"
if ! scripts/surface.sh | cmp -s - harness/surface.md; then
bad "harness/surface.md is stale — run 'make surface'"
else
pass "docs/surface.md matches the code"
pass "harness/surface.md matches the code"
fi
fi
@@ -489,6 +489,6 @@ deadexp=$(echo "$gofiles" | grep '^\./internal/' | grep -v '_test\.go$' | xargs
done)
[ -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:-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