main
17
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
30c26bd1ac
|
resolve relative links against the disk, serve them as addresses
Item 2 of the order of work. An author writes `../day-01.en.md` — the path an editor preview resolves — and the engine emits `/posts/day-01/`. The larger effect is durability. Resolution goes through key → route, and a slug moves the route while never moving the key (ADR-0035), so a relative link survives a rename that a hand-written /posts/a-better-name/ does not. The demo proves it: `../renamed-thing.en.md` renders as href="/posts/a-better-name/" — the author wrote the filename and got the slugged address. This is the engine altering authored markup, which ADR-0045 polices, so the test that matters is what it declines to touch. Fourteen cases must survive exactly as written: an absolute URL, a scheme-relative URL, mailto:, tel:, a root-relative path, a bare fragment, a bare query, a name climbing out of content/, and every relative path whose extension is not .md. That last line is what keeps cover.jpg working — a bundle's assets already resolve because its URL mirrors its directory, so rewriting them would break what works. Nine rewrite cases sit beside them. Key derivation goes through content.KeyFromName, exported for this: the language-suffix rule is the part that would drift between two copies, so it lives in one place while the five lines of joining are duplicated in check. khosra check now reports a relative .md link resolving to no bundle, as fatal — verified by mistyping one and watching exit 1. Only the .md form: an extensionless relative path may be an asset, and a checker that calls a working link broken gets ignored wholesale. Two debts this change paid rather than deferred. render.go reached the file-length advisory, so theme parsing moved to theme.go — 414 and 105 lines, one topic each, since parsing runs per rebuild and rendering runs per request. Not a _helpers.go shard. And the demo's coverage test bound its renderer with a *copy* of the rebuilder's wiring, so it missed this feature entirely while the real binary served it correctly. Navigation had already drifted the same way. Both now call one bind(), which is exactly what ADR-0072 was written about — and the test failing is the only reason the copy was found. Extensions 7 → 8. Core 3020 → 3049 of 3400: the seam is ~20 lines, the feature is in ext where it belongs. 18 files. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c3125e7ad2
|
log every request, and make error mean something again
Item 1 of the order of work, and two gaps rather than one polish item. There was no access log. Nothing recorded that a request happened, and the Dockerfile ships the binary alone with the site root mounted (ADR-0010), so a bare deployment produced none at all — which mattered because offline log analysis is this project's answer to analytics: no counter on the read path, no third-party script. web.Logged now writes one Info line per request with method, path, status, bytes and duration. And slog was never configured. conventions.md makes importing `log` instead of log/slog a hard failure while nothing ever set a level, a handler or a format. Two flags now do, rejected at startup if unusable, because a logger quietly less verbose than asked for hides exactly the lines somebody changed the flag to see. JSON is the half that matters: it is what makes a log parseable. The re-levelling was the larger half. Counts were 38 error, 7 warn, 4 info, 0 debug; they are now 7, 40, 6, 0. Almost every one of those errors was ADR-0029's "logged, not fatal" category — a misspelled directive, an asset path climbing out of its bundle, an unreadable picture — where the engine coped and the reader still got a good page. Error used as "somebody should see this" means an operator cannot tell a broken build from a typo. The seven that remain are the five requests that answer 500 and the two inside fatal. A successful rebuild now says so. It swapped silently before, so an operator could see a failed rebuild and never a successful one, which leaves the failures with nothing to be read against. No wrapper package: log/slog is the module, and a layer over it would be an abstraction with one caller. Duration comes from content.Now, since the clock is confined to one file and verify.sh enforces it by filename. The recorder does not forward Flusher or ReaderFrom — nothing here streams, so the cost is one io.Copy fast path on static files, and implementing interfaces no caller needs is the speculation rule 6 forbids. Two things this change owed and paid. content.go's comment still said content problems were "logged at error level", which the re-levelling made false. And the new flags pushed runServe past the function-length advisory, so the site-opening block became opened() — a warning that fires on correct code gets acted on, not tolerated, and that is the whole reason the advisory exists. Evidence, demo site, JSON: rebuilt bundles=31, then serving, then one request line each for a 200, a 404 and robots.txt with real byte counts and durations. At -log-level warn, request lines disappear. An invalid level exits with the reason. 12 files. Core 2913 → 2959 of 3400. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
69a7eb4733
|
move robots and sitemap out of core, and raise the ceiling on purpose
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>
|
||
|
|
b313b729d9
|
review 130 feature ideas, and delete the lists that held them
One idea at a time: definition, necessity, priority, layer, recommendation. Every row is now shipped, scheduled, parked with a trigger, or dropped with a reason — so both tracking files are gone rather than left as a parallel backlog. Roughly a third already shipped. A quarter needed only a theme fragment or CSS, including several the list assumed were features: arbitrary `theme.*` frontmatter attributes already reach templates through Extra (verified), native lazy loading already ships and is deliberately absent on lead figures, and in-page timelines are a theme-defined container. roadmap.md gains an "Order of work" — nine bodies of work in sequence, because the sections this review added had accumulated without one. Item 0 is a decision, not work: core sits at ~2965 of 3000 and logging alone wants the remainder, so discover.go moves out *and* the ceiling rises with an ADR. feed.go and web/extras.go cannot move; they are resolver cases, and three parked items now wait on that same seam decision. Two ADRs came out of it. ADR-0083: the dependency rule described a practice it forbade. "Stdlib first, always" and "usually 40 lines of stdlib" would, read literally, have argued against all five modules here — every one is a format or algorithm somebody else specified, and their allowlist comments say so. Surfaced when this agent proposed hand-rolling Reed–Solomon for QR encoding. The test is now whether you can verify it, not whether you can write it, with compute-versus-hold as the counterweight. conventions.md and allowed-deps.txt contradicted it and were realigned. ADR-0084: the Effect primitive said every Effect is "idempotent and re-runnable from scratch" — true of derivatives, false of anything outbound, since a sent message cannot be recalled. Split into artifact-producing and outbound, the latter idempotent only against a delivery ledger, at-least-once with a dedupe key, and ledgers in a -state directory distinct from the disposable cache. Asked for as forward-looking design so webmentions need no redesign; recorded as a shape rather than built, the way extensions.md records the Extension struct. Also fixed a STATUS claiming Effects were "not buildable yet" when the derivative pass has inhabited them since ADR-0042. Findings that were defects rather than ideas: the Atom feed emits no <author>, which RFC 4287 requires. HTML comments in content are published verbatim — verified — so anything commented out is already public, and a check warning is scheduled rather than the engine deleting authored bytes. `Page` has no Date, so a theme cannot mark up dt-published or show an article's own date. A frontmatter naming rule after this agent proposed `archive` alongside the accepted `archived`: no near-homograph keys, and name the benefit rather than the vendor. reference/microformats-and-indieweb.md is new and is the reference asked for — microformats2 properties and IndieWeb rel values mapped to where a theme puts them, with spec URLs, marked scheduled. rel="me" is the highest-value lowest-cost item in that space and needs no endpoint at all. Four things this agent got wrong and the human caught: asserting "the engine never fetches" from a sentence scoped to the content repo, twice; inventing a data-sovereignty argument for an item he had filed as "a cool geeky thing", when the raw Markdown in his git already is the sovereignty; the archive naming; and claiming microformats could be fully implemented today. 15 files, +556/-385. No code changed. Nothing was scheduled that this agent could not name a consumer for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c6c3f0aa3f
|
record the core-budget squeeze and why only part of it can move out
Held on the human's call: the code move waits for the idea review, since how much
core room is needed depends on what that review places in core.
Worth recording rather than rediscovering. Core sits at ~35 lines of headroom with
at least four core-bound items before the Arc 2 freeze — the View layer, the Stage
pipeline (roadmap.md Arc 2 item 2, which I had been treating as optional), a
minimal settings cascade the Views counter couples to view selection, and declared
content types.
The relief I recommended was overstated and is corrected here. discover.go (~71
lines) can leave core once ADR-0081's seam passes the live index. feed.go and
web/extras.go cannot: 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 — the limit that ADR's own "revisit if" line predicted and I did not check
before recommending ~196 lines of relief.
So the open choice is extending the seam to resolver participation versus raising
CORE_LOC_MAX with an ADR, and it is the review's to settle.
2 files. No rule or threshold moved.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
949ce34224
|
give pending work four owners instead of six scattered lists
Ideas were spread across six places with real duplication and one list that was about to be lost. Consolidated by merging the duplication, not the categories. The 130-item feature-list triage lived in .scratch/, which is gitignored — 130 verified verdicts a scratchpad cleanup would have deleted. It is now ideas/feature-list-triage.md with the header the ideas gates require, indexed, and out of agent context by default like everything there. exploration.md's Verdicts table was empty and designed for exactly this; it now carries one row per body of work with a pointer, rather than 130 rows nobody could skim. Deferred triggers had no single owner: continue.md restated them and drifted, still listing the extension registry as pending after ADR-0081 built it. deferred-decisions.md now holds the only trigger table, covering the two it already described plus math, corrections, write routes and the registry's remaining fields. continue.md links to it. Two rows record couplings worth not rediscovering: the View layer may pull a minimal settings cascade in with it, because the Views counter says selection resolves *through* the cascade; and ADR-0081's seam mounts GET only, so Arc 3's first write endpoint extends it. Corrections per post is dropped in its cheap form and parked in its real one. A hand-maintained frontmatter list was rejected for an authoring reason rather than a technical one — a half-remembered list of corrections misleads where none would not, because two entries imply those were the only two. Deriving it from repository history is the shape worth waiting for, and the file records what must be settled first: whether the engine may read local git history at all, that it needs a dependency or a subprocess, and that not every commit is a correction. Not merged, on purpose: latent items stay in state.md because they describe shipped code and are gated against .go commits; arcs stay in roadmap.md because they are sequencing decisions. One file would have flattened four levels of authority and made pending work cheap to load, which is the opposite of why ideas/ sits outside harness/ at all. harness/README.md now says which list owns what, in both the topic table and the single-source table. 7 files. No rule or threshold moved. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
16fde5ee68
|
lift the skill's question cap, which CLAUDE.md lifted and it did not
SKILL.md said "maximum three" clarifying questions while CLAUDE.md §4 said
"there is no cap" — lifted in
|
||
|
|
078ec8eedf
|
check bare paths in scripts/, and record why one file has no .sh
Two answers to "add .sh to sh files". Exactly one file lacks the suffix: scripts/hooks/pre-commit. git locates a hook by exact filename, so renaming it would leave a gate that looks present and does nothing — verified in a throwaway repository, where hooks/pre-commit printed and hooks/pre-commit.sh was ignored while the commit succeeded regardless. The name belongs to git, so the file keeps it and conventions.md now states the exception rather than leaving it as an inconsistency someone will try to tidy again. The extension was never the defect anyway. 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 the docs/ rename and surfaced as a fatal inside a commit that otherwise succeeded. That gate now also reads paths in scripts/ unquoted, which is the check that would have caught it. Proven both ways: restoring the exact bug fails the gate with "reference to a path that does not exist: docs/surface.md", and a working tree passes. The first attempt did not catch it — docs had been dropped from the alternation because the directory no longer exists, which is precisely the class of stale reference worth failing on, so docs is in the bare pattern on purpose. Backslashes are stripped before comparing, so a path written as a regex — \.claude/settings\.json — is checked as the file it means rather than flagged as the file it is not. Prose is still checked only inside backticks: a sentence saying "under harness/" makes a point no filesystem can verify, while a script naming a path either has it right or is broken. One gate label changed with it. `pass "harness/HARNESS.md coupling"` read fine while the directory was docs/ and now parses as a filename, which the new check duly flagged; it is "harness and HARNESS.md move together". No doc cited the old label. 4 files. No rule or threshold moved — one gate widened, one convention written down. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
07cf655d09
|
fix the pre-commit hook's path, missed by the rename
ADR-0082 moved docs/ to harness/ and left scripts/hooks/pre-commit staging docs/surface.md. The hook runs on every commit, so the very commit that did the rename hit it: `git add docs/surface.md` failed with a fatal, the commit otherwise succeeded, and surface.md went in only because it had been staged by hand beforehand. The sweep missed it because the file has no extension and the grep that found every other reference was filtered by --include='*.sh'. HARNESS.md now says so where the hook is described, since the next rename will make the same mistake for the same reason. Verified by this commit: the hook ran, regenerated harness/surface.md, and staged it without error. 2 files. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
ec6e9838f0
|
rename docs/ to harness/, and reserve docs/ for the reader
docs/content-model.md opens with "Engine specification". It is also where the rule lives that a leading underscore makes a file unaddressable — and the human who owns this site did not know that rule, because nothing in this repository is addressed to an author. Twelve documents named docs/ while being exclusively about building the parser is a signpost pointing at the wrong room. Naming the directory for its audience makes the gap visible instead of hiding it. docs/ is now reserved and deliberately absent: an empty docs/ is an honest statement that end-user documentation does not exist, where docs/ full of parser specs was a claim that it did. HARNESS.md stays at the root. Root holds the three entry points — README.md for a human, CLAUDE.md for an agent, HARNESS.md for whoever maintains the machine — and harness/README.md is the map of the directory, so moving the guide inside would have collided with it for nothing. Mechanical and wide: 100 path references across 24 files. Every verify.sh gate that names a doc by path, the directory lists the dangling-path and ADR-number gates scan, surface.sh's output target, the Makefile, CLAUDE.md's read order, the skill, four commands, and two Go package comments. A first pass with a shell loop silently edited only four files and the rest still said docs/; the fix was to write the file list out and check the remaining count was zero rather than trust the loop's exit status. No rule, threshold, gate or obligation moved — this is a rename, and the gates demonstrated it twice: they stayed green on the new paths, and the ADR-number gate caught ADR-0082 before the entry existed. Deferred, both on the human's call: the end-user documentation site itself, which wants its own decision about where it lives and whether its claims are gated; and moving examples/ under docs/, since demo-site is a live site root that verify.sh, the coverage test and make demo all point at, and moving it would couple a rename to a design nobody has made. 31 files, +146/-106. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9349c54d2e
|
let a feature own a route, and serve the site's own files at exact paths
Addresses like /.well-known/security.txt are fixed by somebody else's spec. None is a bundle, none belongs under /static/, and core had no way to serve one. This is the trigger the extension registry has been held for, in those words: ADR-0042 called core's generic derived-file route "the seam to revisit when a second feature wants output of its own", and state.md's counter note said to build the registry "when a feature wants a route". Raw passthrough is that feature, so the seam is built rather than worked around. Only Routes, not the seven-field Extension struct extensions.md describes. Five of the other six fields have no implementor and building them would be the speculation rule 6 forbids. It also kept the change inside the core budget, which had 65 lines left: the seam is ~30 core lines and the feature's own code lands in internal/ext/, where there is room. Core is 2965/3000. A feature returns map[string]http.Handler; core mounts each as an exact pattern and learns nothing about who owns it. A path core already answers is skipped with a warning, not overridden — http.ServeMux panics on a duplicate pattern, so a site shipping root/robots.txt would otherwise take the server down at startup. Verified: server alive, engine keeps /robots.txt, warning logged, zero panics. Templating is opt-in by filename. A .tmpl suffix is stripped from the URL and the file is rendered with text/template — never html/template, which would turn an ampersand in a contact address into & and a JSON quote into ". Opt-in by name rather than by sniffing the type, because a key or a signature may contain anything and a pass choosing for itself which files to rewrite would eventually eat one. The data is the site's own declarations and nothing more, which is the point: a security.txt naming its canonical URL should not repeat what site.yaml already says. Headers come from root/_headers.yaml, exact paths only. Globs are a second-use feature and the concrete need is a handful of .well-known names. The manifest is not served, by the leading-underscore rule that already means "not addressable" everywhere else — no special case was added for it. A manifest that will not parse is logged and ignored; the files still serve. Found while counting: the Extensions row read 4 while five packages existed. notation landed in ADR-0061/0062 and was never counted, though the prose beside the number already named all five. Corrected to 6. That is the latent item about counters having no mechanical check, demonstrating itself. Not done, and logged as latent: khosra check cannot report a root/ file shadowing an engine path, because verify.sh fails a feature that imports a sibling and the reserved paths live in passthrough. The startup warning fires on every boot, which is louder than a check finding. Evidence against the demo with a fresh binary: /pubkey answers with its declared text/plain despite having no extension; /.well-known/security.txt answers with Canonical filled from site.yaml's base, plus the declared CORS header; /humans.txt gets a derived type; /_headers.yaml is 404; / and a bundle page are untouched. Eight unit tests cover layout, absence, interpolation, non-escaping, declared and derived headers, a broken template, and a broken manifest. 24 files, +514/-46. Extensions 4 (miscounted) → 6. Routing cases unmoved: exact paths are mux entries, not resolver cases. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
64e53f28c8
|
give a page the assets its own content asked for, and write down the rule
Two decisions and one mechanism. The human wants demos, games and runnable
embeds to carry real CSS and JS while every ordinary page stays scriptless, and
wants adding an asset to be theme work rather than a rebuild.
The mechanism reuses what already had that property. A theme defines
`assets:<name>` beside its other fragments; shortcodes record their own name as
they are opened; after conversion the engine renders each matching fragment once
into Page.Assets. So a gallery calling one shortcode forty times carries its
stylesheet once, and a page that called nothing carries nothing. Frontmatter
`use:` reaches the same fragments without a call.
Considered and rejected: templates/assets.yaml, which reads more declaratively
and buys a parser, a contract shape and a rebuild for conditional markup; and a
table in Go mapping shortcode to files, which would hardcode exactly what was
deliberately made data-driven.
Collection is parse-phase, so no transform counter moves — goldmark's extender
list is already the ordered pipeline for parse work, which state.md's counter
says in its "does not count" column.
Separately, styles/scripts are lifted at last. They sat in content-model.md's
table unread, and the theme contract listed them under "what the engine
provides", which was aspirational rather than true. Both are bundle-relative: a
name with .. or a leading / is dropped and logged, the refusal ::include and a
code block's file= already make. The engine builds the URLs because a theme must
not construct an address.
ADR-0080 writes the antifeature list down, with its single exception inside it.
An antifeature nobody recorded does not bind anything, and each of these dies to
one reasonable-looking request at a time. The exception is author-invoked and
cannot fire by accident.
The reference theme emits the stylesheets and no script element at all. That was
the human's correction to a first attempt which had page.html emitting the tag
and verify.sh narrowed to permit it — narrowing the gate to fit the code was
backwards, and the narrowing was also wrong, passing a probe with a hardcoded src
because it filtered whole lines and every line carries {{define}}. verify.sh is
untouched. examples/demo-site redefines the head block instead, so the JavaScript
half is demonstrated by a site rather than built into the binary, which is a
better demonstration and a stronger property.
Evidence, against the demo site with a freshly built binary: the sandbox page
carries its own css and js at bundle-relative URLs; colophon calls ::tally twice
and carries tally.css once with zero scripts; about calls it never and carries
neither; listings unaffected. Plus a table test for the escape refusal, which
until now had only the running server behind it.
19 files, +355/-86. No counter moves. Demo is 31 bundles.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
69f43e5791
|
enable task lists, superseding the decision that excluded them
ADR-0058 kept task lists out as "a note-taking affordance, not a publishing one". That reasoning measured the wrong axis: a checklist inside a published technical piece — setup steps, a runbook, a what-I-tried list — is publishing, and nothing else in the dialect expresses "this item is done" without the author hand-writing an entity. ADR-0078 supersedes that half and records why. The half of ADR-0058 that mattered is untouched: extension.GFM stays refused, because the bundle drags linkify in with the tables it is wanted for, and linkify rewrites an author's plain text into markup — the line ADR-0034 draws. One named extension is not a bundle, and wire.go now says so where the temptation to reach for GFM will next appear. ADR-0058's Status line names its successor, so a reader arriving there learns the task-list sentence no longer holds. Same in-place Status annotation the mutability rule allows, Decision text untouched. Checkboxes render disabled: static markup, nothing clickable, nothing stored. A reader with scripting off sees the same page, which is the property the whole theme is built on. goldmark adds no class to the list, so theme.css finds it with :has rather than the engine inventing markup to be styled by. Demo carries the case ADR-0051 requires — a colophon checklist of what this build does and does not do, including the unticked "ship a single byte of JavaScript", which is true of that page and asserted by the test's absent list. No counter moves: state.md's counters are explicit that an upstream extension enabled in the list is dialect, not a feature of this engine — only a package under internal/ext/ counts. 8 files, +55/-10. 1 line of engine code. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4fff6fcd43
|
stop the harness deferring to a marker system it deleted
/refresh-docs step 4 told the agent to check content-model.md's `[spec]` versus
`[live]` markers. The `[spec]`/`[arc1]` system was deleted in
|
||
|
|
be92b4b957
|
correct five claims about behaviour this engine does not have
All five were found by checking content-model.md against the parser rather than by any gate, which is the point: verify.sh catches a dangling path and a missing ADR number, never a sentence that is merely untrue. The Scaffolding section documented `khosra demo <empty dir>` writing a generated site. ADR-0051 deleted that generator eleven commits ago in favour of the tracked site in examples/, so the paragraph described a subcommand main.go never dispatched — and justified itself with "nothing in the engine repository is content", which the committed demo site contradicts. Deleted rather than rewritten: what the demo is belongs to state.md's inventory, and restating it here would have broken the single-source rule to fix a smaller problem. ADR-0050 is where that claim originated and it still read "Status: accepted", so a reader arriving there had no way to learn the generator was gone. Its Status line now names ADR-0051 as superseding that half. This deviates from decisions.md being append-only, so the exception is recorded in the mutability column where the convention lives: a Status line may be annotated in place, the Decision text never. "The parser does not read `slug` yet" was false and contradicted by the frontmatter table twelve lines above it in the same file. Deleted. The Sequences section and the Sequence doc comment both said `draft` is not honoured "because no bundle carries it yet". Two errors: the demo site carries one, and draft is honoured — membership resolves through Lookup, which is the single place ADR-0024 hides unpublished bundles. The doc now says what actually happens, including that prev/next closes over the gap. state.md said draft "lands in Extra unread". It is lifted to Bundle.Draft and deleted from Extra, and content_test.go already asserted it. Evidence: a three-chapter series with a draft middle, served by a freshly built binary. The landing lists First Rain and Third Rain; chapter one's next points at three/, not two/; the draft's own URL is 404. That absence has no test, so it is now a latent item with the reason it is safe by construction. 6 files, +25/-25. No counter moves. surface.md regenerated: the comment gained a line and shifted ten declaration numbers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8f5d479f06
|
stop the handoff outranking the log, and gate the pointers into it
A session picked up work from .scratch/continue.md and offered the human a doc trim that was already in HEAD. The handoff was written at 01:06 saying the trim awaited a yes; the commit containing the trim was amended at 01:12, underneath it. Five defects made that possible, and four of them are the harness's. The read order inverted trust: docs/README.md sent the next session to the handoff "if present, otherwise git log" — consulting the one ungated file instead of the record that cannot lie, which is backwards from every other rule here, where the code beats state.md and generated surface.md beats memory. The handoff is now step 4 of CLAUDE.md's read order, explicitly after the harness and never instead of it, and anything it calls pending is checked with git log -- <path> first. The tick fired too early. "A planned item completed → tick it in continue.md" sat in the Document step, so it recorded the plan's version of events while the commit could still move; an amend, a squash or a late fix moves it. Reconciling now happens after the commit exists, and a completed item is deleted rather than ticked, because a ticked item still reads as an item. HARNESS.md authorised the drift outright — it said the file holds "what is done", which is exactly what continue.md's own header promises it never records. It is now described as what it is: a temporary handoff, uncommitted, ungated, discardable, holding the continuation point and the carried findings. state.md still pointed at .scratch/build-queue.md, replaced two commits earlier. The dangling-path gate missed it because .scratch was absent from its alternation, so no pointer into the handoff directory was ever checked. It is included now, guarded on the directory existing — a fresh clone has no handoff and must stay green. Both directions were run: present with a stale pointer fails, absent with docs naming it passes. The gate then flagged its own explanatory comment, which is why that one path is written without backticks. No gate can compare an uncommitted file against anything, so the file's job is narrowed instead of enforced, and HARNESS.md now says which mechanism stands in for the missing gate rather than implying one exists. 6 files, +47/-9. No rule, threshold or counter moved. CLAUDE.md 138 → 142 of 150. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
02268f9121
|
init
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |