From a0f5631843c14e082e6be003bf2f4f312ba64941 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Wed, 4 Mar 2026 10:50:05 +0800 Subject: [PATCH 01/14] feat: lmt for lsp --- docs/java-context-tools-design.md | 328 ++++++++ docs/java-context-tools-skill.md | 228 ++++++ docs/package-json-tools-snippet.jsonc | 191 +++++ .../com.microsoft.jdtls.ext.core/plugin.xml | 1 + .../jdtls/ext/core/AiContextCommand.java | 221 +++++ .../jdtls/ext/core/CommandHandler.java | 2 + .../ext/core/model/ClassDetailResult.java | 49 ++ .../core/model/DependencyDetailsResult.java | 37 + .../ext/core/model/FileImportsResult.java | 45 ++ .../ext/core/model/ProjectContextResult.java | 42 + lsp-ai-tools.md | 752 ++++++++++++++++++ package-lock.json | 19 +- package.json | 165 +++- resources/skills/java-context-tools/SKILL.md | 79 ++ src/commands.ts | 2 + src/copilot/tools/javaContextTools.ts | 330 ++++++++ src/extension.ts | 2 + 17 files changed, 2482 insertions(+), 11 deletions(-) create mode 100644 docs/java-context-tools-design.md create mode 100644 docs/java-context-tools-skill.md create mode 100644 docs/package-json-tools-snippet.jsonc create mode 100644 jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/AiContextCommand.java create mode 100644 jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/model/ClassDetailResult.java create mode 100644 jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/model/DependencyDetailsResult.java create mode 100644 jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/model/FileImportsResult.java create mode 100644 jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/model/ProjectContextResult.java create mode 100644 lsp-ai-tools.md create mode 100644 resources/skills/java-context-tools/SKILL.md create mode 100644 src/copilot/tools/javaContextTools.ts diff --git a/docs/java-context-tools-design.md b/docs/java-context-tools-design.md new file mode 100644 index 00000000..d4c7440b --- /dev/null +++ b/docs/java-context-tools-design.md @@ -0,0 +1,328 @@ +# Java Context Tools — 重新设计方案 + +## 设计哲学 + +``` +旧模式(Push All): 新模式(Lazy Pull): + + Copilot 补全请求 AI 决定调什么 + ↓ ↓ + 一次性推送所有内容 按需逐层深入 + ↓ ↓ + 3000+ tokens 噪音 每次 < 200 tokens 精准信息 +``` + +**核心原则:** +1. **AI 驱动调用**:AI 根据任务决定需要什么信息,而不是我们猜测它需要什么 +2. **分层粒度**:摘要 → 签名 → 详情,每层独立可用 +3. **每次 < 200 tokens**:宁可多调一次,不要一次塞 3000 tokens +4. **结构化 JSON**:AI 直接解析,不需要从文本中提取信息 +5. **Skill 文档引导**:教 AI 什么时候用什么工具,形成高效工作流 + +--- + +## Tool 清单 + +### 总览 + +| Tool | 粒度 | 用途 | 典型 token 量 | +|------|------|------|--------------| +| `java_getProjectContext` | L0 | 项目级概览 | ~100 | +| `java_getFileImports` | L0 | 文件的 import 列表 | ~80 | +| `java_getClassDetail` | L1 | 单个类的签名+方法列表 | ~150 | +| `java_getDependencyDetails` | L1 | 指定依赖的 GAV+scope | ~50 | + +### 与 LSP 标准 Tool 的配合 + +| 标准 LSP Tool(VS Code 内置命令封装) | 粒度 | 对应 LSP 请求 | +|--------------------------------------|------|-------------| +| `java_getFileStructure` | L0 | `textDocument/documentSymbol` | +| `java_findSymbol` | L0 | `workspace/symbol` | +| `java_getTypeAtPosition` | L1 | `textDocument/hover` (后处理) | +| `java_getCallHierarchy` | L1 | `callHierarchy/incomingCalls` + `outgoingCalls` | +| `java_getTypeHierarchy` | L1 | `typeHierarchy/supertypes` + `subtypes` | + +--- + +## Tool 1: `java_getProjectContext` + +**用途**:AI 进入一个 Java 项目时的第一个调用。快速了解项目是什么。 + +### 输入 + +```typescript +{ + fileUri: string // 项目中任意一个文件的 URI +} +``` + +### 输出(示例,~100 tokens) + +```json +{ + "project": { + "name": "my-order-service", + "buildTool": "Maven", + "javaVersion": "17", + "sourceLevel": "17", + "targetLevel": "17", + "sourceRoots": ["src/main/java", "src/test/java"], + "moduleName": null + }, + "dependencies": { + "total": 47, + "direct": [ + "org.springframework.boot:spring-boot-starter-web:3.2.1", + "org.springframework.boot:spring-boot-starter-data-jpa:3.2.1", + "com.google.code.gson:gson:2.10.1", + "org.projectlombok:lombok:1.18.30" + ], + "directCount": 8, + "transitiveCount": 39 + }, + "projectReferences": ["common-lib", "shared-model"] +} +``` + +**关键设计决策:** +- `dependencies.direct` 只列直接依赖的 GAV(不列传递依赖——AI 很少需要) +- 传递依赖只给个数量,AI 需要时再用 `java_getDependencyDetails` 深入 +- `sourceRoots` 帮助 AI 理解项目结构,知道源码在哪 + +### Java 后端命令 + +``` +java.project.getProjectContext(fileUri) → ProjectContextResult +``` + +--- + +## Tool 2: `java_getFileImports` + +**用途**:快速了解一个 Java 文件引用了哪些类型,但不展开细节。 + +### 输入 + +```typescript +{ + fileUri: string // Java 文件的 URI +} +``` + +### 输出(示例,~80 tokens) + +```json +{ + "file": "src/main/java/com/example/OrderService.java", + "imports": [ + { "name": "com.example.model.Order", "kind": "class", "source": "project" }, + { "name": "com.example.model.OrderStatus", "kind": "enum", "source": "project" }, + { "name": "com.example.repo.OrderRepository", "kind": "interface", "source": "project" }, + { "name": "org.springframework.stereotype.Service", "kind": "annotation", "source": "external", "artifact": "spring-context" }, + { "name": "org.springframework.transaction.annotation.Transactional", "kind": "annotation", "source": "external", "artifact": "spring-tx" }, + { "name": "java.util.List", "kind": "interface", "source": "jdk" }, + { "name": "java.util.Optional", "kind": "class", "source": "jdk" } + ], + "staticImports": [ + { "name": "org.junit.Assert.assertEquals", "memberKind": "method", "source": "external" } + ] +} +``` + +**关键设计决策:** +- `source` 三分法:`"project"` / `"external"` / `"jdk"` —— AI 最需要了解的是 `project` 的类 +- `kind` 直接给出类型类别,AI 不需要额外查 +- JDK 类标记为 `"jdk"` 而非 `"external"`,AI 知道不需要深入查 +- `artifact` 字段只对 external 有效,帮助 AI 关联到具体依赖 + +### Java 后端命令 + +``` +java.project.getFileImports(fileUri) → FileImportsResult +``` + +--- + +## Tool 3: `java_getClassDetail` + +**用途**:AI 确定需要了解某个类后,获取它的签名级别信息。 + +### 输入 + +```typescript +{ + qualifiedName: string // 全限定类名,如 "com.example.model.Order" + fileUri?: string // 可选:提供 file context 加速查找 +} +``` + +### 输出(项目内源码类,~150 tokens) + +```json +{ + "qualifiedName": "com.example.model.Order", + "kind": "class", + "uri": "file:///workspace/src/main/java/com/example/model/Order.java", + "source": "project", + "signature": "public class Order implements Serializable", + "superClass": "java.lang.Object", + "interfaces": ["java.io.Serializable"], + "javadocSummary": "Represents a customer order with line items and pricing.", + "constructors": [ + "Order()", + "Order(String orderId, Customer customer)" + ], + "methods": [ + "String getOrderId()", + "Customer getCustomer()", + "List getItems()", + "OrderStatus getStatus()", + "void setStatus(OrderStatus status)", + "BigDecimal getTotalPrice()", + "void addItem(OrderItem item)", + "void removeItem(String itemId)" + ], + "fields": [ + "private String orderId", + "private Customer customer", + "private List items", + "private OrderStatus status" + ], + "annotations": ["@Entity", "@Table(name = \"orders\")"] +} +``` + +### 输出(外部依赖类,~80 tokens,更精简) + +```json +{ + "qualifiedName": "com.google.gson.Gson", + "kind": "class", + "source": "external", + "artifact": "com.google.code.gson:gson:2.10.1", + "signature": "public final class Gson", + "javadocSummary": "This is the main class for using Gson.", + "methods": [ + "String toJson(Object src)", + " T fromJson(String json, Class classOfT)", + " T fromJson(String json, Type typeOfT)", + " T fromJson(Reader json, Type typeOfT)", + "JsonElement toJsonTree(Object src)", + "... (38 more public methods)" + ] +} +``` + +**关键设计决策:** +- 项目源码给完整信息(含 fields、annotations) +- 外部依赖只给签名级别,方法超过一定数量用 `"... (N more)"` 截断 +- 不给 JavaDoc 全文,只给第一句 summary +- `artifact` 字段帮 AI 关联到 pom.xml 中的依赖声明 + +### Java 后端命令 + +``` +java.project.getClassDetail(qualifiedName, fileUri?) → ClassDetailResult +``` + +--- + +## Tool 4: `java_getDependencyDetails` + +**用途**:AI 需要了解某个具体依赖的详细信息(排查版本冲突、检查 scope 等)。 + +### 输入 + +```typescript +{ + fileUri: string // 项目中的文件 URI(用于定位项目) + query?: string // 可选:按名称过滤依赖(模糊匹配) +} +``` + +### 输出(示例,~120 tokens) + +```json +{ + "dependencies": [ + { + "groupId": "com.google.code.gson", + "artifactId": "gson", + "version": "2.10.1", + "scope": "compile", + "isDirect": true, + "jarPath": "gson-2.10.1.jar" + }, + { + "groupId": "com.google.errorprone", + "artifactId": "error_prone_annotations", + "version": "2.18.0", + "scope": "compile", + "isDirect": false, + "broughtBy": "com.google.guava:guava:32.1.3-jre", + "jarPath": "error_prone_annotations-2.18.0.jar" + } + ] +} +``` + +**关键设计决策:** +- `query` 参数支持模糊搜索,AI 不需要拉全量依赖列表 +- `broughtBy` 告诉 AI 传递依赖是谁引入的(排查冲突的关键信息) +- `isDirect` + `scope` 帮 AI 判断依赖的实际作用范围 + +### Java 后端命令 + +``` +java.project.getDependencyDetails(fileUri, query?) → DependencyDetailsResult +``` + +--- + +## Tool 5-9: 标准 LSP 能力封装 + +这些工具直接封装 VS Code 内置命令,不需要新的 Java 后端命令。 + +### Tool 5: `java_getFileStructure` + +封装 `vscode.executeDocumentSymbolProvider`,返回文件的类/方法/字段树。 + +### Tool 6: `java_findSymbol` + +封装 `vscode.executeWorkspaceSymbolProvider`,全局模糊搜索符号。 + +### Tool 7: `java_getTypeAtPosition` + +封装 `vscode.executeHoverProvider` + 后处理提取类型签名。 + +### Tool 8: `java_getCallHierarchy` + +封装 `vscode.prepareCallHierarchy` + `vscode.provideIncomingCalls` / `vscode.provideOutgoingCalls`。 + +### Tool 9: `java_getTypeHierarchy` + +封装 `vscode.prepareTypeHierarchy` + `vscode.provideSupertypes` / `vscode.provideSubtypes`。 + +--- + +## 实现架构 + +``` +┌────────────────────────────────────────────────────────────┐ +│ Copilot Chat (LLM) │ +│ ↓ 读取 skill 文档了解工具用法 │ +│ ↓ 根据任务决定调用哪个工具 │ +│ LanguageModelTool 接口(package.json 注册) │ +│ ↓ │ +│ TS 适配层(src/copilot/tools/*.ts) │ +│ ↓ │ +│ ├── Tool 1-4: delegateCommandHandler → jdtls 扩展命令 │ +│ └── Tool 5-9: vscode.commands.executeCommand → LSP 标准 │ +│ │ +│ Java 后端(jdtls.ext) │ +│ ├── java.project.getProjectContext │ +│ ├── java.project.getFileImports │ +│ ├── java.project.getClassDetail │ +│ └── java.project.getDependencyDetails │ +└────────────────────────────────────────────────────────────┘ +``` diff --git a/docs/java-context-tools-skill.md b/docs/java-context-tools-skill.md new file mode 100644 index 00000000..22e169d7 --- /dev/null +++ b/docs/java-context-tools-skill.md @@ -0,0 +1,228 @@ +# Java Context Tools — Skill Document for LLM + +> This document teaches you how to effectively use the Java-specific tools when working on Java projects. Read this BEFORE using any of these tools. + +## When to Use These Tools + +You have access to 6 Java-specific tools that provide **compiler-accurate** information about Java projects. These tools are faster and more accurate than `grep_search` or `read_file` for understanding Java code structure, types, and relationships. + +**Use these tools when:** +- You need to understand a Java file's structure without reading the entire file +- You need to find where a class, interface, or method is defined in the workspace +- You need to know what a Java file imports and which libraries it depends on +- You need to resolve `var`, lambda, or generic types that aren't obvious from source code +- You need to trace call chains or type hierarchies accurately + +**Do NOT use these tools when:** +- The question can be answered by reading a single file (use `read_file` instead) +- You're working on non-Java files (pom.xml, build.gradle, yaml, etc.) +- You just need to do a text search (use `grep_search`) + +--- + +## Tool Reference + +### `java_getFileStructure` + +**Purpose:** Get the structural outline of a Java file — classes, methods, fields with line ranges. + +**When to call:** +- FIRST tool to call when you need to understand a Java file +- When you need to find which line a specific method starts at +- When planning modifications to a large file (500+ lines) + +**Input:** `{ uri: "" }` + +**Output:** Tree of symbols with kinds (Class, Method, Field, etc.) and line ranges. + +**Typical output size:** ~100 tokens + +**This replaces reading an entire file just to understand its structure.** Use this first, then `read_file` on specific line ranges. + +--- + +### `java_findSymbol` + +**Purpose:** Find classes, interfaces, methods by name across the entire workspace. + +**When to call:** +- When you know a class name (or partial name) but don't know which file it's in +- When searching for implementations of a pattern (e.g., all `*Controller` classes) +- When `grep_search` returns too many false positives (comments, strings, imports) + +**Input:** `{ query: "PaymentGateway" }` + +**Output:** Up to 20 matching symbols with name, kind, and file location. + +**Typical output size:** ~60 tokens + +**This is more precise than grep** — it only returns actual symbol definitions, not mentions in comments or strings. + +--- + +### `java_getFileImports` + +**Purpose:** Get all import statements from a Java file, classified by source (jdk/project/external). + +**When to call:** +- When you need to understand what types a file uses WITHOUT reading the full source +- When deciding which external libraries a file depends on +- When checking if a file already imports a class you want to use + +**Input:** `{ fileUri: "" }` + +**Output:** Classified import list with kind and source for regular and static imports. + +**Typical output size:** ~80 tokens + +**Important:** This tells you WHAT is imported but not HOW it's used. For details about a specific imported class, use `read_file` to read the relevant source or `java_getTypeAtPosition` on its usage. + +--- + +### `java_getTypeAtPosition` + +**Purpose:** Get the exact resolved type of any expression at a specific source position. + +**When to call:** +- When you see `var` and need to know the actual type +- When you need to know the return type of a chained method call +- When working with generic types and need the resolved type parameters +- When lambda parameters don't have explicit types + +**Input:** `{ uri: "", line: 42, character: 15 }` (0-based) + +**Output:** The resolved type signature at that position. + +**Typical output size:** ~20 tokens + +**This uses the Java compiler's own type inference** — it's 100% accurate, unlike guessing from source code. + +--- + +### `java_getCallHierarchy` + +**Purpose:** Find all callers of a method (incoming) or all methods it calls (outgoing). + +**When to call:** +- Before modifying a method's signature — to find all callers that need updating +- When doing impact analysis of a change +- When understanding the flow of a specific feature + +**Input:** `{ uri: "", line: 45, character: 20, direction: "incoming" | "outgoing" }` (0-based) + +**Output:** List of caller/callee methods with file locations. + +**Typical output size:** ~80 tokens + +**This is more precise than `list_code_usages`** — it only returns actual CALLS, not imports, declarations, or comments. + +--- + +### `java_getTypeHierarchy` + +**Purpose:** Find all supertypes or subtypes of a class/interface. + +**When to call:** +- When modifying an interface and need to find ALL implementations (including indirect ones) +- When understanding an inheritance chain +- When checking if a class can be used where a specific type is expected + +**Input:** `{ uri: "", line: 10, character: 14, direction: "supertypes" | "subtypes" }` (0-based) + +**Output:** Type hierarchy with symbol kinds and locations. + +**Typical output size:** ~60 tokens + +**This catches things grep misses** — indirect implementations, anonymous classes, lambda implementations. + +--- + +## Recommended Workflow Patterns + +### Pattern 1: "Fix a bug in a Java file" + +``` +1. java_getFileStructure(file) → Understand what's in the file (100 tokens) +2. read_file(file, relevant_lines) → Read the buggy method +3. [If needed] java_getFileImports(file) → Check what types are used (80 tokens) +4. [If needed] java_getTypeAtPosition() → Resolve ambiguous types (20 tokens) +5. Edit the file +``` +Total tool overhead: ~200 tokens (vs ~3000+ tokens if you blindly dump all imports) + +### Pattern 2: "Understand a new Java project" + +``` +1. read_file(pom.xml or build.gradle) → Check build tool, Java version, deps +2. java_findSymbol("Main") → Find entry points (60 tokens) +3. java_getFileStructure(main_file) → Understand main file (100 tokens) +4. java_getFileImports(main_file) → See what libraries are used (80 tokens) +``` +Total tool overhead: ~240 tokens + +### Pattern 3: "Refactor a method signature" + +``` +1. java_getCallHierarchy(method, "incoming") → Find all callers (80 tokens) +2. For each caller file: + java_getFileStructure(caller_file) → Understand caller context (100 tokens) +3. Edit all affected files +``` + +### Pattern 4: "Find all implementations of an interface" + +``` +1. java_findSymbol("MyInterface") → Locate the interface (60 tokens) +2. java_getTypeHierarchy(interface_pos, "subtypes") → Find all impls (60 tokens) +3. For key implementations: + java_getFileStructure(impl_file) → See what they override +``` + +### Pattern 5: "Understand dependency usage in a file" + +``` +1. java_getFileImports(file) → See all imports classified (80 tokens) +2. [For unfamiliar external types]: + java_getTypeAtPosition() on usage → See the resolved type/method signature +3. [If needed] read_file(pom.xml) → Check dependency coordinates +``` + +--- + +## Anti-Patterns (What NOT to Do) + +### ❌ Don't call java_getTypeAtPosition on obvious types + +```java +String name = "hello"; // Obviously String — don't call the tool +var result = service.process(input); // Not obvious — DO call the tool +``` + +### ❌ Don't use these tools for JDK standard library classes + +You already know `java.util.List`, `java.lang.String`, `java.io.File`. Don't waste a tool call on them. + +### ❌ Don't call java_getFileStructure on small files + +If a file is < 100 lines, just use `read_file` directly. File structure is most valuable for large files. + +### ❌ Don't call java_getCallHierarchy without reading the method first + +Understand what the method does before tracing its callers. + +--- + +## Fallback Strategy + +If a Java tool returns an error or empty result: +1. **jdtls not ready:** The Java language server may still be loading. Wait a moment and retry once. +2. **File not in project:** The file may not be part of a recognized Java project. Fall back to `read_file` + `grep_search`. +3. **Build errors (L1/L2):** If the project has unresolved build configuration errors, imports tool may return incomplete source classification. Structure tools should still work. +4. **Compilation errors (L3):** Most tools work fine. Type resolution near the error location may be imprecise. + +**For dependency/project info not covered by these tools:** +- Read `pom.xml` or `build.gradle` directly with `read_file` +- Use `grep_search` to find dependency declarations +- Use `java_getFileImports` to see what external libraries a file uses + +**General rule:** If a Java-specific tool fails, fall back to the universal tools (`read_file`, `grep_search`, `list_code_usages`). Don't retry more than once. diff --git a/docs/package-json-tools-snippet.jsonc b/docs/package-json-tools-snippet.jsonc new file mode 100644 index 00000000..edfe6216 --- /dev/null +++ b/docs/package-json-tools-snippet.jsonc @@ -0,0 +1,191 @@ +{ + "$comment": "This is the languageModelTools section to add to package.json contributes", + "languageModelTools": [ + { + "name": "java_getProjectContext", + "tags": ["java", "project", "dependencies"], + "modelDescription": "Get a high-level overview of a Java project: build tool (Maven/Gradle), Java version, source roots, and direct dependency list (GAV coordinates). Call this FIRST when starting work on a Java project. Returns ~100 tokens. Do NOT call repeatedly — project info doesn't change within a session.", + "displayName": "Java: Get Project Context", + "inputSchema": { + "type": "object", + "properties": { + "fileUri": { + "type": "string", + "description": "URI of any file in the Java project" + } + }, + "required": ["fileUri"] + } + }, + { + "name": "java_getFileImports", + "tags": ["java", "imports", "file"], + "modelDescription": "Get the classified import list of a Java file. Each import is tagged with its kind (class/interface/enum/annotation) and source (project/external/jdk). Returns ~80 tokens. Use this to understand what a file depends on WITHOUT reading the full source. Follow up with java_getClassDetail only for the 1-2 classes you actually need to understand — do NOT expand every import.", + "displayName": "Java: Get File Imports", + "inputSchema": { + "type": "object", + "properties": { + "fileUri": { + "type": "string", + "description": "URI of the Java file to analyze" + } + }, + "required": ["fileUri"] + } + }, + { + "name": "java_getClassDetail", + "tags": ["java", "class", "type", "api"], + "modelDescription": "Get signature-level details of a specific Java class: methods, fields, constructors, inheritance, annotations, and a one-sentence javadoc summary. Returns ~150 tokens for project sources, ~80 tokens for external dependencies. Use the fully qualified class name (e.g., 'com.example.model.Order'). Do NOT call this for well-known JDK classes like java.util.List or java.lang.String.", + "displayName": "Java: Get Class Detail", + "inputSchema": { + "type": "object", + "properties": { + "qualifiedName": { + "type": "string", + "description": "Fully qualified class name (e.g., 'com.example.model.Order')" + }, + "fileUri": { + "type": "string", + "description": "Optional: URI of a file in the same project (helps locate the class faster)" + } + }, + "required": ["qualifiedName"] + } + }, + { + "name": "java_getDependencyDetails", + "tags": ["java", "dependencies", "maven", "gradle"], + "modelDescription": "Get detailed dependency information: GAV coordinates, scope (compile/test/runtime), whether direct or transitive, and which direct dependency brings in each transitive one. ALWAYS use the query parameter to filter (e.g., query='gson' or query='spring'). Do NOT request all dependencies without filtering.", + "displayName": "Java: Get Dependency Details", + "inputSchema": { + "type": "object", + "properties": { + "fileUri": { + "type": "string", + "description": "URI of any file in the Java project" + }, + "query": { + "type": "string", + "description": "Filter dependencies by name (fuzzy match on groupId, artifactId, or fileName)" + } + }, + "required": ["fileUri"] + } + }, + { + "name": "java_getFileStructure", + "tags": ["java", "structure", "symbols", "outline"], + "modelDescription": "Get the structural outline of a Java file: classes, methods, fields, and their line ranges — WITHOUT reading the file content. Returns ~100 tokens. Use this BEFORE read_file to understand what's in a large file, then read_file on specific line ranges. Much more efficient than reading the entire file.", + "displayName": "Java: Get File Structure", + "inputSchema": { + "type": "object", + "properties": { + "uri": { + "type": "string", + "description": "URI of the Java file" + } + }, + "required": ["uri"] + } + }, + { + "name": "java_findSymbol", + "tags": ["java", "search", "symbol", "workspace"], + "modelDescription": "Search for classes, interfaces, methods, or fields by name across the entire Java workspace. Returns precise symbol definitions only (no comments, no string literals, no import statements). More accurate than grep_search for finding Java symbol definitions. Supports partial names and camelCase matching.", + "displayName": "Java: Find Symbol", + "inputSchema": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "Symbol name or partial name to search for (e.g., 'PaymentGateway', 'OrderServ')" + } + }, + "required": ["query"] + } + }, + { + "name": "java_getTypeAtPosition", + "tags": ["java", "type", "inference", "hover"], + "modelDescription": "Get the compiler-resolved type of any expression at a specific source position. Returns the EXACT type (~20 tokens), using the Java compiler's own type inference. Use this for: var declarations, lambda parameters, generic type arguments, chained method call return types. Do NOT use for obvious types like String literals or explicit type declarations.", + "displayName": "Java: Get Type at Position", + "inputSchema": { + "type": "object", + "properties": { + "uri": { + "type": "string", + "description": "URI of the Java file" + }, + "line": { + "type": "number", + "description": "0-based line number" + }, + "character": { + "type": "number", + "description": "0-based character offset" + } + }, + "required": ["uri", "line", "character"] + } + }, + { + "name": "java_getCallHierarchy", + "tags": ["java", "calls", "hierarchy", "impact"], + "modelDescription": "Find all callers of a method (incoming) or all methods it calls (outgoing). More precise than list_code_usages — returns only actual method CALLS, not imports, declarations, or comments. Use 'incoming' before modifying a method signature to find all callers. Use 'outgoing' to understand what a method depends on.", + "displayName": "Java: Get Call Hierarchy", + "inputSchema": { + "type": "object", + "properties": { + "uri": { + "type": "string", + "description": "URI of the Java file containing the method" + }, + "line": { + "type": "number", + "description": "0-based line number of the method name" + }, + "character": { + "type": "number", + "description": "0-based character offset of the method name" + }, + "direction": { + "type": "string", + "enum": ["incoming", "outgoing"], + "description": "incoming = who calls this method; outgoing = what this method calls" + } + }, + "required": ["uri", "line", "character", "direction"] + } + }, + { + "name": "java_getTypeHierarchy", + "tags": ["java", "inheritance", "hierarchy", "interface"], + "modelDescription": "Find all supertypes or subtypes of a class/interface. Catches things grep misses: indirect implementations, anonymous classes, lambda implementations of functional interfaces. Use 'subtypes' when modifying an interface to find ALL implementations. Use 'supertypes' to understand the inheritance chain.", + "displayName": "Java: Get Type Hierarchy", + "inputSchema": { + "type": "object", + "properties": { + "uri": { + "type": "string", + "description": "URI of the Java file containing the type" + }, + "line": { + "type": "number", + "description": "0-based line number of the type name" + }, + "character": { + "type": "number", + "description": "0-based character offset of the type name" + }, + "direction": { + "type": "string", + "enum": ["supertypes", "subtypes"], + "description": "supertypes = parents/interfaces; subtypes = implementations/subclasses" + } + }, + "required": ["uri", "line", "character", "direction"] + } + } + ] +} diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/plugin.xml b/jdtls.ext/com.microsoft.jdtls.ext.core/plugin.xml index a469ec92..4966a319 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/plugin.xml +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/plugin.xml @@ -12,6 +12,7 @@ + JDK_PREFIXES = new HashSet<>(); + static { + JDK_PREFIXES.add("java."); + JDK_PREFIXES.add("javax."); + JDK_PREFIXES.add("jdk."); + JDK_PREFIXES.add("sun."); + JDK_PREFIXES.add("com.sun."); + JDK_PREFIXES.add("org.xml."); + JDK_PREFIXES.add("org.w3c."); + JDK_PREFIXES.add("jakarta."); // Jakarta EE (post Java EE) + } + + /** + * Get the classified import list of a Java file. + * This is a lightweight AST-only operation — it reads import declarations + * without doing any type resolution (findType) or classpath resolution. + * + * Typical response time: < 5ms + * + * @param arguments List containing the file URI as the first element + * @param monitor Progress monitor for cancellation support + * @return FileImportsResult with classified imports + */ + public static FileImportsResult getFileImports(List arguments, IProgressMonitor monitor) { + FileImportsResult result = new FileImportsResult(); + result.imports = new ArrayList<>(); + result.staticImports = new ArrayList<>(); + + if (arguments == null || arguments.isEmpty()) { + result.error = "No arguments provided"; + return result; + } + + try { + String fileUri = (String) arguments.get(0); + if (fileUri == null || fileUri.trim().isEmpty()) { + result.error = "Invalid file URI"; + return result; + } + + // Resolve compilation unit from URI — this is fast, just a model lookup + java.net.URI uri = JDTUtils.toURI(fileUri); + ICompilationUnit compilationUnit = JDTUtils.resolveCompilationUnit(uri); + + if (compilationUnit == null || !compilationUnit.exists()) { + result.error = "File not found or not a Java file: " + fileUri; + return result; + } + + // Get relative file path + IJavaProject javaProject = compilationUnit.getJavaProject(); + result.file = compilationUnit.getPath().toString(); + + // Collect project source package names for classification + Set projectPackages = collectProjectPackages(javaProject); + + // Read import declarations — pure AST operation, no type resolution + IImportDeclaration[] imports = compilationUnit.getImports(); + if (imports == null || imports.length == 0) { + return result; // No imports, return empty (not an error) + } + + for (IImportDeclaration imp : imports) { + if (monitor.isCanceled()) { + break; + } + + String name = imp.getElementName(); + boolean isStatic = Flags.isStatic(imp.getFlags()); + boolean isOnDemand = name.endsWith(".*"); + + if (isStatic) { + StaticImportEntry entry = new StaticImportEntry(); + entry.name = name; + entry.memberKind = "unknown"; // Would need findType to know — skip + entry.source = classifyByPackageName(name, projectPackages); + result.staticImports.add(entry); + } else { + ImportEntry entry = new ImportEntry(); + entry.name = name; + entry.kind = isOnDemand ? "package" : "unknown"; // Would need findType to know — skip + entry.source = classifyByPackageName(name, projectPackages); + entry.artifact = null; // Would need classpath attributes — skip for now + result.imports.add(entry); + } + } + + return result; + + } catch (Exception e) { + JdtlsExtActivator.logException("Error in getFileImports", e); + result.error = "Exception: " + e.getMessage(); + return result; + } + } + + /** + * Classify an import by its package name prefix. + * This is a heuristic — no type resolution involved. + * + * @param qualifiedName the fully qualified name of the import + * @param projectPackages set of package names found in the project's source roots + * @return "jdk", "project", or "external" + */ + private static String classifyByPackageName(String qualifiedName, Set projectPackages) { + // Check JDK + for (String prefix : JDK_PREFIXES) { + if (qualifiedName.startsWith(prefix)) { + return "jdk"; + } + } + + // Check project packages + String packageName = getPackageName(qualifiedName); + if (packageName != null && projectPackages.contains(packageName)) { + return "project"; + } + + // Check if any project package is a prefix of this import + for (String projPkg : projectPackages) { + if (qualifiedName.startsWith(projPkg + ".")) { + return "project"; + } + } + + return "external"; + } + + /** + * Get the package name from a fully qualified name. + * e.g., "com.example.model.Order" → "com.example.model" + * "com.example.model.*" → "com.example.model" + */ + private static String getPackageName(String qualifiedName) { + if (qualifiedName == null) { + return null; + } + // Handle wildcard imports + if (qualifiedName.endsWith(".*")) { + return qualifiedName.substring(0, qualifiedName.length() - 2); + } + int lastDot = qualifiedName.lastIndexOf('.'); + if (lastDot > 0) { + return qualifiedName.substring(0, lastDot); + } + return null; + } + + /** + * Collect all package names that exist in the project's source roots. + * This uses getPackageFragmentRoots(K_SOURCE) which is fast — it reads + * the project model, not the filesystem. + */ + private static Set collectProjectPackages(IJavaProject javaProject) { + Set packages = new HashSet<>(); + if (javaProject == null) { + return packages; + } + + try { + IPackageFragmentRoot[] roots = javaProject.getPackageFragmentRoots(); + for (IPackageFragmentRoot root : roots) { + if (root.getKind() == IPackageFragmentRoot.K_SOURCE) { + org.eclipse.jdt.core.IJavaElement[] children = root.getChildren(); + for (org.eclipse.jdt.core.IJavaElement child : children) { + if (child instanceof org.eclipse.jdt.core.IPackageFragment) { + String pkgName = child.getElementName(); + if (pkgName != null && !pkgName.isEmpty()) { + packages.add(pkgName); + } + } + } + } + } + } catch (Exception e) { + // Non-critical — fall back to treating everything as external + JdtlsExtActivator.logException("Error collecting project packages", e); + } + + return packages; + } +} diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/CommandHandler.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/CommandHandler.java index 390dda27..49db346f 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/CommandHandler.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/CommandHandler.java @@ -41,6 +41,8 @@ public Object executeCommand(String commandId, List arguments, IProgress return ProjectCommand.getImportClassContent(arguments, monitor); case "java.project.getDependencies": return ProjectCommand.getProjectDependencies(arguments, monitor); + case "java.project.getFileImports": + return AiContextCommand.getFileImports(arguments, monitor); default: break; } diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/model/ClassDetailResult.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/model/ClassDetailResult.java new file mode 100644 index 00000000..1c050cdc --- /dev/null +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/model/ClassDetailResult.java @@ -0,0 +1,49 @@ +package com.microsoft.jdtls.ext.core.model; + +import java.util.List; + +/** + * L1: Detailed class information. + * AI calls this for specific classes it needs to understand, not for all imports. + */ +public class ClassDetailResult { + + public String qualifiedName; // "com.example.model.Order" + public String kind; // "class" | "interface" | "enum" | "annotation" + public String uri; // file URI (for project source) or jar URI + public String source; // "project" | "external" | "jdk" + public String artifact; // GAV for external: "com.google.code.gson:gson:2.10.1" + + public String signature; // "public class Order implements Serializable" + public String superClass; // "java.lang.Object" (null if Object) + public List interfaces; // ["java.io.Serializable"] + public List annotations; // ["@Entity", "@Table(name = \"orders\")"] + + public String javadocSummary; // First sentence only, null if none + + public List constructors; // ["Order()", "Order(String orderId, Customer customer)"] + public List methods; // ["String getOrderId()", "void setStatus(OrderStatus)"] + public List fields; // ["private String orderId", "private List items"] + + public int totalMethodCount; // actual total (methods list may be truncated) + public int totalFieldCount; // actual total + + public String error; // null if success + + /** + * Builder-style static factories for common cases + */ + public static ClassDetailResult notFound(String qualifiedName) { + ClassDetailResult r = new ClassDetailResult(); + r.qualifiedName = qualifiedName; + r.error = "Type not found: " + qualifiedName; + return r; + } + + public static ClassDetailResult error(String qualifiedName, String message) { + ClassDetailResult r = new ClassDetailResult(); + r.qualifiedName = qualifiedName; + r.error = message; + return r; + } +} diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/model/DependencyDetailsResult.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/model/DependencyDetailsResult.java new file mode 100644 index 00000000..dc373de8 --- /dev/null +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/model/DependencyDetailsResult.java @@ -0,0 +1,37 @@ +package com.microsoft.jdtls.ext.core.model; + +import java.util.List; + +/** + * L1: Detailed dependency information with query filtering. + * AI calls this when it needs to investigate specific dependencies. + */ +public class DependencyDetailsResult { + + public List dependencies; + public String error; // null if success + + public static class DependencyEntry { + public String groupId; // "com.google.code.gson" + public String artifactId; // "gson" + public String version; // "2.10.1" + public String scope; // "compile" | "test" | "runtime" | "provided" | "system" + public boolean isDirect; // true = declared in pom.xml/build.gradle + public String broughtBy; // for transitive: "com.google.guava:guava:32.1.3-jre" + public String jarFileName; // "gson-2.10.1.jar" + + public DependencyEntry() {} + + public DependencyEntry(String groupId, String artifactId, String version, + String scope, boolean isDirect, String broughtBy, + String jarFileName) { + this.groupId = groupId; + this.artifactId = artifactId; + this.version = version; + this.scope = scope; + this.isDirect = isDirect; + this.broughtBy = broughtBy; + this.jarFileName = jarFileName; + } + } +} diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/model/FileImportsResult.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/model/FileImportsResult.java new file mode 100644 index 00000000..0f490c7f --- /dev/null +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/model/FileImportsResult.java @@ -0,0 +1,45 @@ +package com.microsoft.jdtls.ext.core.model; + +import java.util.List; + +/** + * L0: Import list for a Java file. + * Returns classified imports without expanding class details. + */ +public class FileImportsResult { + + public String file; // relative file path + public List imports; + public List staticImports; + public String error; // null if success + + public static class ImportEntry { + public String name; // fully qualified name: "com.example.model.Order" + public String kind; // "class" | "interface" | "enum" | "annotation" | "unknown" + public String source; // "project" | "external" | "jdk" + public String artifact; // only for "external": "spring-context", null for others + + public ImportEntry() {} + + public ImportEntry(String name, String kind, String source, String artifact) { + this.name = name; + this.kind = kind; + this.source = source; + this.artifact = artifact; + } + } + + public static class StaticImportEntry { + public String name; // "org.junit.Assert.assertEquals" + public String memberKind; // "method" | "field" | "unknown" + public String source; // "project" | "external" | "jdk" + + public StaticImportEntry() {} + + public StaticImportEntry(String name, String memberKind, String source) { + this.name = name; + this.memberKind = memberKind; + this.source = source; + } + } +} diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/model/ProjectContextResult.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/model/ProjectContextResult.java new file mode 100644 index 00000000..113c0c9f --- /dev/null +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/model/ProjectContextResult.java @@ -0,0 +1,42 @@ +package com.microsoft.jdtls.ext.core.model; + +/** + * Structured result models for Java Context Tools. + * These models are designed for AI consumption — small, structured, layered. + * + * Design principles: + * 1. Each result should serialize to < 200 tokens of JSON + * 2. Use structured fields instead of freeform text + * 3. Only include information the AI actually needs at this granularity level + */ + +import java.util.List; + +/** + * L0: Project-level context overview. + * First thing AI should request when entering a Java project. + */ +public class ProjectContextResult { + + public ProjectMeta project; + public DependencySummary dependencies; + public List projectReferences; + public String error; // null if success + + public static class ProjectMeta { + public String name; + public String buildTool; // "Maven" | "Gradle" | "Unknown" + public String javaVersion; // compiler compliance level + public String sourceLevel; + public String targetLevel; + public List sourceRoots; // relative paths: ["src/main/java", "src/test/java"] + public String moduleName; // Java module name, null if not modular + } + + public static class DependencySummary { + public int total; + public int directCount; + public int transitiveCount; + public List direct; // GAV strings: ["group:artifact:version", ...] + } +} diff --git a/lsp-ai-tools.md b/lsp-ai-tools.md new file mode 100644 index 00000000..68c9a3f3 --- /dev/null +++ b/lsp-ai-tools.md @@ -0,0 +1,752 @@ +# LSP 能力对 AI 辅助 Java 开发的价值分析与工具设计建议 + +## 一、背景:Java 的特殊性 + +### Java 与其他语言的依赖管理对比 + +| 语言 | 依赖声明 | 锁文件/解析结果 | AI 能否静态获取完整依赖信息 | +|------|---------|----------------|------------------------| +| **Java (Maven)** | `pom.xml` | 无标准锁文件 | ❌ 传递依赖需构建工具解析 | +| **Java (Gradle)** | `build.gradle`(代码) | `gradle.lockfile`(可选,少人用) | ❌ DSL 是代码,无法静态解析 | +| **.NET** | `.csproj` | `project.assets.json`(自动生成) | ✅ 读 JSON 文件即可 | +| **Go** | `go.mod` | `go.sum` | ✅ 确定性解析,读文件即可 | +| **Rust** | `Cargo.toml` | `Cargo.lock` | ✅ 读文件即可 | +| **Node.js** | `package.json` | `package-lock.json` / `yarn.lock` | ✅ 读文件即可 | + +**Java 是主流语言中唯一没有标准化、人类/机器可读的依赖解析结果落盘机制的。** 这使得 AI 处理 Java 项目时比其他语言更容易出错。 + +### Java 让 AI 更容易出错的语言特征 + +| Java 特征 | 为什么 AI 更难 | +|-----------|---------------| +| 强类型 + 泛型擦除 | 源码中的类型信息不完整,真实类型需要编译器推断 | +| 依赖管理是间接的 | import 和 Maven artifact 是两层映射,不像 Python 的 `import requests` 直接对应 pip 包名 | +| 构建系统是"活"的 | Gradle DSL 是代码,Maven 有继承/profile,不能静态读取 | +| 模块化程度高 | 企业项目动辄几十个模块,类的来源不直观 | +| 向后兼容重但有断裂点 | javax → jakarta、Java module system、API 废弃 | + +--- + +## 二、jdtls delegateCommandHandler 扩展命令评估 + +### 5 个被识别为"不可替代"的命令 + +| 命令 | 来源 | 不可替代原因 | +|------|------|-------------| +| `resolveClasspath` | java-debug | 完整传递依赖图,含版本仲裁、profile 激活、Gradle 动态依赖 | +| `isOnClasspath` | java-debug | 运行时 classpath 实际状态,区分"声明"与"实际生效" | +| `resolveElementAtSelection` | java-debug | 编译器级语义信息:泛型推断、重载决议、类型绑定 | +| `getDependencies` | java-dependency | 已解析的依赖树 + Java 版本 + 模块信息 + JAR 实际路径 | +| `jacoco.getCoverageDetail` | java-test | 运行时字节码插桩产生的行级覆盖率数据 | + +### 鸡生蛋问题:这些命令的可靠性悖论 + +这些命令有一个根本性问题:**AI 最需要它们的时候,恰恰是它们最不可靠的时候。** + +| 项目状态 | jdtls 状态 | 命令可用性 | AI 对命令的需求 | +|---------|-----------|-----------|---------------| +| 正常编译通过 | 完全工作 | ✅ 全部可用 | 低——项目没问题,AI 读源码基本够用 | +| 源码有编译错误但依赖正确 | 部分工作 | ⚠️ 依赖命令可用,类型解析部分受影响 | 中 | +| pom.xml/build.gradle 有语法错误 | M2E/Buildship 导入失败 | ❌ 依赖命令全部失效 | 高——但恰好用不了 | +| 依赖下载失败(网络/仓库问题) | 部分导入 | ⚠️ 返回不完整的依赖树 | 高——返回的信息可能误导 AI | +| Java 版本升级中间状态 | 取决于具体阶段 | ⚠️ 不确定 | 最高——但最不可靠 | +| 新 clone 的项目未导入 | 未初始化 | ❌ 全部失效 | 高 | + +**修正后的价值评估:** + +| 命令 | 修正前价值 | 考虑可靠性后 | 说明 | +|------|----------|------------|------| +| `getDependencies` / `resolveClasspath` | ⭐⭐⭐ | ⭐⭐ | 项目正常时有用,但正常时需求低 | +| `isOnClasspath` | ⭐⭐⭐ | ⭐ | 返回 false 时无法区分"不可用"和"jdtls 不知道" | +| `resolveElementAtSelection` | ⭐⭐⭐ | ⭐⭐ | 编译错误多时类型绑定不可靠 | +| `jacoco.getCoverageDetail` | ⭐⭐⭐ | ⭐⭐⭐ | 离线数据,不受 jdtls 实时状态影响——唯一例外 | + +**真正的价值区间:** 项目处于"亚健康"状态时(核心结构正确,依赖大部分已解析,但有局部的编译错误或依赖问题),jdtls 命令能提供大部分正确的上下文信息。这大约占所有场景的 40-60%。 + +--- + +## 三、AI 处理 Java 项目时天然吃力的任务类型 + +### 类型 1:跨依赖边界的修改 + +修改涉及的代码不只在项目源码中,还取决于依赖库提供的 API、类型、行为。 + +- 升级框架版本后适配 API 变更 +- 替换一个第三方库为另一个库(如 Gson → Jackson) +- 修复因依赖版本冲突导致的运行时错误 + +**AI 失败模式:** AI 对"项目边界之外"是盲的——不知道 classpath 上的依赖到底提供了哪些类和方法签名。 + +### 类型 2:类型推断密集的代码修改 + +修改大量依赖编译器类型推断能力的代码,源码文本不包含完整类型信息。 + +- 重构使用了 `var`、Lambda、Stream 链式调用的代码 +- 修改泛型类/方法的签名,需要判断所有调用点是否兼容 +- 在有多个重载方法的类中添加新方法,需要确保不会导致调用歧义 + +**AI 失败模式:** AI 看到 `var list = getItems()` 但不知道 `list` 是 `List` 还是 `List`。 + +### 类型 3:构建配置与源码的联动修改 + +修改不仅涉及 `.java` 文件,还涉及构建配置(pom.xml / build.gradle),且两者必须保持一致。 + +- 添加新功能需要引入新依赖 +- 多模块项目中拆分/合并模块 +- 修改编译器参数 + +**AI 失败模式:** AI 可能添加了代码中的 import 但忘记加依赖,或者加了依赖但版本与已有版本冲突。 + +### 类型 4:编译错误的诊断与修复 + +项目处于编译失败状态,AI 需要从错误信息定位到根因并修复。 + +- 拉取最新代码后编译不过 +- 升级 JDK 版本后编译失败 +- merge 之后的冲突解决导致类型不匹配 + +**AI 失败模式:** 编译错误信息指向"症状",根因可能在传递依赖链里。**编译失败时 `mvn dependency:tree` 可能也跑不出来**,但 jdtls 的 `getDependencies` 基于 M2E 增量解析可能仍可用。 + +### 类型 5:多模块项目中的跨模块修改 + +修改需要横跨多个 Maven/Gradle 模块,涉及模块间的依赖关系和 API 契约。 + +- 修改公共模块中的接口,需要同步更新所有依赖模块 +- 将一个类从 module-A 移动到 module-B +- 判断某个功能应该放在哪个模块中 + +**AI 失败模式:** AI 不清楚模块间的依赖方向,把一个类移到错误的模块会导致循环依赖。 + +### 类型 6:运行时行为相关的代码修改 + +修改的正确性不仅取决于编译通过,还取决于运行时行为。 + +- 修改了序列化/反序列化逻辑 +- 修改了 Spring Bean 的注入方式 +- 修改了异常处理路径 + +**AI 失败模式:** 代码改完编译通过,但运行时行为不对。 + +### 共同内核 + +**这 6 类场景的共同点:代码修改的正确性取决于源码文本之外的信息。** + +- 类型 1-3:正确性取决于「构建系统的状态」 +- 类型 2:正确性取决于「编译器的推断结果」 +- 类型 4-5:诊断的准确性取决于「项目的结构化元信息」 +- 类型 6:正确性取决于「运行时状态」 + +--- + +## 四、LSP 标准能力 vs delegateCommandHandler 扩展能力 + +### 已有基线:`list_code_usages` 的覆盖范围 + +VS Code 当前已向 AI 暴露了 `list_code_usages` 工具,其底层基于 LSP 的 `textDocument/references` + `textDocument/definition` + `textDocument/implementation`。在评估新 LSP tool 前,需要明确它已经覆盖了什么、还缺什么: + +**`list_code_usages` 已覆盖的能力:** + +| 能力 | 底层 LSP 请求 | 说明 | +|------|-------------|------| +| 引用查找 | `textDocument/references` | 找到所有使用某符号的位置 | +| 定义跳转 | `textDocument/definition` | 定位符号定义 | +| 实现查找 | `textDocument/implementation` | 找接口的实现类 | + +**新方向与 `list_code_usages` 的关系:** + +| 新方向 | 与 `list_code_usages` 的关系 | 增量价值 | +|--------|---------------------------|--------| +| Document Symbol | 完全正交——usages 找"谁用了这个符号",Symbol 回答"这个文件里有什么" | **高**——省去 read_file 全文 | +| Type Query(Hover 后处理) | 完全正交——usages 不提供类型信息 | **最高**——解决 `var`/泛型/Lambda 的类型盲区 | +| Workspace Symbol | 部分重叠——usages 需已知符号名,Workspace Symbol 支持模糊搜索 | **中高**——解决"大概知道叫什么但不确定"的场景 | +| Call Hierarchy | 是 usages 的升级版——usages 返回所有引用(含 import、注释、声明),Call Hierarchy 只返回调用关系,且支持方向性 | **高**——更精确的影响分析 | +| Type Hierarchy | usages 找 `implements X` 的文本,Type Hierarchy 包含间接继承、匿名类、Lambda | **高**——完整的继承图 | + +**定位建议:** 新 LSP tool 不是从零开始,而是在 `list_code_usages` 已验证的路径上填补缺口。Call Hierarchy 可以定位为「`list_code_usages` 的精确版」——usages 返回的结果中 import、声明、注释、实际调用混在一起,Call Hierarchy 直接给出纯调用关系,省掉筛选 token。 + +### 本质区别 + +| | delegateCommandHandler (resolveClasspath 等) | 标准 LSP 能力 (hover/hierarchy 等) | +|--|---------------------------------------------|----------------------------------| +| Java 特有 | 是 | 否——任何有 LSP 的语言都适用 | +| 鸡生蛋问题 | 严重——依赖解析失败就全挂 | 轻微——只要文件能解析就能工作 | +| 解决什么问题 | Java 依赖管理的设计缺陷 | AI 的 context 窗口和推理精度限制 | +| 可替代性 | 部分不可替代 | AI 理论上可以自己做,但成本高出几个数量级 | + +**标准 LSP 能力的核心价值不是"提供 AI 做不到的能力",而是"用毫秒级查询替代百万 token 推理"。** + +### AI 当前的代码导航方式 + +``` +当前 AI 处理 Java 代码的工具栈: + + 文本搜索(grep/semantic_search) ████████████████ ← 主要依赖 + LSP 引用查找(list_code_usages) ████ ← 有但用得少 + 终端命令(mvn/javac) ████ ← 兜底 + jdtls 扩展命令(delegateCommand) ☐ ← 基本未暴露 + LSP 高级能力(hover/hierarchy) ☐ ← 未暴露 + +理想的 AI 处理 Java 代码的工具栈: + + LSP 核心能力(引用/定义/类型/层次)████████████████ ← 主力 + jdtls 扩展命令(依赖/classpath) ████████ ← 项目级理解 + 文本搜索(grep) ████ ← 快速初筛 + 终端命令 ██ ← 降级兜底 +``` + +--- + +## 五、LSP Tool 设计建议 + +### 方向 1:文件结构摘要(P0 — 最高优先级) + +**对应 LSP 请求:** `textDocument/documentSymbol` + +**当前痛点:** + +AI 理解一个 Java 文件结构,需要 `read_file` 读全部内容(~2000 tokens),然后用推理能力提取结构。 + +**LSP 替代效果:** + +``` +一次 documentSymbol 请求返回: + class OrderService + ├── field: orderRepo (OrderRepository) + ├── method: createOrder(req: CreateOrderRequest): Order [line 45-80] + ├── method: cancelOrder(orderId: String): void [line 82-95] + └── method: getOrders(userId: String): List [line 97-120] + +消耗:~100 tokens(结构化数据),节省 90%+ +``` + +**调研要点:** +- `DocumentSymbol[]`(树形)vs `SymbolInformation[]`(扁平),jdtls 返回哪种 +- 返回信息是否包含参数类型、返回类型、泛型参数、注解 +- 跨语言一致性:TypeScript、Python、Go 的 Language Server 返回格式是否统一 +- 性能:大文件(1000+ 行)上的响应延迟 + +**预期收益:** 文件结构理解的 token 消耗降低 **90%+**。AI 先看摘要,再按需读具体方法。 + +--- + +### 方向 2:类型查询(P0.5 — 高优先级,需封装) + +**对应 LSP 请求:** `textDocument/hover`(需后处理) + +#### UI 层 vs 协议层的辨析 + +`textDocument/hover` 需要区分两个层面: + +| 层面 | 含义 | AI 是否需要 | +|------|------|------------| +| **UI 层**——鼠标悬停弹出 tooltip | 人看的浮窗 | 不需要 | +| **协议层**——LSP 请求 | 给定 (file, line, col) 返回该位置的类型/文档信息 | **需要数据,不需要 UI** | + +`textDocument/hover` 本质上是一个 **位置→类型信息** 的查询 API,AI 可以纯程序化调用,不需要任何 UI 交互。但关键问题是:**hover 返回的内容是为人设计的,不是为 AI 设计的。** + +jdtls 的 `textDocument/hover` 返回 **MarkupContent(Markdown)**,例如: + +```markdown +**String** java.lang.String + +The `String` class represents character strings. All string literals in Java programs... +(后面跟几百字的 Javadoc) +``` + +对 AI 来说: +- **有用的**:`String` 这个类型签名(~5 tokens) +- **没用的**:Javadoc 内容(~200 tokens 的噪音) + +#### 从 P0 降级到 P0.5 的原因 + +| 因素 | 说明 | +|------|------| +| 返回格式非结构化 | Markdown 文本,需要正则提取类型签名 | +| 夹带大量噪音 | Javadoc 内容对 AI 是纯 token 浪费 | +| 跨语言不一致 | 不同 Language Server 的 Markdown 格式不统一,解析逻辑脆弱 | +| 需要封装层 | 必须在 tool 层做后处理,命名为 `get_type_at_position` 而非 `hover` | + +**结论:LSP 标准协议里没有一个"干净的、为程序消费设计的类型查询 API"。Hover 是最接近的,但需要额外封装。** 能力本身依然关键(解决 var/泛型/Lambda 的类型盲区),但实现需要更多工作。 + +#### 类型查询的替代方案对比 + +| 方案 | 原理 | 优点 | 缺点 | +|------|------|------|------| +| Hover + 后处理 | 调用 `textDocument/hover`,正则提取首个 code block 中的类型签名 | 现成 API,jdtls 实现成熟 | 返回格式不标准,解析逻辑脆弱 | +| `resolveElementAtSelection`(jdtls 扩展) | 返回编译器级语义信息 | 比 hover 更结构化,为程序消费设计 | 有鸡生蛋问题 | +| Semantic Tokens | 返回每个 token 的类型分类 | 无噪音 | **不返回具体类型**(只知"是变量",不知"是 List\") | + +**推荐方案:Hover + 后处理封装。** 在 tool 层拦截 hover 返回,只提取类型签名部分。 + +**当前痛点:** + +```java +var result = service.getOrders(userId).stream() + .filter(o -> o.getStatus() == Status.ACTIVE) + .collect(Collectors.groupingBy(Order::getCategory)); +``` + +AI 要知道 `result` 的类型,需要跨多个文件追踪方法返回类型(~1000+ tokens + 可能推断错误)。 + +**LSP 替代效果(后处理后):** + +``` +get_type_at_position(file, line, column of "result") + → "Map>" + +消耗:~20 tokens,准确率 100% +``` + +**调研要点:** +- `textDocument/hover` 返回内容格式(MarkupContent / 纯文本 / 结构化类型信息) +- jdtls 的 hover 在不同位置(变量、方法调用、字段访问、Lambda 参数)返回的 Markdown 格式差异 +- 类型签名提取的正则是否能覆盖 jdtls 的主要返回模式 +- 是否能用于 JAR 中的类(依赖库的类型信息) +- 大型项目上 hover 请求的 P99 延迟 + +**预期收益:** 类型推断场景的 token 消耗降低 **95%**,准确率从"AI 推理猜测"提升到"编译器精确结果"。 + +--- + +### 方向 3:全局符号搜索(P0 — 最高优先级) + +**对应 LSP 请求:** `workspace/symbol` + +**当前痛点:** + +``` +AI 想找 "PaymentGateway" 类: + grep_search("PaymentGateway") → 命中 30 个结果(含注释、字符串、import 语句) + → AI 逐个识别哪个是定义 → 消耗 token + 时间 +``` + +**LSP 替代效果:** + +``` +workspaceSymbol("PaymentGateway") + → [ + { name: "PaymentGateway", kind: Interface, location: "src/.../PaymentGateway.java:15" }, + { name: "PaymentGatewayImpl", kind: Class, location: "src/.../PaymentGatewayImpl.java:8" } + ] + +精确命中,无噪音 +``` + +**调研要点:** +- 支持的查询模式(前缀匹配?模糊匹配?驼峰缩写匹配如 `PG` → `PaymentGateway`?) +- jdtls 是否能搜索到 JAR 中的类 +- 返回结果的数量限制和分页机制 +- 与 VS Code 的 `Go to Symbol in Workspace`(Ctrl+T)底层是否一致 + +**预期收益:** 符号定位从"grep 30 个结果 + AI 筛选"变为"直接命中精确结果"。 + +--- + +### 方向 4:调用链查询(P1) + +**对应 LSP 请求:** `textDocument/prepareCallHierarchy` + `callHierarchy/incomingCalls` / `outgoingCalls` + +**当前痛点:** + +AI 修改方法前需要知道调用方,`grep` 或 `list_code_usages` 返回所有引用(声明、import、调用混在一起),AI 需自行判断哪些是真正的调用。 + +**LSP 替代效果:** + +``` +incomingCalls("UserService.deleteUser") + → [ + { from: "UserController.handleDelete()", location: "UserController.java:45" }, + { from: "AdminJob.cleanup()", location: "AdminJob.java:78" } + ] +只有调用关系,没有噪音 +``` + +**调研要点:** +- 请求流程:先 `prepareCallHierarchy` 再 `incomingCalls` / `outgoingCalls` +- jdtls 的实现完整度(是否支持跨模块、多态调用) +- 调用深度——是否支持递归展开(A→B→C 的完整调用链) +- 大型项目上查询被广泛调用方法时的延迟 + +**预期收益:** AI 做修改影响分析时从"grep + 推理筛选"变为"精确的调用关系图"。 + +--- + +### 方向 5:类型继承层次(P1) + +**对应 LSP 请求:** `textDocument/prepareTypeHierarchy` + `typeHierarchy/supertypes` / `subtypes` + +**当前痛点:** + +``` +grep "implements Processor" → 只找到显式实现,漏掉: + - 间接实现(class A extends B,B implements Processor) + - 匿名类实现 + - Lambda 实现 + - 方法引用实现 +``` + +**LSP 替代效果:** + +``` +subtypes("Processor") + → [StringProcessor, NumberProcessor, (anonymous class at App.java:30), (lambda at App.java:35)] +``` + +**调研要点:** +- jdtls 是否能识别 Lambda 和匿名类作为接口实现 +- 是否支持递归展开(查 C 的 subtypes 时返回间接子类 A) +- supertypes 方向是否包含接口和类继承链 + +**预期收益:** 接口/抽象类修改时的影响分析精确度从 ~70% 提升到 ~100%。 + +--- + +### 不建议优先调研的方向 + +| 方向 | 为什么优先级低 | +|------|-------------| +| Semantic Tokens | 信息量有限,AI 从上下文基本能推断标识符角色 | +| Code Actions / Refactoring | 实现复杂(需要 apply workspace edit),AI 直接编辑文件目前够用 | +| Rename | 实现复杂,AI 用 `list_code_usages` + 手动替换可以凑合 | +| Completion | AI 自己的补全能力已经很强,LSP 补全反而可能不如 LLM | +| Signature Help | hover 已经覆盖了大部分需求 | + +--- + +## 六、投入产出比总览 + +### 修正后的优先级排序 + +考虑 UI/API 适配度和已有 `list_code_usages` 基线后的修正评估: + +| 优先级 | LSP 能力 | AI 获得的能力 | Token 节省 | 实现复杂度 | 与已有工具关系 | +|--------|---------|-------------|-----------|-----------|---------------| +| **P0** | Document Symbol(文件结构摘要) | 快速理解文件结构,节省 context | ~90% | 低——单次请求,返回天然结构化 | 完全正交,全新能力 | +| **P0** | Workspace Symbol(全局符号搜索) | 精确符号定位 | ~80% | 低——单次请求,返回天然结构化 | 精确替代 grep | +| **P0.5** | Type Query(基于 Hover 后处理) | 精确类型推断 | ~95% | 中——需从 Markdown 提取类型签名 | 完全正交,全新能力 | +| **P1** | Call Hierarchy(调用链) | 修改影响分析 | ~70% | 低——两步请求 | `list_code_usages` 的精确版 | +| **P1** | Type Hierarchy(继承层次) | 接口实现分析 | ~60% | 低——两步请求 | 覆盖 usages 的间接继承盲区 | + +### 变更说明 + +- **Document Symbol 和 Workspace Symbol 保持 P0**:返回数据天然结构化,零 UI 依赖,实现最简单 +- **Hover(类型查询)从 P0 降为 P0.5**:能力关键但返回格式为人设计(Markdown),需要额外封装后处理层 +- **Call Hierarchy 定位调整**:明确为 `list_code_usages` 的精确升级版,而非全新能力 + +--- + +## 七、调研实施路径 + +### 第一步:验证可行性(1-2 天) + +- 在 VS Code 扩展 API 中直接调用这些 LSP 请求 +- 用一个中等规模 Java 项目测试返回数据格式和延迟 +- 确认 jdtls 对每个请求的实现完整度 + +### 第二步:量化收益(2-3 天) + +- 找 10 个典型的 AI 辅助编码任务 +- 记录当前方式消耗的 token 数和耗时 +- 模拟用 LSP tool 后消耗的 token 数和耗时 +- 计算实际节省比例 + +### 第三步:确定优先级(1 天) + +- 按 (收益 × 使用频率) / 实现成本 排序 +- 选 2-3 个最高 ROI 的实现 + +### 核心判断标准 + +**不是"这个 LSP 能力多强大",而是"AI 当前在这个环节花了多少 token / 时间,LSP 能省掉多少"。** + +--- + +## 八、关于 Benchmark 的建议 + +### 正确的出发点 + +``` +❌ 错误:jdtls/LSP 有什么能力 → 构造场景证明它有用 +✅ 正确:开发者日常做什么 → AI 在哪里失败/低效 → 失败原因是否是缺少编译器状态/LSP 信息 +``` + +### 适合的 Benchmark 类型 + +msbench 的 Java Upgrade 类任务是一个很好的切入点——升级后依赖未同步的 case 天然暴露了 AI 在依赖理解方面的不足。这类场景中: + +- 项目往往处于"编译不过"的中间状态 +- `mvn dependency:tree` 可能也跑不出来 +- 但 jdtls/M2E 的增量解析在部分情况下仍然可用 + +### 更 General 的 Benchmark 定位 + +不针对特定命令设计题目,而是定义**开发者日常任务类型**,在真实开源项目上执行,观察加入 LSP 工具后的改善: + +| 度量指标 | 含义 | +|---------|------| +| 编译通过率 | 生成/修改的代码能否直接编译通过 | +| Token 消耗 | 完成同一任务消耗的 token 数 | +| 轮次 | 完成任务需要的交互轮数 | +| 首次正确率 | 第一次输出就正确的比例 | +| 人工修正量 | 需要人工修改多少行才能达到可用状态 | + +### 本质结论 + +LSP 能力对 AI 的价值是**效率工具**而非**能力扩展**。它不让 AI 做到之前做不到的事,而是让 AI 用毫秒级查询替代百万 token 推理,把已经能做的事做得更快、更省、更准。对于中大型 Java 项目,这个效率差距足以决定 AI 辅助的实用性。 + +--- + +## 九、实现架构:如何将 LSP 能力暴露给 AI + +### 三种实现路径对比 + +| 路径 | 机制 | AI 如何调用 | 适用场景 | +|------|------|-----------|----------| +| **A. `LanguageModelTool`** | 扩展注册 `vscode.lm.registerTool()` | Copilot Chat 自动发现并调用 | **推荐——最正式的方式** | +| **B. VS Code 内置命令** | `vscode.commands.executeCommand()` | AI 通过已有 tool(如 `run_vscode_command`)间接调用 | 快速验证 / PoC | +| **C. MCP Server** | 独立进程,通过 MCP 协议通信 | 任何支持 MCP 的 AI 客户端 | 跨编辑器 / 跨 AI 客户端 | + +### 推荐路径:LanguageModelTool + +这是 VS Code 为「给 AI 提供工具」专门设计的 API,Copilot Chat 原生支持工具发现和调用。 + +#### 架构图 + +``` +┌─────────────────────────────────────────┐ +│ Copilot Chat (LLM) │ +│ ↓ 调用 tool │ +│ LanguageModelTool 接口 │ +│ ↓ │ +│ 你的 VS Code Extension │ +│ ↓ 内部调用 VS Code API │ +│ vscode.commands.executeCommand(...) │ +│ ↓ │ +│ jdtls (Language Server) │ +└─────────────────────────────────────────┘ +``` + +#### VS Code 内置命令与 LSP 请求的映射 + +这些命令已存在于 VS Code 中,不需要自己实现 LSP 客户端: + +```typescript +// 文件级 +'vscode.executeDocumentSymbolProvider' // → textDocument/documentSymbol +'vscode.executeHoverProvider' // → textDocument/hover + +// 工作区级 +'vscode.executeWorkspaceSymbolProvider' // → workspace/symbol + +// 导航(list_code_usages 已覆盖) +'vscode.executeDefinitionProvider' // → textDocument/definition +'vscode.executeReferenceProvider' // → textDocument/references +'vscode.executeImplementationProvider' // → textDocument/implementation + +// 层次结构 +'vscode.prepareCallHierarchy' // → textDocument/prepareCallHierarchy +'vscode.provideIncomingCalls' // → callHierarchy/incomingCalls +'vscode.provideOutgoingCalls' // → callHierarchy/outgoingCalls +'vscode.prepareTypeHierarchy' // → textDocument/prepareTypeHierarchy +'vscode.provideSupertypes' // → typeHierarchy/supertypes +'vscode.provideSubtypes' // → typeHierarchy/subtypes +``` + +**核心认知:实现层几乎没有复杂度——VS Code 已经把 LSP 封装好了,只需要写一个薄薄的 LanguageModelTool 适配层。** + +### 实现示例 + +#### Tool 1:文件结构摘要(P0) + +```typescript +import * as vscode from 'vscode'; + +vscode.lm.registerTool('java_fileStructure', new FileStructureTool()); + +class FileStructureTool implements vscode.LanguageModelTool<{ uri: string }> { + + async invoke( + options: vscode.LanguageModelToolInvocationOptions<{ uri: string }>, + token: vscode.CancellationToken + ): Promise { + + const uri = vscode.Uri.parse(options.input.uri); + + // 调用 VS Code 内置命令 → 内部转发给 jdtls 的 textDocument/documentSymbol + const symbols = await vscode.commands.executeCommand( + 'vscode.executeDocumentSymbolProvider', uri + ); + + // 格式化为 AI 友好的结构化文本 + const summary = this.formatSymbols(symbols, 0); + + return new vscode.LanguageModelToolResult([ + new vscode.LanguageModelTextPart(summary) + ]); + } + + private formatSymbols(symbols: vscode.DocumentSymbol[], indent: number): string { + return symbols.map(s => { + const prefix = ' '.repeat(indent); + const kind = vscode.SymbolKind[s.kind]; // Class, Method, Field... + const range = `[L${s.range.start.line + 1}-${s.range.end.line + 1}]`; + let line = `${prefix}${kind}: ${s.name} ${range}`; + + if (s.children?.length) { + line += '\n' + this.formatSymbols(s.children, indent + 1); + } + return line; + }).join('\n'); + } +} +``` + +#### Tool 2:全局符号搜索(P0) + +```typescript +class WorkspaceSymbolTool implements vscode.LanguageModelTool<{ query: string }> { + + async invoke( + options: vscode.LanguageModelToolInvocationOptions<{ query: string }>, + token: vscode.CancellationToken + ): Promise { + + // 调用 VS Code 内置命令 → workspace/symbol + const symbols = await vscode.commands.executeCommand( + 'vscode.executeWorkspaceSymbolProvider', options.input.query + ); + + const results = symbols?.map(s => ({ + name: s.name, + kind: vscode.SymbolKind[s.kind], + location: `${vscode.workspace.asRelativePath(s.location.uri)}:${s.location.range.start.line + 1}` + })); + + return new vscode.LanguageModelToolResult([ + new vscode.LanguageModelTextPart(JSON.stringify(results, null, 2)) + ]); + } +} +``` + +#### Tool 3:类型查询(P0.5)——Hover 后处理封装 + +```typescript +class TypeQueryTool implements vscode.LanguageModelTool<{ uri: string; line: number; character: number }> { + + async invoke( + options: vscode.LanguageModelToolInvocationOptions<{ uri: string; line: number; character: number }>, + token: vscode.CancellationToken + ): Promise { + + const uri = vscode.Uri.parse(options.input.uri); + const position = new vscode.Position(options.input.line, options.input.character); + + // 调用 hover → textDocument/hover + const hovers = await vscode.commands.executeCommand( + 'vscode.executeHoverProvider', uri, position + ); + + // 关键后处理:从 Markdown 中提取类型签名,去掉 Javadoc 噪音 + const typeInfo = this.extractTypeSignature(hovers); + + return new vscode.LanguageModelToolResult([ + new vscode.LanguageModelTextPart(typeInfo) + ]); + } + + private extractTypeSignature(hovers: vscode.Hover[] | undefined): string { + if (!hovers?.length) return 'No type information available'; + + for (const hover of hovers) { + for (const content of hover.contents) { + if (content instanceof vscode.MarkdownString) { + // jdtls 的 hover 返回格式通常是: + // ```java\nType signature\n```\n\nJavadoc... + // 只提取第一个 code block + const match = content.value.match(/```java\n([\s\S]*?)```/); + if (match) return match[1].trim(); + } + } + } + return 'Type extraction failed'; + } +} +``` + +### Tool 元数据声明(package.json) + +`modelDescription` 是最重要的字段——LLM 根据这个描述决定什么时候调用 tool: + +```jsonc +{ + "contributes": { + "languageModelTools": [ + { + "name": "java_fileStructure", + "displayName": "Java File Structure", + "modelDescription": "Get the structure (classes, methods, fields) of a Java file without reading its full content. Returns a tree of symbols with their types, names and line ranges. Use this before read_file to understand a file's organization.", + "inputSchema": { + "type": "object", + "properties": { + "uri": { "type": "string", "description": "The file URI to get structure for" } + }, + "required": ["uri"] + } + }, + { + "name": "java_findSymbol", + "displayName": "Find Symbol in Workspace", + "modelDescription": "Search for a class, interface, method or field by name across the entire workspace. Returns exact matches with kind (Class/Interface/Method) and file location. More precise than grep - no noise from comments or imports.", + "inputSchema": { + "type": "object", + "properties": { + "query": { "type": "string", "description": "Symbol name or partial name to search for" } + }, + "required": ["query"] + } + }, + { + "name": "java_getType", + "displayName": "Get Type at Position", + "modelDescription": "Get the compiler-resolved type of a symbol at a specific position in a Java file. Returns the precise type including generics (e.g. Map>). Use this for var declarations, lambda parameters, or complex generic chains where the type is not visible in source code.", + "inputSchema": { + "type": "object", + "properties": { + "uri": { "type": "string", "description": "The file URI" }, + "line": { "type": "number", "description": "0-based line number" }, + "character": { "type": "number", "description": "0-based column number" } + }, + "required": ["uri", "line", "character"] + } + } + ] + } +} +``` + +### 关键设计原则 + +#### 1. `modelDescription` 比实现逻辑更重要 + +``` +❌ "Gets document symbols from LSP" — LLM 不知道什么时候该用 +✅ "Get the structure of a Java file without — LLM 明确知道:需要了解文件结构时, + reading its full content" 用这个替代 read_file +``` + +#### 2. 返回格式要为 AI 优化,不是为人优化 + +| 做法 | AI 消耗 tokens | +|------|---------------| +| 原始 hover Markdown(含完整 Javadoc) | ~200 tokens | +| 后处理只保留类型签名 | ~10 tokens | +| 原始 documentSymbol JSON | ~500 tokens | +| 格式化为缩进文本树 | ~100 tokens | + +#### 3. 选择 LanguageModelTool 而非其他路径的原因 + +- **vs 路径 B**(直接用 `run_vscode_command`):可以快速 PoC,但 AI 不会主动发现这些命令,需要人工提示,无法规模化 +- **vs 路径 C**(MCP Server):需要独立进程管理 jdtls 连接,复杂度高很多,除非目标是脱离 VS Code 的场景 + +**总结:用 LanguageModelTool API 封装 VS Code 内置命令,是目前最轻量、最正式的方式。实现工作量主要在 tool 的输入输出设计,而不是 LSP 通信。** diff --git a/package-lock.json b/package-lock.json index 4b57e7dd..161ba633 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,7 @@ "@types/mocha": "^9.1.1", "@types/node": "20.x", "@types/semver": "^7.3.13", - "@types/vscode": "1.83.1", + "@types/vscode": "1.95.0", "@vscode/test-electron": "^2.4.1", "copy-webpack-plugin": "^11.0.0", "glob": "^7.2.3", @@ -42,7 +42,7 @@ "webpack-cli": "^4.10.0" }, "engines": { - "vscode": "^1.83.1" + "vscode": "^1.95.0" } }, "node_modules/@babel/code-frame": { @@ -843,10 +843,11 @@ "dev": true }, "node_modules/@types/vscode": { - "version": "1.83.1", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.83.1.tgz", - "integrity": "sha512-BHu51NaNKOtDf3BOonY3sKFFmZKEpRkzqkZVpSYxowLbs5JqjOQemYFob7Gs5rpxE5tiGhfpnMpcdF/oKrLg4w==", - "dev": true + "version": "1.95.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.95.0.tgz", + "integrity": "sha512-0LBD8TEiNbet3NvWsmn59zLzOFu/txSlGxnv5yAFHCrhG9WvAnR3IvfHzMOs2aeWqgvNjq9pO99IUw8d3n+unw==", + "dev": true, + "license": "MIT" }, "node_modules/@types/ws": { "version": "8.5.10", @@ -6891,9 +6892,9 @@ "dev": true }, "@types/vscode": { - "version": "1.83.1", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.83.1.tgz", - "integrity": "sha512-BHu51NaNKOtDf3BOonY3sKFFmZKEpRkzqkZVpSYxowLbs5JqjOQemYFob7Gs5rpxE5tiGhfpnMpcdF/oKrLg4w==", + "version": "1.95.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.95.0.tgz", + "integrity": "sha512-0LBD8TEiNbet3NvWsmn59zLzOFu/txSlGxnv5yAFHCrhG9WvAnR3IvfHzMOs2aeWqgvNjq9pO99IUw8d3n+unw==", "dev": true }, "@types/ws": { diff --git a/package.json b/package.json index 1c0301a1..a418e222 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "explorer" ], "engines": { - "vscode": "^1.83.1" + "vscode": "^1.95.0" }, "repository": { "type": "git", @@ -48,6 +48,162 @@ "javaExtensions": [ "./server/com.microsoft.jdtls.ext.core-0.24.1.jar" ], + "languageModelTools": [ + { + "name": "java_getFileStructure", + "tags": ["java", "structure", "outline"], + "toolReferenceName": "javaFileStructure", + "modelDescription": "Get the structure (classes, methods, fields, inner classes) of a Java file. Returns a hierarchical outline with symbol kinds and line ranges. Use this first when you need to understand a Java file's layout before reading specific sections.", + "displayName": "Java: Get File Structure", + "canBeReferencedInPrompt": true, + "icon": "$(symbol-class)", + "inputSchema": { + "type": "object", + "properties": { + "uri": { + "type": "string", + "description": "The path of the Java file. Accepts workspace-relative path (e.g. 'src/main/java/MyClass.java') or full file URI." + } + }, + "required": ["uri"] + } + }, + { + "name": "java_findSymbol", + "tags": ["java", "search", "symbol"], + "toolReferenceName": "javaFindSymbol", + "modelDescription": "Search for Java symbols (classes, interfaces, methods, fields) across the entire workspace by name. Supports partial/fuzzy matching. Use this when you need to find where a class or method is defined.", + "displayName": "Java: Find Symbol", + "canBeReferencedInPrompt": true, + "icon": "$(search)", + "inputSchema": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "The symbol name or pattern to search for, e.g. 'UserService' or 'handleRequest'" + }, + "limit": { + "type": "number", + "description": "Maximum results to return (default: 20, max: 50)" + } + }, + "required": ["query"] + } + }, + { + "name": "java_getFileImports", + "tags": ["java", "imports", "dependencies"], + "toolReferenceName": "javaFileImports", + "modelDescription": "Get all import statements from a Java file, classified by source (jdk/project/external). Includes both regular and static imports. Use this to understand which libraries and project classes a file depends on.", + "displayName": "Java: Get File Imports", + "canBeReferencedInPrompt": true, + "icon": "$(references)", + "inputSchema": { + "type": "object", + "properties": { + "uri": { + "type": "string", + "description": "The path of the Java file. Accepts workspace-relative path (e.g. 'src/main/java/MyClass.java') or full file URI." + } + }, + "required": ["uri"] + } + }, + { + "name": "java_getTypeAtPosition", + "tags": ["java", "type", "hover"], + "toolReferenceName": "javaTypeAtPosition", + "modelDescription": "Get the resolved type signature of a symbol at a specific position in a Java file. Returns the fully qualified type, method signature, or field declaration. Use this to check the exact type of a variable, parameter, or expression. Tip: Use java_getFileStructure first to get the line ranges of your target symbol.", + "displayName": "Java: Get Type at Position", + "canBeReferencedInPrompt": true, + "icon": "$(symbol-field)", + "inputSchema": { + "type": "object", + "properties": { + "uri": { + "type": "string", + "description": "The path of the Java file. Accepts workspace-relative path (e.g. 'src/main/java/MyClass.java') or full file URI." + }, + "line": { + "type": "number", + "description": "Zero-based line number" + }, + "character": { + "type": "number", + "description": "Zero-based character offset" + } + }, + "required": ["uri", "line", "character"] + } + }, + { + "name": "java_getCallHierarchy", + "tags": ["java", "calls", "hierarchy"], + "toolReferenceName": "javaCallHierarchy", + "modelDescription": "Get incoming callers or outgoing callees of a method at a specific position. Use 'incoming' to find all methods that call this method, or 'outgoing' to find all methods this method calls. Essential for understanding control flow and impact analysis. Tip: Use java_getFileStructure first to get the line ranges of your target method.", + "displayName": "Java: Get Call Hierarchy", + "canBeReferencedInPrompt": true, + "icon": "$(call-incoming)", + "inputSchema": { + "type": "object", + "properties": { + "uri": { + "type": "string", + "description": "The path of the Java file. Accepts workspace-relative path (e.g. 'src/main/java/MyClass.java') or full file URI." + }, + "line": { + "type": "number", + "description": "Zero-based line number of the method" + }, + "character": { + "type": "number", + "description": "Zero-based character offset within the method name" + }, + "direction": { + "type": "string", + "enum": ["incoming", "outgoing"], + "default": "incoming", + "description": "Direction: 'incoming' for callers, 'outgoing' for callees" + } + }, + "required": ["uri", "line", "character", "direction"] + } + }, + { + "name": "java_getTypeHierarchy", + "tags": ["java", "inheritance", "hierarchy"], + "toolReferenceName": "javaTypeHierarchy", + "modelDescription": "Get supertypes (parent class and interfaces) or subtypes (subclasses and implementors) of a type at a specific position. Use this to understand inheritance relationships, find implementations of an interface, or check what a class extends. Tip: Use java_getFileStructure first to get the line ranges of your target type.", + "displayName": "Java: Get Type Hierarchy", + "canBeReferencedInPrompt": true, + "icon": "$(type-hierarchy)", + "inputSchema": { + "type": "object", + "properties": { + "uri": { + "type": "string", + "description": "The path of the Java file. Accepts workspace-relative path (e.g. 'src/main/java/MyClass.java') or full file URI." + }, + "line": { + "type": "number", + "description": "Zero-based line number of the type" + }, + "character": { + "type": "number", + "description": "Zero-based character offset within the type name" + }, + "direction": { + "type": "string", + "enum": ["supertypes", "subtypes"], + "default": "supertypes", + "description": "Direction: 'supertypes' for parents, 'subtypes' for children" + } + }, + "required": ["uri", "line", "character", "direction"] + } + } + ], "commands": [ { "command": "java.project.create", @@ -1078,6 +1234,11 @@ } } } + ], + "chatSkills": [ + { + "path": "./resources/skills/java-context-tools/SKILL.md" + } ] }, "scripts": { @@ -1097,7 +1258,7 @@ "@types/mocha": "^9.1.1", "@types/node": "20.x", "@types/semver": "^7.3.13", - "@types/vscode": "1.83.1", + "@types/vscode": "1.95.0", "@vscode/test-electron": "^2.4.1", "copy-webpack-plugin": "^11.0.0", "glob": "^7.2.3", diff --git a/resources/skills/java-context-tools/SKILL.md b/resources/skills/java-context-tools/SKILL.md new file mode 100644 index 00000000..678debdf --- /dev/null +++ b/resources/skills/java-context-tools/SKILL.md @@ -0,0 +1,79 @@ +--- +name: java-context-tools +description: Compiler-accurate Java code intelligence tools powered by the Java Language Server. +--- + +# Java Context Tools + +Compiler-accurate Java code intelligence via the Java Language Server (jdtls). These 6 tools provide structured, low-token answers that are more precise than `grep_search` or `read_file` for Java code. + +## Activation (Required on First Use) + +These tools are **deferred** and must be discovered before first use. Run: +``` +tool_search_tool_regex("java_get|java_find") +``` +This activates all 6 tools in one call. You only need to do this **once per session** — after that, the tools are available directly. + +## Tool Priority (Java Projects) + +For Java source files, **always prefer these tools over generic alternatives**: + +| Instead of | Use | Why | +|---|---|---| +| `grep_search` (symbol lookup) | `java_findSymbol` | Returns only definitions, not comments/strings/imports | +| `grep_search` (find usages) | `java_getCallHierarchy` | Returns actual call sites with context | +| Guessing types | `java_getTypeAtPosition` | Compiler-accurate for `var`, lambdas, generics | + +**Do NOT use when:** +- File path is unknown — use `java_findSymbol` first to get the correct path +- Working on non-Java files (pom.xml, build.gradle, yaml — use `read_file`/`grep_search`) +- File is small (< 100 lines — just `read_file`) +- Type is obvious (`String`, `int`, `java.util.List`) + +## Tools + +All tools accept **workspace-relative paths** (e.g. `src/main/java/com/example/MyClass.java`) or full file URIs. All return structured JSON, each response < 200 tokens. + +### `java_getFileStructure` +Get hierarchical outline (classes, methods, fields) with line ranges. +Input: `{ uri }` → Output: symbol tree with `[L start-end]` ranges (~100 tokens) + +### `java_findSymbol` +Search for symbol definitions by name across the workspace. Supports partial/fuzzy matching. +Input: `{ query }` → Output: up to 20 results with `{ name, kind, location }` (~60 tokens) + +### `java_getFileImports` +Get all imports classified by source (jdk/project/external). +Input: `{ uri }` → Output: classified import list (~80 tokens) + +### `java_getTypeAtPosition` +Get compiler-resolved type signature at a specific position. +Input: `{ uri, line, character }` (0-based) → Output: fully resolved type (~20 tokens) + +### `java_getCallHierarchy` +Find all callers (incoming) or callees (outgoing) of a method. +Input: `{ uri, line, character, direction }` (0-based, direction: `"incoming"` | `"outgoing"`) → Output: list of `{ name, detail, location }` (~80 tokens) + +### `java_getTypeHierarchy` +Find supertypes or subtypes/implementors of a type. +Input: `{ uri, line, character, direction }` (0-based, direction: `"supertypes"` | `"subtypes"`) → Output: list of `{ name, kind, location }` (~60 tokens) + +## Common Workflows + +Most tasks follow the pattern: **findSymbol → getFileStructure → targeted tool**. + +| Scenario | Workflow | +|---|---| +| Debug a bug | `findSymbol` → `getFileStructure` → `read_file` (specific lines) | +| Analyze impact | `findSymbol` → `getFileStructure` → `getCallHierarchy("incoming")` | +| Understand inheritance | `findSymbol` → `getTypeHierarchy("subtypes")` | +| Check dependencies | `getFileImports` → `findSymbol` (dependency) → `getFileStructure` | + +## Fallback + +- **`java_findSymbol` returns empty**: + - Symbol may not exist yet → switch to `read_file` + `grep_search` to confirm, then create it + - Spelling/query too specific → retry once with a shorter keyword (e.g. `"UserSvc"` instead of `"UserServiceImpl"`) +- **Path error** (e.g. "Unable to resolve nonexistent file"): Use `java_findSymbol` to discover the correct file path first, then retry. +- **Tool error / empty result** (jdtls not ready, file not in project): Fall back to `read_file` + `grep_search`. Don't retry more than once. diff --git a/src/commands.ts b/src/commands.ts index 0e7a69cd..5331ab3e 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -138,6 +138,8 @@ export namespace Commands { export const JAVA_PROJECT_GET_IMPORT_CLASS_CONTENT = "java.project.getImportClassContent"; + export const JAVA_PROJECT_GET_FILE_IMPORTS = "java.project.getFileImports"; + export const JAVA_UPGRADE_WITH_COPILOT = "_java.upgradeWithCopilot"; /** diff --git a/src/copilot/tools/javaContextTools.ts b/src/copilot/tools/javaContextTools.ts new file mode 100644 index 00000000..dbb027b2 --- /dev/null +++ b/src/copilot/tools/javaContextTools.ts @@ -0,0 +1,330 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Java Context Tools — First Batch (Zero-Blocking) + * + * These 6 tools are all non-blocking after jdtls is ready: + * 1. java_getFileStructure — LSP documentSymbol + * 2. java_findSymbol — LSP workspaceSymbol + * 3. java_getFileImports — jdtls AST-only command (no type resolution) + * 4. java_getTypeAtPosition — LSP hover (post-processed) + * 5. java_getCallHierarchy — LSP call hierarchy + * 6. java_getTypeHierarchy — LSP type hierarchy + * + * Design principles: + * - Each tool returns < 200 tokens + * - Structured JSON output + * - No classpath resolution, no dependency download + * + * Note: The LanguageModelTool API is not yet in @types/vscode 1.83.1, + * so we use (vscode as any) casts following the same pattern as vscode-java-debug. + */ + +import * as vscode from "vscode"; +import { Commands } from "../../commands"; + +// ──────────────────────────────────────────────────────────── +// Type shims for vscode.lm LanguageModelTool API +// (not available in @types/vscode 1.83.1) +// ──────────────────────────────────────────────────────────── + +interface LanguageModelTool { + invoke(options: { input: T }, token: vscode.CancellationToken): Promise; +} + +// Access the lm namespace via any-cast +const lmApi = (vscode as any).lm; + +function toResult(data: unknown): any { + const text = typeof data === "string" ? data : JSON.stringify(data, null, 2); + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart(text), + ]); +} + +/** + * Resolve a file path to a vscode.Uri. + * Accepts: + * - Full URI: "file:///home/user/project/src/Main.java" + * - Relative path: "src/main/java/Main.java" + * - Absolute path: "/home/user/project/src/Main.java" or "C:\\Users\\...\\Main.java" + * + * Relative paths are resolved against the first workspace folder. + */ +function resolveFileUri(input: string): vscode.Uri { + // Already a full URI (has scheme like file://, untitled:, etc.) + if (/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(input) || input.startsWith("untitled:")) { + return vscode.Uri.parse(input); + } + // Absolute path (Unix or Windows) + if (input.startsWith("/") || /^[a-zA-Z]:[/\\]/.test(input)) { + return vscode.Uri.file(input); + } + // Relative path — resolve against workspace folder + const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; + if (workspaceFolder) { + return vscode.Uri.joinPath(workspaceFolder.uri, input); + } + // Fallback: treat as file path + return vscode.Uri.file(input); +} + +// ============================================================ +// Tool 1: java_getFileStructure (LSP — Document Symbol) +// ============================================================ + +interface FileStructureInput { + uri: string; +} + +const fileStructureTool: LanguageModelTool = { + async invoke(options, _token) { + const uri = resolveFileUri(options.input.uri); + const symbols = await vscode.commands.executeCommand( + "vscode.executeDocumentSymbolProvider", uri, + ); + if (!symbols || symbols.length === 0) { + return toResult({ error: "No symbols found. The file may not be recognized by the Java language server." }); + } + return toResult(formatSymbols(symbols, 0)); + }, +}; + +function formatSymbols(symbols: vscode.DocumentSymbol[], indent: number): string { + return symbols.map(s => { + const prefix = " ".repeat(indent); + const kind = vscode.SymbolKind[s.kind]; + const range = `[L${s.range.start.line + 1}-${s.range.end.line + 1}]`; + const detail = s.detail ? ` ${s.detail}` : ""; + let line = `${prefix}${kind}: ${s.name}${detail} ${range}`; + if (s.children?.length) { + line += "\n" + formatSymbols(s.children, indent + 1); + } + return line; + }).join("\n"); +} + +// ============================================================ +// Tool 2: java_findSymbol (LSP — Workspace Symbol) +// ============================================================ + +interface FindSymbolInput { + query: string; + limit?: number; +} + +const findSymbolTool: LanguageModelTool = { + async invoke(options, _token) { + const symbols = await vscode.commands.executeCommand( + "vscode.executeWorkspaceSymbolProvider", options.input.query, + ); + if (!symbols || symbols.length === 0) { + return toResult({ results: [], message: `No symbols matching '${options.input.query}' found.` }); + } + const limit = Math.min(Math.max(options.input.limit || 20, 1), 50); + const results = symbols.slice(0, limit).map(s => ({ + name: s.name, + kind: vscode.SymbolKind[s.kind], + location: `${vscode.workspace.asRelativePath(s.location.uri)}:${s.location.range.start.line + 1}`, + })); + return toResult({ results, total: symbols.length }); + }, +}; + +// ============================================================ +// Tool 3: java_getFileImports (jdtls — AST-only, non-blocking) +// ============================================================ + +interface FileImportsInput { + uri: string; +} + +const fileImportsTool: LanguageModelTool = { + async invoke(options, _token) { + const uri = resolveFileUri(options.input.uri); + const result = await vscode.commands.executeCommand( + Commands.EXECUTE_WORKSPACE_COMMAND, + Commands.JAVA_PROJECT_GET_FILE_IMPORTS, + uri.toString(), + ); + if (!result) { + return toResult({ error: "No result from Java language server. It may still be loading." }); + } + return toResult(result); + }, +}; + +// ============================================================ +// Tool 4: java_getTypeAtPosition (LSP — Hover post-processed) +// ============================================================ + +interface TypeAtPositionInput { + uri: string; + line: number; + character: number; +} + +const typeAtPositionTool: LanguageModelTool = { + async invoke(options, _token) { + const uri = resolveFileUri(options.input.uri); + const position = new vscode.Position(options.input.line, options.input.character); + const hovers = await vscode.commands.executeCommand( + "vscode.executeHoverProvider", uri, position, + ); + return toResult(extractTypeSignature(hovers)); + }, +}; + +/** + * Extract type signature from jdtls hover result. + * jdtls returns Markdown with ```java code blocks containing the type info. + * We extract just the signature, stripping Javadoc to minimize tokens. + */ +function extractTypeSignature(hovers: vscode.Hover[] | undefined): object { + if (!hovers?.length) { + return { error: "No type information at this position" }; + } + for (const hover of hovers) { + for (const content of hover.contents) { + if (content instanceof vscode.MarkdownString) { + const match = content.value.match(/```java\n([\s\S]*?)```/); + if (match) { + const lines = match[1].trim().split("\n").filter(l => l.trim().length > 0); + return { type: lines.join("\n") }; + } + } + } + } + return { error: "Could not extract type from hover result" }; +} + +// ============================================================ +// Tool 5: java_getCallHierarchy (LSP — Call Hierarchy) +// ============================================================ + +interface CallHierarchyInput { + uri: string; + line: number; + character: number; + direction: "incoming" | "outgoing"; +} + +const callHierarchyTool: LanguageModelTool = { + async invoke(options, _token) { + const uri = resolveFileUri(options.input.uri); + const position = new vscode.Position(options.input.line, options.input.character); + + // Step 1: Prepare call hierarchy item at the given position + const items = await vscode.commands.executeCommand( + "vscode.prepareCallHierarchy", uri, position, + ); + if (!items?.length) { + return toResult({ error: "No callable symbol at this position" }); + } + + // Step 2: Get incoming or outgoing calls + const isIncoming = options.input.direction === "incoming"; + const command = isIncoming ? "vscode.provideIncomingCalls" : "vscode.provideOutgoingCalls"; + const calls = await vscode.commands.executeCommand(command, items[0]); + + if (!calls || calls.length === 0) { + return toResult({ + symbol: items[0].name, + direction: options.input.direction, + calls: [], + message: `No ${options.input.direction} calls found for '${items[0].name}'`, + }); + } + + const results = calls.map((call: any) => { + const item = isIncoming ? call.from : call.to; + return { + name: item.name, + detail: item.detail || undefined, + location: `${vscode.workspace.asRelativePath(item.uri)}:${item.range.start.line + 1}`, + }; + }); + + return toResult({ + symbol: items[0].name, + direction: options.input.direction, + calls: results, + }); + }, +}; + +// ============================================================ +// Tool 6: java_getTypeHierarchy (LSP — Type Hierarchy) +// ============================================================ + +interface TypeHierarchyInput { + uri: string; + line: number; + character: number; + direction: "supertypes" | "subtypes"; +} + +const typeHierarchyTool: LanguageModelTool = { + async invoke(options, _token) { + const uri = resolveFileUri(options.input.uri); + const position = new vscode.Position(options.input.line, options.input.character); + + // Step 1: Prepare type hierarchy item at the given position + const items = await vscode.commands.executeCommand( + "vscode.prepareTypeHierarchy", uri, position, + ); + if (!items?.length) { + return toResult({ error: "No type at this position" }); + } + + // Step 2: Get supertypes or subtypes + const isSuper = options.input.direction === "supertypes"; + const command = isSuper ? "vscode.provideSupertypes" : "vscode.provideSubtypes"; + const types = await vscode.commands.executeCommand(command, items[0]); + + if (!types || types.length === 0) { + return toResult({ + symbol: items[0].name, + direction: options.input.direction, + types: [], + message: `No ${options.input.direction} found for '${items[0].name}'`, + }); + } + + const results = types.map(t => ({ + name: t.name, + kind: vscode.SymbolKind[t.kind], + detail: t.detail || undefined, + location: `${vscode.workspace.asRelativePath(t.uri)}:${t.range.start.line + 1}`, + })); + + return toResult({ + symbol: items[0].name, + direction: options.input.direction, + types: results, + }); + }, +}; + +// ============================================================ +// Registration +// ============================================================ + +export function registerJavaContextTools(context: vscode.ExtensionContext): void { + // Guard: Language Model API may not be available in older VS Code versions + if (!lmApi || typeof lmApi.registerTool !== "function") { + return; + } + + context.subscriptions.push( + lmApi.registerTool("java_getFileStructure", fileStructureTool), + lmApi.registerTool("java_findSymbol", findSymbolTool), + lmApi.registerTool("java_getFileImports", fileImportsTool), + lmApi.registerTool("java_getTypeAtPosition", typeAtPositionTool), + lmApi.registerTool("java_getCallHierarchy", callHierarchyTool), + lmApi.registerTool("java_getTypeHierarchy", typeHierarchyTool), + ); +} diff --git a/src/extension.ts b/src/extension.ts index c89b6547..273f6e79 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -22,6 +22,7 @@ import { CodeActionProvider } from "./tasks/buildArtifact/migration/CodeActionPr import { newJavaFile } from "./explorerCommands/new"; import upgradeManager from "./upgrade/upgradeManager"; import { registerCopilotContextProviders } from "./copilot/contextProvider"; +import { registerJavaContextTools } from "./copilot/tools/javaContextTools"; import { languageServerApiManager } from "./languageServerApi/languageServerApiManager"; export async function activate(context: ExtensionContext): Promise { @@ -90,6 +91,7 @@ async function activateExtension(_operationId: string, context: ExtensionContext languageServerApiManager.ready().then((isReady) => { if (isReady) { registerCopilotContextProviders(context); + registerJavaContextTools(context); } }); } From 035d424c4a2905d39911544b6c5d67d8c27b1467 Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Tue, 10 Mar 2026 09:53:26 +0800 Subject: [PATCH 02/14] fix: update --- package.json | 36 +++++------- resources/skills/java-context-tools/SKILL.md | 60 ++++++++++++-------- 2 files changed, 52 insertions(+), 44 deletions(-) diff --git a/package.json b/package.json index a418e222..61c5df58 100644 --- a/package.json +++ b/package.json @@ -51,9 +51,8 @@ "languageModelTools": [ { "name": "java_getFileStructure", - "tags": ["java", "structure", "outline"], "toolReferenceName": "javaFileStructure", - "modelDescription": "Get the structure (classes, methods, fields, inner classes) of a Java file. Returns a hierarchical outline with symbol kinds and line ranges. Use this first when you need to understand a Java file's layout before reading specific sections.", + "modelDescription": "Get the structure (classes, methods, fields, inner classes) of a Java file. Returns a hierarchical outline with symbol kinds and line ranges. Use this first when you need to understand a file's layout before reading specific sections.", "displayName": "Java: Get File Structure", "canBeReferencedInPrompt": true, "icon": "$(symbol-class)", @@ -62,7 +61,7 @@ "properties": { "uri": { "type": "string", - "description": "The path of the Java file. Accepts workspace-relative path (e.g. 'src/main/java/MyClass.java') or full file URI." + "description": "Java file path (workspace-relative or full URI)" } }, "required": ["uri"] @@ -70,9 +69,8 @@ }, { "name": "java_findSymbol", - "tags": ["java", "search", "symbol"], "toolReferenceName": "javaFindSymbol", - "modelDescription": "Search for Java symbols (classes, interfaces, methods, fields) across the entire workspace by name. Supports partial/fuzzy matching. Use this when you need to find where a class or method is defined.", + "modelDescription": "Search for Java symbols (classes, interfaces, methods, fields) across the workspace by name. Supports partial/fuzzy matching. Use this to find where a class or method is defined.", "displayName": "Java: Find Symbol", "canBeReferencedInPrompt": true, "icon": "$(search)", @@ -81,11 +79,11 @@ "properties": { "query": { "type": "string", - "description": "The symbol name or pattern to search for, e.g. 'UserService' or 'handleRequest'" + "description": "Symbol name or pattern to search for" }, "limit": { "type": "number", - "description": "Maximum results to return (default: 20, max: 50)" + "description": "Maximum results (default: 20, max: 50)" } }, "required": ["query"] @@ -93,9 +91,8 @@ }, { "name": "java_getFileImports", - "tags": ["java", "imports", "dependencies"], "toolReferenceName": "javaFileImports", - "modelDescription": "Get all import statements from a Java file, classified by source (jdk/project/external). Includes both regular and static imports. Use this to understand which libraries and project classes a file depends on.", + "modelDescription": "Get all import statements from a Java file, classified by source (jdk/project/external).", "displayName": "Java: Get File Imports", "canBeReferencedInPrompt": true, "icon": "$(references)", @@ -104,7 +101,7 @@ "properties": { "uri": { "type": "string", - "description": "The path of the Java file. Accepts workspace-relative path (e.g. 'src/main/java/MyClass.java') or full file URI." + "description": "Java file path (workspace-relative or full URI)" } }, "required": ["uri"] @@ -112,9 +109,8 @@ }, { "name": "java_getTypeAtPosition", - "tags": ["java", "type", "hover"], "toolReferenceName": "javaTypeAtPosition", - "modelDescription": "Get the resolved type signature of a symbol at a specific position in a Java file. Returns the fully qualified type, method signature, or field declaration. Use this to check the exact type of a variable, parameter, or expression. Tip: Use java_getFileStructure first to get the line ranges of your target symbol.", + "modelDescription": "Get the compiler-resolved type signature at a specific position. Returns fully qualified type, method signature, or field declaration. Use this when you see var, lambdas, or generics and need the exact type.", "displayName": "Java: Get Type at Position", "canBeReferencedInPrompt": true, "icon": "$(symbol-field)", @@ -123,7 +119,7 @@ "properties": { "uri": { "type": "string", - "description": "The path of the Java file. Accepts workspace-relative path (e.g. 'src/main/java/MyClass.java') or full file URI." + "description": "Java file path (workspace-relative or full URI)" }, "line": { "type": "number", @@ -139,9 +135,8 @@ }, { "name": "java_getCallHierarchy", - "tags": ["java", "calls", "hierarchy"], "toolReferenceName": "javaCallHierarchy", - "modelDescription": "Get incoming callers or outgoing callees of a method at a specific position. Use 'incoming' to find all methods that call this method, or 'outgoing' to find all methods this method calls. Essential for understanding control flow and impact analysis. Tip: Use java_getFileStructure first to get the line ranges of your target method.", + "modelDescription": "Get incoming callers or outgoing callees of a method. PREFER THIS over grep_search when you need to find which methods call a specific method — returns precise call sites without noise from comments, imports, or string matches. Use 'incoming' for callers, 'outgoing' for callees.", "displayName": "Java: Get Call Hierarchy", "canBeReferencedInPrompt": true, "icon": "$(call-incoming)", @@ -150,7 +145,7 @@ "properties": { "uri": { "type": "string", - "description": "The path of the Java file. Accepts workspace-relative path (e.g. 'src/main/java/MyClass.java') or full file URI." + "description": "Java file path (workspace-relative or full URI)" }, "line": { "type": "number", @@ -164,7 +159,7 @@ "type": "string", "enum": ["incoming", "outgoing"], "default": "incoming", - "description": "Direction: 'incoming' for callers, 'outgoing' for callees" + "description": "'incoming' for callers, 'outgoing' for callees" } }, "required": ["uri", "line", "character", "direction"] @@ -172,9 +167,8 @@ }, { "name": "java_getTypeHierarchy", - "tags": ["java", "inheritance", "hierarchy"], "toolReferenceName": "javaTypeHierarchy", - "modelDescription": "Get supertypes (parent class and interfaces) or subtypes (subclasses and implementors) of a type at a specific position. Use this to understand inheritance relationships, find implementations of an interface, or check what a class extends. Tip: Use java_getFileStructure first to get the line ranges of your target type.", + "modelDescription": "Get supertypes or subtypes/implementors of a type. PREFER THIS over grep_search('extends|implements') — returns only actual inheritance relationships. Use 'supertypes' for parents, 'subtypes' for children.", "displayName": "Java: Get Type Hierarchy", "canBeReferencedInPrompt": true, "icon": "$(type-hierarchy)", @@ -183,7 +177,7 @@ "properties": { "uri": { "type": "string", - "description": "The path of the Java file. Accepts workspace-relative path (e.g. 'src/main/java/MyClass.java') or full file URI." + "description": "Java file path (workspace-relative or full URI)" }, "line": { "type": "number", @@ -197,7 +191,7 @@ "type": "string", "enum": ["supertypes", "subtypes"], "default": "supertypes", - "description": "Direction: 'supertypes' for parents, 'subtypes' for children" + "description": "'supertypes' for parents, 'subtypes' for children" } }, "required": ["uri", "line", "character", "direction"] diff --git a/resources/skills/java-context-tools/SKILL.md b/resources/skills/java-context-tools/SKILL.md index 678debdf..69e9861b 100644 --- a/resources/skills/java-context-tools/SKILL.md +++ b/resources/skills/java-context-tools/SKILL.md @@ -1,35 +1,48 @@ --- name: java-context-tools -description: Compiler-accurate Java code intelligence tools powered by the Java Language Server. +description: Compiler-accurate Java code intelligence tools powered by the Java Language Server. ALWAYS load this skill when the workspace contains Java, Maven (pom.xml), or Gradle (build.gradle) projects. Use these tools to find symbol definitions, get type/call hierarchies, resolve types, inspect file outlines, and check imports in Java source files. Prefer over grep_search or read_file for any Java code navigation, understanding, debugging, or refactoring task. --- # Java Context Tools Compiler-accurate Java code intelligence via the Java Language Server (jdtls). These 6 tools provide structured, low-token answers that are more precise than `grep_search` or `read_file` for Java code. -## Activation (Required on First Use) +## Activation -These tools are **deferred** and must be discovered before first use. Run: -``` -tool_search_tool_regex("java_get|java_find") -``` -This activates all 6 tools in one call. You only need to do this **once per session** — after that, the tools are available directly. +These tools are **deferred** and must be discovered before first use. Activate all 6 tools at once with `tool_search_tool_regex` using pattern: -## Tool Priority (Java Projects) +`java_findSymbol|java_getFileStructure|java_getCallHierarchy|java_getTypeHierarchy|java_getTypeAtPosition|java_getFileImports` + +You only need to do this **once per session**. + +## When to Replace grep_search For Java source files, **always prefer these tools over generic alternatives**: -| Instead of | Use | Why | +| You're doing... | Use instead | Why | |---|---|---| -| `grep_search` (symbol lookup) | `java_findSymbol` | Returns only definitions, not comments/strings/imports | -| `grep_search` (find usages) | `java_getCallHierarchy` | Returns actual call sites with context | -| Guessing types | `java_getTypeAtPosition` | Compiler-accurate for `var`, lambdas, generics | +| Find where a class/method is defined | `java_findSymbol` | ~60 tokens vs ~500 for grep (no comment/import noise) | +| Find all callers of a method | `java_getCallHierarchy("incoming")` | ~80 tokens vs ~3000 for grep (precise call sites only) | +| Find all implementations of an interface | `java_getTypeHierarchy("subtypes")` | ~60 tokens vs ~1000 for grep | +| Check a `var`/lambda/generic type | `java_getTypeAtPosition` | ~20 tokens vs guessing wrong | +| Search in non-Java files (xml, yaml, gradle) | Keep using `grep_search` | java_* tools only work on Java source | +| Search for string literals or comments | Keep using `grep_search` | java_* tools return symbol definitions only | + +**Rule of thumb**: If you're searching for a Java symbol name in `.java` files, there is almost always a `java_*` tool that returns more precise results with fewer tokens than `grep_search`. + +## Anti-patterns (Avoid these) -**Do NOT use when:** -- File path is unknown — use `java_findSymbol` first to get the correct path -- Working on non-Java files (pom.xml, build.gradle, yaml — use `read_file`/`grep_search`) -- File is small (< 100 lines — just `read_file`) -- Type is obvious (`String`, `int`, `java.util.List`) +❌ **Don't**: Use `grep_search("decodeLbs")` to find who calls `decodeLbs()` + - Returns 8+ matches including declaration, comments, imports → ~3000 output tokens + +✅ **Do**: Use `java_getCallHierarchy(uri, line, char, "incoming")` + - Returns only actual call sites → ~80 output tokens + +❌ **Don't**: Use `grep_search("class.*extends BaseDecoder")` to find subclasses +✅ **Do**: Use `java_getTypeHierarchy(uri, line, char, "subtypes")` + +❌ **Don't**: Read entire 1000+ line file to understand its structure +✅ **Do**: Use `java_getFileStructure(uri)` first, then `read_file` on specific line ranges ## Tools @@ -63,12 +76,13 @@ Input: `{ uri, line, character, direction }` (0-based, direction: `"supertypes"` Most tasks follow the pattern: **findSymbol → getFileStructure → targeted tool**. -| Scenario | Workflow | -|---|---| -| Debug a bug | `findSymbol` → `getFileStructure` → `read_file` (specific lines) | -| Analyze impact | `findSymbol` → `getFileStructure` → `getCallHierarchy("incoming")` | -| Understand inheritance | `findSymbol` → `getTypeHierarchy("subtypes")` | -| Check dependencies | `getFileImports` → `findSymbol` (dependency) → `getFileStructure` | +| Scenario | Workflow | Trigger | +|---|---|---| +| Debug a bug | `findSymbol` → `getFileStructure` → `read_file` (buggy method) → **`getCallHierarchy("incoming")`** → `read_file` (caller context) | When you found the buggy method and need to know ALL callers | +| Analyze impact | `findSymbol` → `getFileStructure` → `getCallHierarchy("incoming")` | Before editing a method, check who depends on it | +| Understand inheritance | `findSymbol` → `getTypeHierarchy("subtypes")` | When you see a base class and need all implementations | +| Check dependencies | `getFileImports` → `findSymbol` (dependency) → `getFileStructure` | When understanding external library usage | +| Resolve type ambiguity | `getFileStructure` → `getTypeAtPosition` | When you see `var`, generics, or lambda and need exact type | ## Fallback From ae724893b1d6afc24eda6fb489196e90be135b14 Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Wed, 11 Mar 2026 14:43:15 +0800 Subject: [PATCH 03/14] feat: update --- docs/java-context-tools-design.md | 24 +++++----- docs/java-context-tools-skill.md | 48 +++++++++---------- docs/package-json-tools-snippet.jsonc | 12 ++--- lsp-ai-tools.md | 2 +- package.json | 20 +++++--- resources/instruments/loadJavaSkills.md | 16 +++++++ .../SKILL.md | 42 ++++++++-------- src/copilot/tools/javaContextTools.ts | 36 +++++++------- 8 files changed, 111 insertions(+), 89 deletions(-) create mode 100644 resources/instruments/loadJavaSkills.md rename resources/skills/{java-context-tools => java-lsp-tools}/SKILL.md (74%) diff --git a/docs/java-context-tools-design.md b/docs/java-context-tools-design.md index d4c7440b..e667a99e 100644 --- a/docs/java-context-tools-design.md +++ b/docs/java-context-tools-design.md @@ -28,7 +28,7 @@ | Tool | 粒度 | 用途 | 典型 token 量 | |------|------|------|--------------| | `java_getProjectContext` | L0 | 项目级概览 | ~100 | -| `java_getFileImports` | L0 | 文件的 import 列表 | ~80 | +| `lsp_java_getFileImports` | L0 | 文件的 import 列表 | ~80 | | `java_getClassDetail` | L1 | 单个类的签名+方法列表 | ~150 | | `java_getDependencyDetails` | L1 | 指定依赖的 GAV+scope | ~50 | @@ -36,11 +36,11 @@ | 标准 LSP Tool(VS Code 内置命令封装) | 粒度 | 对应 LSP 请求 | |--------------------------------------|------|-------------| -| `java_getFileStructure` | L0 | `textDocument/documentSymbol` | -| `java_findSymbol` | L0 | `workspace/symbol` | -| `java_getTypeAtPosition` | L1 | `textDocument/hover` (后处理) | -| `java_getCallHierarchy` | L1 | `callHierarchy/incomingCalls` + `outgoingCalls` | -| `java_getTypeHierarchy` | L1 | `typeHierarchy/supertypes` + `subtypes` | +| `lsp_java_getFileStructure` | L0 | `textDocument/documentSymbol` | +| `lsp_java_findSymbol` | L0 | `workspace/symbol` | +| `lsp_java_getTypeAtPosition` | L1 | `textDocument/hover` (后处理) | +| `lsp_java_getCallHierarchy` | L1 | `callHierarchy/incomingCalls` + `outgoingCalls` | +| `lsp_java_getTypeHierarchy` | L1 | `typeHierarchy/supertypes` + `subtypes` | --- @@ -97,7 +97,7 @@ java.project.getProjectContext(fileUri) → ProjectContextResult --- -## Tool 2: `java_getFileImports` +## Tool 2: `lsp_java_getFileImports` **用途**:快速了解一个 Java 文件引用了哪些类型,但不展开细节。 @@ -283,23 +283,23 @@ java.project.getDependencyDetails(fileUri, query?) → DependencyDetailsResult 这些工具直接封装 VS Code 内置命令,不需要新的 Java 后端命令。 -### Tool 5: `java_getFileStructure` +### Tool 5: `lsp_java_getFileStructure` 封装 `vscode.executeDocumentSymbolProvider`,返回文件的类/方法/字段树。 -### Tool 6: `java_findSymbol` +### Tool 6: `lsp_java_findSymbol` 封装 `vscode.executeWorkspaceSymbolProvider`,全局模糊搜索符号。 -### Tool 7: `java_getTypeAtPosition` +### Tool 7: `lsp_java_getTypeAtPosition` 封装 `vscode.executeHoverProvider` + 后处理提取类型签名。 -### Tool 8: `java_getCallHierarchy` +### Tool 8: `lsp_java_getCallHierarchy` 封装 `vscode.prepareCallHierarchy` + `vscode.provideIncomingCalls` / `vscode.provideOutgoingCalls`。 -### Tool 9: `java_getTypeHierarchy` +### Tool 9: `lsp_java_getTypeHierarchy` 封装 `vscode.prepareTypeHierarchy` + `vscode.provideSupertypes` / `vscode.provideSubtypes`。 diff --git a/docs/java-context-tools-skill.md b/docs/java-context-tools-skill.md index 22e169d7..110a325b 100644 --- a/docs/java-context-tools-skill.md +++ b/docs/java-context-tools-skill.md @@ -22,7 +22,7 @@ You have access to 6 Java-specific tools that provide **compiler-accurate** info ## Tool Reference -### `java_getFileStructure` +### `lsp_java_getFileStructure` **Purpose:** Get the structural outline of a Java file — classes, methods, fields with line ranges. @@ -41,7 +41,7 @@ You have access to 6 Java-specific tools that provide **compiler-accurate** info --- -### `java_findSymbol` +### `lsp_java_findSymbol` **Purpose:** Find classes, interfaces, methods by name across the entire workspace. @@ -60,7 +60,7 @@ You have access to 6 Java-specific tools that provide **compiler-accurate** info --- -### `java_getFileImports` +### `lsp_java_getFileImports` **Purpose:** Get all import statements from a Java file, classified by source (jdk/project/external). @@ -75,11 +75,11 @@ You have access to 6 Java-specific tools that provide **compiler-accurate** info **Typical output size:** ~80 tokens -**Important:** This tells you WHAT is imported but not HOW it's used. For details about a specific imported class, use `read_file` to read the relevant source or `java_getTypeAtPosition` on its usage. +**Important:** This tells you WHAT is imported but not HOW it's used. For details about a specific imported class, use `read_file` to read the relevant source or `lsp_java_getTypeAtPosition` on its usage. --- -### `java_getTypeAtPosition` +### `lsp_java_getTypeAtPosition` **Purpose:** Get the exact resolved type of any expression at a specific source position. @@ -99,7 +99,7 @@ You have access to 6 Java-specific tools that provide **compiler-accurate** info --- -### `java_getCallHierarchy` +### `lsp_java_getCallHierarchy` **Purpose:** Find all callers of a method (incoming) or all methods it calls (outgoing). @@ -118,7 +118,7 @@ You have access to 6 Java-specific tools that provide **compiler-accurate** info --- -### `java_getTypeHierarchy` +### `lsp_java_getTypeHierarchy` **Purpose:** Find all supertypes or subtypes of a class/interface. @@ -142,10 +142,10 @@ You have access to 6 Java-specific tools that provide **compiler-accurate** info ### Pattern 1: "Fix a bug in a Java file" ``` -1. java_getFileStructure(file) → Understand what's in the file (100 tokens) +1. lsp_java_getFileStructure(file) → Understand what's in the file (100 tokens) 2. read_file(file, relevant_lines) → Read the buggy method -3. [If needed] java_getFileImports(file) → Check what types are used (80 tokens) -4. [If needed] java_getTypeAtPosition() → Resolve ambiguous types (20 tokens) +3. [If needed] lsp_java_getFileImports(file) → Check what types are used (80 tokens) +4. [If needed] lsp_java_getTypeAtPosition() → Resolve ambiguous types (20 tokens) 5. Edit the file ``` Total tool overhead: ~200 tokens (vs ~3000+ tokens if you blindly dump all imports) @@ -154,36 +154,36 @@ Total tool overhead: ~200 tokens (vs ~3000+ tokens if you blindly dump all impor ``` 1. read_file(pom.xml or build.gradle) → Check build tool, Java version, deps -2. java_findSymbol("Main") → Find entry points (60 tokens) -3. java_getFileStructure(main_file) → Understand main file (100 tokens) -4. java_getFileImports(main_file) → See what libraries are used (80 tokens) +2. lsp_java_findSymbol("Main") → Find entry points (60 tokens) +3. lsp_java_getFileStructure(main_file) → Understand main file (100 tokens) +4. lsp_java_getFileImports(main_file) → See what libraries are used (80 tokens) ``` Total tool overhead: ~240 tokens ### Pattern 3: "Refactor a method signature" ``` -1. java_getCallHierarchy(method, "incoming") → Find all callers (80 tokens) +1. lsp_java_getCallHierarchy(method, "incoming") → Find all callers (80 tokens) 2. For each caller file: - java_getFileStructure(caller_file) → Understand caller context (100 tokens) + lsp_java_getFileStructure(caller_file) → Understand caller context (100 tokens) 3. Edit all affected files ``` ### Pattern 4: "Find all implementations of an interface" ``` -1. java_findSymbol("MyInterface") → Locate the interface (60 tokens) -2. java_getTypeHierarchy(interface_pos, "subtypes") → Find all impls (60 tokens) +1. lsp_java_findSymbol("MyInterface") → Locate the interface (60 tokens) +2. lsp_java_getTypeHierarchy(interface_pos, "subtypes") → Find all impls (60 tokens) 3. For key implementations: - java_getFileStructure(impl_file) → See what they override + lsp_java_getFileStructure(impl_file) → See what they override ``` ### Pattern 5: "Understand dependency usage in a file" ``` -1. java_getFileImports(file) → See all imports classified (80 tokens) +1. lsp_java_getFileImports(file) → See all imports classified (80 tokens) 2. [For unfamiliar external types]: - java_getTypeAtPosition() on usage → See the resolved type/method signature + lsp_java_getTypeAtPosition() on usage → See the resolved type/method signature 3. [If needed] read_file(pom.xml) → Check dependency coordinates ``` @@ -191,7 +191,7 @@ Total tool overhead: ~240 tokens ## Anti-Patterns (What NOT to Do) -### ❌ Don't call java_getTypeAtPosition on obvious types +### ❌ Don't call lsp_java_getTypeAtPosition on obvious types ```java String name = "hello"; // Obviously String — don't call the tool @@ -202,11 +202,11 @@ var result = service.process(input); // Not obvious — DO call the tool You already know `java.util.List`, `java.lang.String`, `java.io.File`. Don't waste a tool call on them. -### ❌ Don't call java_getFileStructure on small files +### ❌ Don't call lsp_java_getFileStructure on small files If a file is < 100 lines, just use `read_file` directly. File structure is most valuable for large files. -### ❌ Don't call java_getCallHierarchy without reading the method first +### ❌ Don't call lsp_java_getCallHierarchy without reading the method first Understand what the method does before tracing its callers. @@ -223,6 +223,6 @@ If a Java tool returns an error or empty result: **For dependency/project info not covered by these tools:** - Read `pom.xml` or `build.gradle` directly with `read_file` - Use `grep_search` to find dependency declarations -- Use `java_getFileImports` to see what external libraries a file uses +- Use `lsp_java_getFileImports` to see what external libraries a file uses **General rule:** If a Java-specific tool fails, fall back to the universal tools (`read_file`, `grep_search`, `list_code_usages`). Don't retry more than once. diff --git a/docs/package-json-tools-snippet.jsonc b/docs/package-json-tools-snippet.jsonc index edfe6216..71896e41 100644 --- a/docs/package-json-tools-snippet.jsonc +++ b/docs/package-json-tools-snippet.jsonc @@ -18,7 +18,7 @@ } }, { - "name": "java_getFileImports", + "name": "lsp_java_getFileImports", "tags": ["java", "imports", "file"], "modelDescription": "Get the classified import list of a Java file. Each import is tagged with its kind (class/interface/enum/annotation) and source (project/external/jdk). Returns ~80 tokens. Use this to understand what a file depends on WITHOUT reading the full source. Follow up with java_getClassDetail only for the 1-2 classes you actually need to understand — do NOT expand every import.", "displayName": "Java: Get File Imports", @@ -74,7 +74,7 @@ } }, { - "name": "java_getFileStructure", + "name": "lsp_java_getFileStructure", "tags": ["java", "structure", "symbols", "outline"], "modelDescription": "Get the structural outline of a Java file: classes, methods, fields, and their line ranges — WITHOUT reading the file content. Returns ~100 tokens. Use this BEFORE read_file to understand what's in a large file, then read_file on specific line ranges. Much more efficient than reading the entire file.", "displayName": "Java: Get File Structure", @@ -90,7 +90,7 @@ } }, { - "name": "java_findSymbol", + "name": "lsp_java_findSymbol", "tags": ["java", "search", "symbol", "workspace"], "modelDescription": "Search for classes, interfaces, methods, or fields by name across the entire Java workspace. Returns precise symbol definitions only (no comments, no string literals, no import statements). More accurate than grep_search for finding Java symbol definitions. Supports partial names and camelCase matching.", "displayName": "Java: Find Symbol", @@ -106,7 +106,7 @@ } }, { - "name": "java_getTypeAtPosition", + "name": "lsp_java_getTypeAtPosition", "tags": ["java", "type", "inference", "hover"], "modelDescription": "Get the compiler-resolved type of any expression at a specific source position. Returns the EXACT type (~20 tokens), using the Java compiler's own type inference. Use this for: var declarations, lambda parameters, generic type arguments, chained method call return types. Do NOT use for obvious types like String literals or explicit type declarations.", "displayName": "Java: Get Type at Position", @@ -130,7 +130,7 @@ } }, { - "name": "java_getCallHierarchy", + "name": "lsp_java_getCallHierarchy", "tags": ["java", "calls", "hierarchy", "impact"], "modelDescription": "Find all callers of a method (incoming) or all methods it calls (outgoing). More precise than list_code_usages — returns only actual method CALLS, not imports, declarations, or comments. Use 'incoming' before modifying a method signature to find all callers. Use 'outgoing' to understand what a method depends on.", "displayName": "Java: Get Call Hierarchy", @@ -159,7 +159,7 @@ } }, { - "name": "java_getTypeHierarchy", + "name": "lsp_java_getTypeHierarchy", "tags": ["java", "inheritance", "hierarchy", "interface"], "modelDescription": "Find all supertypes or subtypes of a class/interface. Catches things grep misses: indirect implementations, anonymous classes, lambda implementations of functional interfaces. Use 'subtypes' when modifying an interface to find ALL implementations. Use 'supertypes' to understand the inheritance chain.", "displayName": "Java: Get Type Hierarchy", diff --git a/lsp-ai-tools.md b/lsp-ai-tools.md index 68c9a3f3..3fa5107e 100644 --- a/lsp-ai-tools.md +++ b/lsp-ai-tools.md @@ -695,7 +695,7 @@ class TypeQueryTool implements vscode.LanguageModelTool<{ uri: string; line: num } }, { - "name": "java_findSymbol", + "name": "lsp_java_findSymbol", "displayName": "Find Symbol in Workspace", "modelDescription": "Search for a class, interface, method or field by name across the entire workspace. Returns exact matches with kind (Class/Interface/Method) and file location. More precise than grep - no noise from comments or imports.", "inputSchema": { diff --git a/package.json b/package.json index 61c5df58..2197e156 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ ], "languageModelTools": [ { - "name": "java_getFileStructure", + "name": "lsp_java_getFileStructure", "toolReferenceName": "javaFileStructure", "modelDescription": "Get the structure (classes, methods, fields, inner classes) of a Java file. Returns a hierarchical outline with symbol kinds and line ranges. Use this first when you need to understand a file's layout before reading specific sections.", "displayName": "Java: Get File Structure", @@ -68,7 +68,7 @@ } }, { - "name": "java_findSymbol", + "name": "lsp_java_findSymbol", "toolReferenceName": "javaFindSymbol", "modelDescription": "Search for Java symbols (classes, interfaces, methods, fields) across the workspace by name. Supports partial/fuzzy matching. Use this to find where a class or method is defined.", "displayName": "Java: Find Symbol", @@ -90,7 +90,7 @@ } }, { - "name": "java_getFileImports", + "name": "lsp_java_getFileImports", "toolReferenceName": "javaFileImports", "modelDescription": "Get all import statements from a Java file, classified by source (jdk/project/external).", "displayName": "Java: Get File Imports", @@ -108,7 +108,7 @@ } }, { - "name": "java_getTypeAtPosition", + "name": "lsp_java_getTypeAtPosition", "toolReferenceName": "javaTypeAtPosition", "modelDescription": "Get the compiler-resolved type signature at a specific position. Returns fully qualified type, method signature, or field declaration. Use this when you see var, lambdas, or generics and need the exact type.", "displayName": "Java: Get Type at Position", @@ -134,7 +134,7 @@ } }, { - "name": "java_getCallHierarchy", + "name": "lsp_java_getCallHierarchy", "toolReferenceName": "javaCallHierarchy", "modelDescription": "Get incoming callers or outgoing callees of a method. PREFER THIS over grep_search when you need to find which methods call a specific method — returns precise call sites without noise from comments, imports, or string matches. Use 'incoming' for callers, 'outgoing' for callees.", "displayName": "Java: Get Call Hierarchy", @@ -166,7 +166,7 @@ } }, { - "name": "java_getTypeHierarchy", + "name": "lsp_java_getTypeHierarchy", "toolReferenceName": "javaTypeHierarchy", "modelDescription": "Get supertypes or subtypes/implementors of a type. PREFER THIS over grep_search('extends|implements') — returns only actual inheritance relationships. Use 'supertypes' for parents, 'subtypes' for children.", "displayName": "Java: Get Type Hierarchy", @@ -1231,7 +1231,13 @@ ], "chatSkills": [ { - "path": "./resources/skills/java-context-tools/SKILL.md" + "path": "./resources/skills/java-lsp-tools/SKILL.md" + } + ], + "chatInstructions": [ + { + "path": "./resources/instruments/loadJavaSkills.md", + "when": "java:projectManagerActivated" } ] }, diff --git a/resources/instruments/loadJavaSkills.md b/resources/instruments/loadJavaSkills.md new file mode 100644 index 00000000..59408676 --- /dev/null +++ b/resources/instruments/loadJavaSkills.md @@ -0,0 +1,16 @@ +--- +description: Guidelines for using Java LSP tools when working with Java codebases +applyTo: '**/*.java' +--- + +MANDATORY: For ANY Java code task (bug fixing, code analysis, refactoring): + +1. LOAD TOOLS FIRST — Call tool_search_tool_regex with pattern: + lsp_java_findSymbol|lsp_java_getFileStructure|lsp_java_getCallHierarchy|lsp_java_getTypeHierarchy|lsp_java_getTypeAtPosition|lsp_java_getFileImports + +2. ALWAYS prefer lsp_java_* tools over grep_search/read_file for .java files. + These tools provide compiler-accurate results with ~90% fewer tokens. + +3. Read the "java-lsp-tools" skill for detailed per-tool usage guidance. + +Do NOT skip step 1. Do NOT use grep_search as a substitute for lsp_java_* tools on .java files. \ No newline at end of file diff --git a/resources/skills/java-context-tools/SKILL.md b/resources/skills/java-lsp-tools/SKILL.md similarity index 74% rename from resources/skills/java-context-tools/SKILL.md rename to resources/skills/java-lsp-tools/SKILL.md index 69e9861b..6fd6633c 100644 --- a/resources/skills/java-context-tools/SKILL.md +++ b/resources/skills/java-lsp-tools/SKILL.md @@ -1,9 +1,9 @@ --- -name: java-context-tools +name: java-lsp-tools description: Compiler-accurate Java code intelligence tools powered by the Java Language Server. ALWAYS load this skill when the workspace contains Java, Maven (pom.xml), or Gradle (build.gradle) projects. Use these tools to find symbol definitions, get type/call hierarchies, resolve types, inspect file outlines, and check imports in Java source files. Prefer over grep_search or read_file for any Java code navigation, understanding, debugging, or refactoring task. --- -# Java Context Tools +# Java LSP Tools Compiler-accurate Java code intelligence via the Java Language Server (jdtls). These 6 tools provide structured, low-token answers that are more precise than `grep_search` or `read_file` for Java code. @@ -11,7 +11,7 @@ Compiler-accurate Java code intelligence via the Java Language Server (jdtls). T These tools are **deferred** and must be discovered before first use. Activate all 6 tools at once with `tool_search_tool_regex` using pattern: -`java_findSymbol|java_getFileStructure|java_getCallHierarchy|java_getTypeHierarchy|java_getTypeAtPosition|java_getFileImports` +`lsp_java_findSymbol|lsp_java_getFileStructure|lsp_java_getCallHierarchy|lsp_java_getTypeHierarchy|lsp_java_getTypeAtPosition|lsp_java_getFileImports` You only need to do this **once per session**. @@ -21,54 +21,54 @@ For Java source files, **always prefer these tools over generic alternatives**: | You're doing... | Use instead | Why | |---|---|---| -| Find where a class/method is defined | `java_findSymbol` | ~60 tokens vs ~500 for grep (no comment/import noise) | -| Find all callers of a method | `java_getCallHierarchy("incoming")` | ~80 tokens vs ~3000 for grep (precise call sites only) | -| Find all implementations of an interface | `java_getTypeHierarchy("subtypes")` | ~60 tokens vs ~1000 for grep | -| Check a `var`/lambda/generic type | `java_getTypeAtPosition` | ~20 tokens vs guessing wrong | -| Search in non-Java files (xml, yaml, gradle) | Keep using `grep_search` | java_* tools only work on Java source | -| Search for string literals or comments | Keep using `grep_search` | java_* tools return symbol definitions only | +| Find where a class/method is defined | `lsp_java_findSymbol` | ~60 tokens vs ~500 for grep (no comment/import noise) | +| Find all callers of a method | `lsp_java_getCallHierarchy("incoming")` | ~80 tokens vs ~3000 for grep (precise call sites only) | +| Find all implementations of an interface | `lsp_java_getTypeHierarchy("subtypes")` | ~60 tokens vs ~1000 for grep | +| Check a `var`/lambda/generic type | `lsp_java_getTypeAtPosition` | ~20 tokens vs guessing wrong | +| Search in non-Java files (xml, yaml, gradle) | Keep using `grep_search` | lsp_java_* tools only work on Java source | +| Search for string literals or comments | Keep using `grep_search` | lsp_java_* tools return symbol definitions only | -**Rule of thumb**: If you're searching for a Java symbol name in `.java` files, there is almost always a `java_*` tool that returns more precise results with fewer tokens than `grep_search`. +**Rule of thumb**: If you're searching for a Java symbol name in `.java` files, there is almost always a `lsp_java_*` tool that returns more precise results with fewer tokens than `grep_search`. ## Anti-patterns (Avoid these) ❌ **Don't**: Use `grep_search("decodeLbs")` to find who calls `decodeLbs()` - Returns 8+ matches including declaration, comments, imports → ~3000 output tokens -✅ **Do**: Use `java_getCallHierarchy(uri, line, char, "incoming")` +✅ **Do**: Use `lsp_java_getCallHierarchy(uri, line, char, "incoming")` - Returns only actual call sites → ~80 output tokens ❌ **Don't**: Use `grep_search("class.*extends BaseDecoder")` to find subclasses -✅ **Do**: Use `java_getTypeHierarchy(uri, line, char, "subtypes")` +✅ **Do**: Use `lsp_java_getTypeHierarchy(uri, line, char, "subtypes")` ❌ **Don't**: Read entire 1000+ line file to understand its structure -✅ **Do**: Use `java_getFileStructure(uri)` first, then `read_file` on specific line ranges +✅ **Do**: Use `lsp_java_getFileStructure(uri)` first, then `read_file` on specific line ranges ## Tools All tools accept **workspace-relative paths** (e.g. `src/main/java/com/example/MyClass.java`) or full file URIs. All return structured JSON, each response < 200 tokens. -### `java_getFileStructure` +### `lsp_java_getFileStructure` Get hierarchical outline (classes, methods, fields) with line ranges. Input: `{ uri }` → Output: symbol tree with `[L start-end]` ranges (~100 tokens) -### `java_findSymbol` +### `lsp_java_findSymbol` Search for symbol definitions by name across the workspace. Supports partial/fuzzy matching. Input: `{ query }` → Output: up to 20 results with `{ name, kind, location }` (~60 tokens) -### `java_getFileImports` +### `lsp_java_getFileImports` Get all imports classified by source (jdk/project/external). Input: `{ uri }` → Output: classified import list (~80 tokens) -### `java_getTypeAtPosition` +### `lsp_java_getTypeAtPosition` Get compiler-resolved type signature at a specific position. Input: `{ uri, line, character }` (0-based) → Output: fully resolved type (~20 tokens) -### `java_getCallHierarchy` +### `lsp_java_getCallHierarchy` Find all callers (incoming) or callees (outgoing) of a method. Input: `{ uri, line, character, direction }` (0-based, direction: `"incoming"` | `"outgoing"`) → Output: list of `{ name, detail, location }` (~80 tokens) -### `java_getTypeHierarchy` +### `lsp_java_getTypeHierarchy` Find supertypes or subtypes/implementors of a type. Input: `{ uri, line, character, direction }` (0-based, direction: `"supertypes"` | `"subtypes"`) → Output: list of `{ name, kind, location }` (~60 tokens) @@ -86,8 +86,8 @@ Most tasks follow the pattern: **findSymbol → getFileStructure → targeted to ## Fallback -- **`java_findSymbol` returns empty**: +- **`lsp_java_findSymbol` returns empty**: - Symbol may not exist yet → switch to `read_file` + `grep_search` to confirm, then create it - Spelling/query too specific → retry once with a shorter keyword (e.g. `"UserSvc"` instead of `"UserServiceImpl"`) -- **Path error** (e.g. "Unable to resolve nonexistent file"): Use `java_findSymbol` to discover the correct file path first, then retry. +- **Path error** (e.g. "Unable to resolve nonexistent file"): Use `lsp_java_findSymbol` to discover the correct file path first, then retry. - **Tool error / empty result** (jdtls not ready, file not in project): Fall back to `read_file` + `grep_search`. Don't retry more than once. diff --git a/src/copilot/tools/javaContextTools.ts b/src/copilot/tools/javaContextTools.ts index dbb027b2..6b0117bb 100644 --- a/src/copilot/tools/javaContextTools.ts +++ b/src/copilot/tools/javaContextTools.ts @@ -7,12 +7,12 @@ * Java Context Tools — First Batch (Zero-Blocking) * * These 6 tools are all non-blocking after jdtls is ready: - * 1. java_getFileStructure — LSP documentSymbol - * 2. java_findSymbol — LSP workspaceSymbol - * 3. java_getFileImports — jdtls AST-only command (no type resolution) - * 4. java_getTypeAtPosition — LSP hover (post-processed) - * 5. java_getCallHierarchy — LSP call hierarchy - * 6. java_getTypeHierarchy — LSP type hierarchy + * 1. lsp_java_getFileStructure — LSP documentSymbol + * 2. lsp_java_findSymbol — LSP workspaceSymbol + * 3. lsp_java_getFileImports — jdtls AST-only command (no type resolution) + * 4. lsp_java_getTypeAtPosition — LSP hover (post-processed) + * 5. lsp_java_getCallHierarchy — LSP call hierarchy + * 6. lsp_java_getTypeHierarchy — LSP type hierarchy * * Design principles: * - Each tool returns < 200 tokens @@ -73,7 +73,7 @@ function resolveFileUri(input: string): vscode.Uri { } // ============================================================ -// Tool 1: java_getFileStructure (LSP — Document Symbol) +// Tool 1: lsp_java_getFileStructure (LSP — Document Symbol) // ============================================================ interface FileStructureInput { @@ -108,7 +108,7 @@ function formatSymbols(symbols: vscode.DocumentSymbol[], indent: number): string } // ============================================================ -// Tool 2: java_findSymbol (LSP — Workspace Symbol) +// Tool 2: lsp_java_findSymbol (LSP — Workspace Symbol) // ============================================================ interface FindSymbolInput { @@ -135,7 +135,7 @@ const findSymbolTool: LanguageModelTool = { }; // ============================================================ -// Tool 3: java_getFileImports (jdtls — AST-only, non-blocking) +// Tool 3: lsp_java_getFileImports (jdtls — AST-only, non-blocking) // ============================================================ interface FileImportsInput { @@ -158,7 +158,7 @@ const fileImportsTool: LanguageModelTool = { }; // ============================================================ -// Tool 4: java_getTypeAtPosition (LSP — Hover post-processed) +// Tool 4: lsp_java_getTypeAtPosition (LSP — Hover post-processed) // ============================================================ interface TypeAtPositionInput { @@ -202,7 +202,7 @@ function extractTypeSignature(hovers: vscode.Hover[] | undefined): object { } // ============================================================ -// Tool 5: java_getCallHierarchy (LSP — Call Hierarchy) +// Tool 5: lsp_java_getCallHierarchy (LSP — Call Hierarchy) // ============================================================ interface CallHierarchyInput { @@ -257,7 +257,7 @@ const callHierarchyTool: LanguageModelTool = { }; // ============================================================ -// Tool 6: java_getTypeHierarchy (LSP — Type Hierarchy) +// Tool 6: lsp_java_getTypeHierarchy (LSP — Type Hierarchy) // ============================================================ interface TypeHierarchyInput { @@ -320,11 +320,11 @@ export function registerJavaContextTools(context: vscode.ExtensionContext): void } context.subscriptions.push( - lmApi.registerTool("java_getFileStructure", fileStructureTool), - lmApi.registerTool("java_findSymbol", findSymbolTool), - lmApi.registerTool("java_getFileImports", fileImportsTool), - lmApi.registerTool("java_getTypeAtPosition", typeAtPositionTool), - lmApi.registerTool("java_getCallHierarchy", callHierarchyTool), - lmApi.registerTool("java_getTypeHierarchy", typeHierarchyTool), + lmApi.registerTool("lsp_java_getFileStructure", fileStructureTool), + lmApi.registerTool("lsp_java_findSymbol", findSymbolTool), + lmApi.registerTool("lsp_java_getFileImports", fileImportsTool), + lmApi.registerTool("lsp_java_getTypeAtPosition", typeAtPositionTool), + lmApi.registerTool("lsp_java_getCallHierarchy", callHierarchyTool), + lmApi.registerTool("lsp_java_getTypeHierarchy", typeHierarchyTool), ); } From 8d4cf6db0dce75e7d68ddfadf89ca71ef419d3de Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Wed, 11 Mar 2026 14:49:22 +0800 Subject: [PATCH 04/14] fix: update --- resources/instruments/loadJavaSkills.md | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/instruments/loadJavaSkills.md b/resources/instruments/loadJavaSkills.md index 59408676..10fe8dd0 100644 --- a/resources/instruments/loadJavaSkills.md +++ b/resources/instruments/loadJavaSkills.md @@ -1,6 +1,5 @@ --- description: Guidelines for using Java LSP tools when working with Java codebases -applyTo: '**/*.java' --- MANDATORY: For ANY Java code task (bug fixing, code analysis, refactoring): From c31745c799e663438f630c2fbace8fa04714c607 Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Wed, 11 Mar 2026 15:39:45 +0800 Subject: [PATCH 05/14] fix: adjust the prompt to load lsp java tools to replace search subagent --- resources/instruments/loadJavaSkills.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/instruments/loadJavaSkills.md b/resources/instruments/loadJavaSkills.md index 10fe8dd0..28ed875d 100644 --- a/resources/instruments/loadJavaSkills.md +++ b/resources/instruments/loadJavaSkills.md @@ -1,5 +1,5 @@ --- -description: Guidelines for using Java LSP tools when working with Java codebases +description: ALWAYS load in Java projects. Use lsp_java_* tools instead of grep_search, semantic_search, and runSubagent for Java code navigation, symbol lookup, call tracing, and type resolution. These compiler-accurate tools replace generic search with precise, low-token results. --- MANDATORY: For ANY Java code task (bug fixing, code analysis, refactoring): @@ -7,9 +7,9 @@ MANDATORY: For ANY Java code task (bug fixing, code analysis, refactoring): 1. LOAD TOOLS FIRST — Call tool_search_tool_regex with pattern: lsp_java_findSymbol|lsp_java_getFileStructure|lsp_java_getCallHierarchy|lsp_java_getTypeHierarchy|lsp_java_getTypeAtPosition|lsp_java_getFileImports -2. ALWAYS prefer lsp_java_* tools over grep_search/read_file for .java files. +2. ALWAYS prefer lsp_java_* tools over grep_search/read_file/semantic_search/runSubagent for .java files. These tools provide compiler-accurate results with ~90% fewer tokens. 3. Read the "java-lsp-tools" skill for detailed per-tool usage guidance. -Do NOT skip step 1. Do NOT use grep_search as a substitute for lsp_java_* tools on .java files. \ No newline at end of file +Do NOT skip step 1. Do NOT use grep_search or runSubagent as a substitute for lsp_java_* tools on .java files. \ No newline at end of file From 19e6cad082aaf41730946973ca5e4c5cb13a2e1a Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Wed, 11 Mar 2026 16:29:54 +0800 Subject: [PATCH 06/14] perf: update when --- package.json | 5 +++-- src/constants.ts | 1 + src/extension.ts | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 2197e156..324339ab 100644 --- a/package.json +++ b/package.json @@ -1231,13 +1231,14 @@ ], "chatSkills": [ { - "path": "./resources/skills/java-lsp-tools/SKILL.md" + "path": "./resources/skills/java-lsp-tools/SKILL.md", + "when": "java:lspToolsReady" } ], "chatInstructions": [ { "path": "./resources/instruments/loadJavaSkills.md", - "when": "java:projectManagerActivated" + "when": "java:lspToolsReady" } ] }, diff --git a/src/constants.ts b/src/constants.ts index 8bb176a5..3e17f032 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -9,6 +9,7 @@ export namespace Context { export const WORKSPACE_CONTAINS_BUILD_FILES: string = "java:workspaceContainsBuildFiles"; export const RELOAD_PROJECT_ACTIVE: string = "java:reloadProjectActive"; export const SHOW_DEPRECATED_TASKS: string = "java:showDeprecatedTasks"; + export const LSP_TOOLS_READY: string = "java:lspToolsReady"; } export namespace Explorer { diff --git a/src/extension.ts b/src/extension.ts index 273f6e79..684187ee 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -90,8 +90,8 @@ async function activateExtension(_operationId: string, context: ExtensionContext // Register Copilot context providers after Java Language Server is ready languageServerApiManager.ready().then((isReady) => { if (isReady) { - registerCopilotContextProviders(context); registerJavaContextTools(context); + contextManager.setContextValue(Context.LSP_TOOLS_READY, true); } }); } From a90af1d95c8bddcd0eee91a69bebdda335b6957a Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Mon, 16 Mar 2026 11:26:23 +0800 Subject: [PATCH 07/14] feat: lsp tools and chat instrument --- docs/java-context-tools-design.md | 328 ------------------ docs/java-context-tools-skill.md | 228 ------------ docs/package-json-tools-snippet.jsonc | 191 ---------- package.json | 9 +- .../javaLspContext.instructions.md | 43 +++ resources/instruments/loadJavaSkills.md | 15 - src/extension.ts | 1 - 7 files changed, 47 insertions(+), 768 deletions(-) delete mode 100644 docs/java-context-tools-design.md delete mode 100644 docs/java-context-tools-skill.md delete mode 100644 docs/package-json-tools-snippet.jsonc create mode 100644 resources/instruments/javaLspContext.instructions.md delete mode 100644 resources/instruments/loadJavaSkills.md diff --git a/docs/java-context-tools-design.md b/docs/java-context-tools-design.md deleted file mode 100644 index e667a99e..00000000 --- a/docs/java-context-tools-design.md +++ /dev/null @@ -1,328 +0,0 @@ -# Java Context Tools — 重新设计方案 - -## 设计哲学 - -``` -旧模式(Push All): 新模式(Lazy Pull): - - Copilot 补全请求 AI 决定调什么 - ↓ ↓ - 一次性推送所有内容 按需逐层深入 - ↓ ↓ - 3000+ tokens 噪音 每次 < 200 tokens 精准信息 -``` - -**核心原则:** -1. **AI 驱动调用**:AI 根据任务决定需要什么信息,而不是我们猜测它需要什么 -2. **分层粒度**:摘要 → 签名 → 详情,每层独立可用 -3. **每次 < 200 tokens**:宁可多调一次,不要一次塞 3000 tokens -4. **结构化 JSON**:AI 直接解析,不需要从文本中提取信息 -5. **Skill 文档引导**:教 AI 什么时候用什么工具,形成高效工作流 - ---- - -## Tool 清单 - -### 总览 - -| Tool | 粒度 | 用途 | 典型 token 量 | -|------|------|------|--------------| -| `java_getProjectContext` | L0 | 项目级概览 | ~100 | -| `lsp_java_getFileImports` | L0 | 文件的 import 列表 | ~80 | -| `java_getClassDetail` | L1 | 单个类的签名+方法列表 | ~150 | -| `java_getDependencyDetails` | L1 | 指定依赖的 GAV+scope | ~50 | - -### 与 LSP 标准 Tool 的配合 - -| 标准 LSP Tool(VS Code 内置命令封装) | 粒度 | 对应 LSP 请求 | -|--------------------------------------|------|-------------| -| `lsp_java_getFileStructure` | L0 | `textDocument/documentSymbol` | -| `lsp_java_findSymbol` | L0 | `workspace/symbol` | -| `lsp_java_getTypeAtPosition` | L1 | `textDocument/hover` (后处理) | -| `lsp_java_getCallHierarchy` | L1 | `callHierarchy/incomingCalls` + `outgoingCalls` | -| `lsp_java_getTypeHierarchy` | L1 | `typeHierarchy/supertypes` + `subtypes` | - ---- - -## Tool 1: `java_getProjectContext` - -**用途**:AI 进入一个 Java 项目时的第一个调用。快速了解项目是什么。 - -### 输入 - -```typescript -{ - fileUri: string // 项目中任意一个文件的 URI -} -``` - -### 输出(示例,~100 tokens) - -```json -{ - "project": { - "name": "my-order-service", - "buildTool": "Maven", - "javaVersion": "17", - "sourceLevel": "17", - "targetLevel": "17", - "sourceRoots": ["src/main/java", "src/test/java"], - "moduleName": null - }, - "dependencies": { - "total": 47, - "direct": [ - "org.springframework.boot:spring-boot-starter-web:3.2.1", - "org.springframework.boot:spring-boot-starter-data-jpa:3.2.1", - "com.google.code.gson:gson:2.10.1", - "org.projectlombok:lombok:1.18.30" - ], - "directCount": 8, - "transitiveCount": 39 - }, - "projectReferences": ["common-lib", "shared-model"] -} -``` - -**关键设计决策:** -- `dependencies.direct` 只列直接依赖的 GAV(不列传递依赖——AI 很少需要) -- 传递依赖只给个数量,AI 需要时再用 `java_getDependencyDetails` 深入 -- `sourceRoots` 帮助 AI 理解项目结构,知道源码在哪 - -### Java 后端命令 - -``` -java.project.getProjectContext(fileUri) → ProjectContextResult -``` - ---- - -## Tool 2: `lsp_java_getFileImports` - -**用途**:快速了解一个 Java 文件引用了哪些类型,但不展开细节。 - -### 输入 - -```typescript -{ - fileUri: string // Java 文件的 URI -} -``` - -### 输出(示例,~80 tokens) - -```json -{ - "file": "src/main/java/com/example/OrderService.java", - "imports": [ - { "name": "com.example.model.Order", "kind": "class", "source": "project" }, - { "name": "com.example.model.OrderStatus", "kind": "enum", "source": "project" }, - { "name": "com.example.repo.OrderRepository", "kind": "interface", "source": "project" }, - { "name": "org.springframework.stereotype.Service", "kind": "annotation", "source": "external", "artifact": "spring-context" }, - { "name": "org.springframework.transaction.annotation.Transactional", "kind": "annotation", "source": "external", "artifact": "spring-tx" }, - { "name": "java.util.List", "kind": "interface", "source": "jdk" }, - { "name": "java.util.Optional", "kind": "class", "source": "jdk" } - ], - "staticImports": [ - { "name": "org.junit.Assert.assertEquals", "memberKind": "method", "source": "external" } - ] -} -``` - -**关键设计决策:** -- `source` 三分法:`"project"` / `"external"` / `"jdk"` —— AI 最需要了解的是 `project` 的类 -- `kind` 直接给出类型类别,AI 不需要额外查 -- JDK 类标记为 `"jdk"` 而非 `"external"`,AI 知道不需要深入查 -- `artifact` 字段只对 external 有效,帮助 AI 关联到具体依赖 - -### Java 后端命令 - -``` -java.project.getFileImports(fileUri) → FileImportsResult -``` - ---- - -## Tool 3: `java_getClassDetail` - -**用途**:AI 确定需要了解某个类后,获取它的签名级别信息。 - -### 输入 - -```typescript -{ - qualifiedName: string // 全限定类名,如 "com.example.model.Order" - fileUri?: string // 可选:提供 file context 加速查找 -} -``` - -### 输出(项目内源码类,~150 tokens) - -```json -{ - "qualifiedName": "com.example.model.Order", - "kind": "class", - "uri": "file:///workspace/src/main/java/com/example/model/Order.java", - "source": "project", - "signature": "public class Order implements Serializable", - "superClass": "java.lang.Object", - "interfaces": ["java.io.Serializable"], - "javadocSummary": "Represents a customer order with line items and pricing.", - "constructors": [ - "Order()", - "Order(String orderId, Customer customer)" - ], - "methods": [ - "String getOrderId()", - "Customer getCustomer()", - "List getItems()", - "OrderStatus getStatus()", - "void setStatus(OrderStatus status)", - "BigDecimal getTotalPrice()", - "void addItem(OrderItem item)", - "void removeItem(String itemId)" - ], - "fields": [ - "private String orderId", - "private Customer customer", - "private List items", - "private OrderStatus status" - ], - "annotations": ["@Entity", "@Table(name = \"orders\")"] -} -``` - -### 输出(外部依赖类,~80 tokens,更精简) - -```json -{ - "qualifiedName": "com.google.gson.Gson", - "kind": "class", - "source": "external", - "artifact": "com.google.code.gson:gson:2.10.1", - "signature": "public final class Gson", - "javadocSummary": "This is the main class for using Gson.", - "methods": [ - "String toJson(Object src)", - " T fromJson(String json, Class classOfT)", - " T fromJson(String json, Type typeOfT)", - " T fromJson(Reader json, Type typeOfT)", - "JsonElement toJsonTree(Object src)", - "... (38 more public methods)" - ] -} -``` - -**关键设计决策:** -- 项目源码给完整信息(含 fields、annotations) -- 外部依赖只给签名级别,方法超过一定数量用 `"... (N more)"` 截断 -- 不给 JavaDoc 全文,只给第一句 summary -- `artifact` 字段帮 AI 关联到 pom.xml 中的依赖声明 - -### Java 后端命令 - -``` -java.project.getClassDetail(qualifiedName, fileUri?) → ClassDetailResult -``` - ---- - -## Tool 4: `java_getDependencyDetails` - -**用途**:AI 需要了解某个具体依赖的详细信息(排查版本冲突、检查 scope 等)。 - -### 输入 - -```typescript -{ - fileUri: string // 项目中的文件 URI(用于定位项目) - query?: string // 可选:按名称过滤依赖(模糊匹配) -} -``` - -### 输出(示例,~120 tokens) - -```json -{ - "dependencies": [ - { - "groupId": "com.google.code.gson", - "artifactId": "gson", - "version": "2.10.1", - "scope": "compile", - "isDirect": true, - "jarPath": "gson-2.10.1.jar" - }, - { - "groupId": "com.google.errorprone", - "artifactId": "error_prone_annotations", - "version": "2.18.0", - "scope": "compile", - "isDirect": false, - "broughtBy": "com.google.guava:guava:32.1.3-jre", - "jarPath": "error_prone_annotations-2.18.0.jar" - } - ] -} -``` - -**关键设计决策:** -- `query` 参数支持模糊搜索,AI 不需要拉全量依赖列表 -- `broughtBy` 告诉 AI 传递依赖是谁引入的(排查冲突的关键信息) -- `isDirect` + `scope` 帮 AI 判断依赖的实际作用范围 - -### Java 后端命令 - -``` -java.project.getDependencyDetails(fileUri, query?) → DependencyDetailsResult -``` - ---- - -## Tool 5-9: 标准 LSP 能力封装 - -这些工具直接封装 VS Code 内置命令,不需要新的 Java 后端命令。 - -### Tool 5: `lsp_java_getFileStructure` - -封装 `vscode.executeDocumentSymbolProvider`,返回文件的类/方法/字段树。 - -### Tool 6: `lsp_java_findSymbol` - -封装 `vscode.executeWorkspaceSymbolProvider`,全局模糊搜索符号。 - -### Tool 7: `lsp_java_getTypeAtPosition` - -封装 `vscode.executeHoverProvider` + 后处理提取类型签名。 - -### Tool 8: `lsp_java_getCallHierarchy` - -封装 `vscode.prepareCallHierarchy` + `vscode.provideIncomingCalls` / `vscode.provideOutgoingCalls`。 - -### Tool 9: `lsp_java_getTypeHierarchy` - -封装 `vscode.prepareTypeHierarchy` + `vscode.provideSupertypes` / `vscode.provideSubtypes`。 - ---- - -## 实现架构 - -``` -┌────────────────────────────────────────────────────────────┐ -│ Copilot Chat (LLM) │ -│ ↓ 读取 skill 文档了解工具用法 │ -│ ↓ 根据任务决定调用哪个工具 │ -│ LanguageModelTool 接口(package.json 注册) │ -│ ↓ │ -│ TS 适配层(src/copilot/tools/*.ts) │ -│ ↓ │ -│ ├── Tool 1-4: delegateCommandHandler → jdtls 扩展命令 │ -│ └── Tool 5-9: vscode.commands.executeCommand → LSP 标准 │ -│ │ -│ Java 后端(jdtls.ext) │ -│ ├── java.project.getProjectContext │ -│ ├── java.project.getFileImports │ -│ ├── java.project.getClassDetail │ -│ └── java.project.getDependencyDetails │ -└────────────────────────────────────────────────────────────┘ -``` diff --git a/docs/java-context-tools-skill.md b/docs/java-context-tools-skill.md deleted file mode 100644 index 110a325b..00000000 --- a/docs/java-context-tools-skill.md +++ /dev/null @@ -1,228 +0,0 @@ -# Java Context Tools — Skill Document for LLM - -> This document teaches you how to effectively use the Java-specific tools when working on Java projects. Read this BEFORE using any of these tools. - -## When to Use These Tools - -You have access to 6 Java-specific tools that provide **compiler-accurate** information about Java projects. These tools are faster and more accurate than `grep_search` or `read_file` for understanding Java code structure, types, and relationships. - -**Use these tools when:** -- You need to understand a Java file's structure without reading the entire file -- You need to find where a class, interface, or method is defined in the workspace -- You need to know what a Java file imports and which libraries it depends on -- You need to resolve `var`, lambda, or generic types that aren't obvious from source code -- You need to trace call chains or type hierarchies accurately - -**Do NOT use these tools when:** -- The question can be answered by reading a single file (use `read_file` instead) -- You're working on non-Java files (pom.xml, build.gradle, yaml, etc.) -- You just need to do a text search (use `grep_search`) - ---- - -## Tool Reference - -### `lsp_java_getFileStructure` - -**Purpose:** Get the structural outline of a Java file — classes, methods, fields with line ranges. - -**When to call:** -- FIRST tool to call when you need to understand a Java file -- When you need to find which line a specific method starts at -- When planning modifications to a large file (500+ lines) - -**Input:** `{ uri: "" }` - -**Output:** Tree of symbols with kinds (Class, Method, Field, etc.) and line ranges. - -**Typical output size:** ~100 tokens - -**This replaces reading an entire file just to understand its structure.** Use this first, then `read_file` on specific line ranges. - ---- - -### `lsp_java_findSymbol` - -**Purpose:** Find classes, interfaces, methods by name across the entire workspace. - -**When to call:** -- When you know a class name (or partial name) but don't know which file it's in -- When searching for implementations of a pattern (e.g., all `*Controller` classes) -- When `grep_search` returns too many false positives (comments, strings, imports) - -**Input:** `{ query: "PaymentGateway" }` - -**Output:** Up to 20 matching symbols with name, kind, and file location. - -**Typical output size:** ~60 tokens - -**This is more precise than grep** — it only returns actual symbol definitions, not mentions in comments or strings. - ---- - -### `lsp_java_getFileImports` - -**Purpose:** Get all import statements from a Java file, classified by source (jdk/project/external). - -**When to call:** -- When you need to understand what types a file uses WITHOUT reading the full source -- When deciding which external libraries a file depends on -- When checking if a file already imports a class you want to use - -**Input:** `{ fileUri: "" }` - -**Output:** Classified import list with kind and source for regular and static imports. - -**Typical output size:** ~80 tokens - -**Important:** This tells you WHAT is imported but not HOW it's used. For details about a specific imported class, use `read_file` to read the relevant source or `lsp_java_getTypeAtPosition` on its usage. - ---- - -### `lsp_java_getTypeAtPosition` - -**Purpose:** Get the exact resolved type of any expression at a specific source position. - -**When to call:** -- When you see `var` and need to know the actual type -- When you need to know the return type of a chained method call -- When working with generic types and need the resolved type parameters -- When lambda parameters don't have explicit types - -**Input:** `{ uri: "", line: 42, character: 15 }` (0-based) - -**Output:** The resolved type signature at that position. - -**Typical output size:** ~20 tokens - -**This uses the Java compiler's own type inference** — it's 100% accurate, unlike guessing from source code. - ---- - -### `lsp_java_getCallHierarchy` - -**Purpose:** Find all callers of a method (incoming) or all methods it calls (outgoing). - -**When to call:** -- Before modifying a method's signature — to find all callers that need updating -- When doing impact analysis of a change -- When understanding the flow of a specific feature - -**Input:** `{ uri: "", line: 45, character: 20, direction: "incoming" | "outgoing" }` (0-based) - -**Output:** List of caller/callee methods with file locations. - -**Typical output size:** ~80 tokens - -**This is more precise than `list_code_usages`** — it only returns actual CALLS, not imports, declarations, or comments. - ---- - -### `lsp_java_getTypeHierarchy` - -**Purpose:** Find all supertypes or subtypes of a class/interface. - -**When to call:** -- When modifying an interface and need to find ALL implementations (including indirect ones) -- When understanding an inheritance chain -- When checking if a class can be used where a specific type is expected - -**Input:** `{ uri: "", line: 10, character: 14, direction: "supertypes" | "subtypes" }` (0-based) - -**Output:** Type hierarchy with symbol kinds and locations. - -**Typical output size:** ~60 tokens - -**This catches things grep misses** — indirect implementations, anonymous classes, lambda implementations. - ---- - -## Recommended Workflow Patterns - -### Pattern 1: "Fix a bug in a Java file" - -``` -1. lsp_java_getFileStructure(file) → Understand what's in the file (100 tokens) -2. read_file(file, relevant_lines) → Read the buggy method -3. [If needed] lsp_java_getFileImports(file) → Check what types are used (80 tokens) -4. [If needed] lsp_java_getTypeAtPosition() → Resolve ambiguous types (20 tokens) -5. Edit the file -``` -Total tool overhead: ~200 tokens (vs ~3000+ tokens if you blindly dump all imports) - -### Pattern 2: "Understand a new Java project" - -``` -1. read_file(pom.xml or build.gradle) → Check build tool, Java version, deps -2. lsp_java_findSymbol("Main") → Find entry points (60 tokens) -3. lsp_java_getFileStructure(main_file) → Understand main file (100 tokens) -4. lsp_java_getFileImports(main_file) → See what libraries are used (80 tokens) -``` -Total tool overhead: ~240 tokens - -### Pattern 3: "Refactor a method signature" - -``` -1. lsp_java_getCallHierarchy(method, "incoming") → Find all callers (80 tokens) -2. For each caller file: - lsp_java_getFileStructure(caller_file) → Understand caller context (100 tokens) -3. Edit all affected files -``` - -### Pattern 4: "Find all implementations of an interface" - -``` -1. lsp_java_findSymbol("MyInterface") → Locate the interface (60 tokens) -2. lsp_java_getTypeHierarchy(interface_pos, "subtypes") → Find all impls (60 tokens) -3. For key implementations: - lsp_java_getFileStructure(impl_file) → See what they override -``` - -### Pattern 5: "Understand dependency usage in a file" - -``` -1. lsp_java_getFileImports(file) → See all imports classified (80 tokens) -2. [For unfamiliar external types]: - lsp_java_getTypeAtPosition() on usage → See the resolved type/method signature -3. [If needed] read_file(pom.xml) → Check dependency coordinates -``` - ---- - -## Anti-Patterns (What NOT to Do) - -### ❌ Don't call lsp_java_getTypeAtPosition on obvious types - -```java -String name = "hello"; // Obviously String — don't call the tool -var result = service.process(input); // Not obvious — DO call the tool -``` - -### ❌ Don't use these tools for JDK standard library classes - -You already know `java.util.List`, `java.lang.String`, `java.io.File`. Don't waste a tool call on them. - -### ❌ Don't call lsp_java_getFileStructure on small files - -If a file is < 100 lines, just use `read_file` directly. File structure is most valuable for large files. - -### ❌ Don't call lsp_java_getCallHierarchy without reading the method first - -Understand what the method does before tracing its callers. - ---- - -## Fallback Strategy - -If a Java tool returns an error or empty result: -1. **jdtls not ready:** The Java language server may still be loading. Wait a moment and retry once. -2. **File not in project:** The file may not be part of a recognized Java project. Fall back to `read_file` + `grep_search`. -3. **Build errors (L1/L2):** If the project has unresolved build configuration errors, imports tool may return incomplete source classification. Structure tools should still work. -4. **Compilation errors (L3):** Most tools work fine. Type resolution near the error location may be imprecise. - -**For dependency/project info not covered by these tools:** -- Read `pom.xml` or `build.gradle` directly with `read_file` -- Use `grep_search` to find dependency declarations -- Use `lsp_java_getFileImports` to see what external libraries a file uses - -**General rule:** If a Java-specific tool fails, fall back to the universal tools (`read_file`, `grep_search`, `list_code_usages`). Don't retry more than once. diff --git a/docs/package-json-tools-snippet.jsonc b/docs/package-json-tools-snippet.jsonc deleted file mode 100644 index 71896e41..00000000 --- a/docs/package-json-tools-snippet.jsonc +++ /dev/null @@ -1,191 +0,0 @@ -{ - "$comment": "This is the languageModelTools section to add to package.json contributes", - "languageModelTools": [ - { - "name": "java_getProjectContext", - "tags": ["java", "project", "dependencies"], - "modelDescription": "Get a high-level overview of a Java project: build tool (Maven/Gradle), Java version, source roots, and direct dependency list (GAV coordinates). Call this FIRST when starting work on a Java project. Returns ~100 tokens. Do NOT call repeatedly — project info doesn't change within a session.", - "displayName": "Java: Get Project Context", - "inputSchema": { - "type": "object", - "properties": { - "fileUri": { - "type": "string", - "description": "URI of any file in the Java project" - } - }, - "required": ["fileUri"] - } - }, - { - "name": "lsp_java_getFileImports", - "tags": ["java", "imports", "file"], - "modelDescription": "Get the classified import list of a Java file. Each import is tagged with its kind (class/interface/enum/annotation) and source (project/external/jdk). Returns ~80 tokens. Use this to understand what a file depends on WITHOUT reading the full source. Follow up with java_getClassDetail only for the 1-2 classes you actually need to understand — do NOT expand every import.", - "displayName": "Java: Get File Imports", - "inputSchema": { - "type": "object", - "properties": { - "fileUri": { - "type": "string", - "description": "URI of the Java file to analyze" - } - }, - "required": ["fileUri"] - } - }, - { - "name": "java_getClassDetail", - "tags": ["java", "class", "type", "api"], - "modelDescription": "Get signature-level details of a specific Java class: methods, fields, constructors, inheritance, annotations, and a one-sentence javadoc summary. Returns ~150 tokens for project sources, ~80 tokens for external dependencies. Use the fully qualified class name (e.g., 'com.example.model.Order'). Do NOT call this for well-known JDK classes like java.util.List or java.lang.String.", - "displayName": "Java: Get Class Detail", - "inputSchema": { - "type": "object", - "properties": { - "qualifiedName": { - "type": "string", - "description": "Fully qualified class name (e.g., 'com.example.model.Order')" - }, - "fileUri": { - "type": "string", - "description": "Optional: URI of a file in the same project (helps locate the class faster)" - } - }, - "required": ["qualifiedName"] - } - }, - { - "name": "java_getDependencyDetails", - "tags": ["java", "dependencies", "maven", "gradle"], - "modelDescription": "Get detailed dependency information: GAV coordinates, scope (compile/test/runtime), whether direct or transitive, and which direct dependency brings in each transitive one. ALWAYS use the query parameter to filter (e.g., query='gson' or query='spring'). Do NOT request all dependencies without filtering.", - "displayName": "Java: Get Dependency Details", - "inputSchema": { - "type": "object", - "properties": { - "fileUri": { - "type": "string", - "description": "URI of any file in the Java project" - }, - "query": { - "type": "string", - "description": "Filter dependencies by name (fuzzy match on groupId, artifactId, or fileName)" - } - }, - "required": ["fileUri"] - } - }, - { - "name": "lsp_java_getFileStructure", - "tags": ["java", "structure", "symbols", "outline"], - "modelDescription": "Get the structural outline of a Java file: classes, methods, fields, and their line ranges — WITHOUT reading the file content. Returns ~100 tokens. Use this BEFORE read_file to understand what's in a large file, then read_file on specific line ranges. Much more efficient than reading the entire file.", - "displayName": "Java: Get File Structure", - "inputSchema": { - "type": "object", - "properties": { - "uri": { - "type": "string", - "description": "URI of the Java file" - } - }, - "required": ["uri"] - } - }, - { - "name": "lsp_java_findSymbol", - "tags": ["java", "search", "symbol", "workspace"], - "modelDescription": "Search for classes, interfaces, methods, or fields by name across the entire Java workspace. Returns precise symbol definitions only (no comments, no string literals, no import statements). More accurate than grep_search for finding Java symbol definitions. Supports partial names and camelCase matching.", - "displayName": "Java: Find Symbol", - "inputSchema": { - "type": "object", - "properties": { - "query": { - "type": "string", - "description": "Symbol name or partial name to search for (e.g., 'PaymentGateway', 'OrderServ')" - } - }, - "required": ["query"] - } - }, - { - "name": "lsp_java_getTypeAtPosition", - "tags": ["java", "type", "inference", "hover"], - "modelDescription": "Get the compiler-resolved type of any expression at a specific source position. Returns the EXACT type (~20 tokens), using the Java compiler's own type inference. Use this for: var declarations, lambda parameters, generic type arguments, chained method call return types. Do NOT use for obvious types like String literals or explicit type declarations.", - "displayName": "Java: Get Type at Position", - "inputSchema": { - "type": "object", - "properties": { - "uri": { - "type": "string", - "description": "URI of the Java file" - }, - "line": { - "type": "number", - "description": "0-based line number" - }, - "character": { - "type": "number", - "description": "0-based character offset" - } - }, - "required": ["uri", "line", "character"] - } - }, - { - "name": "lsp_java_getCallHierarchy", - "tags": ["java", "calls", "hierarchy", "impact"], - "modelDescription": "Find all callers of a method (incoming) or all methods it calls (outgoing). More precise than list_code_usages — returns only actual method CALLS, not imports, declarations, or comments. Use 'incoming' before modifying a method signature to find all callers. Use 'outgoing' to understand what a method depends on.", - "displayName": "Java: Get Call Hierarchy", - "inputSchema": { - "type": "object", - "properties": { - "uri": { - "type": "string", - "description": "URI of the Java file containing the method" - }, - "line": { - "type": "number", - "description": "0-based line number of the method name" - }, - "character": { - "type": "number", - "description": "0-based character offset of the method name" - }, - "direction": { - "type": "string", - "enum": ["incoming", "outgoing"], - "description": "incoming = who calls this method; outgoing = what this method calls" - } - }, - "required": ["uri", "line", "character", "direction"] - } - }, - { - "name": "lsp_java_getTypeHierarchy", - "tags": ["java", "inheritance", "hierarchy", "interface"], - "modelDescription": "Find all supertypes or subtypes of a class/interface. Catches things grep misses: indirect implementations, anonymous classes, lambda implementations of functional interfaces. Use 'subtypes' when modifying an interface to find ALL implementations. Use 'supertypes' to understand the inheritance chain.", - "displayName": "Java: Get Type Hierarchy", - "inputSchema": { - "type": "object", - "properties": { - "uri": { - "type": "string", - "description": "URI of the Java file containing the type" - }, - "line": { - "type": "number", - "description": "0-based line number of the type name" - }, - "character": { - "type": "number", - "description": "0-based character offset of the type name" - }, - "direction": { - "type": "string", - "enum": ["supertypes", "subtypes"], - "description": "supertypes = parents/interfaces; subtypes = implementations/subclasses" - } - }, - "required": ["uri", "line", "character", "direction"] - } - } - ] -} diff --git a/package.json b/package.json index 324339ab..c6f49eba 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-java-dependency", "displayName": "Project Manager for Java", "description": "%description%", - "version": "0.27.0", + "version": "0.27.1", "publisher": "vscjava", "preview": false, "aiKey": "5c642b22-e845-4400-badb-3f8509a70777", @@ -14,6 +14,7 @@ "engines": { "vscode": "^1.95.0" }, + "enabledApiProposals": ["chatPromptFiles"], "repository": { "type": "git", "url": "https://github.com/Microsoft/vscode-java-dependency.git" @@ -1231,14 +1232,12 @@ ], "chatSkills": [ { - "path": "./resources/skills/java-lsp-tools/SKILL.md", - "when": "java:lspToolsReady" + "path": "./resources/skills/java-lsp-tools/SKILL.md" } ], "chatInstructions": [ { - "path": "./resources/instruments/loadJavaSkills.md", - "when": "java:lspToolsReady" + "path": "./resources/instruments/javaLspContext.instructions.md" } ] }, diff --git a/resources/instruments/javaLspContext.instructions.md b/resources/instruments/javaLspContext.instructions.md new file mode 100644 index 00000000..a956d24f --- /dev/null +++ b/resources/instruments/javaLspContext.instructions.md @@ -0,0 +1,43 @@ +--- +description: Unconditionally loaded for all Java projects. Provides Java LSP tools (lsp_java_*) for compiler-accurate code navigation — significantly faster and more precise than grep_search, search_subagent, or read_file for locating Java symbols, callers, implementations, and types. +applyTo: '*' +--- + +# Java LSP Tools — Mandatory Initialization + +This workspace contains Java code. You have access to 6 compiler-accurate LSP tools that are **faster and more precise** than generic search for Java code navigation. These tools return structured results in ~20-100 tokens vs ~500-3000 tokens from grep_search, with zero false positives. + +## Step 1: Load Tools (REQUIRED — do this FIRST) + +Before ANY operation, call `tool_search_tool_regex` with: +``` +lsp_java_findSymbol|lsp_java_getFileStructure|lsp_java_getCallHierarchy|lsp_java_getTypeHierarchy|lsp_java_getTypeAtPosition|lsp_java_getFileImports +``` +All 6 tools must be returned. If any are missing, retry once. Do NOT use `grep_search`, `read_file`, `semantic_search`, `search_subagent`, or `runSubagent` on `.java` files until tools are loaded. + +## Step 2: Always Prefer LSP Tools for Java + +| Task | ✅ Use | ❌ Not | +|---|---|---| +| Find class/method/field definition | `lsp_java_findSymbol` | `grep_search`, `semantic_search`, `search_subagent` | +| See file outline (methods, fields) | `lsp_java_getFileStructure` | `read_file` full scan | +| Find all callers of a method | `lsp_java_getCallHierarchy("incoming")` | `grep_search`, `search_subagent` | +| Find all implementations | `lsp_java_getTypeHierarchy("subtypes")` | `grep_search`, `search_subagent` | +| Resolve var/lambda/generic type | `lsp_java_getTypeAtPosition` | Guessing from code | +| Check imports | `lsp_java_getFileImports` | `read_file` first lines | +| Explore Java codebase structure | `lsp_java_*` tools above | `search_subagent`, `runSubagent` | + +**Keep using `grep_search`** for: non-Java files (xml, yaml, gradle), string literals, comments. + +**Self-check**: Before every `.java` operation, ask: "Is there a `lsp_java_*` tool for this?" If yes, use it instead of `grep_search`, `search_subagent`, or `runSubagent`. + +## Typical Workflow + +**findSymbol → getFileStructure → targeted tool → read_file (specific lines only)** + +## Fallback + +- `findSymbol` returns empty → retry with shorter keyword, then fall back to `grep_search` +- Path error → use `findSymbol` to discover correct path first +- Tool error / jdtls not ready → fall back to `read_file` + `grep_search`, don't retry more than once + diff --git a/resources/instruments/loadJavaSkills.md b/resources/instruments/loadJavaSkills.md deleted file mode 100644 index 28ed875d..00000000 --- a/resources/instruments/loadJavaSkills.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -description: ALWAYS load in Java projects. Use lsp_java_* tools instead of grep_search, semantic_search, and runSubagent for Java code navigation, symbol lookup, call tracing, and type resolution. These compiler-accurate tools replace generic search with precise, low-token results. ---- - -MANDATORY: For ANY Java code task (bug fixing, code analysis, refactoring): - -1. LOAD TOOLS FIRST — Call tool_search_tool_regex with pattern: - lsp_java_findSymbol|lsp_java_getFileStructure|lsp_java_getCallHierarchy|lsp_java_getTypeHierarchy|lsp_java_getTypeAtPosition|lsp_java_getFileImports - -2. ALWAYS prefer lsp_java_* tools over grep_search/read_file/semantic_search/runSubagent for .java files. - These tools provide compiler-accurate results with ~90% fewer tokens. - -3. Read the "java-lsp-tools" skill for detailed per-tool usage guidance. - -Do NOT skip step 1. Do NOT use grep_search or runSubagent as a substitute for lsp_java_* tools on .java files. \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index 684187ee..b72033fa 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -21,7 +21,6 @@ import { setContextForDeprecatedTasks, updateExportTaskType } from "./tasks/buil import { CodeActionProvider } from "./tasks/buildArtifact/migration/CodeActionProvider"; import { newJavaFile } from "./explorerCommands/new"; import upgradeManager from "./upgrade/upgradeManager"; -import { registerCopilotContextProviders } from "./copilot/contextProvider"; import { registerJavaContextTools } from "./copilot/tools/javaContextTools"; import { languageServerApiManager } from "./languageServerApi/languageServerApiManager"; From 8e87397e0c5e9d1f37a4a4a7be1c4add59efc1cc Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Tue, 17 Mar 2026 10:47:52 +0800 Subject: [PATCH 08/14] feat: update the feature in exp --- package.json | 22 ++++++++++++++++++++-- package.nls.json | 1 + src/constants.ts | 1 - src/extension.ts | 5 +++-- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index c6f49eba..d4368f8a 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "displayName": "Java: Get File Structure", "canBeReferencedInPrompt": true, "icon": "$(symbol-class)", + "when": "vscode-java-dependency.enableLspTools", "inputSchema": { "type": "object", "properties": { @@ -75,6 +76,7 @@ "displayName": "Java: Find Symbol", "canBeReferencedInPrompt": true, "icon": "$(search)", + "when": "vscode-java-dependency.enableLspTools", "inputSchema": { "type": "object", "properties": { @@ -97,6 +99,7 @@ "displayName": "Java: Get File Imports", "canBeReferencedInPrompt": true, "icon": "$(references)", + "when": "vscode-java-dependency.enableLspTools", "inputSchema": { "type": "object", "properties": { @@ -115,6 +118,7 @@ "displayName": "Java: Get Type at Position", "canBeReferencedInPrompt": true, "icon": "$(symbol-field)", + "when": "vscode-java-dependency.enableLspTools", "inputSchema": { "type": "object", "properties": { @@ -141,6 +145,7 @@ "displayName": "Java: Get Call Hierarchy", "canBeReferencedInPrompt": true, "icon": "$(call-incoming)", + "when": "vscode-java-dependency.enableLspTools", "inputSchema": { "type": "object", "properties": { @@ -173,6 +178,7 @@ "displayName": "Java: Get Type Hierarchy", "canBeReferencedInPrompt": true, "icon": "$(type-hierarchy)", + "when": "vscode-java-dependency.enableLspTools", "inputSchema": { "type": "object", "properties": { @@ -511,6 +517,16 @@ "type": "boolean", "description": "%configuration.java.project.explorer.showNonJavaResources%", "default": true + }, + "vscode-java-dependency.enableLspTools": { + "type": "boolean", + "scope": "application", + "description": "%configuration.vscode-java-dependency.enableLspTools.description%", + "default": false, + "tags": ["experimental"], + "experiment": { + "mode": "startup" + } } } }, @@ -1232,12 +1248,14 @@ ], "chatSkills": [ { - "path": "./resources/skills/java-lsp-tools/SKILL.md" + "path": "./resources/skills/java-lsp-tools/SKILL.md", + "when": "vscode-java-dependency.enableLspTools" } ], "chatInstructions": [ { - "path": "./resources/instruments/javaLspContext.instructions.md" + "path": "./resources/instruments/javaLspContext.instructions.md", + "when": "vscode-java-dependency.enableLspTools" } ] }, diff --git a/package.nls.json b/package.nls.json index 95052e18..a163a415 100644 --- a/package.nls.json +++ b/package.nls.json @@ -50,6 +50,7 @@ "configuration.java.project.exportJar.targetPath.customization": "The output path of the exported jar. Leave it empty if you want to manually pick the output location.", "configuration.java.project.exportJar.targetPath.workspaceFolder": "Export the jar file into the workspace folder. Its name is the same as the folder's.", "configuration.java.project.exportJar.targetPath.select": "Select output location manually when exporting the jar file.", + "configuration.vscode-java-dependency.enableLspTools.description": "Enable LSP tools for Java projects.", "taskDefinitions.java.project.exportJar.label": "The label of export jar task.", "taskDefinitions.java.project.exportJar.elements": "The content list of the exported jar.", "taskDefinitions.java.project.exportJar.mainClass": "The main class in the manifest of the exported jar.", diff --git a/src/constants.ts b/src/constants.ts index 3e17f032..8bb176a5 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -9,7 +9,6 @@ export namespace Context { export const WORKSPACE_CONTAINS_BUILD_FILES: string = "java:workspaceContainsBuildFiles"; export const RELOAD_PROJECT_ACTIVE: string = "java:reloadProjectActive"; export const SHOW_DEPRECATED_TASKS: string = "java:showDeprecatedTasks"; - export const LSP_TOOLS_READY: string = "java:lspToolsReady"; } export namespace Explorer { diff --git a/src/extension.ts b/src/extension.ts index b72033fa..aa915423 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -88,9 +88,10 @@ async function activateExtension(_operationId: string, context: ExtensionContext // Register Copilot context providers after Java Language Server is ready languageServerApiManager.ready().then((isReady) => { - if (isReady) { + const config = workspace.getConfiguration("vscode-java-dependency"); + const isSettingEnabled = config.get("enableLspTools", true); + if (isReady && isSettingEnabled) { registerJavaContextTools(context); - contextManager.setContextValue(Context.LSP_TOOLS_READY, true); } }); } From 4805c9005868d5baf5241c9d4437abfaf44f1f0a Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Tue, 17 Mar 2026 13:49:41 +0800 Subject: [PATCH 09/14] docs: remove unusless id --- lsp-ai-tools.md | 752 ------------------------------------------------ package.json | 16 +- 2 files changed, 8 insertions(+), 760 deletions(-) delete mode 100644 lsp-ai-tools.md diff --git a/lsp-ai-tools.md b/lsp-ai-tools.md deleted file mode 100644 index 3fa5107e..00000000 --- a/lsp-ai-tools.md +++ /dev/null @@ -1,752 +0,0 @@ -# LSP 能力对 AI 辅助 Java 开发的价值分析与工具设计建议 - -## 一、背景:Java 的特殊性 - -### Java 与其他语言的依赖管理对比 - -| 语言 | 依赖声明 | 锁文件/解析结果 | AI 能否静态获取完整依赖信息 | -|------|---------|----------------|------------------------| -| **Java (Maven)** | `pom.xml` | 无标准锁文件 | ❌ 传递依赖需构建工具解析 | -| **Java (Gradle)** | `build.gradle`(代码) | `gradle.lockfile`(可选,少人用) | ❌ DSL 是代码,无法静态解析 | -| **.NET** | `.csproj` | `project.assets.json`(自动生成) | ✅ 读 JSON 文件即可 | -| **Go** | `go.mod` | `go.sum` | ✅ 确定性解析,读文件即可 | -| **Rust** | `Cargo.toml` | `Cargo.lock` | ✅ 读文件即可 | -| **Node.js** | `package.json` | `package-lock.json` / `yarn.lock` | ✅ 读文件即可 | - -**Java 是主流语言中唯一没有标准化、人类/机器可读的依赖解析结果落盘机制的。** 这使得 AI 处理 Java 项目时比其他语言更容易出错。 - -### Java 让 AI 更容易出错的语言特征 - -| Java 特征 | 为什么 AI 更难 | -|-----------|---------------| -| 强类型 + 泛型擦除 | 源码中的类型信息不完整,真实类型需要编译器推断 | -| 依赖管理是间接的 | import 和 Maven artifact 是两层映射,不像 Python 的 `import requests` 直接对应 pip 包名 | -| 构建系统是"活"的 | Gradle DSL 是代码,Maven 有继承/profile,不能静态读取 | -| 模块化程度高 | 企业项目动辄几十个模块,类的来源不直观 | -| 向后兼容重但有断裂点 | javax → jakarta、Java module system、API 废弃 | - ---- - -## 二、jdtls delegateCommandHandler 扩展命令评估 - -### 5 个被识别为"不可替代"的命令 - -| 命令 | 来源 | 不可替代原因 | -|------|------|-------------| -| `resolveClasspath` | java-debug | 完整传递依赖图,含版本仲裁、profile 激活、Gradle 动态依赖 | -| `isOnClasspath` | java-debug | 运行时 classpath 实际状态,区分"声明"与"实际生效" | -| `resolveElementAtSelection` | java-debug | 编译器级语义信息:泛型推断、重载决议、类型绑定 | -| `getDependencies` | java-dependency | 已解析的依赖树 + Java 版本 + 模块信息 + JAR 实际路径 | -| `jacoco.getCoverageDetail` | java-test | 运行时字节码插桩产生的行级覆盖率数据 | - -### 鸡生蛋问题:这些命令的可靠性悖论 - -这些命令有一个根本性问题:**AI 最需要它们的时候,恰恰是它们最不可靠的时候。** - -| 项目状态 | jdtls 状态 | 命令可用性 | AI 对命令的需求 | -|---------|-----------|-----------|---------------| -| 正常编译通过 | 完全工作 | ✅ 全部可用 | 低——项目没问题,AI 读源码基本够用 | -| 源码有编译错误但依赖正确 | 部分工作 | ⚠️ 依赖命令可用,类型解析部分受影响 | 中 | -| pom.xml/build.gradle 有语法错误 | M2E/Buildship 导入失败 | ❌ 依赖命令全部失效 | 高——但恰好用不了 | -| 依赖下载失败(网络/仓库问题) | 部分导入 | ⚠️ 返回不完整的依赖树 | 高——返回的信息可能误导 AI | -| Java 版本升级中间状态 | 取决于具体阶段 | ⚠️ 不确定 | 最高——但最不可靠 | -| 新 clone 的项目未导入 | 未初始化 | ❌ 全部失效 | 高 | - -**修正后的价值评估:** - -| 命令 | 修正前价值 | 考虑可靠性后 | 说明 | -|------|----------|------------|------| -| `getDependencies` / `resolveClasspath` | ⭐⭐⭐ | ⭐⭐ | 项目正常时有用,但正常时需求低 | -| `isOnClasspath` | ⭐⭐⭐ | ⭐ | 返回 false 时无法区分"不可用"和"jdtls 不知道" | -| `resolveElementAtSelection` | ⭐⭐⭐ | ⭐⭐ | 编译错误多时类型绑定不可靠 | -| `jacoco.getCoverageDetail` | ⭐⭐⭐ | ⭐⭐⭐ | 离线数据,不受 jdtls 实时状态影响——唯一例外 | - -**真正的价值区间:** 项目处于"亚健康"状态时(核心结构正确,依赖大部分已解析,但有局部的编译错误或依赖问题),jdtls 命令能提供大部分正确的上下文信息。这大约占所有场景的 40-60%。 - ---- - -## 三、AI 处理 Java 项目时天然吃力的任务类型 - -### 类型 1:跨依赖边界的修改 - -修改涉及的代码不只在项目源码中,还取决于依赖库提供的 API、类型、行为。 - -- 升级框架版本后适配 API 变更 -- 替换一个第三方库为另一个库(如 Gson → Jackson) -- 修复因依赖版本冲突导致的运行时错误 - -**AI 失败模式:** AI 对"项目边界之外"是盲的——不知道 classpath 上的依赖到底提供了哪些类和方法签名。 - -### 类型 2:类型推断密集的代码修改 - -修改大量依赖编译器类型推断能力的代码,源码文本不包含完整类型信息。 - -- 重构使用了 `var`、Lambda、Stream 链式调用的代码 -- 修改泛型类/方法的签名,需要判断所有调用点是否兼容 -- 在有多个重载方法的类中添加新方法,需要确保不会导致调用歧义 - -**AI 失败模式:** AI 看到 `var list = getItems()` 但不知道 `list` 是 `List` 还是 `List`。 - -### 类型 3:构建配置与源码的联动修改 - -修改不仅涉及 `.java` 文件,还涉及构建配置(pom.xml / build.gradle),且两者必须保持一致。 - -- 添加新功能需要引入新依赖 -- 多模块项目中拆分/合并模块 -- 修改编译器参数 - -**AI 失败模式:** AI 可能添加了代码中的 import 但忘记加依赖,或者加了依赖但版本与已有版本冲突。 - -### 类型 4:编译错误的诊断与修复 - -项目处于编译失败状态,AI 需要从错误信息定位到根因并修复。 - -- 拉取最新代码后编译不过 -- 升级 JDK 版本后编译失败 -- merge 之后的冲突解决导致类型不匹配 - -**AI 失败模式:** 编译错误信息指向"症状",根因可能在传递依赖链里。**编译失败时 `mvn dependency:tree` 可能也跑不出来**,但 jdtls 的 `getDependencies` 基于 M2E 增量解析可能仍可用。 - -### 类型 5:多模块项目中的跨模块修改 - -修改需要横跨多个 Maven/Gradle 模块,涉及模块间的依赖关系和 API 契约。 - -- 修改公共模块中的接口,需要同步更新所有依赖模块 -- 将一个类从 module-A 移动到 module-B -- 判断某个功能应该放在哪个模块中 - -**AI 失败模式:** AI 不清楚模块间的依赖方向,把一个类移到错误的模块会导致循环依赖。 - -### 类型 6:运行时行为相关的代码修改 - -修改的正确性不仅取决于编译通过,还取决于运行时行为。 - -- 修改了序列化/反序列化逻辑 -- 修改了 Spring Bean 的注入方式 -- 修改了异常处理路径 - -**AI 失败模式:** 代码改完编译通过,但运行时行为不对。 - -### 共同内核 - -**这 6 类场景的共同点:代码修改的正确性取决于源码文本之外的信息。** - -- 类型 1-3:正确性取决于「构建系统的状态」 -- 类型 2:正确性取决于「编译器的推断结果」 -- 类型 4-5:诊断的准确性取决于「项目的结构化元信息」 -- 类型 6:正确性取决于「运行时状态」 - ---- - -## 四、LSP 标准能力 vs delegateCommandHandler 扩展能力 - -### 已有基线:`list_code_usages` 的覆盖范围 - -VS Code 当前已向 AI 暴露了 `list_code_usages` 工具,其底层基于 LSP 的 `textDocument/references` + `textDocument/definition` + `textDocument/implementation`。在评估新 LSP tool 前,需要明确它已经覆盖了什么、还缺什么: - -**`list_code_usages` 已覆盖的能力:** - -| 能力 | 底层 LSP 请求 | 说明 | -|------|-------------|------| -| 引用查找 | `textDocument/references` | 找到所有使用某符号的位置 | -| 定义跳转 | `textDocument/definition` | 定位符号定义 | -| 实现查找 | `textDocument/implementation` | 找接口的实现类 | - -**新方向与 `list_code_usages` 的关系:** - -| 新方向 | 与 `list_code_usages` 的关系 | 增量价值 | -|--------|---------------------------|--------| -| Document Symbol | 完全正交——usages 找"谁用了这个符号",Symbol 回答"这个文件里有什么" | **高**——省去 read_file 全文 | -| Type Query(Hover 后处理) | 完全正交——usages 不提供类型信息 | **最高**——解决 `var`/泛型/Lambda 的类型盲区 | -| Workspace Symbol | 部分重叠——usages 需已知符号名,Workspace Symbol 支持模糊搜索 | **中高**——解决"大概知道叫什么但不确定"的场景 | -| Call Hierarchy | 是 usages 的升级版——usages 返回所有引用(含 import、注释、声明),Call Hierarchy 只返回调用关系,且支持方向性 | **高**——更精确的影响分析 | -| Type Hierarchy | usages 找 `implements X` 的文本,Type Hierarchy 包含间接继承、匿名类、Lambda | **高**——完整的继承图 | - -**定位建议:** 新 LSP tool 不是从零开始,而是在 `list_code_usages` 已验证的路径上填补缺口。Call Hierarchy 可以定位为「`list_code_usages` 的精确版」——usages 返回的结果中 import、声明、注释、实际调用混在一起,Call Hierarchy 直接给出纯调用关系,省掉筛选 token。 - -### 本质区别 - -| | delegateCommandHandler (resolveClasspath 等) | 标准 LSP 能力 (hover/hierarchy 等) | -|--|---------------------------------------------|----------------------------------| -| Java 特有 | 是 | 否——任何有 LSP 的语言都适用 | -| 鸡生蛋问题 | 严重——依赖解析失败就全挂 | 轻微——只要文件能解析就能工作 | -| 解决什么问题 | Java 依赖管理的设计缺陷 | AI 的 context 窗口和推理精度限制 | -| 可替代性 | 部分不可替代 | AI 理论上可以自己做,但成本高出几个数量级 | - -**标准 LSP 能力的核心价值不是"提供 AI 做不到的能力",而是"用毫秒级查询替代百万 token 推理"。** - -### AI 当前的代码导航方式 - -``` -当前 AI 处理 Java 代码的工具栈: - - 文本搜索(grep/semantic_search) ████████████████ ← 主要依赖 - LSP 引用查找(list_code_usages) ████ ← 有但用得少 - 终端命令(mvn/javac) ████ ← 兜底 - jdtls 扩展命令(delegateCommand) ☐ ← 基本未暴露 - LSP 高级能力(hover/hierarchy) ☐ ← 未暴露 - -理想的 AI 处理 Java 代码的工具栈: - - LSP 核心能力(引用/定义/类型/层次)████████████████ ← 主力 - jdtls 扩展命令(依赖/classpath) ████████ ← 项目级理解 - 文本搜索(grep) ████ ← 快速初筛 - 终端命令 ██ ← 降级兜底 -``` - ---- - -## 五、LSP Tool 设计建议 - -### 方向 1:文件结构摘要(P0 — 最高优先级) - -**对应 LSP 请求:** `textDocument/documentSymbol` - -**当前痛点:** - -AI 理解一个 Java 文件结构,需要 `read_file` 读全部内容(~2000 tokens),然后用推理能力提取结构。 - -**LSP 替代效果:** - -``` -一次 documentSymbol 请求返回: - class OrderService - ├── field: orderRepo (OrderRepository) - ├── method: createOrder(req: CreateOrderRequest): Order [line 45-80] - ├── method: cancelOrder(orderId: String): void [line 82-95] - └── method: getOrders(userId: String): List [line 97-120] - -消耗:~100 tokens(结构化数据),节省 90%+ -``` - -**调研要点:** -- `DocumentSymbol[]`(树形)vs `SymbolInformation[]`(扁平),jdtls 返回哪种 -- 返回信息是否包含参数类型、返回类型、泛型参数、注解 -- 跨语言一致性:TypeScript、Python、Go 的 Language Server 返回格式是否统一 -- 性能:大文件(1000+ 行)上的响应延迟 - -**预期收益:** 文件结构理解的 token 消耗降低 **90%+**。AI 先看摘要,再按需读具体方法。 - ---- - -### 方向 2:类型查询(P0.5 — 高优先级,需封装) - -**对应 LSP 请求:** `textDocument/hover`(需后处理) - -#### UI 层 vs 协议层的辨析 - -`textDocument/hover` 需要区分两个层面: - -| 层面 | 含义 | AI 是否需要 | -|------|------|------------| -| **UI 层**——鼠标悬停弹出 tooltip | 人看的浮窗 | 不需要 | -| **协议层**——LSP 请求 | 给定 (file, line, col) 返回该位置的类型/文档信息 | **需要数据,不需要 UI** | - -`textDocument/hover` 本质上是一个 **位置→类型信息** 的查询 API,AI 可以纯程序化调用,不需要任何 UI 交互。但关键问题是:**hover 返回的内容是为人设计的,不是为 AI 设计的。** - -jdtls 的 `textDocument/hover` 返回 **MarkupContent(Markdown)**,例如: - -```markdown -**String** java.lang.String - -The `String` class represents character strings. All string literals in Java programs... -(后面跟几百字的 Javadoc) -``` - -对 AI 来说: -- **有用的**:`String` 这个类型签名(~5 tokens) -- **没用的**:Javadoc 内容(~200 tokens 的噪音) - -#### 从 P0 降级到 P0.5 的原因 - -| 因素 | 说明 | -|------|------| -| 返回格式非结构化 | Markdown 文本,需要正则提取类型签名 | -| 夹带大量噪音 | Javadoc 内容对 AI 是纯 token 浪费 | -| 跨语言不一致 | 不同 Language Server 的 Markdown 格式不统一,解析逻辑脆弱 | -| 需要封装层 | 必须在 tool 层做后处理,命名为 `get_type_at_position` 而非 `hover` | - -**结论:LSP 标准协议里没有一个"干净的、为程序消费设计的类型查询 API"。Hover 是最接近的,但需要额外封装。** 能力本身依然关键(解决 var/泛型/Lambda 的类型盲区),但实现需要更多工作。 - -#### 类型查询的替代方案对比 - -| 方案 | 原理 | 优点 | 缺点 | -|------|------|------|------| -| Hover + 后处理 | 调用 `textDocument/hover`,正则提取首个 code block 中的类型签名 | 现成 API,jdtls 实现成熟 | 返回格式不标准,解析逻辑脆弱 | -| `resolveElementAtSelection`(jdtls 扩展) | 返回编译器级语义信息 | 比 hover 更结构化,为程序消费设计 | 有鸡生蛋问题 | -| Semantic Tokens | 返回每个 token 的类型分类 | 无噪音 | **不返回具体类型**(只知"是变量",不知"是 List\") | - -**推荐方案:Hover + 后处理封装。** 在 tool 层拦截 hover 返回,只提取类型签名部分。 - -**当前痛点:** - -```java -var result = service.getOrders(userId).stream() - .filter(o -> o.getStatus() == Status.ACTIVE) - .collect(Collectors.groupingBy(Order::getCategory)); -``` - -AI 要知道 `result` 的类型,需要跨多个文件追踪方法返回类型(~1000+ tokens + 可能推断错误)。 - -**LSP 替代效果(后处理后):** - -``` -get_type_at_position(file, line, column of "result") - → "Map>" - -消耗:~20 tokens,准确率 100% -``` - -**调研要点:** -- `textDocument/hover` 返回内容格式(MarkupContent / 纯文本 / 结构化类型信息) -- jdtls 的 hover 在不同位置(变量、方法调用、字段访问、Lambda 参数)返回的 Markdown 格式差异 -- 类型签名提取的正则是否能覆盖 jdtls 的主要返回模式 -- 是否能用于 JAR 中的类(依赖库的类型信息) -- 大型项目上 hover 请求的 P99 延迟 - -**预期收益:** 类型推断场景的 token 消耗降低 **95%**,准确率从"AI 推理猜测"提升到"编译器精确结果"。 - ---- - -### 方向 3:全局符号搜索(P0 — 最高优先级) - -**对应 LSP 请求:** `workspace/symbol` - -**当前痛点:** - -``` -AI 想找 "PaymentGateway" 类: - grep_search("PaymentGateway") → 命中 30 个结果(含注释、字符串、import 语句) - → AI 逐个识别哪个是定义 → 消耗 token + 时间 -``` - -**LSP 替代效果:** - -``` -workspaceSymbol("PaymentGateway") - → [ - { name: "PaymentGateway", kind: Interface, location: "src/.../PaymentGateway.java:15" }, - { name: "PaymentGatewayImpl", kind: Class, location: "src/.../PaymentGatewayImpl.java:8" } - ] - -精确命中,无噪音 -``` - -**调研要点:** -- 支持的查询模式(前缀匹配?模糊匹配?驼峰缩写匹配如 `PG` → `PaymentGateway`?) -- jdtls 是否能搜索到 JAR 中的类 -- 返回结果的数量限制和分页机制 -- 与 VS Code 的 `Go to Symbol in Workspace`(Ctrl+T)底层是否一致 - -**预期收益:** 符号定位从"grep 30 个结果 + AI 筛选"变为"直接命中精确结果"。 - ---- - -### 方向 4:调用链查询(P1) - -**对应 LSP 请求:** `textDocument/prepareCallHierarchy` + `callHierarchy/incomingCalls` / `outgoingCalls` - -**当前痛点:** - -AI 修改方法前需要知道调用方,`grep` 或 `list_code_usages` 返回所有引用(声明、import、调用混在一起),AI 需自行判断哪些是真正的调用。 - -**LSP 替代效果:** - -``` -incomingCalls("UserService.deleteUser") - → [ - { from: "UserController.handleDelete()", location: "UserController.java:45" }, - { from: "AdminJob.cleanup()", location: "AdminJob.java:78" } - ] -只有调用关系,没有噪音 -``` - -**调研要点:** -- 请求流程:先 `prepareCallHierarchy` 再 `incomingCalls` / `outgoingCalls` -- jdtls 的实现完整度(是否支持跨模块、多态调用) -- 调用深度——是否支持递归展开(A→B→C 的完整调用链) -- 大型项目上查询被广泛调用方法时的延迟 - -**预期收益:** AI 做修改影响分析时从"grep + 推理筛选"变为"精确的调用关系图"。 - ---- - -### 方向 5:类型继承层次(P1) - -**对应 LSP 请求:** `textDocument/prepareTypeHierarchy` + `typeHierarchy/supertypes` / `subtypes` - -**当前痛点:** - -``` -grep "implements Processor" → 只找到显式实现,漏掉: - - 间接实现(class A extends B,B implements Processor) - - 匿名类实现 - - Lambda 实现 - - 方法引用实现 -``` - -**LSP 替代效果:** - -``` -subtypes("Processor") - → [StringProcessor, NumberProcessor, (anonymous class at App.java:30), (lambda at App.java:35)] -``` - -**调研要点:** -- jdtls 是否能识别 Lambda 和匿名类作为接口实现 -- 是否支持递归展开(查 C 的 subtypes 时返回间接子类 A) -- supertypes 方向是否包含接口和类继承链 - -**预期收益:** 接口/抽象类修改时的影响分析精确度从 ~70% 提升到 ~100%。 - ---- - -### 不建议优先调研的方向 - -| 方向 | 为什么优先级低 | -|------|-------------| -| Semantic Tokens | 信息量有限,AI 从上下文基本能推断标识符角色 | -| Code Actions / Refactoring | 实现复杂(需要 apply workspace edit),AI 直接编辑文件目前够用 | -| Rename | 实现复杂,AI 用 `list_code_usages` + 手动替换可以凑合 | -| Completion | AI 自己的补全能力已经很强,LSP 补全反而可能不如 LLM | -| Signature Help | hover 已经覆盖了大部分需求 | - ---- - -## 六、投入产出比总览 - -### 修正后的优先级排序 - -考虑 UI/API 适配度和已有 `list_code_usages` 基线后的修正评估: - -| 优先级 | LSP 能力 | AI 获得的能力 | Token 节省 | 实现复杂度 | 与已有工具关系 | -|--------|---------|-------------|-----------|-----------|---------------| -| **P0** | Document Symbol(文件结构摘要) | 快速理解文件结构,节省 context | ~90% | 低——单次请求,返回天然结构化 | 完全正交,全新能力 | -| **P0** | Workspace Symbol(全局符号搜索) | 精确符号定位 | ~80% | 低——单次请求,返回天然结构化 | 精确替代 grep | -| **P0.5** | Type Query(基于 Hover 后处理) | 精确类型推断 | ~95% | 中——需从 Markdown 提取类型签名 | 完全正交,全新能力 | -| **P1** | Call Hierarchy(调用链) | 修改影响分析 | ~70% | 低——两步请求 | `list_code_usages` 的精确版 | -| **P1** | Type Hierarchy(继承层次) | 接口实现分析 | ~60% | 低——两步请求 | 覆盖 usages 的间接继承盲区 | - -### 变更说明 - -- **Document Symbol 和 Workspace Symbol 保持 P0**:返回数据天然结构化,零 UI 依赖,实现最简单 -- **Hover(类型查询)从 P0 降为 P0.5**:能力关键但返回格式为人设计(Markdown),需要额外封装后处理层 -- **Call Hierarchy 定位调整**:明确为 `list_code_usages` 的精确升级版,而非全新能力 - ---- - -## 七、调研实施路径 - -### 第一步:验证可行性(1-2 天) - -- 在 VS Code 扩展 API 中直接调用这些 LSP 请求 -- 用一个中等规模 Java 项目测试返回数据格式和延迟 -- 确认 jdtls 对每个请求的实现完整度 - -### 第二步:量化收益(2-3 天) - -- 找 10 个典型的 AI 辅助编码任务 -- 记录当前方式消耗的 token 数和耗时 -- 模拟用 LSP tool 后消耗的 token 数和耗时 -- 计算实际节省比例 - -### 第三步:确定优先级(1 天) - -- 按 (收益 × 使用频率) / 实现成本 排序 -- 选 2-3 个最高 ROI 的实现 - -### 核心判断标准 - -**不是"这个 LSP 能力多强大",而是"AI 当前在这个环节花了多少 token / 时间,LSP 能省掉多少"。** - ---- - -## 八、关于 Benchmark 的建议 - -### 正确的出发点 - -``` -❌ 错误:jdtls/LSP 有什么能力 → 构造场景证明它有用 -✅ 正确:开发者日常做什么 → AI 在哪里失败/低效 → 失败原因是否是缺少编译器状态/LSP 信息 -``` - -### 适合的 Benchmark 类型 - -msbench 的 Java Upgrade 类任务是一个很好的切入点——升级后依赖未同步的 case 天然暴露了 AI 在依赖理解方面的不足。这类场景中: - -- 项目往往处于"编译不过"的中间状态 -- `mvn dependency:tree` 可能也跑不出来 -- 但 jdtls/M2E 的增量解析在部分情况下仍然可用 - -### 更 General 的 Benchmark 定位 - -不针对特定命令设计题目,而是定义**开发者日常任务类型**,在真实开源项目上执行,观察加入 LSP 工具后的改善: - -| 度量指标 | 含义 | -|---------|------| -| 编译通过率 | 生成/修改的代码能否直接编译通过 | -| Token 消耗 | 完成同一任务消耗的 token 数 | -| 轮次 | 完成任务需要的交互轮数 | -| 首次正确率 | 第一次输出就正确的比例 | -| 人工修正量 | 需要人工修改多少行才能达到可用状态 | - -### 本质结论 - -LSP 能力对 AI 的价值是**效率工具**而非**能力扩展**。它不让 AI 做到之前做不到的事,而是让 AI 用毫秒级查询替代百万 token 推理,把已经能做的事做得更快、更省、更准。对于中大型 Java 项目,这个效率差距足以决定 AI 辅助的实用性。 - ---- - -## 九、实现架构:如何将 LSP 能力暴露给 AI - -### 三种实现路径对比 - -| 路径 | 机制 | AI 如何调用 | 适用场景 | -|------|------|-----------|----------| -| **A. `LanguageModelTool`** | 扩展注册 `vscode.lm.registerTool()` | Copilot Chat 自动发现并调用 | **推荐——最正式的方式** | -| **B. VS Code 内置命令** | `vscode.commands.executeCommand()` | AI 通过已有 tool(如 `run_vscode_command`)间接调用 | 快速验证 / PoC | -| **C. MCP Server** | 独立进程,通过 MCP 协议通信 | 任何支持 MCP 的 AI 客户端 | 跨编辑器 / 跨 AI 客户端 | - -### 推荐路径:LanguageModelTool - -这是 VS Code 为「给 AI 提供工具」专门设计的 API,Copilot Chat 原生支持工具发现和调用。 - -#### 架构图 - -``` -┌─────────────────────────────────────────┐ -│ Copilot Chat (LLM) │ -│ ↓ 调用 tool │ -│ LanguageModelTool 接口 │ -│ ↓ │ -│ 你的 VS Code Extension │ -│ ↓ 内部调用 VS Code API │ -│ vscode.commands.executeCommand(...) │ -│ ↓ │ -│ jdtls (Language Server) │ -└─────────────────────────────────────────┘ -``` - -#### VS Code 内置命令与 LSP 请求的映射 - -这些命令已存在于 VS Code 中,不需要自己实现 LSP 客户端: - -```typescript -// 文件级 -'vscode.executeDocumentSymbolProvider' // → textDocument/documentSymbol -'vscode.executeHoverProvider' // → textDocument/hover - -// 工作区级 -'vscode.executeWorkspaceSymbolProvider' // → workspace/symbol - -// 导航(list_code_usages 已覆盖) -'vscode.executeDefinitionProvider' // → textDocument/definition -'vscode.executeReferenceProvider' // → textDocument/references -'vscode.executeImplementationProvider' // → textDocument/implementation - -// 层次结构 -'vscode.prepareCallHierarchy' // → textDocument/prepareCallHierarchy -'vscode.provideIncomingCalls' // → callHierarchy/incomingCalls -'vscode.provideOutgoingCalls' // → callHierarchy/outgoingCalls -'vscode.prepareTypeHierarchy' // → textDocument/prepareTypeHierarchy -'vscode.provideSupertypes' // → typeHierarchy/supertypes -'vscode.provideSubtypes' // → typeHierarchy/subtypes -``` - -**核心认知:实现层几乎没有复杂度——VS Code 已经把 LSP 封装好了,只需要写一个薄薄的 LanguageModelTool 适配层。** - -### 实现示例 - -#### Tool 1:文件结构摘要(P0) - -```typescript -import * as vscode from 'vscode'; - -vscode.lm.registerTool('java_fileStructure', new FileStructureTool()); - -class FileStructureTool implements vscode.LanguageModelTool<{ uri: string }> { - - async invoke( - options: vscode.LanguageModelToolInvocationOptions<{ uri: string }>, - token: vscode.CancellationToken - ): Promise { - - const uri = vscode.Uri.parse(options.input.uri); - - // 调用 VS Code 内置命令 → 内部转发给 jdtls 的 textDocument/documentSymbol - const symbols = await vscode.commands.executeCommand( - 'vscode.executeDocumentSymbolProvider', uri - ); - - // 格式化为 AI 友好的结构化文本 - const summary = this.formatSymbols(symbols, 0); - - return new vscode.LanguageModelToolResult([ - new vscode.LanguageModelTextPart(summary) - ]); - } - - private formatSymbols(symbols: vscode.DocumentSymbol[], indent: number): string { - return symbols.map(s => { - const prefix = ' '.repeat(indent); - const kind = vscode.SymbolKind[s.kind]; // Class, Method, Field... - const range = `[L${s.range.start.line + 1}-${s.range.end.line + 1}]`; - let line = `${prefix}${kind}: ${s.name} ${range}`; - - if (s.children?.length) { - line += '\n' + this.formatSymbols(s.children, indent + 1); - } - return line; - }).join('\n'); - } -} -``` - -#### Tool 2:全局符号搜索(P0) - -```typescript -class WorkspaceSymbolTool implements vscode.LanguageModelTool<{ query: string }> { - - async invoke( - options: vscode.LanguageModelToolInvocationOptions<{ query: string }>, - token: vscode.CancellationToken - ): Promise { - - // 调用 VS Code 内置命令 → workspace/symbol - const symbols = await vscode.commands.executeCommand( - 'vscode.executeWorkspaceSymbolProvider', options.input.query - ); - - const results = symbols?.map(s => ({ - name: s.name, - kind: vscode.SymbolKind[s.kind], - location: `${vscode.workspace.asRelativePath(s.location.uri)}:${s.location.range.start.line + 1}` - })); - - return new vscode.LanguageModelToolResult([ - new vscode.LanguageModelTextPart(JSON.stringify(results, null, 2)) - ]); - } -} -``` - -#### Tool 3:类型查询(P0.5)——Hover 后处理封装 - -```typescript -class TypeQueryTool implements vscode.LanguageModelTool<{ uri: string; line: number; character: number }> { - - async invoke( - options: vscode.LanguageModelToolInvocationOptions<{ uri: string; line: number; character: number }>, - token: vscode.CancellationToken - ): Promise { - - const uri = vscode.Uri.parse(options.input.uri); - const position = new vscode.Position(options.input.line, options.input.character); - - // 调用 hover → textDocument/hover - const hovers = await vscode.commands.executeCommand( - 'vscode.executeHoverProvider', uri, position - ); - - // 关键后处理:从 Markdown 中提取类型签名,去掉 Javadoc 噪音 - const typeInfo = this.extractTypeSignature(hovers); - - return new vscode.LanguageModelToolResult([ - new vscode.LanguageModelTextPart(typeInfo) - ]); - } - - private extractTypeSignature(hovers: vscode.Hover[] | undefined): string { - if (!hovers?.length) return 'No type information available'; - - for (const hover of hovers) { - for (const content of hover.contents) { - if (content instanceof vscode.MarkdownString) { - // jdtls 的 hover 返回格式通常是: - // ```java\nType signature\n```\n\nJavadoc... - // 只提取第一个 code block - const match = content.value.match(/```java\n([\s\S]*?)```/); - if (match) return match[1].trim(); - } - } - } - return 'Type extraction failed'; - } -} -``` - -### Tool 元数据声明(package.json) - -`modelDescription` 是最重要的字段——LLM 根据这个描述决定什么时候调用 tool: - -```jsonc -{ - "contributes": { - "languageModelTools": [ - { - "name": "java_fileStructure", - "displayName": "Java File Structure", - "modelDescription": "Get the structure (classes, methods, fields) of a Java file without reading its full content. Returns a tree of symbols with their types, names and line ranges. Use this before read_file to understand a file's organization.", - "inputSchema": { - "type": "object", - "properties": { - "uri": { "type": "string", "description": "The file URI to get structure for" } - }, - "required": ["uri"] - } - }, - { - "name": "lsp_java_findSymbol", - "displayName": "Find Symbol in Workspace", - "modelDescription": "Search for a class, interface, method or field by name across the entire workspace. Returns exact matches with kind (Class/Interface/Method) and file location. More precise than grep - no noise from comments or imports.", - "inputSchema": { - "type": "object", - "properties": { - "query": { "type": "string", "description": "Symbol name or partial name to search for" } - }, - "required": ["query"] - } - }, - { - "name": "java_getType", - "displayName": "Get Type at Position", - "modelDescription": "Get the compiler-resolved type of a symbol at a specific position in a Java file. Returns the precise type including generics (e.g. Map>). Use this for var declarations, lambda parameters, or complex generic chains where the type is not visible in source code.", - "inputSchema": { - "type": "object", - "properties": { - "uri": { "type": "string", "description": "The file URI" }, - "line": { "type": "number", "description": "0-based line number" }, - "character": { "type": "number", "description": "0-based column number" } - }, - "required": ["uri", "line", "character"] - } - } - ] - } -} -``` - -### 关键设计原则 - -#### 1. `modelDescription` 比实现逻辑更重要 - -``` -❌ "Gets document symbols from LSP" — LLM 不知道什么时候该用 -✅ "Get the structure of a Java file without — LLM 明确知道:需要了解文件结构时, - reading its full content" 用这个替代 read_file -``` - -#### 2. 返回格式要为 AI 优化,不是为人优化 - -| 做法 | AI 消耗 tokens | -|------|---------------| -| 原始 hover Markdown(含完整 Javadoc) | ~200 tokens | -| 后处理只保留类型签名 | ~10 tokens | -| 原始 documentSymbol JSON | ~500 tokens | -| 格式化为缩进文本树 | ~100 tokens | - -#### 3. 选择 LanguageModelTool 而非其他路径的原因 - -- **vs 路径 B**(直接用 `run_vscode_command`):可以快速 PoC,但 AI 不会主动发现这些命令,需要人工提示,无法规模化 -- **vs 路径 C**(MCP Server):需要独立进程管理 jdtls 连接,复杂度高很多,除非目标是脱离 VS Code 的场景 - -**总结:用 LanguageModelTool API 封装 VS Code 内置命令,是目前最轻量、最正式的方式。实现工作量主要在 tool 的输入输出设计,而不是 LSP 通信。** diff --git a/package.json b/package.json index d4368f8a..9b5c0fdc 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "displayName": "Java: Get File Structure", "canBeReferencedInPrompt": true, "icon": "$(symbol-class)", - "when": "vscode-java-dependency.enableLspTools", + "when": "config.vscode-java-dependency.enableLspTools", "inputSchema": { "type": "object", "properties": { @@ -76,7 +76,7 @@ "displayName": "Java: Find Symbol", "canBeReferencedInPrompt": true, "icon": "$(search)", - "when": "vscode-java-dependency.enableLspTools", + "when": "config.vscode-java-dependency.enableLspTools", "inputSchema": { "type": "object", "properties": { @@ -99,7 +99,7 @@ "displayName": "Java: Get File Imports", "canBeReferencedInPrompt": true, "icon": "$(references)", - "when": "vscode-java-dependency.enableLspTools", + "when": "config.vscode-java-dependency.enableLspTools", "inputSchema": { "type": "object", "properties": { @@ -118,7 +118,7 @@ "displayName": "Java: Get Type at Position", "canBeReferencedInPrompt": true, "icon": "$(symbol-field)", - "when": "vscode-java-dependency.enableLspTools", + "when": "config.vscode-java-dependency.enableLspTools", "inputSchema": { "type": "object", "properties": { @@ -145,7 +145,7 @@ "displayName": "Java: Get Call Hierarchy", "canBeReferencedInPrompt": true, "icon": "$(call-incoming)", - "when": "vscode-java-dependency.enableLspTools", + "when": "config.vscode-java-dependency.enableLspTools", "inputSchema": { "type": "object", "properties": { @@ -178,7 +178,7 @@ "displayName": "Java: Get Type Hierarchy", "canBeReferencedInPrompt": true, "icon": "$(type-hierarchy)", - "when": "vscode-java-dependency.enableLspTools", + "when": "config.vscode-java-dependency.enableLspTools", "inputSchema": { "type": "object", "properties": { @@ -1249,13 +1249,13 @@ "chatSkills": [ { "path": "./resources/skills/java-lsp-tools/SKILL.md", - "when": "vscode-java-dependency.enableLspTools" + "when": "config.vscode-java-dependency.enableLspTools" } ], "chatInstructions": [ { "path": "./resources/instruments/javaLspContext.instructions.md", - "when": "vscode-java-dependency.enableLspTools" + "when": "config.vscode-java-dependency.enableLspTools" } ] }, From e8d8e14158431e73336dfd7a13aba02d6eea6125 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Tue, 17 Mar 2026 14:12:38 +0800 Subject: [PATCH 10/14] feat: update --- package.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 9b5c0fdc..4fae0cd9 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "displayName": "Java: Get File Structure", "canBeReferencedInPrompt": true, "icon": "$(symbol-class)", - "when": "config.vscode-java-dependency.enableLspTools", + "when": "config.vscode-java-dependency.enableLspTools && javaLSReady", "inputSchema": { "type": "object", "properties": { @@ -76,7 +76,7 @@ "displayName": "Java: Find Symbol", "canBeReferencedInPrompt": true, "icon": "$(search)", - "when": "config.vscode-java-dependency.enableLspTools", + "when": "config.vscode-java-dependency.enableLspTools && javaLSReady", "inputSchema": { "type": "object", "properties": { @@ -99,7 +99,7 @@ "displayName": "Java: Get File Imports", "canBeReferencedInPrompt": true, "icon": "$(references)", - "when": "config.vscode-java-dependency.enableLspTools", + "when": "config.vscode-java-dependency.enableLspTools && javaLSReady", "inputSchema": { "type": "object", "properties": { @@ -118,7 +118,7 @@ "displayName": "Java: Get Type at Position", "canBeReferencedInPrompt": true, "icon": "$(symbol-field)", - "when": "config.vscode-java-dependency.enableLspTools", + "when": "config.vscode-java-dependency.enableLspTools && javaLSReady", "inputSchema": { "type": "object", "properties": { @@ -145,7 +145,7 @@ "displayName": "Java: Get Call Hierarchy", "canBeReferencedInPrompt": true, "icon": "$(call-incoming)", - "when": "config.vscode-java-dependency.enableLspTools", + "when": "config.vscode-java-dependency.enableLspTools && javaLSReady", "inputSchema": { "type": "object", "properties": { @@ -178,7 +178,7 @@ "displayName": "Java: Get Type Hierarchy", "canBeReferencedInPrompt": true, "icon": "$(type-hierarchy)", - "when": "config.vscode-java-dependency.enableLspTools", + "when": "config.vscode-java-dependency.enableLspTools && javaLSReady", "inputSchema": { "type": "object", "properties": { @@ -1249,13 +1249,13 @@ "chatSkills": [ { "path": "./resources/skills/java-lsp-tools/SKILL.md", - "when": "config.vscode-java-dependency.enableLspTools" + "when": "config.vscode-java-dependency.enableLspTools && javaLSReady" } ], "chatInstructions": [ { "path": "./resources/instruments/javaLspContext.instructions.md", - "when": "config.vscode-java-dependency.enableLspTools" + "when": "config.vscode-java-dependency.enableLspTools && javaLSReady" } ] }, From bae66b700bb8153b7e5f62907812496c11c55374 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Tue, 17 Mar 2026 14:14:16 +0800 Subject: [PATCH 11/14] docs: update --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4fae0cd9..3dddc229 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-java-dependency", "displayName": "Project Manager for Java", "description": "%description%", - "version": "0.27.1", + "version": "0.27.0", "publisher": "vscjava", "preview": false, "aiKey": "5c642b22-e845-4400-badb-3f8509a70777", From 49cb282caf60471e07e92a1f43b5b77abf7ee5a2 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Tue, 17 Mar 2026 15:11:28 +0800 Subject: [PATCH 12/14] fix: update the code to fix the comments --- README.md | 2 +- .../jdtls/ext/core/AiContextCommand.java | 8 +- .../ext/core/model/FileImportsResult.java | 3 +- src/copilot/tools/javaContextTools.ts | 170 +++++++++++------- 4 files changed, 114 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index 341985ff..ac4954d9 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ You can tell that the glob pattern is supported. And here's more - you can incl ## Requirements -- VS Code (version 1.83.1+) +- VS Code (version 1.95.0+) - [Language Support for Java by Red Hat](https://marketplace.visualstudio.com/items?itemName=redhat.java) diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/AiContextCommand.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/AiContextCommand.java index 75b6a363..3b2a166f 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/AiContextCommand.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/AiContextCommand.java @@ -86,9 +86,10 @@ public static FileImportsResult getFileImports(List arguments, IProgress return result; } - // Get relative file path + // Get project-relative file path (strip the leading project segment + // from the Eclipse workspace-relative path, e.g. "/my-project/src/Foo.java" → "src/Foo.java") IJavaProject javaProject = compilationUnit.getJavaProject(); - result.file = compilationUnit.getPath().toString(); + result.file = compilationUnit.getPath().removeFirstSegments(1).toString(); // Collect project source package names for classification Set projectPackages = collectProjectPackages(javaProject); @@ -117,9 +118,10 @@ public static FileImportsResult getFileImports(List arguments, IProgress } else { ImportEntry entry = new ImportEntry(); entry.name = name; - entry.kind = isOnDemand ? "package" : "unknown"; // Would need findType to know — skip + entry.kind = "unknown"; // Would need findType to know — skip entry.source = classifyByPackageName(name, projectPackages); entry.artifact = null; // Would need classpath attributes — skip for now + entry.isOnDemand = isOnDemand; result.imports.add(entry); } } diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/model/FileImportsResult.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/model/FileImportsResult.java index 0f490c7f..5ef31fdc 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/model/FileImportsResult.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/model/FileImportsResult.java @@ -8,7 +8,7 @@ */ public class FileImportsResult { - public String file; // relative file path + public String file; // project-relative file path (e.g. "src/main/java/com/example/Foo.java") public List imports; public List staticImports; public String error; // null if success @@ -18,6 +18,7 @@ public static class ImportEntry { public String kind; // "class" | "interface" | "enum" | "annotation" | "unknown" public String source; // "project" | "external" | "jdk" public String artifact; // only for "external": "spring-context", null for others + public boolean isOnDemand; // true for wildcard imports (e.g. "import com.example.model.*") public ImportEntry() {} diff --git a/src/copilot/tools/javaContextTools.ts b/src/copilot/tools/javaContextTools.ts index 6b0117bb..8743ad94 100644 --- a/src/copilot/tools/javaContextTools.ts +++ b/src/copilot/tools/javaContextTools.ts @@ -18,58 +18,68 @@ * - Each tool returns < 200 tokens * - Structured JSON output * - No classpath resolution, no dependency download - * - * Note: The LanguageModelTool API is not yet in @types/vscode 1.83.1, - * so we use (vscode as any) casts following the same pattern as vscode-java-debug. */ import * as vscode from "vscode"; import { Commands } from "../../commands"; -// ──────────────────────────────────────────────────────────── -// Type shims for vscode.lm LanguageModelTool API -// (not available in @types/vscode 1.83.1) -// ──────────────────────────────────────────────────────────── - -interface LanguageModelTool { - invoke(options: { input: T }, token: vscode.CancellationToken): Promise; -} +// Hard caps to keep tool responses within the < 200 token budget. +const MAX_SYMBOL_DEPTH = 3; +const MAX_SYMBOL_NODES = 80; +const MAX_CALL_RESULTS = 50; +const MAX_TYPE_RESULTS = 50; +const MAX_IMPORTS = 50; -// Access the lm namespace via any-cast -const lmApi = (vscode as any).lm; - -function toResult(data: unknown): any { +function toResult(data: unknown): vscode.LanguageModelToolResult { const text = typeof data === "string" ? data : JSON.stringify(data, null, 2); - return new (vscode as any).LanguageModelToolResult([ - new (vscode as any).LanguageModelTextPart(text), + return new vscode.LanguageModelToolResult([ + new vscode.LanguageModelTextPart(text), ]); } /** * Resolve a file path to a vscode.Uri. * Accepts: - * - Full URI: "file:///home/user/project/src/Main.java" - * - Relative path: "src/main/java/Main.java" - * - Absolute path: "/home/user/project/src/Main.java" or "C:\\Users\\...\\Main.java" + * - Full file URI: "file:///home/user/project/src/Main.java" + * - Relative path: "src/main/java/Main.java" + * - Absolute path: "/home/user/project/src/Main.java" or "C:\\Users\\...\\Main.java" * * Relative paths are resolved against the first workspace folder. + * The resolved URI must use the file: scheme and fall under a workspace folder. */ function resolveFileUri(input: string): vscode.Uri { - // Already a full URI (has scheme like file://, untitled:, etc.) - if (/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(input) || input.startsWith("untitled:")) { - return vscode.Uri.parse(input); + const folders = vscode.workspace.workspaceFolders; + if (!folders || folders.length === 0) { + throw new Error("No workspace folder is open."); } - // Absolute path (Unix or Windows) - if (input.startsWith("/") || /^[a-zA-Z]:[/\\]/.test(input)) { - return vscode.Uri.file(input); + + let uri: vscode.Uri; + + // Full URI — only allow the file: scheme + if (/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(input) || /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(input)) { + uri = vscode.Uri.parse(input); + if (uri.scheme !== "file") { + throw new Error(`Unsupported URI scheme "${uri.scheme}". Only file: URIs are allowed.`); + } + } else if (input.startsWith("/") || /^[a-zA-Z]:[/\\]/.test(input)) { + // Absolute path (Unix or Windows) + uri = vscode.Uri.file(input); + } else { + // Relative path — resolve against first workspace folder + uri = vscode.Uri.joinPath(folders[0].uri, input); } - // Relative path — resolve against workspace folder - const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; - if (workspaceFolder) { - return vscode.Uri.joinPath(workspaceFolder.uri, input); + + // Ensure the resolved path is under a workspace folder + const resolvedPath = uri.fsPath.toLowerCase(); + const isUnderWorkspace = folders.some(folder => { + const folderPath = folder.uri.fsPath.toLowerCase(); + return resolvedPath === folderPath || resolvedPath.startsWith(folderPath + (process.platform === "win32" ? "\\" : "/")); + }); + if (!isUnderWorkspace) { + throw new Error("The resolved path is outside the current workspace."); } - // Fallback: treat as file path - return vscode.Uri.file(input); + + return uri; } // ============================================================ @@ -80,7 +90,7 @@ interface FileStructureInput { uri: string; } -const fileStructureTool: LanguageModelTool = { +const fileStructureTool: vscode.LanguageModelTool = { async invoke(options, _token) { const uri = resolveFileUri(options.input.uri); const symbols = await vscode.commands.executeCommand( @@ -89,22 +99,42 @@ const fileStructureTool: LanguageModelTool = { if (!symbols || symbols.length === 0) { return toResult({ error: "No symbols found. The file may not be recognized by the Java language server." }); } - return toResult(formatSymbols(symbols, 0)); + const counter = { count: 0 }; + const result = symbolsToJson(symbols, 0, counter); + const truncated = counter.count >= MAX_SYMBOL_NODES; + return toResult({ symbols: result, ...(truncated && { truncated: true }) }); }, }; -function formatSymbols(symbols: vscode.DocumentSymbol[], indent: number): string { - return symbols.map(s => { - const prefix = " ".repeat(indent); - const kind = vscode.SymbolKind[s.kind]; - const range = `[L${s.range.start.line + 1}-${s.range.end.line + 1}]`; - const detail = s.detail ? ` ${s.detail}` : ""; - let line = `${prefix}${kind}: ${s.name}${detail} ${range}`; - if (s.children?.length) { - line += "\n" + formatSymbols(s.children, indent + 1); +interface SymbolNode { + name: string; + kind: string; + range: string; + detail?: string; + children?: SymbolNode[]; +} + +function symbolsToJson(symbols: vscode.DocumentSymbol[], depth: number, counter: { count: number }): SymbolNode[] { + const result: SymbolNode[] = []; + for (const s of symbols) { + if (counter.count >= MAX_SYMBOL_NODES) { + break; + } + counter.count++; + const node: SymbolNode = { + name: s.name, + kind: vscode.SymbolKind[s.kind], + range: `L${s.range.start.line + 1}-${s.range.end.line + 1}`, + }; + if (s.detail) { + node.detail = s.detail; + } + if (s.children?.length && depth < MAX_SYMBOL_DEPTH) { + node.children = symbolsToJson(s.children, depth + 1, counter); } - return line; - }).join("\n"); + result.push(node); + } + return result; } // ============================================================ @@ -116,7 +146,7 @@ interface FindSymbolInput { limit?: number; } -const findSymbolTool: LanguageModelTool = { +const findSymbolTool: vscode.LanguageModelTool = { async invoke(options, _token) { const symbols = await vscode.commands.executeCommand( "vscode.executeWorkspaceSymbolProvider", options.input.query, @@ -142,7 +172,7 @@ interface FileImportsInput { uri: string; } -const fileImportsTool: LanguageModelTool = { +const fileImportsTool: vscode.LanguageModelTool = { async invoke(options, _token) { const uri = resolveFileUri(options.input.uri); const result = await vscode.commands.executeCommand( @@ -153,6 +183,9 @@ const fileImportsTool: LanguageModelTool = { if (!result) { return toResult({ error: "No result from Java language server. It may still be loading." }); } + if (Array.isArray(result) && result.length > MAX_IMPORTS) { + return toResult({ imports: result.slice(0, MAX_IMPORTS), total: result.length, truncated: true }); + } return toResult(result); }, }; @@ -167,7 +200,7 @@ interface TypeAtPositionInput { character: number; } -const typeAtPositionTool: LanguageModelTool = { +const typeAtPositionTool: vscode.LanguageModelTool = { async invoke(options, _token) { const uri = resolveFileUri(options.input.uri); const position = new vscode.Position(options.input.line, options.input.character); @@ -192,7 +225,15 @@ function extractTypeSignature(hovers: vscode.Hover[] | undefined): object { if (content instanceof vscode.MarkdownString) { const match = content.value.match(/```java\n([\s\S]*?)```/); if (match) { - const lines = match[1].trim().split("\n").filter(l => l.trim().length > 0); + const lines = match[1].trim().split("\n").filter(l => { + const trimmed = l.trim(); + if (trimmed.length === 0) return false; + // Strip Javadoc and block comment lines + if (trimmed.startsWith("/**") || trimmed.startsWith("*/") || trimmed.startsWith("* ") || trimmed === "*") return false; + // Strip single-line comments + if (trimmed.startsWith("//")) return false; + return true; + }); return { type: lines.join("\n") }; } } @@ -212,7 +253,7 @@ interface CallHierarchyInput { direction: "incoming" | "outgoing"; } -const callHierarchyTool: LanguageModelTool = { +const callHierarchyTool: vscode.LanguageModelTool = { async invoke(options, _token) { const uri = resolveFileUri(options.input.uri); const position = new vscode.Position(options.input.line, options.input.character); @@ -239,7 +280,9 @@ const callHierarchyTool: LanguageModelTool = { }); } - const results = calls.map((call: any) => { + const truncated = calls.length > MAX_CALL_RESULTS; + const capped = truncated ? calls.slice(0, MAX_CALL_RESULTS) : calls; + const results = capped.map((call: any) => { const item = isIncoming ? call.from : call.to; return { name: item.name, @@ -252,6 +295,7 @@ const callHierarchyTool: LanguageModelTool = { symbol: items[0].name, direction: options.input.direction, calls: results, + ...(truncated && { total: calls.length, truncated: true }), }); }, }; @@ -267,7 +311,7 @@ interface TypeHierarchyInput { direction: "supertypes" | "subtypes"; } -const typeHierarchyTool: LanguageModelTool = { +const typeHierarchyTool: vscode.LanguageModelTool = { async invoke(options, _token) { const uri = resolveFileUri(options.input.uri); const position = new vscode.Position(options.input.line, options.input.character); @@ -294,7 +338,9 @@ const typeHierarchyTool: LanguageModelTool = { }); } - const results = types.map(t => ({ + const truncated = types.length > MAX_TYPE_RESULTS; + const capped = truncated ? types.slice(0, MAX_TYPE_RESULTS) : types; + const results = capped.map(t => ({ name: t.name, kind: vscode.SymbolKind[t.kind], detail: t.detail || undefined, @@ -305,6 +351,7 @@ const typeHierarchyTool: LanguageModelTool = { symbol: items[0].name, direction: options.input.direction, types: results, + ...(truncated && { total: types.length, truncated: true }), }); }, }; @@ -314,17 +361,12 @@ const typeHierarchyTool: LanguageModelTool = { // ============================================================ export function registerJavaContextTools(context: vscode.ExtensionContext): void { - // Guard: Language Model API may not be available in older VS Code versions - if (!lmApi || typeof lmApi.registerTool !== "function") { - return; - } - context.subscriptions.push( - lmApi.registerTool("lsp_java_getFileStructure", fileStructureTool), - lmApi.registerTool("lsp_java_findSymbol", findSymbolTool), - lmApi.registerTool("lsp_java_getFileImports", fileImportsTool), - lmApi.registerTool("lsp_java_getTypeAtPosition", typeAtPositionTool), - lmApi.registerTool("lsp_java_getCallHierarchy", callHierarchyTool), - lmApi.registerTool("lsp_java_getTypeHierarchy", typeHierarchyTool), + vscode.lm.registerTool("lsp_java_getFileStructure", fileStructureTool), + vscode.lm.registerTool("lsp_java_findSymbol", findSymbolTool), + vscode.lm.registerTool("lsp_java_getFileImports", fileImportsTool), + vscode.lm.registerTool("lsp_java_getTypeAtPosition", typeAtPositionTool), + vscode.lm.registerTool("lsp_java_getCallHierarchy", callHierarchyTool), + vscode.lm.registerTool("lsp_java_getTypeHierarchy", typeHierarchyTool), ); } From e0308fba92e87463006a559c4643693dfd8288a0 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Tue, 17 Mar 2026 15:22:12 +0800 Subject: [PATCH 13/14] fix: update --- src/copilot/tools/javaContextTools.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/copilot/tools/javaContextTools.ts b/src/copilot/tools/javaContextTools.ts index 8743ad94..423f6eac 100644 --- a/src/copilot/tools/javaContextTools.ts +++ b/src/copilot/tools/javaContextTools.ts @@ -227,11 +227,17 @@ function extractTypeSignature(hovers: vscode.Hover[] | undefined): object { if (match) { const lines = match[1].trim().split("\n").filter(l => { const trimmed = l.trim(); - if (trimmed.length === 0) return false; + if (trimmed.length === 0) { + return false; + } // Strip Javadoc and block comment lines - if (trimmed.startsWith("/**") || trimmed.startsWith("*/") || trimmed.startsWith("* ") || trimmed === "*") return false; + if (trimmed.startsWith("/**") || trimmed.startsWith("*/") || trimmed.startsWith("* ") || trimmed === "*") { + return false; + } // Strip single-line comments - if (trimmed.startsWith("//")) return false; + if (trimmed.startsWith("//")) { + return false; + } return true; }); return { type: lines.join("\n") }; From ad38c4621d636ce22e609d09b0a8c93f6acccf87 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Tue, 17 Mar 2026 15:54:29 +0800 Subject: [PATCH 14/14] fix: chat instrument --- .../instruments/javaLspContext.instructions.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/resources/instruments/javaLspContext.instructions.md b/resources/instruments/javaLspContext.instructions.md index a956d24f..492598c7 100644 --- a/resources/instruments/javaLspContext.instructions.md +++ b/resources/instruments/javaLspContext.instructions.md @@ -9,11 +9,19 @@ This workspace contains Java code. You have access to 6 compiler-accurate LSP to ## Step 1: Load Tools (REQUIRED — do this FIRST) -Before ANY operation, call `tool_search_tool_regex` with: +Before ANY operation, load all 6 tools by calling `tool_search_tool_regex` **twice** (the API returns at most 5 tools per call): + +**Call 1** — basic navigation tools: +``` +lsp_java_findSymbol|lsp_java_getFileStructure|lsp_java_getFileImports ``` -lsp_java_findSymbol|lsp_java_getFileStructure|lsp_java_getCallHierarchy|lsp_java_getTypeHierarchy|lsp_java_getTypeAtPosition|lsp_java_getFileImports + +**Call 2** — hierarchy and type tools: ``` -All 6 tools must be returned. If any are missing, retry once. Do NOT use `grep_search`, `read_file`, `semantic_search`, `search_subagent`, or `runSubagent` on `.java` files until tools are loaded. +lsp_java_getCallHierarchy|lsp_java_getTypeHierarchy|lsp_java_getTypeAtPosition +``` + +All 6 tools must be returned across the two calls. If any are missing, retry that call once. Do NOT use `grep_search`, `read_file`, `semantic_search`, `search_subagent`, or `runSubagent` on `.java` files until all tools are loaded. ## Step 2: Always Prefer LSP Tools for Java