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.
118 lines
7.1 KiB
Markdown
118 lines
7.1 KiB
Markdown
# 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. `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".
|
|
4. Every doc owning a topic your change asserts a rule about (ownership table in `docs/README.md`).
|
|
5. 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
|
|
|
|
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
|
|
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`.
|
|
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` —
|
|
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 → 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.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.
|
|
- [ ] 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: `docs/conventions.md` —
|
|
read them, do not ask.
|