Naming is free before a module is published, a URL is shared, or a binary is deployed; every day it waits costs more. Swept every form: module path, binary, cmd/ directory, KHOSRA_SITE, the feature-loop skill directory, and the prose in earlier ADRs — which describe this project under its old name, not a different project. Recorded as ADR-0030. go mod init lands here rather than with the first feature because the module path is what the rename is about. x/text and yaml.v3 are required but not yet imported, so both are indirect and no direct dependency is claimed yet.
6.6 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.
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.