Item 0 of the roadmap's order of work, and it blocked everything after it: core
sat at 2965 of 3000 while the review scheduled four core-bound items, the first
of which — logging — wanted the whole remainder.
/robots.txt and /sitemap.xml are exact paths somebody else's software asks for by
name. They own no core concept and pass every test the architecture applies to a
feature; they lived in internal/web only because a feature could not own a route
until ADR-0081. internal/ext/discover/ now holds them. Core 2965 → 2913.
The seam gained one parameter to make it possible: a func() *content.Site, since a
sitemap must list what is served now and the index is swapped whole on every
rebuild (ADR-0077). A captured pointer would have frozen the site at startup —
which is the kind of bug that only shows up after a rebuild, in production.
The ceiling rises to 3400 as well as the move, because the move alone could not buy
the room. feed.go and web/extras.go cannot follow discover out: a feed lives at
/{section}/feed.xml and extras under a bundle's own URL, so both are resolver cases
while the seam mounts exact paths only. Raising by the minimum that unblocks one
item produces a ceiling nobody believes, so 3400 fits the View cluster with
headroom. HARNESS.md asks that a raise be read as evidence something belongs in
ext before evidence the number was small; both readings were true, so both actions
were taken.
web no longer reserves those two paths, so a clash between features is wire.go's:
it merges route maps in declaration order, keeps the earlier claim, logs the loser.
Verified — a site shipping root/robots.txt starts, serves the engine's robots.txt,
and logs the passthrough claim, where an unguarded mux.Handle would have panicked.
Evidence: robots.txt and sitemap.xml are byte-identical before and after the move
against the demo site (67 and 2701 bytes, cmp clean), and the sitemap keeps its
application/xml type.
One real cost, recorded in both places rather than hidden. internal/web's
visibility test asserted that a listing, a feed *and* a sitemap all hide
unpublished bundles — one property, one test, because all three share a Query. The
sitemap half moved to the feature instead of a web test importing ext, which would
invert the one-way layering the architecture gate enforces. That property is now
asserted twice, once per package owning a surface.
Three gates caught real mistakes on the way: the staged-tree check found a partial
stage where git rm had staged a deletion while the caller edits were unstaged, the
coupling gates demanded state.md and HARNESS.md, and the nesting advisory rejected
a closure that put the merge loop one level too deep — fixed by making it a plain
function rather than tolerated.
Extensions 6 → 7. Routing cases unmoved: exact paths are mux entries, never
resolver cases, which is what that counter's exclusion column already said.
13 files. Core 2913/3400, ext 2495/3500.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
304 lines
23 KiB
Markdown
304 lines
23 KiB
Markdown
# The khosra harness — human's guide
|
|
|
|
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
|
|
constitution (always loaded). `harness/` is what the agent needs to build the
|
|
engine — anything in it may be pulled into context on demand. It is named for its audience, not its
|
|
format: `docs/` is reserved for documentation written for whoever *uses* khosra, and is absent because
|
|
that does not exist yet (ADR-0082). An empty `docs/` says so honestly; `docs/` full of parser
|
|
specifications said the opposite, and cost its own author a rule he did not know was written down. `ideas/` and `reference/` sit outside
|
|
`harness/` deliberately: storage, opened only when you name a file, swept by nothing.
|
|
`.claude/` holds the feature-loop skill (`skills/feature-loop/`), the commands, and `launch.json`.
|
|
`scripts/` holds the gate. `.scratch/continue.md` is a **temporary** handoff — uncommitted, ungated,
|
|
discardable — holding the continuation point and the findings worth carrying that no doc owns. It is read
|
|
after the harness rather than instead of it (`CLAUDE.md` §1), and never records what landed: that is
|
|
`git log`'s alone, for the reason under "Why the pieces exist".
|
|
|
|
## How you use it
|
|
|
|
You ask for a feature. The skill fires by itself and runs
|
|
`Clarify → Plan → Implement → Verify → Document → Commit → 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:** the clarifying questions whose answers change the code — as many as there are, batched into
|
|
one turn, 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, commits, reports.
|
|
|
|
Occasional maintenance, by you:
|
|
|
|
- "Park this" → the agent writes `ideas/<slug>.md`, resumable cold, and indexes it. Name the file
|
|
later to pick the thread up; it reads these only when named. `ideas/exploration.md` is the same idea for
|
|
engine features nobody has asked for — a catalog, deliberately outside `harness/` so it adds no weight to the
|
|
working set.
|
|
- `/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 <topic>` 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 `harness/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 `<script>` tag in that theme,
|
|
because a reference theme that grows taste stops being a reference (ADR-0026).
|
|
|
|
**The demo is gated like the docs.** `examples/demo-site/` is a real site, tracked as files so you can read it,
|
|
edit it and serve it with `make demo`. It stays true by two gates: a coverage test in `cmd/khosra` serves it
|
|
through the real handler and the real feature list with one case per feature (ADR-0072), and `verify.sh` runs `khosra check` over it. A feature added
|
|
without a case there is a feature the demo does not show, and the build says so (ADR-0051).
|
|
`.claude/launch.json` points the editor's preview at that same `make demo` on `localhost:8080`, so
|
|
"look at it" and "test it" are the one site. It is the only dev server this repo has: the engine serves
|
|
a site root and there is nothing else to run.
|
|
|
|
**The agent names the layer as well as the primitive.** Content on disk, engine, theme, browser — and it builds
|
|
at the outermost layer that can do the job (`CLAUDE.md` §2, `harness/architecture.md`). This exists because a whole
|
|
feature was built at the wrong one: widow prevention as a Markdown transform that inserted a non-breaking space
|
|
into an author's prose. It worked and it had tests; `text-wrap: pretty` does it better with no bytes in the
|
|
content, so the feature was deleted (ADR-0045). If a feature only rearranges how something looks, expect the
|
|
agent to push back toward CSS or a template.
|
|
|
|
**Two ceilings, and raising one is a signature.** `CORE_LOC_MAX` and `EXT_LOC_MAX` exist so that "core
|
|
stops growing, ext rises" (invariant 9) is observable rather than asserted. The values live in
|
|
`scripts/budgets.env`; raising either needs an ADR stating what moved and why, and the ADR log is where that
|
|
history belongs, not here. Read a core raise as evidence something belongs in `internal/ext/` before reading
|
|
it as evidence the number was small. ADR-0085 is the worked example: `discover` left core for
|
|
`internal/ext/` **and** the ceiling rose to 3400, because the move alone freed 57 lines against four
|
|
scheduled core-bound items, and `feed.go` cannot follow it out — a feed is a resolver case, not an exact
|
|
path.
|
|
|
|
**Context is a budget, and parts of it are mechanical.** The limit on this project is how much
|
|
work fits in a session, so `harness/context-economy.md` holds the reading, searching and reporting
|
|
disciplines — read the compressed form first, batch calls, script anything repeatable, never pay twice
|
|
for the same bytes (ADR-0053). These are enforced rather than trusted: `harness/surface.md` is
|
|
regenerated and staged by the pre-commit hook and independently compared by `verify.sh`, so the map of
|
|
the code cannot mislead — an order of magnitude smaller than the source it stands for — and no one has to
|
|
remember to run it;
|
|
`CLAUDE_LOC_MAX` bounds the one file re-sent on every turn;
|
|
and `./scripts/verify.sh --quiet` prints a verdict instead of a line per gate, which is what the
|
|
pre-commit hook now runs. The doc leads with a floor rather than the savings, because every cheap
|
|
failure mode is also a token saving — not opening the owning doc, guessing a signature, reporting
|
|
from a diff — and each of those has already cost this repo a defect.
|
|
|
|
**The agent commits by itself, and tells you the first time.** Work is committed as part of the loop
|
|
rather than on request (ADR-0052) — one commit per revertible unit, never a `push`. In a session where
|
|
you have not asked for that, the first commit is preceded by a line saying so and naming the off
|
|
switch ("stop committing"), because a commit appearing in your repository unannounced is a surprise
|
|
even when it is harmless. The asymmetry is the argument: a commit you did not want is one `git revert`
|
|
away, and work that existed only in the working tree when something went wrong is gone. If you want
|
|
the tree left dirty, say so and the loop stops at Document.
|
|
|
|
Authorship records who wrote the bytes: a commit the agent wrote is authored by it, one you both wrote
|
|
is yours with the agent as co-author, one you wrote alone names only you (`conventions.md` "Git"). You
|
|
remain the committer and the signature in every case — the key says you take responsibility for the
|
|
commit, which is not the same claim as having typed it. `git log --author=Claude` is then a truthful
|
|
answer to how much of this the agent wrote.
|
|
|
|
**What is staged has to build, not just the working tree.** Every other gate looks at your files; this one
|
|
looks at the commit you are about to make, by building a throwaway checkout of the index. It exists because
|
|
staging a subset — a changed signature without its caller — produced a commit that did not compile while
|
|
`verify.sh` reported green, and a broken commit is something `git bisect` trips over for as long as the repo
|
|
lives. It only runs when something is staged.
|
|
|
|
**The injection boundary is a gate now, not a memory.** Raw HTML renders for site-root content, because
|
|
that content is the author's own (ADR-0060) — and the *trusted* half of that sentence is the whole claim.
|
|
`verify.sh` fails unless `html.WithUnsafe()` appears in exactly one file, `internal/render/render.go`. A
|
|
second call site means some other input has been handed the pipeline that trusts its author, which is the
|
|
failure ADR-0003 exists to prevent; when comments arrive they get their own goldmark without it. A feature
|
|
wanting to emit HTML still renders a theme template instead (ADR-0036).
|
|
|
|
## Coming back after a long absence
|
|
|
|
In order, cheapest first:
|
|
|
|
1. `./scripts/verify.sh` — one command, tells you whether the thing is still coherent and whether
|
|
`harness/state.md` has fallen behind the code.
|
|
2. `harness/state.md` — what exists, the earn-it counters, the latent list. This is the only doc that
|
|
describes the present, and `verify.sh` compares its last commit against the last `.go` one rather than
|
|
trusting a sha written by hand (ADR-0057). Its companion `harness/surface.md` is generated:
|
|
every declaration and its line, so you can find your way around without opening anything.
|
|
3. `git log --oneline` — one revertible unit per commit, each body saying *why* (`conventions.md`). This is the
|
|
real map of how the code got here.
|
|
4. `/refresh-docs` — reconciles every doc against the actual code and reports drift, which is exactly the
|
|
question you have after a year.
|
|
5. `harness/decisions.md` — the ADR log, when you hit something and think "why on earth is it like this".
|
|
Each entry names the observation that would overturn it, so you can tell a stale decision from a
|
|
deliberate one.
|
|
6. `harness/toolchain.md` — if something is broken rather than merely unfamiliar. It records what this was
|
|
built against and which agent-tooling contracts it assumes, so a tooling change is diagnosable instead
|
|
of looking like a harness bug.
|
|
|
|
Then `harness/README.md` for whichever topic you are actually here for.
|
|
|
|
## Why the pieces exist
|
|
|
|
**Every counter row says what does *not* count**, and `verify.sh` fails on a row that leaves that column
|
|
empty (ADR-0070). Four counters had to be re-scoped the first time anything tested them, and each fix was a
|
|
sentence about what had been wrongly included — so the sentence is now required up front. A counter that
|
|
cannot name an exclusion is measuring a symptom.
|
|
|
|
**Counters in `harness/state.md`** turn "no abstraction before its second use" into arithmetic. Every
|
|
threshold lives in that one table and nowhere else. The agent cannot argue a pipeline into existence
|
|
one transform early — it writes the next one inline and lets the count force the extraction. Most
|
|
load-bearing mechanism here, and the one with least machine enforcement, which is why `verify.sh`
|
|
fails any `.go` change that does not touch `state.md`. That does not prove the counters are *right*;
|
|
it makes forgetting them impossible, which is the real failure mode.
|
|
|
|
**The dependency allowlist** names modules that are permitted, not required. **Stdlib first means what you
|
|
can verify by reading, not everything you could technically write** (ADR-0083): a format, a grammar or an
|
|
error-correcting code that somebody else specified gets a maintained module, because the work there is
|
|
verification rather than authorship and a subtly wrong implementation fails silently. The counterweight is
|
|
unchanged — a module that *computes* is walkaway-safe since its output is reproducible, a module that
|
|
*holds* data is not, and that is the line rather than size. Every module here is the first case, which is
|
|
why the rule was amended to describe the practice rather than the practice bent to the rule. The gate also fails an
|
|
untidy `go.mod`, because `go get` marks a module indirect until something imports it — and an indirect
|
|
entry is not checked against the allowlist, so an unapproved dependency could sit there unnoticed. `#` starts a comment anywhere on a line, including after an entry — the gate strips those, so an entry may carry the reason it exists. Being listed is permission;
|
|
`DEPS_MAX` counts what `go.mod` actually pulls in. `scripts/allowed-deps.txt` is the list.
|
|
|
|
**Budgets in `scripts/budgets.env`.** Two hard LOC ceilings, core and extensions, plus a dependency
|
|
cap. Exceeding one fails `verify.sh`, so raising it is a deliberate act with an ADR attached rather
|
|
than a drift. The split makes "the core stops growing after Arc 2" measurable: post-freeze the core
|
|
figure holds and only ext rises. File and function length are `_WARN`s, not ceilings — the cheapest
|
|
way to satisfy a hard per-file limit is sharding a coherent file into a `_helpers.go`, which is
|
|
worse code with a greener gate.
|
|
|
|
**The latent list** absorbs the urge to refactor mid-feature: a line in `state.md` instead of a
|
|
drive-by fix. You decide when latent items become features — but an arc cannot close with an
|
|
untriaged one, so the list drains instead of becoming a graveyard.
|
|
|
|
**This repo is engine source only.** The site root — `content/`, `static/`, optional `templates/` —
|
|
lives in its own repository and is passed to the binary with `-site` (ADR-0011), and validating content
|
|
is the engine's own job rather than the gate's. The gate skips `ideas/`, `reference/` and the uncommitted
|
|
`.scratch/`, so exploratory code parked there never has to compile.
|
|
|
|
**The handoff is the one state-describing file no gate can check, so its job is narrowed instead.**
|
|
`state.md`'s currency is compared against the last `.go` commit (ADR-0057), `surface.md` is generated and
|
|
diffed, the theme contract and this file are coupled to what they describe. `.scratch/continue.md` can have
|
|
none of that: it is gitignored, so there is no commit to compare it against. Two things stand in for the
|
|
gate it cannot have — it holds the plan and the carried findings but never what landed, and the read order
|
|
puts `git log` first with the handoff read against it (`harness/README.md`). The one check that *is* mechanical
|
|
now runs: a `.scratch/` path named in a doc must exist, guarded on the directory being present so a fresh
|
|
clone with no handoff still passes. A handoff that recorded what landed was believed for a whole session —
|
|
it said a doc trim awaited a yes while the commit being amended around it had already done the trim.
|
|
|
|
**The architecture gate.** `verify.sh` enforces the layering in `conventions.md` from `go list`
|
|
output: content imports no sibling, render imports neither web nor ext, web imports no ext, nothing
|
|
imports `cmd`. One convenient sibling import is what turns a layered engine into a ball of mud, and
|
|
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/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).
|
|
|
|
**Documentation is gated too.** Every package carries a package comment, and a decision cited in code
|
|
(`// Path shape: ADR-0008.`) must name an ADR that exists. Doc comments on exported identifiers are
|
|
warned rather than failed — presence is checkable, usefulness is not, and a hard gate there would buy
|
|
`// Load loads.` The point
|
|
is a codebase still navigable by `go doc` and `git log` alone, years from now, with no agent available —
|
|
`verify.sh` enforces presence, and only you can enforce that the comment says something.
|
|
|
|
**The style floor fails, it does not warn.** Forbidden package names, `init()`, importing `log`
|
|
instead of `log/slog`, `panic()` outside `cmd/`, and `time.Now()` outside a `clock.go` are stated
|
|
absolutely in `conventions.md`, so they exit non-zero. That last one exists because a Stage that reads
|
|
the clock without an expiry would serve staleness invisibly. A rule enforced as a suggestion teaches the agent to read every rule as one.
|
|
A green run has **no** warnings once code exists. An advisory that fires on correct code gets fixed —
|
|
either the check or the code — because a warning nobody can act on trains you to skim the ones you can.
|
|
Two of these have already been narrowed after firing on code the harness itself mandates.
|
|
|
|
Softer signals stay advisory: `fmt.Errorf` without `%w`, nesting past 4, exported-and-referenced-once,
|
|
and `any` in an **exported** signature — only exported, because ADR-0002 mandates an open page object,
|
|
so unexported code reading frontmatter takes `any` legitimately and forever.
|
|
|
|
**STATUS markers in `harness/architecture.md`** give the target shape *and* what is legal today, so
|
|
the agent can read the endgame without building toward it. They are the only markers of that kind left:
|
|
`content-model.md`'s `[spec]` sections were deleted rather than given a stricter "do not build from this"
|
|
rule, because a marker inside a doc the agent already has open still gets read. Those shapes moved to
|
|
`ideas/`, which nothing sweeps. The asymmetry is deliberate — a primitive's endgame is load-bearing for the
|
|
next decision, an unbuilt disk format is not — so one belongs in a doc that loads and the other in storage.
|
|
|
|
**Six primitives, and everything reduces to one.** `Effect` (ADR-0012) covers work off the request
|
|
path — derivatives, indexes, feed files, outbound syndication — on content change, on a schedule, or on
|
|
demand. The test that keeps it honest: if the thing is computable at render time from the page plus the
|
|
clock, it is a Stage, not an Effect. An "old article" banner is a Stage; it needs no job and can never
|
|
be stale.
|
|
|
|
## The harness maintains itself
|
|
|
|
The harness is code and drifts like code. Same rule as the engine: **a change ships with the docs
|
|
that describe it, in the same change.**
|
|
|
|
**The `CLAUDE.md`/`SKILL.md` redundancy is deliberate and has now drifted twice.** The constitution is
|
|
always loaded and the skill is not, so both state the loop on purpose (`harness/README.md`, compression
|
|
contract) — but a rule changed in one and left in the other is a contradiction the agent obeys at random.
|
|
It happened when the clarifying-question cap was lifted in `CLAUDE.md` and left at "maximum three" in the
|
|
skill, and again when `[spec]` markers were deleted while the skill's conflict table still deferred to
|
|
them. No gate can catch it: both files are prose, and both are internally consistent. Changing a rule in
|
|
either means grepping the other, in that change.
|
|
|
|
- `verify.sh` fails when `CLAUDE.md`, `scripts/` or `.claude/` changes without `HARNESS.md`
|
|
changing — except `.claude/settings.json`, which is Claude Code's permission list rather than anything
|
|
about khosra. That exemption is one path: `scripts/budgets.env` and every other file under `scripts/`
|
|
and `.claude/` stays gated, and khosra's own settings are not exempt from anything. This file is the current description of the machine, not a snapshot of its design.
|
|
- **Invariant 7 is mechanical now.** "Every feature is a leaf" means only `cmd/` may import
|
|
`internal/ext/…`, and `verify.sh` states that as one rule rather than a list of the pairs that happen to
|
|
exist today (ADR-0069) — so a core package added next month cannot quietly import a feature. A feature
|
|
importing its sibling fails the same rule.
|
|
- `verify.sh` fails when a `.go` file changes without `harness/state.md` changing, warns when `state.md`'s last
|
|
commit is older than the last `.go` one — the two together mean the doc ships inside the change, never in a
|
|
commit trailing it (ADR-0057) — and fails when `cmd/` or
|
|
`internal/` code changes without a `_test.go` changing — behaviour ships with a test. A comment-only or
|
|
`gofmt`-only diff is exempt: it ships no behaviour, and failing it would only teach you `--no-verify`.
|
|
- The gate is not optional. `scripts/hooks/pre-commit` runs it on every commit; enable once per clone
|
|
with `git config core.hooksPath scripts/hooks`. `--no-verify` bypasses it, and the commit body should
|
|
say why. The hook also regenerates `harness/surface.md` and stages it, so a generated file is never
|
|
something you have to remember — and it refuses a commit with unstaged `.go` changes, because what it
|
|
generated describes the working tree rather than what you are committing. It carries **no file
|
|
extension** because git locates a hook by exact name — the one exception to "shell scripts end in `.sh`"
|
|
(`conventions.md`), confirmed by renaming it in a throwaway repo and watching a commit succeed with the
|
|
hook silent. A `.sh` suffix there would leave a gate that looks present and does nothing.
|
|
- **Paths inside `scripts/` are checked whether or not they are quoted.** The dangling-path gate had only
|
|
ever matched backticked citations, so `git add docs/surface.md` in the hook — an argument, not a
|
|
citation — survived ADR-0082's rename and surfaced as a `fatal:` inside a commit that otherwise
|
|
succeeded. Backslashes are stripped first, so a path written as a regex is compared as the file it
|
|
means. Prose is still checked only in backticks: a sentence saying "under `harness/`" is making a point
|
|
a filesystem cannot check, while a script naming a path either has it right or is broken.
|
|
- `harness/decisions.md` registers every ADR number ever used, entries and withdrawals alike, so a citation
|
|
can resolve to a decision or to a deferral but never to nothing.
|
|
- `./scripts/verify.sh --list` names every gate that exists. A doc claiming enforcement is checkable
|
|
against it in one command, and `/refresh-docs` checks it in both directions — a claimed gate that is
|
|
missing, and a real gate nothing explains. Asserting a mechanism before it exists is the drift that
|
|
reads as enforcement and is decoration; `CLAUDE.md` rule 8 forbids it.
|
|
- `harness/README.md` carries three tables: topic → doc to **read before asserting a rule**, change →
|
|
doc to **update after making one**, and an authority table naming the one home of every value.
|
|
A new mechanism adds a row; nothing outside a value's home may restate it. A number written twice
|
|
eventually disagrees with itself.
|
|
|
|
## Open
|
|
|
|
- No ADR gate blocks Arc 1. One question remains in `harness/state.md`: the language suffix on the first
|
|
content file.
|
|
- `go.mod` does not exist yet — `go mod init` belongs to the first feature, and the module path is
|
|
still unchosen.
|
|
- `.claude/settings.json` denies reading `./.env*`, but secrets a parent directory's `.envrc`
|
|
exports are in every command's environment regardless. The deny rule is narrower than it looks.
|