opencode-idea 0.0.18 → 0.0.20

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.
@@ -14,6 +14,25 @@
14
14
  // 2. Only IDE-only capability is routed here; reading, writing and searching
15
15
  // stay on the native tools.
16
16
  //
17
+ // 校验 (lint / build) is one place that also carries a *when*, because the tools
18
+ // are not there to be offered — they are there to be used unprompted, and the
19
+ // model simply skips them (observed: it went and compiled by hand in `shell`
20
+ // instead, then reported success). The rule is a decision, not a mandate: batch
21
+ // edits → one `build_project` at the end, single-file fiddling → `lint_files`
22
+ // once when the round is over, never one check per keystroke and never zero
23
+ // checks. Spelling out both failure modes is the point — "always check" is as
24
+ // wrong as "never check", and only the model knows which round it is in.
25
+ //
26
+ // 符号 carries the same kind of *when*, for the same reason. In the observed
27
+ // session (a JetBrains plugin job on the IntelliJ Platform API) the model ran 398
28
+ // shells and mentioned `javap` 175 times in its own reasoning while mentioning
29
+ // `search_symbol` three times, two of which were after the user insisted. So the
30
+ // text now names the *situation* first ("you want to know how this class or
31
+ // method is implemented"), then bans the route it actually takes (`javap`,
32
+ // `unzip` on a hand-assembled jar path) with the reason that wins that argument:
33
+ // the IDE index holds the copy the project compiles against, and it hands back
34
+ // decompiled source instead of a bytecode listing.
35
+ //
17
36
  // ## 一个会话,多个项目,多套前缀
18
37
  //
19
38
  // 每个接入的项目都用**自己的** MCP server 注册(root = `ide`,子项目 = 目录名),
@@ -27,12 +46,21 @@
27
46
  // The `{{#if can…}}` blocks gate the companion plugin's tools (`mcp/enhance.js`),
28
47
  // which only exist when the user installed it. Nothing in the document says so:
29
48
  // which plugin a tool came from is this plugin's plumbing, not the model's
30
- // business — it either sees the tool or it does not.
49
+ // business — it either sees the tool or it does not. The deployment section is
50
+ // gated on ONE of those flags (`canListDeploymentServers`) rather than on a flag of
51
+ // its own: the four tools are registered together, so one of them answers for the
52
+ // section — heading included, which is why the gate wraps the `###` line too.
31
53
 
32
54
  import { readFileSync } from 'node:fs';
33
55
 
34
56
  import { IDE_LABELS } from './ide-projects.js';
35
- import { runLifecycleCapabilities } from './mcp/enhance.js';
57
+ import {
58
+ DEPLOYMENT_SUFFIXES,
59
+ RUN_LIFECYCLE_SUFFIXES,
60
+ deploymentCapabilities,
61
+ runLifecycleCapabilities,
62
+ servesTool,
63
+ } from './mcp/enhance.js';
36
64
  import { renderTemplate } from './template.js';
37
65
 
38
66
  /** The prompt document, next to this module so it ships with it. */
@@ -41,6 +69,17 @@ const TEMPLATE_PATH = new URL('ide-guidance.template.md', import.meta.url);
41
69
  /** Read once: the file is fixed for the life of the process, and this runs per request. */
42
70
  const TEMPLATE = readFileSync(TEMPLATE_PATH, 'utf8');
43
71
 
72
+ /**
73
+ * 配套插件那两组工具的后缀全集 —— 判据只有一条:**这张表里的工具按项目分别存在**。
74
+ *
75
+ * 表外(内置 MCP Server 自带的那批)是每个项目都有的,列命令名时不需要看快照;表内的必须
76
+ * 逐项目核对(见 {@link commandVariables})。两组都收进来而不是只收部署那组:它们的
77
+ * 存在性本来就按项目、按 IDE 不同 —— 启停工具取决于装没装配套插件,部署工具还多一道
78
+ * "是不是 Ultimate"。把 run-lifecycle 留在表外,等于默认"一个项目装了、别的项目也装了",
79
+ * 而那只是个默认,不是事实。
80
+ */
81
+ const ENHANCE_SUFFIXES = new Set([...RUN_LIFECYCLE_SUFFIXES, ...DEPLOYMENT_SUFFIXES]);
82
+
44
83
  /**
45
84
  * 「本会话有哪些项目、各自用哪个 MCP 工具前缀」的清单。
46
85
  *
@@ -71,18 +110,27 @@ function routeTableFor(projects) {
71
110
  * 每条功能在各项目下的命令名,预渲染成一行。
72
111
  *
73
112
  * 变量名**直接扫模板得到**:模板里写 `{{cmd_search_symbol}}`,这里就填
74
- * `` `.` `ide_search_symbol` | `modelserver` `modelserver_search_symbol` ``。
113
+ * `` `根项目 ide_search_symbol` | `modelserver modelserver_search_symbol` ``。
75
114
  * 所以模板增删一条工具不需要动这里的代码,也不会出现"模板里写了、代码忘了给值"的空行。
76
115
  *
77
- * @param {ReadonlyArray<{ label: string, server: string }>} projects
116
+ * **配套插件那条线要逐项目过滤**(见 {@link ENHANCE_SUFFIXES}):内置工具每个项目都有,列全即可;
117
+ * 而 `idea-mcp-enhance` 的工具是**按项目分别存在**的(装了插件的那个 IDE 有,另一个可能没有;
118
+ * 部署那组还要求 Ultimate)。它常常同进同出,但"通常"不是"总是" —— 一旦不一致,列全就会在
119
+ * 缺工具的那个项目下点名一个模型调不到的命令,而那正是这整个模块要防的失败。
120
+ * 过滤不会产生空行:每条 bullet 的 `{{#if}}` 用的是**并集**判断(任一项目有即渲染),
121
+ * 并集为真 ⇒ 至少一个项目通过过滤。
122
+ *
123
+ * @param {ReadonlyArray<{ label: string, server: string, isRoot: boolean }>} projects
124
+ * @param {Record<string, unknown> | undefined} tools 本次请求的工具快照
78
125
  */
79
- function commandVariables(projects) {
126
+ function commandVariables(projects, tools) {
80
127
  /** @type {Record<string, string>} */
81
128
  const variables = {};
82
129
  for (const match of TEMPLATE.matchAll(/\{\{\s*(cmd_[A-Za-z0-9_]+)\s*\}\}/g)) {
83
130
  const name = match[1];
84
131
  const suffix = name.slice('cmd_'.length);
85
132
  variables[name] = projects
133
+ .filter((project) => !ENHANCE_SUFFIXES.has(suffix) || servesTool(tools, `${project.server}_`, suffix))
86
134
  // 每条都写清"哪个项目 → 哪个命令":根项目直接用它的角色名,子项目用目录名。
87
135
  // 不写 `.`/目录名这种需要回看清单才能解读的代号 —— 模型读每条功能时不必回头查表。
88
136
  .map((project) => `${project.isRoot ? '根项目' : project.label} \`${project.server}_${suffix}\``)
@@ -110,6 +158,32 @@ function mergeRunLifecycle(projects, tools) {
110
158
  };
111
159
  }
112
160
 
161
+ /**
162
+ * 部署工具同理(见 `mergeRunLifecycle`):逐项目判断,任一项目提供即算存在。
163
+ *
164
+ * **整节的锚点就是 `canListDeploymentServers`**(模板里 `{{#if canListDeploymentServers}}` 直接包住标题):
165
+ * 配套插件只要装了,IDE 的 MCP 工具表里就多出这一组(它们由同一道前提注册,同进同出),
166
+ * 所以拿其中一个当判据就够了 —— 不需要再造一个"四个里任一个"的聚合标志。
167
+ *
168
+ * 不锚在那些"必须有工具才算"的地方也不成问题:一个都没有时 `canListDeploymentServers` 为 false,
169
+ * 标题与正文一起消失;而这一节和前一组不同 —— run-lifecycle 那节是内置工具
170
+ * (`get_run_configurations` / `execute_run_configuration`)撑起来的**自己的**标题,缺配套插件时标题仍在。
171
+ * 顺带一提,这一组还额外要求 `com.jetbrains.plugins.webDeployment`(**仅 Ultimate 有**),所以
172
+ * "整节不出现"是常态而非边角。
173
+ *
174
+ * @param {ReadonlyArray<{ server: string }>} projects
175
+ * @param {Record<string, unknown> | undefined} tools
176
+ */
177
+ function mergeDeployment(projects, tools) {
178
+ const all = projects.map((project) => deploymentCapabilities(tools, `${project.server}_`));
179
+ return {
180
+ canListDeploymentServers: all.some((c) => c.canListDeploymentServers),
181
+ canUploadToDeployment: all.some((c) => c.canUploadToDeployment),
182
+ canDownloadFromDeployment: all.some((c) => c.canDownloadFromDeployment),
183
+ canGetDeploymentTaskStatus: all.some((c) => c.canGetDeploymentTaskStatus),
184
+ };
185
+ }
186
+
113
187
  /**
114
188
  * 把本会话接入的全部项目渲染成**一份**引导文本。
115
189
  *
@@ -118,7 +192,7 @@ function mergeRunLifecycle(projects, tools) {
118
192
  *
119
193
  * @param {ReadonlyArray<{ path: string, server: string, app: string, label: string, isRoot: boolean }>} projects
120
194
  * @param {{ tools?: Record<string, unknown> }} [options] `tools` 是本次请求的工具快照
121
- * (`event.tools`),用来判断 run-lifecycle 工具在不在。
195
+ * (`event.tools`),用来判断配套插件(run-lifecycle / deployment)的工具在不在。
122
196
  * @returns {string}
123
197
  */
124
198
  export function buildIdeGuidanceText(projects, options = {}) {
@@ -126,13 +200,14 @@ export function buildIdeGuidanceText(projects, options = {}) {
126
200
  projectCount: projects.length,
127
201
  routeTable: routeTableFor(projects),
128
202
  ...mergeRunLifecycle(projects, options.tools),
129
- ...commandVariables(projects),
203
+ ...mergeDeployment(projects, options.tools),
204
+ ...commandVariables(projects, options.tools),
130
205
  });
131
206
  }
132
207
 
133
208
  export const MCP_RECOVERY_GUIDANCE = [
134
209
  '### IDE MCP failure recovery',
135
- 'If an IDE MCP tool (the `<prefix>_*` ones) is missing, MCP is disconnected, the connection is refused, or a tool times out: stop retrying immediately; do not run extra probes or wait in tool calls. Tell the user that IDE MCP is unavailable and ask them to run /open-in-idea to reconnect.',
210
+ 'If an IDE MCP tool (the `<prefix>_*` ones) is missing, MCP is disconnected, the connection is refused, or a tool times out: stop retrying immediately; do not run extra probes or wait in tool calls. Tell the user that IDE MCP is unavailable and ask them to run /open-in-ide to reconnect.',
136
211
  ].join('\n');
137
212
 
138
213
  /**
@@ -142,7 +217,7 @@ export const MCP_RECOVERY_GUIDANCE = [
142
217
  */
143
218
  export const MCP_RECOVERY_GUIDANCE_MARKED = [
144
219
  '### IDE MCP failure recovery',
145
- 'This project is IDE-connected and the plugin reconnects the IDE MCP automatically. When an IDE MCP call fails with a connection error (for example `MCP server "ide" is not available`), do not run extra probes or wait in tool calls: the reconnect is already under way, so retry the operation once. Only if it still fails, tell the user that IDE MCP is unavailable and to run /open-in-idea.',
220
+ 'This project is IDE-connected and the plugin reconnects the IDE MCP automatically. When an IDE MCP call fails with a connection error (for example `MCP server "ide" is not available`), do not run extra probes or wait in tool calls: the reconnect is already under way, so retry the operation once. Only if it still fails, tell the user that IDE MCP is unavailable and to run /open-in-ide.',
146
221
  ].join('\n');
147
222
 
148
223
  /**
@@ -155,7 +230,7 @@ export const MCP_RECOVERY_GUIDANCE_MARKED = [
155
230
  */
156
231
  export const MCP_RECOVERY_GUIDANCE_RECONNECTING = [
157
232
  '### IDE MCP failure recovery',
158
- 'No IDE MCP tool (the `<prefix>_*` ones) is in this request: the IDE connection dropped and the plugin is reconnecting in the background. Do NOT call any of them now — it will fail with `No tool named "..." is currently available` — and do not go looking for them through Code Mode either: they are only ever listed as native tools, so a search finds nothing. Do not fall back to shell/read/write for in-project work. Continue with what you can, or tell the user the IDE is reconnecting; if the tools are still missing on a later message, ask the user to run /open-in-idea.',
233
+ 'No IDE MCP tool (the `<prefix>_*` ones) is in this request: the IDE connection dropped and the plugin is reconnecting in the background. Do NOT call any of them now — it will fail with `No tool named "..." is currently available` — and do not go looking for them through Code Mode either: they are only ever listed as native tools, so a search finds nothing. Do not fall back to shell/read/write for in-project work. Continue with what you can, or tell the user the IDE is reconnecting; if the tools are still missing on a later message, ask the user to run /open-in-ide.',
159
234
  ].join('\n');
160
235
 
161
236
  /**
@@ -23,15 +23,18 @@
23
23
  - 下面这些 IDE 工具也是原生工具:直接调用(参数按各工具 schema 传),不要包进 `execute`;它们不在 Code Mode 的工具目录里,`search` 搜不到。
24
24
  - **只有 IDE 才有的能力才用它们**:符号级检索、项目模型、检查与构建、重构与格式化、run configuration。
25
25
  - **读写与搜索用原生工具**(`read` / `write` / `edit` / `grep` / `glob`):不要为读一个文件、写一个文件、搜一段文本去绕 IDE 工具。
26
- - **唯一例外**:压缩包内的条目(路径带 `.jar!/`,原生 `read` 打不开)—— 见下面「读压缩包内的东西」。
27
- - 某条功能下面没有你正要操作的那个项目的命令,就是那个项目没接上:提示用户执行 `/open-in-idea`,不要重试,也不要去别处找。
26
+ - **改完代码自己先查一遍**(`lint_files` / `build_project`,怎么挑见下面「校验」):什么时候查、查几次由你判断,但**别把校验整段省掉** —— 自己查一次,远比让用户替你发现或留到构建 / 测试炸了才暴露要便宜。
27
+ - **要看某个类 / 方法 / 字段在哪定义、内部怎么实现的**(见下面「符号」):`<P>_search_symbol`(`include_external: true`)找到位置 → `<P>_read_file` 原样读它给的路径。
28
+ - 某条功能下面没有你正要操作的那个项目的命令,就是那个项目没接上:提示用户执行 `/open-in-ide`,不要重试,也不要去别处找。
28
29
 
29
30
  ## 工具(每条第一行是各项目的命令,接着是它的用法)
30
31
 
31
32
  ### 符号(只有 IDE 的语义索引能给)
32
33
 
34
+ **要读任何类 / 方法 / 字段的源码,就两步**:`<P>_search_symbol`(`q` 传名字,**带 `include_external: true`**)找到位置 → `<P>_read_file` 原样读它给的路径。**依赖库 / 三方包 / 平台 jar 里的实现,正是它最该用的场合**,也是 `javap` / `unzip` / 解包 jar / 按 jar 路径 `grep` 唯一的正解:**IDE 索引里那份就是项目编译时真正用的依赖**(自己拼的 jar 路径常常是另一份副本,版本对不上),读出来还是反编译源码 —— 字节码清单要来回十几次才能拼出逻辑,这边一次到位。
35
+
33
36
  - {{cmd_search_symbol}}
34
- **按名字找符号的声明位置**(`q` 传查询文本,**固定带上 `include_external: true`**):本插件最不可替代的能力 —— 它搜的是**符号**(类 / 接口 / 方法 / 函数 / 字段 / 类型 / 常量这类**有名字的定义**)的**声明位置**,不是"哪段文本提到过它"(那是 `grep` 的事);带上 `include_external` 才会连**依赖库和标准库**一起搜,于是你能直接定位第三方库里的实现 —— 这是原生 `grep` / `glob` 够不着的地方(IDE 自己的 `search_text` / `search_file` 也只搜项目内)。
37
+ **`q` 传查询文本,固定带上 `include_external: true`。** 本插件最不可替代的能力 —— 它搜的是**符号**(类 / 接口 / 方法 / 函数 / 字段 / 类型 / 常量这类**有名字的定义**)的**声明位置**,不是"哪段文本提到过它"(那是 `grep` 的事);带上 `include_external` 才会连**依赖库和标准库**一起搜,于是你能直接定位第三方库里的实现 —— 这是原生 `grep` / `glob` 够不着的地方(IDE 自己的 `search_text` / `search_file` 也只搜项目内)。
35
38
  - **典型用法 ①:代码里引用了某个库的 API(最常见)** —— 读代码时看到 `from fastapi import FastAPI`、`import org.apache.poi...FormulaParser`、`gorm.DeletedAt`、`from 'vue'` 这类引用时,**包名就在代码里**,直接**用「包名+符号名」当 `q` 搜**,一次定位到它的实现:
36
39
  - **Java**:全限定名 —— `com.nimbusds.jose.util.Base64`(实测 1 条命中 `~/.m2/.../poi-5.3.0-sources.jar!/...`)
37
40
  - **Go**:包名.符号 —— `gorm.DeletedAt`(实测 2 条,全在 `go/pkg/mod/gorm.io/gorm@...`)
@@ -82,6 +85,19 @@
82
85
  - {{cmd_build_project}}
83
86
  **构建校验**:`filesToRebuild` 可只构建指定文件;有问题返回在 `problems`,没返回即没问题。
84
87
 
88
+ **改完代码,自己先查一遍再收尾。** 这不是流程要求,是你自己的判断 —— 查一次很便宜,而问题一旦漏到用户
89
+ 看见、或留到构建 / 测试炸了才暴露,回头纠正的代价就高一个量级了。
90
+
91
+ - **这次要改一大片**(多文件、跨模块、"顺手把这几处一起对齐")→ 全部改完再 `build_project` 一次。中途不查:
92
+ 半成品状态下的检查结果只会误导你。
93
+ - **这次只改一处,或在同一个文件上反复微调** → 告一段落时,把这轮改过的文件一次 `lint_files` 掉;要看单个
94
+ 文件的细节再上 `get_file_problems`。
95
+ - **改了签名 / 接口 / import / 依赖**这类"别处会跟着坏"的地方 → 走 `build_project`;只动了文件内部的实现 →
96
+ lint 就够。
97
+ - 查出来的问题**改掉,或说清为什么不改**,别把没处理的问题原样交给用户。
98
+ - 校验走上面这些工具,**不要改用 `shell` 里临时自己编一遍代替**。
99
+ - 也别先猜"索引还没同步 / 那个环境是另一个进程"就绕开 —— 先调一次,它真看不到会明说。
100
+
85
101
  ### 读压缩包内的东西
86
102
 
87
103
  - {{cmd_read_file}}
@@ -95,7 +111,11 @@
95
111
  **跑已有配置**:Shell Script 要 `EXECUTE_IN_TERMINAL: false`;覆盖参数(`programArguments` / `workingDirectory` / `envs`)需配置支持 `supportsDynamicLaunchOverrides`。
96
112
  {{#if canCreateRunConfiguration}}
97
113
  - {{cmd_create_shell_run_configuration}}
98
- **新建项目级 Shell 配置**(`configurationName` + `scriptPath` / `scriptText` / `cloneFrom` 三选一;写 `.run/<name>.run.xml`):**重操作** —— 只给固定要跑、反复要用的流程建,**且建之前必须先取得用户同意**;只支持 Shell 类型,**其他类型请用户在 IDE 里建**。
114
+ **新建项目级 Shell 配置**(`configurationName` + `scriptPath` / `scriptText` / `cloneFrom` 三选一;写 `.run/<name>.run.xml`):只支持 Shell 类型。它是**项目资产**,不是"跑命令"的手段 —— 只是要跑一条命令就用原生 `shell`。判据只有一条:**这件事以后会不会以同样的形态再发生?**
115
+ - **这里只是"建"的一个入口,不是全部**:非 Shell 类型,以及**删 / 改**,都走 `idea-run-config-manager` skill —— 让用户调它(它有逐类型模板;没有模板的类型,它会让用户在 IDE 里配)。
116
+ - **会 → 该建,而且你要主动提**。典型信号:用户让你做的是**重复性动作**,还会对别的环境 / 别的分支 / 下一次改表再做一遍 —— 例如改完表结构要**同步某个环境的 DDL**、跑数据库迁移或回滚、生成代码、打包、起 dev server、往某个环境部署、按固定步骤清洗数据。这时**别默默每次手搓命令**(下次还得从头猜一遍),也**别自己动手建**:**先问用户**"这个流程以后要反复跑,要不要固化成一个 Shell run configuration?",并把方案摊开:配置名、实际执行的命令(`scriptText`,或它指向的 `scriptPath`)、工作目录、为什么值得留。**同意才建**;用户说这次先这样,就照办,别再追着问。
117
+ - **不会 → 用原生 `shell` 直接跑,别建**。这就是两者的分界线:原生 `shell` 是**就地执行、用完不留痕**(命令只存在于会话里);`create_shell_run_configuration` 是**把命令落盘成 `.run/<name>.run.xml`、往后反复用**(它产出的是项目文件,IDE 的 Run 窗口里也会多一项)。所以一次性的、探索性的、调试性的、参数每次都不一样的操作(查条数据、看个日志、试一条命令、临时换个路径跑一下)一律走 `shell` 跑完算 —— 这类东西建配置只是给项目留垃圾。确需项目环境的解释器时,在 `shell` 命令里显式指定、或在已有配置上单次覆盖参数,也别为这一次性的事新建配置。
118
+ - **建之前先查,防泛滥**:先 `get_run_configurations` 看有没有已在做同一件事的配置 —— 有就**复用**(必要时让用户改它),别新建一个几乎一样的;同一件事**不要按参数 / 按文件 / 按环境拆成一堆配置**,变化的入参用 `programArguments` / `envs` 单次覆盖。一个项目通常就那么**几个**(构建、装依赖、起服务、迁移这类固定流程);配置数量明显长起来就是滥用信号 —— 动手前先问自己:这是项目固定流程,还是我只是想省一条 shell 命令?
99
119
  {{/if}}
100
120
  {{#if canListRunningConfigurations}}
101
121
  - {{cmd_list_running_configurations}}
@@ -109,10 +129,37 @@
109
129
  - {{cmd_rerun_run_configuration}}
110
130
  **改完代码重启无热重载的进程**:在跑就重启、没在跑就启动;读 `started` 判断新进程是否真的起来了。不要用 `pkill` / `kill` / `shell` 代替。
111
131
  {{/if}}
132
+ {{#if canListDeploymentServers}}
133
+
134
+ ### 服务器文件同步
135
+
136
+ 往服务器传文件 / 从服务器取文件,走这一组 —— **别用 `ssh` / `rsync` / `scp` 自己拼一份**:IDE 的连接、路径映射、排除规则都在它们后面,自己拼的不是 IDE 在管的那一份。
137
+
138
+ - {{cmd_list_deployment_servers}}
139
+ **本项目能用的部署服务器**:名称(后面几条用它当 `serverName`)、项目级还是应用级、协议、host / port、在本项目里的路径映射,以及**项目级**的部署选项与危险组合告警。**只读 IDE 配置、不联网**;动手前先看 `warnings[]`。
140
+ {{#if canUploadToDeployment}}
141
+ - {{cmd_upload_to_deployment}}
142
+ **把映射到的本地目录传到服务器**(等同 IDE 的 Upload to …)。`mode` 默认 `mirror` —— **会删掉服务器上多出来的文件**,使两边一致;要留住它们就传 `upload`。**破坏性操作**。
143
+ {{/if}}
144
+ {{#if canDownloadFromDeployment}}
145
+ - {{cmd_download_from_deployment}}
146
+ **把服务器上映射目录的内容拉回本地**(等同 IDE 的 Download from …),**以服务器为准**:两边都有的文件会被远端覆盖(内容相同则不写)。**没有"保留我的版本"这个选项**。
147
+ {{/if}}
148
+ {{#if canGetDeploymentTaskStatus}}
149
+ - {{cmd_get_deployment_task_status}}
150
+ **查一次传输的状态**:`handle` 用上传 / 下载返回的那个 —— **它自己不启动任何传输**。
151
+ {{/if}}
152
+
153
+ 上传 / 下载都在 IDE 后台跑,单次调用只等到 `waitMs`(默认 10 秒)为止;返回 `in_progress` 是**正常**的,不是失败 —— 拿 `handle` 去查进度,**别用重复调上传 / 下载代替**(那样拿不到进度)。
154
+ {{/if}}
112
155
 
113
156
  ## 命令执行(所有项目通用)
114
157
 
115
158
  - 需要项目环境的命令(依赖 IDE 里配好的 JDK / Node / Python 版本等):用该项目前缀的 run configuration 工具,不要用原生 `shell`。
116
- - run configuration 是**重资产**:一个项目通常只建几个(固定要跑的构建 / 装依赖 / dev server 之类),**不是随手一条命令就建一个**。要新建时先确认它**反复要用、且属于项目固定流程**,再**先告诉用户要建什么、为什么,取得同意才动手**,别自己决定。
117
- - **配置一律不由你写**:不要手写 `.run/*.run.xml`,也不要手搓启动命令。
159
+ - 写 `.run/*.run.xml` **只走 `idea-run-config-manager` skill**:它有逐类型模板(`assets/types/`),写完还要复查。**平时不要自己手写配置,也不要用一条裸命令代替配置。**
160
+ {{#if canCreateRunConfiguration}}
161
+ - 要固化的固定流程(判据见上:会不会以同样形态重复发生):Shell 类型用上面那个「新建项目级 Shell 配置」建;非 Shell 类型与删 / 改,交给用户调 `idea-run-config-manager` skill。
162
+ {{else}}
163
+ - 要固化的固定流程:把配置名、实际执行的命令、工作目录、为什么值得留讲清楚,**请用户在 IDE 里自己建** —— 当前没有能建它的工具(`idea-run-config-manager` skill 的门就是这个工具,所以它这次也不会出现),不要手写文件凑。
164
+ {{/if}}
118
165
  - 不需要项目环境的命令:用原生 `shell`。
@@ -246,7 +246,7 @@ export function parseSubprojectArgument(text) {
246
246
  /**
247
247
  * 从参数文本里切出 IDE 名。
248
248
  *
249
- * `/open-in-idea pycharm` 是"AI 不配合时的直通逃生口":正常流程由 AI 向用户提问后
249
+ * `/open-in-ide pycharm` 是"AI 不配合时的直通逃生口":正常流程由 AI 向用户提问后
250
250
  * 调工具写回,带参只是保险。空串表示"没给",由调用方决定是提问还是用默认值。
251
251
  *
252
252
  * @param {unknown} text
@@ -8,17 +8,27 @@
8
8
  // * native -> `idea.js` (always present; the IDE owns the catalog)
9
9
  // * plugin -> this file (present only if the user installed the plugin)
10
10
  //
11
- // The companion plugin (https://github.com/syllr/idea-mcp-enhance) fills the gaps
12
- // the bundled server leaves in run configuration handling: the bundled server can
13
- // only START an existing configuration — it cannot create one, stop one, restart
14
- // one, or say what is currently running.
11
+ // The companion plugin (https://github.com/syllr/idea-mcp-enhance) fills gaps the
12
+ // bundled server leaves in two places:
13
+ //
14
+ // * run configuration — the bundled server can only START an existing
15
+ // configuration; it cannot create one, stop one, restart one, or say what is
16
+ // currently running;
17
+ // * deployment — the bundled server has nothing at all for the IDE's
18
+ // Deployment settings or for moving files to/from a server.
19
+ //
20
+ // The two are kept as separate groups everywhere (here, in the capability
21
+ // functions, and in the guidance), because they are NOT the same precondition:
22
+ // deployment additionally needs `com.jetbrains.plugins.webDeployment`, which
23
+ // exists only in Ultimate. One boolean covering both would let a deployment tool
24
+ // answer "are the run-lifecycle tools here?".
15
25
  //
16
26
  // Consequence of the split, and the reason it matters: a stock IDE serves a
17
27
  // SHORTER catalog than an enhanced one. Nothing here may be assumed — every use
18
- // has to be gated on the tools really being in the request (`servingRunLifecycleTools`).
28
+ // has to be gated on the tools really being in the request.
19
29
 
20
30
  /**
21
- * The enhance plugin's tool names, as **suffixes**.
31
+ * The enhance plugin's **run-lifecycle** tool names, as **suffixes**.
22
32
  *
23
33
  * OpenCode namespaces an MCP server's tools with the server name, and each
24
34
  * project is registered under its own prefix (root = `ide`, a subproject = its
@@ -45,6 +55,48 @@ export const RUN_LIFECYCLE_SUFFIXES = [
45
55
  RERUN_RUN_CONFIGURATION_SUFFIX,
46
56
  ];
47
57
 
58
+ /**
59
+ * The enhance plugin's **deployment** tool names, as suffixes — same contract and
60
+ * same reason for suffixes as {@link RUN_LIFECYCLE_SUFFIXES}.
61
+ *
62
+ * Keep in sync with `DeploymentSchemas.kt` in the enhance repository.
63
+ *
64
+ * The four are gated together on one plugin (`com.jetbrains.plugins.webDeployment`,
65
+ * Ultimate only) rather than on version drift, so they usually appear or vanish as
66
+ * a set — the guidance still names them one by one, because "usually" is not
67
+ * "always" and a named tool the model cannot call is only a failed call.
68
+ */
69
+ export const LIST_DEPLOYMENT_SERVERS_SUFFIX = 'list_deployment_servers';
70
+ export const UPLOAD_TO_DEPLOYMENT_SUFFIX = 'upload_to_deployment';
71
+ export const DOWNLOAD_FROM_DEPLOYMENT_SUFFIX = 'download_from_deployment';
72
+ export const GET_DEPLOYMENT_TASK_STATUS_SUFFIX = 'get_deployment_task_status';
73
+
74
+ /** All of them: look at the servers, upload, download, then poll a transfer. */
75
+ export const DEPLOYMENT_SUFFIXES = [
76
+ LIST_DEPLOYMENT_SERVERS_SUFFIX,
77
+ UPLOAD_TO_DEPLOYMENT_SUFFIX,
78
+ DOWNLOAD_FROM_DEPLOYMENT_SUFFIX,
79
+ GET_DEPLOYMENT_TASK_STATUS_SUFFIX,
80
+ ];
81
+
82
+ /**
83
+ * Does this request carry the tool called `suffix` under `prefix`?
84
+ *
85
+ * A malformed or missing snapshot answers "no": assuming a tool exists is the one
86
+ * mistake that cannot be walked back once the model has read the guidance.
87
+ *
88
+ * Exported because the guidance asks this twice. The capability flags (below) ask
89
+ * "does this request have the tool somewhere" (any project will do); the command
90
+ * list next to each tool asks "under WHICH prefix", and that answer must be exact —
91
+ * 提示词里点名一个模型调不到的命令,是这个模块唯一要防的失败。
92
+ *
93
+ * @param {Record<string, unknown> | undefined} tools
94
+ * @param {string} prefix 该项目的工具前缀(含下划线)。
95
+ * @param {string} suffix
96
+ */
97
+ export const servesTool = (tools, prefix, suffix) =>
98
+ Boolean(tools) && typeof tools === 'object' && `${prefix}${suffix}` in tools;
99
+
48
100
  /**
49
101
  * Which of the enhance plugin's tools **this project's prefix** carries in this
50
102
  * request.
@@ -60,12 +112,30 @@ export const RUN_LIFECYCLE_SUFFIXES = [
60
112
  * @param {string} prefix 该项目的工具前缀(含下划线)。
61
113
  */
62
114
  export function runLifecycleCapabilities(tools, prefix) {
63
- const has = (suffix) =>
64
- Boolean(tools) && typeof tools === 'object' && `${prefix}${suffix}` in tools;
65
115
  return {
66
- canCreateRunConfiguration: has(CREATE_SHELL_RUN_CONFIGURATION_SUFFIX),
67
- canListRunningConfigurations: has(LIST_RUNNING_CONFIGURATIONS_SUFFIX),
68
- canStopRunConfiguration: has(STOP_RUN_CONFIGURATION_SUFFIX),
69
- canRerunRunConfiguration: has(RERUN_RUN_CONFIGURATION_SUFFIX),
116
+ canCreateRunConfiguration: servesTool(tools, prefix, CREATE_SHELL_RUN_CONFIGURATION_SUFFIX),
117
+ canListRunningConfigurations: servesTool(tools, prefix, LIST_RUNNING_CONFIGURATIONS_SUFFIX),
118
+ canStopRunConfiguration: servesTool(tools, prefix, STOP_RUN_CONFIGURATION_SUFFIX),
119
+ canRerunRunConfiguration: servesTool(tools, prefix, RERUN_RUN_CONFIGURATION_SUFFIX),
120
+ };
121
+ }
122
+
123
+ /**
124
+ * Same question for the deployment group, and the same answer shape — see
125
+ * {@link runLifecycleCapabilities}.
126
+ *
127
+ * The names repeat the tool's subject rather than the group (`canUploadToDeployment`,
128
+ * not `canUpload`): the guidance is one document about two groups, and a flag whose
129
+ * owner is unclear is how the wrong section gets the wrong text.
130
+ *
131
+ * @param {Record<string, unknown> | undefined} tools
132
+ * @param {string} prefix 该项目的工具前缀(含下划线)。
133
+ */
134
+ export function deploymentCapabilities(tools, prefix) {
135
+ return {
136
+ canListDeploymentServers: servesTool(tools, prefix, LIST_DEPLOYMENT_SERVERS_SUFFIX),
137
+ canUploadToDeployment: servesTool(tools, prefix, UPLOAD_TO_DEPLOYMENT_SUFFIX),
138
+ canDownloadFromDeployment: servesTool(tools, prefix, DOWNLOAD_FROM_DEPLOYMENT_SUFFIX),
139
+ canGetDeploymentTaskStatus: servesTool(tools, prefix, GET_DEPLOYMENT_TASK_STATUS_SUFFIX),
70
140
  };
71
141
  }
package/src/mcp/idea.js CHANGED
@@ -13,7 +13,7 @@
13
13
  // HTTP header `IJ_MCP_SERVER_PROJECT_PATH` (see `serverConfig`).
14
14
  // * Availability probe: a single `initialize` POST to `/stream` succeeds when
15
15
  // the MCP server is up. We do NOT check whether a specific project is open —
16
- // the user triggers `/open-in-idea` for the project they want, so "server is
16
+ // the user triggers `/open-in-ide` for the project they want, so "server is
17
17
  // up" is the only thing worth knowing.
18
18
 
19
19
  export const PROJECT_HEADER = 'IJ_MCP_SERVER_PROJECT_PATH';
@@ -127,7 +127,7 @@ export async function callTool(port, projectPath, name, args = {}, options = {})
127
127
  params: {
128
128
  protocolVersion: PROTOCOL_VERSION,
129
129
  capabilities: {},
130
- clientInfo: { name: 'opencode-idea', version: '0.0.18' },
130
+ clientInfo: { name: 'opencode-idea', version: '0.0.20' },
131
131
  },
132
132
  });
133
133
  // The IDE's Streamable-HTTP endpoint binds follow-up requests to the session