commit 02268f91216e4373b95d5580a4ef86fa68afbcf4 Author: bdeshi Date: Thu Jul 30 00:34:18 2026 +0600 init Co-Authored-By: Claude Opus 5 diff --git a/.claude/commands/adr.md b/.claude/commands/adr.md new file mode 100644 index 0000000..71162b8 --- /dev/null +++ b/.claude/commands/adr.md @@ -0,0 +1,21 @@ +--- +description: Record an architectural decision in six lines +--- + +Append an ADR to `docs/decisions.md` using the exact format at the top of that file. + +First, before writing anything: find the doc that owns this topic via the ownership table in +`docs/README.md` and read it. If it already carries the rule, do not write an ADR — amend that doc +and say that is what you did. An ADR that restates an existing doc is a duplicate, not a decision. + +Rules: +- Next sequential number. Never renumber, never rewrite an existing entry. +- To reverse a decision, add a new ADR and mark the old one `superseded by ADR-NNNN`. +- Six lines. If the reasoning needs more, the decision is not yet made. +- `Revisit if:` must name a specific observable event, not "if requirements change". +- If the decision adds a dependency, update `scripts/allowed-deps.txt` in the same change. +- If the decision raises a budget, update `scripts/budgets.env` in the same change and state + the old and new values in the ADR. + +If the argument for `$ARGUMENTS` is thin — no forcing reason, or no consequence you can name — +say so and ask one question rather than writing a hollow entry. diff --git a/.claude/commands/audit.md b/.claude/commands/audit.md new file mode 100644 index 0000000..5080071 --- /dev/null +++ b/.claude/commands/audit.md @@ -0,0 +1,26 @@ +--- +description: Hunt speculative complexity and propose deletions +--- + +Read the code and find what has not earned its place. Propose removals; change nothing yet. + +Look for: +- Interfaces with one implementation. Type parameters with one instantiation. +- Function parameters, struct fields, config knobs, or frontmatter keys with no real caller + or no real author using them. +- Two implementations of the same idea (parsing, path joining, slugging, date handling). +- Abstractions built ahead of their counter: a pipeline at two transforms, a resolver at one + route, an extension registry before its counter is due. +- Files and functions over the advisory sizes in `scripts/budgets.env`; nesting past 4. +- Error handling that logs and returns, or wraps without adding information. +- Tests that assert on private helpers, or that need a mock to exist. +- Dead code, commented-out code, `TODO`s older than the feature that introduced them. +- Comments that narrate control flow. +- Anything in `docs/` describing code that no longer exists — excluding `ideas/` and + `reference/`, which are never audited against the code and must not be opened here. + +Report as a table: item, location, why it has not earned its place, LOC recovered if deleted, +and risk of deleting. Order by LOC recovered, descending. Recommend a top three. + +Then stop. Deletions happen as their own small changes, with a go-ahead each, and each one runs +`./scripts/verify.sh`. diff --git a/.claude/commands/invariants.md b/.claude/commands/invariants.md new file mode 100644 index 0000000..badf24f --- /dev/null +++ b/.claude/commands/invariants.md @@ -0,0 +1,33 @@ +--- +description: Check the architecture invariants against the code, not against the docs +--- + +`verify.sh` checks what a grep can check. This pass checks the nine invariants in +`docs/architecture.md`, which need reading the code. Run at arc boundaries, before a freeze, and +before the first deploy. + +For each invariant, answer **held / violated / not yet applicable**, with a file and line when +violated. Do not fix anything in this pass. + +1. **Open page object** — are all fields optional in practice? Find any place a missing field causes + a nil deref, an error return, or a template failure that reaches the response. +2. **Trusted / untrusted modes** — trace every path by which non-repo bytes could reach shortcode or + template evaluation. This is the one invariant where "probably fine" is not an answer: name the + check that stops it, or report it violated. +3. **Identity is not language** — is the bundle key free of language? Any place a language suffix + leaks into a cache key, a URL, or a dependency key is a violation. +4. **Request-time render behind a cache** — does export walk the same code path as the server, or has + a second path appeared? +5. **Permalinks permanent** — does any code derive a URL by a rule other than the one in + `content-model.md`? Does every rename path emit an alias plus a permanent redirect? +6. **Interactions off the content graph** — can an interaction invalidate more than its own fragment? +7. **Every feature is a leaf** — is each `internal/ext/*` package deletable without touching the + core? Try naming the diff that removes one. +8. **Degrades with external services off** — for each external client, is there a path that still + serves correctly, slowly, when it is unreachable? +9. **Core stops growing after Arc 2** — compare the `core` figure `verify.sh` prints against the one + recorded at the freeze in `state.md`. Post-freeze growth is the finding, not the number. + +Then: report held/violated per invariant, and for anything violated, whether it is a bug to fix now +or a latent item with a trigger. A violated invariant is a stop condition (`CLAUDE.md §6`) — say so +plainly rather than documenting it as the new normal. diff --git a/.claude/commands/leaf.md b/.claude/commands/leaf.md new file mode 100644 index 0000000..c9e5476 --- /dev/null +++ b/.claude/commands/leaf.md @@ -0,0 +1,24 @@ +--- +description: Decide whether a catalog item is a leaf worth building, or a trunk that waits +--- + +Evaluate `$ARGUMENTS` against the leaf/trunk test in `docs/exploration.md`. + +Research it only as far as needed to answer accurately — what it actually requires, not what its +homepage claims. If it is a protocol or spec, state which parts are mandatory versus optional. + +Answer these, briefly: + +1. **Leaf or trunk?** Per `architecture.md` invariant 7. +2. **Reduces to:** which primitive, concretely, with the shape of the implementation in two lines. +3. **Prerequisites:** which arc or primitive must exist first. Is that gate open today? +4. **Cost:** estimated LOC, new dependencies, new disk fields, ongoing maintenance burden, + and anything it makes permanent (URLs, identifiers, published data). +5. **Sovereignty test:** apply the one in `roadmap.md` Governors. +6. **Fit:** does it serve fiction, webcomics, essays, Bengali-language work, or the + low-bandwidth ethos — or is it interesting for its own sake? Interesting-for-its-own-sake is + a legitimate answer for a cheap leaf and a disqualifying one for anything expensive. +7. **Verdict:** build now / build when gate X opens / trunk, needs an ADR / no. + +Write the verdict as one row in the Verdicts table in `docs/exploration.md`. Do not implement +anything in this pass. diff --git a/.claude/commands/refresh-docs.md b/.claude/commands/refresh-docs.md new file mode 100644 index 0000000..85a2010 --- /dev/null +++ b/.claude/commands/refresh-docs.md @@ -0,0 +1,33 @@ +--- +description: Reconcile the docs against the actual code and report drift +--- + +Reconcile documentation with reality. The code is the truth; the docs are the suspects. + +1. Inventory the actual Go files, their line counts, and the non-stdlib dependencies in `go.mod`. +2. Compare against `docs/state.md`: inventory rows, counters, latent items, `verified against`. + Recount the counters **from the code** — number of render transforms, routing cases, views, + output formats, extensions — rather than trusting the recorded numbers. +3. Check `docs/architecture.md` STATUS lines: has a primitive become real, or is one described + as live when it is not built? +4. Check `docs/content-model.md` `[spec]` versus `[live]` markers against what the parser + actually accepts. Frontmatter fields the code reads but the doc omits are drift; fields the + doc promises but the code ignores are worse drift. +5. Check `scripts/allowed-deps.txt` against `go.mod`. +6. Look for facts stated in two docs. Delete one, link to the other. +7. Run `./scripts/verify.sh --list` and check every doc sentence claiming a gate against it. A doc that + says "`verify.sh` fails on X" where no such gate exists is the most damaging drift there is: it reads + as enforcement and is decoration. +8. Check the reverse too — a gate in the list that no doc explains. Either document it or delete it. +9. Follow every cross-doc citation of a *section* ("see `roadmap.md` Governors", "`CLAUDE.md` §6") and + confirm the heading exists. Whole sections have gone missing while another doc still cited them. + +Scope: the docs listed above and nothing else. **Do not open `ideas/` or `reference/`** — +they describe proposals and facts, never the state of the code, so they cannot be drifted against +it. `verify.sh` already checks their indexes mechanically. + +Then: +- Fix the docs. Doc-only diff; no code changes in this pass, no matter what you find. +- Anything in the code that contradicts an ADR: report it, do not silently document it as + correct. A drifted invariant is a bug, not a new decision. +- Report drift found, drift fixed, and anything that needs a human decision. diff --git a/.claude/commands/verify.md b/.claude/commands/verify.md new file mode 100644 index 0000000..266886c --- /dev/null +++ b/.claude/commands/verify.md @@ -0,0 +1,18 @@ +--- +description: Run the objective gates and report evidence, not opinions +--- + +Run `./scripts/verify.sh` and report the result. + +Then, if the working tree has uncommitted changes, produce feature-specific evidence for what +changed: start the server and `curl` the affected URL, run the relevant test by name, or diff +the golden file. Show real output. + +If anything fails: +1. Report the failure verbatim before interpreting it. +2. Diagnose in one paragraph. +3. Propose the smallest fix. Do not apply it without a go-ahead unless it is a formatting fix. + +If a budget in `scripts/budgets.env` is exceeded, do not raise the budget. Report which budget, +by how much, and which files are responsible. Offer the two options: shrink the code, or an ADR +raising the ceiling deliberately. diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..25fc1af --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,39 @@ +{ + "permissions": { + "defaultMode": "default", + "allow": [ + "Bash(gofmt:*)", + "Bash(go build:*)", + "Bash(go vet:*)", + "Bash(go test:*)", + "Bash(go run:*)", + "Bash(go doc:*)", + "Bash(go list:*)", + "Bash(go mod tidy)", + "Bash(go mod why:*)", + "Bash(./scripts/verify.sh)", + "Bash(bash scripts/verify.sh)", + "Bash(git rev-parse:*)", + "Bash(git status:*)", + "Bash(git status)", + "Bash(git diff:*)", + "Bash(git log:*)", + "Bash(git show:*)", + "Bash(rg:*)", + "Bash(wc:*)", + "Bash(curl -s http://localhost:*)", + "Bash(curl -s http://127.0.0.1:*)" + ], + "ask": [ + "Bash(go get:*)", + "Bash(git commit:*)" + ], + "deny": [ + "Bash(git push:*)", + "Bash(rm -rf:*)", + "Read(./.env)", + "Read(./.env.*)", + "Read(./.envrc)" + ] + } +} diff --git a/.claude/skills/atelier-feature-loop/SKILL.md b/.claude/skills/atelier-feature-loop/SKILL.md new file mode 100644 index 0000000..4d06012 --- /dev/null +++ b/.claude/skills/atelier-feature-loop/SKILL.md @@ -0,0 +1,169 @@ +# The feature loop + +Six steps, in order: **Clarify → Plan → Implement → Verify → Document → Report.** +`CLAUDE.md` holds the rules; this file holds the procedure. + +Scale, do not skip. A one-sentence request gets one line of plan and a short report — and still gets +the conflict check, propagation, verification, the `state.md` touch and the stale-reference sweep. +Casual phrasing is not a smaller change: *"default-language files do not need the `.en` part"* is one +unambiguous sentence that rewrites filenames on disk, makes a suffix optional in code, and falsifies +every doc calling it required. + +## Propagation — a change is not done until every surface agrees + +One request can touch four surfaces. Name each as done or n/a before reporting; the ones people miss +are the first and the last. + +| Surface | What it means | +|---|---| +| the site root | Out of reach: it lives in its own repo (ADR-0011). A disk-contract change ships a **written migration step** the author runs, plus a note on whether any bundle key or URL moves. Never claim to have migrated files you cannot see. | +| code | The engine, plus a test for the new behaviour. | +| the theme | Out of reach, like the site root (ADR-0023). This repo ships a **contract extension** in +`docs/theme-contract.md` plus a written note of what a theme must do — never the theme itself, and never +a layout or markup decision dressed as an engine feature. | +| fixtures and emitted output | Fixture sites in `testdata/`, the embedded default templates (a reference implementation of the contract), and anything reading a field or path shape you altered. | +| harness docs | Every doc, ADR, marker or example that assumes the old form. An ADR that mandates what you just made optional is not stale, it is **contradicted** — supersede it, do not quietly reword it. | + +--- + +## 1. Clarify + +Read `docs/state.md`, then `docs/README.md` — the map, always, before deciding what else to open. +Use its topic-ownership table to list the docs owning anything this change asserts a rule about, +and read those. That list is a floor: skipping it is how a rule gets written twice and the second +copy contradicts the first. + +### Conflict check — every request + +Before anything else, ask whether the request contradicts something already decided. Classify and +act; do not average two positions into a compromise nobody chose. + +| Kind | Examples | What to do | +|---|---|---| +| **Hard** — reverses a deliberate decision | an ADR, an architecture invariant, the permalink shape, the untrusted boundary, a `_MAX` budget, a frozen contract, the dependency policy | **Stop.** Quote the line, name the file, give both paths: comply, or change the decision (new ADR, or supersede the old one). Wait. Never pick for them. | +| **Soft** — exceeds a convention or a `[spec]` shape | a `_WARN` threshold, a style-floor preference, an unbuilt `[spec]` section's suggested shape, a latent item's trigger | State it in one line, proceed with the request, record the deviation where the convention lives. Conventions are a floor, not a decision. | +| **Stale** — a doc contradicts the code | `state.md` inventory, a STATUS line, an out-of-date example | Auto-resolve: the code wins. Fix the doc in Document, mention it in the report. No question needed. | +| **None** — the request refines or extends what is written | asking for something a `[spec]` section already describes | Proceed. Say nothing about it. | + +Resolve intelligently where the answer is genuinely unambiguous — a stale doc, or a request that is a +superset of what is written. Surface anything where a reasonable person could have meant either +thing, and everything in the Hard row without exception. One conflict is worth one message; a +silently reversed decision costs the trust in every other decision. + +Then decide what you genuinely do not know. Ask **only** questions whose answer changes the code or +the bytes on disk. Maximum three, one message, up front, each with a **bold** default. + +Never ask about: anything `conventions.md`, `content-model.md`, or an ADR already decides; naming, +formatting, file placement, test style; permission to follow the constitution; "would you like me +to also…" — that is scope creep wearing a question mark. + +Do ask when: the feature has two plausible disk formats, URL shapes, or authoring ergonomics; it +touches an open question in `state.md`; success criteria are not observable from the request; +it appears to need a dependency, a new package, or a frozen-contract change. + +No such questions? One line — *"No questions — assuming tag pages live at the section root and +reuse the post list template."* — and continue. + +## 2. Plan + +Post this before writing code. Under fifteen lines. + +``` +Goal: one sentence, observable from outside the program +Reduces to: Bundle | Stage | Query | View | Interaction | Effect | bundle-as-program +Success criteria: the checks that will prove it works (commands, URLs, expected output) +Files: exact paths, marked new / edit, with an estimated ±LOC each +New deps: none (anything else needs an ADR first — stop and ask) +Earn-it check: which counter this increments, and whether an extraction is due this change +Trust check: does any untrusted input reach this code? ("n/a" if not) +Docs read: the owning docs you opened ("none" only if the change asserts no rules) +Not doing: the two or three adjacent temptations you are declining +``` + +Wait for a go-ahead unless the user said "just do it" or the change is under ten lines in one file. +If the plan reveals a trunk, say so instead of planning and offer the leaf. + +## 3. Implement + +- Smallest code satisfying the success criteria. Nothing for the version after next. +- Only the planned files. No renames, reordering, reformatting beyond `gofmt`, or tidying of + neighbouring code. +- Something wrong outside the plan? One line on the Latent list in `docs/state.md`, keep moving. + That is the whole mechanism; use it instead of a drive-by fix. +- Reuse the existing shape before inventing one. Copying eight lines twice beats an abstraction at + use two; the third use is when it becomes a design. +- Halt and ask if any `CLAUDE.md §6` stop condition fires. Halting costs one message. + +## 4. Verify + +Run `./scripts/verify.sh` — doc coupling, format, vet, build, test, dependency allowlist, budgets. + +Then **at least one piece of feature-specific evidence you actually executed**: the new test name +and its output, `curl -s localhost:PORT/path | head`, a golden-file diff, the before/after fragment +of real generated markup, or benchmark numbers. + +Budget failure: shrink the change, or stop and propose an ADR raising it. Never raise it silently. + +Never write "should work", "this will now…", or your own diff summarised as a result. If you could +not run something, say which and why. + +## 5. Document + +Same change, not later. Triggers: `docs/README.md`. Walk the propagation table above — every surface +done or explicitly n/a. Minimum: + +- `docs/state.md`: inventory rows, counters, `verified against` line, latent items added/removed. +- `docs/decisions.md`: an ADR if the choice is expensive to reverse. Six lines. First open the doc + that owns the topic — if it already carries the rule, amend it there; an ADR restating an + existing doc is a duplicate, not a decision. +- Any other doc **only if the change made it wrong.** Never state one fact in two docs. + +Doc edits are surgical too. Prefer deleting a stale line to appending a corrected one. + +**Then sweep for what you replaced.** Any rule, value, name or path you changed may be described +elsewhere in the old terms: + +``` +grep -rn '' docs CLAUDE.md HARNESS.md ideas reference .claude scripts +``` + +`verify.sh` fails on dangling file paths, ADR numbers and `CLAUDE.md` section refs. It cannot detect +a sentence that is merely now untrue, or a concept renamed in one place — that sweep is the author's, +and skipping it is how a doc ends up contradicting the file it points at. + +## 6. Report + +Short — one line each, no prose unless a conflict or a stop condition needs explaining. Drop the lines +that are genuinely n/a rather than padding them. + +``` +Did: what now works, in the user's terms +Evidence: the command you ran and its result +Diff: files touched, ±LOC +Propagated: content / code / templates / docs — each done or n/a, with content files named +Earned: counters after this change; anything now due for extraction +Skipped: what you deliberately did not do, and the latent items you logged +Conflicts: hard ones surfaced and how they were settled, soft ones deviated from, or "none" +Swept: the old form you grepped for after a rename, or "n/a" +``` + +--- + +## Failure modes to catch in yourself + +| Symptom | What it means | Do instead | +|---|---|---| +| Interface with one implementation | Anticipating, not designing | Concrete type; wait for the second | +| A parameter no caller passes | Speculative flexibility | Delete it | +| Reaching for a dependency | Usually 40 lines of stdlib | Write the 40 lines, or stop and ask | +| Wanting a `switch` on post type in the core | A View or Query is the seam | Move it out | +| Needing a mock to test | Wrong seam | `fstest.MapFS` or a `testdata` dir | +| Renaming "for clarity" mid-feature | Drive-by refactor | Latent list | +| Explaining why the boundary can bend here | It cannot (ADR-0003) | Stop and ask | +| Plan grew while implementing | Scope drift | Stop, re-plan, continue | +| Writing a rule without reading its owning doc | Guessing where you could look | `docs/README.md` topic table, then amend that doc | +| Quietly doing what was asked against a recorded decision | The conflict was real and you hid it | Quote the line, give both paths, wait | +| Changed the rule in code, left the docs describing the old one | Propagation stopped at the first surface | Walk all four surfaces | +| Made a field optional, left the ADR mandating it | Contradicted, not stale | Supersede the ADR | +| Treated a one-sentence request as a narrow change | Articulation size is not blast radius | Walk the propagation table | +| Splitting the difference between request and policy | A compromise nobody chose | Surface it; the human picks | diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c35c445 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/atelier +*.test +*.out +.DS_Store diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..fcd8221 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,111 @@ +# atelier — agent constitution + +`atelier` 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. + +## 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/atelier-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. diff --git a/HARNESS.md b/HARNESS.md new file mode 100644 index 0000000..f55e6b5 --- /dev/null +++ b/HARNESS.md @@ -0,0 +1,163 @@ +# The atelier harness — human's guide + +Scaffolding that makes an agent build `atelier` 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 +constitution (always loaded). `docs/` is what the agent needs to build the +engine — anything in it may be pulled into context on demand. `ideas/` and `reference/` sit outside +`docs/` deliberately: storage, opened only when you name a file, swept by nothing. +`.claude/` holds the feature-loop skill and six commands. `scripts/` holds the gate. + +## How you use it + +You ask for a feature. The skill fires by itself and runs +`Clarify → Plan → Implement → Verify → Document → Report`. + +This runs for casual requests too — the skill fires on "the default-language files do not need the +`.en` part" exactly as it does on "add tag pages", with a one-line plan instead of a full one. A short +sentence is not a small change: that one renames content on disk, makes a suffix optional in code, and +contradicts every doc calling it required. The agent walks every surface — code, fixture sites under +`testdata/`, harness docs — and, because it cannot reach your site root, writes down the migration you +need to run there rather than pretending to have done it. + +1. **You:** "Add tag pages." +2. **Agent:** up to three clarifying questions, each with a default. Answer or ignore. +3. **Agent:** a short plan — goal, primitive, success criteria, files, ±LOC, deps, earn-it check, + and what it is deliberately *not* doing. +4. **You:** "go", or edit the plan. "Just do it" skips the gate on small changes. +5. **Agent:** implements, runs `./scripts/verify.sh`, shows real output, updates docs, reports. + +Occasional maintenance, by you: + +- "Park this" → the agent writes `ideas/.md`, resumable cold, and indexes it. Name the file + later to pick the thread up; it reads these only when named. +- `/audit` every ~5 features — finds abstractions that never earned their keep. +- `/invariants` at arc boundaries, before a freeze, before the first deploy — checks the nine + architecture invariants against the code, which no grep can do. + +**Conflicts come back to you.** If a request contradicts a recorded decision — an ADR, an invariant, +the permalink shape, the untrusted boundary, a hard budget — the agent stops, quotes the line, and +gives you two paths: comply, or change the decision on purpose. It resolves only what is genuinely +unambiguous, such as a doc that has fallen behind the code. Repeating the request is a valid answer +and is taken as your decision; what it will not do is quietly reverse a choice you made earlier, or +split the difference into a compromise nobody picked. +- `/refresh-docs` after a burst of work — reconciles docs with reality, reports drift. +- `/verify`, `/adr`, `/leaf ` as needed. + +**Three surfaces, one of them yours to edit here.** Engine source lives in this repo; content lives in +the site root (ADR-0011); the theme is a third surface with its own owner (ADR-0023). A request that spans +engine and theme produces a *contract extension* in `docs/theme-contract.md` plus a note of what the theme +must do — not the theme. `verify.sh` fails if the embedded reference theme changes without the contract doc +changing, because in practice those two drift together — and it fails on a `