opencode-idea 0.0.8 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -62
- package/package.json +6 -7
- package/skills/idea-run-config/SKILL.md +118 -0
- package/src/ide-guidance.js +58 -23
- package/src/{ide-env.js → mcp/ide-env.js} +1 -1
- package/src/{idea-mcp.js → mcp/idea.js} +2 -2
- package/src/plugin.js +15 -13
- package/src/skill/loader.js +105 -0
- package/CHANGELOG.md +0 -80
- package/src/ide-run-config-skill.js +0 -61
- package/src/ide-run-config-skill.md +0 -92
package/README.md
CHANGED
|
@@ -6,8 +6,7 @@ OpenCode 插件,面向 JetBrains 项目 (IntelliJ IDEA / PyCharm / WebStorm 等)
|
|
|
6
6
|
检索、读、改、构建、重构、调试、数据库……)注册进 OpenCode。
|
|
7
7
|
2. **优先使用 IDE 工具** —— IDE 可用时,向模型注入"项目内操作优先使用 IDEA MCP"的系统提示引导 + 能力映射;IDE 不可用时回退原生工具
|
|
8
8
|
(正常回退,无需声明)。
|
|
9
|
-
3. **注入 IDE 环境变量** —— 从 IDE
|
|
10
|
-
注入每个 OpenCode shell。
|
|
9
|
+
3. **注入 IDE 环境变量** —— 从 IDE 集成终端读取它实际运行的环境,注入每个 OpenCode shell。
|
|
11
10
|
|
|
12
11
|
非 JetBrains 项目完全不受影响。 **全手动**:插件启动不探测、不连接 IDE,只有跑 `/open-in-idea` 才接入;接入之后也没有
|
|
13
12
|
任何自主动作(环境变量只在 `/open-in-idea` 时刷新)。
|
|
@@ -64,13 +63,13 @@ IDE MCP 可用时,插件通过 **系统提示注入**提高模型主动使用 ID
|
|
|
64
63
|
- 检索 → `idea_search_text` / `idea_search_regex` / `idea_search_file`(参数 `q`,可带 `paths` 收窄)
|
|
65
64
|
- 读文件 → `idea_read_file`(`file_path` / `offset` / `limit`);目录 → `idea_list_directory_tree`
|
|
66
65
|
- 改文件 → `idea_apply_patch`(`input`);新建 → `idea_create_new_file`(`pathInProject`)
|
|
67
|
-
- 校验 → `idea_lint_files` / `idea_get_file_problems`
|
|
66
|
+
- 校验 → `idea_lint_files` / `idea_get_file_problems`
|
|
68
67
|
- 重构/格式化 → `idea_rename_refactoring` / `idea_reformat_file`
|
|
69
68
|
- 数据库 → `idea_list_database_connections` / `idea_execute_sql_query` / `idea_preview_table_data` 等;没有数据源时先建议用户在
|
|
70
69
|
IDEA 里配置
|
|
71
70
|
- 边界:只约束 **代码与文件操作**;未暴露的领域 (版本控制、shell/终端等) 工具表里没有 `idea_*` 选项,模型自然走原生
|
|
72
71
|
|
|
73
|
-
不会把整份工具目录重复塞进 system prompt
|
|
72
|
+
不会把整份工具目录重复塞进 system prompt,也不要求模型先写编排代码。IDEA MCP 工具自身的
|
|
74
73
|
description/schema 仍是参数使用的权威来源;工具返回业务错误时先修正参数,不要误判成工具不可用。MCP 不可用时立即
|
|
75
74
|
提示用户执行 `/open-in-idea`,不重复探测或等待。
|
|
76
75
|
|
|
@@ -87,15 +86,16 @@ IDE MCP 只能 **查**(`get_run_configurations`)和 **执行**(`execute_run_conf
|
|
|
87
86
|
- 写完必须用 `idea_get_run_configurations` **校验它能被列出**、并用 `idea_execute_run_configuration` **实跑确认**;
|
|
88
87
|
- 改配置用 `idea_apply_patch` **定向改 option**,不整文件重写。
|
|
89
88
|
|
|
90
|
-
skill 由 `ctx.skill.transform` 在插件 `setup` 时注册, **与 `/open-in-idea` 无关**;非 JetBrains
|
|
91
|
-
`src/
|
|
89
|
+
skill 由 `ctx.skill.transform` 在插件 `setup` 时注册, **与 `/open-in-idea` 无关**;非 JetBrains 项目不受影响。文档放在
|
|
90
|
+
`skills/<id>/SKILL.md`(每个 skill 一个目录,`name` / `description` 走 YAML frontmatter),由 `src/skill/loader.js` 扫描加载 ——
|
|
91
|
+
**加新 skill 只需加一个目录,不用改代码**。
|
|
92
92
|
|
|
93
93
|
## 环境变量注入
|
|
94
94
|
|
|
95
|
-
**唯一来源:IDE 集成终端**(`execute_terminal_command printenv`)。它反映 IDE 实际运行的环境 ——
|
|
96
|
-
|
|
95
|
+
**唯一来源:IDE 集成终端**(`execute_terminal_command printenv`)。它反映 IDE 实际运行的环境 —— 这些环境往往在 .idea
|
|
96
|
+
配置里读不到。
|
|
97
97
|
|
|
98
|
-
抽取的变量 (`src/ide-env.js` 的 `SDK_ENV_KEYS`):`JAVA_HOME` / `JRE_HOME` / `JDK_HOME` / `VIRTUAL_ENV` / `PYTHONPATH` /
|
|
98
|
+
抽取的变量 (`src/mcp/ide-env.js` 的 `SDK_ENV_KEYS`):`JAVA_HOME` / `JRE_HOME` / `JDK_HOME` / `VIRTUAL_ENV` / `PYTHONPATH` /
|
|
99
99
|
`PYTHONHOME` / `GOROOT` / `GOPATH` / `GOBIN` / `NODE_PATH` / `NVM_BIN` / `NVM_DIR` / `MAVEN_HOME` / `M2_HOME` /
|
|
100
100
|
`GRADLE_HOME` / `SDKMAN_DIR` / `CONDA_PREFIX`。
|
|
101
101
|
|
|
@@ -234,9 +234,9 @@ export { default } from "/Users/yutao/Projects/opencode-idea/src/index.js";
|
|
|
234
234
|
- 环境变量依赖 `execute_terminal_command` + Brave Mode;拿不到时环境为空。
|
|
235
235
|
- 一批用不到的 IDEA MCP 工具被硬编码隐藏 (`src/plugin.js` 的 `HIDDEN_IDEA_TOOLS`,共 31 个):VCS (`idea_git_status` /
|
|
236
236
|
`idea_get_repositories`,版本控制走原生 `git`)、Router 派发工具 (`idea_execute_tool`,未启用 router-only 时冗余)、Debugger
|
|
237
|
-
(全部 `idea_xdebug_*`)、Dev Kit MCP、Inspection KTS MCP
|
|
238
|
-
IDEA 里配置,AI 只读和查询)。`idea_execute_terminal_command` 也不暴露给模型
|
|
239
|
-
|
|
237
|
+
(全部 `idea_xdebug_*`)、Dev Kit MCP、Inspection KTS MCP、解释器环境 MCP、以及数据库的建/改数据源 (数据源由用户在
|
|
238
|
+
IDEA 里配置,AI 只读和查询)。`idea_execute_terminal_command` 也不暴露给模型 —— 它只由插件内部读取 IDE 环境,而那是插件直接调
|
|
239
|
+
MCP,不经过模型工具表。
|
|
240
240
|
- 探测只确认「MCP 服务是否在监听」,不校验「当前项目是否已在 IDE 打开」;MCP 关闭时会用一次短探测并立即给出设置提示。
|
|
241
241
|
- OpenCode 的 MCP 自动重连**只覆盖 legacy Streamable HTTP 的 session 过期**(`SessionExpiredError`:有 session id + 404 /
|
|
242
242
|
"Server not initialized" → `recover()` + 重试该次调用);modern 端点 (IDE 2026.2,protocol 2025-06-18,不带 session id)
|
|
@@ -246,56 +246,6 @@ export { default } from "/Users/yutao/Projects/opencode-idea/src/index.js";
|
|
|
246
246
|
- 插件热重载会断开 IDE 连接,需重跑 `/open-in-idea`。
|
|
247
247
|
- 插件不修改任何工具的定义、描述或可用性;IDE MCP 工具以 `idea_*` 直接暴露给模型 (`codemode: false`),不用 `execute` 包裹。
|
|
248
248
|
|
|
249
|
-
### `execute_terminal_command` 与原生 shell:互补,不是替代
|
|
250
|
-
|
|
251
|
-
> **结论:已加入 `HIDDEN_IDEA_TOOLS`,不暴露给模型** —— 下面的对比数据用于说明这个决定,也供手工调用时参考。
|
|
252
|
-
> 理由:① AI 拿不到长任务结果 (约 60 秒硬截止 + 没有工具能读终端缓冲);② "看结尾"被原生覆盖 (原生超限时保尾部,
|
|
253
|
-
> 并把完整输出存成文件);③ 唯一实质价值是"让用户看到 / 接手",而模型不需要它 —— 插件自己的 `printenv` 是
|
|
254
|
-
> **直接调 MCP**(`src/ide-env.js`),不经过模型工具表。
|
|
255
|
-
|
|
256
|
-
两者能力相近但取舍不同 (macOS + IDEA 2026.2.3 实测):
|
|
257
|
-
|
|
258
|
-
| 维度 | 原生 `shell` | IDE `execute_terminal_command` |
|
|
259
|
-
| ---------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------- |
|
|
260
|
-
| 每次调用增量开销 | ~9ms(OS spawn) | ~90ms(多一次 MCP 往返) |
|
|
261
|
-
| 会话常驻 | ❌ 每次新 shell | ❌ 每次新 shell(`reuseExistingTerminalWindow` 也不保留变量 / `cd`) |
|
|
262
|
-
| `timeout` | ✅ 生效:到点即中止并杀掉进程(长任务须自行调大) | **约 60 秒硬截止**,`timeout` 参数无法改变:到点返回已有输出 + `is_timed_out`,**不杀进程** |
|
|
263
|
-
| 输出截断 | `tool_output` 上限(默认 8000 行 / 256KB);想看结尾得在命令里自己接 `tail` | `maxLinesCount` + `truncateMode`,可**只留结尾** |
|
|
264
|
-
| 用户可见性 | 只在 agent 侧 | 在 IDE 里,用户可看 / 可接手 |
|
|
265
|
-
|
|
266
|
-
**各自优势**
|
|
267
|
-
|
|
268
|
-
- **原生**:更快、超时可控 (到点杀进程)、输出上限更宽 —— **默认走这条**。
|
|
269
|
-
- **IDE**:
|
|
270
|
-
① **长任务**(安装 / 构建 / 迁移):`reuseExistingTerminalWindow: true` 让它在 IDE 终端里跑,用户 **能看进度、能随时取消**。
|
|
271
|
-
但工具 **约 60 秒**就返回已产生的输出 (`is_timed_out: true`),之后命令仍在后台跑、AI 拿不到后续 —— 所以它适合
|
|
272
|
-
"**交给用户盯**",不适合"AI 等最终结果"(那要用原生,或让命令把结果写文件再读)。
|
|
273
|
-
② **精确控制返回哪一段 + 行数**:`truncateMode` 取 `START`(保头)/`MIDDLE`(头+尾)/`END`(保尾)+ `maxLinesCount`。
|
|
274
|
-
其中 **`MIDDLE` 是真正独有的** —— 原生超限时 **固定保尾部**,并把 **完整输出存成文件**返回路径,所以 `END` 与原生默认重复,
|
|
275
|
-
而原生那份全量文件反而是 IDE 侧拿不到的 (IDE 丢弃的部分只留在终端)。
|
|
276
|
-
|
|
277
|
-
判据 (手工调用时的参考):**要"人看" 或 要"头 + 尾一起看" (`MIDDLE`) → IDE;其余 → 原生。**
|
|
278
|
-
|
|
279
|
-
**`truncateMode` 速查 (语义反直觉,以实测为准)**
|
|
280
|
-
|
|
281
|
-
| 传值 | 保留哪部分 |
|
|
282
|
-
| -------------- | -------------------------- |
|
|
283
|
-
| 不传 / `START` | 开头 |
|
|
284
|
-
| `MIDDLE` | 头 + 尾 |
|
|
285
|
-
| **`END`** | **结尾**(截断标记在最上面) |
|
|
286
|
-
| `NONE` | 全部,不截断 |
|
|
287
|
-
|
|
288
|
-
默认 `maxLinesCount` ≈ 1000 行,要更多就显式传。注意 `END` 是"**保留结尾**",不是"截掉结尾"。
|
|
289
|
-
|
|
290
|
-
## 测试
|
|
291
|
-
|
|
292
|
-
```bash
|
|
293
|
-
npm test
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
使用 [Vitest](https://vitest.dev/),含纯函数测试、IDE 环境解析、环境合并、启动保护状态机、IDE 启动器、引导文本,以及「假 IDE MCP
|
|
297
|
-
服务」的端到端探测与插件集成测试。
|
|
298
|
-
|
|
299
249
|
## License
|
|
300
250
|
|
|
301
251
|
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-idea",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "OpenCode plugin that connects to JetBrains IDE MCP servers and prefers IDE tools when the current project is open in the IDE",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -8,16 +8,15 @@
|
|
|
8
8
|
"index.js",
|
|
9
9
|
"src/index.js",
|
|
10
10
|
"src/plugin.js",
|
|
11
|
-
"src/idea
|
|
12
|
-
"src/ide-env.js",
|
|
11
|
+
"src/mcp/idea.js",
|
|
12
|
+
"src/mcp/ide-env.js",
|
|
13
13
|
"src/project.js",
|
|
14
14
|
"src/env.js",
|
|
15
15
|
"src/ide-launcher.js",
|
|
16
16
|
"src/ide-guidance.js",
|
|
17
|
-
"src/
|
|
18
|
-
"
|
|
19
|
-
"README.md"
|
|
20
|
-
"CHANGELOG.md"
|
|
17
|
+
"src/skill/loader.js",
|
|
18
|
+
"skills",
|
|
19
|
+
"README.md"
|
|
21
20
|
],
|
|
22
21
|
"scripts": {
|
|
23
22
|
"test": "vitest run",
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: idea-run-config
|
|
3
|
+
description: "管理 JetBrains IDE 的 run configuration:在项目级 `.run/*.run.xml` 里创建、修改、删除运行配置。当用户要新建运行配置、修改或删除已有配置、把脚本或执行流程包成可运行的配置,或让 AI 执行用户手配的配置时使用。只操作项目级 `.run/` 文件,不碰 `.idea/workspace.xml`。"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# JetBrains Run Configuration 管理
|
|
7
|
+
|
|
8
|
+
## 怎么操作:查询走 MCP,增删改走文件
|
|
9
|
+
|
|
10
|
+
| 动作 | 手段 |
|
|
11
|
+
| ----------------------------- | ------------------------------------------------------------------------- |
|
|
12
|
+
| 查看有哪些配置 / 判断是否生效 | **MCP 工具** `idea_get_run_configurations` |
|
|
13
|
+
| 新增 / 修改 / 删除 | **直接操作文件** `.run/<name>.run.xml` |
|
|
14
|
+
| 执行 / 调试 | **MCP 工具** `idea_execute_run_configuration` |
|
|
15
|
+
| 找同类型样本 | 只读 **`.run/`** 下的 `*.run.xml`(`idea_search_file` + `paths: [".run"]`) |
|
|
16
|
+
|
|
17
|
+
## 存放位置:只有 `.run/`
|
|
18
|
+
|
|
19
|
+
所有配置都写 **项目根 `.run/<name>.run.xml`**。
|
|
20
|
+
|
|
21
|
+
**只做项目级配置,不存在全局(IDE 级)配置** —— 所以永远不写 IDE 的全局配置目录,也不写 `.idea/workspace.xml`。
|
|
22
|
+
|
|
23
|
+
新建配置时 `.run/` 的结构:
|
|
24
|
+
|
|
25
|
+
```xml
|
|
26
|
+
|
|
27
|
+
<component name="ProjectRunConfigurationManager">
|
|
28
|
+
<configuration default="false" name="<名字>" type="<类型 ID>">
|
|
29
|
+
<option name="..." value="..."/>
|
|
30
|
+
<envs/>
|
|
31
|
+
<method v="2"/>
|
|
32
|
+
</configuration>
|
|
33
|
+
</component>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 铁律
|
|
37
|
+
|
|
38
|
+
1. 不要凭空写 option 名。先在 **`.run/` 里**找真实样本:
|
|
39
|
+
- 用 `idea_search_file` 传 `q="*.run.xml"` + `paths: [".run"]` 看项目里已有的配置(**只看 `.run/`**,不去翻 `.idea/`)。
|
|
40
|
+
- 没有目标类型 → 请用户在 IDEA 里新建一个配置,勾选 "Store as project file" 并**存到项目根的 `.run/`**,再读生成的 XML 当模板。
|
|
41
|
+
- 仍然没有 → 直接说明需要该类型的真实样本,不要猜 option 名。
|
|
42
|
+
2. 写完必须校验:
|
|
43
|
+
- **唯一判据**是 `idea_get_run_configurations` 里能看到它。**看不到就是错的** —— 不许用"应该生效了"结案。
|
|
44
|
+
- 看不到的常见原因(按概率):`type` 不是 IDE 当前启用的类型(**IDEA 会静默忽略整个文件,日志里也不报**)、根 component 名写错、
|
|
45
|
+
结构或 option 名不对。改掉重试。
|
|
46
|
+
- 能看到不等于能跑:`idea_execute_run_configuration` 传 `configurationName` 实跑一次确认。Shell Script 必须
|
|
47
|
+
`EXECUTE_IN_TERMINAL=false`,否则实跑直接失败(见"样本"一节)。
|
|
48
|
+
- IDE MCP 不可用 → 提示用户执行 `/open-in-idea` 后再校验,不要跳过校验当成成功。
|
|
49
|
+
3. 改配置用定向修改:只用 `idea_apply_patch` 改需要的那几行 option,不要整文件重写。
|
|
50
|
+
4. 删除配置 = 删除对应的 `.run/<name>.run.xml`;不要试图从 `.idea/workspace.xml` 里删。
|
|
51
|
+
|
|
52
|
+
## 常见字段
|
|
53
|
+
|
|
54
|
+
- `$PROJECT_DIR$`:项目根;`$MODULE_WORKING_DIR$`:模块目录。
|
|
55
|
+
- 环境变量:
|
|
56
|
+
|
|
57
|
+
```xml
|
|
58
|
+
|
|
59
|
+
<envs>
|
|
60
|
+
<env name="KEY" value="VALUE"/>
|
|
61
|
+
</envs>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- Before launch 流程:`<method v="2">` 里加 `Make` 选项会先构建。
|
|
65
|
+
|
|
66
|
+
## 起步参考 (不是契约;动手前一律以真实样本为准)
|
|
67
|
+
|
|
68
|
+
| 用途 | type | 关键 option |
|
|
69
|
+
| ---------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
70
|
+
| Shell 脚本 | `ShConfigurationType` | 内联脚本:`SCRIPT_TEXT` + `EXECUTE_SCRIPT_FILE=false`;脚本文件:`SCRIPT_PATH` + `EXECUTE_SCRIPT_FILE=true`;`INTERPRETER_PATH`;`SCRIPT_WORKING_DIRECTORY` |
|
|
71
|
+
| Java 应用 | `Application` | `MAIN_CLASS_NAME`;`WORKING_DIRECTORY`;`PROGRAM_PARAMETERS`;`VM_PARAMETERS` |
|
|
72
|
+
| JUnit | `JUnit` | `TEST_OBJECT`;`MAIN_CLASS_NAME`;`WORKING_DIRECTORY` |
|
|
73
|
+
| Gradle | `GradleRunConfiguration` | `ExternalProjectPath`;`TaskList` |
|
|
74
|
+
| npm | `js.build_tools.npm` | `package-json`;`command`;`scripts` |
|
|
75
|
+
| 串行流程 | `CompoundRunConfiguration` | `method` 里多个 `RunConfigurationTask`(带 `run_configuration_name` / `run_configuration_type`) |
|
|
76
|
+
|
|
77
|
+
### 样本:Shell Script(IDEA 自己生成的 `.run/*.run.xml`)
|
|
78
|
+
|
|
79
|
+
```xml
|
|
80
|
+
<component name="ProjectRunConfigurationManager">
|
|
81
|
+
<configuration default="false" name="Unnamed" type="ShConfigurationType" nameIsGenerated="true">
|
|
82
|
+
<option name="SCRIPT_TEXT" value="" />
|
|
83
|
+
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
|
84
|
+
<option name="SCRIPT_PATH" value="" />
|
|
85
|
+
<option name="SCRIPT_OPTIONS" value="" />
|
|
86
|
+
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
|
|
87
|
+
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
|
88
|
+
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
|
|
89
|
+
<option name="INTERPRETER_PATH" value="/bin/zsh" />
|
|
90
|
+
<option name="INTERPRETER_OPTIONS" value="" />
|
|
91
|
+
<option name="EXECUTE_IN_TERMINAL" value="true" />
|
|
92
|
+
<option name="EXECUTE_SCRIPT_FILE" value="true" />
|
|
93
|
+
<envs />
|
|
94
|
+
<method v="2" />
|
|
95
|
+
</configuration>
|
|
96
|
+
</component>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
要点:`INDEPENDENT_*` 三件套表示该字段**独立于模板**(true = 用本配置里的值)。**内联脚本**要 `EXECUTE_SCRIPT_FILE=false` 并填
|
|
100
|
+
`SCRIPT_TEXT`;**跑脚本文件**要 `EXECUTE_SCRIPT_FILE=true` 并填 `SCRIPT_PATH`(上面的样本是未配置的空模板,所以两处都是空串)。
|
|
101
|
+
|
|
102
|
+
AI 自己写的配置一律给 `EXECUTE_IN_TERMINAL=false`;用户已有的 `true` 配置要执行,先问要不要改。
|
|
103
|
+
|
|
104
|
+
## 命名
|
|
105
|
+
|
|
106
|
+
- 把 `name` 转成文件名:去掉 `/ \ : * ? " < > |` 等非法字符,写成 `.run/<净化后的名字>.run.xml`。
|
|
107
|
+
- 目标文件已存在时,先确认是要覆盖。
|
|
108
|
+
|
|
109
|
+
## 流程
|
|
110
|
+
|
|
111
|
+
1. **查现状**:`idea_get_run_configurations`(有哪些配置)。要找样本则在 `.run/` 里读 `*.run.xml`(限定 `paths: [".run"]`)。
|
|
112
|
+
2. **定结构**:有同类样本 → 照它的结构改写;没有 → 请用户在 IDEA 里建一个并**存到 `.run/`**;仍然没有 → 说明需要真实样本,
|
|
113
|
+
不猜 option 名。
|
|
114
|
+
3. **写文件**:`idea_create_new_file` 写 `.run/<name>.run.xml`。
|
|
115
|
+
4. **校验(必做)**:`idea_get_run_configurations` 必须能看到它 —— 看不到就是错的,不许用"应该生效了"结案。
|
|
116
|
+
5. **实跑(必做)**:`idea_execute_run_configuration` 传 `configurationName` 确认能跑(Shell Script 必须
|
|
117
|
+
`EXECUTE_IN_TERMINAL=false`)。
|
|
118
|
+
6. **改 / 删**:改 = `idea_apply_patch` 定向改 option,不要整文件重写;删 = 删除 `.run/<name>.run.xml`,再确认它从列表里消失。
|
package/src/ide-guidance.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
// IDEA MCP routing guidance injected into the model's system prompt.
|
|
2
2
|
//
|
|
3
3
|
// Two rules for this file:
|
|
4
|
-
// 1.
|
|
4
|
+
// 1. Per tool: what it does, its parameters, and the positive flow for using
|
|
5
|
+
// it. No caveats or edge cases, and nothing about languages or frameworks —
|
|
6
|
+
// the project's own setup decides that.
|
|
5
7
|
// 2. The boundary is the project path. Inside it -> idea_* tools.
|
|
6
8
|
// Outside it -> whatever else is available.
|
|
7
9
|
//
|
|
8
10
|
// The IDE MCP server is registered with `codemode: false`, so every idea_*
|
|
9
|
-
// tool is exposed directly on the provider tool list with its full schema.
|
|
10
|
-
//
|
|
11
|
-
//
|
|
11
|
+
// tool is exposed directly on the provider tool list with its full schema. This
|
|
12
|
+
// text only says which tool to use for which operation, plus the parameters and
|
|
13
|
+
// result shapes that are not obvious from the schema alone.
|
|
12
14
|
|
|
13
15
|
/** Build the guidance text for one project path. */
|
|
14
16
|
export function buildIdeGuidance(projectPath) {
|
|
@@ -17,28 +19,61 @@ export function buildIdeGuidance(projectPath) {
|
|
|
17
19
|
``,
|
|
18
20
|
`项目路径: \`${projectPath}\``,
|
|
19
21
|
``,
|
|
20
|
-
`##
|
|
22
|
+
`## 总则`,
|
|
21
23
|
`- 路径在 \`${projectPath}\` 内的代码与文件操作,必须使用 \`idea_*\` 工具。`,
|
|
22
|
-
`-
|
|
23
|
-
`-
|
|
24
|
-
`-
|
|
25
|
-
`- 读文件用 \`idea_read_file\`,不要用 \`read\`。`,
|
|
26
|
-
`- 浏览目录用 \`idea_list_directory_tree\`,不要用 \`ls\` / \`find\`。`,
|
|
27
|
-
`- 修改已有文件用 \`idea_apply_patch\`,不要用 \`edit\` / \`patch\`。`,
|
|
28
|
-
`- 新建文件用 \`idea_create_new_file\`,不要用 \`write\`。`,
|
|
29
|
-
`- 改完代码用 \`idea_lint_files\` 或 \`idea_get_file_problems\` 校验。`,
|
|
30
|
-
`- 工具参数按该工具的 schema 传。`,
|
|
24
|
+
`- 路径在 \`${projectPath}\` 外:用其他可用工具。`,
|
|
25
|
+
`- 直接调用工具,不要用 \`execute\` 包裹;参数按各工具 schema 传。`,
|
|
26
|
+
`- \`idea_*\` 工具缺失、连接失败或超时:提示用户执行 \`/open-in-idea\`,不要重试。`,
|
|
31
27
|
``,
|
|
32
|
-
`##
|
|
33
|
-
`- 数据库操作走 IDEA 的 Database 工具(\`idea_list_database_connections\` / \`idea_introspect_schema\` / \`idea_execute_sql_query\` / \`idea_preview_table_data\` 等),不要用原生 CLI 客户端。`,
|
|
34
|
-
`- 先 \`idea_list_database_connections\` 看有没有现成数据源。`,
|
|
35
|
-
`- 没有数据源时:若目标数据库在 IDEA Database Tools 支持范围内(PostgreSQL / MySQL / MariaDB / Oracle / SQL Server / SQLite / DB2 / H2 / Derby / Sybase / Vertica / Greenplum / ClickHouse / CockroachDB / Snowflake / BigQuery / Redshift / MongoDB / Redis / Cassandra / Couchbase / Hive / DynamoDB / Exasol 等,或任何提供 JDBC 驱动的库),先建议用户在 IDEA 里配好数据源,再回来用工具操作。`,
|
|
36
|
-
`- 不要自己建或改数据源,不要向用户索取数据库凭据。`,
|
|
28
|
+
`## 工具补充(每条只写补充说明;工具定义以自身 schema 为准)`,
|
|
37
29
|
``,
|
|
38
|
-
|
|
39
|
-
`-
|
|
40
|
-
`-
|
|
41
|
-
`- \`
|
|
30
|
+
`### 检索`,
|
|
31
|
+
`- \`idea_search_text\`:不要用 \`grep\`。`,
|
|
32
|
+
`- \`idea_search_regex\`:不要用 \`grep -E\`;结果带行列坐标。`,
|
|
33
|
+
`- \`idea_search_file\`:不要用 \`glob\`。`,
|
|
34
|
+
`- \`idea_search_symbol\`:按名字找符号的声明位置;要看签名 / 文档接着用 \`idea_get_symbol_info\`。`,
|
|
35
|
+
``,
|
|
36
|
+
`### 阅读`,
|
|
37
|
+
`- \`idea_read_file\`:不要用 \`read\`;支持 \`<jar>!/<entry>\` 与 \`jrt://\` 形式的包内读取。`,
|
|
38
|
+
`- \`idea_list_directory_tree\`:不要用 \`ls\` / \`find\`;\`maxDepth\` 控深度。`,
|
|
39
|
+
`- \`idea_get_symbol_info\`:一个符号的签名 / 文档 / 声明位置,\`filePath\` + \`line\` + \`column\`。`,
|
|
40
|
+
`- \`idea_get_all_open_file_paths\`:用户在 IDE 里打开了哪些文件(含当前激活的那个)。`,
|
|
41
|
+
`- \`idea_open_file_in_editor\`:把文件在 IDE 里打开并激活,给用户看。`,
|
|
42
|
+
``,
|
|
43
|
+
`### 写入`,
|
|
44
|
+
`- \`idea_apply_patch\`:不要用 \`edit\` / \`patch\`;只接受项目内路径。`,
|
|
45
|
+
`- \`idea_create_new_file\`:不要用 \`write\`;\`overwrite\` 控制同名覆盖。`,
|
|
46
|
+
`- \`idea_rename_refactoring\`:按符号改名,项目内的引用一并更新。`,
|
|
47
|
+
`- \`idea_reformat_file\`:按 IDE 的代码风格重排。`,
|
|
48
|
+
``,
|
|
49
|
+
`### 校验`,
|
|
50
|
+
`- \`idea_lint_files\`:批量检查,\`files\` 传数组;只列有问题的文件。`,
|
|
51
|
+
`- \`idea_get_file_problems\`:单文件检查,\`filePath\`;结果在 \`errors\`。`,
|
|
52
|
+
`- \`idea_build_project\`:构建校验,\`filesToRebuild\` 可只构建指定文件;有问题返回在 \`problems\`,没返回即没问题。`,
|
|
53
|
+
``,
|
|
54
|
+
`### 分析`,
|
|
55
|
+
`- \`idea_analyze_calls\`:调用关系,\`symbolFqn\` 传符号名,\`analysisKind\` 取 \`INCOMING_CALLS\` / \`OUTGOING_CALLS\`;\`depth\` 控层数,\`childOffset\` 翻页。`,
|
|
56
|
+
`- \`idea_get_project_modules\`:项目模块及类型。`,
|
|
57
|
+
`- \`idea_get_project_dependencies\`:项目直接声明的依赖。`,
|
|
58
|
+
``,
|
|
59
|
+
`### 运行配置`,
|
|
60
|
+
`- \`idea_get_run_configurations\`:读项目级 \`.run/*.run.xml\`(增删改直接改文件);传 \`filePath\` 可列该文件的运行入口。`,
|
|
61
|
+
`- \`idea_execute_run_configuration\`:跑已有配置;Shell Script 要 \`EXECUTE_IN_TERMINAL: false\`;覆盖参数(\`programArguments\` / \`workingDirectory\` / \`envs\`)需配置支持 \`supportsDynamicLaunchOverrides\`。`,
|
|
62
|
+
``,
|
|
63
|
+
`### 数据库 (数据源由用户在 IDE 里配置:不要自己建/改,不要向用户索取数据库凭据)`,
|
|
64
|
+
`- \`idea_list_database_connections\`:先看有没有现成数据源;每条给 \`id\` / \`dbms\` / \`jdbcUrl\` / \`readOnly\` / \`isDDL\`。`,
|
|
65
|
+
`- \`idea_test_database_connection\`(\`id\`):\`hasProblems\` 为 \`NO\` 才算通,另带 DBMS / 驱动版本与 ping。`,
|
|
66
|
+
`- \`idea_list_database_schemas\`(\`connectionId\`):MySQL 下 \`databaseName\` 为空、库名在 \`schemaName\`;带 \`isIntrospected\`。`,
|
|
67
|
+
`- \`idea_list_schema_object_kinds\`(\`connectionId\`):先看有哪些类型可查(\`table\` / \`view\` / \`routine\` / \`scheduled-event\`)。`,
|
|
68
|
+
`- \`idea_list_schema_objects\`(\`connectionId\` + \`databaseName\` + \`schemaName\` + \`kind\`):列该 schema 下的对象。`,
|
|
69
|
+
`- \`idea_introspect_schema\`(\`connectionId\` + \`databaseName\` + \`schemaName\`):拉元数据,返回该 schema 的 \`isIntrospected\`。`,
|
|
70
|
+
`- \`idea_get_database_object_description\`(\`kind\` + \`objectName\`):层级文本,含列类型 / 列注释 / 索引 / 主键。`,
|
|
71
|
+
`- \`idea_execute_sql_query\`:跑 SQL;结果在 \`text\`(CSV)与 \`resultSetId\`,一次 10 行,后续用 \`fetch_query_result\` 翻页。`,
|
|
72
|
+
`- \`idea_fetch_query_result\`(\`resultSetId\` + \`offset\`):按页继续取结果,10 行一页。`,
|
|
73
|
+
`- \`idea_preview_table_data\`(\`tableName\` + \`maxRowCount\`):直接回 CSV。`,
|
|
74
|
+
`- \`idea_list_recent_sql_queries\`(\`connectionId\`):最近(含正在跑)的查询,带 \`sessionId\` / \`state\` / \`status\`。`,
|
|
75
|
+
`- \`idea_cancel_sql_query\`(\`sessionId\`):取消正在跑的查询,成功后该条 \`status\` 变 \`CANCELLED\`。`,
|
|
76
|
+
`- 没有数据源时:若目标库在 IDEA Database Tools 支持范围内(PostgreSQL / MySQL / MariaDB / Oracle / SQL Server / SQLite / DB2 / H2 / Derby / Sybase / Vertica / Greenplum / ClickHouse / CockroachDB / Snowflake / BigQuery / Redshift / MongoDB / Redis / Cassandra / Couchbase / Hive / DynamoDB / Exasol 等,或任何提供 JDBC 驱动的库),先建议用户在 IDEA 里配好数据源,再回来用工具操作。`,
|
|
42
77
|
].join('\n');
|
|
43
78
|
}
|
|
44
79
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// variables. The IDE terminal is the single source of truth for the environment
|
|
3
3
|
// the IDE actually runs with (version-managed Node, goenv Go, SDKMAN Java, ...).
|
|
4
4
|
|
|
5
|
-
import { callTool } from './idea
|
|
5
|
+
import { callTool } from './idea.js';
|
|
6
6
|
|
|
7
7
|
/** SDK-related variables worth mirroring into OpenCode's shell environment. */
|
|
8
8
|
export const SDK_ENV_KEYS = [
|
|
@@ -81,7 +81,7 @@ export async function probePort(port, options = {}) {
|
|
|
81
81
|
params: {
|
|
82
82
|
protocolVersion: PROTOCOL_VERSION,
|
|
83
83
|
capabilities: {},
|
|
84
|
-
clientInfo: { name: 'opencode-idea', version: '0.0.
|
|
84
|
+
clientInfo: { name: 'opencode-idea', version: '0.0.9' },
|
|
85
85
|
},
|
|
86
86
|
}),
|
|
87
87
|
signal: controller.signal,
|
|
@@ -151,7 +151,7 @@ export async function callTool(port, projectPath, name, args = {}, options = {})
|
|
|
151
151
|
params: {
|
|
152
152
|
protocolVersion: PROTOCOL_VERSION,
|
|
153
153
|
capabilities: {},
|
|
154
|
-
clientInfo: { name: 'opencode-idea', version: '0.0.
|
|
154
|
+
clientInfo: { name: 'opencode-idea', version: '0.0.9' },
|
|
155
155
|
},
|
|
156
156
|
});
|
|
157
157
|
const response = await post({
|
package/src/plugin.js
CHANGED
|
@@ -27,8 +27,8 @@ import {
|
|
|
27
27
|
IDEA_SERVER_NAME,
|
|
28
28
|
findIdePort,
|
|
29
29
|
serverConfig,
|
|
30
|
-
} from './idea
|
|
31
|
-
import { readIdeTerminalEnv } from './ide-env.js';
|
|
30
|
+
} from './mcp/idea.js';
|
|
31
|
+
import { readIdeTerminalEnv } from './mcp/ide-env.js';
|
|
32
32
|
import { currentProjectPath, hasIdeaDirectory } from './project.js';
|
|
33
33
|
import { applyEnv, mergeEnv } from './env.js';
|
|
34
34
|
import {
|
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
appendIdeGuidance,
|
|
41
41
|
appendIdeRecoveryGuidance,
|
|
42
42
|
} from './ide-guidance.js';
|
|
43
|
-
import {
|
|
43
|
+
import { readSkills } from './skill/loader.js';
|
|
44
44
|
|
|
45
45
|
const DEFAULT_LAUNCH_COOLDOWN_MS = 120000;
|
|
46
46
|
const DEFAULT_LAUNCH_MAX_ATTEMPTS = 5;
|
|
@@ -86,7 +86,7 @@ export const HIDDEN_IDEA_TOOLS = [
|
|
|
86
86
|
// Router
|
|
87
87
|
'idea_execute_tool',
|
|
88
88
|
// Terminal: the plugin reads the IDE env through its own direct MCP call
|
|
89
|
-
// (`src/ide-env.js`), so the model never needs this tool. Measured value for
|
|
89
|
+
// (`src/mcp/ide-env.js`), so the model never needs this tool. Measured value for
|
|
90
90
|
// the agent is nil: ~60s hard cutoff with no way to read the terminal buffer,
|
|
91
91
|
// and the native shell already keeps the output tail plus a full-output file.
|
|
92
92
|
'idea_execute_terminal_command',
|
|
@@ -232,8 +232,9 @@ export default {
|
|
|
232
232
|
* transform: (cb: (editor: { add: (definition: {
|
|
233
233
|
* id: string,
|
|
234
234
|
* name: string,
|
|
235
|
-
* description
|
|
236
|
-
*
|
|
235
|
+
* description?: string,
|
|
236
|
+
* autoinvoke?: boolean,
|
|
237
|
+
* path: string,
|
|
237
238
|
* content: string,
|
|
238
239
|
* }) => void }) => void) => Promise<{ dispose: () => Promise<void> | void }>,
|
|
239
240
|
* },
|
|
@@ -504,19 +505,20 @@ export default {
|
|
|
504
505
|
}
|
|
505
506
|
};
|
|
506
507
|
|
|
507
|
-
// The
|
|
508
|
-
//
|
|
509
|
-
//
|
|
508
|
+
// The shipped skills are static knowledge (`skills/<id>/SKILL.md`), e.g. the
|
|
509
|
+
// run-configuration workflow: where `.run/*.run.xml` live, how to bootstrap a
|
|
510
|
+
// schema from a real example, and how to verify a write. JetBrains projects
|
|
511
|
+
// only, and independent of `/open-in-idea`.
|
|
510
512
|
//
|
|
511
|
-
// Deliberately NOT wrapped in a catch:
|
|
513
|
+
// Deliberately NOT wrapped in a catch: these are core features of the plugin,
|
|
512
514
|
// so a rejected definition must fail loudly. Swallowing it would ship a
|
|
513
515
|
// silently incomplete plugin — which is exactly how 0.0.6 lost
|
|
514
516
|
// `/open-in-idea` without anyone noticing until the command was gone.
|
|
515
517
|
if (isIdeaProject && typeof ctx.skill?.transform === 'function') {
|
|
516
|
-
// Read the
|
|
517
|
-
const
|
|
518
|
+
// Read the documents before the transform: callbacks must stay synchronous.
|
|
519
|
+
const skills = readSkills();
|
|
518
520
|
skillRegistration = await ctx.skill.transform((editor) => {
|
|
519
|
-
editor.add(
|
|
521
|
+
for (const skill of skills) editor.add(skill);
|
|
520
522
|
});
|
|
521
523
|
}
|
|
522
524
|
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// The skills this plugin ships, loaded from `skills/<id>/SKILL.md`.
|
|
2
|
+
//
|
|
3
|
+
// This directory holds the *code* (singular `src/skill/`); the documents live in
|
|
4
|
+
// the package-root `skills/` (plural), so reviewing the feature means two clearly
|
|
5
|
+
// named places: the loader here, the content there.
|
|
6
|
+
//
|
|
7
|
+
// The layout mirrors the convention OpenCode itself reads: one directory per
|
|
8
|
+
// skill, the document named `SKILL.md`, `name`/`description` in YAML
|
|
9
|
+
// frontmatter, and the directory as the base for any relative asset the skill
|
|
10
|
+
// ships next to it. Adding a skill is therefore a directory, not a code change.
|
|
11
|
+
//
|
|
12
|
+
// Three contract details are enforced deliberately:
|
|
13
|
+
// * `description` is required. OpenCode drops a skill without one from the
|
|
14
|
+
// model-facing list, so a missing description would silently disable the
|
|
15
|
+
// skill instead of failing.
|
|
16
|
+
// * the returned definition must satisfy `Skill.Info` exactly (`id` / `name` /
|
|
17
|
+
// `description` / `path` / `content`). A rejected definition fails the skill
|
|
18
|
+
// transform, and a transform failure disables the whole plugin.
|
|
19
|
+
// * `content` is the document body only. The frontmatter is metadata and must
|
|
20
|
+
// not leak into the model prompt.
|
|
21
|
+
//
|
|
22
|
+
// The frontmatter reader below handles only the flat `key: value` form this
|
|
23
|
+
// package writes. It is not a YAML engine and must not grow into one.
|
|
24
|
+
|
|
25
|
+
import { readFileSync, readdirSync } from 'node:fs';
|
|
26
|
+
import { fileURLToPath } from 'node:url';
|
|
27
|
+
|
|
28
|
+
/** Package-root `skills/`: one sub-directory per skill, next to `src/`. */
|
|
29
|
+
const SKILLS_DIR = new URL('../../skills/', import.meta.url);
|
|
30
|
+
|
|
31
|
+
/** Document name inside each skill directory. */
|
|
32
|
+
export const SKILL_FILE_NAME = 'SKILL.md';
|
|
33
|
+
|
|
34
|
+
const FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n)?/;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {string} value
|
|
38
|
+
* @returns {string}
|
|
39
|
+
*/
|
|
40
|
+
function unquote(value) {
|
|
41
|
+
const quote = value[0];
|
|
42
|
+
if (value.length >= 2 && (quote === '"' || quote === "'") && value.endsWith(quote)) {
|
|
43
|
+
return value.slice(1, -1);
|
|
44
|
+
}
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Read the flat `key: value` frontmatter block a `SKILL.md` must start with.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} text
|
|
52
|
+
* @param {string} source Used in error messages.
|
|
53
|
+
* @returns {Record<string, string>}
|
|
54
|
+
*/
|
|
55
|
+
export function parseSkillFrontmatter(text, source) {
|
|
56
|
+
const match = FRONTMATTER.exec(text);
|
|
57
|
+
if (!match) throw new Error(`${source}: ${SKILL_FILE_NAME} must start with a YAML frontmatter block`);
|
|
58
|
+
/** @type {Record<string, string>} */
|
|
59
|
+
const fields = {};
|
|
60
|
+
for (const raw of match[1].split(/\r?\n/)) {
|
|
61
|
+
const line = raw.trim();
|
|
62
|
+
if (line === '' || line.startsWith('#')) continue;
|
|
63
|
+
const entry = /^([A-Za-z0-9_/-]+):[ \t]*(.*)$/.exec(line);
|
|
64
|
+
if (!entry) throw new Error(`${source}: unsupported frontmatter line: ${line}`);
|
|
65
|
+
fields[entry[1]] = unquote(entry[2].trim());
|
|
66
|
+
}
|
|
67
|
+
return fields;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Load one skill directory into a `Skill.Info`-shaped definition.
|
|
72
|
+
*
|
|
73
|
+
* @param {string} id Directory name, used as the skill id.
|
|
74
|
+
* @returns {{ id: string, name: string, description: string, path: string, content: string }}
|
|
75
|
+
*/
|
|
76
|
+
export function readSkill(id) {
|
|
77
|
+
const file = new URL(`${id}/${SKILL_FILE_NAME}`, SKILLS_DIR);
|
|
78
|
+
const source = `skills/${id}/${SKILL_FILE_NAME}`;
|
|
79
|
+
const text = readFileSync(file, 'utf8');
|
|
80
|
+
const fields = parseSkillFrontmatter(text, source);
|
|
81
|
+
const description = fields.description;
|
|
82
|
+
if (!description) {
|
|
83
|
+
throw new Error(`${source}: frontmatter needs a "description", otherwise the skill is never offered`);
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
id,
|
|
87
|
+
name: fields.name || id,
|
|
88
|
+
description,
|
|
89
|
+
path: fileURLToPath(file),
|
|
90
|
+
content: text.replace(FRONTMATTER, '').replace(/^\s+/, '').trimEnd(),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Every shipped skill, sorted by id.
|
|
96
|
+
*
|
|
97
|
+
* @returns {Array<{ id: string, name: string, description: string, path: string, content: string }>}
|
|
98
|
+
*/
|
|
99
|
+
export function readSkills() {
|
|
100
|
+
return readdirSync(SKILLS_DIR, { withFileTypes: true })
|
|
101
|
+
.filter((entry) => entry.isDirectory())
|
|
102
|
+
.map((entry) => entry.name)
|
|
103
|
+
.sort()
|
|
104
|
+
.map((id) => readSkill(id));
|
|
105
|
+
}
|
package/CHANGELOG.md
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## 0.0.8
|
|
4
|
-
|
|
5
|
-
- **撤销 0.0.7 的「skill 注册尽力而为」**。skill 是本插件的核心功能,不是可选知识:定义被 schema 拒绝就说明我们写错了,必须**立即失败**。原来那个 catch 会把「功能残缺」伪装成「运行正常」—— 正是 0.0.6 丢掉 `/open-in-idea` 却没人察觉的那种失败模式。现在恢复为直接注册 (无 try/catch)。
|
|
6
|
-
- **补测试**:断言打包后的 skill 文档 (`src/ide-run-config-skill.md`) 真实存在,这样 `.md` 漏进 `package.json` 的 `files` 会在测试阶段暴露,而不是在 `setup` 时才炸。
|
|
7
|
-
- **文档**:README 限制节写明 skill 字段名以 schema 为准 (`Skill.Info` 要 `path`,官方文档里的 `location` 不存在),用错会禁用整个插件。
|
|
8
|
-
|
|
9
|
-
## 0.0.7
|
|
10
|
-
|
|
11
|
-
- **修复:0.0.6 因 skill schema 校验失败导致整个插件被禁用**。`idea-run-config` skill 的定义用了 `location` 字段,而 `Skill.Info` 要求的是 `path` (AbsolutePath);`ctx.skill.transform` 因此抛 `SchemaError(Missing key at ["path"])`。OpenCode 的策略是**任何 transform 失败都会禁用整个插件**,所以 0.0.6 里 `/open-in-idea` 命令和 IDE MCP 注册都没能生效 (日志:`disabled plugin after transform failure plugin.id=opencode-idea state=skill`)。现在改用 `path`,并在测试里钉死字段集合 (`content` / `description` / `id` / `name` / `path`)。
|
|
12
|
-
- **skill 注册改为尽力而为**:`runConfigSkill()` 或 `ctx.skill.transform` 抛错时只放弃 skill,不再中断 `setup` —— 可选知识不该拖垮命令与 MCP 接线。
|
|
13
|
-
- **新增回归测试**:skill transform 被拒时 `/open-in-idea` 仍然注册。
|
|
14
|
-
|
|
15
|
-
## 0.0.6
|
|
16
|
-
|
|
17
|
-
- **修复:工具报错不再注销 IDE MCP**。旧实现在 `tool.execute.after` 里把**任何** `idea_*` 报错都当成「端点已死」并注销整个注册 —— 业务错误 (例如路径不存在返回 `File not found`) 和一次传输抖动都会触发;注销后紧跟的 `ctx.mcp.reload()` 还会掐断同一批正在执行的其他调用。现在插件**不注册**该钩子:连接状态以 OpenCode 自己维护的工具快照为准 (`context` 钩子收到的 `event.tools` 里有没有 `idea_*`),只有 `/open-in-idea` 会 (重新) 连接。
|
|
18
|
-
- **隐藏 31 个用不到的 IDEA 工具** (`HIDDEN_IDEA_TOOLS`):VCS、Router 派发工具、Debugger (全部 `idea_xdebug_*`)、Dev Kit MCP、Inspection KTS MCP、Python Environment MCP、数据库建/改数据源、终端 `execute_terminal_command`。模型可见的 `idea_*` 从 64 降到 33。
|
|
19
|
-
- **引导词重写**:边界从「所有操作」收窄为「**代码与文件操作**」(未暴露的领域自然走原生,不再列例外);新增数据库段 (走 IDEA Database 工具 + 用户配数据源 + 不索取凭据);删除终端命令段 (该工具已隐藏)。
|
|
20
|
-
- **新增 `idea-run-config` skill**:教模型在 `.run/*.run.xml` 里建/改/删 run configuration,并用 `get_run_configurations` 校验、`execute_run_configuration` 实跑 (IDE MCP 没有增删改能力)。仅 JetBrains 项目注册,与 `/open-in-idea` 无关。
|
|
21
|
-
- **`/open-in-idea` 改为问注册表要连接状态**:活着只刷新,死了才换注册 (OpenCode 对未变更的 MCP 配置不会重连)。
|
|
22
|
-
- **默认 MCP 端口收敛**:`DEFAULT_PORTS` 从 `[64342, 6420, 6421, 63342]` 改为 `[64342]`。
|
|
23
|
-
- **修复发布脚本**:`assertCleanGit()` 原先排在版本递增之后,导致正式发布必然中途失败并留下已被改动的版本号;现在移到递增之前。
|
|
24
|
-
|
|
25
|
-
## 0.0.5
|
|
26
|
-
|
|
27
|
-
- **冷启动等待 MCP 端点**:`/open-in-idea` 在 `open -a` 之后按 `mcpStartTimeoutMs`(默认 60s)轮询,直到 IDEA 自带的 MCP server 开始监听。冷启动时一次命令即可连上,不再像之前那样探测一次失败就报「未检测到 MCP 服务」。
|
|
28
|
-
- **工具就绪判定**:连接后轮询 `ctx.tool.list()`,确认 `idea_read_file` / `idea_apply_patch` 已注册。未就绪时状态为 `tools-loading` 并提示稍后重试,不再在原生工具表尚未就绪时谎称「已连接」。
|
|
29
|
-
- **重复执行刷新工具列表**:再次执行 `/open-in-idea` 会重建 MCP server 并依次 `ctx.mcp.reload()` + `ctx.tool.reload()`,让已经捕获过工具快照的会话立即看到最新工具,而不是停留在旧快照。
|
|
30
|
-
- **IDE 掉线自动注销**:每次请求前复检端点,IDEA 关闭或某次 `idea_*` 工具调用失败时,立即移除 `idea_*` 工具并恢复原生工具,避免后续请求继续调用已失效的 IDE 工具;只有再次执行 `/open-in-idea` 才能重新注册。
|
|
31
|
-
- **清理**:移除调试日志;删除过时的 `scripts/publish.sh`(旧包名 + bypass token 流程,已由 `scripts/release.mjs` 取代);`package.json` 的 `files` 移除早已删除的 `src/idea-tool-descriptions.js`。
|
|
32
|
-
|
|
33
|
-
## 0.0.4
|
|
34
|
-
|
|
35
|
-
- **`/open-in-idea` 刷新工具列表**:在 `ctx.mcp.reload()` 之后显式调用 `ctx.tool.reload()`。之前只有 MCP 层重建目录,已经捕获过工具快照的会话仍然看不到新的 `idea_*` 原生工具;现在下一次模型请求即可用,无需重开会话。
|
|
36
|
-
|
|
37
|
-
## 0.0.3
|
|
38
|
-
|
|
39
|
-
- **直接暴露 IDEA MCP 工具**:注册 MCP 服务器时设置 `codemode: false`,让 `idea_*` 工具直接出现在原生工具表(而非 Code Mode 的 `tools.idea.*`)。模型可以直接调用并看到完整参数 schema,不再需要先探索、也不会猜错参数名。
|
|
40
|
-
- **引导补充**:明确要求「直接调用工具,不要用 `execute` 包裹」。
|
|
41
|
-
|
|
42
|
-
## 0.0.2
|
|
43
|
-
|
|
44
|
-
- **路由引导重写**:system prompt 明确写出项目绝对路径,并要求路径内的检索 / 读文件 / 目录浏览 / 改文件 / 新建文件 / 校验都使用 `idea_*` 工具,逐条点名禁止对应的原生工具(`grep` / `glob` / `read` / `edit` / `write` / `ls` / `find`)。
|
|
45
|
-
- **不再修改任何工具描述**:移除对 IDEA MCP 工具描述的前缀改写(`src/idea-tool-descriptions.js` 已删除),插件不再调用 `ctx.tool.transform`;IDEA 工具与原生工具的描述均保持原样。
|
|
46
|
-
- **引导文案精简**:删除解释性内容和宽泛的「例外」条款,只保留要求与项目内 / 外边界。
|
|
47
|
-
- **移除无用文件**:删除 `skills/`(setup skill)与 `UPGRADING.md`。
|
|
48
|
-
- **安装文档更新**:README 的安装与配置示例改为 V2 的 `plugins` 对象写法。
|
|
49
|
-
|
|
50
|
-
## 0.0.1
|
|
51
|
-
|
|
52
|
-
首个版本(包名 `opencode-idea`,从 `opencode-jetbrains-mcp` 重命名而来)。
|
|
53
|
-
|
|
54
|
-
**接入 IDE MCP**
|
|
55
|
-
|
|
56
|
-
- **`/open-in-idea` 命令**:用 IntelliJ IDEA 打开当前项目(复用已运行实例),并即时加载 IDE MCP。命令可重入,重复触发共享同一次启动,不会重复 spawn。
|
|
57
|
-
- **IDE 启动器** (`src/ide-launcher.js`):`open -a "IntelliJ IDEA" <dir>` 复用已运行实例(Toolbox CLI 的 `open -na` 会新开实例,故不使用)。
|
|
58
|
-
- **启动保护 (launch guard)**:每目录记录 `lastSpawnAt` / `attempts`;冷却 `launchCooldownMs`(默认 120s)、上限 `launchMaxAttempts`(默认 5)。
|
|
59
|
-
- **MCP 未开启时 fast-fail**:`/open-in-idea` 先短探测,未检测到 MCP 就立即返回并提示在 IDEA 中开启 MCP 服务和 Brave Mode,不轮询、不等待超时;用户开启后重试命令即可。
|
|
60
|
-
- **Streamable HTTP 端点**:注册给 OpenCode 的 URL 使用 `/stream`(IntelliJ 2026.2+)。OpenCode V2 的远程 MCP 客户端只支持 Streamable HTTP,旧版 `/sse`(POST 返回 405)不可用;插件探测同样走 `/stream`。
|
|
61
|
-
- **结果通知**(`feedback: "message"`,默认):命令结束后发一条标注清楚的通知,用户看到干净气泡(`metadata.displayText`),模型只看到"这是通知,请只回复『收到』";`false` 静默。
|
|
62
|
-
|
|
63
|
-
**IDE 工具优先**
|
|
64
|
-
|
|
65
|
-
- **系统提示直接调用引导** (`src/ide-guidance.js`):IDE 可用时,通过 `ctx.session.hook("context", …)` 往每次请求的 system 追加「项目内操作优先使用 IDEA MCP」引导 + 能力映射表,要求模型直接调用 `idea_*` 工具。`injectGuidance: false` 可关闭。
|
|
66
|
-
- **IDEA 工具描述增强** (`src/idea-tool-descriptions.js`):通过 `ctx.tool.transform` 只更新 `idea_*` / `idea.*` 工具的 description,强调直接调用、按当前 schema 传参。原生 `edit` / `write` / `patch` / `shell` 的定义、描述和可用性保持不变。
|
|
67
|
-
- **失败恢复指引**:IDEA MCP 工具缺失、连接被拒或超时时,提示用户执行 `/open-in-idea`,不反复探测或长时间等待。
|
|
68
|
-
|
|
69
|
-
**IDE 环境注入**
|
|
70
|
-
|
|
71
|
-
- **环境变量只来自 IDE 集成终端**:用 `execute_terminal_command printenv` 读取 IDE 实际运行环境(版本化 Node、goenv Go、SDKMAN Java、Maven 等),通过 V2 `shell` 的 `create.before` 钩子注入每个 shell;只补 SDK 变量并前置 PATH,不覆盖原有 PATH。需要 IDE MCP 可达 + Brave Mode。
|
|
72
|
-
- **抽取值** (`src/ide-env.js` 的 `SDK_ENV_KEYS`):`JAVA_HOME` / `JRE_HOME` / `JDK_HOME` / `VIRTUAL_ENV` / `PYTHONPATH` / `PYTHONHOME` / `GOROOT` / `GOPATH` / `GOBIN` / `NODE_PATH` / `NVM_BIN` / `NVM_DIR` / `MAVEN_HOME` / `M2_HOME` / `GRADLE_HOME` / `SDKMAN_DIR` / `CONDA_PREFIX`。
|
|
73
|
-
|
|
74
|
-
**其他**
|
|
75
|
-
|
|
76
|
-
- **纯手动**:启动不探测、不连接 IDE MCP;IDE MCP 注册、环境重读、引导注入全部由 `/open-in-idea` 触发。
|
|
77
|
-
- **根级 `index.js` 转发入口**:使仓库可作为 V2 本地目录插件直接加载(`plugins: [{ "package": "<repo>" }]`)。
|
|
78
|
-
- **发布脚本** (`scripts/release.mjs`):`npm run release` 自动递增 patch、同步版本文件、跑测试并发布。
|
|
79
|
-
- **配置项**:`ports` / `injectEnv` / `injectGuidance` / `openInIde` / `launchCooldownMs` / `launchMaxAttempts` / `mcpProbeTimeoutMs` / `feedback`。
|
|
80
|
-
- 仅支持 **OpenCode V2**;非 JetBrains 项目完全不受影响。
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
// The `idea-run-config` skill registered by the plugin.
|
|
2
|
-
//
|
|
3
|
-
// Run configurations are plain XML files under `.run/`. The IDE MCP server can
|
|
4
|
-
// list and execute them (`get_run_configurations` / `execute_run_configuration`)
|
|
5
|
-
// but has no create/update/delete. Hard-coding a template per configuration type
|
|
6
|
-
// would be an open-ended maintenance hole (Application, JUnit, Gradle, npm,
|
|
7
|
-
// Shell Script, Spring Boot, Docker, Compound, ...), so the plugin ships a skill
|
|
8
|
-
// instead: it teaches the model where the files live, how to bootstrap a schema
|
|
9
|
-
// from a real example, and how to verify a write.
|
|
10
|
-
//
|
|
11
|
-
// The text lives in `ide-run-config-skill.md` next to this module, so it is a
|
|
12
|
-
// real skill document and the XML snippets it contains are not mistaken for
|
|
13
|
-
// markup inside a string literal.
|
|
14
|
-
//
|
|
15
|
-
// The skill is registered at setup and does NOT depend on `/open-in-idea`.
|
|
16
|
-
|
|
17
|
-
import { readFileSync } from 'node:fs';
|
|
18
|
-
import { fileURLToPath } from 'node:url';
|
|
19
|
-
|
|
20
|
-
/** Skill id used by the registry. */
|
|
21
|
-
export const RUN_CONFIG_SKILL_ID = 'idea-run-config';
|
|
22
|
-
|
|
23
|
-
/** Description shown to the model when picking a skill. */
|
|
24
|
-
export const RUN_CONFIG_SKILL_DESCRIPTION = [
|
|
25
|
-
'管理 JetBrains IDE 的 run configuration:在项目级 `.run/*.run.xml` 里创建、修改、删除运行配置。',
|
|
26
|
-
'当用户要新建运行配置、修改或删除已有配置、把脚本或执行流程包成可运行的配置,',
|
|
27
|
-
'或让 AI 执行用户手配的配置时使用。只操作项目级 `.run/` 文件,不碰 `.idea/workspace.xml`。',
|
|
28
|
-
].join('');
|
|
29
|
-
|
|
30
|
-
const SKILL_FILE = new URL('./ide-run-config-skill.md', import.meta.url);
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Read the skill document shipped next to this module.
|
|
34
|
-
*
|
|
35
|
-
* @returns {string}
|
|
36
|
-
*/
|
|
37
|
-
export function readRunConfigSkillContent() {
|
|
38
|
-
return readFileSync(SKILL_FILE, 'utf8').trimEnd();
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Build the skill definition registered through `ctx.skill.transform`.
|
|
43
|
-
*
|
|
44
|
-
* The schema (`Skill.Info` in `@opencode/schema/skill`) requires the field to be
|
|
45
|
-
* named `path` and to hold an absolute path. Point it at the shipped `.md` — the
|
|
46
|
-
* real skill document — rather than inventing a virtual filename.
|
|
47
|
-
*
|
|
48
|
-
* @returns {{ id: string, name: string, description: string, path: string, content: string }}
|
|
49
|
-
*/
|
|
50
|
-
export function runConfigSkill() {
|
|
51
|
-
return {
|
|
52
|
-
id: RUN_CONFIG_SKILL_ID,
|
|
53
|
-
name: 'idea-run-config',
|
|
54
|
-
description: RUN_CONFIG_SKILL_DESCRIPTION,
|
|
55
|
-
// Field names are schema-critical: `Skill.Info` requires `path`
|
|
56
|
-
// (AbsolutePath). A wrong key fails validation and takes the whole plugin
|
|
57
|
-
// down, not just the skill.
|
|
58
|
-
path: fileURLToPath(SKILL_FILE),
|
|
59
|
-
content: readRunConfigSkillContent(),
|
|
60
|
-
};
|
|
61
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
# JetBrains Run Configuration 管理
|
|
2
|
-
|
|
3
|
-
本 skill 用于 **管理 JetBrains IDEA 的 run configuration (运行配置)**:创建、修改、删除、查看,并触发运行与调试。配置以 XML
|
|
4
|
-
文件形式存放在项目里,AI 通过读写这些文件完成管理,再用 IDE MCP 工具做校验和执行。
|
|
5
|
-
|
|
6
|
-
## 存放位置:只有 `.run/`
|
|
7
|
-
|
|
8
|
-
所有配置都写 **项目根 `.run/<name>.run.xml`**。这是 AI 唯一能可靠写入、且 IDEA 会 **即时加载**的位置。文件要不要提交到
|
|
9
|
-
git,由用户自己决定,AI 不代劳、也不必追问。
|
|
10
|
-
|
|
11
|
-
### 为什么不用 `.idea/`
|
|
12
|
-
|
|
13
|
-
- `.idea/workspace.xml` → `RunManager`:IDEA 的原生私有位置,但 **IDEA 是这份文件的主人** ——
|
|
14
|
-
工作区状态缓存在内存里、保存时整体重写,还有"工作区恢复"机制会覆盖或还原外部改动。AI 写进去不可靠。
|
|
15
|
-
- `.idea/runConfigurations/*.xml`:这是 **2020.1 之前的共享位置**(不是私有位置),而且 `.idea` 常被 gitignore,反而更难共享。
|
|
16
|
-
|
|
17
|
-
用户已有的、存在 `.idea` 里的配置,AI 仍可 **读取和运行**(`idea_get_run_configurations` 能看到),只是不负责创建、修改或删除。
|
|
18
|
-
|
|
19
|
-
## 位置速查
|
|
20
|
-
|
|
21
|
-
这是 **事实参考**(IDEA 实际有三个位置),供识别用户已有配置用:
|
|
22
|
-
|
|
23
|
-
| 位置 | 谁在用 | AI 能否写 |
|
|
24
|
-
|--------------------------------------|--------------------------------------------|-----------------------------------------|
|
|
25
|
-
| `.run/<name>.run.xml`(项目根) | 2020.1+ 的项目级位置 | 能写,IDEA 即时加载 |
|
|
26
|
-
| `.idea/runConfigurations/*.xml` | 2020.1 **之前**的共享位置;现在仍兼容读取 | 一般不动,老项目可沿用 |
|
|
27
|
-
| `.idea/workspace.xml` → `RunManager` | **默认**位置(不勾 "Store as project file") | **不写**(会被 IDEA 覆盖);但能读取和运行 |
|
|
28
|
-
|
|
29
|
-
新建配置时 `.run/` 的结构:
|
|
30
|
-
|
|
31
|
-
```xml
|
|
32
|
-
|
|
33
|
-
<component name="ProjectRunConfigurationManager">
|
|
34
|
-
<configuration default="false" name="<名字>" type="<类型 ID>">
|
|
35
|
-
<option name="..." value="..."/>
|
|
36
|
-
<envs/>
|
|
37
|
-
<method v="2"/>
|
|
38
|
-
</configuration>
|
|
39
|
-
</component>
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
## 铁律
|
|
43
|
-
|
|
44
|
-
1. 不要凭空写 option 名。先找真实样本:
|
|
45
|
-
- 用 `idea_search_file` 传 `q="**/*.run.xml"` 看项目里已有的配置。
|
|
46
|
-
- 没有目标类型 → 请用户在 IDEA 里新建一个配置并勾选 "Store as project file",然后读生成的 XML 当模板。
|
|
47
|
-
- 仍然没有 → 直接说明需要该类型的真实样本,不要猜 option 名。
|
|
48
|
-
2. 写完必须校验:
|
|
49
|
-
- 在 `idea_get_run_configurations` 里能看到才算写对;看不到说明 `type` 写错了,改了重试。
|
|
50
|
-
- 能看到不等于能跑:`idea_execute_run_configuration` 传 `configurationName` 实跑一次确认。
|
|
51
|
-
- IDE MCP 不可用 → 提示用户执行 `/open-in-idea` 后再校验,不要跳过校验当成成功。
|
|
52
|
-
3. 改配置用定向修改:只用 `idea_apply_patch` 改需要的那几行 option,不要整文件重写。
|
|
53
|
-
4. 删除配置 = 删除对应的 `.run/<name>.run.xml`;不要试图从 `.idea/workspace.xml` 里删。
|
|
54
|
-
|
|
55
|
-
## 常见字段
|
|
56
|
-
|
|
57
|
-
- `$PROJECT_DIR$`:项目根;`$MODULE_WORKING_DIR$`:模块目录。
|
|
58
|
-
- 环境变量:
|
|
59
|
-
|
|
60
|
-
```xml
|
|
61
|
-
|
|
62
|
-
<envs>
|
|
63
|
-
<env name="KEY" value="VALUE"/>
|
|
64
|
-
</envs>
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
- Before launch 流程:`<method v="2">` 里加 `Make` 选项会先构建。
|
|
68
|
-
|
|
69
|
-
## 起步参考 (不是契约;动手前一律以真实样本为准)
|
|
70
|
-
|
|
71
|
-
| 用途 | type | 关键 option |
|
|
72
|
-
|------------|----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
73
|
-
| Shell 脚本 | `ShConfigurationType` | 内联脚本:`SCRIPT_TEXT` + `EXECUTE_SCRIPT_FILE=false`;脚本文件:`SCRIPT_PATH` + `EXECUTE_SCRIPT_FILE=true`;`INTERPRETER_PATH`;`SCRIPT_WORKING_DIRECTORY` |
|
|
74
|
-
| Java 应用 | `Application` | `MAIN_CLASS_NAME`;`WORKING_DIRECTORY`;`PROGRAM_PARAMETERS`;`VM_PARAMETERS` |
|
|
75
|
-
| JUnit | `JUnit` | `TEST_OBJECT`;`MAIN_CLASS_NAME`;`WORKING_DIRECTORY` |
|
|
76
|
-
| Gradle | `GradleRunConfiguration` | `ExternalProjectPath`;`TaskList` |
|
|
77
|
-
| npm | `js.build_tools.npm` | `package-json`;`command`;`scripts` |
|
|
78
|
-
| 串行流程 | `CompoundRunConfiguration` | `method` 里多个 `RunConfigurationTask`(带 `run_configuration_name` / `run_configuration_type`) |
|
|
79
|
-
|
|
80
|
-
## 命名
|
|
81
|
-
|
|
82
|
-
- 把 `name` 转成文件名:去掉 `/ \ : * ? " < > |` 等非法字符,写成 `.run/<净化后的名字>.run.xml`。
|
|
83
|
-
- 目标文件已存在时,先确认是要覆盖。
|
|
84
|
-
|
|
85
|
-
## 流程
|
|
86
|
-
|
|
87
|
-
1. `idea_search_file` 找 `**/*.run.xml` 样本。
|
|
88
|
-
2. 有同类样本 → 照它的结构改写;没有 → 请用户建一个。
|
|
89
|
-
3. `idea_create_new_file` 写 `.run/<name>.run.xml`。
|
|
90
|
-
4. `idea_get_run_configurations` 校验它出现。
|
|
91
|
-
5. `idea_execute_run_configuration` 实跑确认。
|
|
92
|
-
6. 修改用 `idea_apply_patch` 定向改 option;删除后确认它从列表里消失。
|