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 +1 -1
- package/src/commands/build.ts +1 -1
- package/src/commands/init.ts +144 -2
- package/src/commands/update.ts +8 -7
- package/src/main.ts +40 -11
package/package.json
CHANGED
package/src/commands/build.ts
CHANGED
package/src/commands/init.ts
CHANGED
|
@@ -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
|
-
|
|
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 };
|
package/src/commands/update.ts
CHANGED
|
@@ -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
|
|
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
|
|
66
|
-
const
|
|
67
|
-
|
|
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.
|
|
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
|
-
|
|
459
|
+
printUnknownSymbolError(db, symbol);
|
|
463
460
|
process.exit(1);
|
|
464
461
|
}
|
|
465
462
|
if (nodes.length > 1) {
|
|
466
463
|
db.close();
|
|
467
|
-
|
|
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,
|