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.
Files changed (55) hide show
  1. luria-0.1.0/.gitignore +5 -0
  2. luria-0.1.0/LICENSE +21 -0
  3. luria-0.1.0/PKG-INFO +168 -0
  4. luria-0.1.0/README.md +138 -0
  5. luria-0.1.0/docs/README.md +19 -0
  6. luria-0.1.0/docs/decisions/README.md +67 -0
  7. luria-0.1.0/docs/devlog/README.md +30 -0
  8. luria-0.1.0/luria/__init__.py +11 -0
  9. luria-0.1.0/luria/adr_index.py +400 -0
  10. luria-0.1.0/luria/adr_pending.py +156 -0
  11. luria-0.1.0/luria/badges.py +130 -0
  12. luria-0.1.0/luria/cli.py +67 -0
  13. luria-0.1.0/luria/collect.py +154 -0
  14. luria-0.1.0/luria/config.py +560 -0
  15. luria-0.1.0/luria/directives.py +253 -0
  16. luria-0.1.0/luria/doc_refs.py +705 -0
  17. luria-0.1.0/luria/init.py +104 -0
  18. luria-0.1.0/luria/journal.py +283 -0
  19. luria-0.1.0/luria/link_refs.py +48 -0
  20. luria-0.1.0/luria/lint.py +316 -0
  21. luria-0.1.0/luria/parallel.py +64 -0
  22. luria-0.1.0/luria/ref_status.py +524 -0
  23. luria-0.1.0/luria/remotes.py +527 -0
  24. luria-0.1.0/luria/reports.py +176 -0
  25. luria-0.1.0/pyproject.toml +55 -0
  26. luria-0.1.0/template/.github/workflows/docs.yml +54 -0
  27. luria-0.1.0/template/CLAUDE.md +136 -0
  28. luria-0.1.0/template/docs/README.md +16 -0
  29. luria-0.1.0/template/luria.toml +81 -0
  30. luria-0.1.0/template/record/changelog.d/_template.md +27 -0
  31. luria-0.1.0/template/record/decisions.d/README.stub +31 -0
  32. luria-0.1.0/template/record/decisions.d/_template.md +80 -0
  33. luria-0.1.0/template/record/decisions.d/tags.yaml +14 -0
  34. luria-0.1.0/template/record/devlog.d/_template.md +34 -0
  35. luria-0.1.0/template/record/principles.d/DP-001.md +27 -0
  36. luria-0.1.0/template/record/principles.d/DP-002.md +27 -0
  37. luria-0.1.0/template/record/principles.d/DP-003.md +22 -0
  38. luria-0.1.0/template/record/principles.d/DP-004.md +21 -0
  39. luria-0.1.0/template/record/principles.d/DP-005.md +21 -0
  40. luria-0.1.0/template/record/principles.d/README.stub +28 -0
  41. luria-0.1.0/template/record/principles.d/_template.md +80 -0
  42. luria-0.1.0/tests/_scheme.py +31 -0
  43. luria-0.1.0/tests/conftest.py +43 -0
  44. luria-0.1.0/tests/test_adr_index.py +331 -0
  45. luria-0.1.0/tests/test_adr_pending.py +129 -0
  46. luria-0.1.0/tests/test_badges.py +122 -0
  47. luria-0.1.0/tests/test_directives.py +168 -0
  48. luria-0.1.0/tests/test_doc_refs.py +333 -0
  49. luria-0.1.0/tests/test_doc_reports.py +79 -0
  50. luria-0.1.0/tests/test_journal.py +219 -0
  51. luria-0.1.0/tests/test_lint.py +221 -0
  52. luria-0.1.0/tests/test_parallel.py +51 -0
  53. luria-0.1.0/tests/test_ref_status.py +317 -0
  54. luria-0.1.0/tests/test_remotes.py +410 -0
  55. luria-0.1.0/tests/test_wikilinks.py +137 -0
luria-0.1.0/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ __pycache__/
2
+ *.egg-info/
3
+ .pytest_cache/
4
+ build/
5
+ dist/
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
+ [![CI](https://github.com/dmarx/luria/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/dmarx/luria/actions/workflows/ci.yml)
34
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/downloads/)
35
+ [![License: MIT](https://img.shields.io/github/license/dmarx/luria)](LICENSE)
36
+ <!-- luria:badges -->
37
+ [![needs decision: 0](https://img.shields.io/badge/needs%20decision-0-brightgreen)](docs/decisions/README.md)
38
+ [![cited but retired: 0](https://img.shields.io/badge/cited%20but%20retired-0-brightgreen)](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
+ [![CI](https://github.com/dmarx/luria/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/dmarx/luria/actions/workflows/ci.yml)
4
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/downloads/)
5
+ [![License: MIT](https://img.shields.io/github/license/dmarx/luria)](LICENSE)
6
+ <!-- luria:badges -->
7
+ [![needs decision: 0](https://img.shields.io/badge/needs%20decision-0-brightgreen)](docs/decisions/README.md)
8
+ [![cited but retired: 0](https://img.shields.io/badge/cited%20but%20retired-0-brightgreen)](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"