luria 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- luria-0.1.0/.gitignore +5 -0
- luria-0.1.0/LICENSE +21 -0
- luria-0.1.0/PKG-INFO +168 -0
- luria-0.1.0/README.md +138 -0
- luria-0.1.0/docs/README.md +19 -0
- luria-0.1.0/docs/decisions/README.md +67 -0
- luria-0.1.0/docs/devlog/README.md +30 -0
- luria-0.1.0/luria/__init__.py +11 -0
- luria-0.1.0/luria/adr_index.py +400 -0
- luria-0.1.0/luria/adr_pending.py +156 -0
- luria-0.1.0/luria/badges.py +130 -0
- luria-0.1.0/luria/cli.py +67 -0
- luria-0.1.0/luria/collect.py +154 -0
- luria-0.1.0/luria/config.py +560 -0
- luria-0.1.0/luria/directives.py +253 -0
- luria-0.1.0/luria/doc_refs.py +705 -0
- luria-0.1.0/luria/init.py +104 -0
- luria-0.1.0/luria/journal.py +283 -0
- luria-0.1.0/luria/link_refs.py +48 -0
- luria-0.1.0/luria/lint.py +316 -0
- luria-0.1.0/luria/parallel.py +64 -0
- luria-0.1.0/luria/ref_status.py +524 -0
- luria-0.1.0/luria/remotes.py +527 -0
- luria-0.1.0/luria/reports.py +176 -0
- luria-0.1.0/pyproject.toml +55 -0
- luria-0.1.0/template/.github/workflows/docs.yml +54 -0
- luria-0.1.0/template/CLAUDE.md +136 -0
- luria-0.1.0/template/docs/README.md +16 -0
- luria-0.1.0/template/luria.toml +81 -0
- luria-0.1.0/template/record/changelog.d/_template.md +27 -0
- luria-0.1.0/template/record/decisions.d/README.stub +31 -0
- luria-0.1.0/template/record/decisions.d/_template.md +80 -0
- luria-0.1.0/template/record/decisions.d/tags.yaml +14 -0
- luria-0.1.0/template/record/devlog.d/_template.md +34 -0
- luria-0.1.0/template/record/principles.d/DP-001.md +27 -0
- luria-0.1.0/template/record/principles.d/DP-002.md +27 -0
- luria-0.1.0/template/record/principles.d/DP-003.md +22 -0
- luria-0.1.0/template/record/principles.d/DP-004.md +21 -0
- luria-0.1.0/template/record/principles.d/DP-005.md +21 -0
- luria-0.1.0/template/record/principles.d/README.stub +28 -0
- luria-0.1.0/template/record/principles.d/_template.md +80 -0
- luria-0.1.0/tests/_scheme.py +31 -0
- luria-0.1.0/tests/conftest.py +43 -0
- luria-0.1.0/tests/test_adr_index.py +331 -0
- luria-0.1.0/tests/test_adr_pending.py +129 -0
- luria-0.1.0/tests/test_badges.py +122 -0
- luria-0.1.0/tests/test_directives.py +168 -0
- luria-0.1.0/tests/test_doc_refs.py +333 -0
- luria-0.1.0/tests/test_doc_reports.py +79 -0
- luria-0.1.0/tests/test_journal.py +219 -0
- luria-0.1.0/tests/test_lint.py +221 -0
- luria-0.1.0/tests/test_parallel.py +51 -0
- luria-0.1.0/tests/test_ref_status.py +317 -0
- luria-0.1.0/tests/test_remotes.py +410 -0
- luria-0.1.0/tests/test_wikilinks.py +137 -0
luria-0.1.0/.gitignore
ADDED
luria-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 David Marx
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
luria-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: luria
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A project's memory: decisions, principles, changelog and devlog, kept honest by lint
|
|
5
|
+
Project-URL: Homepage, https://github.com/dmarx/luria
|
|
6
|
+
Project-URL: Repository, https://github.com/dmarx/luria
|
|
7
|
+
Project-URL: Issues, https://github.com/dmarx/luria/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/dmarx/luria/blob/main/CHANGELOG.md
|
|
9
|
+
Author: dmarx
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: adr,architecture-decision-records,changelog,devlog,documentation,lint,project-memory
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Documentation
|
|
22
|
+
Classifier: Topic :: Software Development :: Documentation
|
|
23
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
24
|
+
Requires-Python: >=3.11
|
|
25
|
+
Requires-Dist: pyyaml>=6
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: build>=1; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# Luria
|
|
32
|
+
|
|
33
|
+
[](https://github.com/dmarx/luria/actions/workflows/ci.yml)
|
|
34
|
+
[](https://www.python.org/downloads/)
|
|
35
|
+
[](LICENSE)
|
|
36
|
+
<!-- luria:badges -->
|
|
37
|
+
[](docs/decisions/README.md)
|
|
38
|
+
[](docs/decisions/README.md)
|
|
39
|
+
<!-- /luria:badges -->
|
|
40
|
+
|
|
41
|
+
A project's memory: the decisions, the principles, the changelog and the
|
|
42
|
+
narrative log — kept where the next collaborator will find them, and kept honest
|
|
43
|
+
by a lint.
|
|
44
|
+
|
|
45
|
+
Half the collaborators on a modern codebase are stateless. They arrive with no
|
|
46
|
+
memory, read some pages, work, and vanish. Unwritten knowledge is re-derived at
|
|
47
|
+
cost, per session, forever. Luria is the machinery for a record that survives
|
|
48
|
+
that: [project memory](docs/project-memory.md) is the doctrine, and this package
|
|
49
|
+
is what stops it drifting.
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
pip install luria
|
|
53
|
+
luria init --issue-url https://github.com/owner/repo/issues
|
|
54
|
+
luria index && luria lint
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## What it does
|
|
58
|
+
|
|
59
|
+
| command | |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `luria lint` | the only command that can fail: index completeness, frontmatter, a stale generated index, and references that should be links |
|
|
62
|
+
| `luria link --fix` | rewrites bare references as hyperlinks — the same scanner the lint reads, so the failure names its own remedy |
|
|
63
|
+
| `luria index` | regenerates every generated view from frontmatter — the decision index and its per-tag pages, the principles document |
|
|
64
|
+
| `luria ref-status` | which retired decisions are still cited, and where |
|
|
65
|
+
| `luria pending` | which documents are undecided, by age **and** citation count — every scheme |
|
|
66
|
+
| `luria badges` | the README's two counts, derived from the record |
|
|
67
|
+
| `luria reports` | both reports as markdown, for a CI artifact |
|
|
68
|
+
| `luria collect` | assembles fragment directories into their views |
|
|
69
|
+
| `luria remotes` | another project's record: how each foreign reference resolves, and whether it is reachable |
|
|
70
|
+
| `luria init` | scaffolds the record into a project that has none |
|
|
71
|
+
|
|
72
|
+
## The four layers
|
|
73
|
+
|
|
74
|
+
| layer | holds | test |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| design principles | standing **values**, numbered, citable and **versioned** | *have we re-derived this more than once?* |
|
|
77
|
+
| decisions | a **choice among alternatives** at a point in time | *did we reject an alternative, or set a constraint?* |
|
|
78
|
+
| changelog fragments | **what changed**, operator-facing | *would someone running this notice?* |
|
|
79
|
+
| devlog entries | **how it went**, including the wrong theories | *will a future debugger want the narrative?* |
|
|
80
|
+
|
|
81
|
+
Each contribution writes a *fragment* nobody else touches; the shared documents
|
|
82
|
+
are **views**. A file every contribution appends to is a lock, and its conflicts
|
|
83
|
+
carry no information ([DP-2](docs/design-principles.md#dp-2)).
|
|
84
|
+
|
|
85
|
+
The tree says which is which ([DP-9](docs/design-principles.md#dp-9),
|
|
86
|
+
[ADR-021](record/decisions.d/ADR-021.md)): **you read in `docs/`, you file in
|
|
87
|
+
`record/`**. Browsing lands on prose and generated views; the sources sit in
|
|
88
|
+
`record/`'s `.d`-suffixed containers, arrived at by link or on purpose. A view
|
|
89
|
+
directory holds only what the generator wrote — a hand edit there is a lint
|
|
90
|
+
failure, not a plea in a comment.
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
docs/ READ — doctrine + every generated view
|
|
94
|
+
record/decisions.d/ WRITE — one file per decision
|
|
95
|
+
record/principles.d/ WRITE — one file per principle
|
|
96
|
+
record/changelog.d/ WRITE — fragments, collected into /CHANGELOG.md
|
|
97
|
+
record/devlog.d/ WRITE — journal entries, yyyy/mm/dd/hhmmss.md
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Views come in two kinds, and the difference is whether the sources survive
|
|
101
|
+
([ADR-012](record/decisions.d/ADR-012.md)). The changelog is **collected**: its
|
|
102
|
+
fragments are consumed, so the view can only be appended to. The decision index,
|
|
103
|
+
the principles document and the devlog are **generated** — a pure function of
|
|
104
|
+
sources that persist, which is the only reason `luria lint` can tell you one has
|
|
105
|
+
gone stale.
|
|
106
|
+
|
|
107
|
+
The devlog is a **journal**: entries are filed at their authoring timestamp
|
|
108
|
+
(`record/devlog.d/2026/08/03/211926.md`), never deleted, and rendered into one book per
|
|
109
|
+
month with a generated contents list ([ADR-020](record/decisions.d/ADR-020.md)). A
|
|
110
|
+
dated observation was true when it was written and stays true; consuming it
|
|
111
|
+
throws away the only copy of something that never expires.
|
|
112
|
+
|
|
113
|
+
## Citing another project
|
|
114
|
+
|
|
115
|
+
A record extracted from another project cites it constantly, and an unprefixed
|
|
116
|
+
code can't mean both "ours" and "theirs". Register the remote once:
|
|
117
|
+
|
|
118
|
+
```toml
|
|
119
|
+
[luria.remotes.SG]
|
|
120
|
+
repo = "dmarx/strata-g"
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
and `SG-ADR-032` becomes a first-class reference — `luria link --fix` writes the
|
|
124
|
+
URL, `luria lint` demands it, and `luria remotes --check` says whether it still
|
|
125
|
+
resolves. A remote that names its files after their codes needs nothing else; one
|
|
126
|
+
whose filenames carry title slugs gets `luria remotes --refresh` once, which
|
|
127
|
+
discovers them into a committed lockfile so CI and offline checkouts resolve
|
|
128
|
+
identically ([ADR-016](record/decisions.d/ADR-016.md)).
|
|
129
|
+
|
|
130
|
+
A citation can land before its URL does. Luria cites both `SG` (the pilot it was
|
|
131
|
+
extracted from, whose filenames haven't been converted yet) and `LU` (itself,
|
|
132
|
+
which the `luria init` scaffold points at). Naming the document is the durable
|
|
133
|
+
half and works immediately; the URL improves when the remote does
|
|
134
|
+
([ADR-017](record/decisions.d/ADR-017.md)).
|
|
135
|
+
|
|
136
|
+
## Why a lint
|
|
137
|
+
|
|
138
|
+
Because the same audit result keeps recurring: **every documentation surface
|
|
139
|
+
with an executable guard held; every surface governed by prose alone had
|
|
140
|
+
drifted.** Not toward one wrong value — toward *variety*, which is worse,
|
|
141
|
+
because a reader can't learn what the convention is.
|
|
142
|
+
|
|
143
|
+
So the norms that matter get walked up the ladder — prose → convention →
|
|
144
|
+
mechanism → guarantee ([DP-5](docs/design-principles.md#dp-5)) — and this
|
|
145
|
+
package is the last rung.
|
|
146
|
+
|
|
147
|
+
## Provenance
|
|
148
|
+
|
|
149
|
+
Every rule here was earned in
|
|
150
|
+
[strata-g](https://github.com/dmarx/strata-g), where the machinery was built and
|
|
151
|
+
run before it was extracted. The principles and decisions name the incidents
|
|
152
|
+
that produced them, because a rule whose evidence is missing reads as taste, and
|
|
153
|
+
taste gets re-litigated ([ADR-009](record/decisions.d/ADR-009.md)).
|
|
154
|
+
|
|
155
|
+
Luria runs its own machinery on its own record — the decision index and the
|
|
156
|
+
principles document in this repo are both generated by `luria index`, and these
|
|
157
|
+
files are linted by `luria lint`. That is not tidiness: it is how the first
|
|
158
|
+
consumer to hit a bug is this repo.
|
|
159
|
+
|
|
160
|
+
## Docs
|
|
161
|
+
|
|
162
|
+
- [Project memory](docs/project-memory.md) — the doctrine
|
|
163
|
+
- [Design principles](docs/design-principles.md)
|
|
164
|
+
- [Decisions](docs/decisions/README.md)
|
|
165
|
+
- [Comment directives](docs/directives.md) — `inactive-ok`, `unexempt`
|
|
166
|
+
- [Adopting Luria](docs/adopting.md)
|
|
167
|
+
|
|
168
|
+
MIT.
|
luria-0.1.0/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Luria
|
|
2
|
+
|
|
3
|
+
[](https://github.com/dmarx/luria/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.python.org/downloads/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
<!-- luria:badges -->
|
|
7
|
+
[](docs/decisions/README.md)
|
|
8
|
+
[](docs/decisions/README.md)
|
|
9
|
+
<!-- /luria:badges -->
|
|
10
|
+
|
|
11
|
+
A project's memory: the decisions, the principles, the changelog and the
|
|
12
|
+
narrative log — kept where the next collaborator will find them, and kept honest
|
|
13
|
+
by a lint.
|
|
14
|
+
|
|
15
|
+
Half the collaborators on a modern codebase are stateless. They arrive with no
|
|
16
|
+
memory, read some pages, work, and vanish. Unwritten knowledge is re-derived at
|
|
17
|
+
cost, per session, forever. Luria is the machinery for a record that survives
|
|
18
|
+
that: [project memory](docs/project-memory.md) is the doctrine, and this package
|
|
19
|
+
is what stops it drifting.
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
pip install luria
|
|
23
|
+
luria init --issue-url https://github.com/owner/repo/issues
|
|
24
|
+
luria index && luria lint
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## What it does
|
|
28
|
+
|
|
29
|
+
| command | |
|
|
30
|
+
|---|---|
|
|
31
|
+
| `luria lint` | the only command that can fail: index completeness, frontmatter, a stale generated index, and references that should be links |
|
|
32
|
+
| `luria link --fix` | rewrites bare references as hyperlinks — the same scanner the lint reads, so the failure names its own remedy |
|
|
33
|
+
| `luria index` | regenerates every generated view from frontmatter — the decision index and its per-tag pages, the principles document |
|
|
34
|
+
| `luria ref-status` | which retired decisions are still cited, and where |
|
|
35
|
+
| `luria pending` | which documents are undecided, by age **and** citation count — every scheme |
|
|
36
|
+
| `luria badges` | the README's two counts, derived from the record |
|
|
37
|
+
| `luria reports` | both reports as markdown, for a CI artifact |
|
|
38
|
+
| `luria collect` | assembles fragment directories into their views |
|
|
39
|
+
| `luria remotes` | another project's record: how each foreign reference resolves, and whether it is reachable |
|
|
40
|
+
| `luria init` | scaffolds the record into a project that has none |
|
|
41
|
+
|
|
42
|
+
## The four layers
|
|
43
|
+
|
|
44
|
+
| layer | holds | test |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| design principles | standing **values**, numbered, citable and **versioned** | *have we re-derived this more than once?* |
|
|
47
|
+
| decisions | a **choice among alternatives** at a point in time | *did we reject an alternative, or set a constraint?* |
|
|
48
|
+
| changelog fragments | **what changed**, operator-facing | *would someone running this notice?* |
|
|
49
|
+
| devlog entries | **how it went**, including the wrong theories | *will a future debugger want the narrative?* |
|
|
50
|
+
|
|
51
|
+
Each contribution writes a *fragment* nobody else touches; the shared documents
|
|
52
|
+
are **views**. A file every contribution appends to is a lock, and its conflicts
|
|
53
|
+
carry no information ([DP-2](docs/design-principles.md#dp-2)).
|
|
54
|
+
|
|
55
|
+
The tree says which is which ([DP-9](docs/design-principles.md#dp-9),
|
|
56
|
+
[ADR-021](record/decisions.d/ADR-021.md)): **you read in `docs/`, you file in
|
|
57
|
+
`record/`**. Browsing lands on prose and generated views; the sources sit in
|
|
58
|
+
`record/`'s `.d`-suffixed containers, arrived at by link or on purpose. A view
|
|
59
|
+
directory holds only what the generator wrote — a hand edit there is a lint
|
|
60
|
+
failure, not a plea in a comment.
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
docs/ READ — doctrine + every generated view
|
|
64
|
+
record/decisions.d/ WRITE — one file per decision
|
|
65
|
+
record/principles.d/ WRITE — one file per principle
|
|
66
|
+
record/changelog.d/ WRITE — fragments, collected into /CHANGELOG.md
|
|
67
|
+
record/devlog.d/ WRITE — journal entries, yyyy/mm/dd/hhmmss.md
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Views come in two kinds, and the difference is whether the sources survive
|
|
71
|
+
([ADR-012](record/decisions.d/ADR-012.md)). The changelog is **collected**: its
|
|
72
|
+
fragments are consumed, so the view can only be appended to. The decision index,
|
|
73
|
+
the principles document and the devlog are **generated** — a pure function of
|
|
74
|
+
sources that persist, which is the only reason `luria lint` can tell you one has
|
|
75
|
+
gone stale.
|
|
76
|
+
|
|
77
|
+
The devlog is a **journal**: entries are filed at their authoring timestamp
|
|
78
|
+
(`record/devlog.d/2026/08/03/211926.md`), never deleted, and rendered into one book per
|
|
79
|
+
month with a generated contents list ([ADR-020](record/decisions.d/ADR-020.md)). A
|
|
80
|
+
dated observation was true when it was written and stays true; consuming it
|
|
81
|
+
throws away the only copy of something that never expires.
|
|
82
|
+
|
|
83
|
+
## Citing another project
|
|
84
|
+
|
|
85
|
+
A record extracted from another project cites it constantly, and an unprefixed
|
|
86
|
+
code can't mean both "ours" and "theirs". Register the remote once:
|
|
87
|
+
|
|
88
|
+
```toml
|
|
89
|
+
[luria.remotes.SG]
|
|
90
|
+
repo = "dmarx/strata-g"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
and `SG-ADR-032` becomes a first-class reference — `luria link --fix` writes the
|
|
94
|
+
URL, `luria lint` demands it, and `luria remotes --check` says whether it still
|
|
95
|
+
resolves. A remote that names its files after their codes needs nothing else; one
|
|
96
|
+
whose filenames carry title slugs gets `luria remotes --refresh` once, which
|
|
97
|
+
discovers them into a committed lockfile so CI and offline checkouts resolve
|
|
98
|
+
identically ([ADR-016](record/decisions.d/ADR-016.md)).
|
|
99
|
+
|
|
100
|
+
A citation can land before its URL does. Luria cites both `SG` (the pilot it was
|
|
101
|
+
extracted from, whose filenames haven't been converted yet) and `LU` (itself,
|
|
102
|
+
which the `luria init` scaffold points at). Naming the document is the durable
|
|
103
|
+
half and works immediately; the URL improves when the remote does
|
|
104
|
+
([ADR-017](record/decisions.d/ADR-017.md)).
|
|
105
|
+
|
|
106
|
+
## Why a lint
|
|
107
|
+
|
|
108
|
+
Because the same audit result keeps recurring: **every documentation surface
|
|
109
|
+
with an executable guard held; every surface governed by prose alone had
|
|
110
|
+
drifted.** Not toward one wrong value — toward *variety*, which is worse,
|
|
111
|
+
because a reader can't learn what the convention is.
|
|
112
|
+
|
|
113
|
+
So the norms that matter get walked up the ladder — prose → convention →
|
|
114
|
+
mechanism → guarantee ([DP-5](docs/design-principles.md#dp-5)) — and this
|
|
115
|
+
package is the last rung.
|
|
116
|
+
|
|
117
|
+
## Provenance
|
|
118
|
+
|
|
119
|
+
Every rule here was earned in
|
|
120
|
+
[strata-g](https://github.com/dmarx/strata-g), where the machinery was built and
|
|
121
|
+
run before it was extracted. The principles and decisions name the incidents
|
|
122
|
+
that produced them, because a rule whose evidence is missing reads as taste, and
|
|
123
|
+
taste gets re-litigated ([ADR-009](record/decisions.d/ADR-009.md)).
|
|
124
|
+
|
|
125
|
+
Luria runs its own machinery on its own record — the decision index and the
|
|
126
|
+
principles document in this repo are both generated by `luria index`, and these
|
|
127
|
+
files are linted by `luria lint`. That is not tidiness: it is how the first
|
|
128
|
+
consumer to hit a bug is this repo.
|
|
129
|
+
|
|
130
|
+
## Docs
|
|
131
|
+
|
|
132
|
+
- [Project memory](docs/project-memory.md) — the doctrine
|
|
133
|
+
- [Design principles](docs/design-principles.md)
|
|
134
|
+
- [Decisions](docs/decisions/README.md)
|
|
135
|
+
- [Comment directives](docs/directives.md) — `inactive-ok`, `unexempt`
|
|
136
|
+
- [Adopting Luria](docs/adopting.md)
|
|
137
|
+
|
|
138
|
+
MIT.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Luria docs
|
|
2
|
+
|
|
3
|
+
- [Project memory: how a repo thinks](project-memory.md) — the doctrine. Start
|
|
4
|
+
here; this is what an agent file should point at.
|
|
5
|
+
- [Design principles](design-principles.md) — the standing values, numbered and
|
|
6
|
+
citable.
|
|
7
|
+
- [Decisions](decisions/README.md) — the choices, with their alternatives.
|
|
8
|
+
- [Comment directives](directives.md) — the `inactive-ok` / `unexempt`
|
|
9
|
+
vocabulary and its scope rules.
|
|
10
|
+
- [Development log](devlog/README.md) — the narrative: failed approaches, root
|
|
11
|
+
causes, and the traps worth not rediscovering. One book per month, generated
|
|
12
|
+
from the dated entries in `record/devlog.d/`.
|
|
13
|
+
- [Adopting Luria](adopting.md) — putting the record into a project that hasn't
|
|
14
|
+
got one.
|
|
15
|
+
|
|
16
|
+
Everything in this directory is for *reading* — the prose pages are authored,
|
|
17
|
+
and the decisions index, principles document and devlog books are generated.
|
|
18
|
+
Filing happens in [`record/`](../record/), whose `.d`-suffixed containers hold
|
|
19
|
+
the sources ([ADR-021](../record/decisions.d/ADR-021.md)).
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Architecture decision records
|
|
2
|
+
|
|
3
|
+
A decision record is a **choice among alternatives at a point in time**. Write
|
|
4
|
+
one when you rejected an alternative, chose a constraint, or made something a
|
|
5
|
+
future edit could silently violate. Copy [`_template.md`](../../record/decisions.d/_template.md); the
|
|
6
|
+
number is the next one free.
|
|
7
|
+
|
|
8
|
+
Values that decisions *cite* live in
|
|
9
|
+
[design-principles.md](../../docs/design-principles.md) instead. The split, and why
|
|
10
|
+
these are separate files, is [ADR-003](../../record/decisions.d/ADR-003.md).
|
|
11
|
+
|
|
12
|
+
A decision whose **choice** changes is superseded by *adding* a decision and
|
|
13
|
+
flipping the old one's status — not by rewriting its body. A record you can
|
|
14
|
+
quietly rewrite can't be trusted about what you used to think.
|
|
15
|
+
|
|
16
|
+
That is a rule about silence, not about editing. A decision whose choice stands
|
|
17
|
+
but whose *reasoning* was wrong is corrected in place, with a `version` bump and
|
|
18
|
+
a `history:` entry saying what the previous version claimed
|
|
19
|
+
([ADR-019](../../record/decisions.d/ADR-019.md)). Nothing here is frozen; it is only
|
|
20
|
+
un-silently revisable, and this record has worked examples of both
|
|
21
|
+
([project memory §2](../../docs/project-memory.md)).
|
|
22
|
+
|
|
23
|
+
<!-- GENERATED below this line by `luria index` — edit README.stub instead. -->
|
|
24
|
+
|
|
25
|
+
## By tag
|
|
26
|
+
|
|
27
|
+
**[The record](tags/record.md)** (18) — what the four layers hold, and the rules between them:
|
|
28
|
+
[001](../../record/decisions.d/ADR-001.md) · [002](../../record/decisions.d/ADR-002.md) · [003](../../record/decisions.d/ADR-003.md) · [009](../../record/decisions.d/ADR-009.md) · [012](../../record/decisions.d/ADR-012.md) · [013](../../record/decisions.d/ADR-013.md) · [014](../../record/decisions.d/ADR-014.md) · [015](../../record/decisions.d/ADR-015.md) · [016](../../record/decisions.d/ADR-016.md) · [017](../../record/decisions.d/ADR-017.md) · [018](../../record/decisions.d/ADR-018.md) · [019](../../record/decisions.d/ADR-019.md) · [020](../../record/decisions.d/ADR-020.md) · [021](../../record/decisions.d/ADR-021.md) · [022](../../record/decisions.d/ADR-022.md) · [023](../../record/decisions.d/ADR-023.md) · [024](../../record/decisions.d/ADR-024.md) · [025](../../record/decisions.d/ADR-025.md)
|
|
29
|
+
|
|
30
|
+
**[Mechanism](tags/mechanism.md)** (20) — collectors, generators, the lint, the directive vocabulary:
|
|
31
|
+
[002](../../record/decisions.d/ADR-002.md) · [003](../../record/decisions.d/ADR-003.md) · [004](../../record/decisions.d/ADR-004.md) · [005](../../record/decisions.d/ADR-005.md) · [006](../../record/decisions.d/ADR-006.md) · [007](../../record/decisions.d/ADR-007.md) · [008](../../record/decisions.d/ADR-008.md) · [012](../../record/decisions.d/ADR-012.md) · [013](../../record/decisions.d/ADR-013.md) · [014](../../record/decisions.d/ADR-014.md) · [015](../../record/decisions.d/ADR-015.md) · [016](../../record/decisions.d/ADR-016.md) · [018](../../record/decisions.d/ADR-018.md) · [020](../../record/decisions.d/ADR-020.md) · [021](../../record/decisions.d/ADR-021.md) · [023](../../record/decisions.d/ADR-023.md) · [024](../../record/decisions.d/ADR-024.md) · [025](../../record/decisions.d/ADR-025.md) · [026](../../record/decisions.d/ADR-026.md) · [027](../../record/decisions.d/ADR-027.md)
|
|
32
|
+
|
|
33
|
+
**[Process](tags/process.md)** (8) — how the machinery is adopted, run, and reported on:
|
|
34
|
+
[007](../../record/decisions.d/ADR-007.md) · [009](../../record/decisions.d/ADR-009.md) · [010](../../record/decisions.d/ADR-010.md) · [011](../../record/decisions.d/ADR-011.md) · [017](../../record/decisions.d/ADR-017.md) · [019](../../record/decisions.d/ADR-019.md) · [022](../../record/decisions.d/ADR-022.md) · [027](../../record/decisions.d/ADR-027.md)
|
|
35
|
+
|
|
36
|
+
## Chronological
|
|
37
|
+
|
|
38
|
+
| # | Title | Status |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| [ADR-001](../../record/decisions.d/ADR-001.md) v2 | Four layers, each with a one-line test for what belongs in it: design principles hold standing values, decisions hold a choice among alternatives at a point in time, changelog fragments hold what an operator would notice, devlog fragments hold how it went — including the wrong theories, which are the reusable part. Separate files rather than one document because they have different lifecycles: a principle is revised, a decision whose choice changes is superseded rather than rewritten, a fragment is collected and deleted. Rejected: one CHANGELOG holding all four (the layers' different write patterns collide, and the one that gets skipped is always the narrative), and inferring the narrative from git history (commit messages are written to a different audience, and the failed approaches — the expensive part — never appear in them). | Active |
|
|
41
|
+
| [ADR-002](../../record/decisions.d/ADR-002.md) v2 | Every contribution writes a fragment nobody else touches (`changelog.d/<slug>.md`, a journal entry, one decision file); the shared documents are VIEWS assembled on a cadence, never hand-edited. A file every contribution appends to is a lock ([DP-2](../../record/decisions.d/../../docs/design-principles.md#dp-2)) — the conflicts carry no information and each hand-resolution can silently drop someone's work. Collection is deliberately NOT per-merge: the bot commit races in-flight rebases, so it runs weekly or on demand. A stub fragment (only an HTML comment) keeps "every contribution files one" enforceable when the honest answer is "nothing a reader would notice". Rejected: per-merge collection (the race), and asking contributors to hand-merge carefully (contention, not carelessness). | Active |
|
|
42
|
+
| [ADR-003](../../record/decisions.d/ADR-003.md) | A decision's status comes from a closed vocabulary (Active | Proposed | Deferred | Superseded | Rejected, plus an optional em-dash note) and lives in YAML frontmatter alongside tags, date and issue — with the lint enforcing both. The vocabulary is closed because an open one drifts into synonyms: a strata-g audit found ~30 distinct status forms, with `Accepted` and `Active` split 44/46 and meaning the same thing. Frontmatter rather than a prose header because the index is generated from these fields ([ADR-004](../../record/decisions.d/ADR-004.md)), and parsing prose to build it puts a regex between a decision and its own metadata. `Deferred` earns its place: postponement stated is better than postponement faked as `Proposed`. Rejected: free-text status (drifted), and keeping the bold `**Status:**` header alongside frontmatter (two copies to drift, [DP-4](../../record/decisions.d/../../docs/design-principles.md#dp-4)). | Active |
|
|
43
|
+
| [ADR-004](../../record/decisions.d/ADR-004.md) | The decision index and its per-tag pages are GENERATED from each decision's frontmatter; `luria lint` fails on a stale one. Hand-maintaining the index made it both a lock ([DP-2](../../record/decisions.d/../../docs/design-principles.md#dp-2)) and a drifting copy ([DP-3](../../record/decisions.d/../../docs/design-principles.md#dp-3)) — in strata-g, 45 of 155 rows disagreed with their own decision's status, and two were filed under a category their header didn't claim. Prose lives in a `README.stub` with `{categories}`/`{table}` placeholders so humans still edit prose in markdown. Adding a decision is one new file with no shared edit; adding a TAG needs no code change at all. Every rendered field is rebased for the directory it lands in, so a link in a summary works from both the index and the one-level-deeper tag pages. Rejected: a fragment directory like changelog.d (the data is derivable, so generation beats collection — no step to forget). | Active |
|
|
44
|
+
| [ADR-005](../../record/decisions.d/ADR-005.md) | Every document code, design principle and issue number cited in prose is a hyperlink, and `luria lint` fails on one that isn't; `luria link --fix` writes exactly the links the lint demands, from the same scanner, so the linter can never demand a rewrite the fixer wouldn't make ([DP-4](../../record/decisions.d/../../docs/design-principles.md#dp-4)). In the corpus this was extracted from, 2,246 references across 160 files were bare — and the split between linked and bare was random, which is worse than uniformly bare because a reader can't learn which references are worth clicking. Non-obvious rules, each found by a wrong rewrite: markdown isn't parsed inside a raw HTML block (those get an `<a href>`); backtick pairing is per-paragraph or one stray tick inverts code-vs-prose for a whole file; a low `#N` is ambiguous and stays bare without an explicit cue; fragment links resolve from the file the collector puts them in. Rejected: absolute URLs everywhere (breaks offline and non-GitHub reading), and a prose rule with no lint (the surface that drifted). | Active |
|
|
45
|
+
| [ADR-006](../../record/decisions.d/ADR-006.md) | A referable document family — ADR, RFC, SPEC — is a `[luria.schemes.X]` entry naming a directory and an in-force status, not a hardcoded prefix. The annotation vocabulary follows: the verb is `inactive-ok`, not `adr-ok`, and a code carries its prefix (`ADR-012`, never `012`), with a bare number reported as an error rather than assumed. ADRs are the only scheme this package ships, so the short forms were available and would have baked one knowledge-management system into the vocabulary — the cost of the general form is one prefix per annotation; the cost of the specific one is a rename across every annotation the day a second scheme appears. Also configured: issue URL, code globs, fragment directories, and which files are dated records. Rejected: arguments threaded through every entry point (the second caller forgets one and the linter and fixer diverge — [DP-4](../../record/decisions.d/../../docs/design-principles.md#dp-4)). | Active |
|
|
46
|
+
| [ADR-007](../../record/decisions.d/ADR-007.md) | Two standing reports — `luria ref-status` (references to retired documents) and `luria pending` (undecided decisions by age AND citation count) — are WARNINGS that never fail a build, with one summary line each from `luria lint`. Citing a `Rejected` decision is often exactly right, and a decision can be legitimately open for months, so only a human can judge a row; a guard that's wrong most of the time gets suppressed, and then the times it's right go unread. A deliberate reference is silenced by an `inactive-ok:` comment (line, `-block` or `-file` scoped), counted in the report rather than hidden, and reported when it stops applying. `luria reports` writes both in full for a CI artifact, because a warning with nowhere to be read is a warning nobody reads. Rejected: a lint error (fails CI on correct citations), a bot that auto-updates statuses (invents decisions nobody made), and a ratchet on a checked-in baseline (a file every contribution touches — [DP-2](../../record/decisions.d/../../docs/design-principles.md#dp-2)). | Active |
|
|
47
|
+
| [ADR-008](../../record/decisions.d/ADR-008.md) | Comment directives share one parser, one shape — `<name>[-block|-file]: <args> — <reason>` — and one scope rule with no per-directive defaults: bare is the line and the line below, `-block` the run of non-blank lines it sits in, `-file` the document. `inactive-ok` acknowledges a deliberate reference to a retired document; `unexempt` is the inverse, putting a region the linter skips (a code block) back under it. The directive must OPEN its comment, `# noqa` style, and directive-SHAPED text is never a citation — both rules exist because documenting the syntax kept invoking it, four separate times. A first pass gave `unexempt` an implicit block scope so that a plausible example would parse; that bought one example and cost the rule its predictability, and was removed. Rejected: per-directive default scopes, and a wildcard that would mute a whole file including references added later. | Active |
|
|
48
|
+
| [ADR-009](../../record/decisions.d/ADR-009.md) | Luria's decisions are renumbered from 001 and rewritten as decisions about Luria, each naming its strata-g ancestor as provenance rather than importing the original number. Porting the eight ancestor decisions verbatim would have carried a numbering with gaps that mean nothing here, bodies arguing about a graph tool, and cross-references to decisions that were never extracted. The evidence is kept — every principle and decision names the incident that earned it, because a rule whose evidence is missing reads as taste and gets re-litigated. Luria also runs its own machinery on its own record, so the first consumer to hit a bug is this repo. Rejected: importing numbers verbatim (dangling cross-references), and starting clean with no provenance (throws away the only thing that makes the rules persuasive). | Active |
|
|
49
|
+
| [ADR-010](../../record/decisions.d/ADR-010.md) | Name the package **chester**, after Chesterton's Fence — the parable that a fence should not be removed until you know why it was put there, which is what a decision record is for. Superseded the same day by [ADR-011](../../record/decisions.d/ADR-011.md): the allusion names only the narrowest slice of what the record does (defending existing constraints), it survives being shortened to "chester" only for readers who already know the parable, and it frames the record defensively — as an argument against change — when its actual job is to make change cheap. Kept for the record, and as the corpus's worked example of supersession. | Superseded — by [ADR-011](../../record/decisions.d/ADR-011.md) |
|
|
50
|
+
| [ADR-011](../../record/decisions.d/ADR-011.md) | Name the package **Luria**, after Alexander Luria and his case study *The Mind of a Mnemonist* — a man who could not forget. The name points at the faculty the package supplies (memory that survives the session) rather than at one failure it prevents, and the book carries its own cautionary half: Shereshevsky's total recall was a burden, because a record that never forgets and never abstracts becomes unusable. That tension is the design brief, not a flaw in the allusion. Supersedes [ADR-010](../../record/decisions.d/ADR-010.md) (chester, after Chesterton's Fence), whose allusion named only the narrowest slice of the job, survived shortening only for readers who already knew the parable, and framed the record defensively when its actual job is to make change cheap. | Active |
|
|
51
|
+
| [ADR-012](../../record/decisions.d/ADR-012.md) v2 | Design principles are decomposed into one fragment each, with frontmatter carrying a `version`, the decisions that shaped them (`influenced_by`), and an `origin` note; `docs/design-principles.md` becomes a generated view. This is the same move as the decision index, not a third mechanism — a scheme gains a `render` setting, `index` (a table plus tag pages) or `document` (bodies concatenated). The distinction that matters is not frontmatter but whether the sources survive: a collected view (the changelog) consumes its fragments and can only be appended to; generated views are a pure function of sources that persist, which is the only reason `luria lint` can detect a stale one. Rejected: collecting principles like a changelog (the fragments would be deleted, taking the version history with them, and staleness would become undetectable), and leaving the document hand-maintained (a lock and a drifting projection, [DP-2](../../record/decisions.d/../../docs/design-principles.md) and [DP-3](../../record/decisions.d/../../docs/design-principles.md)). | Active |
|
|
52
|
+
| [ADR-013](../../record/decisions.d/ADR-013.md) | A scheme's documents are named for their code alone — `ADR-013.md`, not `adr-013-a-documents-filename-is-its-code.md` — and the title moves into a `title:` frontmatter field, which the generated views prefer over the body's H1. A slug in the filename is a third copy of the title that no tool reads, that a rename plus every inbound link is required to correct, and that therefore never gets corrected. The body H1 stays, because someone opening the file alone needs a heading, so `luria lint` guards that the two agree — rung 2 of [DP-3](../../record/decisions.d/../../docs/design-principles.md#dp-3), since rung 1 isn't available. Filename decoding, which had accumulated five separate copies, is now `Scheme.documents()` alone, and it reads legacy slug filenames so adoption isn't a rename-everything-first proposition. Rejected: dropping the H1 (unreadable on its own), and enforcing the short filename on adopters (a convention choice, not a defect). | Active |
|
|
53
|
+
| [ADR-014](../../record/decisions.d/ADR-014.md) | A reference whose code names no document here was silently skipped: the fixer can't link it, so the lint — which never demands a rewrite the fixer wouldn't make — said nothing. That silence hid ten stale strata-g numbers left in ported docstrings, one of them a link to a file that does not exist. Such codes are now counted in `luria ref-status`, `luria lint` and the CI report, as a WARNING: a typo, a foreign project's decision and an illustrative example look identical to a scanner and only a human can tell them apart. `unresolved-ok:` retires a deliberate one, at the same three scopes as `inactive-ok:` and with the validity check inverted — it must name a code that *doesn't* resolve. Codes inside URLs are masked, because linking out to another project's [ADR-013](../../record/decisions.d/ADR-013.md) is the correct way to cite a foreign document. Rejected: a lint error (a foreign citation is often right), and inferring a URL for unresolvable codes (silently wrong on a typo). | Active |
|
|
54
|
+
| [ADR-015](../../record/decisions.d/ADR-015.md) | Another project's decision is cited as `SG-ADR-032` — a registered remote prefix composed with that project's own code — and a `[luria.remotes.SG]` entry teaches Luria to build the URL, so `luria link --fix` writes it and `luria lint` demands it exactly as for a local code. This is the alternative [ADR-014](../../record/decisions.d/ADR-014.md) rejected, and what it was missing is a **lockfile**: a remote whose filenames carry title slugs can't be resolved by any template, but it can be *discovered* — from a local clone, or the GitHub contents API, reading the remote's own `luria.toml` for where its documents live — and the code→filename map committed so CI and offline checkouts resolve identically. `luria remotes --check` probes reachability; it is a separate command, never part of `luria lint`, because a check that opens a socket fails on a train. Rejected: a live lookup (flaky, and useless for a private remote), and a flat second scheme (loses which project a code belongs to). | Superseded — by [ADR-016](../../record/decisions.d/ADR-016.md), which drops the local-clone discovery path and makes Luria its own worked example |
|
|
55
|
+
| [ADR-016](../../record/decisions.d/ADR-016.md) | Supersedes [ADR-015](../../record/decisions.d/ADR-015.md), keeping its core — a foreign record is cited as `LU-ADR-013`, a registered prefix composed with that project's own code, and the config builds the URL — and dropping the part that bent the tool around one repository. Discovery reads **public HTTPS only**; the local-clone option is gone, because a resolution that depends on what happens to be on somebody's disk is not reproducible and quietly made the ancestor's pre-convention shape Luria's problem. Luria registers **itself** as remote `LU`, which the `luria init` scaffold cites instead of pasting URLs, so the mechanism is exercised by the package rather than only by its tests. Also: `version:` becomes standard frontmatter for every scheme, not just principles, shown in the index only when it isn't 1; and `*.stub` files are linted, closing a hole where hand-written prose rendered into a page the lint skipped for being generated. Rejected: keeping the clone path behind a flag (the reproducibility problem is the same), and a credentialed fetch. | Active |
|
|
56
|
+
| [ADR-017](../../record/decisions.d/ADR-017.md) | `SG-ADR-032` stays in the prose even though that URL 404s today: strata-g's filenames still carry title slugs, and the code-only convention Luria builds against is what strata-g will use once its record is ported. The reference is therefore correct and merely early. Naming a foreign document is the durable half — which project, which decision — and it survives whatever the URL does; dropping the citations to avoid a broken link would throw away the meaning to protect the plumbing. Two remotes are registered, `SG` and `LU`, and their difference is the point: `LU` is public and already on the convention, so `luria remotes --check` verifies it for real, while `SG` reports as unverifiable and stays that way until the port. Rejected: a hand-authored lockfile (a drifting projection with no guard, since the repo is private), and de-prefixing the citations back to prose (unresolvable *and* unnameable). | Active |
|
|
57
|
+
| [ADR-018](../../record/decisions.d/ADR-018.md) v2 | The two decorative badges ("generated index", "versioned") said nothing a reader couldn't guess and could never be wrong, which makes them furniture. They are replaced by the two numbers the record actually has to answer for — **needs decision** (`Proposed` + `Deferred`, across every scheme) and **cited but retired** (retired documents still cited without an acknowledgement) — baked into static shields URLs that `luria index` rewrites and `luria lint` checks for staleness. Derived, not hand-written: rung 1 of [DP-3](../../record/decisions.d/../../docs/design-principles.md#dp-3). `luria pending` was generalized to every scheme in the process, since a `Proposed` principle is an open question exactly as a decision is. Rejected: a shields endpoint reading a committed JSON file (it always reports the default branch, so the count can't move in a reviewer's diff), and a live query (nothing outside this repo can compute either number). | Active |
|
|
58
|
+
| [ADR-019](../../record/decisions.d/ADR-019.md) | Two ways a decision record can be wrong, and only one of them is a supersession. If the **choice** changes, add a new decision and retire the old one — the rule [ADR-001](../../record/decisions.d/ADR-001.md) has always stated. If the choice stands but a **reason** for it turns out to be wrong, correct the body in place, bump `version`, and say what changed in `history:`. Superseding over a bad argument is theatre: it retires a decision still in force, and every citation of it has to be repointed at an identical claim. Leaving the argument standing is worse, because a wrong reason is exactly what gets quoted at the next person. The version field makes the correction visible, which is the whole reason "never rewrite a body" exists — the objection is to *silent* revision, not to being wrong out loud. Rejected: superseding for any change (churn, and it makes the status vocabulary lie), and correcting silently (indistinguishable from rewriting history). | Active |
|
|
59
|
+
| [ADR-020](../../record/decisions.d/ADR-020.md) | The devlog stops being a collected view and becomes a **journal**: entries live at `devlog.d/yyyy/mm/dd/hhmmss.md`, persist, and are rendered into one generated book per period plus an index. Identity is the authoring timestamp, so there is no number to assign and no name to collide on; ordering is a pure function of the tree rather than of commit order, which a rebase can change. Entries carry the standard frontmatter — `title:` is what each book's contents list shows — and `luria lint` checks the path agrees with `created:`. Rejected: a dated file per period appended to directly (two branches in the same month still conflict, which is most of them), a single growing `docs/devlog.md` (8,281 lines in 40 days at the pilot's rate), and slugs in the filename (a second name for the thing `title:` already names, [DP-3](../../record/decisions.d/../../docs/design-principles.md#dp-3)). | Active |
|
|
60
|
+
| [ADR-021](../../record/decisions.d/ADR-021.md) | The repository layout is split along what a person is doing: `docs/` holds everything a reader browses — prose and every generated view — and `record/` holds everything a contributor files, each container inside carrying the `.d` suffix as the visual affordance that you have crossed into the write domain. A scheme gains a separate `output` so its index renders into `docs/` while its sources live in `record/`; the stub and `tags.yaml` move to the source side, so a view directory holds only what the generator wrote — which turns "don't hand-edit" from a comment into a lint. Rejected: marking sources with `.d` at the top level (four write locations, no single answer to "where do I file"), segregating by *project* into a `meta/` directory (the wrong axis — it split ours-vs-theirs when every repo's actual confusion is read-vs-write), and burying the write root as a dotfile (contributors work there daily). | Active |
|
|
61
|
+
| [ADR-022](../../record/decisions.d/ADR-022.md) | The `url-ok` directive — and the warning it acknowledges — applies only to links whose label is a composed foreign code (`SG-DP-18`), never to local codes or arbitrary hand-targeted links. Three reasons: 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; a local code has a family of legitimate targets, so the same check would flag correct links until acknowledgement became reflex); the risk profiles differ (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, the way Luria registers `LU`. The underlying asymmetry: a local hand URL restates a fact the machinery already owns, so the remedy is mechanical, while a foreign hand URL carries a fact the construction has no home for. That reframes the directive's role: each `url-ok` reason describes a gap in what remote config can express, and a recurring shape is the cue to grow the config — per-scheme remote mappings are the known first candidate — not to keep annotating. | Active |
|
|
62
|
+
| [ADR-023](../../record/decisions.d/ADR-023.md) | A remote gains per-scheme construction: `[luria.remotes.X.schemes.Y]` maps one code family to `dir` (file per code), `document` plus an `anchor` template (sections of one assembled page), or a `url` template. The remote model had assumed one directory of code-named files, so a document-rendered scheme's codes constructed confident URLs to files that never existed — the gap [ADR-022](../../record/decisions.d/ADR-022.md) predicted `url-ok` reasons would accumulate. The anchor template defaults to `dp-{number}`-shaped stable anchors, which is what Luria's own document render emits, so a remote on current conventions needs one `document` line. The lockfile's authority stays scoped to what discovery can see — files — so an anchor construction never consults it. Rejected: zero-config prefix magic (a default is a guess about the remote, and a legacy remote would get confidently wrong anchors; explicit config is a claim by the user), and anchor discovery by fetching the document (network in the resolution path, useless for private repos). The residue — legacy heading-derived anchors no template can compute — remains `url-ok`'s jurisdiction, now excusing only the anchor. | Active |
|
|
63
|
+
| [ADR-024](../../record/decisions.d/ADR-024.md) | A remote need not hold a Luria-shaped record. Give it a `uid` regex and its references are the prefix, a configurable `delim` and whatever the pattern matches — an arxiv id, a ticket key — constructed through the `url` template, which can address the uid's capture groups by position (`{1}.{2}`) or take the whole tail as `{0}`/`{uid}`. A uid is exact and is never normalised: zero-padding an arxiv id would quietly cite a different paper. One rung only — no lockfile, no code-only convention, and a uid remote without a template constructs nothing rather than guessing. The refactor this forced was overdue anyway: one combined regex with a hardcoded hyphen became per-remote patterns behind `references()` and `parse_code()`, so the delimiter and tail shape are spelled in one place. Rejected: overloading `schemes` for uid families (a scheme implies documents with statuses and an index; an arxiv paper has neither), and auto-linkifying unregistered shapes (an unconfigured prefix must never match). | Active |
|
|
64
|
+
| [ADR-025](../../record/decisions.d/ADR-025.md) | `[[ADR-013]]`, `[[SG-DP-18]]`, `[[ARXIV-2403.05530|the report]]` — wikilink brackets are the author asserting "this is a reference, link it", and the assertion changes the rules on both sides. Inside the brackets no prose heuristics apply: a bare `DP-3` (no `#`) resolves, a low `[[#10]]` needs no cue, any registered remote shape works. And an unresolvable wikilink is a lint violation the fixer cannot clear — the one deliberate exception to "the lint never demands what `--fix` won't do", because an explicit request deserves an explicit refusal rather than a silent skip. Wikilinks are consumed: `luria link --fix` rewrites them to ordinary markdown links (an `<a href>` inside raw-HTML blocks), because GitHub renders `[[…]]` as literal brackets and source files must read as plain markdown wherever views aren't generated. Rejected: expanding at view-generation time (fails everywhere sources are read directly), and heuristic-free auto-linking without brackets (already rejected in [ADR-024](../../record/decisions.d/ADR-024.md) — this is the sanctioned way to opt a single reference out of the heuristics). | Active |
|
|
65
|
+
| [ADR-026](../../record/decisions.d/ADR-026.md) | Parallelism lands as one primitive — `parallel.pmap(fn, items)`, a thread pool that returns results in input order — applied at three seams: render units in `outputs()` (a scheme, a journal), per-file scans in the bare-reference lint, and per-URL probes in `remotes --check`. Measured honestly: the probes are the real win (6.6s → 2.9s, round-trips overlapped); renders and scans are a wash at today's cardinality, because that work is regex-CPU under the GIL — those seams are structure bought now, cheap, against the growth the issue anticipates. Threads not processes (the winning workloads are I/O; processes add pickling and spawn cost for no measured gain), ordered results not as-completed (the staleness diff and the lint report must read identically at any width), and `LURIA_JOBS=1` as the serial escape hatch. If render units ever measure in seconds, the `pmap` seam is where a process pool swaps in. | Active |
|
|
66
|
+
| [ADR-027](../../record/decisions.d/ADR-027.md) | Luria publishes to PyPI through GitHub's trusted publishing: a `publish.yml` workflow whose `pypi` environment's OIDC identity is the whole credential — no token exists to leak, rotate or forget. Build and publish are separate jobs; the artifact that ships is the artifact that passed a cold-install smoke test (`pip install dist/*.whl`, then `init → index → journal new → lint` in an empty directory), which is the guard against shipping a `luria init` that only works from a checkout. The scaffold ships *inside* the package (`luria/template/` in the wheel, via hatchling force-include) while staying top-level in the repository where a visitor browses it ([ADR-021](../../record/decisions.d/ADR-021.md)) — the setuptools parent-relative package-data it replaces leaked a bare `template/` into site-packages, functional only by collision-prone accident. Publishing fires on a GitHub release (plus manual dispatch); the sdist carries build inputs, not the record — the record's browsing surface is the repository. | Active |
|
|
67
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<!-- GENERATED by `luria index` from record/devlog.d/ — edit the entries, not this file. -->
|
|
2
|
+
|
|
3
|
+
# Development log
|
|
4
|
+
|
|
5
|
+
The narrative that doesn't fit a changelog entry: root-cause archaeology, failed approaches, verification details, and the traps the next person would otherwise rediscover. Generated from the entries in `record/devlog.d/` — file one with `luria journal new "A title"`.
|
|
6
|
+
|
|
7
|
+
## Currently — [August 2026](2026-08.md)
|
|
8
|
+
|
|
9
|
+
- [4 Aug 18:44 — Shipping: the wheel that worked was working by accident](2026-08.md#20260804184404)
|
|
10
|
+
- [4 Aug 18:18 — Parallelism measured first: the probes were the win, the rest is seams](2026-08.md#20260804181814)
|
|
11
|
+
- [4 Aug 18:12 — Wikilinks: the brackets are consent, and typing them flips the rules](2026-08.md#20260804181244)
|
|
12
|
+
- [4 Aug 17:29 — Numbers were the special case: uid remotes and one parser for a code's anatomy](2026-08.md#20260804172947)
|
|
13
|
+
- [4 Aug 17:09 — A remote learns its schemes, and the url-ok loop closes on schedule](2026-08.md#20260804170921)
|
|
14
|
+
- [4 Aug 15:37 — The read/write boundary: record/ for filing, docs/ for reading](2026-08.md#20260804153748)
|
|
15
|
+
- [4 Aug 03:44 — The devlog becomes a journal, and the migration's timestamps came from the commits](2026-08.md#20260804034408)
|
|
16
|
+
- [4 Aug 03:27 — A decision cited as a principle](2026-08.md#20260804032711)
|
|
17
|
+
- [3 Aug 22:56 — Two badges that could never be wrong, replaced with two that can](2026-08.md#20260803225648)
|
|
18
|
+
- [3 Aug 22:13 — Foreign decisions get a namespace, and the first version bent the tool around one repo](2026-08.md#20260803221327)
|
|
19
|
+
- [3 Aug 21:50 — Four bare codes were a hole in the lint with ten real defects in it](2026-08.md#20260803215013)
|
|
20
|
+
- [3 Aug 21:19 — The filename is the code, and the rename removed a copy rather than adding one](2026-08.md#20260803211926)
|
|
21
|
+
- [3 Aug 21:02 — Principles become fragments, and collected turns out not to mean generated](2026-08.md#20260803210211)
|
|
22
|
+
- [3 Aug 20:35 — Extracting the project-memory framework out of strata-g](2026-08.md#20260803203505)
|
|
23
|
+
|
|
24
|
+
## All books
|
|
25
|
+
|
|
26
|
+
14 entries across 1 book, newest first.
|
|
27
|
+
|
|
28
|
+
| Book | Entries | First | Last |
|
|
29
|
+
|---|--:|---|---|
|
|
30
|
+
| [2026-08](2026-08.md) | 14 | 2026-08-03 | 2026-08-04 |
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Luria — a project's memory, kept where the next collaborator will find it.
|
|
2
|
+
|
|
3
|
+
The four layers (design principles, decisions, changelog, devlog), the fragment
|
|
4
|
+
convention that keeps them conflict-free, the generated views, and the lint that
|
|
5
|
+
stops all of it from drifting.
|
|
6
|
+
|
|
7
|
+
Public surface is the CLI (`luria --help`); the modules are importable for
|
|
8
|
+
projects that want to extend a check rather than replace it.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
__version__ = "0.1.0"
|