Segregate Luria's own project memory into meta/ - #4
Conversation
This repo is two things: a package (the CLI plus the `template/` scaffold `luria init` copies) and a project that runs its own machinery on its own record. Both were served out of `docs/`, so a visitor evaluating Luria met twenty decisions about its internals before reaching the four pages that are the product. Measured: the scaffold is 16 files and 28.6 KB, the record was ~300 KB. The record moves to `meta/`, a sibling of `template/`. `docs/` is what the package documents; `meta/` is the evidence, labelled as such in its own README. Nothing about `luria init` changes — it never saw the record — and an adopting project still keeps everything under `docs/`. `paths.docs` now accepts a list of roots, each indexed by its own README. Per root rather than repo-wide: one index spanning both would put each audience's material in the other's way, which is what the split fixes. Two hardcoded paths surfaced, both of which would have failed open: - `badges.region()` defaulted its link to a literal `docs/decisions/…`, restating a configured value. It reads `current().index` now. - The reference lint reached the decisions only because they sat under `cfg.docs`. A scheme directory is now scanned on its own account, so a scheme configured anywhere else can't drop out of the hyperlink lint. - A journal's source directory joins the index check's exemption list, like a scheme's. Every link inside the record survived the move: the whole thing moved together, so the relative shapes are unchanged. Only the 94 crossing the boundary were rewritten, plus one from README.stub back out to the doctrine. Rejected: a separate repo submoduled here (voids ADR-009's dogfooding claim, leaves the corpus-dependent tests without a corpus, and recursive clones pull it anyway), and inverting the layout to hoist `template/` to the root (a much larger diff for the same result). ADR-021 records the decision. Closes #3 only in part — the distribution mechanism and the bare local codes in `template/` remain open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014H7APrKgWA23SVZMNF8H86
|
This was a fruitful exercise and I'm glad we proved out that refactoring the project structure like this was fairly painless. I think maybe I was thinking about it wrong and so I'm going to just think outloud for a moment to try to better characterize what's bugging me about the current layout. hopefully that will help pin down what problem this PR should be solving and we can then better tailor our intervention. The themes that are popping up in my head right now are discoverability, attention engineering, overwhelm, and hot/cold storage. When I proposed that PR earlier, I had frankly completely forgotten that we were already designing around a template + init command. We've already identified that we want to make it clear where the borders are between the downstream deliverable and the scaffolding which supports it. I think maybe where we still don't have well defined boundaries is between the atomic ground truth artifacts and the generated views. changelog.d and devlog.d are both top level directories, exposed as siblings of CHANGELOG.md and docs/. that's two of the three fragments directories exposed as if they're normal user facing locations. the other fragment directories are tucked away in docs/principles/ and docs/decisions, with no .d designating them as fragment containers. Then again, the generated docs/design-principles.md is a sibling of the fragments container it's generated from, docs/principles/. The generated entrypoint to the decisions is docs/decisions/README.md, which looks like it should make sense, except that file is a direct sibling of the fragments it's generated from which all sort on top of it because of the ADR- prefix, so the generated document is completely buried, and also sits right next to its .stub which I think is a strong indicator that it belongs somewhere else. The generated entrypoint to the logbooks is buried all the way at docs/devlog/README.md, with the current devlog at docs/devlog/2026-08.md, which makes it look like an archival journal rather than an intended reading target for the user. I think what's bugging me is less about meta separation than it is about our current structure broadly. We should make it clearer what the user should be reading vs where the user should be writing. There are certain documents we want to draw the user's attention to because they are entrypoints or summaries: we should make sure these artifacts are front-and-center. We should make it easier to land on the read content accidental and harder to land on the write content accidentally. Closing in favor of an exercise which better addresses these presentation issues. |
* The read/write boundary: views in docs/, sources in record/ The layout had grown one decision at a time and mixed sources with views at every level: two of four source containers marked `.d` and two not, a generated document beside its own sources, the decisions index buried under the ADR- sort with its .stub next to it, and README.md meaning "edit me" in one directory and "never edit me" in the next. One boundary, stated as a browsing property: browsing lands you on views; only a link or a deliberate act of filing lands you in sources. docs/ READ — prose + every generated view record/decisions.d/ WRITE — one file per decision record/principles.d/ WRITE — one file per principle record/changelog.d/ WRITE — fragments, collected into /CHANGELOG.md record/devlog.d/ WRITE — journal entries, yyyy/mm/dd/hhmmss.md What you read at docs/X you file at record/X.d — the `.d` suffix inside record/ is the visual affordance that you have crossed into the write domain. CHANGELOG.md stays at the root, where convention puts it. Mechanism: - A scheme's `output` is separate from its source `dir`; the index and tag pages render into the view, with rebase prefixes computed from the actual paths. Unset output = the old collocated layout, byte-identical, so existing projects upgrade without moving anything. - README.stub and tags.yaml live with the sources; a stub's links resolve from where the index renders (a third link base, same ADR-005 rule). - A view directory holds only what the generator wrote — anything else is a lint violation. Generalizes the orphaned-tag-page check to every view dir, and catches a book stranded by a granularity change. - The journal's front page inlines the current book's contents above the shelf, newest first. - Badge link targets derive from config instead of restating a path. - tests/_scheme derives the filing location from the current config; path assertions in tests assert resolution rules, not addresses. ADR-021 records the decision; DP-9 is the value under it. The meta/ segregation attempt (closed unmerged as PR #4) is recorded in ADR-021 as the rejected alternative: it split ours-vs-theirs when the actual confusion was read-vs-write. Closes #3. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014H7APrKgWA23SVZMNF8H86 * Generalize DP-9: spend affordances deliberately, not just at this boundary The first draft stated the read/write boundary as the principle. On review the boundary is a decision (ADR-021); the standing value is the philosophy it applies — structure is read before any text, so affordances are a medium to spend deliberately: on shaping attention (prominence is a budget), on discovery (a rule expressible as a path convention is findable by anyone), and on diagnosis (affordance inconsistency is a smell to read — a rule that moved from the tree into somebody's memory, or a file straddling a boundary the layout stopped expressing). Named the sibling claim from the pilot — SG's 'the affordance is the contract' binds affordances to the truth; this one is about their reach — and kept the two disciplines from the draft: structural beats documentary, and checkable properties walk up DP-5's ladder to a lint. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014H7APrKgWA23SVZMNF8H86 * Cite SG-DP-18 by its composed code; add the url-ok directive The pilot's principle is now cited as [SG-DP-18](hand URL) — the composed code as the label, the explicit URL as the target, because strata-g's principles are sections of one document and no filename convention can construct a URL to one. That hand URL is a projection frozen at writing time: if the remote later adopts a convention or the lockfile learns the real filename, nothing updates it. So the override is now a checked, acknowledged state rather than an invisible one — `remotes.hand_links()` reports any link whose label is a composed foreign code but whose target differs from the constructed URL, as a lint warning (never an error, per ADR-007), and a `url-ok:` directive acknowledges the deliberate ones: <!-- url-ok-block: SG-DP-18 — their principles are sections of one document --> Same parser, shape and scope rules as every other directive; validity is inverted like unresolved-ok (an acknowledgement whose link matches the construction, or covers no link, reports itself as stale); a link quoted in backticks is a specimen and needs nothing. Fired all three ways before trusting: unacknowledged hand link warns, acknowledged is silent, stale acknowledgement is listed. Documented in docs/directives.md and both CLAUDE.md directive tables; 7 new tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014H7APrKgWA23SVZMNF8H86 * ADR-022: url-ok acknowledges foreign codes only The scoping question came up in review — the name reads generic, so why not let it acknowledge a hand-targeted link to a local document too? The answer earned a decision record because it was re-litigated live before the directive was a day old. Three reasons, in decreasing order of force. A warning needs a well-defined wrong, and only the remote namespace has one: a foreign code has exactly one constructed URL, so "differs" is a meaningful binary, while a local code has a family of legitimate targets (source file, index row, document anchor, book anchor) and the same check would flag correct links until acknowledging became reflex. The risk profiles are opposite: a local link fails loudly when its target moves; a remote hand URL fails silently, and the directive exists to compensate for the silent case. And the local want has a better answer the repo already models: register yourself as a remote, as Luria does with LU, and the stable absolute citation becomes a composed code with a construction and a check — with url-ok available exactly where the construction runs out. docs/directives.md and the changelog fragment point at the decision. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014H7APrKgWA23SVZMNF8H86 * Sharpen ADR-022: a hand URL's namespace decides its information content Review poked the weakest sentence — "a defect with a mechanical remedy, not a deliberate state" reads as true of the foreign case too. The revision states the real asymmetry: a local hand URL restates a fact the machinery already owns (the fixer computes the target with zero new information — redundant, remedy is a rewrite), while a foreign hand URL carries a fact the construction has no home for (which document, which anchor). Same-looking artifact, opposite information content. That reframes what the directive is for beyond any one citation: each url-ok reason describes a gap in what remote config can express, and a recurring shape is the cue to grow the config rather than keep annotating. The worked example is exact — strata-g is a Luria project on a legacy layout, its principles a document-rendered scheme, and a remote on current conventions has a constructible DP target (#dp-N anchors exist for exactly this). Per-scheme remote mappings are the named follow-up; recorded in Alternatives as right-direction-wrong-sequencing, since the residue (heading-derived legacy anchors, private repos discovery can't read) needs an acknowledged home either way. When the feature lands, the covered acknowledgements become fixable and the stale-directive report retires them — the loop closes the way it was designed to. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014H7APrKgWA23SVZMNF8H86 --------- Co-authored-by: Claude <noreply@anthropic.com>
Addresses #3. Recorded as ADR-021.
The shape
docs/is four pages now.meta/says in its own README that it is evidence, for contributors, and that nothing in it ships.Why this shape
Measuring first changed what the fix should be. The scaffold
luria initwrites is 16 files / 28.6 KB; the record was ~300 KB. So underpip install && luria initthe deadweight the issue describes is already zero — nothing in the record was ever insidetemplate/. What was actually wrong is that a visitor openingdocs/met twenty decisions about Luria's internals before reaching the four pages that are the product. A navigation problem wearing a token-cost costume, and a directory rename fixes it.Rejected: a separate repo, submoduled. It costs the property that makes the package credible — ADR-009 is a claim that has to keep being true, and several tests are deliberately corpus-dependent against this corpus. Move it out and
luria linthere has nothing real to lint. It also doesn't solve the stated problem:--recursiveis standard enough that the bytes come along anyway.Rejected: inverting the layout to hoist
template/to the root. Same outcome, much larger diff, and the root would then mix the scaffold's files with the package's.Config
paths.docsaccepts a list of roots, each indexed by its own README:Per root rather than repo-wide — one index spanning both would put each audience's material in the other's way, which is the thing the split fixes. The plain string form is unchanged and stays the default, so
template/luria.tomlneeds no edit and a scaffolded project gets nometa/.Two hardcoded paths, both of which would have failed open
badges.region()defaulted its link to the literal"docs/decisions/README.md"— a default restating a configured value. After the move both README badges pointed at nothing. It readscurrent().indexnow.docs/decisions/sat undercfg.docs. A scheme directory is now scanned on its own account, someta/decisions/can't silently drop out of the hyperlink lint — which is exactly the fail-stale polarity DP-3 rules out.What didn't need touching
Every link inside the record survived. The whole thing moved together, so
../design-principles.md#dp-3from an ADR still resolves —docs/decisions → docs/andmeta/decisions → meta/are the same two hops. Only the 94 links crossing the boundary were rewritten, plus one fromREADME.stubback out to the doctrine. The journal entries needed nothing at all:link_baseresolves them from the journal's configured output directory, so changing one line ofluria.tomlmoved all of their link bases at once.Assumption worth overruling if wrong
CHANGELOG.mdandchangelog.d/stay at the repository root. The asymmetry with the devlog is deliberate: a changelog is a claim about the package, which an adopter reads, and a rootCHANGELOG.mdis a universal convention; the devlog is how the package came to be, which only a contributor reads. Sorting by audience puts them in different places even though they look like siblings. Easy to move if you'd rather they travel together.Verification
luria lintclean; 191 tests pass (6 new covering multi-root indexing and scheme scanning).meta/, and a bare reference added undermeta/decisions/. Each produced exactly one violation naming the file.ADR-NNN.mdplaceholder in the decision template.luria init→index→journal new→index→lint, clean, and the result contains nometa/.Still open on #3
The distribution mechanism itself (PyPI, a release tarball of
template/, or both), and the bare local codes intemplate/—template/luria.tomlcites(ADR-005),(ADR-012),(ADR-020)andtemplate/changelog.d/_template.mdcites(ADR-002), which will resolve to an adopting project's decisions rather than to Luria's. Neither is blocked by this change. Happy to take the second one next as a small follow-up with a lint rule.Generated by Claude Code