rename the project to khosra, initialise the module

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.
This commit is contained in:
2026-08-01 02:23:34 +06:00
parent 39dcb8cc34
commit c468cf24c3
14 changed files with 78 additions and 18 deletions
+2
View File
@@ -19,6 +19,8 @@
"Bash(git diff:*)",
"Bash(git log:*)",
"Bash(git show:*)",
"Bash(git add:*)",
"Bash(git commit:*)",
"Bash(rg:*)",
"Bash(wc:*)",
"Bash(curl -s http://localhost:*)",
+1 -1
View File
@@ -1,4 +1,4 @@
/atelier
/khosra
*.test
*.out
.DS_Store
+3 -3
View File
@@ -1,6 +1,6 @@
# atelier — agent constitution
# khosra — agent constitution
`atelier` is a flat-file personal publishing engine in Go, built solo, one feature at a time.
`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
@@ -74,7 +74,7 @@ service or a core-model change): stop, say so in a paragraph, propose the leaf,
## 4. The loop (every request, no exceptions)
`Clarify → Plan → Implement → Verify → Document → Report`.
Procedure: `.claude/skills/atelier-feature-loop/SKILL.md`. The two gates people skip:
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,
+3 -3
View File
@@ -1,6 +1,6 @@
# The atelier harness — human's guide
# The khosra harness — human's guide
Scaffolding that makes an agent build `atelier` the way you want: minimally, surgically, questions
Scaffolding that makes an agent build `khosra` the way you want: minimally, surgically, questions
before code, docs that stay true.
`README.md` is a two-line signpost — human here, agent to `CLAUDE.md`. `CLAUDE.md` is the
@@ -107,7 +107,7 @@ imports `cmd`. One convenient sibling import is what turns a layered engine into
it always looks locally reasonable — so it is a hard failure, not a review note.
**Feature locality is enforced, not hoped for.** A feature is one directory under `internal/ext/<name>/`
plus one line in `cmd/atelier/wire.go`. `verify.sh` fails on a feature importing a sibling and on a
plus one line in `cmd/khosra/wire.go`. `verify.sh` fails on a feature importing a sibling and on a
feature package without a `doc.go` — the first because sibling imports make an agent's read set compound,
the second because a four-line `doc.go` turns orientation into a fifteen-line read (ADR-0027).
+1 -1
View File
@@ -1,4 +1,4 @@
# atelier
# khosra
A flat-file personal publishing engine in Go. Point the binary at a directory of Markdown and it
becomes an owned, networked home for fiction, webcomics, art, and essays, in English and Bengali.
+1 -1
View File
@@ -12,7 +12,7 @@ whatever builds it. What exists is `state.md`'s job. Never build a `[spec]` sect
## The site root
The engine is pointed at a site root outside this repository (`-site <dir>`, or `ATELIER_SITE`), with
The engine is pointed at a site root outside this repository (`-site <dir>`, or `KHOSRA_SITE`), with
its own git history (ADR-0011). Nothing in the engine repo is content. `templates/` in the site root
overrides the defaults the binary embeds, so a bare root still renders.
+3 -3
View File
@@ -8,7 +8,7 @@ goes through an ADR, not a diff.
- `net/http`, `html/template`, `log/slog`, `os`, `io/fs`, `embed`. Routing is `http.ServeMux` with Go
1.22+ patterns — `GET /{section}/{slug}/` and `{path...}` for multi-segment slugs — so no router
library is needed. No web framework, ORM, or config library — flags plus environment variables, parsed in one place. The site root
(`-site`, `ATELIER_SITE`) is the only required setting; default templates are `embed`ded so a bare
(`-site`, `KHOSRA_SITE`) is the only required setting; default templates are `embed`ded so a bare
site root renders (ADR-0011).
- New dependency = ADR + human approval + `scripts/allowed-deps.txt`. `verify.sh` enforces it.
- Prefer 40 lines of obvious code over a dependency doing it in one call — unless the 40 lines would be
@@ -16,7 +16,7 @@ goes through an ADR, not a diff.
## Package layout
```
cmd/atelier/ main, flag parsing, explicit wiring — the only place things are assembled
cmd/khosra/ main, flag parsing, explicit wiring — the only place things are assembled
internal/content/ bundles, frontmatter, slugs, queries — knows the disk, not HTTP
internal/render/ markdown, transforms, templates — knows content, not HTTP
internal/web/ handlers, routing, headers, caching — knows both, exposes neither
@@ -26,7 +26,7 @@ Dependencies point inward. `internal/content` imports nothing from the others; `
and is imported by nothing. A feature under `internal/ext/` may import `internal/content` and
`internal/render`, and must not import `internal/web`, `cmd/`, or **another feature** — sibling imports
are what make an agent's read set compound (ADR-0027). Every `internal/ext/*` package carries a `doc.go`;
`verify.sh` fails without one. Routes reach `web` by assembly in `cmd/atelier/wire.go`, so `web` never
`verify.sh` fails without one. Routes reach `web` by assembly in `cmd/khosra/wire.go`, so `web` never
learns features exist. No `utils`, `helpers`, `common`, `shared`, `manager`, `base`,
`impl`, `core` — a package name not describing a domain is a smell. Flat until a package exceeds
`FILE_LOC_WARN`; do not pre-partition, and a single-file package therefore warns at the same point it
+43 -2
View File
@@ -116,7 +116,7 @@ Revisit if: the engine can no longer boot and serve correctly with every externa
Date: 2026-07-28 · Status: accepted
Decision: the engine is pointed at a **site root** — a directory outside this repository, versioned in
its own git repo — holding `content/`, `static/`, and optionally `templates/` overriding the defaults
the binary embeds. Selected by `-site <dir>` or `ATELIER_SITE`. This repository contains engine source
the binary embeds. Selected by `-site <dir>` or `KHOSRA_SITE`. This repository contains engine source
only; no content, ever, not even an example.
Why: content is the database, and a database does not live in the application's repo. Mixing them
interleaves "fixed a typo in a poem" with "extracted the resolver" in one history, makes every typo a
@@ -298,7 +298,7 @@ Revisit if: it starts accumulating design decisions — then split it into a ref
## ADR-0027 — Feature locality: one directory, no sibling imports, a mandatory `doc.go`
Date: 2026-07-30 · Status: accepted
Decision: a feature is one directory under `internal/ext/<name>/` from its first use, plus one line in
`cmd/atelier/wire.go` — the only file that knows every feature. No `internal/ext` package may import
`cmd/khosra/wire.go` — the only file that knows every feature. No `internal/ext` package may import
another. Every one carries a `doc.go` stating, in four lines: what it contributes, which cascade keys it
reads, which theme-contract fields it adds, and what it deliberately does not do. A feature may import
`internal/content` and `internal/render`; never `internal/web`, never `cmd/`, never a sibling.
@@ -313,3 +313,44 @@ inward (earning it under the counters) or stay duplicated until the third use, w
rather than a new one. Expensive — `wire.go` absorbs all the coupling on purpose and will look
repetitive; and this buys cheap *features*, not cheap spine changes, which still need the core read.
Revisit if: `wire.go` becomes hard to read, which means the registry from `extensions.md` is due.
## ADR-0028 — Pagination lives in the path: `/{section}/page/2/`
Date: 2026-07-30 · Status: accepted
Decision: page two of a listing is `/{section}/page/2/`; page one is the bare listing URL and never
`/page/1/`, which permanently redirects to it. Tag listings compose the same way:
`/tags/{tag}/page/2/`. `page` is therefore a reserved segment inside a section.
Why: a query parameter is not a permalink — it reads as ephemeral, is dropped by careless sharing, and
splits caching by URL shape rather than by content. Putting it in the path keeps every listing page
linkable and cacheable on the same terms as a bundle.
Consequence: cheap — one more resolver case, and every paginated listing is a first-class URL. Expensive
— no bundle may be slugged `page` inside a paginated section, and changing the page size renumbers pages,
so page URLs are stable only while the size is.
Revisit if: never for the shape. Page size is a separate setting and changing it is a URL event.
## ADR-0029 — A bundle that fails to parse is skipped loudly, not fatal
Date: 2026-07-30 · Status: accepted
Decision: a bundle whose frontmatter or filename cannot be parsed is logged at error level with its path
and line, excluded from the site, and does not stop startup or any request. The `check` command reports
the same failures as errors, and that is where a non-zero exit belongs.
Why: `conventions.md` says startup failure is fatal and loud, and separately that request-time failure
degrades. A single mistyped colon in one post is not a startup failure — treating it as one means one
typo takes the whole site down, which is the worst possible failure mode for a personal site published
from a text editor. Fatal is right for a broken site root or an unreadable directory, not for one file.
Consequence: cheap — the site always serves whatever is valid, and the author sees the error in the log
and in `check`. Expensive — a silently absent page is a real failure mode, so the log line must name the
file and the reason plainly, and `check` must exist early enough to be the place you look.
Revisit if: skipped bundles start going unnoticed in practice — then `check` runs in CI, rather than the
engine becoming fatal.
## ADR-0030 — The project is named khosra
Date: 2026-07-30 · Status: accepted
Decision: the engine is `khosra`. Module path `khosra`, binary `khosra`, command directory
`cmd/khosra/`, environment variable `KHOSRA_SITE`, feature-loop skill `khosra-feature-loop`. The former
name `atelier` appears nowhere, including in earlier ADRs, which describe this project under its old
name rather than a different project.
Why: naming is the author's, and it is free now — before a module is published, a URL is shared, or a
binary is deployed. Every day it waits, it costs more.
Consequence: cheap now, and permanent-ish once the module path is fetched by anything. Expensive later —
a module rename after publication needs a redirect or a major-version bump, and a deployed binary name
appears in service files and container tags.
Revisit if: never. Renaming again costs strictly more than this did.
+1 -1
View File
@@ -41,7 +41,7 @@ type Extension struct {
}
```
`cmd/atelier/wire.go` holds the only list of enabled extensions. Enabling or disabling one is a
`cmd/khosra/wire.go` holds the only list of enabled extensions. Enabling or disabling one is a
one-line diff and a rebuild. Removing one leaves no trace elsewhere — that property is the test of
whether the contract is right.
+6 -2
View File
@@ -5,8 +5,12 @@ If this file disagrees with the code, the code is right and this file is a bug.
## Inventory
No Go source, no `go.mod`. The harness is installed; the engine is unwritten. This repo holds engine
source only — the site root is external and passed with `-site` (ADR-0011).
| File | Purpose | LOC |
|---|---|---|
| `go.mod` | module `khosra`; `x/text` and `yaml.v3` required, not yet imported | 8 |
No Go source yet. This repo holds engine source only — the site root is external and passed with `-site`
(ADR-0011).
Dependencies: none.
+8
View File
@@ -0,0 +1,8 @@
module khosra
go 1.26.1
require (
golang.org/x/text v0.40.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+5
View File
@@ -0,0 +1,5 @@
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+1 -1
View File
@@ -333,7 +333,7 @@ longfuncs=$(echo "$gofiles" | xargs awk -v m="$FUNC_LOC_WARN" '
if [ -f Dockerfile ] && [ "${VERIFY_DOCKER:-0}" = "1" ]; then
head_ "container (VERIFY_DOCKER=1)"
if command -v docker >/dev/null 2>&1; then
if docker build -q -t atelier:verify . >/tmp/docker.log 2>&1; then
if docker build -q -t khosra:verify . >/tmp/docker.log 2>&1; then
pass "docker build"
else
bad "docker build"; tail -20 /tmp/docker.log | sed 's/^/ /'