lat.md 0.9.0 → 0.10.1
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/README.md +17 -14
- package/dist/src/cli/check.js +20 -3
- package/dist/src/cli/index.js +1 -1
- package/dist/src/cli/init.js +11 -0
- package/dist/src/cli/refs.d.ts +4 -2
- package/dist/src/cli/refs.js +126 -3
- package/dist/src/cli/section.d.ts +13 -0
- package/dist/src/cli/section.js +103 -4
- package/dist/src/code-refs.d.ts +3 -0
- package/dist/src/code-refs.js +161 -4
- package/dist/src/mcp/server.js +1 -1
- package/dist/src/source-parser.js +22 -12
- package/package.json +1 -1
- package/templates/logo-dark.svg +182 -0
- package/templates/logo-light.svg +182 -0
- package/templates/pi-extension.ts +9 -6
- package/templates/lat-prompt-hook.sh +0 -16
package/README.md
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="templates/logo-dark.svg" alt="lat.md" width="500">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="https://github.com/1st1/lat.md/actions/workflows/ci.yml"><img src="https://github.com/1st1/lat.md/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
7
|
+
<a href="https://www.npmjs.com/package/lat.md"><img src="https://img.shields.io/npm/v/lat.md" alt="npm"></a>
|
|
8
|
+
</p>
|
|
5
9
|
|
|
6
|
-
A knowledge graph for your codebase, written in markdown
|
|
10
|
+
<p align="center">A knowledge graph for your codebase, written in markdown.</p>
|
|
7
11
|
|
|
8
12
|
## The problem
|
|
9
13
|
|
|
@@ -27,9 +31,7 @@ The result is a structured knowledge base that:
|
|
|
27
31
|
npm install -g lat.md
|
|
28
32
|
```
|
|
29
33
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
After installing, run `lat init` in the repo you want to use lat in.
|
|
34
|
+
Then run `lat init` in the repo you want to use lat in.
|
|
33
35
|
|
|
34
36
|
## How it works
|
|
35
37
|
|
|
@@ -50,13 +52,14 @@ my-project/
|
|
|
50
52
|
## CLI
|
|
51
53
|
|
|
52
54
|
```bash
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
lat init # scaffold a lat.md/ directory
|
|
56
|
+
lat check # validate all wiki links and code refs
|
|
57
|
+
lat locate "OAuth Flow" # find sections by name (exact, fuzzy)
|
|
58
|
+
lat section "auth#OAuth Flow" # show a section with its links and refs
|
|
59
|
+
lat refs "auth#OAuth Flow" # find what references a section
|
|
60
|
+
lat search "how do we auth?" # semantic search via embeddings
|
|
61
|
+
lat expand "fix [[OAuth Flow]]" # expand [[refs]] in a prompt for agents
|
|
62
|
+
lat mcp # start MCP server for editor integration
|
|
60
63
|
```
|
|
61
64
|
|
|
62
65
|
## Configuration
|
package/dist/src/cli/check.js
CHANGED
|
@@ -131,9 +131,10 @@ export async function checkCodeRefs(latticeDir) {
|
|
|
131
131
|
for (const ref of scan.refs) {
|
|
132
132
|
const { resolved, ambiguous, suggested } = resolveRef(ref.target, sectionIds, fileIndex);
|
|
133
133
|
mentionedSections.add(resolved.toLowerCase());
|
|
134
|
+
const displayPath = relative(process.cwd(), join(projectRoot, ref.file));
|
|
134
135
|
if (ambiguous) {
|
|
135
136
|
errors.push({
|
|
136
|
-
file:
|
|
137
|
+
file: displayPath,
|
|
137
138
|
line: ref.line,
|
|
138
139
|
target: ref.target,
|
|
139
140
|
message: ambiguousMessage(ref.target, ambiguous, suggested),
|
|
@@ -141,7 +142,7 @@ export async function checkCodeRefs(latticeDir) {
|
|
|
141
142
|
}
|
|
142
143
|
else if (!sectionIds.has(resolved.toLowerCase())) {
|
|
143
144
|
errors.push({
|
|
144
|
-
file:
|
|
145
|
+
file: displayPath,
|
|
145
146
|
line: ref.line,
|
|
146
147
|
target: ref.target,
|
|
147
148
|
message: `@lat: [[${ref.target}]] — no matching section found`,
|
|
@@ -371,17 +372,22 @@ function formatErrorCount(count, s) {
|
|
|
371
372
|
}
|
|
372
373
|
// --- Unified command functions ---
|
|
373
374
|
export async function checkAllCommand(ctx) {
|
|
375
|
+
const startTime = Date.now();
|
|
374
376
|
const md = await checkMd(ctx.latDir);
|
|
375
377
|
const code = await checkCodeRefs(ctx.latDir);
|
|
376
378
|
const indexErrors = await checkIndex(ctx.latDir);
|
|
377
379
|
const sectionErrors = await checkSections(ctx.latDir);
|
|
380
|
+
const elapsed = Date.now() - startTime;
|
|
378
381
|
const allErrors = [...md.errors, ...code.errors];
|
|
379
382
|
const allFiles = { ...md.files };
|
|
380
383
|
for (const [ext, n] of Object.entries(code.files)) {
|
|
381
384
|
allFiles[ext] = (allFiles[ext] || 0) + n;
|
|
382
385
|
}
|
|
383
386
|
const s = ctx.styler;
|
|
384
|
-
const
|
|
387
|
+
const elapsedStr = elapsed < 1000 ? `${elapsed}ms` : `${(elapsed / 1000).toFixed(1)}s`;
|
|
388
|
+
const lines = [
|
|
389
|
+
formatFileStats(allFiles, s) + s.dim(` in ${elapsedStr}`),
|
|
390
|
+
];
|
|
385
391
|
// Init version warning first — user should fix setup before addressing errors
|
|
386
392
|
const storedVersion = readInitVersion(ctx.latDir);
|
|
387
393
|
if (storedVersion === null) {
|
|
@@ -424,6 +430,17 @@ export async function checkAllCommand(ctx) {
|
|
|
424
430
|
s.cyan('lat init') +
|
|
425
431
|
' to configure.');
|
|
426
432
|
}
|
|
433
|
+
// Suggest ripgrep if check was slow (>1s) and rg is not available
|
|
434
|
+
if (elapsed > 1000) {
|
|
435
|
+
const { hasRipgrep } = await import('../code-refs.js');
|
|
436
|
+
if (!(await hasRipgrep())) {
|
|
437
|
+
lines.push(s.yellow('Tip:') +
|
|
438
|
+
' Install ' +
|
|
439
|
+
s.cyan('ripgrep') +
|
|
440
|
+
' (rg) for faster code scanning.' +
|
|
441
|
+
' See https://github.com/BurntSushi/ripgrep#installation');
|
|
442
|
+
}
|
|
443
|
+
}
|
|
427
444
|
return { output: lines.join('\n') };
|
|
428
445
|
}
|
|
429
446
|
export async function checkMdCommand(ctx) {
|
package/dist/src/cli/index.js
CHANGED
|
@@ -61,7 +61,7 @@ program
|
|
|
61
61
|
.command('refs')
|
|
62
62
|
.description('Find references to a section')
|
|
63
63
|
.argument('<query>', 'section id to find references for')
|
|
64
|
-
.option('--scope <scope>', 'where to search: md, code, or md+code', 'md')
|
|
64
|
+
.option('--scope <scope>', 'where to search: md, code, or md+code', 'md+code')
|
|
65
65
|
.action(async (query, opts) => {
|
|
66
66
|
const scope = opts.scope;
|
|
67
67
|
if (scope !== 'md' && scope !== 'code' && scope !== 'md+code') {
|
package/dist/src/cli/init.js
CHANGED
|
@@ -682,6 +682,17 @@ export async function initCmd(targetDir) {
|
|
|
682
682
|
' Run ' +
|
|
683
683
|
chalk.cyan('lat check') +
|
|
684
684
|
' to validate your setup.');
|
|
685
|
+
// Suggest ripgrep if not available
|
|
686
|
+
const { hasRipgrep } = await import('../code-refs.js');
|
|
687
|
+
if (!(await hasRipgrep())) {
|
|
688
|
+
console.log('');
|
|
689
|
+
console.log(chalk.yellow('Tip:') +
|
|
690
|
+
' Install ' +
|
|
691
|
+
chalk.cyan('ripgrep') +
|
|
692
|
+
' (rg) for faster code scanning.' +
|
|
693
|
+
' See ' +
|
|
694
|
+
chalk.underline('https://github.com/BurntSushi/ripgrep#installation'));
|
|
695
|
+
}
|
|
685
696
|
}
|
|
686
697
|
finally {
|
|
687
698
|
rl?.close();
|
package/dist/src/cli/refs.d.ts
CHANGED
|
@@ -13,8 +13,10 @@ export type RefsError = {
|
|
|
13
13
|
};
|
|
14
14
|
export type RefsResult = RefsFound | RefsError;
|
|
15
15
|
/**
|
|
16
|
-
* Find all sections and code locations that reference a given section
|
|
17
|
-
*
|
|
16
|
+
* Find all sections and code locations that reference a given section or
|
|
17
|
+
* source file. Accepts section ids (full-path, short-form) and source file
|
|
18
|
+
* paths (e.g. src/app.rs#foo). Source file queries match wiki links directly
|
|
19
|
+
* without section resolution.
|
|
18
20
|
*/
|
|
19
21
|
export declare function findRefs(ctx: CmdContext, query: string, scope: Scope): Promise<RefsResult>;
|
|
20
22
|
export declare function refsCommand(ctx: CmdContext, query: string, scope: Scope): Promise<CmdResult>;
|
package/dist/src/cli/refs.js
CHANGED
|
@@ -1,13 +1,135 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { extname, join, relative } from 'node:path';
|
|
2
4
|
import { listLatticeFiles, loadAllSections, findSections, extractRefs, flattenSections, buildFileIndex, resolveRef, } from '../lattice.js';
|
|
3
5
|
import { formatResultList } from '../format.js';
|
|
4
6
|
import { scanCodeRefs } from '../code-refs.js';
|
|
7
|
+
/** Extensions recognized as source code for ref queries. */
|
|
8
|
+
const SOURCE_EXTS = new Set([
|
|
9
|
+
'.ts',
|
|
10
|
+
'.tsx',
|
|
11
|
+
'.js',
|
|
12
|
+
'.jsx',
|
|
13
|
+
'.py',
|
|
14
|
+
'.rs',
|
|
15
|
+
'.go',
|
|
16
|
+
'.c',
|
|
17
|
+
'.h',
|
|
18
|
+
]);
|
|
5
19
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
20
|
+
* Check if a query looks like a source file path (has a recognized extension
|
|
21
|
+
* and the file exists on disk).
|
|
22
|
+
*/
|
|
23
|
+
function isSourceQuery(query, projectRoot) {
|
|
24
|
+
const hashIdx = query.indexOf('#');
|
|
25
|
+
const filePart = hashIdx === -1 ? query : query.slice(0, hashIdx);
|
|
26
|
+
const symbolPart = hashIdx === -1 ? '' : query.slice(hashIdx + 1);
|
|
27
|
+
const ext = extname(filePart);
|
|
28
|
+
if (!SOURCE_EXTS.has(ext))
|
|
29
|
+
return null;
|
|
30
|
+
if (!existsSync(join(projectRoot, filePart)))
|
|
31
|
+
return null;
|
|
32
|
+
return { filePart, symbolPart };
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Find references to a source file or symbol across lat.md and code files.
|
|
36
|
+
* For file-level queries (no #symbol), matches all wiki links targeting
|
|
37
|
+
* that file or any symbol in it.
|
|
38
|
+
*/
|
|
39
|
+
async function findSourceRefs(latDir, projectRoot, query, scope) {
|
|
40
|
+
const hashIdx = query.indexOf('#');
|
|
41
|
+
const filePart = hashIdx === -1 ? query : query.slice(0, hashIdx);
|
|
42
|
+
const isFileLevel = hashIdx === -1;
|
|
43
|
+
const queryLower = query.toLowerCase();
|
|
44
|
+
const fileLower = filePart.toLowerCase();
|
|
45
|
+
// Build a synthetic Section for the target
|
|
46
|
+
const target = {
|
|
47
|
+
id: query,
|
|
48
|
+
heading: hashIdx === -1 ? filePart : query.slice(hashIdx + 1),
|
|
49
|
+
depth: 0,
|
|
50
|
+
file: filePart,
|
|
51
|
+
filePath: filePart,
|
|
52
|
+
children: [],
|
|
53
|
+
startLine: 0,
|
|
54
|
+
endLine: 0,
|
|
55
|
+
firstParagraph: '',
|
|
56
|
+
};
|
|
57
|
+
// Try to get real line info from the source parser
|
|
58
|
+
try {
|
|
59
|
+
const { resolveSourceSymbol } = await import('../source-parser.js');
|
|
60
|
+
if (hashIdx !== -1) {
|
|
61
|
+
const symbolPart = query.slice(hashIdx + 1);
|
|
62
|
+
const { found, symbols } = await resolveSourceSymbol(filePart, symbolPart, projectRoot);
|
|
63
|
+
if (found) {
|
|
64
|
+
const parts = symbolPart.split('#');
|
|
65
|
+
const sym = symbols.find((s) => parts.length === 1
|
|
66
|
+
? s.name === parts[0] && !s.parent
|
|
67
|
+
: s.name === parts[1] && s.parent === parts[0]);
|
|
68
|
+
if (sym) {
|
|
69
|
+
target.startLine = sym.startLine;
|
|
70
|
+
target.endLine = sym.endLine;
|
|
71
|
+
target.firstParagraph = sym.signature;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// source parser unavailable — proceed without line info
|
|
78
|
+
}
|
|
79
|
+
const allSections = await loadAllSections(latDir);
|
|
80
|
+
const flat = flattenSections(allSections);
|
|
81
|
+
const mdRefs = [];
|
|
82
|
+
const codeRefs = [];
|
|
83
|
+
if (scope === 'md' || scope === 'md+code') {
|
|
84
|
+
const files = await listLatticeFiles(latDir);
|
|
85
|
+
const matchingFromSections = new Set();
|
|
86
|
+
for (const file of files) {
|
|
87
|
+
const content = await readFile(file, 'utf-8');
|
|
88
|
+
const fileRefs = extractRefs(file, content, projectRoot);
|
|
89
|
+
for (const ref of fileRefs) {
|
|
90
|
+
const targetLower = ref.target.toLowerCase();
|
|
91
|
+
const matches = isFileLevel
|
|
92
|
+
? targetLower === fileLower || targetLower.startsWith(fileLower + '#')
|
|
93
|
+
: targetLower === queryLower;
|
|
94
|
+
if (matches) {
|
|
95
|
+
matchingFromSections.add(ref.fromSection.toLowerCase());
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (matchingFromSections.size > 0) {
|
|
100
|
+
const referrers = flat.filter((s) => matchingFromSections.has(s.id.toLowerCase()));
|
|
101
|
+
for (const s of referrers) {
|
|
102
|
+
mdRefs.push({ section: s, reason: 'wiki link' });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (scope === 'code' || scope === 'md+code') {
|
|
107
|
+
const { refs: scannedRefs } = await scanCodeRefs(projectRoot);
|
|
108
|
+
for (const ref of scannedRefs) {
|
|
109
|
+
const targetLower = ref.target.toLowerCase();
|
|
110
|
+
const matches = isFileLevel
|
|
111
|
+
? targetLower === fileLower || targetLower.startsWith(fileLower + '#')
|
|
112
|
+
: targetLower === queryLower;
|
|
113
|
+
if (matches) {
|
|
114
|
+
const displayPath = relative(process.cwd(), join(projectRoot, ref.file));
|
|
115
|
+
codeRefs.push(`${displayPath}:${ref.line}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return { kind: 'found', target, mdRefs, codeRefs };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Find all sections and code locations that reference a given section or
|
|
123
|
+
* source file. Accepts section ids (full-path, short-form) and source file
|
|
124
|
+
* paths (e.g. src/app.rs#foo). Source file queries match wiki links directly
|
|
125
|
+
* without section resolution.
|
|
8
126
|
*/
|
|
9
127
|
export async function findRefs(ctx, query, scope) {
|
|
10
128
|
query = query.replace(/^\[\[|\]\]$/g, '');
|
|
129
|
+
// Source file queries bypass section resolution
|
|
130
|
+
if (isSourceQuery(query, ctx.projectRoot)) {
|
|
131
|
+
return findSourceRefs(ctx.latDir, ctx.projectRoot, query, scope);
|
|
132
|
+
}
|
|
11
133
|
const allSections = await loadAllSections(ctx.latDir);
|
|
12
134
|
const flat = flattenSections(allSections);
|
|
13
135
|
const sectionIds = new Set(flat.map((s) => s.id.toLowerCase()));
|
|
@@ -58,7 +180,8 @@ export async function findRefs(ctx, query, scope) {
|
|
|
58
180
|
for (const ref of scannedRefs) {
|
|
59
181
|
const { resolved: codeResolved } = resolveRef(ref.target, sectionIds, fileIndex);
|
|
60
182
|
if (codeResolved.toLowerCase() === targetId) {
|
|
61
|
-
|
|
183
|
+
const displayPath = relative(process.cwd(), join(ctx.projectRoot, ref.file));
|
|
184
|
+
codeRefs.push(`${displayPath}:${ref.line}`);
|
|
62
185
|
}
|
|
63
186
|
}
|
|
64
187
|
}
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import { type Section, type SectionMatch } from '../lattice.js';
|
|
2
2
|
import type { CmdContext, CmdResult } from '../context.js';
|
|
3
|
+
export type CodeBackRef = {
|
|
4
|
+
file: string;
|
|
5
|
+
line: number;
|
|
6
|
+
snippet: string;
|
|
7
|
+
};
|
|
8
|
+
export type SourceRef = {
|
|
9
|
+
target: string;
|
|
10
|
+
file: string;
|
|
11
|
+
line: number;
|
|
12
|
+
snippet: string;
|
|
13
|
+
};
|
|
3
14
|
export type SectionFound = {
|
|
4
15
|
kind: 'found';
|
|
5
16
|
section: Section;
|
|
@@ -8,7 +19,9 @@ export type SectionFound = {
|
|
|
8
19
|
target: string;
|
|
9
20
|
resolved: Section;
|
|
10
21
|
}[];
|
|
22
|
+
outgoingSourceRefs: SourceRef[];
|
|
11
23
|
incomingRefs: SectionMatch[];
|
|
24
|
+
codeRefs: CodeBackRef[];
|
|
12
25
|
};
|
|
13
26
|
export type SectionResult = SectionFound | {
|
|
14
27
|
kind: 'no-match';
|
package/dist/src/cli/section.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
|
-
import { join, relative } from 'node:path';
|
|
2
|
+
import { extname, join, relative } from 'node:path';
|
|
3
3
|
import { loadAllSections, findSections, flattenSections, extractRefs, buildFileIndex, resolveRef, listLatticeFiles, } from '../lattice.js';
|
|
4
|
+
import { scanCodeRefs } from '../code-refs.js';
|
|
5
|
+
import { SOURCE_EXTENSIONS, resolveSourceSymbol } from '../source-parser.js';
|
|
4
6
|
import { formatSectionId, formatNavHints } from '../format.js';
|
|
5
7
|
/**
|
|
6
8
|
* Look up a section by id, return its content, outgoing wiki link targets,
|
|
@@ -35,10 +37,53 @@ export async function getSection(ctx, query) {
|
|
|
35
37
|
const sectionRefs = extractRefs(absPath, fileContent, ctx.projectRoot);
|
|
36
38
|
const sectionId = section.id.toLowerCase();
|
|
37
39
|
const outgoingRefs = [];
|
|
40
|
+
const outgoingSourceRefs = [];
|
|
38
41
|
const seen = new Set();
|
|
39
42
|
for (const ref of sectionRefs) {
|
|
40
43
|
if (ref.fromSection.toLowerCase() !== sectionId)
|
|
41
44
|
continue;
|
|
45
|
+
// Detect source code references by file extension
|
|
46
|
+
const hashIdx = ref.target.indexOf('#');
|
|
47
|
+
const filePart = hashIdx === -1 ? ref.target : ref.target.slice(0, hashIdx);
|
|
48
|
+
const ext = extname(filePart);
|
|
49
|
+
if (SOURCE_EXTENSIONS.has(ext)) {
|
|
50
|
+
const targetLower = ref.target.toLowerCase();
|
|
51
|
+
if (!seen.has(targetLower)) {
|
|
52
|
+
seen.add(targetLower);
|
|
53
|
+
const symbolPart = hashIdx === -1 ? '' : ref.target.slice(hashIdx + 1);
|
|
54
|
+
let line = 0;
|
|
55
|
+
let snippet = '';
|
|
56
|
+
if (symbolPart) {
|
|
57
|
+
const { found, symbols } = await resolveSourceSymbol(filePart, symbolPart, ctx.projectRoot);
|
|
58
|
+
if (found) {
|
|
59
|
+
const parts = symbolPart.split('#');
|
|
60
|
+
const sym = symbols.find((s) => parts.length === 1
|
|
61
|
+
? s.name === parts[0] && !s.parent
|
|
62
|
+
: s.name === parts[1] && s.parent === parts[0]);
|
|
63
|
+
if (sym) {
|
|
64
|
+
line = sym.startLine;
|
|
65
|
+
try {
|
|
66
|
+
const src = await readFile(join(ctx.projectRoot, filePart), 'utf-8');
|
|
67
|
+
const srcLines = src.split('\n');
|
|
68
|
+
const start = sym.startLine - 1;
|
|
69
|
+
const end = Math.min(srcLines.length, start + 5);
|
|
70
|
+
snippet = srcLines.slice(start, end).join('\n');
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// file unreadable
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
outgoingSourceRefs.push({
|
|
79
|
+
target: ref.target,
|
|
80
|
+
file: filePart,
|
|
81
|
+
line,
|
|
82
|
+
snippet,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
42
87
|
const { resolved } = resolveRef(ref.target, sectionIds, fileIndex);
|
|
43
88
|
const resolvedLower = resolved.toLowerCase();
|
|
44
89
|
if (seen.has(resolvedLower))
|
|
@@ -70,7 +115,36 @@ export async function getSection(ctx, query) {
|
|
|
70
115
|
}
|
|
71
116
|
}
|
|
72
117
|
}
|
|
73
|
-
|
|
118
|
+
// Find code back-references: @lat: comments pointing to this section
|
|
119
|
+
const codeRefs = [];
|
|
120
|
+
const { refs: scannedRefs } = await scanCodeRefs(ctx.projectRoot);
|
|
121
|
+
for (const ref of scannedRefs) {
|
|
122
|
+
const { resolved: codeResolved } = resolveRef(ref.target, sectionIds, fileIndex);
|
|
123
|
+
if (codeResolved.toLowerCase() === sectionId) {
|
|
124
|
+
const absFile = join(ctx.projectRoot, ref.file);
|
|
125
|
+
let snippet = '';
|
|
126
|
+
try {
|
|
127
|
+
const src = await readFile(absFile, 'utf-8');
|
|
128
|
+
const srcLines = src.split('\n');
|
|
129
|
+
const start = Math.max(0, ref.line - 1 - 2);
|
|
130
|
+
const end = Math.min(srcLines.length, ref.line - 1 + 3);
|
|
131
|
+
snippet = srcLines.slice(start, end).join('\n');
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// file unreadable — skip snippet
|
|
135
|
+
}
|
|
136
|
+
codeRefs.push({ file: ref.file, line: ref.line, snippet });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
kind: 'found',
|
|
141
|
+
section,
|
|
142
|
+
content,
|
|
143
|
+
outgoingRefs,
|
|
144
|
+
outgoingSourceRefs,
|
|
145
|
+
incomingRefs,
|
|
146
|
+
codeRefs,
|
|
147
|
+
};
|
|
74
148
|
}
|
|
75
149
|
function fullEndLine(section) {
|
|
76
150
|
if (section.children.length === 0)
|
|
@@ -85,7 +159,7 @@ function truncate(s, max) {
|
|
|
85
159
|
*/
|
|
86
160
|
export function formatSectionOutput(ctx, result) {
|
|
87
161
|
const s = ctx.styler;
|
|
88
|
-
const { section, content, outgoingRefs, incomingRefs } = result;
|
|
162
|
+
const { section, content, outgoingRefs, outgoingSourceRefs, incomingRefs, codeRefs, } = result;
|
|
89
163
|
const relPath = relative(process.cwd(), join(ctx.projectRoot, section.filePath));
|
|
90
164
|
const loc = `${s.cyan(relPath)}${s.dim(`:${section.startLine}-${section.endLine}`)}`;
|
|
91
165
|
const quoted = content
|
|
@@ -97,7 +171,7 @@ export function formatSectionOutput(ctx, result) {
|
|
|
97
171
|
'',
|
|
98
172
|
quoted,
|
|
99
173
|
];
|
|
100
|
-
if (outgoingRefs.length > 0) {
|
|
174
|
+
if (outgoingRefs.length > 0 || outgoingSourceRefs.length > 0) {
|
|
101
175
|
parts.push('', '## This section references:', '');
|
|
102
176
|
for (const ref of outgoingRefs) {
|
|
103
177
|
const body = ref.resolved.firstParagraph
|
|
@@ -105,6 +179,18 @@ export function formatSectionOutput(ctx, result) {
|
|
|
105
179
|
: '';
|
|
106
180
|
parts.push(`${s.dim('*')} [[${formatSectionId(ref.resolved.id, s)}]]${body}`);
|
|
107
181
|
}
|
|
182
|
+
for (const ref of outgoingSourceRefs) {
|
|
183
|
+
const loc = ref.line
|
|
184
|
+
? `${s.dim(` (${ref.file}:${ref.line})`)}`
|
|
185
|
+
: `${s.dim(` (${ref.file})`)}`;
|
|
186
|
+
parts.push(`${s.dim('*')} [[${s.cyan(ref.target)}]]${loc}`);
|
|
187
|
+
if (ref.snippet) {
|
|
188
|
+
const snippetLines = ref.snippet.split('\n');
|
|
189
|
+
for (const line of snippetLines) {
|
|
190
|
+
parts.push(` ${s.dim('|')} ${line}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
108
194
|
}
|
|
109
195
|
if (incomingRefs.length > 0) {
|
|
110
196
|
parts.push('', '## Referenced by:', '');
|
|
@@ -115,6 +201,19 @@ export function formatSectionOutput(ctx, result) {
|
|
|
115
201
|
parts.push(`${s.dim('*')} [[${formatSectionId(ref.section.id, s)}]]${body}`);
|
|
116
202
|
}
|
|
117
203
|
}
|
|
204
|
+
if (codeRefs.length > 0) {
|
|
205
|
+
parts.push('', '## Referenced by code:', '');
|
|
206
|
+
for (const ref of codeRefs) {
|
|
207
|
+
const codeRelPath = relative(process.cwd(), join(ctx.projectRoot, ref.file));
|
|
208
|
+
parts.push(`${s.dim('*')} ${s.cyan(codeRelPath)}${s.dim(`:${ref.line}`)}`);
|
|
209
|
+
if (ref.snippet) {
|
|
210
|
+
const snippetLines = ref.snippet.split('\n');
|
|
211
|
+
for (const line of snippetLines) {
|
|
212
|
+
parts.push(` ${s.dim('|')} ${line}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
118
217
|
parts.push(formatNavHints(ctx));
|
|
119
218
|
return parts.join('\n');
|
|
120
219
|
}
|
package/dist/src/code-refs.d.ts
CHANGED
|
@@ -10,5 +10,8 @@ export type CodeRef = {
|
|
|
10
10
|
export type ScanResult = {
|
|
11
11
|
refs: CodeRef[];
|
|
12
12
|
files: string[];
|
|
13
|
+
usedRg: boolean;
|
|
13
14
|
};
|
|
15
|
+
/** Check whether ripgrep (`rg`) is available on PATH. */
|
|
16
|
+
export declare function hasRipgrep(): Promise<boolean>;
|
|
14
17
|
export declare function scanCodeRefs(projectRoot: string): Promise<ScanResult>;
|
package/dist/src/code-refs.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
2
3
|
import { join, relative } from 'node:path';
|
|
3
4
|
import { walkEntries } from './walk.js';
|
|
5
|
+
/** Glob patterns used to exclude directories/files from code-ref scanning.
|
|
6
|
+
* Shared between rg args and the TS fallback's walkFiles filter. */
|
|
7
|
+
const EXCLUDE_DIRS = ['lat.md', '.claude'];
|
|
8
|
+
const EXCLUDE_GLOBS = ['*.md'];
|
|
4
9
|
/** Walk project files for code-ref scanning. Uses walkEntries for .gitignore
|
|
5
10
|
* support, then additionally skips .md files, lat.md/, .claude/, and sub-projects. */
|
|
6
11
|
export async function walkFiles(dir) {
|
|
@@ -31,8 +36,141 @@ export const LAT_REF_RE = re('gv') `
|
|
|
31
36
|
( [^\]]+ )
|
|
32
37
|
\]\]
|
|
33
38
|
`;
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Run an external command and return stdout, or null if the command is not found
|
|
41
|
+
* or fails.
|
|
42
|
+
*/
|
|
43
|
+
function tryExec(cmd, args, cwd) {
|
|
44
|
+
return new Promise((resolve) => {
|
|
45
|
+
execFile(cmd, args, { cwd, maxBuffer: 50 * 1024 * 1024 }, (err, out) => {
|
|
46
|
+
if (err) {
|
|
47
|
+
// Exit code 1 with no stderr typically means "no matches" for grep/rg
|
|
48
|
+
const exitCode = err.code;
|
|
49
|
+
if (exitCode === 'ENOENT') {
|
|
50
|
+
resolve(null); // command not found
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
// rg/grep exit 1 = no matches (not an error)
|
|
54
|
+
if ('status' in err &&
|
|
55
|
+
err.status === 1 &&
|
|
56
|
+
out === '') {
|
|
57
|
+
resolve('');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
resolve(null);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
resolve(out);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Detect sub-projects (directories containing their own lat.md/) using
|
|
69
|
+
* rg --files. Finds files inside nested lat.md/ dirs and extracts the parent
|
|
70
|
+
* directory paths. Returns paths relative to projectRoot.
|
|
71
|
+
*/
|
|
72
|
+
async function findSubProjects(projectRoot) {
|
|
73
|
+
// List files inside any lat.md/ dir, then extract unique parent paths.
|
|
74
|
+
// The root lat.md/ is excluded by EXCLUDE_DIRS in the caller, so we only
|
|
75
|
+
// need to find nested ones here — search for files under */lat.md/.
|
|
76
|
+
const out = await tryExec('rg', ['--files', '--glob', '**/lat.md/**', '.'], projectRoot);
|
|
77
|
+
if (!out)
|
|
78
|
+
return [];
|
|
79
|
+
const subProjects = new Set();
|
|
80
|
+
for (const line of out.split('\n')) {
|
|
81
|
+
if (!line)
|
|
82
|
+
continue;
|
|
83
|
+
const clean = line.startsWith('./') ? line.slice(2) : line;
|
|
84
|
+
// "tests/cases/foo/lat.md/specs.md" → "tests/cases/foo"
|
|
85
|
+
// Skip root lat.md/ (no parent prefix — starts with "lat.md/")
|
|
86
|
+
const idx = clean.indexOf('/lat.md/');
|
|
87
|
+
if (idx !== -1)
|
|
88
|
+
subProjects.add(clean.slice(0, idx));
|
|
89
|
+
}
|
|
90
|
+
return [...subProjects];
|
|
91
|
+
}
|
|
92
|
+
/** Build rg glob exclusion args. */
|
|
93
|
+
function rgExcludeArgs(subProjects) {
|
|
94
|
+
const args = [];
|
|
95
|
+
for (const dir of EXCLUDE_DIRS)
|
|
96
|
+
args.push('--glob', `!${dir}/`);
|
|
97
|
+
for (const glob of EXCLUDE_GLOBS)
|
|
98
|
+
args.push('--glob', `!${glob}`);
|
|
99
|
+
for (const sp of subProjects)
|
|
100
|
+
args.push('--glob', `!${sp}/`);
|
|
101
|
+
return args;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Try scanning with ripgrep. Returns parsed refs and scanned file list, or null
|
|
105
|
+
* if rg is not available. rg respects .gitignore by default; we add glob
|
|
106
|
+
* exclusions for lat.md/, .claude/, *.md files, and sub-projects.
|
|
107
|
+
*/
|
|
108
|
+
async function tryRipgrep(projectRoot) {
|
|
109
|
+
// Detect sub-projects first so we can exclude them from all rg calls
|
|
110
|
+
const subProjects = await findSubProjects(projectRoot);
|
|
111
|
+
const excludes = rgExcludeArgs(subProjects);
|
|
112
|
+
// Search for @lat refs
|
|
113
|
+
const searchArgs = [
|
|
114
|
+
'--no-heading',
|
|
115
|
+
'--line-number',
|
|
116
|
+
'--with-filename',
|
|
117
|
+
...excludes,
|
|
118
|
+
'@lat:.*\\[\\[',
|
|
119
|
+
'.',
|
|
120
|
+
];
|
|
121
|
+
const out = await tryExec('rg', searchArgs, projectRoot);
|
|
122
|
+
if (out === null)
|
|
123
|
+
return null;
|
|
124
|
+
const { refs } = parseGrepOutput(out, projectRoot);
|
|
125
|
+
// List all scanned files (for stats) — rg --files is fast
|
|
126
|
+
const filesOut = await tryExec('rg', ['--files', ...excludes, '.'], projectRoot);
|
|
127
|
+
const files = (filesOut || '')
|
|
128
|
+
.split('\n')
|
|
129
|
+
.filter(Boolean)
|
|
130
|
+
.map((f) => {
|
|
131
|
+
const clean = f.startsWith('./') ? f.slice(2) : f;
|
|
132
|
+
return join(projectRoot, clean);
|
|
133
|
+
});
|
|
134
|
+
return { refs, files };
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Parse rg output lines (file:line:content) into CodeRef entries.
|
|
138
|
+
*/
|
|
139
|
+
function parseGrepOutput(output, projectRoot) {
|
|
140
|
+
const refs = [];
|
|
141
|
+
if (!output.trim())
|
|
142
|
+
return { refs };
|
|
143
|
+
for (const line of output.split('\n')) {
|
|
144
|
+
if (!line)
|
|
145
|
+
continue;
|
|
146
|
+
// Format: ./path/to/file:linenum:content
|
|
147
|
+
const firstColon = line.indexOf(':');
|
|
148
|
+
if (firstColon === -1)
|
|
149
|
+
continue;
|
|
150
|
+
const secondColon = line.indexOf(':', firstColon + 1);
|
|
151
|
+
if (secondColon === -1)
|
|
152
|
+
continue;
|
|
153
|
+
let filePath = line.slice(0, firstColon);
|
|
154
|
+
const lineNum = parseInt(line.slice(firstColon + 1, secondColon), 10);
|
|
155
|
+
const content = line.slice(secondColon + 1);
|
|
156
|
+
if (isNaN(lineNum))
|
|
157
|
+
continue;
|
|
158
|
+
// Strip leading ./ from path
|
|
159
|
+
if (filePath.startsWith('./'))
|
|
160
|
+
filePath = filePath.slice(2);
|
|
161
|
+
// Extract targets using the same regex as the TS fallback
|
|
162
|
+
LAT_REF_RE.lastIndex = 0;
|
|
163
|
+
let match;
|
|
164
|
+
while ((match = LAT_REF_RE.exec(content)) !== null) {
|
|
165
|
+
refs.push({ target: match[1], file: filePath, line: lineNum });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return { refs };
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* TypeScript fallback: read every file and scan for @lat refs.
|
|
172
|
+
*/
|
|
173
|
+
async function scanWithTs(files, projectRoot) {
|
|
36
174
|
const refs = [];
|
|
37
175
|
for (const file of files) {
|
|
38
176
|
let content;
|
|
@@ -50,11 +188,30 @@ export async function scanCodeRefs(projectRoot) {
|
|
|
50
188
|
while ((match = LAT_REF_RE.exec(lines[i])) !== null) {
|
|
51
189
|
refs.push({
|
|
52
190
|
target: match[1],
|
|
53
|
-
file: relative(
|
|
191
|
+
file: relative(projectRoot, file),
|
|
54
192
|
line: i + 1,
|
|
55
193
|
});
|
|
56
194
|
}
|
|
57
195
|
}
|
|
58
196
|
}
|
|
59
|
-
return
|
|
197
|
+
return refs;
|
|
198
|
+
}
|
|
199
|
+
/** Check whether ripgrep (`rg`) is available on PATH. */
|
|
200
|
+
export async function hasRipgrep() {
|
|
201
|
+
const result = await tryExec('rg', ['--version'], '.');
|
|
202
|
+
return result !== null;
|
|
203
|
+
}
|
|
204
|
+
export async function scanCodeRefs(projectRoot) {
|
|
205
|
+
// Fast path: use rg for both searching and file listing
|
|
206
|
+
// _LAT_DISABLE_RG is a test-only escape hatch to force the TS fallback
|
|
207
|
+
if (process.env._LAT_DISABLE_RG !== '1') {
|
|
208
|
+
const rgResult = await tryRipgrep(projectRoot);
|
|
209
|
+
if (rgResult !== null) {
|
|
210
|
+
return { refs: rgResult.refs, files: rgResult.files, usedRg: true };
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Fallback: walk files ourselves and scan with TS
|
|
214
|
+
const files = await walkFiles(projectRoot);
|
|
215
|
+
const refs = await scanWithTs(files, projectRoot);
|
|
216
|
+
return { refs, files, usedRg: false };
|
|
60
217
|
}
|
package/dist/src/mcp/server.js
CHANGED
|
@@ -50,7 +50,7 @@ export async function startMcpServer() {
|
|
|
50
50
|
scope: z
|
|
51
51
|
.enum(['md', 'code', 'md+code'])
|
|
52
52
|
.optional()
|
|
53
|
-
.default('md')
|
|
53
|
+
.default('md+code')
|
|
54
54
|
.describe('Where to search: md, code, or md+code'),
|
|
55
55
|
}, async ({ query, scope }) => toMcp(await refsCommand(ctx, query, scope)));
|
|
56
56
|
const transport = new StdioServerTransport();
|
|
@@ -173,33 +173,43 @@ function extractPySymbols(tree) {
|
|
|
173
173
|
const node = root.child(i);
|
|
174
174
|
const startLine = node.startPosition.row + 1;
|
|
175
175
|
const endLine = node.endPosition.row + 1;
|
|
176
|
-
|
|
177
|
-
|
|
176
|
+
// Unwrap decorated_definition to get the inner function/class
|
|
177
|
+
const inner = node.type === 'decorated_definition'
|
|
178
|
+
? node.childForFieldName('definition')
|
|
179
|
+
: node;
|
|
180
|
+
if (!inner)
|
|
181
|
+
continue;
|
|
182
|
+
if (inner.type === 'function_definition') {
|
|
183
|
+
const name = extractName(inner);
|
|
178
184
|
if (name) {
|
|
179
185
|
symbols.push({
|
|
180
186
|
name,
|
|
181
187
|
kind: 'function',
|
|
182
188
|
startLine,
|
|
183
189
|
endLine,
|
|
184
|
-
signature: firstLine(
|
|
190
|
+
signature: firstLine(inner.text),
|
|
185
191
|
});
|
|
186
192
|
}
|
|
187
193
|
}
|
|
188
|
-
else if (
|
|
189
|
-
const name = extractName(
|
|
194
|
+
else if (inner.type === 'class_definition') {
|
|
195
|
+
const name = extractName(inner);
|
|
190
196
|
if (name) {
|
|
191
197
|
symbols.push({
|
|
192
198
|
name,
|
|
193
199
|
kind: 'class',
|
|
194
200
|
startLine,
|
|
195
201
|
endLine,
|
|
196
|
-
signature: firstLine(
|
|
202
|
+
signature: firstLine(inner.text),
|
|
197
203
|
});
|
|
198
204
|
// Extract methods
|
|
199
|
-
const body =
|
|
205
|
+
const body = inner.childForFieldName('body');
|
|
200
206
|
if (body) {
|
|
201
207
|
for (let j = 0; j < body.namedChildCount; j++) {
|
|
202
|
-
|
|
208
|
+
let member = body.namedChild(j);
|
|
209
|
+
// Unwrap decorated methods
|
|
210
|
+
if (member.type === 'decorated_definition') {
|
|
211
|
+
member = member.childForFieldName('definition') ?? member;
|
|
212
|
+
}
|
|
203
213
|
if (member.type === 'function_definition') {
|
|
204
214
|
const methodName = extractName(member);
|
|
205
215
|
if (methodName) {
|
|
@@ -217,11 +227,11 @@ function extractPySymbols(tree) {
|
|
|
217
227
|
}
|
|
218
228
|
}
|
|
219
229
|
}
|
|
220
|
-
else if (
|
|
221
|
-
|
|
222
|
-
|
|
230
|
+
else if (inner.type === 'expression_statement' &&
|
|
231
|
+
inner.namedChildCount === 1 &&
|
|
232
|
+
inner.namedChild(0).type === 'assignment') {
|
|
223
233
|
// Top-level assignment: FOO = ...
|
|
224
|
-
const assign =
|
|
234
|
+
const assign = inner.namedChild(0);
|
|
225
235
|
const left = assign.childForFieldName('left');
|
|
226
236
|
if (left && left.type === 'identifier') {
|
|
227
237
|
symbols.push({
|
package/package.json
CHANGED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 73224 16688" fill="#444">
|
|
2
|
+
<defs>
|
|
3
|
+
<path id="g2591" d="M934 -512V-251H1094V-512ZM298 -512V-251H457V-512ZM616 -147V114H775V-147ZM-20 -147V114H138V-147ZM934 219V480H1094V219ZM298 219V480H457V219ZM616 584V845H775V584ZM-20 584V845H138V584ZM934 950V1211H1094V950ZM298 950V1211H457V950ZM616 1315V1576H775V1315ZM-20 1315V1576H138V1315Z"/>
|
|
4
|
+
<path id="g2588" d="M-20 -512V1576H1253V-512Z"/>
|
|
5
|
+
</defs>
|
|
6
|
+
<use href="#g2591" transform="translate(0,0) scale(1,-1) translate(0,-1901)"/>
|
|
7
|
+
<use href="#g2588" transform="translate(1356,0) scale(1,-1) translate(0,-1901)"/>
|
|
8
|
+
<use href="#g2588" transform="translate(2712,0) scale(1,-1) translate(0,-1901)"/>
|
|
9
|
+
<use href="#g2591" transform="translate(21696,0) scale(1,-1) translate(0,-1901)"/>
|
|
10
|
+
<use href="#g2588" transform="translate(23052,0) scale(1,-1) translate(0,-1901)"/>
|
|
11
|
+
<use href="#g2588" transform="translate(24408,0) scale(1,-1) translate(0,-1901)"/>
|
|
12
|
+
<use href="#g2591" transform="translate(69156,0) scale(1,-1) translate(0,-1901)"/>
|
|
13
|
+
<use href="#g2588" transform="translate(70512,0) scale(1,-1) translate(0,-1901)"/>
|
|
14
|
+
<use href="#g2588" transform="translate(71868,0) scale(1,-1) translate(0,-1901)"/>
|
|
15
|
+
<use href="#g2591" transform="translate(0,2384) scale(1,-1) translate(0,-1901)"/>
|
|
16
|
+
<use href="#g2588" transform="translate(1356,2384) scale(1,-1) translate(0,-1901)"/>
|
|
17
|
+
<use href="#g2588" transform="translate(2712,2384) scale(1,-1) translate(0,-1901)"/>
|
|
18
|
+
<use href="#g2591" transform="translate(21696,2384) scale(1,-1) translate(0,-1901)"/>
|
|
19
|
+
<use href="#g2588" transform="translate(23052,2384) scale(1,-1) translate(0,-1901)"/>
|
|
20
|
+
<use href="#g2588" transform="translate(24408,2384) scale(1,-1) translate(0,-1901)"/>
|
|
21
|
+
<use href="#g2591" transform="translate(69156,2384) scale(1,-1) translate(0,-1901)"/>
|
|
22
|
+
<use href="#g2588" transform="translate(70512,2384) scale(1,-1) translate(0,-1901)"/>
|
|
23
|
+
<use href="#g2588" transform="translate(71868,2384) scale(1,-1) translate(0,-1901)"/>
|
|
24
|
+
<use href="#g2591" transform="translate(0,4768) scale(1,-1) translate(0,-1901)"/>
|
|
25
|
+
<use href="#g2588" transform="translate(1356,4768) scale(1,-1) translate(0,-1901)"/>
|
|
26
|
+
<use href="#g2588" transform="translate(2712,4768) scale(1,-1) translate(0,-1901)"/>
|
|
27
|
+
<use href="#g2591" transform="translate(6780,4768) scale(1,-1) translate(0,-1901)"/>
|
|
28
|
+
<use href="#g2588" transform="translate(8136,4768) scale(1,-1) translate(0,-1901)"/>
|
|
29
|
+
<use href="#g2588" transform="translate(9492,4768) scale(1,-1) translate(0,-1901)"/>
|
|
30
|
+
<use href="#g2588" transform="translate(10848,4768) scale(1,-1) translate(0,-1901)"/>
|
|
31
|
+
<use href="#g2588" transform="translate(12204,4768) scale(1,-1) translate(0,-1901)"/>
|
|
32
|
+
<use href="#g2588" transform="translate(13560,4768) scale(1,-1) translate(0,-1901)"/>
|
|
33
|
+
<use href="#g2588" transform="translate(14916,4768) scale(1,-1) translate(0,-1901)"/>
|
|
34
|
+
<use href="#g2591" transform="translate(18984,4768) scale(1,-1) translate(0,-1901)"/>
|
|
35
|
+
<use href="#g2588" transform="translate(20340,4768) scale(1,-1) translate(0,-1901)"/>
|
|
36
|
+
<use href="#g2588" transform="translate(21696,4768) scale(1,-1) translate(0,-1901)"/>
|
|
37
|
+
<use href="#g2588" transform="translate(23052,4768) scale(1,-1) translate(0,-1901)"/>
|
|
38
|
+
<use href="#g2588" transform="translate(24408,4768) scale(1,-1) translate(0,-1901)"/>
|
|
39
|
+
<use href="#g2588" transform="translate(25764,4768) scale(1,-1) translate(0,-1901)"/>
|
|
40
|
+
<use href="#g2588" transform="translate(27120,4768) scale(1,-1) translate(0,-1901)"/>
|
|
41
|
+
<use href="#g2591" transform="translate(37968,4768) scale(1,-1) translate(0,-1901)"/>
|
|
42
|
+
<use href="#g2588" transform="translate(39324,4768) scale(1,-1) translate(0,-1901)"/>
|
|
43
|
+
<use href="#g2588" transform="translate(40680,4768) scale(1,-1) translate(0,-1901)"/>
|
|
44
|
+
<use href="#g2588" transform="translate(42036,4768) scale(1,-1) translate(0,-1901)"/>
|
|
45
|
+
<use href="#g2588" transform="translate(43392,4768) scale(1,-1) translate(0,-1901)"/>
|
|
46
|
+
<use href="#g2588" transform="translate(44748,4768) scale(1,-1) translate(0,-1901)"/>
|
|
47
|
+
<use href="#g2588" transform="translate(46104,4768) scale(1,-1) translate(0,-1901)"/>
|
|
48
|
+
<use href="#g2588" transform="translate(47460,4768) scale(1,-1) translate(0,-1901)"/>
|
|
49
|
+
<use href="#g2588" transform="translate(48816,4768) scale(1,-1) translate(0,-1901)"/>
|
|
50
|
+
<use href="#g2588" transform="translate(50172,4768) scale(1,-1) translate(0,-1901)"/>
|
|
51
|
+
<use href="#g2588" transform="translate(51528,4768) scale(1,-1) translate(0,-1901)"/>
|
|
52
|
+
<use href="#g2588" transform="translate(52884,4768) scale(1,-1) translate(0,-1901)"/>
|
|
53
|
+
<use href="#g2588" transform="translate(54240,4768) scale(1,-1) translate(0,-1901)"/>
|
|
54
|
+
<use href="#g2588" transform="translate(55596,4768) scale(1,-1) translate(0,-1901)"/>
|
|
55
|
+
<use href="#g2591" transform="translate(61020,4768) scale(1,-1) translate(0,-1901)"/>
|
|
56
|
+
<use href="#g2588" transform="translate(62376,4768) scale(1,-1) translate(0,-1901)"/>
|
|
57
|
+
<use href="#g2588" transform="translate(63732,4768) scale(1,-1) translate(0,-1901)"/>
|
|
58
|
+
<use href="#g2588" transform="translate(65088,4768) scale(1,-1) translate(0,-1901)"/>
|
|
59
|
+
<use href="#g2588" transform="translate(66444,4768) scale(1,-1) translate(0,-1901)"/>
|
|
60
|
+
<use href="#g2588" transform="translate(67800,4768) scale(1,-1) translate(0,-1901)"/>
|
|
61
|
+
<use href="#g2588" transform="translate(69156,4768) scale(1,-1) translate(0,-1901)"/>
|
|
62
|
+
<use href="#g2588" transform="translate(70512,4768) scale(1,-1) translate(0,-1901)"/>
|
|
63
|
+
<use href="#g2588" transform="translate(71868,4768) scale(1,-1) translate(0,-1901)"/>
|
|
64
|
+
<use href="#g2591" transform="translate(0,7152) scale(1,-1) translate(0,-1901)"/>
|
|
65
|
+
<use href="#g2588" transform="translate(1356,7152) scale(1,-1) translate(0,-1901)"/>
|
|
66
|
+
<use href="#g2588" transform="translate(2712,7152) scale(1,-1) translate(0,-1901)"/>
|
|
67
|
+
<use href="#g2591" transform="translate(13560,7152) scale(1,-1) translate(0,-1901)"/>
|
|
68
|
+
<use href="#g2588" transform="translate(14916,7152) scale(1,-1) translate(0,-1901)"/>
|
|
69
|
+
<use href="#g2588" transform="translate(16272,7152) scale(1,-1) translate(0,-1901)"/>
|
|
70
|
+
<use href="#g2591" transform="translate(21696,7152) scale(1,-1) translate(0,-1901)"/>
|
|
71
|
+
<use href="#g2588" transform="translate(23052,7152) scale(1,-1) translate(0,-1901)"/>
|
|
72
|
+
<use href="#g2588" transform="translate(24408,7152) scale(1,-1) translate(0,-1901)"/>
|
|
73
|
+
<use href="#g2591" transform="translate(37968,7152) scale(1,-1) translate(0,-1901)"/>
|
|
74
|
+
<use href="#g2588" transform="translate(39324,7152) scale(1,-1) translate(0,-1901)"/>
|
|
75
|
+
<use href="#g2588" transform="translate(40680,7152) scale(1,-1) translate(0,-1901)"/>
|
|
76
|
+
<use href="#g2591" transform="translate(46104,7152) scale(1,-1) translate(0,-1901)"/>
|
|
77
|
+
<use href="#g2588" transform="translate(47460,7152) scale(1,-1) translate(0,-1901)"/>
|
|
78
|
+
<use href="#g2588" transform="translate(48816,7152) scale(1,-1) translate(0,-1901)"/>
|
|
79
|
+
<use href="#g2591" transform="translate(54240,7152) scale(1,-1) translate(0,-1901)"/>
|
|
80
|
+
<use href="#g2588" transform="translate(55596,7152) scale(1,-1) translate(0,-1901)"/>
|
|
81
|
+
<use href="#g2588" transform="translate(56952,7152) scale(1,-1) translate(0,-1901)"/>
|
|
82
|
+
<use href="#g2591" transform="translate(59664,7152) scale(1,-1) translate(0,-1901)"/>
|
|
83
|
+
<use href="#g2588" transform="translate(61020,7152) scale(1,-1) translate(0,-1901)"/>
|
|
84
|
+
<use href="#g2588" transform="translate(62376,7152) scale(1,-1) translate(0,-1901)"/>
|
|
85
|
+
<use href="#g2591" transform="translate(69156,7152) scale(1,-1) translate(0,-1901)"/>
|
|
86
|
+
<use href="#g2588" transform="translate(70512,7152) scale(1,-1) translate(0,-1901)"/>
|
|
87
|
+
<use href="#g2588" transform="translate(71868,7152) scale(1,-1) translate(0,-1901)"/>
|
|
88
|
+
<use href="#g2591" transform="translate(0,9536) scale(1,-1) translate(0,-1901)"/>
|
|
89
|
+
<use href="#g2588" transform="translate(1356,9536) scale(1,-1) translate(0,-1901)"/>
|
|
90
|
+
<use href="#g2588" transform="translate(2712,9536) scale(1,-1) translate(0,-1901)"/>
|
|
91
|
+
<use href="#g2591" transform="translate(6780,9536) scale(1,-1) translate(0,-1901)"/>
|
|
92
|
+
<use href="#g2588" transform="translate(8136,9536) scale(1,-1) translate(0,-1901)"/>
|
|
93
|
+
<use href="#g2588" transform="translate(9492,9536) scale(1,-1) translate(0,-1901)"/>
|
|
94
|
+
<use href="#g2588" transform="translate(10848,9536) scale(1,-1) translate(0,-1901)"/>
|
|
95
|
+
<use href="#g2588" transform="translate(12204,9536) scale(1,-1) translate(0,-1901)"/>
|
|
96
|
+
<use href="#g2588" transform="translate(13560,9536) scale(1,-1) translate(0,-1901)"/>
|
|
97
|
+
<use href="#g2588" transform="translate(14916,9536) scale(1,-1) translate(0,-1901)"/>
|
|
98
|
+
<use href="#g2588" transform="translate(16272,9536) scale(1,-1) translate(0,-1901)"/>
|
|
99
|
+
<use href="#g2591" transform="translate(21696,9536) scale(1,-1) translate(0,-1901)"/>
|
|
100
|
+
<use href="#g2588" transform="translate(23052,9536) scale(1,-1) translate(0,-1901)"/>
|
|
101
|
+
<use href="#g2588" transform="translate(24408,9536) scale(1,-1) translate(0,-1901)"/>
|
|
102
|
+
<use href="#g2591" transform="translate(37968,9536) scale(1,-1) translate(0,-1901)"/>
|
|
103
|
+
<use href="#g2588" transform="translate(39324,9536) scale(1,-1) translate(0,-1901)"/>
|
|
104
|
+
<use href="#g2588" transform="translate(40680,9536) scale(1,-1) translate(0,-1901)"/>
|
|
105
|
+
<use href="#g2591" transform="translate(46104,9536) scale(1,-1) translate(0,-1901)"/>
|
|
106
|
+
<use href="#g2588" transform="translate(47460,9536) scale(1,-1) translate(0,-1901)"/>
|
|
107
|
+
<use href="#g2588" transform="translate(48816,9536) scale(1,-1) translate(0,-1901)"/>
|
|
108
|
+
<use href="#g2591" transform="translate(54240,9536) scale(1,-1) translate(0,-1901)"/>
|
|
109
|
+
<use href="#g2588" transform="translate(55596,9536) scale(1,-1) translate(0,-1901)"/>
|
|
110
|
+
<use href="#g2588" transform="translate(56952,9536) scale(1,-1) translate(0,-1901)"/>
|
|
111
|
+
<use href="#g2591" transform="translate(59664,9536) scale(1,-1) translate(0,-1901)"/>
|
|
112
|
+
<use href="#g2588" transform="translate(61020,9536) scale(1,-1) translate(0,-1901)"/>
|
|
113
|
+
<use href="#g2588" transform="translate(62376,9536) scale(1,-1) translate(0,-1901)"/>
|
|
114
|
+
<use href="#g2591" transform="translate(69156,9536) scale(1,-1) translate(0,-1901)"/>
|
|
115
|
+
<use href="#g2588" transform="translate(70512,9536) scale(1,-1) translate(0,-1901)"/>
|
|
116
|
+
<use href="#g2588" transform="translate(71868,9536) scale(1,-1) translate(0,-1901)"/>
|
|
117
|
+
<use href="#g2591" transform="translate(0,11920) scale(1,-1) translate(0,-1901)"/>
|
|
118
|
+
<use href="#g2588" transform="translate(1356,11920) scale(1,-1) translate(0,-1901)"/>
|
|
119
|
+
<use href="#g2588" transform="translate(2712,11920) scale(1,-1) translate(0,-1901)"/>
|
|
120
|
+
<use href="#g2591" transform="translate(5424,11920) scale(1,-1) translate(0,-1901)"/>
|
|
121
|
+
<use href="#g2588" transform="translate(6780,11920) scale(1,-1) translate(0,-1901)"/>
|
|
122
|
+
<use href="#g2588" transform="translate(8136,11920) scale(1,-1) translate(0,-1901)"/>
|
|
123
|
+
<use href="#g2591" transform="translate(13560,11920) scale(1,-1) translate(0,-1901)"/>
|
|
124
|
+
<use href="#g2588" transform="translate(14916,11920) scale(1,-1) translate(0,-1901)"/>
|
|
125
|
+
<use href="#g2588" transform="translate(16272,11920) scale(1,-1) translate(0,-1901)"/>
|
|
126
|
+
<use href="#g2591" transform="translate(21696,11920) scale(1,-1) translate(0,-1901)"/>
|
|
127
|
+
<use href="#g2588" transform="translate(23052,11920) scale(1,-1) translate(0,-1901)"/>
|
|
128
|
+
<use href="#g2588" transform="translate(24408,11920) scale(1,-1) translate(0,-1901)"/>
|
|
129
|
+
<use href="#g2591" transform="translate(37968,11920) scale(1,-1) translate(0,-1901)"/>
|
|
130
|
+
<use href="#g2588" transform="translate(39324,11920) scale(1,-1) translate(0,-1901)"/>
|
|
131
|
+
<use href="#g2588" transform="translate(40680,11920) scale(1,-1) translate(0,-1901)"/>
|
|
132
|
+
<use href="#g2591" transform="translate(46104,11920) scale(1,-1) translate(0,-1901)"/>
|
|
133
|
+
<use href="#g2588" transform="translate(47460,11920) scale(1,-1) translate(0,-1901)"/>
|
|
134
|
+
<use href="#g2588" transform="translate(48816,11920) scale(1,-1) translate(0,-1901)"/>
|
|
135
|
+
<use href="#g2591" transform="translate(54240,11920) scale(1,-1) translate(0,-1901)"/>
|
|
136
|
+
<use href="#g2588" transform="translate(55596,11920) scale(1,-1) translate(0,-1901)"/>
|
|
137
|
+
<use href="#g2588" transform="translate(56952,11920) scale(1,-1) translate(0,-1901)"/>
|
|
138
|
+
<use href="#g2591" transform="translate(59664,11920) scale(1,-1) translate(0,-1901)"/>
|
|
139
|
+
<use href="#g2588" transform="translate(61020,11920) scale(1,-1) translate(0,-1901)"/>
|
|
140
|
+
<use href="#g2588" transform="translate(62376,11920) scale(1,-1) translate(0,-1901)"/>
|
|
141
|
+
<use href="#g2591" transform="translate(67800,11920) scale(1,-1) translate(0,-1901)"/>
|
|
142
|
+
<use href="#g2588" transform="translate(69156,11920) scale(1,-1) translate(0,-1901)"/>
|
|
143
|
+
<use href="#g2588" transform="translate(70512,11920) scale(1,-1) translate(0,-1901)"/>
|
|
144
|
+
<use href="#g2588" transform="translate(71868,11920) scale(1,-1) translate(0,-1901)"/>
|
|
145
|
+
<use href="#g2591" transform="translate(0,14304) scale(1,-1) translate(0,-1901)"/>
|
|
146
|
+
<use href="#g2588" transform="translate(1356,14304) scale(1,-1) translate(0,-1901)"/>
|
|
147
|
+
<use href="#g2588" transform="translate(2712,14304) scale(1,-1) translate(0,-1901)"/>
|
|
148
|
+
<use href="#g2591" transform="translate(6780,14304) scale(1,-1) translate(0,-1901)"/>
|
|
149
|
+
<use href="#g2588" transform="translate(8136,14304) scale(1,-1) translate(0,-1901)"/>
|
|
150
|
+
<use href="#g2588" transform="translate(9492,14304) scale(1,-1) translate(0,-1901)"/>
|
|
151
|
+
<use href="#g2588" transform="translate(10848,14304) scale(1,-1) translate(0,-1901)"/>
|
|
152
|
+
<use href="#g2588" transform="translate(12204,14304) scale(1,-1) translate(0,-1901)"/>
|
|
153
|
+
<use href="#g2588" transform="translate(13560,14304) scale(1,-1) translate(0,-1901)"/>
|
|
154
|
+
<use href="#g2591" transform="translate(14916,14304) scale(1,-1) translate(0,-1901)"/>
|
|
155
|
+
<use href="#g2588" transform="translate(16272,14304) scale(1,-1) translate(0,-1901)"/>
|
|
156
|
+
<use href="#g2588" transform="translate(17628,14304) scale(1,-1) translate(0,-1901)"/>
|
|
157
|
+
<use href="#g2591" transform="translate(23052,14304) scale(1,-1) translate(0,-1901)"/>
|
|
158
|
+
<use href="#g2588" transform="translate(24408,14304) scale(1,-1) translate(0,-1901)"/>
|
|
159
|
+
<use href="#g2588" transform="translate(25764,14304) scale(1,-1) translate(0,-1901)"/>
|
|
160
|
+
<use href="#g2588" transform="translate(27120,14304) scale(1,-1) translate(0,-1901)"/>
|
|
161
|
+
<use href="#g2591" transform="translate(31188,14304) scale(1,-1) translate(0,-1901)"/>
|
|
162
|
+
<use href="#g2588" transform="translate(32544,14304) scale(1,-1) translate(0,-1901)"/>
|
|
163
|
+
<use href="#g2588" transform="translate(33900,14304) scale(1,-1) translate(0,-1901)"/>
|
|
164
|
+
<use href="#g2591" transform="translate(37968,14304) scale(1,-1) translate(0,-1901)"/>
|
|
165
|
+
<use href="#g2588" transform="translate(39324,14304) scale(1,-1) translate(0,-1901)"/>
|
|
166
|
+
<use href="#g2588" transform="translate(40680,14304) scale(1,-1) translate(0,-1901)"/>
|
|
167
|
+
<use href="#g2591" transform="translate(46104,14304) scale(1,-1) translate(0,-1901)"/>
|
|
168
|
+
<use href="#g2588" transform="translate(47460,14304) scale(1,-1) translate(0,-1901)"/>
|
|
169
|
+
<use href="#g2588" transform="translate(48816,14304) scale(1,-1) translate(0,-1901)"/>
|
|
170
|
+
<use href="#g2591" transform="translate(54240,14304) scale(1,-1) translate(0,-1901)"/>
|
|
171
|
+
<use href="#g2588" transform="translate(55596,14304) scale(1,-1) translate(0,-1901)"/>
|
|
172
|
+
<use href="#g2588" transform="translate(56952,14304) scale(1,-1) translate(0,-1901)"/>
|
|
173
|
+
<use href="#g2591" transform="translate(61020,14304) scale(1,-1) translate(0,-1901)"/>
|
|
174
|
+
<use href="#g2588" transform="translate(62376,14304) scale(1,-1) translate(0,-1901)"/>
|
|
175
|
+
<use href="#g2588" transform="translate(63732,14304) scale(1,-1) translate(0,-1901)"/>
|
|
176
|
+
<use href="#g2588" transform="translate(65088,14304) scale(1,-1) translate(0,-1901)"/>
|
|
177
|
+
<use href="#g2588" transform="translate(66444,14304) scale(1,-1) translate(0,-1901)"/>
|
|
178
|
+
<use href="#g2588" transform="translate(67800,14304) scale(1,-1) translate(0,-1901)"/>
|
|
179
|
+
<use href="#g2591" transform="translate(69156,14304) scale(1,-1) translate(0,-1901)"/>
|
|
180
|
+
<use href="#g2588" transform="translate(70512,14304) scale(1,-1) translate(0,-1901)"/>
|
|
181
|
+
<use href="#g2588" transform="translate(71868,14304) scale(1,-1) translate(0,-1901)"/>
|
|
182
|
+
</svg>
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 73224 16688" fill="#ddd">
|
|
2
|
+
<defs>
|
|
3
|
+
<path id="g2588" d="M-20 -512V1576H1253V-512Z"/>
|
|
4
|
+
<path id="g2591" d="M934 -512V-251H1094V-512ZM298 -512V-251H457V-512ZM616 -147V114H775V-147ZM-20 -147V114H138V-147ZM934 219V480H1094V219ZM298 219V480H457V219ZM616 584V845H775V584ZM-20 584V845H138V584ZM934 950V1211H1094V950ZM298 950V1211H457V950ZM616 1315V1576H775V1315ZM-20 1315V1576H138V1315Z"/>
|
|
5
|
+
</defs>
|
|
6
|
+
<use href="#g2591" transform="translate(0,0) scale(1,-1) translate(0,-1901)"/>
|
|
7
|
+
<use href="#g2588" transform="translate(1356,0) scale(1,-1) translate(0,-1901)"/>
|
|
8
|
+
<use href="#g2588" transform="translate(2712,0) scale(1,-1) translate(0,-1901)"/>
|
|
9
|
+
<use href="#g2591" transform="translate(21696,0) scale(1,-1) translate(0,-1901)"/>
|
|
10
|
+
<use href="#g2588" transform="translate(23052,0) scale(1,-1) translate(0,-1901)"/>
|
|
11
|
+
<use href="#g2588" transform="translate(24408,0) scale(1,-1) translate(0,-1901)"/>
|
|
12
|
+
<use href="#g2591" transform="translate(69156,0) scale(1,-1) translate(0,-1901)"/>
|
|
13
|
+
<use href="#g2588" transform="translate(70512,0) scale(1,-1) translate(0,-1901)"/>
|
|
14
|
+
<use href="#g2588" transform="translate(71868,0) scale(1,-1) translate(0,-1901)"/>
|
|
15
|
+
<use href="#g2591" transform="translate(0,2384) scale(1,-1) translate(0,-1901)"/>
|
|
16
|
+
<use href="#g2588" transform="translate(1356,2384) scale(1,-1) translate(0,-1901)"/>
|
|
17
|
+
<use href="#g2588" transform="translate(2712,2384) scale(1,-1) translate(0,-1901)"/>
|
|
18
|
+
<use href="#g2591" transform="translate(21696,2384) scale(1,-1) translate(0,-1901)"/>
|
|
19
|
+
<use href="#g2588" transform="translate(23052,2384) scale(1,-1) translate(0,-1901)"/>
|
|
20
|
+
<use href="#g2588" transform="translate(24408,2384) scale(1,-1) translate(0,-1901)"/>
|
|
21
|
+
<use href="#g2591" transform="translate(69156,2384) scale(1,-1) translate(0,-1901)"/>
|
|
22
|
+
<use href="#g2588" transform="translate(70512,2384) scale(1,-1) translate(0,-1901)"/>
|
|
23
|
+
<use href="#g2588" transform="translate(71868,2384) scale(1,-1) translate(0,-1901)"/>
|
|
24
|
+
<use href="#g2591" transform="translate(0,4768) scale(1,-1) translate(0,-1901)"/>
|
|
25
|
+
<use href="#g2588" transform="translate(1356,4768) scale(1,-1) translate(0,-1901)"/>
|
|
26
|
+
<use href="#g2588" transform="translate(2712,4768) scale(1,-1) translate(0,-1901)"/>
|
|
27
|
+
<use href="#g2591" transform="translate(6780,4768) scale(1,-1) translate(0,-1901)"/>
|
|
28
|
+
<use href="#g2588" transform="translate(8136,4768) scale(1,-1) translate(0,-1901)"/>
|
|
29
|
+
<use href="#g2588" transform="translate(9492,4768) scale(1,-1) translate(0,-1901)"/>
|
|
30
|
+
<use href="#g2588" transform="translate(10848,4768) scale(1,-1) translate(0,-1901)"/>
|
|
31
|
+
<use href="#g2588" transform="translate(12204,4768) scale(1,-1) translate(0,-1901)"/>
|
|
32
|
+
<use href="#g2588" transform="translate(13560,4768) scale(1,-1) translate(0,-1901)"/>
|
|
33
|
+
<use href="#g2588" transform="translate(14916,4768) scale(1,-1) translate(0,-1901)"/>
|
|
34
|
+
<use href="#g2591" transform="translate(18984,4768) scale(1,-1) translate(0,-1901)"/>
|
|
35
|
+
<use href="#g2588" transform="translate(20340,4768) scale(1,-1) translate(0,-1901)"/>
|
|
36
|
+
<use href="#g2588" transform="translate(21696,4768) scale(1,-1) translate(0,-1901)"/>
|
|
37
|
+
<use href="#g2588" transform="translate(23052,4768) scale(1,-1) translate(0,-1901)"/>
|
|
38
|
+
<use href="#g2588" transform="translate(24408,4768) scale(1,-1) translate(0,-1901)"/>
|
|
39
|
+
<use href="#g2588" transform="translate(25764,4768) scale(1,-1) translate(0,-1901)"/>
|
|
40
|
+
<use href="#g2588" transform="translate(27120,4768) scale(1,-1) translate(0,-1901)"/>
|
|
41
|
+
<use href="#g2591" transform="translate(37968,4768) scale(1,-1) translate(0,-1901)"/>
|
|
42
|
+
<use href="#g2588" transform="translate(39324,4768) scale(1,-1) translate(0,-1901)"/>
|
|
43
|
+
<use href="#g2588" transform="translate(40680,4768) scale(1,-1) translate(0,-1901)"/>
|
|
44
|
+
<use href="#g2588" transform="translate(42036,4768) scale(1,-1) translate(0,-1901)"/>
|
|
45
|
+
<use href="#g2588" transform="translate(43392,4768) scale(1,-1) translate(0,-1901)"/>
|
|
46
|
+
<use href="#g2588" transform="translate(44748,4768) scale(1,-1) translate(0,-1901)"/>
|
|
47
|
+
<use href="#g2588" transform="translate(46104,4768) scale(1,-1) translate(0,-1901)"/>
|
|
48
|
+
<use href="#g2588" transform="translate(47460,4768) scale(1,-1) translate(0,-1901)"/>
|
|
49
|
+
<use href="#g2588" transform="translate(48816,4768) scale(1,-1) translate(0,-1901)"/>
|
|
50
|
+
<use href="#g2588" transform="translate(50172,4768) scale(1,-1) translate(0,-1901)"/>
|
|
51
|
+
<use href="#g2588" transform="translate(51528,4768) scale(1,-1) translate(0,-1901)"/>
|
|
52
|
+
<use href="#g2588" transform="translate(52884,4768) scale(1,-1) translate(0,-1901)"/>
|
|
53
|
+
<use href="#g2588" transform="translate(54240,4768) scale(1,-1) translate(0,-1901)"/>
|
|
54
|
+
<use href="#g2588" transform="translate(55596,4768) scale(1,-1) translate(0,-1901)"/>
|
|
55
|
+
<use href="#g2591" transform="translate(61020,4768) scale(1,-1) translate(0,-1901)"/>
|
|
56
|
+
<use href="#g2588" transform="translate(62376,4768) scale(1,-1) translate(0,-1901)"/>
|
|
57
|
+
<use href="#g2588" transform="translate(63732,4768) scale(1,-1) translate(0,-1901)"/>
|
|
58
|
+
<use href="#g2588" transform="translate(65088,4768) scale(1,-1) translate(0,-1901)"/>
|
|
59
|
+
<use href="#g2588" transform="translate(66444,4768) scale(1,-1) translate(0,-1901)"/>
|
|
60
|
+
<use href="#g2588" transform="translate(67800,4768) scale(1,-1) translate(0,-1901)"/>
|
|
61
|
+
<use href="#g2588" transform="translate(69156,4768) scale(1,-1) translate(0,-1901)"/>
|
|
62
|
+
<use href="#g2588" transform="translate(70512,4768) scale(1,-1) translate(0,-1901)"/>
|
|
63
|
+
<use href="#g2588" transform="translate(71868,4768) scale(1,-1) translate(0,-1901)"/>
|
|
64
|
+
<use href="#g2591" transform="translate(0,7152) scale(1,-1) translate(0,-1901)"/>
|
|
65
|
+
<use href="#g2588" transform="translate(1356,7152) scale(1,-1) translate(0,-1901)"/>
|
|
66
|
+
<use href="#g2588" transform="translate(2712,7152) scale(1,-1) translate(0,-1901)"/>
|
|
67
|
+
<use href="#g2591" transform="translate(13560,7152) scale(1,-1) translate(0,-1901)"/>
|
|
68
|
+
<use href="#g2588" transform="translate(14916,7152) scale(1,-1) translate(0,-1901)"/>
|
|
69
|
+
<use href="#g2588" transform="translate(16272,7152) scale(1,-1) translate(0,-1901)"/>
|
|
70
|
+
<use href="#g2591" transform="translate(21696,7152) scale(1,-1) translate(0,-1901)"/>
|
|
71
|
+
<use href="#g2588" transform="translate(23052,7152) scale(1,-1) translate(0,-1901)"/>
|
|
72
|
+
<use href="#g2588" transform="translate(24408,7152) scale(1,-1) translate(0,-1901)"/>
|
|
73
|
+
<use href="#g2591" transform="translate(37968,7152) scale(1,-1) translate(0,-1901)"/>
|
|
74
|
+
<use href="#g2588" transform="translate(39324,7152) scale(1,-1) translate(0,-1901)"/>
|
|
75
|
+
<use href="#g2588" transform="translate(40680,7152) scale(1,-1) translate(0,-1901)"/>
|
|
76
|
+
<use href="#g2591" transform="translate(46104,7152) scale(1,-1) translate(0,-1901)"/>
|
|
77
|
+
<use href="#g2588" transform="translate(47460,7152) scale(1,-1) translate(0,-1901)"/>
|
|
78
|
+
<use href="#g2588" transform="translate(48816,7152) scale(1,-1) translate(0,-1901)"/>
|
|
79
|
+
<use href="#g2591" transform="translate(54240,7152) scale(1,-1) translate(0,-1901)"/>
|
|
80
|
+
<use href="#g2588" transform="translate(55596,7152) scale(1,-1) translate(0,-1901)"/>
|
|
81
|
+
<use href="#g2588" transform="translate(56952,7152) scale(1,-1) translate(0,-1901)"/>
|
|
82
|
+
<use href="#g2591" transform="translate(59664,7152) scale(1,-1) translate(0,-1901)"/>
|
|
83
|
+
<use href="#g2588" transform="translate(61020,7152) scale(1,-1) translate(0,-1901)"/>
|
|
84
|
+
<use href="#g2588" transform="translate(62376,7152) scale(1,-1) translate(0,-1901)"/>
|
|
85
|
+
<use href="#g2591" transform="translate(69156,7152) scale(1,-1) translate(0,-1901)"/>
|
|
86
|
+
<use href="#g2588" transform="translate(70512,7152) scale(1,-1) translate(0,-1901)"/>
|
|
87
|
+
<use href="#g2588" transform="translate(71868,7152) scale(1,-1) translate(0,-1901)"/>
|
|
88
|
+
<use href="#g2591" transform="translate(0,9536) scale(1,-1) translate(0,-1901)"/>
|
|
89
|
+
<use href="#g2588" transform="translate(1356,9536) scale(1,-1) translate(0,-1901)"/>
|
|
90
|
+
<use href="#g2588" transform="translate(2712,9536) scale(1,-1) translate(0,-1901)"/>
|
|
91
|
+
<use href="#g2591" transform="translate(6780,9536) scale(1,-1) translate(0,-1901)"/>
|
|
92
|
+
<use href="#g2588" transform="translate(8136,9536) scale(1,-1) translate(0,-1901)"/>
|
|
93
|
+
<use href="#g2588" transform="translate(9492,9536) scale(1,-1) translate(0,-1901)"/>
|
|
94
|
+
<use href="#g2588" transform="translate(10848,9536) scale(1,-1) translate(0,-1901)"/>
|
|
95
|
+
<use href="#g2588" transform="translate(12204,9536) scale(1,-1) translate(0,-1901)"/>
|
|
96
|
+
<use href="#g2588" transform="translate(13560,9536) scale(1,-1) translate(0,-1901)"/>
|
|
97
|
+
<use href="#g2588" transform="translate(14916,9536) scale(1,-1) translate(0,-1901)"/>
|
|
98
|
+
<use href="#g2588" transform="translate(16272,9536) scale(1,-1) translate(0,-1901)"/>
|
|
99
|
+
<use href="#g2591" transform="translate(21696,9536) scale(1,-1) translate(0,-1901)"/>
|
|
100
|
+
<use href="#g2588" transform="translate(23052,9536) scale(1,-1) translate(0,-1901)"/>
|
|
101
|
+
<use href="#g2588" transform="translate(24408,9536) scale(1,-1) translate(0,-1901)"/>
|
|
102
|
+
<use href="#g2591" transform="translate(37968,9536) scale(1,-1) translate(0,-1901)"/>
|
|
103
|
+
<use href="#g2588" transform="translate(39324,9536) scale(1,-1) translate(0,-1901)"/>
|
|
104
|
+
<use href="#g2588" transform="translate(40680,9536) scale(1,-1) translate(0,-1901)"/>
|
|
105
|
+
<use href="#g2591" transform="translate(46104,9536) scale(1,-1) translate(0,-1901)"/>
|
|
106
|
+
<use href="#g2588" transform="translate(47460,9536) scale(1,-1) translate(0,-1901)"/>
|
|
107
|
+
<use href="#g2588" transform="translate(48816,9536) scale(1,-1) translate(0,-1901)"/>
|
|
108
|
+
<use href="#g2591" transform="translate(54240,9536) scale(1,-1) translate(0,-1901)"/>
|
|
109
|
+
<use href="#g2588" transform="translate(55596,9536) scale(1,-1) translate(0,-1901)"/>
|
|
110
|
+
<use href="#g2588" transform="translate(56952,9536) scale(1,-1) translate(0,-1901)"/>
|
|
111
|
+
<use href="#g2591" transform="translate(59664,9536) scale(1,-1) translate(0,-1901)"/>
|
|
112
|
+
<use href="#g2588" transform="translate(61020,9536) scale(1,-1) translate(0,-1901)"/>
|
|
113
|
+
<use href="#g2588" transform="translate(62376,9536) scale(1,-1) translate(0,-1901)"/>
|
|
114
|
+
<use href="#g2591" transform="translate(69156,9536) scale(1,-1) translate(0,-1901)"/>
|
|
115
|
+
<use href="#g2588" transform="translate(70512,9536) scale(1,-1) translate(0,-1901)"/>
|
|
116
|
+
<use href="#g2588" transform="translate(71868,9536) scale(1,-1) translate(0,-1901)"/>
|
|
117
|
+
<use href="#g2591" transform="translate(0,11920) scale(1,-1) translate(0,-1901)"/>
|
|
118
|
+
<use href="#g2588" transform="translate(1356,11920) scale(1,-1) translate(0,-1901)"/>
|
|
119
|
+
<use href="#g2588" transform="translate(2712,11920) scale(1,-1) translate(0,-1901)"/>
|
|
120
|
+
<use href="#g2591" transform="translate(5424,11920) scale(1,-1) translate(0,-1901)"/>
|
|
121
|
+
<use href="#g2588" transform="translate(6780,11920) scale(1,-1) translate(0,-1901)"/>
|
|
122
|
+
<use href="#g2588" transform="translate(8136,11920) scale(1,-1) translate(0,-1901)"/>
|
|
123
|
+
<use href="#g2591" transform="translate(13560,11920) scale(1,-1) translate(0,-1901)"/>
|
|
124
|
+
<use href="#g2588" transform="translate(14916,11920) scale(1,-1) translate(0,-1901)"/>
|
|
125
|
+
<use href="#g2588" transform="translate(16272,11920) scale(1,-1) translate(0,-1901)"/>
|
|
126
|
+
<use href="#g2591" transform="translate(21696,11920) scale(1,-1) translate(0,-1901)"/>
|
|
127
|
+
<use href="#g2588" transform="translate(23052,11920) scale(1,-1) translate(0,-1901)"/>
|
|
128
|
+
<use href="#g2588" transform="translate(24408,11920) scale(1,-1) translate(0,-1901)"/>
|
|
129
|
+
<use href="#g2591" transform="translate(37968,11920) scale(1,-1) translate(0,-1901)"/>
|
|
130
|
+
<use href="#g2588" transform="translate(39324,11920) scale(1,-1) translate(0,-1901)"/>
|
|
131
|
+
<use href="#g2588" transform="translate(40680,11920) scale(1,-1) translate(0,-1901)"/>
|
|
132
|
+
<use href="#g2591" transform="translate(46104,11920) scale(1,-1) translate(0,-1901)"/>
|
|
133
|
+
<use href="#g2588" transform="translate(47460,11920) scale(1,-1) translate(0,-1901)"/>
|
|
134
|
+
<use href="#g2588" transform="translate(48816,11920) scale(1,-1) translate(0,-1901)"/>
|
|
135
|
+
<use href="#g2591" transform="translate(54240,11920) scale(1,-1) translate(0,-1901)"/>
|
|
136
|
+
<use href="#g2588" transform="translate(55596,11920) scale(1,-1) translate(0,-1901)"/>
|
|
137
|
+
<use href="#g2588" transform="translate(56952,11920) scale(1,-1) translate(0,-1901)"/>
|
|
138
|
+
<use href="#g2591" transform="translate(59664,11920) scale(1,-1) translate(0,-1901)"/>
|
|
139
|
+
<use href="#g2588" transform="translate(61020,11920) scale(1,-1) translate(0,-1901)"/>
|
|
140
|
+
<use href="#g2588" transform="translate(62376,11920) scale(1,-1) translate(0,-1901)"/>
|
|
141
|
+
<use href="#g2591" transform="translate(67800,11920) scale(1,-1) translate(0,-1901)"/>
|
|
142
|
+
<use href="#g2588" transform="translate(69156,11920) scale(1,-1) translate(0,-1901)"/>
|
|
143
|
+
<use href="#g2588" transform="translate(70512,11920) scale(1,-1) translate(0,-1901)"/>
|
|
144
|
+
<use href="#g2588" transform="translate(71868,11920) scale(1,-1) translate(0,-1901)"/>
|
|
145
|
+
<use href="#g2591" transform="translate(0,14304) scale(1,-1) translate(0,-1901)"/>
|
|
146
|
+
<use href="#g2588" transform="translate(1356,14304) scale(1,-1) translate(0,-1901)"/>
|
|
147
|
+
<use href="#g2588" transform="translate(2712,14304) scale(1,-1) translate(0,-1901)"/>
|
|
148
|
+
<use href="#g2591" transform="translate(6780,14304) scale(1,-1) translate(0,-1901)"/>
|
|
149
|
+
<use href="#g2588" transform="translate(8136,14304) scale(1,-1) translate(0,-1901)"/>
|
|
150
|
+
<use href="#g2588" transform="translate(9492,14304) scale(1,-1) translate(0,-1901)"/>
|
|
151
|
+
<use href="#g2588" transform="translate(10848,14304) scale(1,-1) translate(0,-1901)"/>
|
|
152
|
+
<use href="#g2588" transform="translate(12204,14304) scale(1,-1) translate(0,-1901)"/>
|
|
153
|
+
<use href="#g2588" transform="translate(13560,14304) scale(1,-1) translate(0,-1901)"/>
|
|
154
|
+
<use href="#g2591" transform="translate(14916,14304) scale(1,-1) translate(0,-1901)"/>
|
|
155
|
+
<use href="#g2588" transform="translate(16272,14304) scale(1,-1) translate(0,-1901)"/>
|
|
156
|
+
<use href="#g2588" transform="translate(17628,14304) scale(1,-1) translate(0,-1901)"/>
|
|
157
|
+
<use href="#g2591" transform="translate(23052,14304) scale(1,-1) translate(0,-1901)"/>
|
|
158
|
+
<use href="#g2588" transform="translate(24408,14304) scale(1,-1) translate(0,-1901)"/>
|
|
159
|
+
<use href="#g2588" transform="translate(25764,14304) scale(1,-1) translate(0,-1901)"/>
|
|
160
|
+
<use href="#g2588" transform="translate(27120,14304) scale(1,-1) translate(0,-1901)"/>
|
|
161
|
+
<use href="#g2591" transform="translate(31188,14304) scale(1,-1) translate(0,-1901)"/>
|
|
162
|
+
<use href="#g2588" transform="translate(32544,14304) scale(1,-1) translate(0,-1901)"/>
|
|
163
|
+
<use href="#g2588" transform="translate(33900,14304) scale(1,-1) translate(0,-1901)"/>
|
|
164
|
+
<use href="#g2591" transform="translate(37968,14304) scale(1,-1) translate(0,-1901)"/>
|
|
165
|
+
<use href="#g2588" transform="translate(39324,14304) scale(1,-1) translate(0,-1901)"/>
|
|
166
|
+
<use href="#g2588" transform="translate(40680,14304) scale(1,-1) translate(0,-1901)"/>
|
|
167
|
+
<use href="#g2591" transform="translate(46104,14304) scale(1,-1) translate(0,-1901)"/>
|
|
168
|
+
<use href="#g2588" transform="translate(47460,14304) scale(1,-1) translate(0,-1901)"/>
|
|
169
|
+
<use href="#g2588" transform="translate(48816,14304) scale(1,-1) translate(0,-1901)"/>
|
|
170
|
+
<use href="#g2591" transform="translate(54240,14304) scale(1,-1) translate(0,-1901)"/>
|
|
171
|
+
<use href="#g2588" transform="translate(55596,14304) scale(1,-1) translate(0,-1901)"/>
|
|
172
|
+
<use href="#g2588" transform="translate(56952,14304) scale(1,-1) translate(0,-1901)"/>
|
|
173
|
+
<use href="#g2591" transform="translate(61020,14304) scale(1,-1) translate(0,-1901)"/>
|
|
174
|
+
<use href="#g2588" transform="translate(62376,14304) scale(1,-1) translate(0,-1901)"/>
|
|
175
|
+
<use href="#g2588" transform="translate(63732,14304) scale(1,-1) translate(0,-1901)"/>
|
|
176
|
+
<use href="#g2588" transform="translate(65088,14304) scale(1,-1) translate(0,-1901)"/>
|
|
177
|
+
<use href="#g2588" transform="translate(66444,14304) scale(1,-1) translate(0,-1901)"/>
|
|
178
|
+
<use href="#g2588" transform="translate(67800,14304) scale(1,-1) translate(0,-1901)"/>
|
|
179
|
+
<use href="#g2591" transform="translate(69156,14304) scale(1,-1) translate(0,-1901)"/>
|
|
180
|
+
<use href="#g2588" transform="translate(70512,14304) scale(1,-1) translate(0,-1901)"/>
|
|
181
|
+
<use href="#g2588" transform="translate(71868,14304) scale(1,-1) translate(0,-1901)"/>
|
|
182
|
+
</svg>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
3
|
-
import { keyHint } from "@mariozechner/pi-coding-agent";
|
|
3
|
+
import { getMarkdownTheme, keyHint } from "@mariozechner/pi-coding-agent";
|
|
4
4
|
import type { Theme } from "@mariozechner/pi-tui";
|
|
5
|
-
import { Box, Text } from "@mariozechner/pi-tui";
|
|
5
|
+
import { Box, Markdown, Text } from "@mariozechner/pi-tui";
|
|
6
6
|
|
|
7
7
|
const PREVIEW_LINES = 4;
|
|
8
8
|
|
|
@@ -14,10 +14,11 @@ function collapsibleResult(
|
|
|
14
14
|
const text = result.content?.[0]?.type === "text" ? (result.content[0] as { type: "text"; text: string }).text : "";
|
|
15
15
|
if (!text) return new Text(theme.fg("dim", "(empty)"), 0, 0);
|
|
16
16
|
if (options.isPartial) return new Text(theme.fg("dim", "…"), 0, 0);
|
|
17
|
-
|
|
17
|
+
const mdTheme = getMarkdownTheme();
|
|
18
|
+
if (options.expanded) return new Markdown(text, 0, 0, mdTheme);
|
|
18
19
|
|
|
19
20
|
const lines = text.split("\n");
|
|
20
|
-
if (lines.length <= PREVIEW_LINES) return new
|
|
21
|
+
if (lines.length <= PREVIEW_LINES) return new Markdown(text, 0, 0, mdTheme);
|
|
21
22
|
|
|
22
23
|
const preview = lines.slice(0, PREVIEW_LINES).join("\n");
|
|
23
24
|
const remaining = lines.length - PREVIEW_LINES;
|
|
@@ -223,7 +224,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
223
224
|
pi.registerMessageRenderer("lat-reminder", (message, { expanded }, theme) => {
|
|
224
225
|
const box = new Box(1, 1, (t) => theme.bg("customMessageBg", t));
|
|
225
226
|
if (expanded) {
|
|
226
|
-
box.addChild(new Text(theme.fg("accent", "lat.md")
|
|
227
|
+
box.addChild(new Text(theme.fg("accent", "lat.md"), 0, 0));
|
|
228
|
+
box.addChild(new Markdown(message.content, 0, 0, getMarkdownTheme()));
|
|
227
229
|
} else {
|
|
228
230
|
const hint = keyHint("expandTools", "to expand");
|
|
229
231
|
box.addChild(new Text(
|
|
@@ -238,7 +240,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
238
240
|
pi.registerMessageRenderer("lat-check", (message, { expanded }, theme) => {
|
|
239
241
|
const box = new Box(1, 1, (t) => theme.bg("customMessageBg", t));
|
|
240
242
|
if (expanded) {
|
|
241
|
-
box.addChild(new Text(theme.fg("warning", "lat check")
|
|
243
|
+
box.addChild(new Text(theme.fg("warning", "lat check"), 0, 0));
|
|
244
|
+
box.addChild(new Markdown(message.content, 0, 0, getMarkdownTheme()));
|
|
242
245
|
} else {
|
|
243
246
|
const hint = keyHint("expandTools", "to expand");
|
|
244
247
|
const firstLine = message.content.split("\n")[0];
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# lat.md prompt hook for Claude Code
|
|
3
|
-
# Injected by `lat init` — reminds the agent to consult lat.md before working.
|
|
4
|
-
#
|
|
5
|
-
# This hook fires on every user prompt submission. It adds a short instruction
|
|
6
|
-
# telling the agent to run `lat search` and `lat expand` so that lat.md context
|
|
7
|
-
# is consulted before any code changes.
|
|
8
|
-
|
|
9
|
-
cat <<'HOOK_JSON'
|
|
10
|
-
{
|
|
11
|
-
"hookSpecificOutput": {
|
|
12
|
-
"hookEventName": "UserPromptSubmit",
|
|
13
|
-
"additionalContext": "Before starting work on this task:\n1. Run `lat search` with a query relevant to the task and read the results to understand the design intent.\n2. If the prompt contains [[refs]], run `lat expand` on the full prompt text to resolve them.\n3. After completing work, run `lat check` to validate all links and code refs.\nDo not skip these steps."
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
HOOK_JSON
|