lattice-graph 0.4.0 → 0.5.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lattice-graph",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Knowledge graph CLI for coding agents — navigate code through flows, not grep.",
5
5
  "module": "src/main.ts",
6
6
  "type": "module",
@@ -82,4 +82,4 @@ function buildLanguageConfigs(config: LatticeConfig): readonly LanguageConfig[]
82
82
  return configs;
83
83
  }
84
84
 
85
- export { executeBuild };
85
+ export { buildLanguageConfigs, executeBuild };
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
1
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { err, ok, type Result } from "../types/result.ts";
4
4
 
@@ -27,6 +27,17 @@ function executeInit(projectRoot: string): Result<string, string> {
27
27
  writeFileSync(tomlPath, toml);
28
28
  }
29
29
 
30
+ // Append Lattice section to CLAUDE.md (skip if already present)
31
+ const claudeDir = join(projectRoot, ".claude");
32
+ const claudeMdPath = join(claudeDir, "CLAUDE.md");
33
+ mkdirSync(claudeDir, { recursive: true });
34
+ const existing = existsSync(claudeMdPath) ? readFileSync(claudeMdPath, "utf-8") : "";
35
+ if (!existing.includes("## Code Navigation")) {
36
+ const snippet = generateClaudeSnippet(languages);
37
+ const separator = existing.length > 0 && !existing.endsWith("\n\n") ? "\n\n" : "";
38
+ appendFileSync(claudeMdPath, `${separator}${snippet}`);
39
+ }
40
+
30
41
  // Check LSP server availability
31
42
  const warnings = checkLspAvailability(languages);
32
43
  const message = ["Initialized Lattice project", ...warnings].join("\n");
@@ -111,4 +122,135 @@ function checkLspAvailability(languages: readonly string[]): readonly string[] {
111
122
  return warnings;
112
123
  }
113
124
 
114
- export { executeInit };
125
+ const PYTHON_EXAMPLE = `\`\`\`bash
126
+ # 1. Orient: what flows exist?
127
+ lattice overview
128
+
129
+ # 2. Locate: find the relevant flow
130
+ lattice flow user-registration
131
+
132
+ # Output:
133
+ # register (app/auth/routes.py:45)
134
+ # → validate_input (app/auth/validation.py:12)
135
+ # → create_user (app/auth/service.py:30)
136
+ # → hash_password (app/auth/crypto.py:8)
137
+ # → insert_user (app/storage/postgres.py:55) [postgres]
138
+ # → send_welcome_email (app/notifications/email.py:20) [sendgrid]
139
+
140
+ # 3. Understand: zoom into the function you suspect
141
+ lattice context create_user
142
+
143
+ # 4. Scope: check what breaks if you change it
144
+ lattice impact create_user
145
+
146
+ # 5. Read: get just that function's source
147
+ lattice code create_user
148
+
149
+ # 6. Edit: use Read/Edit tools on app/auth/service.py:30
150
+ \`\`\`
151
+
152
+ ### Symbol format
153
+
154
+ Unique names work directly. Ambiguous names need file qualification.
155
+
156
+ \`\`\`bash
157
+ lattice context create_user # unique name
158
+ lattice context app/auth/service.py::create_user # file::function
159
+ lattice context app/models.py::User.save # file::Class.method
160
+ \`\`\``;
161
+
162
+ const TYPESCRIPT_EXAMPLE = `\`\`\`bash
163
+ # 1. Orient: what flows exist?
164
+ lattice overview
165
+
166
+ # 2. Locate: find the relevant flow
167
+ lattice flow checkout
168
+
169
+ # Output:
170
+ # handleCheckout (src/api/checkout.ts:25)
171
+ # → validateCart (src/cart/validation.ts:12)
172
+ # → createOrder (src/orders/service.ts:40)
173
+ # → insertOrder (src/db/orders.ts:18) [postgres]
174
+ # → chargePayment (src/payments/stripe.ts:30) [stripe]
175
+ # → sendConfirmation (src/notifications/email.ts:55) [sendgrid]
176
+
177
+ # 3. Understand: zoom into the function you suspect
178
+ lattice context createOrder
179
+
180
+ # 4. Scope: check what breaks if you change it
181
+ lattice impact createOrder
182
+
183
+ # 5. Read: get just that function's source
184
+ lattice code createOrder
185
+
186
+ # 6. Edit: use Read/Edit tools on src/orders/service.ts:40
187
+ \`\`\`
188
+
189
+ ### Symbol format
190
+
191
+ Unique names work directly. Ambiguous names need file qualification.
192
+
193
+ \`\`\`bash
194
+ lattice context createOrder # unique name
195
+ lattice context src/orders/service.ts::createOrder # file::function
196
+ \`\`\``;
197
+
198
+ const GO_EXAMPLE = `\`\`\`bash
199
+ # 1. Orient: what flows exist?
200
+ lattice overview
201
+
202
+ # 2. Locate: find the relevant flow
203
+ lattice flow create-order
204
+
205
+ # Output:
206
+ # HandleCreateOrder (internal/api/orders.go:35)
207
+ # → ValidateRequest (internal/api/validation.go:20)
208
+ # → CreateOrder (internal/service/orders.go:45)
209
+ # → InsertOrder (internal/repo/orders.go:28) [postgres]
210
+ # → PublishEvent (internal/events/publisher.go:15) [nats]
211
+
212
+ # 3. Understand: zoom into the function you suspect
213
+ lattice context CreateOrder
214
+
215
+ # 4. Scope: check what breaks if you change it
216
+ lattice impact CreateOrder
217
+
218
+ # 5. Read: get just that function's source
219
+ lattice code CreateOrder
220
+
221
+ # 6. Edit: use Read/Edit tools on internal/service/orders.go:45
222
+ \`\`\`
223
+
224
+ ### Symbol format
225
+
226
+ Unique names work directly. Ambiguous names need file qualification.
227
+
228
+ \`\`\`bash
229
+ lattice context CreateOrder # unique name
230
+ lattice context internal/service/orders.go::CreateOrder # file::function
231
+ lattice context internal/api/server.go::Server.Handle # file::Struct.Method
232
+ \`\`\``;
233
+
234
+ /** Generates a CLAUDE.md snippet with language-appropriate few-shot examples. */
235
+ function generateClaudeSnippet(languages: readonly string[]): string {
236
+ const primary = languages[0] ?? "typescript";
237
+ const example =
238
+ primary === "python" ? PYTHON_EXAMPLE : primary === "go" ? GO_EXAMPLE : TYPESCRIPT_EXAMPLE;
239
+
240
+ return `## Code Navigation
241
+
242
+ This project uses **Lattice** for codebase navigation. Use Lattice before reading files or Grep.
243
+
244
+ ### Example: full workflow
245
+
246
+ ${example}
247
+
248
+ ### After code changes
249
+
250
+ \`\`\`bash
251
+ lattice update
252
+ \`\`\`
253
+ `;
254
+ }
255
+
256
+ export { executeInit, generateClaudeSnippet };
@@ -5,7 +5,7 @@ import { discoverFiles } from "../files.ts";
5
5
  import { checkSchemaVersion } from "../graph/database.ts";
6
6
  import type { LatticeConfig } from "../types/config.ts";
7
7
  import { isOk, ok, type Result } from "../types/result.ts";
8
- import { executeBuild } from "./build.ts";
8
+ import { buildLanguageConfigs, executeBuild } from "./build.ts";
9
9
 
10
10
  /** Statistics from an incremental update. */
11
11
  type UpdateStats = {
@@ -58,13 +58,14 @@ async function executeUpdate(
58
58
  }
59
59
  const lastBuild = Number.parseInt(metaRow.value, 10);
60
60
 
61
- // Discover files
62
- const extensions = [".ts", ".tsx"];
63
- const sourceRoots = config.typescript?.sourceRoots ?? [config.root];
61
+ // Discover files across all configured languages
62
+ const languageConfigs = buildLanguageConfigs(config);
64
63
  const allFiles: string[] = [];
65
- for (const srcRoot of sourceRoots) {
66
- const absRoot = resolve(projectRoot, srcRoot);
67
- allFiles.push(...discoverFiles(absRoot, extensions, config.exclude));
64
+ for (const lang of languageConfigs) {
65
+ for (const srcRoot of lang.sourceRoots) {
66
+ const absRoot = resolve(projectRoot, srcRoot);
67
+ allFiles.push(...discoverFiles(absRoot, lang.extensions, config.exclude));
68
+ }
68
69
  }
69
70
 
70
71
  // Find changed files
package/src/main.ts CHANGED
@@ -37,7 +37,7 @@ import {
37
37
  import type { Node } from "./types/graph.ts";
38
38
  import { isOk, unwrap } from "./types/result.ts";
39
39
 
40
- const VERSION = "0.4.0";
40
+ const VERSION = "0.5.0";
41
41
 
42
42
  const program = new Command();
43
43
  program.name("lattice").description("Knowledge graph CLI for coding agents").version(VERSION);
@@ -269,17 +269,14 @@ program
269
269
  const nodes = resolveSymbol(db, symbol);
270
270
 
271
271
  if (nodes.length === 0) {
272
+ printUnknownSymbolError(db, symbol);
272
273
  db.close();
273
- console.log(`Unknown symbol: ${symbol}`);
274
274
  process.exit(1);
275
275
  }
276
276
 
277
277
  if (nodes.length > 1) {
278
+ printAmbiguousSymbolError(nodes);
278
279
  db.close();
279
- console.log("Ambiguous symbol. Matches:");
280
- for (const n of nodes) {
281
- console.log(` ${n.id}`);
282
- }
283
280
  process.exit(1);
284
281
  }
285
282
 
@@ -459,15 +456,12 @@ function resolveOne(db: Database, symbol: string): Node {
459
456
  const nodes = resolveSymbol(db, symbol);
460
457
  if (nodes.length === 0) {
461
458
  db.close();
462
- console.error(`Unknown symbol: ${symbol}`);
459
+ printUnknownSymbolError(db, symbol);
463
460
  process.exit(1);
464
461
  }
465
462
  if (nodes.length > 1) {
466
463
  db.close();
467
- console.error("Ambiguous symbol. Matches:");
468
- for (const n of nodes) {
469
- console.error(` ${n.id}`);
470
- }
464
+ printAmbiguousSymbolError(nodes);
471
465
  process.exit(1);
472
466
  }
473
467
  const node = nodes[0];
@@ -479,6 +473,41 @@ function resolveOne(db: Database, symbol: string): Node {
479
473
  return node;
480
474
  }
481
475
 
476
+ function printUnknownSymbolError(db: Database, symbol: string): void {
477
+ console.error(`Unknown symbol: ${symbol}\n`);
478
+ console.error("Symbol format:");
479
+ console.error(" function_name unique name (if unambiguous)");
480
+ console.error(" src/file.ts::functionName file::function");
481
+ console.error(" src/file.py::Class.method file::Class.method\n");
482
+
483
+ const similar = findSimilarSymbols(db, symbol);
484
+ if (similar.length > 0) {
485
+ console.error("Did you mean:");
486
+ for (const s of similar) {
487
+ console.error(` ${s}`);
488
+ }
489
+ }
490
+ }
491
+
492
+ function printAmbiguousSymbolError(nodes: readonly Node[]): void {
493
+ console.error("Ambiguous symbol. Qualify with file path:\n");
494
+ for (const n of nodes) {
495
+ console.error(` lattice context ${n.id}`);
496
+ }
497
+ }
498
+
499
+ function findSimilarSymbols(db: Database, symbol: string): readonly string[] {
500
+ const lower = symbol.toLowerCase();
501
+ const parts = lower.split(".");
502
+ const name = parts[parts.length - 1] ?? lower;
503
+
504
+ const rows = db
505
+ .query("SELECT id, name FROM nodes WHERE LOWER(name) LIKE ? LIMIT 5")
506
+ .all(`%${name}%`) as { id: string; name: string }[];
507
+
508
+ return rows.map((r) => r.id);
509
+ }
510
+
482
511
  /** Builds a flow tree by recursively following call edges from a root node. */
483
512
  function buildFlowTree(
484
513
  db: Database,