From ec6e9838f049dbfc447940be6c02de35941de334 Mon Sep 17 00:00:00 2001 From: bdeshi Date: Sun, 2 Aug 2026 20:11:06 +0600 Subject: [PATCH] rename docs/ to harness/, and reserve docs/ for the reader MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .claude/commands/adr.md | 4 +-- .claude/commands/audit.md | 2 +- .claude/commands/invariants.md | 2 +- .claude/commands/refresh-docs.md | 6 ++-- .claude/skills/feature-loop/SKILL.md | 22 ++++++------- CLAUDE.md | 32 +++++++++--------- HARNESS.md | 47 ++++++++++++++------------- Makefile | 2 +- {docs => harness}/README.md | 10 ++++-- {docs => harness}/architecture.md | 0 {docs => harness}/content-model.md | 0 {docs => harness}/context-economy.md | 4 +-- {docs => harness}/conventions.md | 0 {docs => harness}/decisions.md | 39 +++++++++++++++++++--- {docs => harness}/extensions.md | 0 {docs => harness}/roadmap.md | 0 {docs => harness}/state.md | 8 +++-- {docs => harness}/surface.md | 0 {docs => harness}/theme-contract.md | 2 +- {docs => harness}/toolchain.md | 0 ideas/README.md | 10 +++--- ideas/exploration.md | 2 +- ideas/specs-as-secondary-artifacts.md | 2 +- ideas/token-conservation.md | 2 +- internal/render/render.go | 2 +- internal/render/view.go | 2 +- reference/README.md | 4 +-- scripts/allowed-deps.txt | 2 +- scripts/budgets.env | 2 +- scripts/surface.sh | 8 ++--- scripts/verify.sh | 36 ++++++++++---------- 31 files changed, 146 insertions(+), 106 deletions(-) rename {docs => harness}/README.md (95%) rename {docs => harness}/architecture.md (100%) rename {docs => harness}/content-model.md (100%) rename {docs => harness}/context-economy.md (97%) rename {docs => harness}/conventions.md (100%) rename {docs => harness}/decisions.md (97%) rename {docs => harness}/extensions.md (100%) rename {docs => harness}/roadmap.md (100%) rename {docs => harness}/state.md (97%) rename {docs => harness}/surface.md (100%) rename {docs => harness}/theme-contract.md (99%) rename {docs => harness}/toolchain.md (100%) diff --git a/.claude/commands/adr.md b/.claude/commands/adr.md index 71162b8..a441d29 100644 --- a/.claude/commands/adr.md +++ b/.claude/commands/adr.md @@ -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: diff --git a/.claude/commands/audit.md b/.claude/commands/audit.md index 5080071..26b3885 100644 --- a/.claude/commands/audit.md +++ b/.claude/commands/audit.md @@ -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, diff --git a/.claude/commands/invariants.md b/.claude/commands/invariants.md index badf24f..ebf6c8b 100644 --- a/.claude/commands/invariants.md +++ b/.claude/commands/invariants.md @@ -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 diff --git a/.claude/commands/refresh-docs.md b/.claude/commands/refresh-docs.md index 14a7558..972294b 100644 --- a/.claude/commands/refresh-docs.md +++ b/.claude/commands/refresh-docs.md @@ -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 diff --git a/.claude/skills/feature-loop/SKILL.md b/.claude/skills/feature-loop/SKILL.md index 3992adf..fa674cb 100644 --- a/.claude/skills/feature-loop/SKILL.md +++ b/.claude/skills/feature-loop/SKILL.md @@ -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 -- ` 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 '' docs CLAUDE.md HARNESS.md ideas reference .claude scripts +grep -rn '' 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 -- ` 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 | diff --git a/CLAUDE.md b/CLAUDE.md index cf98b63..cf713e4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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/` 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. diff --git a/HARNESS.md b/HARNESS.md index 8f1d063..dd9491d 100644 --- a/HARNESS.md +++ b/HARNESS.md @@ -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/.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 `