lattice-graph 0.2.0 → 0.3.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.2.0",
3
+ "version": "0.3.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",
@@ -9,10 +9,12 @@
9
9
  },
10
10
  "files": [
11
11
  "src",
12
+ "scripts",
12
13
  "README.md",
13
14
  "LICENSE"
14
15
  ],
15
16
  "scripts": {
17
+ "postinstall": "bun scripts/postinstall.ts",
16
18
  "dev": "bun src/main.ts",
17
19
  "test": "bun test",
18
20
  "lint": "bunx biome check src/ tests/",
@@ -44,11 +46,10 @@
44
46
  "@types/bun": "latest",
45
47
  "lefthook": "^2.1.4"
46
48
  },
47
- "peerDependencies": {
48
- "typescript": "^5"
49
- },
50
49
  "dependencies": {
51
50
  "commander": "^14.0.3",
52
- "smol-toml": "^1.6.0"
51
+ "smol-toml": "^1.6.0",
52
+ "typescript": "^5",
53
+ "typescript-language-server": "^4"
53
54
  }
54
55
  }
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Postinstall script — creates a minimal Python venv and installs zuban into it.
4
+ * This gives zuban a proper Python environment with typeshed stubs.
5
+ */
6
+ import { existsSync } from "node:fs";
7
+ import { join } from "node:path";
8
+
9
+ const VENDOR_DIR = join(import.meta.dir, "..", "vendor");
10
+ const VENV_DIR = join(VENDOR_DIR, "venv");
11
+
12
+ function findPython(): string | undefined {
13
+ for (const cmd of ["python3", "python"]) {
14
+ if (Bun.which(cmd)) return cmd;
15
+ }
16
+ return undefined;
17
+ }
18
+
19
+ async function main() {
20
+ const isWindows = process.platform === "win32";
21
+ const zubanBin = join(VENV_DIR, isWindows ? "Scripts" : "bin", isWindows ? "zubanls.exe" : "zubanls");
22
+
23
+ if (existsSync(zubanBin)) {
24
+ console.log("zuban already installed");
25
+ return;
26
+ }
27
+
28
+ const python = findPython();
29
+ if (!python) {
30
+ console.warn("Warning: Python not found. Python support requires python3 in PATH.");
31
+ return;
32
+ }
33
+
34
+ console.log("Installing zuban for Python support...");
35
+
36
+ try {
37
+ // Create venv
38
+ const venvResult = Bun.spawnSync([python, "-m", "venv", VENV_DIR], {
39
+ stdout: "ignore",
40
+ stderr: "pipe",
41
+ });
42
+ if (venvResult.exitCode !== 0) {
43
+ throw new Error(`Failed to create venv: ${venvResult.stderr.toString()}`);
44
+ }
45
+
46
+ // Install zuban via pip
47
+ const pip = join(VENV_DIR, isWindows ? "Scripts" : "bin", "pip");
48
+ const pipResult = Bun.spawnSync([pip, "install", "zuban", "--quiet"], {
49
+ stdout: "ignore",
50
+ stderr: "pipe",
51
+ });
52
+ if (pipResult.exitCode !== 0) {
53
+ throw new Error(`Failed to install zuban: ${pipResult.stderr.toString()}`);
54
+ }
55
+
56
+ if (!existsSync(zubanBin)) {
57
+ throw new Error("zubanls binary not found after installation");
58
+ }
59
+
60
+ console.log("zuban installed successfully");
61
+ } catch (error) {
62
+ console.warn(
63
+ `Warning: Failed to install zuban: ${error instanceof Error ? error.message : error}. ` +
64
+ "Python support requires: pip install zuban",
65
+ );
66
+ }
67
+ }
68
+
69
+ main();
@@ -1,5 +1,5 @@
1
1
  import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
- import { join } from "node:path";
2
+ import { join, resolve } from "node:path";
3
3
  import { err, ok, type Result } from "../types/result.ts";
4
4
 
5
5
  /**
@@ -102,25 +102,29 @@ function generateToml(languages: readonly string[], root: string): string {
102
102
  return lines.join("\n");
103
103
  }
104
104
 
105
- /** Checks if language server binaries are available. */
105
+ /** Checks if language server binaries are available (bundled or in PATH). */
106
106
  function checkLspAvailability(languages: readonly string[]): readonly string[] {
107
107
  const warnings: string[] = [];
108
- const commands: Record<string, string> = {
109
- typescript: "typescript-language-server",
110
- python: "zubanls",
111
- go: "gopls",
108
+ const latticeRoot = resolve(import.meta.dir, "..", "..");
109
+
110
+ const checks: Record<string, { bundled: string; system: string; install: string }> = {
111
+ typescript: {
112
+ bundled: join(latticeRoot, "node_modules", ".bin", "typescript-language-server"),
113
+ system: "typescript-language-server",
114
+ install: "reinstall lattice-graph",
115
+ },
116
+ python: {
117
+ bundled: join(latticeRoot, "vendor", "venv", "bin", "zubanls"),
118
+ system: "zubanls",
119
+ install: "reinstall lattice-graph (or pip install zuban)",
120
+ },
112
121
  };
122
+
113
123
  for (const lang of languages) {
114
- const cmd = commands[lang];
115
- if (!cmd) continue;
116
- if (!Bun.which(cmd)) {
117
- const installHint =
118
- lang === "python"
119
- ? "pip install zuban"
120
- : lang === "typescript"
121
- ? "npm install -g typescript-language-server typescript"
122
- : `install ${cmd}`;
123
- warnings.push(`Warning: ${cmd} not found. Run '${installHint}' before 'lattice build'.`);
124
+ const check = checks[lang];
125
+ if (!check) continue;
126
+ if (!existsSync(check.bundled) && !Bun.which(check.system)) {
127
+ warnings.push(`Warning: ${check.system} not found. ${check.install}`);
124
128
  }
125
129
  }
126
130
  return warnings;
@@ -1,5 +1,6 @@
1
1
  import type { Database } from "bun:sqlite";
2
- import { relative, resolve } from "node:path";
2
+ import { existsSync } from "node:fs";
3
+ import { join, relative, resolve } from "node:path";
3
4
  import { scanTags } from "../extract/tag-scanner.ts";
4
5
  import { discoverFiles } from "../files.ts";
5
6
  import {
@@ -39,18 +40,30 @@ type BuildStats = {
39
40
  readonly durationMs: number;
40
41
  };
41
42
 
42
- /** Default LSP server commands per language. */
43
- const DEFAULT_LSP: Record<
44
- string,
45
- { command: string; args: readonly string[]; languageId: string }
46
- > = {
47
- typescript: {
48
- command: "typescript-language-server",
49
- args: ["--stdio"],
50
- languageId: "typescript",
51
- },
52
- python: { command: "zubanls", args: [], languageId: "python" },
53
- };
43
+ /** Resolves the LSP server binary for a language, checking bundled paths first. */
44
+ function resolveLspServer(
45
+ language: string,
46
+ ): { command: string; args: readonly string[]; languageId: string } | undefined {
47
+ if (language === "typescript") {
48
+ // Check node_modules/.bin/ first (bundled with lattice-graph)
49
+ const bundled = join(
50
+ import.meta.dir,
51
+ "..",
52
+ "..",
53
+ "node_modules",
54
+ ".bin",
55
+ "typescript-language-server",
56
+ );
57
+ const command = existsSync(bundled) ? bundled : "typescript-language-server";
58
+ return { command, args: ["--stdio"], languageId: "typescript" };
59
+ }
60
+ if (language === "python") {
61
+ const bundled = join(import.meta.dir, "..", "..", "vendor", "venv", "bin", "zubanls");
62
+ const command = existsSync(bundled) ? bundled : "zubanls";
63
+ return { command, args: [], languageId: "python" };
64
+ }
65
+ return undefined;
66
+ }
54
67
 
55
68
  /**
56
69
  * Builds the knowledge graph by querying LSP servers for symbols and call hierarchy,
@@ -81,7 +94,7 @@ async function buildGraph(opts: BuildGraphOptions): Promise<BuildStats> {
81
94
  if (files.length === 0) continue;
82
95
  totalFiles += files.length;
83
96
 
84
- const lsp = DEFAULT_LSP[langConfig.language];
97
+ const lsp = resolveLspServer(langConfig.language);
85
98
  if (!lsp) continue;
86
99
 
87
100
  const client = await createLspClient({
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.2.0";
40
+ const VERSION = "0.3.0";
41
41
 
42
42
  const program = new Command();
43
43
  program.name("lattice").description("Knowledge graph CLI for coding agents").version(VERSION);