@nurix/etna 0.20.25 → 0.20.26
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.
- package/data/harness-index.json +1 -1
- package/data/harnesses/code/CLAUDE.md +4 -4
- package/data/harnesses/code/agents/bookkeeper.md +1 -1
- package/data/harnesses/code/agents/change-review.md +1 -1
- package/data/harnesses/code/agents/lld-mode.md +1 -1
- package/data/harnesses/code/agents/maintenance.md +7 -9
- package/data/harnesses/code/docs/structure.md +133 -71
- package/data/harnesses/code/harness.json +4 -4
- package/data/harnesses/code/hooks/awareness.mjs +0 -10
- package/data/harnesses/code/hooks/lib/ownership.mjs +4 -2
- package/data/harnesses/code/hooks/lib/sweep.mjs +4 -8
- package/data/harnesses/code/skills/documentation/SKILL.md +19 -8
- package/data/harnesses/code/skills/documentation/references/frontmatter.md +3 -3
- package/data/harnesses/code/skills/documentation/references/style-guide.md +70 -27
- package/data/harnesses/code/skills/documentation/references/untemplated.md +2 -1
- package/data/harnesses/code/skills/documentation/scripts/doc-lint.mjs +87 -1
- package/data/harnesses/code/skills/documentation/templates/Readme.md +4 -3
- package/data/harnesses/code/skills/documentation/templates/bookkeeping.md +1 -1
- package/data/harnesses/code/skills/documentation/templates/docs-readme.md +15 -36
- package/data/harnesses/code/skills/documentation/templates/doctrine.md +82 -0
- package/data/harnesses/code/skills/documentation/templates/feature.md +31 -123
- package/data/harnesses/code/skills/documentation/templates/handover.md +174 -0
- package/data/harnesses/code/skills/documentation/templates/operations-procedure.md +1 -1
- package/data/harnesses/code/skills/documentation/templates/package.md +28 -94
- package/data/harnesses/code/skills/documentation/templates/root-readme.md +103 -65
- package/data/harnesses/code/skills/documentation/templates/setup.md +138 -0
- package/data/harnesses/code/skills/documentation/templates/system.md +25 -109
- package/data/harnesses/delivery/agents/lld-mode.md +1 -1
- package/data/harnesses/delivery/agents/maintenance.md +7 -9
- package/data/harnesses/delivery/harness.json +2 -2
- package/data/harnesses/delivery/skills/documentation/SKILL.md +19 -8
- package/data/harnesses/delivery/skills/documentation/references/frontmatter.md +3 -3
- package/data/harnesses/delivery/skills/documentation/references/style-guide.md +70 -27
- package/data/harnesses/delivery/skills/documentation/references/untemplated.md +2 -1
- package/data/harnesses/delivery/skills/documentation/scripts/doc-lint.mjs +87 -1
- package/data/harnesses/delivery/skills/documentation/templates/Readme.md +4 -3
- package/data/harnesses/delivery/skills/documentation/templates/bookkeeping.md +1 -1
- package/data/harnesses/delivery/skills/documentation/templates/docs-readme.md +15 -36
- package/data/harnesses/delivery/skills/documentation/templates/doctrine.md +82 -0
- package/data/harnesses/delivery/skills/documentation/templates/feature.md +31 -123
- package/data/harnesses/delivery/skills/documentation/templates/handover.md +174 -0
- package/data/harnesses/delivery/skills/documentation/templates/operations-procedure.md +1 -1
- package/data/harnesses/delivery/skills/documentation/templates/package.md +28 -94
- package/data/harnesses/delivery/skills/documentation/templates/root-readme.md +103 -65
- package/data/harnesses/delivery/skills/documentation/templates/setup.md +138 -0
- package/data/harnesses/delivery/skills/documentation/templates/system.md +25 -109
- package/data/harnesses/etna/agents/change-review.md +1 -1
- package/data/harnesses/etna/agents/lld-mode.md +1 -1
- package/data/harnesses/etna/agents/maintenance.md +7 -9
- package/data/harnesses/etna/harness.json +2 -2
- package/data/harnesses/etna/skills/documentation/SKILL.md +19 -8
- package/data/harnesses/etna/skills/documentation/references/frontmatter.md +3 -3
- package/data/harnesses/etna/skills/documentation/references/style-guide.md +70 -27
- package/data/harnesses/etna/skills/documentation/references/untemplated.md +2 -1
- package/data/harnesses/etna/skills/documentation/scripts/doc-lint.mjs +87 -1
- package/data/harnesses/etna/skills/documentation/templates/Readme.md +4 -3
- package/data/harnesses/etna/skills/documentation/templates/bookkeeping.md +1 -1
- package/data/harnesses/etna/skills/documentation/templates/docs-readme.md +15 -36
- package/data/harnesses/etna/skills/documentation/templates/doctrine.md +82 -0
- package/data/harnesses/etna/skills/documentation/templates/feature.md +31 -123
- package/data/harnesses/etna/skills/documentation/templates/handover.md +174 -0
- package/data/harnesses/etna/skills/documentation/templates/operations-procedure.md +1 -1
- package/data/harnesses/etna/skills/documentation/templates/package.md +28 -94
- package/data/harnesses/etna/skills/documentation/templates/root-readme.md +103 -65
- package/data/harnesses/etna/skills/documentation/templates/setup.md +138 -0
- package/data/harnesses/etna/skills/documentation/templates/system.md +25 -109
- package/lib/install/materialize.mjs +4 -12
- package/lib/install/plan.mjs +4 -11
- package/package.json +3 -2
- package/data/harnesses/code/skills/documentation/templates/structure.md +0 -71
- package/data/harnesses/delivery/skills/documentation/templates/structure.md +0 -71
- package/data/harnesses/etna/skills/documentation/templates/structure.md +0 -71
|
@@ -119,11 +119,97 @@ function lintFile(path, findings) {
|
|
|
119
119
|
|
|
120
120
|
// A folder index (Readme.md/README.md) is a browse page, not a feature doc — it owns no code.
|
|
121
121
|
const isFolderIndex = /(^|\/)(readme)\.md$/i.test(path);
|
|
122
|
+
|
|
123
|
+
// An index names folders, never files (SKILL.md § The Two Natures). A link from a folder index or
|
|
124
|
+
// the authority map to an individual document is a row that must be edited on every add, rename
|
|
125
|
+
// and retirement; the only files an index may link are the mandated scaffolds and a sibling
|
|
126
|
+
// folder's own Readme.
|
|
127
|
+
const isAuthorityMap = /(^|\/)docs\/structure\.md$/.test(path);
|
|
128
|
+
const isRootReadme = /^(\.\/)?README\.md$/i.test(path.replace(process.cwd() + '/', ''));
|
|
129
|
+
if (scope === 'truth' && (isFolderIndex || isAuthorityMap) && !disabled.has('index-lists-files')) {
|
|
130
|
+
const SCAFFOLD = /(^|\/)(readme|structure|changelog|backlog|main|health-log|journal|claude|agents)\.md$|(^|\/)operations\/setup\.md$/i;
|
|
131
|
+
for (const { n, line } of lines) {
|
|
132
|
+
if (line.includes('doc-lint-disable: index-lists-files')) continue;
|
|
133
|
+
for (const m of line.matchAll(/\]\(([^)\s#]+\.md)(#[^)]*)?\)/g)) {
|
|
134
|
+
const target = m[1];
|
|
135
|
+
if (/^[a-z]+:\/\//.test(target) || SCAFFOLD.test(target)) continue;
|
|
136
|
+
// The repo-root README routes by intent (templates/root-readme.md): its Map names owning package and system
|
|
137
|
+
// docs, its Where-to-go rows name procedures and rules. Individual feature docs stay off it — the ownership index finds them.
|
|
138
|
+
if (isRootReadme && /(^|\/)docs\/(architecture\/(packages|systems|common)\/|operations\/)[^/]+\.md$|(^|\/)\.claude\/rules\/[^/]+\.md$/.test(target)) continue;
|
|
139
|
+
if (isRootReadme && /(^|\/)docs\/features\/[^/]+\.md$/.test(target)) { findings.push({ path, n, severity: 'warn', id: 'root-readme-feature-link', message: `root README links an individual feature doc (\`${target}\`) — point at docs/features/ and let the ownership index find it`, excerpt: line.trim().slice(0, 90) }); continue; }
|
|
140
|
+
findings.push({ path, n, severity: 'block', id: 'index-lists-files', message: `index links an individual document (\`${target}\`) — name the folder and how its files are named; \`ls\` is the roster`, excerpt: line.trim().slice(0, 90) });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// A folder index describes directories; a drawn tree is a file roster in another costume
|
|
145
|
+
// (SKILL.md § Indexes describe directories, never files). structure.md is the authority map and keeps its tree.
|
|
146
|
+
if (scope === 'truth' && isFolderIndex && (isRootReadme || /(^|\/)docs\//.test(path)) && !disabled.has('index-file-tree')) {
|
|
147
|
+
// Raw lines, not proseLines: a tree is drawn inside a code fence, which proseLines drops. The repo-root README routes by a Map table; its tree is docs/structure.md's too (templates/root-readme.md).
|
|
148
|
+
const rawLines = text.split('\n').map((line, i) => ({ n: i + 1, line }));
|
|
149
|
+
const tree = rawLines.find(({ line }) => /^\s*[│├└]/.test(line));
|
|
150
|
+
if (tree) findings.push({ path, n: tree.n, severity: 'block', id: 'index-file-tree', message: 'a tree drawn in a folder index — describe each directory in a sentence; the tree is docs/structure.md\'s', excerpt: tree.line.trim().slice(0, 90) });
|
|
151
|
+
}
|
|
152
|
+
// The repo-root README is an orientation page with a budget (templates/root-readme.md): up to about 140 non-blank lines, plain Markdown, no chrome.
|
|
153
|
+
if (scope === 'truth' && isRootReadme) {
|
|
154
|
+
const raw = text.split('\n');
|
|
155
|
+
const nonBlank = raw.filter((l) => l.trim()).length;
|
|
156
|
+
if (!disabled.has('root-readme-budget') && nonBlank > 140) findings.push({ path, n: 1, severity: 'warn', id: 'root-readme-budget', message: `${nonBlank} non-blank lines — the root README orients and routes; a section that needs a paragraph needs a doc (templates/root-readme.md)` });
|
|
157
|
+
const chrome = raw.findIndex((l) => /shields\.io|<img\b|<div\b|<p align|^!\[/.test(l));
|
|
158
|
+
if (!disabled.has('root-readme-chrome') && chrome !== -1) findings.push({ path, n: chrome + 1, severity: 'block', id: 'root-readme-chrome', message: 'badge, image or HTML layout in the root README — agents cannot read it and the team does not need it', excerpt: raw[chrome].trim().slice(0, 90) });
|
|
159
|
+
}
|
|
122
160
|
if (scope === 'truth' && !isFolderIndex && /(^|\/)docs\/features\/[^/]+\.md$/.test(path)) {
|
|
123
161
|
const fm = text.startsWith('---') ? text.slice(0, text.indexOf('\n---', 3)) : '';
|
|
124
|
-
for (const key of ['owns:', '
|
|
162
|
+
for (const key of ['owns:', 'status:', 'type:'])
|
|
125
163
|
if (!fm.includes(key)) findings.push({ path, n: 1, severity: 'block', id: 'feature-frontmatter', message: `feature doc missing \`${key}\` frontmatter` });
|
|
126
164
|
}
|
|
165
|
+
|
|
166
|
+
// An owning document (feature, package, system) is intent and promises; the sections that narrate
|
|
167
|
+
// mechanism — a file roster, a walk through the code, a schema or route table — are the graph's
|
|
168
|
+
// to compute and are retired from the genre (SKILL.md § A document is intent and promises).
|
|
169
|
+
const isOwningDoc = scope === 'truth' && !isFolderIndex && /(^|\/)docs\/(features|architecture\/(packages|systems))\/[^/]+\.md$/.test(path);
|
|
170
|
+
if (isOwningDoc && !disabled.has('mechanism-section')) {
|
|
171
|
+
const RETIRED = /^#{2,4}\s+(Files|Current Behavior|Key Functions|Data|API|Refusals?|Architecture|State|Redux|Affected Modules|Conventions to Follow|Ownership Conflicts|Tests|The shape|Layout|Build and test|Commands|Routes|Schema|Wire)\b/i;
|
|
172
|
+
for (const { n, line } of lines) {
|
|
173
|
+
if (!RETIRED.test(line) || line.includes('doc-lint-disable: mechanism-section')) continue;
|
|
174
|
+
findings.push({ path, n, severity: 'block', id: 'mechanism-section', message: 'mechanism section in an owning document — columns, routes, guards, functions and files are the graph\'s to compute; keep the decision or the promise, point by name, cut the rest', excerpt: line.trim().slice(0, 90) });
|
|
175
|
+
}
|
|
176
|
+
const fm = text.startsWith('---') ? text.slice(0, text.indexOf('\n---', 3)) : '';
|
|
177
|
+
if (!/^objects:/m.test(fm)) findings.push({ path, n: 1, severity: 'block', id: 'objects-missing', message: 'owning document names no `objects:` — the product objects it designs are its edge into the graph and its discovery terms' });
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// The body of an owning document is read by people; its "For the agent" footer is the reference
|
|
181
|
+
// surface (style-guide § Documents are read by people). These three warn, never block: a seam
|
|
182
|
+
// reference is allowed in the body, and the judgment of which identifier is a seam is the author's.
|
|
183
|
+
if (isOwningDoc) {
|
|
184
|
+
const OLD_SHAPE = /^##\s+(Background|Decisions|Promises|Objects|Related)\s*$/;
|
|
185
|
+
const footerAt = lines.findIndex(({ line }) => /^##\s+For the agent\b/.test(line));
|
|
186
|
+
const body = footerAt === -1 ? lines : lines.slice(0, footerAt);
|
|
187
|
+
let section = null; // proseLines already dropped the frontmatter and the fences
|
|
188
|
+
const sections = [];
|
|
189
|
+
for (const { n, line } of body) {
|
|
190
|
+
if (/^##\s+/.test(line)) {
|
|
191
|
+
if (!disabled.has('old-shape-section') && OLD_SHAPE.test(line)) findings.push({ path, n, severity: 'warn', id: 'old-shape-section', message: 'second-shape heading — the owning genres now read Why it exists · How it works · What was decided · What it promises · Known gaps · For the agent', excerpt: line.trim() });
|
|
192
|
+
section = { n, title: line.replace(/^##\s+/, '').trim(), bullets: 0, lines: 0, ids: 0, boldRun: 0, maxRun: 0 };
|
|
193
|
+
sections.push(section);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (!section || !line.trim()) { if (section) section.boldRun = 0; continue; }
|
|
197
|
+
section.lines++;
|
|
198
|
+
const isBullet = /^\s*[-*]\s+/.test(line);
|
|
199
|
+
if (isBullet) section.bullets++;
|
|
200
|
+
if (/^\s*[-*]\s+\*\*/.test(line)) { section.boldRun++; section.maxRun = Math.max(section.maxRun, section.boldRun); } else section.boldRun = 0;
|
|
201
|
+
for (const m of line.matchAll(/`([^`]+)`/g)) {
|
|
202
|
+
const t = m[1];
|
|
203
|
+
if (/[\/]|\.(ts|tsx|mjs|js|md|json|sql)$|_|\(|^[a-z]+[A-Z]|^[A-Z][a-z]+[A-Z]|^(GET|POST|PUT|PATCH|DELETE) /.test(t)) section.ids++;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
for (const s of sections) {
|
|
207
|
+
if (/^(Ownership|Known gaps)$/.test(s.title)) continue;
|
|
208
|
+
if (!disabled.has('body-identifier') && s.ids > 3) findings.push({ path, n: s.n, severity: 'warn', id: 'body-identifier', message: `${s.ids} code identifiers in a body section — the body names code only at a cross-cutting seam (a middleware, gate, hook, store); the rest is plain names in prose, with identifiers in "For the agent"`, excerpt: s.title });
|
|
209
|
+
if (!disabled.has('list-not-prose') && s.bullets >= 4 && s.bullets / s.lines > 0.6) findings.push({ path, n: s.n, severity: 'warn', id: 'list-not-prose', message: 'a body section that is a list — a decision, a promise or an explanation is prose; a list is for genuinely parallel one-liners', excerpt: s.title });
|
|
210
|
+
if (!disabled.has('formula-run') && s.maxRun >= 3) findings.push({ path, n: s.n, severity: 'warn', id: 'formula-run', message: 'three or more bold-led bullets in a row — the "**X** — over Y, because Z" formula reads as a ledger; write the decisions as themed paragraphs', excerpt: s.title });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
127
213
|
}
|
|
128
214
|
|
|
129
215
|
// A changed plan whose committed version is already frozen must not be edited.
|
|
@@ -6,6 +6,7 @@ One file per template. Every template is either a **truth document** (states cur
|
|
|
6
6
|
| --- | --- | --- |
|
|
7
7
|
| Feature (LLD) | [feature.md](feature.md) | Truth |
|
|
8
8
|
| Package (one workspace package) | [package.md](package.md) | Truth |
|
|
9
|
+
| Doctrine (one concern the platform handles everywhere) | [doctrine.md](doctrine.md) | Truth |
|
|
9
10
|
| System (one machine spanning packages) | [system.md](system.md) | Truth |
|
|
10
11
|
| Architecture Decision (AD) | [architecture-decision.md](architecture-decision.md) | Truth (Decision Changelog tail sanctioned) |
|
|
11
12
|
| System overview | [system-overview.md](system-overview.md) | Truth |
|
|
@@ -18,13 +19,13 @@ One file per template. Every template is either a **truth document** (states cur
|
|
|
18
19
|
| Testing strategy | [testing-strategy.md](testing-strategy.md) | Truth |
|
|
19
20
|
| Behavioral checklist | [behavioral-checklist.md](behavioral-checklist.md) | Truth |
|
|
20
21
|
| UX behavior | [ux-behavior.md](ux-behavior.md) | Truth |
|
|
22
|
+
| Setup (first run, one per repo) | [setup.md](setup.md) | Truth |
|
|
21
23
|
| Operations procedure | [operations-procedure.md](operations-procedure.md) | Truth |
|
|
22
24
|
| Implementation map | [implementation-map.md](implementation-map.md) | Truth |
|
|
23
25
|
| Bookkeeping ledger | [bookkeeping.md](bookkeeping.md) | Truth (work queue) |
|
|
24
26
|
| Protected files | [protected-files.md](protected-files.md) | Truth |
|
|
25
|
-
| Project root README | [root-readme.md](root-readme.md) | Truth |
|
|
27
|
+
| Project root README (outward opening, then orientation and routing) | [root-readme.md](root-readme.md) | Truth |
|
|
26
28
|
| docs/README.md (doc index) | [docs-readme.md](docs-readme.md) | Truth |
|
|
27
|
-
| docs/structure.md (authority map) | [structure.md](structure.md) | Truth (Health Log tail sanctioned) |
|
|
28
29
|
| Plan | [plan.md](plan.md) | Record |
|
|
29
30
|
| Changelog entry | [changelog-entry.md](changelog-entry.md) | Record |
|
|
30
31
|
| Migration runbook | [migration-runbook.md](migration-runbook.md) | Record |
|
|
@@ -35,6 +36,6 @@ One file per template. Every template is either a **truth document** (states cur
|
|
|
35
36
|
|
|
36
37
|
**The `type:` enum lives here.** A doc's `type:` frontmatter takes its value from this table — one genre per template (`feature`, `architecture-decision`, `non-goals`, …). External generators consume this list rather than defining their own; a type this table does not name defaults to `docs/architecture/` placement and is never added to the protected-files list.
|
|
37
38
|
|
|
38
|
-
**Baseline scaffolds** — files that must exist after every baseline; the `maintenance` agent (baseline mode) creates any that are missing: [docs-readme.md](docs-readme.md), [
|
|
39
|
+
**Baseline scaffolds** — files that must exist after every baseline (`docs/structure.md` is not among them: it is harness-owned, shipped whole by the installer and overwritten on every refresh, and has no template); the `maintenance` agent (baseline mode) creates any that are missing: [docs-readme.md](docs-readme.md), [changelog-index.md](changelog-index.md), [backlog.md](backlog.md), [memory-main.md](memory-main.md), [setup.md](setup.md) (when the repo has a lockfile and a runnable root script; never overwriting an existing first-run procedure), and [root-readme.md](root-readme.md) (never overwriting an existing root README).
|
|
39
40
|
|
|
40
41
|
**Ownership.** Three genres carry code — **feature**, **package**, **system** — and each is the canonical reference for the code paths in its **`owns:` frontmatter**, the only ownership key. To find what a doc owns, read its `owns:` globs; to find the doc for a path, match the path against the injected ownership index (the union of every owning doc's globs), narrower glob wins. That is what layers them: a package doc claims `packages/{name}/**` and is the tree's **residual** owner, while a system doc claims the narrower subtrees of one machine and a feature doc claims one surface — both win those paths by specificity, and the package doc keeps everything neither claims. Two docs may never claim a path at **equal** specificity. Every other genre here owns no code, including the cross-cutting doctrine in `docs/architecture/common/`. See [`../references/frontmatter.md`](../references/frontmatter.md) for turning code into `owns:` globs.
|
|
@@ -21,4 +21,4 @@ The shared work queue keeping documentation and code in sync — a queue, never
|
|
|
21
21
|
|
|
22
22
|
**A waiver covers a finding only when it names the finding's kind and its subject in backticks** — the matcher (`waiverCovers`) tests the backticked subject exactly, so a waiver written without them silences nothing and the wall stands. A `waived:` row's `expires=` date is enforced: CI honors the waiver until the date, then fails. `dead-glob` waivers double as the dead-glob allowlist; `ownership-overlap` waivers cover the CI ownership check the same way.
|
|
23
23
|
|
|
24
|
-
Finding kinds for `auto:`: `dead-glob` · `doc-implicated` · `doc-lint` · `header-missing` · `
|
|
24
|
+
Finding kinds for `auto:`: `dead-glob` · `doc-implicated` · `doc-lint` · `header-missing` · `package-rejected` · `frozen-record-edited` · `source-lint`. The CI ownership check additionally accepts `waived:` rows of kind `ownership-overlap`.
|
|
@@ -2,67 +2,46 @@
|
|
|
2
2
|
|
|
3
3
|
Truth document. Use for: the entry point for all documentation in a repo. Created during baseline audit.
|
|
4
4
|
|
|
5
|
+
**This file describes directories, never files.** Each section names one folder under `docs/` and says what the folder holds, who reads it and how its files are named — a description of the directory, not of its contents. No per-file rows, no file tree, no `├──` block: the tree is `structure.md`'s, as the authority on placement, and `ls` is the roster. A row per file forces an index edit on every file added, renamed or retired, and the index's own history then records roster churn instead of what the index said — a repo carried 103 commits on `docs/README.md` before this rule, most of them row edits. Each section below names a folder and says what the folder holds and how its files are named; the only files an index may name are the mandated scaffolds (`structure.md`, `operations/setup.md`, `history/CHANGELOG.md`, `history/backlog.md`) and the root `CLAUDE.md`. The repo-root `README.md` routes the team and agents in by intent; this file describes the folders of the docs tree — each links the other once and neither restates the other. `doc-lint` blocks a folder index that links an individual document (`index-lists-files`) or draws a tree (`index-file-tree`).
|
|
6
|
+
|
|
5
7
|
**Link only what exists.** Every row below is a placeholder — drop any whose target the repo does not have. Most `history/` and `architecture/` folders are created on first use, so linking them unconditionally ships a docs index with dead links on day one. An index is a browse page: a link that resolves to nothing is worse than an absent row.
|
|
6
8
|
|
|
7
9
|
```
|
|
8
10
|
# {Project Name} Documentation
|
|
9
11
|
|
|
10
|
-
Technical documentation for {project description}.
|
|
12
|
+
Technical documentation for {project description}. {Two or three sentences: what the product is, its planes or packages, who the docs are for.}
|
|
11
13
|
|
|
12
14
|
> For documentation organization, authority map, and folder rules, see **[structure.md](structure.md)**.
|
|
13
15
|
|
|
14
16
|
---
|
|
15
17
|
|
|
16
|
-
## Quick Links
|
|
17
|
-
|
|
18
|
-
| Document | Description |
|
|
19
|
-
| --------------------------------- | ------------------------------------ |
|
|
20
|
-
| [Structure](structure.md) | Documentation entry point and rules |
|
|
21
|
-
| [Setup](operations/setup.md) | Installation and configuration guide |
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
18
|
## Architecture
|
|
26
19
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
- [system-design.md](architecture/system-design.md) - System overview: subsystems and key flows
|
|
30
|
-
- [domain-model.md](architecture/domain-model.md) - Entities, relationships, state ownership
|
|
31
|
-
- [glossary.md](architecture/glossary.md) - Project vocabulary
|
|
32
|
-
- [non-goals.md](architecture/non-goals.md) - What this system deliberately does not do
|
|
33
|
-
- [decisions/](architecture/decisions/Readme.md) - Architecture Decisions (ADs)
|
|
34
|
-
{list other architecture docs — api.md, security.md — when present}
|
|
35
|
-
|
|
36
|
-
---
|
|
20
|
+
The design layer — [architecture/](architecture/): {what the folder holds — the system overview, steering docs, cross-cutting doctrine, the per-package and per-system documents, and `decisions/` (one Architecture Decision per subject, `AD-NNN-{slug}.md`, status in each file's frontmatter)}.
|
|
37
21
|
|
|
38
22
|
## Features
|
|
39
23
|
|
|
40
|
-
|
|
24
|
+
[features/](features/) — one living reference per feature (how it works today), flat, named `{feature}.md`; each carries `owns:` for the code it speaks for. Find a feature by its name or by the ownership index, never by a row here.
|
|
41
25
|
|
|
42
|
-
|
|
43
|
-
{list all feature docs}
|
|
26
|
+
## Tests
|
|
44
27
|
|
|
45
|
-
|
|
28
|
+
[tests/](tests/) — one behavioral checklist per documented surface, `{feature}.md`, `verifies:` naming the document whose promises it proves.
|
|
46
29
|
|
|
47
30
|
## Operations
|
|
48
31
|
|
|
49
|
-
|
|
32
|
+
[operations/](operations/) — how to set up, run, develop, deploy and recover. Start at [`setup.md`](operations/setup.md) on a new machine; every other procedure is named for its task.
|
|
33
|
+
|
|
34
|
+
## History
|
|
50
35
|
|
|
51
|
-
|
|
52
|
-
{list all operations docs — environment.md when present}
|
|
36
|
+
[history/](history/) — the records: `plans/` (one dated plan per unit of work, frozen at completion), `changelog/` (one entry per unit of work, indexed by [CHANGELOG.md](history/CHANGELOG.md)), `migrations/` (one-shot runbooks), `brainstorms/`, `research/`, and [backlog.md](history/backlog.md).
|
|
53
37
|
|
|
54
|
-
|
|
38
|
+
## Research and memory
|
|
55
39
|
|
|
56
|
-
|
|
40
|
+
[research/](research/) — in-progress brainstorm sessions and standing research. [memory/](memory/) — project memory, one file per agent.
|
|
57
41
|
|
|
58
|
-
|
|
42
|
+
## Notes for this repository
|
|
59
43
|
|
|
60
|
-
-
|
|
61
|
-
- [changelog/](history/changelog/) - Individual changelog entries
|
|
62
|
-
- [plans/](history/plans/) - Implementation plans (frozen at completion)
|
|
63
|
-
- [migrations/](history/migrations/) - One-shot migration runbooks (frozen once executed)
|
|
64
|
-
- [research/](history/research/) - Deep-research artifacts (one folder per topic)
|
|
65
|
-
- [backlog.md](history/backlog.md) - Tracked work items and technical debt
|
|
44
|
+
{The one place project-specific placement facts live, because `structure.md` is the harness's and is overwritten on every refresh: legacy folders kept as frozen provenance, a retired genre whose residue is still on disk, a local exception to a folder rule. One bullet each; delete the section when there are none.}
|
|
66
45
|
|
|
67
46
|
---
|
|
68
47
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Doctrine Documentation (docs/architecture/common/{concern}.md)
|
|
2
|
+
|
|
3
|
+
Truth document. Use for **one concern the platform handles everywhere**: authentication, authorization, storage, the API protocol, credentials, telemetry. One file per concern, named for the concern. A doctrine document owns no code and carries no `owns:`. A surface declares that it is bound by one through its own `applies:`.
|
|
4
|
+
|
|
5
|
+
**A doctrine document states the design and nothing beneath it** (`references/style-guide.md` § Shape — the model first, the detail after). It is the most stable document in the tree, because it says why the platform is shaped the way it is, and that changes far less often than the code. It explains no mechanism: no function, file, column, cookie attribute, header value or per-route error code, since each of those fails the stability test. The code graph answers what the code does. This document answers why, and which principles any new code in the concern must keep.
|
|
6
|
+
|
|
7
|
+
**Required:**
|
|
8
|
+
|
|
9
|
+
- the frontmatter
|
|
10
|
+
- the opening (the question, the boundary, and the authority sentence)
|
|
11
|
+
- **The model**, ending in numbered principles, each with its reason
|
|
12
|
+
- **The parts**: one table or diagram placing every part in the model, then how the parts relate
|
|
13
|
+
- **one section per part**
|
|
14
|
+
- **For the agent**
|
|
15
|
+
|
|
16
|
+
**Optional:**
|
|
17
|
+
|
|
18
|
+
- an `UNBUILT` block for decided-but-unbuilt intent
|
|
19
|
+
- **Related**
|
|
20
|
+
|
|
21
|
+
Nothing else. Expect one hundred and fifty to three hundred lines; past that, mechanism has usually crept in.
|
|
22
|
+
|
|
23
|
+
```yaml
|
|
24
|
+
---
|
|
25
|
+
type: doctrine # required tooling key
|
|
26
|
+
status: active # required — active | draft
|
|
27
|
+
objects: [principal, credential, device grant] # recommended — the concepts this doctrine governs, by their graph names
|
|
28
|
+
---
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
# {Concern}
|
|
33
|
+
|
|
34
|
+
{The question this concern answers for every request, row or run, in one sentence, bolded — then what it does not answer and which document does. One paragraph.}
|
|
35
|
+
|
|
36
|
+
{The authority sentence: this document states the design, not the code; for what does what, read the code roots in the footer or ask the code graph; where this document and the code disagree on *what*, the code is right; on *why* or a principle, the disagreement is a defect to raise.}
|
|
37
|
+
|
|
38
|
+
## The model
|
|
39
|
+
|
|
40
|
+
### {What the platform owns here, and what it delegates}
|
|
41
|
+
|
|
42
|
+
{The one framing fact a reader needs before anything else — e.g. "NuStack checks identity; it does not own it". Plain words, no addresses.}
|
|
43
|
+
|
|
44
|
+
### {The parties or parts}
|
|
45
|
+
|
|
46
|
+
{Who or what participates, in a short list of one-liners — each named in plain words first, its term of art in parentheses.}
|
|
47
|
+
|
|
48
|
+
### {The axis that organizes them}
|
|
49
|
+
|
|
50
|
+
{The one or two dimensions every part is placed on — e.g. "who issued the proof decides how it is checked". A small table is right here.}
|
|
51
|
+
|
|
52
|
+
### The principles
|
|
53
|
+
|
|
54
|
+
{Numbered invariants that hold across every part. Each one sentence of rule plus its reason. Later sections cite them by number.}
|
|
55
|
+
|
|
56
|
+
## {The parts} — e.g. "The credential families"
|
|
57
|
+
|
|
58
|
+
{One table: each part placed on the model's axes. Then "How the parts relate": what they share, where they meet, what keeps them apart, and the order in which they are tried — each as a bold-led paragraph with its reason. A role-level diagram (mermaid) where the relationships are a flow.}
|
|
59
|
+
|
|
60
|
+
## {Each part, one section per part} — e.g. "How each family works"
|
|
61
|
+
|
|
62
|
+
### {A part, named for who meets it} — e.g. "A person in a browser"
|
|
63
|
+
|
|
64
|
+
{Why the part exists, then the decisions it rests on, each with its reason, citing principles by number. A sequence diagram at role level where the part is a flow. Detail that a person or test can check is welcome; detail that only restates the code is not.}
|
|
65
|
+
|
|
66
|
+
## For the agent
|
|
67
|
+
|
|
68
|
+
{Pointers, not an inventory: the objects with one line each; the code roots as directories; any cross-cutting seam by name; the neighbouring doctrine with a purpose each.}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Marking what is decided and unbuilt
|
|
72
|
+
|
|
73
|
+
Code cannot reveal what it does not yet do, so decided-but-unbuilt intent is one of the few places a doctrine document records something the code does not. Wrap it in `<!-- UNBUILT:START — {what, and the evidence it is not built} -->` … `<!-- UNBUILT:END -->`, in plain words. A design decided *against* carries no marker.
|
|
74
|
+
|
|
75
|
+
### What a doctrine document is not
|
|
76
|
+
|
|
77
|
+
| It is not | That is |
|
|
78
|
+
| --- | --- |
|
|
79
|
+
| A tour of the code that implements the concern | The code graph |
|
|
80
|
+
| The catalog of routes, error codes or variables | An API reference or environment registry |
|
|
81
|
+
| The one feature that happens to use the concern most | That feature's own document, which `applies:` this one |
|
|
82
|
+
| The story of how the concern got this way | A decision record (`decisions/`) for the why, and the changelog for the when |
|
|
@@ -1,151 +1,59 @@
|
|
|
1
|
-
# Feature Documentation
|
|
1
|
+
# Feature Documentation (docs/features/{feature}.md)
|
|
2
2
|
|
|
3
|
-
Truth document. Use for: every feature in `docs/features
|
|
3
|
+
Truth document. Use for: every feature in `docs/features/` — something a person points at: an address, a command family, a named control. One file per feature, cut by the objects it designs, never by the code's routers, tables, plans or packages. The file persists from design (`status: draft`) to live reference (`status: active`).
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**A document is read by people, and its footer is for the agent** (`references/style-guide.md` § Documents are read by people). The body is plain English a newcomer reads top to bottom: what the thing is, why it is shaped that way, how a person meets it, what it promises. Every computable fact — columns, vocabularies, foreign keys, writers, routes, guards, codes, files — is the code graph's (`docs/architecture/systems/knowledge-layer.md`); a hand-typed copy is the defect this genre removes. The only code the body names is a cross-cutting seam the graph would not lead a reader to from the feature's own files: a middleware, a gate, a hook, a scheduler, a shared store. Everything else by plain name. Identifiers the graph cannot find from the roots live in the footer.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**A feature document runs deepest toward its end** (`references/style-guide.md` § Shape — the model first, the detail after). The opening and Why it exists are the model; How it works is the journey; **What it promises** goes as deep as the behaviour does: every state the surface can be in, every edge case, what a refusal tells the person, the exact wording wherever the wording itself is promised. That depth is earned because a person or a test checks it and the code cannot state it as intent. It still stops before mechanism: a promise reads "a removed membership shows a panel, not a redirect, because nothing can restore it", never which route answers it.
|
|
8
|
+
|
|
9
|
+
**Required:** the frontmatter, the goal line, **Why it exists**, **How it works**, **What it promises**, **For the agent**. **What was decided** once any choice was made the code cannot explain. **Known gaps** only when there is one. Nothing else — `doc-lint` blocks the retired mechanism sections (`mechanism-section`) and warns on identifiers in the body (`body-identifier`), on a section that is a list rather than prose (`list-not-prose`), and on three bold-led bullets in a row (`formula-run`). Forty to a hundred and twenty lines.
|
|
8
10
|
|
|
9
11
|
```yaml
|
|
10
12
|
---
|
|
11
|
-
|
|
12
|
-
module: billing # optional — single bounded-context label; omit if cross-cutting
|
|
13
|
-
keywords: [invoice, pdf] # required — discovery terms
|
|
13
|
+
type: feature # required tooling key — preserve, never narrate
|
|
14
14
|
status: active # required — active | draft | deprecated
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
objects: [device, credential grant] # required — the product objects this document designs, by their graph names
|
|
16
|
+
owns: ["packages/studio/src/server/routes/devices.ts"] # required until the graph computes ownership — the code this document speaks for; narrower glob wins
|
|
17
|
+
module: identity # optional — one bounded-context label
|
|
18
|
+
applies: [docs/architecture/common/security.md] # optional — doctrine this surface is BOUND by
|
|
18
19
|
---
|
|
19
20
|
```
|
|
20
21
|
|
|
21
|
-
> Declare the code paths it owns in its `owns:` frontmatter — projected at Draft, confirmed against real code at Active.
|
|
22
|
-
|
|
23
22
|
```
|
|
24
|
-
# {Feature
|
|
25
|
-
|
|
26
|
-
<!-- Lifecycle lives in the frontmatter `status:` key (active | draft | deprecated) — the single source of truth. Don't restate it in the body. -->
|
|
27
|
-
**Plan:** [history/plans/YYYY-MM-DD-feature-name.md](../history/plans/YYYY-MM-DD-feature-name.md) <!-- added once a plan exists -->
|
|
28
|
-
|
|
29
|
-
## Goal
|
|
30
|
-
|
|
31
|
-
One sentence. What this feature accomplishes, in user-facing terms.
|
|
32
|
-
|
|
33
|
-
## Goals & Non-Goals
|
|
34
|
-
|
|
35
|
-
**In scope**
|
|
36
|
-
- Bullet 1
|
|
37
|
-
- Bullet 2
|
|
38
|
-
|
|
39
|
-
**Out of scope**
|
|
40
|
-
- Bullet 1
|
|
41
|
-
|
|
42
|
-
## Background
|
|
43
|
-
|
|
44
|
-
Why this feature exists. The user need, the business trigger, the constraint that pushed it above the line. One paragraph.
|
|
45
|
-
|
|
46
|
-
(For Product-level work, also include: problem statement, key hypotheses, open questions.)
|
|
47
|
-
|
|
48
|
-
## Approach
|
|
49
|
-
|
|
50
|
-
High-level shape of the solution. ASCII diagram if it helps. Implementation specifics belong in the plan.
|
|
51
|
-
|
|
52
|
-
## Affected Modules
|
|
53
|
-
|
|
54
|
-
(From lld-mode research) Files, routes, components, slices this feature involves. Group by module.
|
|
55
|
-
|
|
56
|
-
| Module | Path | Role in this feature |
|
|
57
|
-
|--------|------|----------------------|
|
|
23
|
+
# {Feature name}
|
|
58
24
|
|
|
59
|
-
|
|
25
|
+
{One sentence: what this lets a person do.}
|
|
60
26
|
|
|
61
|
-
|
|
27
|
+
## Why it exists
|
|
62
28
|
|
|
63
|
-
|
|
29
|
+
{The need, the trigger, the constraint; what it is deliberately not for and why. Prose, one or two paragraphs. An AD is cited by number for the reasoning it holds, never restated.}
|
|
64
30
|
|
|
65
|
-
|
|
31
|
+
## How it works
|
|
66
32
|
|
|
67
|
-
|
|
33
|
+
{The feature as a person meets it, start to finish, present tense, plain words: what they see, what happens, what refuses them and what they are told. Introduce each term of art the first time. Name code only at a cross-cutting seam — "every request passes the organization gate before this page is reached (`requireAuth`)" — and nothing else. No columns, routes, guards, functions, files or diagrams: that is the graph's.}
|
|
68
34
|
|
|
69
|
-
|
|
35
|
+
## What was decided
|
|
70
36
|
|
|
71
|
-
|
|
72
|
-
|-----------------|---------------------|------------|
|
|
37
|
+
{The choices the code cannot explain, as two to four themed paragraphs. A rejected alternative is named only where a reader would reach for it. No formula; no bullet per decision.}
|
|
73
38
|
|
|
74
|
-
##
|
|
39
|
+
## What it promises
|
|
75
40
|
|
|
76
|
-
|
|
41
|
+
{The guarantees and refusals a person or a test can check, grouped by theme, as short paragraphs — or a short list only where the items are genuinely parallel one-liners. Positive where possible. This is the deepest section: each state, edge case and refusal the person can meet, and the wording where it is promised. A table of states is right when the states are genuinely parallel.}
|
|
77
42
|
|
|
78
|
-
##
|
|
43
|
+
## Known gaps
|
|
79
44
|
|
|
80
|
-
|
|
81
|
-
**Goal:** What it accomplishes
|
|
82
|
-
**Input:**
|
|
83
|
-
- `param1` (type) - Description
|
|
84
|
-
- `param2` (type) - Description
|
|
85
|
-
**Output:** Return type and shape
|
|
86
|
-
**Side Effects:** DB writes, API calls, state updates
|
|
45
|
+
{One paragraph, only when the code does not keep something the document would otherwise promise. What is missing and what it would take; the backlog row carries the rest.}
|
|
87
46
|
|
|
88
|
-
|
|
89
|
-
**Goal:** What it provides
|
|
90
|
-
**Returns:** `{ data, loading, error }`
|
|
91
|
-
**Triggers:** On mount, on change, manual
|
|
47
|
+
## For the agent
|
|
92
48
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
**Frontend (at persistence):**
|
|
96
|
-
{
|
|
97
|
-
"data": [],
|
|
98
|
-
"changes": { "added": [], "updated": [], "removed": [] }
|
|
99
|
-
// "cache": {} // UNUSED: dead key; kept so old persisted state rehydrates
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
**Storage:** localStorage `app_state`
|
|
103
|
-
|
|
104
|
-
**Backend:**
|
|
105
|
-
export const resources = pgTable('resources', {
|
|
106
|
-
id: text('id').primaryKey(),
|
|
107
|
-
data: jsonb('data'),
|
|
108
|
-
// oldField: text('old_field'), // UNUSED: dead column; nothing reads it
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
**API Shape:**
|
|
112
|
-
{ "status": 1, "data": {...} }
|
|
113
|
-
|
|
114
|
-
## Redux (if applicable)
|
|
115
|
-
|
|
116
|
-
**Slice:** `sliceName`
|
|
117
|
-
|
|
118
|
-
**Actions:**
|
|
119
|
-
- `setData(payload)` - Replace all data
|
|
120
|
-
- `upsertItem(item)` - Add or update single item
|
|
121
|
-
|
|
122
|
-
**Selectors:**
|
|
123
|
-
- `selectAll(state)` → `Array<Item>`
|
|
124
|
-
- `selectById(state, id)` → `Item | undefined`
|
|
125
|
-
|
|
126
|
-
## Files
|
|
127
|
-
|
|
128
|
-
- `src/path/to/main.js` - Core logic
|
|
129
|
-
- `src/path/to/hooks.js` - React hooks
|
|
130
|
-
|
|
131
|
-
## Open Questions
|
|
132
|
-
|
|
133
|
-
- Question — who decides, by when
|
|
134
|
-
- (Resolved questions: fold the resolution into Background.)
|
|
135
|
-
|
|
136
|
-
## Risks
|
|
137
|
-
|
|
138
|
-
- Risk — likelihood / impact / mitigation
|
|
49
|
+
Objects: {object} — {one plain-words line}; {object} — {…}. Pages {`/…`}. Code roots {`packages/…/src/…/`} — directories, where the graph should start. Seams {`name`} only where the roots would not lead to them. Checklist `tests/{feature}.md`. See also {neighbour} ({why}), {neighbour} ({why}). No tables, routes or symbol rosters — those are the graph's.
|
|
139
50
|
```
|
|
140
51
|
|
|
141
|
-
###
|
|
142
|
-
|
|
143
|
-
Sections fill in across the feature's lifecycle. No template swap — they accumulate in place.
|
|
52
|
+
### Lifecycle
|
|
144
53
|
|
|
145
|
-
| When |
|
|
146
|
-
| --- | --- |
|
|
147
|
-
|
|
|
148
|
-
|
|
|
149
|
-
| Status: Draft → Active (implementation lands) | Architecture, Key Functions, State, Redux, Files; `owns:` globs confirmed against real code | Main agent (from actual code) |
|
|
54
|
+
| When | What is written |
|
|
55
|
+
| --- | --- |
|
|
56
|
+
| `status: draft` (design) | goal, Why it exists, What was decided, What it promises (with gaps named), For the agent; the LLD research goes to the plan |
|
|
57
|
+
| `status: active` | How it works written from the shipped surface; `owns:` confirmed against real code |
|
|
150
58
|
|
|
151
|
-
|
|
59
|
+
A feature whose code is gone is deleted, or `status: deprecated` with its footer pointing at what replaced it.
|