The constitution asks which primitive a feature is. It never asked *where* the feature belongs, and that let a whole feature get built at the wrong layer. Four layers, outermost wins: content on disk, engine (facts only the engine can produce), theme (markup), browser (CSS, then JS). Two rules fall out — the engine never edits authored text to change how it looks, and a feature needing no engine fact is not an engine feature. architecture.md carries the table and the test; CLAUDE.md carries the one-line version, since it is the file always loaded. The example is named in the docs on purpose. A rule with a scar attached is one an agent can apply; a rule stated in the abstract gets reasoned around.
7.1 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)
- This file.
docs/state.md— what exists right now, plus the earn-it counters.docs/README.md— always. The map: how you find which doc owns your topic. Not "exceeding".- Every doc owning a topic your change asserts a rule about (ownership table in
docs/README.md). - 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
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.
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: docs/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: docs/architecture.md.
3. Hard rules
- No abstraction before its second concrete use — pipeline, resolver, interface, generic, config
knob, registry. The counters table in
docs/state.mdholds every threshold and is the only place they are written down: read them, increment them, never anticipate them. - No new dependency without an ADR and human approval. Allowlist:
scripts/allowed-deps.txt. Stdlib first, always. - 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 indocs/state.md. - 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.
- 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— read it before emitting a URL, and never invent a second shape. - 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, noutils/helpers/common/manager/basepackages, ever. - Delete before you add. If removing code buys the feature, do that.
- Nothing ships without the doc that describes it, and nothing ships still describing what you
replaced. Code carries its
state.mdupdate; harness changes (this file,scripts/,.claude/) carry theirHARNESS.mdupdate; 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.shcatches 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 --listnames 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. - 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 → Report.
Procedure: .claude/skills/khosra-feature-loop/SKILL.md. The two gates people skip:
Clarify. Only questions whose answer changes the code or the bytes on disk. Max three,
batched, up front, each with a bold default so silence answers. Never about naming, formatting,
or anything docs/conventions.md decides. None to ask? State assumptions in one line and move on.
Verify. ./scripts/verify.sh 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.
5. Definition of done
- Plan's success criteria demonstrated with real output.
./scripts/verify.shgreen.- Diff contains nothing outside the planned files.
docs/state.mdupdated (inventory, counters, latent items, verified-at line).- ADR in
docs/decisions.mdif a load-bearing choice was made. - 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
switchon 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: docs/conventions.md —
read them, do not ask.