Files
khosra/CLAUDE.md
T
bdeshiandClaude Opus 5 ec6e9838f0 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>
2026-08-02 20:11:06 +06:00

9.2 KiB

khosra — agent constitution

khosra is a flat-file personal publishing engine in Go, built solo, one feature at a time. You implement; the human owns scope and taste. This file overrides your defaults.

A personal publishing substrate: a directory of Markdown becomes an owned, networked home for fiction, webcomics, art, and essays, in English and Bengali. Values: data sovereignty, minimalism as aesthetic, comprehensibility by one person.


1. Read order (do not skip, do not exceed)

  1. This file.
  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.mdafter 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 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 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. 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: 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.

2. The primitives — everything reduces to one

Bundle · Stage · Query · View · Interaction · Effect (+ bundle-as-program). 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.

Name the layer too. Content on disk · engine (data only the engine can produce) · theme (markup) · 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: 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 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 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 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 cache until requests feel slow, no concurrency until profiled, no utils/helpers/common/manager/base packages, ever.
  7. Delete before you add. If removing code buys the feature, do that.
  8. Nothing ships without the doc that describes it, and nothing ships still describing what you replaced. Code carries its state.md update; harness changes (this file, scripts/, .claude/) carry their HARNESS.md update; both are gated. Changed a rule, value, name or path? Grep the repo for the old form and every doc that names the concept, and fix them in this change — verify.sh catches dangling paths and ADR numbers, never a superseded sentence. And never assert a mechanism that does not exist yet: if a doc says a gate rejects something, run it and watch it reject, or do not write the sentence. ./scripts/verify.sh --list names every gate there actually is. The harness evolves: when a rule here proves wrong, fix the rule and its description rather than working around it.
  9. Surface conflicts; never resolve them silently. If a request contradicts a decision already recorded — an ADR, an architecture invariant, the permalink shape, the untrusted boundary, a budget, a frozen contract — say so before writing code: quote the line, name the file, and give the two paths (comply, or change the decision on purpose). A request that merely exceeds a convention is not a conflict; a request that reverses a deliberate choice always is. Kinds and procedure: the Clarify step in SKILL.md. Silently doing what was asked is the failure mode this rule exists to prevent — the human cannot audit a conflict they were never shown.

4. The loop (every request, no exceptions)

Clarify → Plan → Implement → Verify → Document → Commit → Report. Procedure: .claude/skills/feature-loop/SKILL.md. The gates people skip:

Clarify. Only questions whose answer changes the code or the bytes on disk — but ask every one of 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 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 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.

Announce it once per session, prominently, when it was not asked for. If the human has not already asked for automatic commits in this session, the first commit is preceded by a visible line of its own — not a clause inside the report — saying that this change is being committed, that later changes will be too, and that saying so turns it off. Silence is consent only after the notice exists; before it, an unannounced commit is a surprise in someone else's repository.

5. Definition of done

  • Plan's success criteria demonstrated with real output.
  • ./scripts/verify.sh green.
  • Diff contains nothing outside the planned files.
  • 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.

6. Stop conditions — halt and ask

  • The change exceeds planned LOC by ~50%, or touches an unplanned file.
  • You need a new dependency, a new package, or a change to a frozen contract.
  • You are about to write a mock, a switch on a type, or a second copy of parsing logic.
  • The feature only works if the untrusted boundary bends.
  • Two reasonable designs exist and the choice is expensive to reverse — present both, briefly.

Stopping early costs one message. Guessing costs a refactor.

7. Style floor

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: harness/conventions.md — read them, do not ask.