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:
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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,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
@@ -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,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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user