@plebeiantech/lat.md 0.12.3-fork.4 → 0.12.3-fork.6
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/dist/src/cli/check-coverage.d.ts +2 -0
- package/dist/src/cli/check-coverage.js +71 -0
- package/dist/src/cli/check-mode.d.ts +6 -0
- package/dist/src/cli/check-mode.js +44 -2
- package/dist/src/cli/check.js +5 -1
- package/dist/src/cli/fork-instructions.d.ts +19 -0
- package/dist/src/cli/fork-instructions.js +183 -0
- package/dist/src/cli/fork-scaffold.d.ts +16 -0
- package/dist/src/cli/fork-scaffold.js +129 -0
- package/dist/src/cli/index.js +29 -4
- package/dist/src/cli/init.js +4 -0
- package/dist/src/cli/search.js +6 -4
- package/dist/src/cli/ui-build.d.ts +4 -0
- package/dist/src/cli/ui-build.js +13 -0
- package/dist/src/cli/ui.js +18 -2
- package/dist/src/code-refs.js +25 -4
- package/dist/src/fork/upstream-drift-cli.d.ts +1 -0
- package/dist/src/fork/upstream-drift-cli.js +2 -0
- package/dist/src/fork/upstream-drift.d.ts +26 -0
- package/dist/src/fork/upstream-drift.js +185 -0
- package/dist/src/fork/upstream-guard-cli.d.ts +1 -0
- package/dist/src/fork/upstream-guard-cli.js +2 -0
- package/dist/src/fork/upstream-guard.d.ts +72 -0
- package/dist/src/fork/upstream-guard.js +445 -0
- package/dist/src/init-version.d.ts +1 -1
- package/dist/src/init-version.js +1 -1
- package/dist/src/lattice.d.ts +2 -1
- package/dist/src/lattice.js +1 -0
- package/dist/src/search/search.d.ts +1 -0
- package/dist/src/search/search.js +15 -9
- package/dist/src/view/client/assets/index-VZwPPiFE.js +334 -0
- package/dist/src/view/client/assets/index-W8ktvNj9.css +1 -0
- package/dist/src/view/client/assets/logo-CNF8zs47.svg +182 -0
- package/dist/src/view/client/index.html +2 -2
- package/dist/src/view/git-diff.js +17 -0
- package/dist/src/view/graph.d.ts +6 -0
- package/dist/src/view/graph.js +176 -0
- package/dist/src/view/markdown.d.ts +5 -1
- package/dist/src/view/markdown.js +112 -25
- package/dist/src/view/protocol.d.ts +43 -0
- package/dist/src/view/protocol.js +1 -1
- package/dist/src/view/references.d.ts +2 -0
- package/dist/src/view/references.js +30 -8
- package/dist/src/view/repository.d.ts +5 -2
- package/dist/src/view/repository.js +13 -12
- package/dist/src/view/search.js +1 -0
- package/dist/src/view/server.d.ts +3 -0
- package/dist/src/view/server.js +42 -9
- package/dist/src/view/source-target.d.ts +8 -0
- package/dist/src/view/source-target.js +22 -0
- package/dist/src/view/static-build.d.ts +16 -0
- package/dist/src/view/static-build.js +408 -0
- package/dist/src/view/static-protocol.d.ts +23 -0
- package/dist/src/view/static-protocol.js +10 -0
- package/dist/src/view/store.d.ts +9 -1
- package/dist/src/view/store.js +82 -15
- package/dist/src/view/table-of-contents.d.ts +9 -0
- package/dist/src/view/table-of-contents.js +103 -0
- package/package.json +3 -1
- package/templates/AGENTS.md +2 -13
- package/templates/fork/conventions.md +95 -0
- package/templates/skill/SKILL.md +3 -14
- package/dist/src/view/client/assets/index-B0n0u03y.css +0 -1
- package/dist/src/view/client/assets/index-xm2_px4q.js +0 -23
package/dist/src/view/store.js
CHANGED
|
@@ -8,10 +8,14 @@ import { toPosix } from '../walk.js';
|
|
|
8
8
|
import { renderMarkdown } from './markdown.js';
|
|
9
9
|
import { buildViewDiagnostics } from './diagnostics.js';
|
|
10
10
|
import { buildGitDiffTree } from './git-diff.js';
|
|
11
|
+
import { buildViewGraph } from './graph.js';
|
|
12
|
+
import { buildViewTableOfContents } from './table-of-contents.js';
|
|
11
13
|
import { emptyViewGitSnapshot, findViewGitRepository, readViewGitSnapshot, sameViewGitSnapshot, } from './git.js';
|
|
14
|
+
import { DEFAULT_VIEW_LOGO_TEXT } from './protocol.js';
|
|
12
15
|
import { createMarkdownWikiLinkResolver, getViewSource, ViewDocumentNotFoundError, } from './repository.js';
|
|
13
16
|
import { buildViewReferenceIndex, parseViewMarkdownFile, renderSectionBackReferences, } from './references.js';
|
|
14
17
|
const DEFAULT_DEBOUNCE_MS = 75;
|
|
18
|
+
const DEFAULT_GIT_POLL_MS = 2_000;
|
|
15
19
|
function isInside(root, candidate) {
|
|
16
20
|
const rel = relative(root, candidate);
|
|
17
21
|
return (rel === '' ||
|
|
@@ -21,6 +25,10 @@ function projectPath(projectRoot, path) {
|
|
|
21
25
|
const normalized = isAbsolute(path) ? relative(projectRoot, path) : path;
|
|
22
26
|
return toPosix(normalized).replace(/^\.\//, '');
|
|
23
27
|
}
|
|
28
|
+
function excludedCodePath(projectRoot, path, excludedPaths) {
|
|
29
|
+
const absolutePath = resolve(projectRoot, path);
|
|
30
|
+
return excludedPaths.some((root) => isInside(resolve(root), absolutePath));
|
|
31
|
+
}
|
|
24
32
|
function sourcePath(path) {
|
|
25
33
|
return SOURCE_EXTENSIONS.has(extname(path).toLowerCase());
|
|
26
34
|
}
|
|
@@ -64,11 +72,14 @@ async function loadCodeReferenceFiles(projectRoot, refs) {
|
|
|
64
72
|
}));
|
|
65
73
|
return files;
|
|
66
74
|
}
|
|
67
|
-
async function scanCodeState(projectRoot) {
|
|
75
|
+
async function scanCodeState(projectRoot, excludedPaths = []) {
|
|
68
76
|
const scan = await scanCodeRefs(projectRoot);
|
|
69
|
-
const
|
|
77
|
+
const allowed = (path) => !excludedCodePath(projectRoot, path, excludedPaths);
|
|
78
|
+
const files = scan.files.filter(allowed);
|
|
79
|
+
const refs = scan.refs.filter((ref) => allowed(ref.file));
|
|
80
|
+
const scope = new Set(files.map((path) => projectPath(projectRoot, path)));
|
|
70
81
|
return {
|
|
71
|
-
files: await loadCodeReferenceFiles(projectRoot,
|
|
82
|
+
files: await loadCodeReferenceFiles(projectRoot, refs),
|
|
72
83
|
scope,
|
|
73
84
|
};
|
|
74
85
|
}
|
|
@@ -89,18 +100,21 @@ function viewIndex(latDir, paths, diagnostics, git) {
|
|
|
89
100
|
files: Object.fromEntries([...git.files].map(([path, file]) => [path, file.status])),
|
|
90
101
|
}
|
|
91
102
|
: null,
|
|
103
|
+
logoText: DEFAULT_VIEW_LOGO_TEXT,
|
|
92
104
|
};
|
|
93
105
|
}
|
|
94
106
|
async function buildSnapshot(latDir, projectRoot, markdownFiles, codeFiles, git, generation, markdownGeneration) {
|
|
95
107
|
const files = new Map([...markdownFiles].sort(([left], [right]) => left.localeCompare(right)));
|
|
96
108
|
const allSections = [...files.values()].flatMap((file) => file.sections);
|
|
97
109
|
const diagnostics = await buildViewDiagnostics(files.values(), codeFiles.values(), allSections, projectRoot);
|
|
110
|
+
const references = buildViewReferenceIndex(files.values(), codeFiles.values(), allSections);
|
|
98
111
|
return {
|
|
99
112
|
generation,
|
|
100
113
|
markdownGeneration,
|
|
101
114
|
files,
|
|
102
115
|
allSections,
|
|
103
|
-
references
|
|
116
|
+
references,
|
|
117
|
+
graph: buildViewGraph(files.values(), codeFiles.values(), allSections, diagnostics, git, generation),
|
|
104
118
|
diagnostics,
|
|
105
119
|
git,
|
|
106
120
|
index: viewIndex(latDir, [...files.keys()], diagnostics, git),
|
|
@@ -121,6 +135,8 @@ export class ViewStore {
|
|
|
121
135
|
watcher = null;
|
|
122
136
|
pendingPaths = new Set();
|
|
123
137
|
debounceTimer = null;
|
|
138
|
+
gitPollTimer = null;
|
|
139
|
+
gitPollQueued = false;
|
|
124
140
|
refreshTail = Promise.resolve();
|
|
125
141
|
closed = false;
|
|
126
142
|
constructor(latDir, projectRoot, realLatDir, snapshot, codeFiles, codeScope, gitRepository, options) {
|
|
@@ -140,6 +156,7 @@ export class ViewStore {
|
|
|
140
156
|
return this.snapshotValue.markdownGeneration;
|
|
141
157
|
}
|
|
142
158
|
startWatching() {
|
|
159
|
+
this.startGitPolling();
|
|
143
160
|
if (this.options.watch === false || this.watcher)
|
|
144
161
|
return;
|
|
145
162
|
try {
|
|
@@ -159,6 +176,9 @@ export class ViewStore {
|
|
|
159
176
|
getIndex() {
|
|
160
177
|
return this.snapshotValue.index;
|
|
161
178
|
}
|
|
179
|
+
getGraph() {
|
|
180
|
+
return this.snapshotValue.graph;
|
|
181
|
+
}
|
|
162
182
|
async getDocument(requestedPath) {
|
|
163
183
|
if (!requestedPath ||
|
|
164
184
|
requestedPath.includes('\\') ||
|
|
@@ -171,19 +191,39 @@ export class ViewStore {
|
|
|
171
191
|
if (!file) {
|
|
172
192
|
throw new ViewDocumentNotFoundError('Markdown document not found');
|
|
173
193
|
}
|
|
174
|
-
const resolver = await createMarkdownWikiLinkResolver(this.latDir, requestedPath, snapshot.allSections);
|
|
194
|
+
const resolver = await createMarkdownWikiLinkResolver(this.latDir, requestedPath, snapshot.allSections, snapshot.references);
|
|
175
195
|
const rendered = await renderMarkdown(file.content, requestedPath, resolver, { errors: [...(snapshot.diagnostics.get(requestedPath) ?? [])] }, file.tree);
|
|
196
|
+
const errors = [...(snapshot.diagnostics.get(requestedPath) ?? [])];
|
|
176
197
|
const gitFile = snapshot.git.files.get(requestedPath);
|
|
177
|
-
const
|
|
178
|
-
?
|
|
198
|
+
const gitTree = gitFile
|
|
199
|
+
? buildGitDiffTree(gitFile.baseContent, file.content, file.tree)
|
|
200
|
+
: null;
|
|
201
|
+
const gitRendered = gitTree
|
|
202
|
+
? await renderMarkdown(file.content, requestedPath, resolver, { errors }, gitTree)
|
|
179
203
|
: null;
|
|
180
|
-
const errors = [...(snapshot.diagnostics.get(requestedPath) ?? [])];
|
|
181
204
|
return {
|
|
182
205
|
path: requestedPath,
|
|
183
206
|
...rendered,
|
|
184
207
|
gitHtml: gitRendered?.html ?? null,
|
|
208
|
+
tableOfContents: buildViewTableOfContents(file.sections, file.tree, {
|
|
209
|
+
errors,
|
|
210
|
+
gitTree,
|
|
211
|
+
}),
|
|
212
|
+
graphNodeIds: Object.fromEntries(snapshot.graph.nodes
|
|
213
|
+
.filter((node) => node.documentPath === requestedPath && node.kind === 'document')
|
|
214
|
+
.map((node) => {
|
|
215
|
+
const hash = new URL(node.url, 'http://lat.local').hash.slice(1);
|
|
216
|
+
let headingId = hash;
|
|
217
|
+
try {
|
|
218
|
+
headingId = decodeURIComponent(hash);
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// Keep malformed fragments as-is; they cannot collide with valid ids.
|
|
222
|
+
}
|
|
223
|
+
return [headingId, node.id];
|
|
224
|
+
})),
|
|
185
225
|
errors,
|
|
186
|
-
backReferences: await renderSectionBackReferences(snapshot.references, file.sections, this.latDir, this.projectRoot, (path) => createMarkdownWikiLinkResolver(this.latDir, path, snapshot.allSections)),
|
|
226
|
+
backReferences: await renderSectionBackReferences(snapshot.references, file.sections, this.latDir, this.projectRoot, (path) => createMarkdownWikiLinkResolver(this.latDir, path, snapshot.allSections, snapshot.references)),
|
|
187
227
|
frontmatter: {
|
|
188
228
|
requireCodeMention: parseFrontmatter(file.content).requireCodeMention === true,
|
|
189
229
|
},
|
|
@@ -197,7 +237,10 @@ export class ViewStore {
|
|
|
197
237
|
if (this.closed)
|
|
198
238
|
return Promise.resolve();
|
|
199
239
|
const normalized = paths.map((path) => path ? projectPath(this.projectRoot, path) : '');
|
|
200
|
-
|
|
240
|
+
return this.queueRefresh(normalized, false);
|
|
241
|
+
}
|
|
242
|
+
queueRefresh(paths, pollGit) {
|
|
243
|
+
const refresh = this.refreshTail.then(() => this.applyRefresh(paths, pollGit));
|
|
201
244
|
this.refreshTail = refresh.catch(() => { });
|
|
202
245
|
return refresh;
|
|
203
246
|
}
|
|
@@ -206,6 +249,9 @@ export class ViewStore {
|
|
|
206
249
|
if (this.debounceTimer)
|
|
207
250
|
clearTimeout(this.debounceTimer);
|
|
208
251
|
this.debounceTimer = null;
|
|
252
|
+
if (this.gitPollTimer)
|
|
253
|
+
clearInterval(this.gitPollTimer);
|
|
254
|
+
this.gitPollTimer = null;
|
|
209
255
|
this.watcher?.close();
|
|
210
256
|
this.watcher = null;
|
|
211
257
|
await this.refreshTail;
|
|
@@ -226,6 +272,24 @@ export class ViewStore {
|
|
|
226
272
|
});
|
|
227
273
|
}, this.options.debounceMs ?? DEFAULT_DEBOUNCE_MS);
|
|
228
274
|
}
|
|
275
|
+
startGitPolling() {
|
|
276
|
+
const interval = this.options.gitPollMs ?? DEFAULT_GIT_POLL_MS;
|
|
277
|
+
if (!this.gitRepository || interval <= 0 || this.gitPollTimer)
|
|
278
|
+
return;
|
|
279
|
+
this.gitPollTimer = setInterval(() => {
|
|
280
|
+
if (this.closed || this.gitPollQueued)
|
|
281
|
+
return;
|
|
282
|
+
this.gitPollQueued = true;
|
|
283
|
+
void this.queueRefresh([], true)
|
|
284
|
+
.catch((error) => {
|
|
285
|
+
process.stderr.write(`lat ui git: ${error.message}\n`);
|
|
286
|
+
})
|
|
287
|
+
.finally(() => {
|
|
288
|
+
this.gitPollQueued = false;
|
|
289
|
+
});
|
|
290
|
+
}, interval);
|
|
291
|
+
this.gitPollTimer.unref();
|
|
292
|
+
}
|
|
229
293
|
async readMarkdownFile(absolutePath) {
|
|
230
294
|
let realFile;
|
|
231
295
|
try {
|
|
@@ -239,7 +303,7 @@ export class ViewStore {
|
|
|
239
303
|
const content = await readFile(realFile, 'utf8');
|
|
240
304
|
return parseViewMarkdownFile(absolutePath, content, this.latDir, this.projectRoot);
|
|
241
305
|
}
|
|
242
|
-
async applyRefresh(paths) {
|
|
306
|
+
async applyRefresh(paths, pollGit) {
|
|
243
307
|
const fullRefresh = paths.includes('');
|
|
244
308
|
const latPath = projectPath(this.projectRoot, this.latDir);
|
|
245
309
|
const touchesMarkdown = fullRefresh ||
|
|
@@ -298,7 +362,7 @@ export class ViewStore {
|
|
|
298
362
|
}
|
|
299
363
|
linkedResourceChanged = paths.some((path) => path.startsWith(`${latPath}/`) && !path.toLowerCase().endsWith('.md'));
|
|
300
364
|
}
|
|
301
|
-
if (touchesMarkdown && this.gitRepository) {
|
|
365
|
+
if ((touchesMarkdown || pollGit) && this.gitRepository) {
|
|
302
366
|
try {
|
|
303
367
|
const nextGit = await readViewGitSnapshot(this.gitRepository, markdownFiles);
|
|
304
368
|
gitChanged = !sameViewGitSnapshot(git, nextGit);
|
|
@@ -308,12 +372,15 @@ export class ViewStore {
|
|
|
308
372
|
process.stderr.write(`lat ui git: ${error.message}\n`);
|
|
309
373
|
}
|
|
310
374
|
}
|
|
311
|
-
const codePaths = paths.filter((path) => path &&
|
|
375
|
+
const codePaths = paths.filter((path) => path &&
|
|
376
|
+
sourcePath(path) &&
|
|
377
|
+
!obviouslyIgnoredCodePath(path, latPath) &&
|
|
378
|
+
!excludedCodePath(this.projectRoot, path, this.options.codeExcludePaths ?? []));
|
|
312
379
|
const refreshCodeScope = fullRefresh ||
|
|
313
380
|
paths.some((path) => path === '.gitignore' || path.endsWith('/.gitignore')) ||
|
|
314
381
|
codePaths.some((path) => !this.codeScope.has(path) && !this.ignoredCodePaths.has(path));
|
|
315
382
|
if (refreshCodeScope) {
|
|
316
|
-
const nextCode = await scanCodeState(this.projectRoot);
|
|
383
|
+
const nextCode = await scanCodeState(this.projectRoot, this.options.codeExcludePaths);
|
|
317
384
|
codeFiles = nextCode.files;
|
|
318
385
|
this.codeScope = nextCode.scope;
|
|
319
386
|
this.ignoredCodePaths.clear();
|
|
@@ -369,7 +436,7 @@ export async function createViewStore(latDir, projectRoot, options = {}) {
|
|
|
369
436
|
const [realLatDir, markdownPaths, codeState, gitRepository] = await Promise.all([
|
|
370
437
|
realpath(latDir),
|
|
371
438
|
listLatticeFiles(latDir),
|
|
372
|
-
scanCodeState(projectRoot),
|
|
439
|
+
scanCodeState(projectRoot, options.codeExcludePaths),
|
|
373
440
|
options.git === false
|
|
374
441
|
? Promise.resolve(null)
|
|
375
442
|
: findViewGitRepository(projectRoot, latDir),
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Root } from 'mdast';
|
|
2
|
+
import { type Section } from '../lattice.js';
|
|
3
|
+
import type { ViewDocumentError, ViewDocumentTocItem } from './protocol.js';
|
|
4
|
+
export type ViewTableOfContentsOptions = {
|
|
5
|
+
errors?: readonly ViewDocumentError[];
|
|
6
|
+
gitTree?: Root | null;
|
|
7
|
+
};
|
|
8
|
+
/** Project rendered headings into the document's local navigation. */
|
|
9
|
+
export declare function buildViewTableOfContents(sections: Section[], tree: Root, options?: ViewTableOfContentsOptions): ViewDocumentTocItem[];
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { visit } from 'unist-util-visit';
|
|
2
|
+
import { flattenSections } from '../lattice.js';
|
|
3
|
+
function headingText(node) {
|
|
4
|
+
if (!node || typeof node !== 'object')
|
|
5
|
+
return '';
|
|
6
|
+
const value = node;
|
|
7
|
+
if (value.type === 'text' || value.type === 'inlineCode') {
|
|
8
|
+
return value.value ?? '';
|
|
9
|
+
}
|
|
10
|
+
if (value.type === 'image' || value.type === 'imageReference') {
|
|
11
|
+
return value.alt ?? '';
|
|
12
|
+
}
|
|
13
|
+
if (value.type === 'wikiLink') {
|
|
14
|
+
return value.data?.alias ?? value.value ?? '';
|
|
15
|
+
}
|
|
16
|
+
return value.children?.map(headingText).join('') ?? '';
|
|
17
|
+
}
|
|
18
|
+
function sectionAtLine(sections, line) {
|
|
19
|
+
let owner = null;
|
|
20
|
+
for (const section of sections) {
|
|
21
|
+
if (line < section.startLine || line > section.endLine)
|
|
22
|
+
continue;
|
|
23
|
+
if (!owner || section.depth >= owner.depth)
|
|
24
|
+
owner = section;
|
|
25
|
+
}
|
|
26
|
+
return owner;
|
|
27
|
+
}
|
|
28
|
+
function classNames(node) {
|
|
29
|
+
const value = node.data?.hProperties?.className;
|
|
30
|
+
if (Array.isArray(value))
|
|
31
|
+
return value.map(String);
|
|
32
|
+
return value ? [String(value)] : [];
|
|
33
|
+
}
|
|
34
|
+
function hasGitChanges(node) {
|
|
35
|
+
if (classNames(node).some((name) => name.startsWith('git-'))) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
if (!('children' in node) || !Array.isArray(node.children))
|
|
39
|
+
return false;
|
|
40
|
+
return node.children.some((child) => hasGitChanges(child));
|
|
41
|
+
}
|
|
42
|
+
function gitChangedSections(sections, tree) {
|
|
43
|
+
const changed = new Set();
|
|
44
|
+
if (!tree)
|
|
45
|
+
return changed;
|
|
46
|
+
const headingStack = [];
|
|
47
|
+
const rootSection = sections[0] ?? null;
|
|
48
|
+
for (const node of tree.children) {
|
|
49
|
+
if (node.type === 'heading') {
|
|
50
|
+
while (headingStack.length > 0 &&
|
|
51
|
+
headingStack[headingStack.length - 1].depth >= node.depth) {
|
|
52
|
+
headingStack.pop();
|
|
53
|
+
}
|
|
54
|
+
const line = node.position?.start.line;
|
|
55
|
+
const section = line
|
|
56
|
+
? (sections.find((candidate) => candidate.startLine === line && candidate.depth === node.depth) ?? null)
|
|
57
|
+
: null;
|
|
58
|
+
if (section)
|
|
59
|
+
headingStack.push(section);
|
|
60
|
+
if (hasGitChanges(node)) {
|
|
61
|
+
const owner = section ?? headingStack[headingStack.length - 1] ?? rootSection;
|
|
62
|
+
if (owner)
|
|
63
|
+
changed.add(owner.id);
|
|
64
|
+
}
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (!hasGitChanges(node))
|
|
68
|
+
continue;
|
|
69
|
+
const line = node.position?.start.line;
|
|
70
|
+
const owner = (line ? sectionAtLine(sections, line) : null) ??
|
|
71
|
+
headingStack[headingStack.length - 1] ??
|
|
72
|
+
rootSection;
|
|
73
|
+
if (owner)
|
|
74
|
+
changed.add(owner.id);
|
|
75
|
+
}
|
|
76
|
+
return changed;
|
|
77
|
+
}
|
|
78
|
+
/** Project rendered headings into the document's local navigation. */
|
|
79
|
+
export function buildViewTableOfContents(sections, tree, options = {}) {
|
|
80
|
+
const flatSections = flattenSections(sections);
|
|
81
|
+
const titles = [];
|
|
82
|
+
visit(tree, 'heading', (node) => {
|
|
83
|
+
titles.push(headingText(node));
|
|
84
|
+
});
|
|
85
|
+
const errorCounts = new Map();
|
|
86
|
+
for (const error of options.errors ?? []) {
|
|
87
|
+
const owner = sectionAtLine(flatSections, error.line);
|
|
88
|
+
if (owner)
|
|
89
|
+
errorCounts.set(owner.id, (errorCounts.get(owner.id) ?? 0) + 1);
|
|
90
|
+
}
|
|
91
|
+
const gitChanges = gitChangedSections(flatSections, options.gitTree);
|
|
92
|
+
return flatSections.flatMap((section, index) => section.githubSlug
|
|
93
|
+
? [
|
|
94
|
+
{
|
|
95
|
+
id: section.githubSlug,
|
|
96
|
+
title: titles[index] || section.heading,
|
|
97
|
+
depth: section.depth,
|
|
98
|
+
errorCount: errorCounts.get(section.id) ?? 0,
|
|
99
|
+
hasGitChanges: gitChanges.has(section.id),
|
|
100
|
+
},
|
|
101
|
+
]
|
|
102
|
+
: []);
|
|
103
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plebeiantech/lat.md",
|
|
3
|
-
"version": "0.12.3-fork.
|
|
3
|
+
"version": "0.12.3-fork.6",
|
|
4
4
|
"description": "A knowledge graph for your codebase, written in markdown",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -32,12 +32,14 @@
|
|
|
32
32
|
"@vitejs/plugin-react": "^5.0.0",
|
|
33
33
|
"@types/mdast": "^4.0.4",
|
|
34
34
|
"@types/node": "^25.3.0",
|
|
35
|
+
"graphology": "^0.26.0",
|
|
35
36
|
"highlight.js": "^11.12.0",
|
|
36
37
|
"mdast-util-from-markdown": "^2.0.3",
|
|
37
38
|
"micromark-util-types": "^2.0.2",
|
|
38
39
|
"prettier": "^3.8.1",
|
|
39
40
|
"react": "^19.0.0",
|
|
40
41
|
"react-dom": "^19.0.0",
|
|
42
|
+
"sigma": "^3.0.3",
|
|
41
43
|
"tsx": "^4.21.0",
|
|
42
44
|
"typescript": "^5.7.0",
|
|
43
45
|
"vite": "^7.0.0",
|
package/templates/AGENTS.md
CHANGED
|
@@ -17,6 +17,8 @@ After EVERY task, before responding to the user:
|
|
|
17
17
|
|
|
18
18
|
This project uses [lat.md](https://www.npmjs.com/package/lat.md) to maintain a structured knowledge graph of its architecture, design decisions, and test specs in the `lat.md/` directory. It is a set of cross-linked markdown files that describe **what** this project does and **why** — the domain concepts, key design decisions, business logic, and test specifications. Use it to ground your work in the actual architecture rather than guessing. Do not treat `lat.md/` as a journal or changelog; it should be a focused snapshot of current or planned state, and should not grow just to note insignificant details.
|
|
19
19
|
|
|
20
|
+
Project-specific lat documentation belongs in `lat.md/`. Do not modify this generated instruction file or a generated `lat-md` `SKILL.md` to record project guidance: both are owned by lat tooling and may be replaced by a later `lat init`.
|
|
21
|
+
|
|
20
22
|
# Commands
|
|
21
23
|
|
|
22
24
|
```bash
|
|
@@ -103,16 +105,3 @@ Details about this child topic.
|
|
|
103
105
|
```
|
|
104
106
|
|
|
105
107
|
The second example is invalid because `Bad Section` has no leading paragraph. `lat check` validates this rule and reports errors for missing or overly long leading paragraphs.
|
|
106
|
-
|
|
107
|
-
# Diátaxis modes
|
|
108
|
-
|
|
109
|
-
Documents under `lat.md/tutorials/`, `lat.md/how-to/`, `lat.md/reference/`, and `lat.md/explanation/` are checked against their Diátaxis mode. Declare it explicitly with nested frontmatter, or rely on the directory:
|
|
110
|
-
|
|
111
|
-
```markdown
|
|
112
|
-
---
|
|
113
|
-
lat:
|
|
114
|
-
mode: reference
|
|
115
|
-
---
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
If a document is in one of the four mode directories, the declared mode must match that directory. Each mode enforces one shape rule: tutorials need an ordered list of steps and a stated outcome; how-to guides need an ordered list of steps; reference docs must not contain narrative prose (no second paragraph under a heading); explanations must not give imperative commands. Run `lat check mode` to validate.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# lat.md conventions this project enforces
|
|
2
|
+
|
|
3
|
+
`lat check` fails on every rule below. They are not style preferences. This
|
|
4
|
+
block is generated and a later `lat init` replaces it — record project guidance
|
|
5
|
+
in `lat.md/`, never here.
|
|
6
|
+
|
|
7
|
+
## Every document goes in a Diátaxis mode directory
|
|
8
|
+
|
|
9
|
+
A document belongs in exactly one of four subdirectories, and `lat check mode`
|
|
10
|
+
enforces one shape rule for each:
|
|
11
|
+
|
|
12
|
+
| Directory | Holds | Shape rule |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| `lat.md/tutorials/` | a guided first pass for someone new | an ordered list of steps, and a stated outcome |
|
|
15
|
+
| `lat.md/how-to/` | a recipe for a task that recurs | an ordered list of steps |
|
|
16
|
+
| `lat.md/reference/` | lookup facts: fields, states, limits, names | no narrative prose — no second paragraph under a heading |
|
|
17
|
+
| `lat.md/explanation/` | why the design is what it is | no imperative commands |
|
|
18
|
+
|
|
19
|
+
Do not write documents at the top of `lat.md/`. The root index is the only file
|
|
20
|
+
that belongs there. When it carries `require-mode: true`, a document with no
|
|
21
|
+
declared mode and no mode directory is a `lat check` error.
|
|
22
|
+
|
|
23
|
+
**A document that fails its mode is usually two documents.** A file that lists
|
|
24
|
+
the states *and* argues for the design is mixed: the lookup half goes in
|
|
25
|
+
`reference/`, the reasoning half in `explanation/`, and each links the other.
|
|
26
|
+
Filing it under one mode and softening the prose until the check passes is the
|
|
27
|
+
wrong repair. So is leaving it at the top level where no check can see it.
|
|
28
|
+
|
|
29
|
+
Classify before writing, not after. If a `diataxis` skill is available, use it.
|
|
30
|
+
|
|
31
|
+
## Every project has at least one `@lat:` code ref
|
|
32
|
+
|
|
33
|
+
`lat check` fails a tree that has documents and no `@lat:` ref anywhere in the
|
|
34
|
+
codebase. A graph nothing points at cannot be reached from the code, which is
|
|
35
|
+
the only reason it exists.
|
|
36
|
+
|
|
37
|
+
Start at the application entrypoint — `config/application.rb`, `src/index.ts`,
|
|
38
|
+
`main.go`, `manage.py`, whatever a new reader opens first — and point it at the
|
|
39
|
+
root index:
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
# @lat: [[lat#Acme]]
|
|
43
|
+
module Acme
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Then one ref per section that source code actually implements, placed at that
|
|
47
|
+
code. Never in a block at the top of a file.
|
|
48
|
+
|
|
49
|
+
### An `@lat:` pointer is exempt from this project's comment conventions
|
|
50
|
+
|
|
51
|
+
A repository that minimises, budgets, or forbids comments is right about prose
|
|
52
|
+
comments and does not reach this one. An `@lat:` line is a machine directive,
|
|
53
|
+
in the same class as `# frozen_string_literal:` or `// eslint-disable`. It
|
|
54
|
+
carries no rationale, asserts nothing a reader could dispute, and cannot rot
|
|
55
|
+
quietly — `lat check` fails the moment its target moves or disappears.
|
|
56
|
+
|
|
57
|
+
Add the ref. Then, if the reasoning that would have been a comment is worth
|
|
58
|
+
keeping, put it in a `lat.md/explanation/` section and let the pointer reach it.
|
|
59
|
+
|
|
60
|
+
### The marker is a comment syntax, not a language allowlist
|
|
61
|
+
|
|
62
|
+
`//` for C-family languages; `#` for Ruby, Python, shell, Elixir, YAML, Make,
|
|
63
|
+
and everything else that comments with `#`. The scan is textual and walks every
|
|
64
|
+
non-markdown file in the project, so every language works.
|
|
65
|
+
|
|
66
|
+
Any list of example languages you find is a list of examples. Never conclude a
|
|
67
|
+
language is unsupported because it is absent from one. `lat check` prints what
|
|
68
|
+
it scanned:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
Scanned 41 .md, 622 .rb, 118 .tsx in 1.2s
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Read that line rather than guessing, and if you are still unsure, add one ref
|
|
75
|
+
and run the check. It costs one command.
|
|
76
|
+
|
|
77
|
+
(The tree-sitter grammars limit only `[[src/file.ts#symbol]]` wiki links, which
|
|
78
|
+
resolve a symbol inside source. `@lat:` refs point the other way and have no
|
|
79
|
+
such limit.)
|
|
80
|
+
|
|
81
|
+
## Test specs are wired to their tests
|
|
82
|
+
|
|
83
|
+
A document describing tests declares it in frontmatter:
|
|
84
|
+
|
|
85
|
+
```markdown
|
|
86
|
+
---
|
|
87
|
+
lat:
|
|
88
|
+
require-code-mention: true
|
|
89
|
+
---
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Every leaf section then needs exactly one `@lat:` comment at the test that
|
|
93
|
+
covers it, and every section needs at least one sentence saying what it
|
|
94
|
+
verifies and why. `lat check` reports any spec section no test claims, and any
|
|
95
|
+
ref pointing at no section.
|
package/templates/skill/SKILL.md
CHANGED
|
@@ -10,6 +10,8 @@ description: >-
|
|
|
10
10
|
|
|
11
11
|
This skill covers the syntax, structure rules, and conventions for writing `lat.md/` files. Load it whenever you need to create or edit sections in the `lat.md/` directory.
|
|
12
12
|
|
|
13
|
+
Project-specific lat documentation belongs in `lat.md/`. Do not modify this generated skill or a generated `AGENTS.md` to record project guidance: both are owned by lat tooling and may be replaced by a later `lat init`.
|
|
14
|
+
|
|
13
15
|
## What belongs in lat.md
|
|
14
16
|
|
|
15
17
|
`lat.md/` files describe **what** the project does and **why** — domain concepts, key design decisions, business logic, and test specifications. They do NOT duplicate source code. Think of each section as an anchor that source code references back to.
|
|
@@ -162,20 +164,7 @@ lat:
|
|
|
162
164
|
---
|
|
163
165
|
```
|
|
164
166
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
## Diátaxis modes
|
|
168
|
-
|
|
169
|
-
Documents in `tutorials/`, `how-to/`, `reference/`, or `explanation/` under `lat.md/` are checked against their Diátaxis mode. Declare it with nested frontmatter:
|
|
170
|
-
|
|
171
|
-
```yaml
|
|
172
|
-
---
|
|
173
|
-
lat:
|
|
174
|
-
mode: reference
|
|
175
|
-
---
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
If the document is in one of the four mode directories, the declared mode must match that directory. Each mode enforces one shape rule: tutorials need an ordered list of steps and a stated outcome; how-to guides need an ordered list of steps; reference docs must not contain narrative prose (no second paragraph under a heading); explanations must not give imperative commands. `lat check mode` validates this.
|
|
167
|
+
Currently the only supported field is `require-code-mention` for test spec enforcement.
|
|
179
168
|
|
|
180
169
|
## Validation
|
|
181
170
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
:root{color:#25231f;background:#f8f7f3;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;font-synthesis:none;text-rendering:optimizeLegibility;--background: #f8f7f3;--panel: #f0eee8;--panel-border: #dedbd1;--text: #25231f;--muted: #777268;--accent: #7357d9;--accent-soft: #e7e0ff;--danger: #b72f3d;--danger-soft: #f8dfe2;--danger-border: #e7a5ad;--warning: #a87000;--warning-soft: #f5e7b8;--success: #2f7a48;--success-soft: #dceede;--git-notification: #e47f19;--code: #ece9e1;--syntax-built-in: #9a3f75;--syntax-comment: #777268;--syntax-keyword: #6c4bc2;--syntax-number: #a85414;--syntax-string: #397348;--syntax-title: #26649a}@media(prefers-color-scheme:dark){:root{color:#e8e6df;background:#181816;--background: #181816;--panel: #201f1c;--panel-border: #34322d;--text: #e8e6df;--muted: #9c978d;--accent: #ad9aff;--accent-soft: #332b52;--danger: #ff7e8a;--danger-soft: #49262b;--danger-border: #713941;--warning: #e9bd4c;--warning-soft: #43391e;--success: #83d79a;--success-soft: #213d29;--git-notification: #f0a044;--code: #25241f;--syntax-built-in: #e6a2cc;--syntax-comment: #88847c;--syntax-keyword: #c2adff;--syntax-number: #efb271;--syntax-string: #9fd0a5;--syntax-title: #8fc6ef}}*{box-sizing:border-box}body{margin:0;min-width:320px;min-height:100vh;color:var(--text);background:var(--background)}a{color:var(--accent)}.app-shell{display:grid;grid-template-columns:260px minmax(0,1fr);min-height:100vh}.sidebar{position:sticky;top:0;height:100vh;padding:28px 18px;overflow-y:auto;background:var(--panel);border-right:1px solid var(--panel-border)}.sidebar-header{display:flex;margin:0 10px 34px;align-items:center;justify-content:space-between}.brand{display:inline-block;color:var(--text);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:21px;font-weight:750;letter-spacing:-.04em;text-decoration:none}.brand span{color:var(--accent)}.sidebar-actions{display:flex;align-items:center;gap:3px}.sidebar-search,.sidebar-git{display:inline-flex;width:28px;height:28px;align-items:center;justify-content:center;color:var(--muted);cursor:pointer;background:transparent;border:0;border-radius:6px}.sidebar-search:hover,.sidebar-search[aria-current=page],.sidebar-git:hover{color:var(--accent);background:var(--accent-soft)}.sidebar-git[aria-pressed=true]{color:var(--warning);background:var(--warning-soft)}.sidebar-git{position:relative}.sidebar-git[data-has-changes=true]:after{position:absolute;top:3px;right:3px;width:6px;height:6px;content:"";background:var(--git-notification);border-radius:50%;box-shadow:0 0 0 1.5px var(--panel)}.sidebar-search svg,.sidebar-git svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-linecap:round;stroke-width:1.8}.file-tree{display:grid;min-width:0;gap:2px}.tree-directory>summary{display:flex;padding:7px 10px;align-items:center;gap:7px;color:var(--muted);font-size:12px;font-weight:650;cursor:pointer;list-style:none;border-radius:7px}.tree-directory>summary::-webkit-details-marker{display:none}.tree-directory>summary>a,.tree-directory>summary>span{min-width:0;flex:1;color:inherit;text-decoration:none}.tree-directory>summary>a,.tree-directory>summary>span{display:flex;align-items:center;gap:8px}.tree-directory>summary:before{content:"›";display:inline-block;width:10px;font-size:15px;line-height:1;transform-origin:center;transition:transform .12s ease}.tree-directory[open]>summary:before{transform:rotate(90deg)}.tree-directory>summary:hover{color:var(--text);background:color-mix(in srgb,var(--accent-soft) 55%,transparent)}.tree-children{display:grid;margin-left:15px;padding-left:7px;gap:2px;border-left:1px solid var(--panel-border)}.tree-directory[open]>.tree-children{margin-top:2px}.document-link{display:flex;padding:7px 10px;align-items:center;gap:8px;overflow:hidden;color:var(--muted);font-size:13px;line-height:1.35;text-decoration:none;text-overflow:ellipsis;white-space:nowrap;border-radius:7px}.document-link-name{min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.document-state-disc{width:7px;height:7px;flex:0 0 auto;border-radius:50%}.document-state-disc.has-errors{background:var(--danger);box-shadow:0 0 0 2px color-mix(in srgb,var(--danger) 12%,transparent)}.document-state-disc.git-modified{background:var(--warning);box-shadow:0 0 0 2px color-mix(in srgb,var(--warning) 12%,transparent)}.document-state-disc.git-new{background:var(--success);box-shadow:0 0 0 2px color-mix(in srgb,var(--success) 12%,transparent)}.document-state-disc.has-errors.git-modified{background:linear-gradient(90deg,var(--danger) 0 50%,var(--warning) 50% 100%)}.document-state-disc.has-errors.git-new{background:linear-gradient(90deg,var(--danger) 0 50%,var(--success) 50% 100%)}.document-link:hover,.document-link.active{color:var(--text);background:var(--accent-soft)}.document-link.active{font-weight:650}.main{width:100%;max-width:980px;min-width:0;padding:46px clamp(28px,6vw,82px) 100px}.main.restoring-history-scroll{visibility:hidden}.document-header{max-width:760px;margin-bottom:34px}.document-metadata{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.document-path{color:var(--muted);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:12px}.document-flag{padding:3px 8px;color:var(--accent);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:10px;font-weight:700;letter-spacing:.025em;background:var(--accent-soft);border:1px solid color-mix(in srgb,var(--accent) 25%,transparent);border-radius:999px}.document-error-toggle{display:inline-flex;padding:3px 8px;align-items:center;color:var(--danger);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:10px;font-weight:700;letter-spacing:.025em;cursor:pointer;background:var(--danger-soft);border:1px solid var(--danger-border);border-radius:999px}.document-error-toggle:hover,.document-error-toggle[aria-expanded=true]{color:var(--text)}.document-error-panel{margin-top:12px;overflow:hidden;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;background:color-mix(in srgb,var(--danger-soft) 55%,var(--background));border:1px solid var(--danger-border);border-radius:8px}.document-error-header{padding:9px 13px 7px;color:var(--danger);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:10px;font-weight:750;letter-spacing:.08em;text-transform:uppercase}.document-error-item{display:grid;padding:10px 13px 11px;gap:5px;color:inherit;text-decoration:none}.document-error-item+.document-error-item{border-top:1px solid var(--danger-border)}.document-error-item:hover{background:color-mix(in srgb,var(--danger-soft) 75%,transparent)}.document-error-location{color:var(--danger);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:11px;font-weight:700}.document-error-message{color:var(--muted);font-size:12px;line-height:1.45;white-space:pre-wrap}.source-context{position:relative;z-index:1;width:calc(100% - 5.25rem);max-width:760px;margin:12px 16px 12px 4.25rem;overflow:visible;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;background:color-mix(in srgb,var(--code) 72%,transparent);border:1px solid var(--panel-border);border-radius:8px}.source-context.has-references:not(.references-open){margin-bottom:24px}.source-context-header{display:flex;min-height:36px;padding:7px 10px 7px 13px;align-items:center;gap:12px;color:var(--muted);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase}.source-reference-toggle{position:relative;z-index:1;display:flex;height:0;align-items:center;gap:10px}.source-reference-toggle:before,.source-reference-toggle:after{height:1px;flex:1;content:"";background:var(--panel-border);visibility:hidden}.source-context.references-open .source-reference-toggle:before,.source-context.references-open .source-reference-toggle:after{visibility:visible}.source-reference-toggle button{display:inline-flex;padding:4px 7px;align-items:center;gap:6px;color:var(--accent);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:10px;font-weight:650;line-height:1.3;letter-spacing:.02em;text-transform:none;cursor:pointer;background:var(--accent-soft);border:0;border-radius:5px}.source-reference-toggle button:hover{color:var(--text)}.source-reference-toggle button>span{min-width:16px;padding:1px 4px;color:var(--muted);text-align:center;background:color-mix(in srgb,var(--background) 65%,transparent);border-radius:999px}.source-context-current{padding:0 13px 13px}.source-context-current>a,.source-reference-item>a{color:var(--text);text-decoration:none}.source-breadcrumbs{display:flex;align-items:center;gap:5px;overflow:hidden;color:var(--accent);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:12px;font-weight:650;white-space:nowrap}.source-breadcrumbs>span{display:inline-flex;min-width:0;align-items:center;gap:5px}.source-breadcrumbs>span>span{color:var(--muted)}.source-context-current>a:hover .source-breadcrumbs,.source-reference-item>a:hover .source-breadcrumbs{text-decoration:underline;text-underline-offset:3px}.source-context-paragraph p{margin:8px 0 0;color:var(--text);font-size:13px;line-height:1.5}.source-context-paragraph a,.source-reference-paragraph a{text-underline-offset:2px}.source-reference-list{padding:17px 0 3px}.source-reference-item{padding:10px 13px}.source-reference-item+.source-reference-item{border-top:1px solid var(--panel-border)}.source-reference-paragraph p{margin:5px 0 0;color:var(--muted);font-size:12px;line-height:1.4}.source-code{max-width:100%;padding:10px 0;overflow-x:auto;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:13px;line-height:1.55;background:var(--code);border:1px solid var(--panel-border);border-radius:10px;overflow-anchor:none}.source-collapse{display:flex;min-width:max-content;min-height:34px;align-items:center;color:var(--muted);background:color-mix(in srgb,var(--panel) 52%,transparent);border-top:1px solid color-mix(in srgb,var(--panel-border) 72%,transparent);border-bottom:1px solid color-mix(in srgb,var(--panel-border) 72%,transparent)}.source-collapse:first-child{margin-top:-10px;border-top:0;border-radius:10px 10px 0 0}.source-collapse:last-child{margin-bottom:-10px;border-bottom:0;border-radius:0 0 10px 10px}.source-collapse>span{flex:0 0 4.25rem;padding-right:14px;text-align:right;-webkit-user-select:none;user-select:none}.source-collapse button{padding:6px 20px 6px 0;color:var(--muted);font:inherit;font-size:11px;cursor:pointer;background:transparent;border:0}.source-collapse button:hover{color:var(--accent)}.source-line{display:flex;min-width:max-content}.source-line.focused{background:color-mix(in srgb,var(--accent-soft) 72%,transparent)}.source-line-number{flex:0 0 4.25rem;padding-right:14px;color:var(--muted);text-align:right;-webkit-user-select:none;user-select:none}.source-line.focused .source-line-number{color:var(--accent)}.source-line-content{flex:1 0 auto;padding-right:20px;color:var(--text);white-space:pre}.source-code .hljs-comment,.source-code .hljs-quote{color:var(--syntax-comment);font-style:italic}.source-code .hljs-keyword,.source-code .hljs-literal,.source-code .hljs-selector-tag,.source-code .hljs-section{color:var(--syntax-keyword)}.source-code .hljs-string,.source-code .hljs-regexp,.source-code .hljs-addition,.source-code .hljs-attribute{color:var(--syntax-string)}.source-code .hljs-number,.source-code .hljs-symbol,.source-code .hljs-bullet{color:var(--syntax-number)}.source-code .hljs-title,.source-code .hljs-function,.source-code .hljs-class,.source-code .hljs-type{color:var(--syntax-title)}.source-code .hljs-built_in,.source-code .hljs-variable.language_,.source-code .hljs-meta{color:var(--syntax-built-in)}.source-code .hljs-deletion{color:#bd3d3d}.source-code .hljs-emphasis{font-style:italic}.source-code .hljs-strong{font-weight:700}.markdown{max-width:760px;font-size:16px;line-height:1.72}.markdown h1,.markdown h2,.markdown h3,.markdown h4{margin:2em 0 .65em;line-height:1.2;letter-spacing:-.025em;scroll-margin-top:24px}.section-back-reference-toggle{display:inline-flex;padding:3px 5px 3px 7px;margin-left:10px;align-items:center;gap:5px;color:var(--accent);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:10px;font-weight:650;line-height:1.3;letter-spacing:.01em;vertical-align:middle;cursor:pointer;background:var(--accent-soft);border:0;border-radius:5px}.section-back-reference-toggle:hover,.section-back-reference-toggle[aria-expanded=true]{color:var(--text)}.section-back-reference-count{min-width:16px;padding:1px 4px;color:var(--muted);text-align:center;background:color-mix(in srgb,var(--background) 65%,transparent);border-radius:999px}.section-back-reference-panel{margin:-.15em 0 1.5em;overflow:hidden;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;background:color-mix(in srgb,var(--code) 72%,transparent);border:1px solid var(--panel-border);border-radius:8px}.section-back-reference-panel[hidden]{display:none}.section-back-reference-header{padding:9px 13px 7px;color:var(--muted);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase}.section-back-reference-item{padding:10px 13px 11px}.section-back-reference-item+.section-back-reference-item{border-top:1px solid var(--panel-border)}.section-back-reference-location{display:inline-flex;align-items:center;gap:5px;color:var(--accent);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:12px;font-weight:650;line-height:1.35;text-decoration:none}.section-back-reference-location:hover{text-decoration:underline;text-underline-offset:3px}.section-back-reference-location>span[aria-hidden=true]{color:var(--muted)}.section-back-reference-paragraph p{margin:5px 0 0;color:var(--muted);font-size:12px;line-height:1.45}.section-back-reference-paragraph a{text-underline-offset:2px}.section-back-reference-code .code-link-language{margin-right:0;font-size:7px}.markdown .section-back-reference-code>code{display:block;padding:7px 9px;margin-top:7px;overflow-x:auto;color:var(--muted);font-size:11px;line-height:1.4;white-space:pre;border:1px solid color-mix(in srgb,var(--panel-border) 75%,transparent)}.markdown h1{margin-top:0;font-size:clamp(32px,5vw,46px)}.markdown h2{padding-top:.4em;font-size:26px;border-top:1px solid var(--panel-border)}.markdown h3{font-size:20px}.markdown p,.markdown ul,.markdown ol,.markdown blockquote,.markdown pre{margin:0 0 1.25em}.markdown li+li{margin-top:.35em}.markdown a{text-decoration-thickness:1px;text-underline-offset:3px}.markdown .markdown-error{color:var(--danger);scroll-margin-top:24px;text-decoration-color:currentColor}.markdown p.markdown-error,.markdown li.markdown-error,.markdown blockquote.markdown-error,.markdown pre.markdown-error,.markdown h1.markdown-error,.markdown h2.markdown-error,.markdown h3.markdown-error,.markdown h4.markdown-error,.markdown h5.markdown-error,.markdown h6.markdown-error{padding-left:10px;background:color-mix(in srgb,var(--danger-soft) 42%,transparent);box-shadow:inset 3px 0 0 var(--danger)}.markdown a.markdown-error,.markdown span.markdown-error{padding:.08em .2em;margin:-.08em -.2em;background:var(--danger-soft);border-radius:3px;box-decoration-break:clone;-webkit-box-decoration-break:clone}.markdown ins.git-added,.markdown del.git-removed{padding:.08em .12em;margin:-.08em -.04em;border-radius:3px;box-decoration-break:clone;-webkit-box-decoration-break:clone}.markdown ins.git-added{color:var(--success);text-decoration:none;background:var(--success-soft)}.markdown del.git-removed{color:var(--danger);background:var(--danger-soft);text-decoration-thickness:1px}.markdown p.git-added,.markdown blockquote.git-added,.markdown pre.git-added,.markdown h1.git-added,.markdown h2.git-added,.markdown h3.git-added,.markdown h4.git-added,.markdown h5.git-added,.markdown h6.git-added,.markdown li.git-added{padding-left:10px;color:var(--success);background:color-mix(in srgb,var(--success-soft) 55%,transparent);box-shadow:inset 3px 0 0 var(--success)}.markdown p.git-removed,.markdown div.git-removed,.markdown blockquote.git-removed,.markdown pre.git-removed,.markdown li.git-removed{padding-left:10px;color:var(--danger);text-decoration:line-through;background:color-mix(in srgb,var(--danger-soft) 55%,transparent);box-shadow:inset 3px 0 0 var(--danger)}.wiki-link-context{color:var(--muted)}a.wiki-link-segmented{text-decoration:none}a.wiki-link-segmented>.wiki-link-context,a.wiki-link-segmented>.wiki-link-leaf{text-decoration-line:underline;text-decoration-color:currentColor;text-decoration-thickness:1px;text-underline-offset:3px}.code-link-language{display:inline-flex;width:1.55em;height:1.55em;margin-right:.38em;align-items:center;justify-content:center;color:#fff;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.62em;font-style:normal;font-weight:800;line-height:1;text-decoration:none;vertical-align:.12em;background:var(--muted);border-radius:3px}.code-language-ts{background:#3178c6}.code-language-js{color:#25231f;background:#f0db4f}.code-language-py{background:#3776ab}.code-language-rs{background:#9a4b2f}.code-language-go{color:#15343b;background:#6ad7e5}.code-language-c{background:#5968ba}a.wiki-link-active{padding:.12em .28em;margin:-.12em -.28em;background:var(--accent-soft);border-radius:4px;box-decoration-break:clone;-webkit-box-decoration-break:clone}.markdown blockquote{margin-left:0;padding-left:18px;color:var(--muted);border-left:3px solid var(--panel-border)}.markdown code{padding:.16em .36em;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.9em;background:var(--code);border-radius:5px}.markdown pre{padding:18px 20px;overflow-x:auto;background:var(--code);border:1px solid var(--panel-border);border-radius:10px}.markdown pre code{padding:0;background:transparent}.markdown img{max-width:100%}.markdown hr{margin:2.5em 0;border:0;border-top:1px solid var(--panel-border)}.visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;white-space:nowrap;border:0;clip:rect(0,0,0,0)}.search-page{width:100%;max-width:760px}.search-input-shell{display:flex;height:52px;padding:0 16px;align-items:center;gap:11px;color:var(--muted);background:var(--panel);border:1px solid var(--panel-border);border-radius:10px;box-shadow:0 1px 1px color-mix(in srgb,var(--text) 5%,transparent)}.search-input-shell:focus-within{color:var(--accent);border-color:color-mix(in srgb,var(--accent) 55%,var(--panel-border));box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 12%,transparent)}.search-input-shell svg{width:20px;height:20px;flex:0 0 auto;fill:none;stroke:currentColor;stroke-linecap:round;stroke-width:1.8}.search-input-shell input{width:100%;min-width:0;padding:0;color:var(--text);font:inherit;font-size:17px;line-height:1;background:transparent;border:0;outline:0}.search-input-shell input::placeholder{color:var(--muted)}.search-input-shell input::-webkit-search-cancel-button{cursor:pointer}.search-status{min-height:42px;padding:12px 2px 8px;color:var(--muted);font-size:12px}.search-results{display:grid;gap:10px}.search-result{display:block;padding:16px 18px;color:var(--text);text-decoration:none;background:color-mix(in srgb,var(--panel) 72%,transparent);border:1px solid var(--panel-border);border-radius:9px;transition:border-color .12s ease,background .12s ease,transform .12s ease}.search-result:hover{background:var(--panel);border-color:color-mix(in srgb,var(--accent) 38%,var(--panel-border));transform:translateY(-1px)}.search-result-breadcrumbs{display:flex;align-items:center;gap:5px;overflow:hidden;color:var(--accent);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:11px;font-weight:650;white-space:nowrap}.search-result-breadcrumbs>span{display:inline-flex;min-width:0;align-items:center;gap:5px}.search-result-breadcrumbs>span>span{color:var(--muted)}.search-result h2{margin:7px 0 0;font-size:18px;line-height:1.3;letter-spacing:-.015em}.search-result p{margin:6px 0 0;color:var(--muted);font-size:13px;line-height:1.5}.state{display:flex;min-height:50vh;align-items:center;justify-content:center;color:var(--muted)}.state.error{flex-direction:column;gap:8px;text-align:center}.state.error strong{color:var(--text)}@media(max-width:760px){.app-shell{display:block}.sidebar{position:static;width:100%;height:auto;padding:18px;border-right:0;border-bottom:1px solid var(--panel-border)}.sidebar-header{margin-bottom:18px}.sidebar nav{display:none}.main{padding-top:28px}.source-context{width:calc(100% - 24px);margin-right:12px;margin-left:12px}}
|