@nudojs/lsp 0.4.0 → 0.4.1

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": "@nudojs/lsp",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Language Server Protocol implementation for the Nudo type inference engine",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -11,7 +11,17 @@
11
11
  "lsp",
12
12
  "language-server"
13
13
  ],
14
- "main": "src/server.ts",
14
+ "main": "./dist/server.js",
15
+ "types": "./dist/server.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/server.d.ts",
19
+ "default": "./dist/server.js"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
15
25
  "repository": {
16
26
  "type": "git",
17
27
  "url": "git+https://github.com/nudojs/nudo.git",
@@ -27,9 +37,9 @@
27
37
  "dependencies": {
28
38
  "vscode-languageserver": "^9.0.0",
29
39
  "vscode-languageserver-textdocument": "^1.0.0",
30
- "@nudojs/service": "0.3.0",
31
- "@nudojs/core": "0.3.0",
32
- "@nudojs/parser": "0.3.0"
40
+ "@nudojs/service": "0.3.1",
41
+ "@nudojs/core": "0.3.1",
42
+ "@nudojs/parser": "0.3.1"
33
43
  },
34
44
  "scripts": {
35
45
  "build": "tsup src/server.ts --format esm --dts"
package/CHANGELOG.md DELETED
@@ -1,63 +0,0 @@
1
- # @nudojs/lsp
2
-
3
- ## 0.4.0
4
-
5
- ### Minor Changes
6
-
7
- - 5786fa5: Promote the B-path (transpile + in-process Abs runtime) to the primary evaluation path for capable sources.
8
-
9
- - Module graph now supports relative imports, bare-package harvest, default/namespace imports, re-exports, `export *`, require, cycle/depth/missing guards, and env modules (`path` / `node:path` etc.).
10
- - Diagnostics, `@nudo:env` / mock injection, `call@` / `entry@` provenance, method-missing, unknown-recv, generators, classes, async/await, optional chaining, and destructuring run through B.
11
- - When B hosts a file, `evaluateProgram` is skipped so TypeValue no longer double-reports.
12
- - CLI `check` / `types` / `test` accept directories; check uses an Abs-only gate.
13
- - LSP hover / `getTypeAtPosition` on capable files read the Abs node table; `*.nudo.js` edits evict L0 and recheck open parents.
14
- - Public core surface now re-exports the algebra API (`Abs`, check, generalize, exec, bridge, `parseSource`, `stripTypes`).
15
-
16
- ### Patch Changes
17
-
18
- - Updated dependencies [5786fa5]
19
- - Updated dependencies [5786fa5]
20
- - Updated dependencies [5786fa5]
21
- - @nudojs/core@0.3.0
22
- - @nudojs/service@0.3.0
23
- - @nudojs/parser@0.3.0
24
-
25
- ## 0.3.0
26
-
27
- ### Minor Changes
28
-
29
- - 0fd0718: Merge the three environment packages into one: `@nudojs/env-es`, `@nudojs/env-web`, `@nudojs/env-node` are replaced by a single `@nudojs/env` package with subpath exports `@nudojs/env/es`, `@nudojs/env/web`, `@nudojs/env/node`.
30
-
31
- Move agent-facing tools from the standalone MCP server into the language server: `@nudojs/mcp` is removed. `@nudojs/lsp` now exposes `nudo.whatIf`, `nudo.suggestCase`, `nudo.trace`, `nudo.selectCase`, and `nudo.getActiveCases` via `workspace/executeCommand` (custom-request aliases `nudo/whatIf` etc. included), adds pull-mode diagnostics, and works on files that are not open in the editor (disk fallback). `nudo.whatIf` now actually applies the given type bindings — previously they were ignored. AI agents connect through any LSP↔MCP bridge (cclsp, mcpls, agent-lsp) or a native LSP client; an installable agent skill ships at `packages/lsp/agent-skill/SKILL.md`.
32
-
33
- ### Patch Changes
34
-
35
- - @nudojs/service@0.2.1
36
-
37
- ## 0.2.0
38
-
39
- ### Minor Changes
40
-
41
- - 6c38283: docs and ci
42
- - 9f7f819: fix pkg info
43
- - c175f71: version
44
-
45
- ### Patch Changes
46
-
47
- - Updated dependencies [6c38283]
48
- - Updated dependencies [9f7f819]
49
- - Updated dependencies [c175f71]
50
- - @nudojs/core@0.2.0
51
- - @nudojs/service@0.2.0
52
-
53
- ## 0.1.0
54
-
55
- ### Minor Changes
56
-
57
- - Conceptual design and basic implementation.
58
-
59
- ### Patch Changes
60
-
61
- - Updated dependencies
62
- - @nudojs/core@0.1.0
63
- - @nudojs/service@0.1.0
@@ -1,82 +0,0 @@
1
- ---
2
- name: nudo
3
- description: Query precise JavaScript types by abstract interpretation — use when the project uses Nudo (@nudo: directives) and you need the inferred type of a variable or expression, a function's input→output type trace, what-if type hypotheses, case coverage, or type diagnostics for a .js file.
4
- ---
5
-
6
- # Nudo — type inference for JavaScript
7
-
8
- Nudo is a comment-driven type inference engine for plain JavaScript. It derives types by **executing** code with symbolic type values (`T.number`, `T.string`) instead of requiring TypeScript annotations: functions marked with `@nudo:case` directives are run under abstract interpretation, and unmarked functions get cases synthesized from their call sites (whole-program inference). Ask Nudo instead of guessing what a refactor does to types.
9
-
10
- ## Install and connect
11
-
12
- Nudo's agent face lives in its language server. Install it in the user's project (or globally):
13
-
14
- ```bash
15
- npm i @nudojs/lsp # project-local; or: npm i -g @nudojs/lsp
16
- ```
17
-
18
- The server speaks LSP over stdio:
19
-
20
- ```bash
21
- node node_modules/@nudojs/lsp/src/server.ts # Node >= 22.18; on older Node: npx tsx <path>
22
- ```
23
-
24
- Three ways to connect (details in the [Agent Integration Guide](https://nudojs.github.io/nudo/docs/guides/mcp-server)):
25
-
26
- 1. **Generic LSP→MCP bridge** (cclsp, mcpls, agent-lsp) — registers Nudo as the language server for `.js` files; verify the bridge passes through `workspace/executeCommand`.
27
- 2. **Native LSP client** — spawn the server over stdio, `initialize`, then call `workspace/executeCommand` (or the custom request aliases below).
28
- 3. **VS Code / Cursor** — the `nudo-vscode` extension launches the server automatically.
29
-
30
- ## Command cheat sheet
31
-
32
- All commands are available as `workspace/executeCommand` (dot form) and as custom LSP requests (slash form); agents use the `file` parameter everywhere (accepts a `file://` URI or a bare path). Editor extensions call the `nudo/selectCase` / `nudo/getActiveCases` requests with editor-style `uri` params instead — same handlers. Files that are not open in an editor are read from disk. `whatIf`, `suggestCase`, and `trace` return MCP-style text content — `{ content: [{ type: "text", text }] }`.
33
-
34
- | Command (request alias) | Arguments (JSON) | Returns |
35
- |---|---|---|
36
- | `nudo.whatIf` (`nudo/whatIf`) | `{ "file": "src/config.js", "bindings": [{ "name": "raw", "type": "string" }], "target": "size" }` | Text: the inferred type of `target` **under the assumed bindings** — e.g. `Type of "size": number`; bindings match top-level declarations only |
37
- | `nudo.trace` (`nudo/trace`) | `{ "file": "src/app.js", "functionName": "parse" }` | Text: one line per case, e.g. `Input: (T.string) => Output: number` |
38
- | `nudo.suggestCase` (`nudo/suggestCase`) | `{ "file": "src/app.js", "functionName": "parse" }` | Text: paste-ready `@nudo:case` directives when every case is call-site synthesized, e.g. `Function "parse" has 2 synthesized case(s); suggested directives:`; otherwise the current case count, e.g. `Function "parse" already has 3 case(s)`, or a suggested `@nudo:case` directive |
39
- | `nudo.selectCase` (`nudo/selectCase`) | `{ "file": "src/app.js", "functionName": "parse", "caseIndex": 1 }` | `{ "success": true }` — switches the active case (affects hover/diagnostics until changed back) |
40
- | `nudo.getActiveCases` (`nudo/getActiveCases`) | `{ "file": "src/app.js" }` | `{ "parse": 1, "greet": 0 }` — active case index per function |
41
-
42
- Diagnostics (failed `@nudo:refine` assertions, unreachable code, …) are available as LSP diagnostics — push (`textDocument/publishDiagnostics`) and pull (`textDocument/diagnostic`).
43
-
44
- ## What-if workflow
45
-
46
- The signature move: hypothesize a type for a top-level binding, observe what another binding becomes — without editing any source.
47
-
48
- ```js
49
- // src/config.js
50
- const raw = readInput();
51
- const size = raw.length;
52
- ```
53
-
54
- Assume `raw` is a string, ask what `size` is:
55
-
56
- ```json
57
- {
58
- "command": "nudo.whatIf",
59
- "arguments": [{
60
- "file": "src/config.js",
61
- "bindings": [{ "name": "raw", "type": "string" }],
62
- "target": "size"
63
- }]
64
- }
65
- ```
66
-
67
- → `Type of "size": number`. Then flip the hypothesis (`"type": "string | null"`) and re-ask: the same `size` now reports `unknown`, because the null arm can propagate through `.length`. Use this to preview refactors, validate an API's return type before calling it, or compare how alternative type assumptions ripple through downstream code.
68
-
69
- Bindings only match **top-level declarations** (`const`/`let`/`var`/`function`) in the file. A function parameter name does not resolve — you get `Type of "...": unknown` plus `Bindings not applied (no top-level declaration found): x` in the reply.
70
-
71
- ## Type expression syntax
72
-
73
- `bindings[].type` accepts a primitive or a `|`-separated union of primitives:
74
-
75
- - `number`, `string`, `boolean`, `null`, `undefined`, `bigint`, `symbol`
76
- - Unions: `string | null`, `number | string`
77
-
78
- ## Notes
79
-
80
- - **Unopened files use disk state.** If the file is not open in a connected editor, analysis runs on the on-disk content; edits the user has not saved are invisible.
81
- - Commands that report types reflect Nudo's inference, which follows runtime semantics (e.g. `Number("")` is `0`, not an error) — trust them over guesswork, but remember they describe the current code, not the user's intent.
82
- - Whole-program inference means every function with inferable call sites already has cases. When all of them are call-site synthesized, `suggestCase` returns ready-to-paste `@nudo:case` directive text (paste it above the function); `already has N case(s)` (handwritten or entry-only cases) is the normal report for the rest, not an error.
@@ -1,47 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { checkTool } from "../agent-tools.ts";
3
-
4
- const STD = `
5
- export const positive = number().gt(0);
6
- export const delay = number().gt(0);
7
- `;
8
-
9
- const src = `
10
- /// @nudo:import { positive } from "./std.nudo.js"
11
- /**
12
- * @nudo:refine x positive
13
- */
14
- function needsPositive(x) {
15
- if (x > 0) return x;
16
- return 0;
17
- }
18
- needsPositive(-1);
19
- `;
20
-
21
- const loadModule = (spec: string) =>
22
- spec.includes("std.nudo") ? STD : undefined;
23
-
24
- describe("nudo.check agent tool", () => {
25
- it("returns CheckJson v1 in json format", () => {
26
- const r = checkTool(
27
- { file: "/t/a.js", source: src, format: "json", loadModule },
28
- { readFile: () => src },
29
- );
30
- const parsed = JSON.parse(r.content[0].text);
31
- expect(parsed.version).toBe(1);
32
- expect(parsed.ok).toBe(false);
33
- expect(parsed.issues.some((i: { code: string }) => i.code === "nudo:constraint-violated")).toBe(true);
34
- });
35
-
36
- it("text format includes actual/expected", () => {
37
- const r = checkTool(
38
- { file: "/t/a.js", source: src, loadModule },
39
- { readFile: () => src },
40
- );
41
- const text = r.content[0].text;
42
- expect(text).toContain("FAILED");
43
- expect(text).toContain("actual");
44
- expect(text).toContain("expected");
45
- expect(text).toContain('"version": 1');
46
- });
47
- });
@@ -1,28 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { hoverTool } from "../agent-tools.ts";
3
-
4
- describe("nudo.hover agent tool", () => {
5
- it("returns lossless abs at position", () => {
6
- const src = `const x = 1 + 2;\n`;
7
- // col of `2` is 14 (0-based)
8
- const r = hoverTool(
9
- { file: "/t/h.js", line: 1, column: 14, source: src },
10
- { readFile: () => src },
11
- );
12
- const payload = JSON.parse(r.content[0].text);
13
- expect(payload.abs).toBeDefined();
14
- expect(payload.abs).toContain("2");
15
- expect(payload).toHaveProperty("ext");
16
- });
17
-
18
- it("can include inlays", () => {
19
- const src = `function needsPositive(x) {\n if (x > 0) return x;\n return 0;\n}\n`;
20
- const r = hoverTool(
21
- { file: "/t/i.js", line: 1, column: 9, source: src, includeInlays: true },
22
- { readFile: () => src },
23
- );
24
- const payload = JSON.parse(r.content[0].text);
25
- expect(Array.isArray(payload.inlays)).toBe(true);
26
- expect(payload.inlays.length).toBeGreaterThan(0);
27
- });
28
- });
@@ -1,38 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { inferTool } from "../agent-tools.ts";
3
-
4
- describe("nudo.infer agent tool", () => {
5
- const src = `function scale(x) { return x + 1; }\n`;
6
-
7
- it("returns InferJson v1 with abs intension", () => {
8
- const r = inferTool(
9
- { file: "/t/scale.js", source: src, format: "json" },
10
- { readFile: () => src },
11
- );
12
- const json = JSON.parse(r.content[0].text);
13
- expect(json.version).toBe(1);
14
- expect(json.functions[0].name).toBe("scale");
15
- expect(json.functions[0].cases[0].intension.abs).toContain("#");
16
- });
17
-
18
- it("text format lists abs lines", () => {
19
- const r = inferTool({ file: "/t/scale.js", source: src }, { readFile: () => src });
20
- const text = r.content[0].text;
21
- expect(text).toContain("scale");
22
- expect(text).toContain("abs:");
23
- expect(text).toContain('"version": 1');
24
- });
25
-
26
- it("filters by function name", () => {
27
- const multi = `
28
- function a(x) { return x; }
29
- function b(x) { return x + 1; }
30
- `;
31
- const r = inferTool(
32
- { file: "/t/m.js", source: multi, format: "json", functions: ["b"] },
33
- { readFile: () => multi },
34
- );
35
- const json = JSON.parse(r.content[0].text);
36
- expect(json.functions.map((f: { name: string }) => f.name)).toEqual(["b"]);
37
- });
38
- });
@@ -1,46 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { clearValidationState, checkToLspDiagnostics, validateText } from "../validation.ts";
3
-
4
- const STD = `
5
- export const positive = number().gt(0);
6
- `;
7
-
8
- const src = `
9
- /// @nudo:import { positive } from "./std.nudo.js"
10
- /**
11
- * @nudo:refine x positive
12
- */
13
- function needsPositive(x) {
14
- if (x > 0) return x;
15
- return 0;
16
- }
17
- needsPositive(-1);
18
- `;
19
-
20
- const loadModule = (spec: string) =>
21
- spec.includes("std.nudo") ? STD : undefined;
22
-
23
- describe("Abs check as LSP diagnostics", () => {
24
- it("maps constraint violations to nudo-check diagnostics", () => {
25
- const diags = checkToLspDiagnostics("/t/check.js", src, loadModule);
26
- expect(diags.length).toBeGreaterThan(0);
27
- const d = diags[0]!;
28
- expect(d.source).toBe("nudo-check");
29
- expect(d.code).toBe("nudo:constraint-violated");
30
- expect(d.message).toContain("actual");
31
- expect(d.message).toContain("expected");
32
- });
33
-
34
- it("validateText publishes check diags first", async () => {
35
- clearValidationState();
36
- let sent: { source?: string; code?: string }[] = [];
37
- await validateText("/t/check2.js", "file:///t/check2.js", src, 1, {
38
- sendDiagnostics: (p) => {
39
- sent = p.diagnostics.map((d) => ({ source: d.source, code: d.code as string }));
40
- },
41
- });
42
- // validateText 用磁盘 loadModule;虚拟路径下无 std.nudo.js → 无 check diags 也可
43
- // 至少不崩溃
44
- expect(Array.isArray(sent)).toBe(true);
45
- });
46
- });
@@ -1,32 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { parse } from "@nudojs/parser";
3
- import { buildSymbolTable, findDefinition, findReferences } from "../symbols.ts";
4
-
5
- describe("symbol table", () => {
6
- it("finds function definition", () => {
7
- const source = `function add(a, b) { return a + b; }`;
8
- const ast = parse(source);
9
- const table = buildSymbolTable(ast, "file:///test.js");
10
- const def = findDefinition(table, "add");
11
- expect(def).not.toBeNull();
12
- expect(def!.kind).toBe("function");
13
- expect(def!.loc.start.line).toBe(1);
14
- });
15
-
16
- it("finds variable definition", () => {
17
- const source = `const x = 42;`;
18
- const ast = parse(source);
19
- const table = buildSymbolTable(ast, "file:///test.js");
20
- const def = findDefinition(table, "x");
21
- expect(def).not.toBeNull();
22
- expect(def!.kind).toBe("variable");
23
- });
24
-
25
- it("collects references", () => {
26
- const source = `const x = 42; const y = x + 1;`;
27
- const ast = parse(source);
28
- const table = buildSymbolTable(ast, "file:///test.js");
29
- const refs = table.references.filter((r) => r.name === "x");
30
- expect(refs.length).toBeGreaterThan(0);
31
- });
32
- });