Skip to content

Segregate Luria's own project memory into meta/ - #4

Closed
dmarx wants to merge 1 commit into
mainfrom
claude/segregate-meta-project-memory
Closed

Segregate Luria's own project memory into meta/#4
dmarx wants to merge 1 commit into
mainfrom
claude/segregate-meta-project-memory

Conversation

@dmarx

@dmarx dmarx commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Addresses #3. Recorded as ADR-021.

The shape

docs/         what the package documents — doctrine, directives, adopting
template/     what `luria init` writes into a project
meta/         Luria's own record: decisions, principles, devlog
luria/        the tool

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 init writes is 16 files / 28.6 KB; the record was ~300 KB. So under pip install && luria init the deadweight the issue describes is already zero — nothing in the record was ever inside template/. What was actually wrong is that a visitor opening docs/ 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 lint here has nothing real to lint. It also doesn't solve the stated problem: --recursive is 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.docs accepts a list of roots, each indexed by its own README:

[luria.paths]
docs = ["docs", "meta"]
decisions = "meta/decisions"

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.toml needs no edit and a scaffolded project gets no meta/.

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 reads current().index now.
  • The reference lint reached the decisions only because docs/decisions/ sat under cfg.docs. A scheme directory is now scanned on its own account, so meta/decisions/ can't silently drop out of the hyperlink lint — which is exactly the fail-stale polarity DP-3 rules out.
  • A journal's source directory joins the index check's exemption list, like a scheme's. It only became visible to that check by moving inside a documentation root.

What didn't need touching

Every link inside the record survived. The whole thing moved together, so ../design-principles.md#dp-3 from an ADR still resolves — docs/decisions → docs/ and meta/decisions → meta/ are the same two hops. Only the 94 links crossing the boundary were rewritten, plus one from README.stub back out to the doctrine. The journal entries needed nothing at all: link_base resolves them from the journal's configured output directory, so changing one line of luria.toml moved all of their link bases at once.

Assumption worth overruling if wrong

CHANGELOG.md and changelog.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 root CHANGELOG.md is 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 lint clean; 191 tests pass (6 new covering multi-root indexing and scheme scanning).
  • Both new guards fired deliberately (DP-6): an unindexed page dropped into meta/, and a bare reference added under meta/decisions/. Each produced exactly one violation naming the file.
  • 600 relative links re-resolved from their render bases, 1 "broken" — the ADR-NNN.md placeholder in the decision template.
  • Fresh scaffold end to end into an empty directory: luria initindexjournal newindexlint, clean, and the result contains no meta/.

Still open on #3

The distribution mechanism itself (PyPI, a release tarball of template/, or both), and the bare local codes in template/template/luria.toml cites (ADR-005), (ADR-012), (ADR-020) and template/changelog.d/_template.md cites (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

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
@dmarx dmarx closed this Aug 4, 2026
@dmarx

dmarx commented Aug 4, 2026

Copy link
Copy Markdown
Owner Author

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.

dmarx added a commit that referenced this pull request Aug 4, 2026
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants