lat.md 0.7.0 → 0.7.2
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 +3 -2
- package/dist/src/cli/check.js +3 -14
- package/dist/src/cli/hook.js +86 -6
- package/dist/src/cli/refs.js +1 -1
- package/dist/src/cli/search.js +2 -8
- package/dist/src/cli/section.js +17 -8
- package/dist/src/format.d.ts +1 -0
- package/dist/src/format.js +10 -1
- package/dist/src/lattice.d.ts +0 -1
- package/dist/src/lattice.js +2 -5
- package/dist/src/parser.js +2 -0
- package/dist/src/source-parser.d.ts +2 -0
- package/dist/src/source-parser.js +15 -11
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ After installing, run `lat init` in the repo you want to use lat in.
|
|
|
33
33
|
|
|
34
34
|
## How it works
|
|
35
35
|
|
|
36
|
-
Run `lat init` to scaffold a `lat.md/` directory, then write markdown files describing your architecture, business logic, test specs — whatever matters. Link between sections using `[[file#Section#Subsection]]` syntax. Annotate source code with `// @lat: [[section-id]]` (or `# @lat: [[section-id]]` in Python) comments to tie implementation back to concepts.
|
|
36
|
+
Run `lat init` to scaffold a `lat.md/` directory, then write markdown files describing your architecture, business logic, test specs — whatever matters. Link between sections using `[[file#Section#Subsection]]` syntax. Link to source code symbols with `[[src/auth.ts#validateToken]]`. Annotate source code with `// @lat: [[section-id]]` (or `# @lat: [[section-id]]` in Python) comments to tie implementation back to concepts.
|
|
37
37
|
|
|
38
38
|
```
|
|
39
39
|
my-project/
|
|
@@ -53,9 +53,10 @@ my-project/
|
|
|
53
53
|
npx lat.md init # scaffold a lat.md/ directory
|
|
54
54
|
npx lat.md check # validate all wiki links and code refs
|
|
55
55
|
npx lat.md locate "OAuth Flow" # find sections by name (exact, fuzzy)
|
|
56
|
+
npx lat.md section "auth#OAuth Flow" # show a section with its links and refs
|
|
56
57
|
npx lat.md refs "auth#OAuth Flow" # find what references a section
|
|
57
58
|
npx lat.md search "how do we auth?" # semantic search via embeddings
|
|
58
|
-
npx lat.md
|
|
59
|
+
npx lat.md expand "fix [[OAuth Flow]]" # expand [[refs]] in a prompt for agents
|
|
59
60
|
```
|
|
60
61
|
|
|
61
62
|
## Configuration
|
package/dist/src/cli/check.js
CHANGED
|
@@ -3,6 +3,7 @@ import { existsSync } from 'node:fs';
|
|
|
3
3
|
import { basename, dirname, extname, join, relative } from 'node:path';
|
|
4
4
|
import { listLatticeFiles, loadAllSections, extractRefs, flattenSections, parseFrontmatter, parseSections, buildFileIndex, resolveRef, } from '../lattice.js';
|
|
5
5
|
import { scanCodeRefs } from '../code-refs.js';
|
|
6
|
+
import { SOURCE_EXTENSIONS } from '../source-parser.js';
|
|
6
7
|
import { walkEntries } from '../walk.js';
|
|
7
8
|
import { INIT_VERSION, readInitVersion } from '../init-version.js';
|
|
8
9
|
function filePart(id) {
|
|
@@ -32,23 +33,11 @@ function countByExt(paths) {
|
|
|
32
33
|
}
|
|
33
34
|
return stats;
|
|
34
35
|
}
|
|
35
|
-
/** Source file extensions recognized for code wiki links. */
|
|
36
|
-
const SOURCE_EXTS = new Set([
|
|
37
|
-
'.ts',
|
|
38
|
-
'.tsx',
|
|
39
|
-
'.js',
|
|
40
|
-
'.jsx',
|
|
41
|
-
'.py',
|
|
42
|
-
'.rs',
|
|
43
|
-
'.go',
|
|
44
|
-
'.c',
|
|
45
|
-
'.h',
|
|
46
|
-
]);
|
|
47
36
|
function isSourcePath(target) {
|
|
48
37
|
const hashIdx = target.indexOf('#');
|
|
49
38
|
const filePart = hashIdx === -1 ? target : target.slice(0, hashIdx);
|
|
50
39
|
const ext = extname(filePart);
|
|
51
|
-
return
|
|
40
|
+
return SOURCE_EXTENSIONS.has(ext);
|
|
52
41
|
}
|
|
53
42
|
/**
|
|
54
43
|
* Try resolving a wiki link target as a source code reference.
|
|
@@ -61,7 +50,7 @@ async function tryResolveSourceRef(target, projectRoot) {
|
|
|
61
50
|
const filePart = hashIdx === -1 ? target : target.slice(0, hashIdx);
|
|
62
51
|
const ext = extname(filePart);
|
|
63
52
|
if (ext && hashIdx !== -1) {
|
|
64
|
-
const supported = [...
|
|
53
|
+
const supported = [...SOURCE_EXTENSIONS].sort().join(', ');
|
|
65
54
|
return `broken link [[${target}]] — unsupported file extension "${ext}". Supported: ${supported}`;
|
|
66
55
|
}
|
|
67
56
|
return `broken link [[${target}]] — no matching section found`;
|
package/dist/src/cli/hook.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { dirname, extname } from 'node:path';
|
|
2
3
|
import { findLatticeDir } from '../lattice.js';
|
|
3
4
|
import { plainStyler } from '../context.js';
|
|
4
5
|
import { expandPrompt } from './expand.js';
|
|
5
6
|
import { runSearch } from './search.js';
|
|
6
7
|
import { getSection, formatSectionOutput } from './section.js';
|
|
7
8
|
import { getLlmKey } from '../config.js';
|
|
9
|
+
import { checkMd, checkCodeRefs, checkIndex, checkSections } from './check.js';
|
|
10
|
+
import { SOURCE_EXTENSIONS } from '../source-parser.js';
|
|
8
11
|
function outputPromptSubmit(context) {
|
|
9
12
|
process.stdout.write(JSON.stringify({
|
|
10
13
|
hookSpecificOutput: {
|
|
@@ -74,7 +77,7 @@ async function handleUserPromptSubmit() {
|
|
|
74
77
|
// If we can't parse stdin, still emit the reminder
|
|
75
78
|
}
|
|
76
79
|
const parts = [];
|
|
77
|
-
parts.push("Before starting work, run `lat search` with one or more queries describing the user's intent.", 'ALWAYS do this, even when the task seems straightforward — search results may reveal critical design details, protocols, or constraints.', 'Use `lat section` to read the full content of relevant matches.', 'Do not read files, write code, or run commands until you have searched.');
|
|
80
|
+
parts.push("Before starting work, run `lat search` with one or more queries describing the user's intent.", 'ALWAYS do this, even when the task seems straightforward — search results may reveal critical design details, protocols, or constraints.', 'Use `lat section` to read the full content of relevant matches.', 'Do not read files, write code, or run commands until you have searched.', '', 'Remember: `lat.md/` must stay in sync with the codebase. If you change code, update the relevant sections in `lat.md/` and run `lat check` before finishing.');
|
|
78
81
|
const latDir = findLatticeDir();
|
|
79
82
|
if (latDir && userPrompt) {
|
|
80
83
|
const ctx = makeHookCtx(latDir);
|
|
@@ -106,8 +109,44 @@ async function handleUserPromptSubmit() {
|
|
|
106
109
|
}
|
|
107
110
|
outputPromptSubmit(parts.join('\n'));
|
|
108
111
|
}
|
|
112
|
+
/** Minimum diff size (in lines) to consider "significant" code change. */
|
|
113
|
+
/** Minimum code change size (lines) before we consider flagging lat.md/ sync. */
|
|
114
|
+
const DIFF_THRESHOLD = 5;
|
|
115
|
+
/** lat.md/ changes below this ratio of code changes trigger a sync reminder. */
|
|
116
|
+
const LATMD_RATIO = 0.05;
|
|
117
|
+
/** Run `git diff --numstat` and return { codeLines, latMdLines }. */
|
|
118
|
+
function analyzeDiff(projectRoot) {
|
|
119
|
+
let output;
|
|
120
|
+
try {
|
|
121
|
+
output = execSync('git diff HEAD --numstat', {
|
|
122
|
+
cwd: projectRoot,
|
|
123
|
+
encoding: 'utf-8',
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return { codeLines: 0, latMdLines: 0 };
|
|
128
|
+
}
|
|
129
|
+
let codeLines = 0;
|
|
130
|
+
let latMdLines = 0;
|
|
131
|
+
// Each line: "added\tremoved\tfile" (e.g. "42\t11\tsrc/cli/hook.ts")
|
|
132
|
+
for (const line of output.split('\n')) {
|
|
133
|
+
const parts = line.split('\t');
|
|
134
|
+
if (parts.length < 3)
|
|
135
|
+
continue;
|
|
136
|
+
const added = parseInt(parts[0], 10) || 0;
|
|
137
|
+
const removed = parseInt(parts[1], 10) || 0;
|
|
138
|
+
const file = parts[2];
|
|
139
|
+
const changed = added + removed;
|
|
140
|
+
if (file.startsWith('lat.md/')) {
|
|
141
|
+
latMdLines += changed;
|
|
142
|
+
}
|
|
143
|
+
else if (SOURCE_EXTENSIONS.has(extname(file))) {
|
|
144
|
+
codeLines += changed;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return { codeLines, latMdLines };
|
|
148
|
+
}
|
|
109
149
|
async function handleStop() {
|
|
110
|
-
// Only emit the reminder if we're in a project with lat.md
|
|
111
150
|
const latDir = findLatticeDir();
|
|
112
151
|
if (!latDir)
|
|
113
152
|
return;
|
|
@@ -121,11 +160,52 @@ async function handleStop() {
|
|
|
121
160
|
catch {
|
|
122
161
|
// If we can't parse stdin, treat as first attempt
|
|
123
162
|
}
|
|
124
|
-
//
|
|
125
|
-
|
|
163
|
+
// Always run lat check — even on second pass
|
|
164
|
+
const md = await checkMd(latDir);
|
|
165
|
+
const code = await checkCodeRefs(latDir);
|
|
166
|
+
const indexErrors = await checkIndex(latDir);
|
|
167
|
+
const sectionErrors = await checkSections(latDir);
|
|
168
|
+
const totalErrors = md.errors.length +
|
|
169
|
+
code.errors.length +
|
|
170
|
+
indexErrors.length +
|
|
171
|
+
sectionErrors.length;
|
|
172
|
+
const checkFailed = totalErrors > 0;
|
|
173
|
+
// Second pass — warn the user but don't block again
|
|
174
|
+
if (stopHookActive) {
|
|
175
|
+
if (checkFailed) {
|
|
176
|
+
console.error(`lat check is still failing (${totalErrors} error(s)). Run \`lat check\` to see details.`);
|
|
177
|
+
}
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const projectRoot = dirname(latDir);
|
|
181
|
+
// Analyze git diff — flag when lat.md/ changes are < 5% of code changes
|
|
182
|
+
const { codeLines, latMdLines } = analyzeDiff(projectRoot);
|
|
183
|
+
let needsSync = false;
|
|
184
|
+
if (codeLines >= DIFF_THRESHOLD) {
|
|
185
|
+
// Round up lat.md lines to 1 if there are more than 5 code lines changed
|
|
186
|
+
// (a tiny lat.md touch still counts as effort)
|
|
187
|
+
const effectiveLatMd = latMdLines === 0 ? 0 : Math.max(latMdLines, 1);
|
|
188
|
+
needsSync = effectiveLatMd < codeLines * LATMD_RATIO;
|
|
189
|
+
}
|
|
190
|
+
// Nothing to do — let the agent stop cleanly
|
|
191
|
+
if (!checkFailed && !needsSync)
|
|
126
192
|
return;
|
|
127
193
|
const parts = [];
|
|
128
|
-
|
|
194
|
+
if (checkFailed && needsSync) {
|
|
195
|
+
parts.push('`lat check` found errors AND the codebase has changes (' +
|
|
196
|
+
codeLines +
|
|
197
|
+
' lines) with no updates to `lat.md/`. Before finishing:', '', '1. Update `lat.md/` to reflect your code changes — run `lat search` to find relevant sections.', '2. Run `lat check` until it passes.');
|
|
198
|
+
}
|
|
199
|
+
else if (checkFailed) {
|
|
200
|
+
parts.push('`lat check` found ' +
|
|
201
|
+
totalErrors +
|
|
202
|
+
' error(s). Run `lat check`, fix the errors, and repeat until it passes.');
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
parts.push('The codebase has changes (' +
|
|
206
|
+
codeLines +
|
|
207
|
+
' lines) but `lat.md/` was not updated. Update `lat.md/` to be in sync with the changes — run `lat search` to find relevant sections. Run `lat check` at the end.');
|
|
208
|
+
}
|
|
129
209
|
outputStop(parts.join('\n'));
|
|
130
210
|
}
|
|
131
211
|
export async function hookCmd(agent, event) {
|
package/dist/src/cli/refs.js
CHANGED
|
@@ -97,7 +97,7 @@ export async function refsCommand(ctx, query, scope) {
|
|
|
97
97
|
parts.push(formatResultList(ctx, `References to "${target.id}":`, mdRefs));
|
|
98
98
|
}
|
|
99
99
|
if (codeRefs.length > 0) {
|
|
100
|
-
parts.push(
|
|
100
|
+
parts.push('## Code references:' +
|
|
101
101
|
'\n\n' +
|
|
102
102
|
codeRefs.map((l) => `${s.dim('*')} ${l}`).join('\n'));
|
|
103
103
|
}
|
package/dist/src/cli/search.js
CHANGED
|
@@ -3,7 +3,7 @@ import { detectProvider } from '../search/provider.js';
|
|
|
3
3
|
import { indexSections } from '../search/index.js';
|
|
4
4
|
import { searchSections } from '../search/search.js';
|
|
5
5
|
import { loadAllSections, flattenSections, } from '../lattice.js';
|
|
6
|
-
import { formatResultList } from '../format.js';
|
|
6
|
+
import { formatResultList, formatNavHints } from '../format.js';
|
|
7
7
|
async function withDb(latDir, key, progress, fn) {
|
|
8
8
|
const provider = detectProvider(key);
|
|
9
9
|
const db = openDb(latDir);
|
|
@@ -94,14 +94,8 @@ export async function searchCommand(ctx, query, opts, progress) {
|
|
|
94
94
|
if (result.matches.length === 0) {
|
|
95
95
|
return { output: 'No results found.' };
|
|
96
96
|
}
|
|
97
|
-
const navHints = ctx.mode === 'cli'
|
|
98
|
-
? '- `lat section "section#id"` \u2014 show full content with outgoing/incoming refs\n' +
|
|
99
|
-
'- `lat search "new query"` \u2014 search for something else'
|
|
100
|
-
: '- `lat_section` \u2014 show full content with outgoing/incoming refs\n' +
|
|
101
|
-
'- `lat_search` \u2014 search for something else';
|
|
102
97
|
return {
|
|
103
98
|
output: formatResultList(ctx, `Search results for "${query}":`, result.matches) +
|
|
104
|
-
|
|
105
|
-
navHints,
|
|
99
|
+
formatNavHints(ctx),
|
|
106
100
|
};
|
|
107
101
|
}
|
package/dist/src/cli/section.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { join, relative } from 'node:path';
|
|
3
3
|
import { loadAllSections, findSections, flattenSections, extractRefs, buildFileIndex, resolveRef, listLatticeFiles, } from '../lattice.js';
|
|
4
|
-
import { formatSectionId } from '../format.js';
|
|
4
|
+
import { formatSectionId, formatNavHints } from '../format.js';
|
|
5
5
|
/**
|
|
6
6
|
* Look up a section by id, return its content, outgoing wiki link targets,
|
|
7
7
|
* and incoming references from other sections.
|
|
@@ -22,13 +22,12 @@ export async function getSection(ctx, query) {
|
|
|
22
22
|
return { kind: 'no-match', suggestions: matches };
|
|
23
23
|
}
|
|
24
24
|
const section = top.section;
|
|
25
|
-
// Read raw content between startLine and
|
|
25
|
+
// Read raw content between startLine and the end of the last descendant
|
|
26
26
|
const absPath = join(ctx.projectRoot, section.filePath);
|
|
27
27
|
const fileContent = await readFile(absPath, 'utf-8');
|
|
28
28
|
const lines = fileContent.split('\n');
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
.join('\n');
|
|
29
|
+
const end = fullEndLine(section);
|
|
30
|
+
const content = lines.slice(section.startLine - 1, end).join('\n');
|
|
32
31
|
// Find outgoing wiki link targets within this section's content
|
|
33
32
|
const flat = flattenSections(allSections);
|
|
34
33
|
const sectionIds = new Set(flat.map((s) => s.id.toLowerCase()));
|
|
@@ -73,6 +72,11 @@ export async function getSection(ctx, query) {
|
|
|
73
72
|
}
|
|
74
73
|
return { kind: 'found', section, content, outgoingRefs, incomingRefs };
|
|
75
74
|
}
|
|
75
|
+
function fullEndLine(section) {
|
|
76
|
+
if (section.children.length === 0)
|
|
77
|
+
return section.endLine;
|
|
78
|
+
return fullEndLine(section.children[section.children.length - 1]);
|
|
79
|
+
}
|
|
76
80
|
function truncate(s, max) {
|
|
77
81
|
return s.length > max ? s.slice(0, max) + '...' : s;
|
|
78
82
|
}
|
|
@@ -84,13 +88,17 @@ export function formatSectionOutput(ctx, result) {
|
|
|
84
88
|
const { section, content, outgoingRefs, incomingRefs } = result;
|
|
85
89
|
const relPath = relative(process.cwd(), join(ctx.projectRoot, section.filePath));
|
|
86
90
|
const loc = `${s.cyan(relPath)}${s.dim(`:${section.startLine}-${section.endLine}`)}`;
|
|
91
|
+
const quoted = content
|
|
92
|
+
.split('\n')
|
|
93
|
+
.map((line) => (line ? `> ${line}` : '>'))
|
|
94
|
+
.join('\n');
|
|
87
95
|
const parts = [
|
|
88
96
|
`${s.bold('[[' + formatSectionId(section.id, s) + ']]')} (${loc})`,
|
|
89
97
|
'',
|
|
90
|
-
|
|
98
|
+
quoted,
|
|
91
99
|
];
|
|
92
100
|
if (outgoingRefs.length > 0) {
|
|
93
|
-
parts.push('',
|
|
101
|
+
parts.push('', '## This section references:', '');
|
|
94
102
|
for (const ref of outgoingRefs) {
|
|
95
103
|
const body = ref.resolved.firstParagraph
|
|
96
104
|
? ` ${s.dim('—')} ${truncate(ref.resolved.firstParagraph, 120)}`
|
|
@@ -99,7 +107,7 @@ export function formatSectionOutput(ctx, result) {
|
|
|
99
107
|
}
|
|
100
108
|
}
|
|
101
109
|
if (incomingRefs.length > 0) {
|
|
102
|
-
parts.push('',
|
|
110
|
+
parts.push('', '## Referenced by:', '');
|
|
103
111
|
for (const ref of incomingRefs) {
|
|
104
112
|
const body = ref.section.firstParagraph
|
|
105
113
|
? ` ${s.dim('—')} ${truncate(ref.section.firstParagraph, 120)}`
|
|
@@ -107,6 +115,7 @@ export function formatSectionOutput(ctx, result) {
|
|
|
107
115
|
parts.push(`${s.dim('*')} [[${formatSectionId(ref.section.id, s)}]]${body}`);
|
|
108
116
|
}
|
|
109
117
|
}
|
|
118
|
+
parts.push(formatNavHints(ctx));
|
|
110
119
|
return parts.join('\n');
|
|
111
120
|
}
|
|
112
121
|
export async function sectionCommand(ctx, query) {
|
package/dist/src/format.d.ts
CHANGED
|
@@ -5,3 +5,4 @@ export declare function formatSectionPreview(ctx: CmdContext, section: Section,
|
|
|
5
5
|
reason?: string;
|
|
6
6
|
}): string;
|
|
7
7
|
export declare function formatResultList(ctx: CmdContext, header: string, matches: SectionMatch[]): string;
|
|
8
|
+
export declare function formatNavHints(ctx: CmdContext): string;
|
package/dist/src/format.js
CHANGED
|
@@ -21,7 +21,7 @@ export function formatSectionPreview(ctx, section, opts) {
|
|
|
21
21
|
return lines.join('\n');
|
|
22
22
|
}
|
|
23
23
|
export function formatResultList(ctx, header, matches) {
|
|
24
|
-
const lines = ['',
|
|
24
|
+
const lines = ['', `## ${header}`, ''];
|
|
25
25
|
for (let i = 0; i < matches.length; i++) {
|
|
26
26
|
if (i > 0)
|
|
27
27
|
lines.push('');
|
|
@@ -32,3 +32,12 @@ export function formatResultList(ctx, header, matches) {
|
|
|
32
32
|
lines.push('');
|
|
33
33
|
return lines.join('\n');
|
|
34
34
|
}
|
|
35
|
+
export function formatNavHints(ctx) {
|
|
36
|
+
const s = ctx.styler;
|
|
37
|
+
const hints = ctx.mode === 'cli'
|
|
38
|
+
? `${s.dim('*')} \`lat section "section#id"\` \u2014 show full content with outgoing/incoming refs\n` +
|
|
39
|
+
`${s.dim('*')} \`lat search "new query"\` \u2014 search for something else`
|
|
40
|
+
: `${s.dim('*')} \`lat_section\` \u2014 show full content with outgoing/incoming refs\n` +
|
|
41
|
+
`${s.dim('*')} \`lat_search\` \u2014 search for something else`;
|
|
42
|
+
return `\n## To navigate further:\n\n${hints}`;
|
|
43
|
+
}
|
package/dist/src/lattice.d.ts
CHANGED
|
@@ -19,7 +19,6 @@ export type LatFrontmatter = {
|
|
|
19
19
|
requireCodeMention?: boolean;
|
|
20
20
|
};
|
|
21
21
|
export declare function parseFrontmatter(content: string): LatFrontmatter;
|
|
22
|
-
export declare function stripFrontmatter(content: string): string;
|
|
23
22
|
export declare function findLatticeDir(from?: string): string | null;
|
|
24
23
|
export declare function findProjectRoot(from?: string): string | null;
|
|
25
24
|
export declare function listLatticeFiles(latticeDir: string): Promise<string[]>;
|
package/dist/src/lattice.js
CHANGED
|
@@ -15,9 +15,6 @@ export function parseFrontmatter(content) {
|
|
|
15
15
|
}
|
|
16
16
|
return result;
|
|
17
17
|
}
|
|
18
|
-
export function stripFrontmatter(content) {
|
|
19
|
-
return content.replace(/^---\n[\s\S]*?\n---\n*/, '');
|
|
20
|
-
}
|
|
21
18
|
export function findLatticeDir(from) {
|
|
22
19
|
let dir = resolve(from ?? process.cwd());
|
|
23
20
|
while (true) {
|
|
@@ -69,7 +66,7 @@ function lastLine(content) {
|
|
|
69
66
|
return lines[lines.length - 1] === '' ? lines.length - 1 : lines.length;
|
|
70
67
|
}
|
|
71
68
|
export function parseSections(filePath, content, projectRoot) {
|
|
72
|
-
const tree = parse(
|
|
69
|
+
const tree = parse(content);
|
|
73
70
|
const file = projectRoot
|
|
74
71
|
? relative(projectRoot, filePath).replace(/\.md$/, '')
|
|
75
72
|
: basename(filePath, '.md');
|
|
@@ -522,7 +519,7 @@ export function findSections(sections, query) {
|
|
|
522
519
|
];
|
|
523
520
|
}
|
|
524
521
|
export function extractRefs(filePath, content, projectRoot) {
|
|
525
|
-
const tree = parse(
|
|
522
|
+
const tree = parse(content);
|
|
526
523
|
const file = projectRoot
|
|
527
524
|
? relative(projectRoot, filePath).replace(/\.md$/, '')
|
|
528
525
|
: basename(filePath, '.md');
|
package/dist/src/parser.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { unified } from 'unified';
|
|
2
2
|
import remarkParse from 'remark-parse';
|
|
3
3
|
import remarkStringify from 'remark-stringify';
|
|
4
|
+
import remarkFrontmatter from 'remark-frontmatter';
|
|
4
5
|
import { wikiLinkSyntax, wikiLinkFromMarkdown, wikiLinkToMarkdown, } from './extensions/wiki-link/index.js';
|
|
5
6
|
const processor = unified()
|
|
6
7
|
.use(remarkParse)
|
|
8
|
+
.use(remarkFrontmatter)
|
|
7
9
|
.use(remarkStringify)
|
|
8
10
|
.data('micromarkExtensions', [wikiLinkSyntax()])
|
|
9
11
|
.data('fromMarkdownExtensions', [wikiLinkFromMarkdown()])
|
|
@@ -7,6 +7,8 @@ export type SourceSymbol = {
|
|
|
7
7
|
endLine: number;
|
|
8
8
|
signature: string;
|
|
9
9
|
};
|
|
10
|
+
/** All source file extensions that lat can parse (derived from grammarMap). */
|
|
11
|
+
export declare const SOURCE_EXTENSIONS: ReadonlySet<string>;
|
|
10
12
|
export declare function parseSourceSymbols(filePath: string, content: string): Promise<SourceSymbol[]>;
|
|
11
13
|
/**
|
|
12
14
|
* Check whether a source file path (relative to projectRoot) has a given symbol.
|
|
@@ -21,18 +21,22 @@ async function ensureParser() {
|
|
|
21
21
|
}
|
|
22
22
|
return parserInstance;
|
|
23
23
|
}
|
|
24
|
+
/** Extension → tree-sitter WASM grammar mapping. This is the single source of
|
|
25
|
+
* truth for which source file extensions lat supports. */
|
|
26
|
+
const grammarMap = {
|
|
27
|
+
'.ts': 'tree-sitter-typescript.wasm',
|
|
28
|
+
'.tsx': 'tree-sitter-tsx.wasm',
|
|
29
|
+
'.js': 'tree-sitter-javascript.wasm',
|
|
30
|
+
'.jsx': 'tree-sitter-javascript.wasm',
|
|
31
|
+
'.py': 'tree-sitter-python.wasm',
|
|
32
|
+
'.rs': 'tree-sitter-rust.wasm',
|
|
33
|
+
'.go': 'tree-sitter-go.wasm',
|
|
34
|
+
'.c': 'tree-sitter-c.wasm',
|
|
35
|
+
'.h': 'tree-sitter-c.wasm',
|
|
36
|
+
};
|
|
37
|
+
/** All source file extensions that lat can parse (derived from grammarMap). */
|
|
38
|
+
export const SOURCE_EXTENSIONS = new Set(Object.keys(grammarMap));
|
|
24
39
|
async function getLanguage(ext) {
|
|
25
|
-
const grammarMap = {
|
|
26
|
-
'.ts': 'tree-sitter-typescript.wasm',
|
|
27
|
-
'.tsx': 'tree-sitter-tsx.wasm',
|
|
28
|
-
'.js': 'tree-sitter-javascript.wasm',
|
|
29
|
-
'.jsx': 'tree-sitter-javascript.wasm',
|
|
30
|
-
'.py': 'tree-sitter-python.wasm',
|
|
31
|
-
'.rs': 'tree-sitter-rust.wasm',
|
|
32
|
-
'.go': 'tree-sitter-go.wasm',
|
|
33
|
-
'.c': 'tree-sitter-c.wasm',
|
|
34
|
-
'.h': 'tree-sitter-c.wasm',
|
|
35
|
-
};
|
|
36
40
|
const wasmFile = grammarMap[ext];
|
|
37
41
|
if (!wasmFile)
|
|
38
42
|
return null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lat.md",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"description": "A knowledge graph for your codebase, written in markdown",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@10.30.2",
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
"commander": "^14.0.3",
|
|
56
56
|
"ignore-walk": "^8.0.0",
|
|
57
57
|
"mdast-util-to-markdown": "^2.1.0",
|
|
58
|
+
"remark-frontmatter": "^5.0.0",
|
|
58
59
|
"remark-parse": "^11.0.0",
|
|
59
60
|
"remark-stringify": "^11.0.0",
|
|
60
61
|
"unified": "^11.0.0",
|