lat.md 0.1.0 → 0.1.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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +58 -8
  3. package/dist/src/cli/check.d.ts +18 -0
  4. package/dist/src/cli/check.js +122 -0
  5. package/dist/src/cli/context.d.ts +10 -0
  6. package/dist/src/cli/context.js +14 -0
  7. package/dist/src/cli/gen.d.ts +2 -0
  8. package/dist/src/cli/gen.js +14 -0
  9. package/dist/src/cli/index.js +126 -17
  10. package/dist/src/cli/init.d.ts +1 -0
  11. package/dist/src/cli/init.js +67 -0
  12. package/dist/src/cli/locate.d.ts +2 -1
  13. package/dist/src/cli/locate.js +8 -20
  14. package/dist/src/cli/prompt.d.ts +2 -0
  15. package/dist/src/cli/prompt.js +62 -0
  16. package/dist/src/cli/refs.d.ts +4 -1
  17. package/dist/src/cli/refs.js +36 -109
  18. package/dist/src/cli/search.d.ts +5 -0
  19. package/dist/src/cli/search.js +55 -0
  20. package/dist/src/cli/templates.d.ts +1 -0
  21. package/dist/src/cli/templates.js +15 -0
  22. package/dist/src/code-refs.d.ts +13 -0
  23. package/dist/src/code-refs.js +63 -0
  24. package/dist/src/format.d.ts +7 -1
  25. package/dist/src/format.js +26 -4
  26. package/dist/src/lattice.d.ts +6 -0
  27. package/dist/src/lattice.js +98 -11
  28. package/dist/src/search/db.d.ts +4 -0
  29. package/dist/src/search/db.js +31 -0
  30. package/dist/src/search/embeddings.d.ts +2 -0
  31. package/dist/src/search/embeddings.js +25 -0
  32. package/dist/src/search/index.d.ts +9 -0
  33. package/dist/src/search/index.js +66 -0
  34. package/dist/src/search/provider.d.ts +8 -0
  35. package/dist/src/search/provider.js +40 -0
  36. package/dist/src/search/search.d.ts +9 -0
  37. package/dist/src/search/search.js +17 -0
  38. package/package.json +28 -4
  39. package/templates/AGENTS.md +56 -0
  40. package/templates/README +1 -0
  41. package/templates/init/README.md +5 -0
@@ -0,0 +1,66 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { loadAllSections, flattenSections } from '../lattice.js';
5
+ import { embed } from './embeddings.js';
6
+ function hashContent(text) {
7
+ return createHash('sha256').update(text).digest('hex');
8
+ }
9
+ async function sectionContent(section, latDir) {
10
+ const filePath = join(latDir, section.file + '.md');
11
+ const content = await readFile(filePath, 'utf-8');
12
+ const lines = content.split('\n');
13
+ return lines.slice(section.startLine - 1, section.endLine).join('\n');
14
+ }
15
+ export async function indexSections(latDir, db, provider, key) {
16
+ const allSections = await loadAllSections(latDir);
17
+ const flat = flattenSections(allSections);
18
+ // Build current state: id -> { section, content, hash }
19
+ const current = new Map();
20
+ for (const s of flat) {
21
+ const text = await sectionContent(s, latDir);
22
+ current.set(s.id, { section: s, content: text, hash: hashContent(text) });
23
+ }
24
+ // Get existing hashes from DB
25
+ const existing = new Map();
26
+ const rows = await db.execute('SELECT id, content_hash FROM sections');
27
+ for (const row of rows.rows) {
28
+ existing.set(row.id, row.content_hash);
29
+ }
30
+ // Partition into new, changed, unchanged, deleted
31
+ const toEmbed = [];
32
+ let unchanged = 0;
33
+ for (const [id, entry] of current) {
34
+ const existingHash = existing.get(id);
35
+ if (existingHash === entry.hash) {
36
+ unchanged++;
37
+ }
38
+ else {
39
+ toEmbed.push({ id, content: entry.content, section: entry.section });
40
+ }
41
+ }
42
+ const toDelete = [...existing.keys()].filter((id) => !current.has(id));
43
+ // Embed new/changed sections
44
+ if (toEmbed.length > 0) {
45
+ const texts = toEmbed.map((e) => e.content);
46
+ const vectors = await embed(texts, provider, key);
47
+ const now = Date.now();
48
+ for (let i = 0; i < toEmbed.length; i++) {
49
+ const { id, content, section } = toEmbed[i];
50
+ const hash = current.get(id).hash;
51
+ const vecJson = JSON.stringify(vectors[i]);
52
+ await db.execute({
53
+ sql: `INSERT OR REPLACE INTO sections (id, file, heading, content, content_hash, embedding, updated_at)
54
+ VALUES (?, ?, ?, ?, ?, vector(?), ?)`,
55
+ args: [id, section.file, section.heading, content, hash, vecJson, now],
56
+ });
57
+ }
58
+ }
59
+ // Delete removed sections
60
+ for (const id of toDelete) {
61
+ await db.execute({ sql: 'DELETE FROM sections WHERE id = ?', args: [id] });
62
+ }
63
+ const added = toEmbed.filter((e) => !existing.has(e.id)).length;
64
+ const updated = toEmbed.filter((e) => existing.has(e.id)).length;
65
+ return { added, updated, removed: toDelete.length, unchanged };
66
+ }
@@ -0,0 +1,8 @@
1
+ export type EmbeddingProvider = {
2
+ name: string;
3
+ apiBase: string;
4
+ model: string;
5
+ dimensions: number;
6
+ headers: (key: string) => Record<string, string>;
7
+ };
8
+ export declare function detectProvider(key: string): EmbeddingProvider;
@@ -0,0 +1,40 @@
1
+ const openai = {
2
+ name: 'openai',
3
+ apiBase: 'https://api.openai.com/v1',
4
+ model: 'text-embedding-3-small',
5
+ dimensions: 1536,
6
+ headers: (key) => ({
7
+ Authorization: `Bearer ${key}`,
8
+ 'Content-Type': 'application/json',
9
+ }),
10
+ };
11
+ const vercel = {
12
+ name: 'vercel',
13
+ apiBase: 'https://ai-gateway.vercel.sh/v1',
14
+ model: 'openai/text-embedding-3-small',
15
+ dimensions: 1536,
16
+ headers: (key) => ({
17
+ Authorization: `Bearer ${key}`,
18
+ 'Content-Type': 'application/json',
19
+ }),
20
+ };
21
+ export function detectProvider(key) {
22
+ if (key.startsWith('REPLAY_LAT_LLM_KEY::')) {
23
+ const replayUrl = key.slice('REPLAY_LAT_LLM_KEY::'.length);
24
+ return {
25
+ name: 'replay',
26
+ apiBase: replayUrl,
27
+ model: 'replay',
28
+ dimensions: 1536,
29
+ headers: () => ({ 'Content-Type': 'application/json' }),
30
+ };
31
+ }
32
+ if (key.startsWith('sk-ant-')) {
33
+ throw new Error("Anthropic doesn't offer an embedding model. Set LAT_LLM_KEY to an OpenAI (sk-...) or Vercel AI (vck_...) key.");
34
+ }
35
+ if (key.startsWith('vck_'))
36
+ return vercel;
37
+ if (key.startsWith('sk-'))
38
+ return openai;
39
+ throw new Error(`Unrecognized LAT_LLM_KEY prefix. Supported: OpenAI (sk-...), Vercel AI (vck_...).`);
40
+ }
@@ -0,0 +1,9 @@
1
+ import type { Client } from '@libsql/client';
2
+ import type { EmbeddingProvider } from './provider.js';
3
+ export type SearchResult = {
4
+ id: string;
5
+ file: string;
6
+ heading: string;
7
+ content: string;
8
+ };
9
+ export declare function searchSections(db: Client, query: string, provider: EmbeddingProvider, key: string, limit?: number): Promise<SearchResult[]>;
@@ -0,0 +1,17 @@
1
+ import { embed } from './embeddings.js';
2
+ export async function searchSections(db, query, provider, key, limit = 5) {
3
+ const [queryVec] = await embed([query], provider, key);
4
+ const vecJson = JSON.stringify(queryVec);
5
+ const rows = await db.execute({
6
+ sql: `SELECT s.id, s.file, s.heading, s.content
7
+ FROM vector_top_k('sections_vec_idx', vector(?), ?) AS v
8
+ JOIN sections AS s ON s.rowid = v.id`,
9
+ args: [vecJson, limit],
10
+ });
11
+ return rows.rows.map((row) => ({
12
+ id: row.id,
13
+ file: row.file,
14
+ heading: row.heading,
15
+ content: row.content,
16
+ }));
17
+ }
package/package.json CHANGED
@@ -1,13 +1,31 @@
1
1
  {
2
2
  "name": "lat.md",
3
- "version": "0.1.0",
4
- "description": "Anchor source code to high-level concepts defined in markdown",
3
+ "version": "0.1.2",
4
+ "description": "A knowledge graph for your codebase, written in markdown",
5
5
  "type": "module",
6
+ "packageManager": "pnpm@10.30.2",
7
+ "license": "MIT",
8
+ "author": "Yury Selivanov",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/1st1/lat.md.git"
12
+ },
13
+ "homepage": "https://github.com/1st1/lat.md",
14
+ "keywords": [
15
+ "markdown",
16
+ "knowledge-graph",
17
+ "documentation",
18
+ "agents",
19
+ "cli",
20
+ "wiki-links",
21
+ "codebase"
22
+ ],
6
23
  "bin": {
7
24
  "lat": "./dist/src/cli/index.js"
8
25
  },
9
26
  "files": [
10
- "dist/src"
27
+ "dist/src",
28
+ "templates"
11
29
  ],
12
30
  "scripts": {
13
31
  "build": "tsc",
@@ -15,7 +33,8 @@
15
33
  "test:watch": "vitest",
16
34
  "typecheck": "tsc --noEmit",
17
35
  "format": "prettier --write 'src/**/*.ts'",
18
- "format:check": "prettier --check 'src/**/*.ts'"
36
+ "format:check": "prettier --check 'src/**/*.ts'",
37
+ "cook-test-rag": "tsx scripts/cook-test-rag.ts"
19
38
  },
20
39
  "devDependencies": {
21
40
  "@types/mdast": "^4.0.4",
@@ -23,10 +42,15 @@
23
42
  "mdast-util-from-markdown": "^2.0.3",
24
43
  "micromark-util-types": "^2.0.2",
25
44
  "prettier": "^3.8.1",
45
+ "tsx": "^4.21.0",
26
46
  "typescript": "^5.7.0",
27
47
  "vitest": "^3.0.0"
28
48
  },
29
49
  "dependencies": {
50
+ "@libsql/client": "^0.17.0",
51
+ "chalk": "^5.6.2",
52
+ "commander": "^14.0.3",
53
+ "ignore-walk": "^8.0.0",
30
54
  "mdast-util-to-markdown": "^2.1.0",
31
55
  "remark-parse": "^11.0.0",
32
56
  "remark-stringify": "^11.0.0",
@@ -0,0 +1,56 @@
1
+ # Before starting work
2
+
3
+ - Run `lat search` to find sections relevant to your task. Read them to understand the design intent before writing code.
4
+ - Run `lat prompt` on user prompts to expand any `[[refs]]` — this resolves section names to file locations and provides context.
5
+
6
+ # Post-task checklist (REQUIRED — do not skip)
7
+
8
+ After EVERY task, before responding to the user:
9
+
10
+ - [ ] Update `lat.md/` if you added or changed any functionality, architecture, tests, or behavior
11
+ - [ ] Run `lat check` — all wiki links and code refs must pass
12
+ - [ ] Do not skip these steps. Do not consider your task done until both are complete.
13
+
14
+ ---
15
+
16
+ # What is lat.md?
17
+
18
+ This project uses [lat.md](https://www.npmjs.com/package/lat.md) to maintain a structured knowledge graph of its architecture, design decisions, and test specs in the `lat.md/` directory. It is a set of cross-linked markdown files that describe **what** this project does and **why** — the domain concepts, key design decisions, business logic, and test specifications. Use it to ground your work in the actual architecture rather than guessing.
19
+
20
+ # Commands
21
+
22
+ ```bash
23
+ lat locate "Section Name" # find a section by name (exact, fuzzy)
24
+ lat refs "file#Section" # find what references a section
25
+ lat search "natural language" # semantic search across all sections
26
+ lat prompt "user prompt text" # expand [[refs]] to resolved locations
27
+ lat check # validate all links and code refs
28
+ ```
29
+
30
+ Run `lat --help` when in doubt about available commands or options.
31
+
32
+ If `lat search` fails because `LAT_LLM_KEY` is not set, explain to the user that semantic search requires an API key (`export LAT_LLM_KEY=sk-...` for OpenAI or `export LAT_LLM_KEY=vck_...` for Vercel). If the user doesn't want to set it up, use `lat locate` for direct lookups instead.
33
+
34
+ # Syntax primer
35
+
36
+ - **Section ids**: `file-stem#Heading#SubHeading` (e.g. `cli#search#Indexing`)
37
+ - **Wiki links**: `[[target]]` or `[[target|alias]]` — cross-references between sections
38
+ - **Code refs**: `// @lat: [[section-id]]` (JS/TS) or `# @lat: [[section-id]]` (Python) — ties source code to concepts
39
+
40
+ # Test specs
41
+
42
+ Key tests can be described as sections in `lat.md/` files (e.g. `tests.md`). Add frontmatter to require that every leaf section is referenced by a `// @lat:` or `# @lat:` comment in test code:
43
+
44
+ ```markdown
45
+ ---
46
+ lat:
47
+ require-code-mention: true
48
+ ---
49
+ # Tests
50
+
51
+ ## User login
52
+ ### Rejects expired tokens
53
+ ### Handles missing password
54
+ ```
55
+
56
+ Each test in code should reference its spec: `// @lat: [[tests#User login#Rejects expired tokens]]`. Running `lat check` will flag any spec section not covered by a code reference, and any code reference pointing to a nonexistent section.
@@ -0,0 +1 @@
1
+ This directory is used as source of templates for commands like `lat init`.
@@ -0,0 +1,5 @@
1
+ This directory defines the high-level concepts, business logic, and architecture of this project using markdown.
2
+
3
+ It is managed by [lat.md](https://www.npmjs.com/package/lat.md) — a tool that anchors source code to these definitions.
4
+
5
+ Install the `lat` command with `npm i -g lat.md` and run `lat --help`.