@shaohui_jin/git-cockpit-mcp-server 0.1.13 → 0.1.15

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 CHANGED
@@ -54,7 +54,8 @@ git-cockpit version # 输出版本号
54
54
 
55
55
  - **多仓库管理**:统一打开/移除仓库,跨仓库操作
56
56
  - **安全机制**:工具按风险分级(readonly / write / dangerous),写操作 dry-run 预览 → 确认 → 执行;高危操作可配置人工审批
57
- - **内置 Web UI**:状态 / 历史 / 日志 / 设置视图,支持提交、分支、stash、pull/push、硬重置等操作
57
+ - **MCP 默认摘要**:`git_diff` / `git_show` / `git_merge_rehearse` 默认不回完整正文;要正文请加 `path` 或 `detail=true`(网页 GET 仍是全文)
58
+ - **内置 Web UI**:工作台 / 状态 / 历史 / 日志 / 设置视图,支持提交、分支、stash、pull/push、硬重置等操作
58
59
  - **审计日志**:所有工具调用记录落库,可回溯
59
60
  - **备份**:高危操作前自动备份分支引用与 stash 快照
60
61
 
@@ -6,6 +6,9 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
6
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
7
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
8
8
 
9
+ // package.json
10
+ var version = "0.1.15";
11
+
9
12
  // src/tools/handlers.ts
10
13
  import { z } from "zod";
11
14
  import {
@@ -13,6 +16,98 @@ import {
13
16
  GitOperationError,
14
17
  PermissionError
15
18
  } from "@shaohui_jin/git-cockpit-core";
19
+
20
+ // src/tools/format.ts
21
+ var FILE_CONTENT_LIMIT = 64 * 1024;
22
+ function wantsDetail(args) {
23
+ return args?.detail === true;
24
+ }
25
+ function hasPath(args) {
26
+ return typeof args?.path === "string" && args.path.trim().length > 0;
27
+ }
28
+ function asRecord(value) {
29
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
30
+ }
31
+ function stripDiffPatch(diff) {
32
+ const d = asRecord(diff);
33
+ if (!d || !("rawPatch" in d)) return diff;
34
+ const { rawPatch: _raw, ...rest } = d;
35
+ return { ...rest, detailAvailable: true };
36
+ }
37
+ function stripRehearseBodies(result) {
38
+ const r = asRecord(result);
39
+ if (!r) return result;
40
+ const files = Array.isArray(r.conflictFiles) ? r.conflictFiles : [];
41
+ const paths = files.map((f) => {
42
+ const row = asRecord(f);
43
+ return {
44
+ path: row?.path ?? "",
45
+ contentConflict: row?.contentConflict ?? true,
46
+ hunks: []
47
+ };
48
+ });
49
+ const first = typeof paths[0]?.path === "string" ? paths[0].path : "";
50
+ return {
51
+ ...r,
52
+ conflictFiles: paths,
53
+ detailAvailable: true,
54
+ next: first ? [{ tool: "git_merge_rehearse", args: { into: r.into, from: r.from, path: first } }] : []
55
+ };
56
+ }
57
+ function summarizeForAgent(tool, result, args = {}) {
58
+ if (result == null) return result;
59
+ const detail2 = wantsDetail(args);
60
+ const pathSet = hasPath(args);
61
+ if (tool === "git_diff" || tool === "git_stash_show") {
62
+ if (detail2 || pathSet) return result;
63
+ return stripDiffPatch(result);
64
+ }
65
+ if (tool === "git_show") {
66
+ if (detail2 || pathSet) return result;
67
+ const row = asRecord(result);
68
+ if (!row) return result;
69
+ return { ...row, diff: stripDiffPatch(row.diff) };
70
+ }
71
+ if (tool === "git_merge_rehearse") {
72
+ if (detail2 || pathSet) return result;
73
+ return stripRehearseBodies(result);
74
+ }
75
+ if (tool === "git_file_content") {
76
+ if (detail2) return result;
77
+ const row = asRecord(result);
78
+ const content = typeof row?.content === "string" ? row.content : "";
79
+ const bytes = Buffer.byteLength(content, "utf8");
80
+ if (bytes <= FILE_CONTENT_LIMIT) return result;
81
+ return {
82
+ path: args.path,
83
+ truncated: true,
84
+ bytes,
85
+ detailAvailable: true
86
+ };
87
+ }
88
+ return result;
89
+ }
90
+ function formatResultForMcp(exec) {
91
+ if (!exec.success) {
92
+ const prefix = exec.error?.requiredApproval ? "\u9700\u8981\u5BA1\u6279" : "\u64CD\u4F5C\u5931\u8D25";
93
+ return `${prefix}\uFF1A${exec.error?.message ?? "\u672A\u77E5\u9519\u8BEF"}`;
94
+ }
95
+ const parts = [];
96
+ if (exec.dryRun) {
97
+ parts.push("[dry-run \u9884\u89C8] \u672A\u5B9E\u9645\u6267\u884C\u4EFB\u4F55\u64CD\u4F5C");
98
+ }
99
+ if (exec.backupCreated) {
100
+ parts.push(
101
+ `[\u81EA\u52A8\u5907\u4EFD] \u5206\u652F ${exec.backupCreated.branch ?? "(\u672A\u521B\u5EFA)"}${exec.backupCreated.stashRef ? `\uFF0Cstash ${exec.backupCreated.stashRef}` : ""}`
102
+ );
103
+ }
104
+ const summarized = summarizeForAgent(exec.tool, exec.result, exec.args ?? {});
105
+ const payload = typeof summarized === "string" ? summarized : JSON.stringify(summarized);
106
+ parts.push(payload);
107
+ return parts.join("\n\n");
108
+ }
109
+
110
+ // src/tools/handlers.ts
16
111
  async function resolveRepo(runtime, ctx, argsPath) {
17
112
  if (ctx.repoId !== void 0) {
18
113
  const handle = await runtime.repoManager.getById(ctx.repoId);
@@ -73,11 +168,15 @@ async function executeTool(def, rawArgs, ctx) {
73
168
  record("error", rawArgs, e.message);
74
169
  return { tool: def.name, source: ctx.source, dryRun: dryRun2, success: false, error: e, durationMs: Date.now() - t0 };
75
170
  }
76
- const handle = await resolveRepo(runtime, ctx, args.repoPath);
77
- const git = handle.service;
78
- const repoPath2 = git.repoPath;
171
+ let git;
172
+ let repoPath2 = "";
173
+ if (def.needsRepo !== false) {
174
+ const handle = await resolveRepo(runtime, ctx, args.repoPath);
175
+ git = handle.service;
176
+ repoPath2 = git.repoPath;
177
+ }
79
178
  let backup = null;
80
- if (!dryRun2 && def.risk === "dangerous" && runtime.config.git.backupOnDangerousOps) {
179
+ if (!dryRun2 && def.risk === "dangerous" && runtime.config.git.backupOnDangerousOps && git) {
81
180
  backup = await new BackupManager(git).createBackup();
82
181
  args = { ...args, __backup: { branch: backup.branch, stashRef: backup.stashRef } };
83
182
  }
@@ -92,7 +191,8 @@ async function executeTool(def, rawArgs, ctx) {
92
191
  result,
93
192
  preview,
94
193
  backupCreated: backup,
95
- durationMs: Date.now() - t0
194
+ durationMs: Date.now() - t0,
195
+ args
96
196
  };
97
197
  } catch (err) {
98
198
  const e = extractError(err);
@@ -108,34 +208,68 @@ async function executeTool(def, rawArgs, ctx) {
108
208
  };
109
209
  }
110
210
  }
111
- function formatResultForMcp(exec) {
112
- if (!exec.success) {
113
- const prefix = exec.error?.requiredApproval ? "\u9700\u8981\u5BA1\u6279" : "\u64CD\u4F5C\u5931\u8D25";
114
- return `${prefix}\uFF1A${exec.error?.message ?? "\u672A\u77E5\u9519\u8BEF"}`;
115
- }
116
- const parts = [];
117
- if (exec.dryRun) {
118
- parts.push("[dry-run \u9884\u89C8] \u672A\u5B9E\u9645\u6267\u884C\u4EFB\u4F55\u64CD\u4F5C");
119
- }
120
- if (exec.backupCreated) {
121
- parts.push(`[\u81EA\u52A8\u5907\u4EFD] \u5206\u652F ${exec.backupCreated.branch ?? "(\u672A\u521B\u5EFA)"}${exec.backupCreated.stashRef ? `\uFF0Cstash ${exec.backupCreated.stashRef}` : ""}`);
122
- }
123
- const payload = typeof exec.result === "string" ? exec.result : JSON.stringify(exec.result, null, 2);
124
- parts.push(payload);
125
- return parts.join("\n\n");
126
- }
127
211
 
128
212
  // src/tools/index.ts
129
213
  import {
130
214
  enrichPrepareMr,
131
215
  createPullOrMergeRequest,
132
- BackupManager as BackupManager2
216
+ BackupManager as BackupManager2,
217
+ GitOperationError as GitOperationError2,
218
+ SURVEY_ASYNC_THRESHOLD
133
219
  } from "@shaohui_jin/git-cockpit-core";
134
220
 
221
+ // src/overview.ts
222
+ var CONCURRENCY = 4;
223
+ function unavailableOverview(path, extra = {}) {
224
+ const name = path.replace(/[\\/]+$/, "").split(/[\\/]/).pop() || path;
225
+ return {
226
+ path,
227
+ name,
228
+ available: false,
229
+ current: "",
230
+ tracking: null,
231
+ ahead: 0,
232
+ behind: 0,
233
+ dirtyCount: 0,
234
+ conflictCount: 0,
235
+ operation: "none",
236
+ tempMergeBranchCount: 0,
237
+ ...extra
238
+ };
239
+ }
240
+ async function mapPool(items, n, fn) {
241
+ const out = new Array(items.length);
242
+ let i = 0;
243
+ async function worker() {
244
+ while (i < items.length) {
245
+ const idx = i++;
246
+ out[idx] = await fn(items[idx]);
247
+ }
248
+ }
249
+ const workers = Math.min(n, items.length);
250
+ await Promise.all(Array.from({ length: workers }, () => worker()));
251
+ return out;
252
+ }
253
+ async function collectRepoOverviews(runtime) {
254
+ const repos = runtime.repoManager.list();
255
+ if (!repos.length) return [];
256
+ return mapPool(repos, CONCURRENCY, async (r) => {
257
+ try {
258
+ const handle = await runtime.repoManager.getById(r.id);
259
+ if (!handle) return unavailableOverview(r.path, { id: r.id, lastOpenedAt: r.lastOpenedAt });
260
+ const o = await handle.service.getOverview();
261
+ return { ...o, id: r.id, lastOpenedAt: r.lastOpenedAt };
262
+ } catch {
263
+ return unavailableOverview(r.path, { id: r.id, lastOpenedAt: r.lastOpenedAt });
264
+ }
265
+ });
266
+ }
267
+
135
268
  // src/tools/schemas.ts
136
269
  import { z as z2 } from "zod";
137
270
  var repoPath = z2.string().optional().describe("\u4ED3\u5E93\u7EDD\u5BF9\u8DEF\u5F84\u3002\u4E0D\u63D0\u4F9B\u65F6\u4F7F\u7528\u5F53\u524D\u6253\u5F00\u7684\u4ED3\u5E93\u3002");
138
271
  var dryRun = z2.boolean().optional().describe("\u4E3A true \u65F6\u4EC5\u8FD4\u56DE\u5C06\u6267\u884C\u7684\u547D\u4EE4\u4E0E\u5F71\u54CD\u8303\u56F4\uFF08\u9884\u89C8/\u5E72\u8FD0\u884C\uFF09\uFF0C\u4E0D\u771F\u6B63\u4FEE\u6539\u4ED3\u5E93\u3002");
272
+ var detail = z2.boolean().optional().describe("\u4E3A true \u65F6 MCP \u8FD4\u56DE\u5B8C\u6574\u6B63\u6587\uFF1B\u9ED8\u8BA4\u53EA\u56DE\u6458\u8981\uFF08\u8DEF\u5F84/\u7EDF\u8BA1\uFF09\u3002\u7F51\u9875 GET \u4E0D\u53D7\u5F71\u54CD\u3002");
139
273
  var maxCount = z2.number().int().min(1).max(1e4).optional().describe("\u6700\u591A\u8FD4\u56DE\u7684\u63D0\u4EA4\u6570\u91CF\u3002");
140
274
  var readonlyBase = { repoPath };
141
275
  var GitStatusSchema = z2.object({ ...readonlyBase });
@@ -154,13 +288,15 @@ var GitDiffSchema = z2.object({
154
288
  to: z2.string().optional().describe("\u7EC8\u70B9\u63D0\u4EA4"),
155
289
  path: z2.string().optional().describe("\u9650\u5B9A\u7684\u6587\u4EF6\u8DEF\u5F84"),
156
290
  staged: z2.boolean().optional().describe("\u67E5\u770B\u5DF2\u6682\u5B58\uFF08\u7D22\u5F15\uFF09\u5DEE\u5F02"),
157
- maxPatchBytes: z2.number().int().positive().optional().describe("diff \u6B63\u6587\u6700\u5927\u5B57\u8282\u6570\uFF0C\u8D85\u51FA\u622A\u65AD")
291
+ maxPatchBytes: z2.number().int().positive().optional().describe("diff \u6B63\u6587\u6700\u5927\u5B57\u8282\u6570\uFF0C\u8D85\u51FA\u622A\u65AD"),
292
+ detail
158
293
  });
159
294
  var GitShowSchema = z2.object({
160
295
  ...readonlyBase,
161
296
  commit: z2.string().describe("\u8981\u67E5\u770B\u7684\u63D0\u4EA4 hash"),
162
297
  path: z2.string().optional().describe("\u9650\u5B9A\u6587\u4EF6"),
163
- maxPatchBytes: z2.number().int().positive().optional()
298
+ maxPatchBytes: z2.number().int().positive().optional(),
299
+ detail
164
300
  });
165
301
  var GitBranchListSchema = z2.object({ ...readonlyBase });
166
302
  var GitTagListSchema = z2.object({ ...readonlyBase });
@@ -168,7 +304,8 @@ var GitRemoteListSchema = z2.object({ ...readonlyBase });
168
304
  var GitFileContentSchema = z2.object({
169
305
  ...readonlyBase,
170
306
  commit: z2.string().describe("\u63D0\u4EA4 hash\uFF08\u5982 HEAD\u3001main\uFF09"),
171
- path: z2.string().describe("\u4ED3\u5E93\u5185\u6587\u4EF6\u76F8\u5BF9\u8DEF\u5F84")
307
+ path: z2.string().describe("\u4ED3\u5E93\u5185\u6587\u4EF6\u76F8\u5BF9\u8DEF\u5F84"),
308
+ detail
172
309
  });
173
310
  var GitGraphSchema = z2.object({
174
311
  ...readonlyBase,
@@ -176,7 +313,9 @@ var GitGraphSchema = z2.object({
176
313
  });
177
314
  var GitBranchGraphSchema = z2.object({
178
315
  ...readonlyBase,
179
- maxNodes: z2.number().int().min(1).max(2e3).optional().describe("rev-list \u6700\u591A\u679A\u4E3E\u7684\u63D0\u4EA4\u6570\uFF0C\u7F3A\u7701 200")
316
+ maxNodes: z2.number().int().min(1).max(2e3).optional().describe("rev-list \u6700\u591A\u679A\u4E3E\u7684\u63D0\u4EA4\u6570\uFF0C\u7F3A\u7701 200"),
317
+ into: z2.string().optional().describe("\u5408\u5165\u76EE\u6807\uFF08\u7EBF\u4E0A / ours\uFF09\u3002\u4E0E from \u4E00\u8D77\u65F6\u8FD4\u56DE lineage"),
318
+ from: z2.string().optional().describe("\u6211\u7684\u5206\u652F\uFF08theirs\uFF09\u3002\u4E0E into \u4E00\u8D77\u65F6\u8FD4\u56DE lineage")
180
319
  });
181
320
  var GitReflogSchema = z2.object({
182
321
  ...readonlyBase,
@@ -196,7 +335,8 @@ var GitMergePreviewSchema = z2.object({
196
335
  var GitMergeRehearseSchema = z2.object({
197
336
  ...readonlyBase,
198
337
  ...mergeIntoFrom,
199
- maxFiles: z2.number().int().min(1).max(100).optional().describe("\u6700\u591A\u751F\u6210\u51B2\u7A81\u6B63\u6587\u7684\u6587\u4EF6\u6570\uFF0C\u7F3A\u7701 20")
338
+ maxFiles: z2.number().int().min(1).max(100).optional().describe("\u6700\u591A\u751F\u6210\u51B2\u7A81\u6B63\u6587\u7684\u6587\u4EF6\u6570\uFF0C\u7F3A\u7701 20"),
339
+ detail
200
340
  });
201
341
  var GitMergeBlameSchema = z2.object({
202
342
  ...readonlyBase,
@@ -211,7 +351,8 @@ var GitMergeSurveySchema = z2.object({
211
351
  intos: z2.array(z2.string().min(1)).min(1).describe("\u5408\u5165\u76EE\u6807\uFF08\u7EBF\u4E0A / ours\uFF09\u5217\u8868"),
212
352
  froms: z2.array(z2.string().min(1)).min(1).describe("\u6211\u7684\u5206\u652F\uFF08theirs\uFF09\u5217\u8868"),
213
353
  fetch: z2.boolean().optional().describe("\u662F\u5426\u5148\u975E\u4EA4\u4E92 fetch\u3002\u7F3A\u7701 true\uFF1B\u6574\u6279\u53EA fetch \u4E00\u6B21"),
214
- remote: z2.string().optional().describe("fetch \u4F7F\u7528\u7684\u8FDC\u7A0B\u540D\uFF0C\u7F3A\u7701 origin")
354
+ remote: z2.string().optional().describe("fetch \u4F7F\u7528\u7684\u8FDC\u7A0B\u540D\uFF0C\u7F3A\u7701 origin"),
355
+ async: z2.boolean().optional().describe("\u4E3A true \u6216\u683C\u5B50\u6570>20 \u65F6\u540E\u53F0\u8DD1\uFF0C\u7ACB\u5373\u8FD4\u56DE jobId\uFF0C\u7528 git_job_get \u67E5\u7ED3\u679C")
215
356
  });
216
357
  var GitMergeOrderSchema = z2.object({
217
358
  ...readonlyBase,
@@ -252,6 +393,26 @@ var GitPullSchema = z2.object({
252
393
  remote: z2.string().optional().describe("\u8FDC\u7A0B\u540D\uFF08\u7F3A\u7701 origin\uFF09"),
253
394
  branch: z2.string().optional().describe("\u8FDC\u7AEF\u5206\u652F\u540D")
254
395
  });
396
+ var GitFetchSchema = z2.object({
397
+ ...writeBase,
398
+ remote: z2.string().optional().describe("\u8FDC\u7A0B\u540D\uFF08\u7F3A\u7701 origin\uFF09")
399
+ });
400
+ var continueFiles = z2.array(
401
+ z2.object({
402
+ path: z2.string().describe("\u4ED3\u5E93\u5185\u76F8\u5BF9\u8DEF\u5F84"),
403
+ resolvedContent: z2.string().describe("\u8BE5\u6587\u4EF6\u89E3\u51B3\u540E\u7684\u5B8C\u6574\u5185\u5BB9")
404
+ })
405
+ ).optional().describe("\u5DF2\u89E3\u51B3\u51B2\u7A81\u7684\u6587\u4EF6\uFF1B\u5199\u5165\u5DE5\u4F5C\u533A\u5E76 git add \u540E\u518D continue\u3002\u5DF2\u5728\u7F16\u8F91\u5668\u89E3\u51B3\u5E76\u53EF\u4E3A\u7A7A");
406
+ var GitMergeAbortSchema = z2.object({ ...writeBase });
407
+ var GitMergeContinueSchema = z2.object({
408
+ ...writeBase,
409
+ files: continueFiles
410
+ });
411
+ var GitRebaseAbortSchema = z2.object({ ...writeBase });
412
+ var GitRebaseContinueSchema = z2.object({
413
+ ...writeBase,
414
+ files: continueFiles
415
+ });
255
416
  var GitPushSchema = z2.object({
256
417
  ...writeBase,
257
418
  remote: z2.string().optional().describe("\u8FDC\u7A0B\u540D\uFF08\u7F3A\u7701 origin\uFF09"),
@@ -273,7 +434,18 @@ var GitStashListSchema = z2.object({ ...readonlyBase });
273
434
  var GitStashShowSchema = z2.object({
274
435
  ...readonlyBase,
275
436
  index: z2.number().int().min(0).optional().describe("stash \u5E8F\u53F7\uFF0C\u7F3A\u7701\u4E3A stash@{0}"),
276
- maxPatchBytes: z2.number().int().positive().optional().describe("diff \u6B63\u6587\u6700\u5927\u5B57\u8282\u6570\uFF0C\u8D85\u51FA\u622A\u65AD")
437
+ maxPatchBytes: z2.number().int().positive().optional().describe("diff \u6B63\u6587\u6700\u5927\u5B57\u8282\u6570\uFF0C\u8D85\u51FA\u622A\u65AD"),
438
+ detail
439
+ });
440
+ var GitRepoOverviewSchema = z2.object({ ...readonlyBase });
441
+ var GitJobListSchema = z2.object({ ...readonlyBase });
442
+ var GitJobGetSchema = z2.object({
443
+ ...readonlyBase,
444
+ id: z2.string().min(1).describe("\u540E\u53F0\u4EFB\u52A1 id")
445
+ });
446
+ var GitJobCancelSchema = z2.object({
447
+ ...writeBase,
448
+ id: z2.string().min(1).describe("\u8981\u53D6\u6D88\u7684\u540E\u53F0\u4EFB\u52A1 id")
277
449
  });
278
450
  var GitStashApplySchema = z2.object({
279
451
  ...writeBase,
@@ -424,7 +596,11 @@ var TOOL_DEFS = [
424
596
  description: "\u5206\u652F tip DAG\uFF1Afor-each-ref + rev-list --parents\u3002\u8282\u70B9\u662F\u5404\u5206\u652F tip\uFF0C\u8FB9\u662F\u6700\u8FD1\u7956\u5148 tip\u3002\u4E0E Git Insight \u753B\u5E03\u540C\u4E00\u53E3\u5F84\u3002\u4E0D\u6539\u5DE5\u4F5C\u533A\u3002",
425
597
  risk: "readonly",
426
598
  schema: GitBranchGraphSchema,
427
- handler: async (args, ctx) => ctx.git.getBranchGraph(args.maxNodes)
599
+ handler: async (args, ctx) => ctx.git.getBranchGraph({
600
+ maxNodes: args.maxNodes,
601
+ into: args.into,
602
+ from: args.from
603
+ })
428
604
  },
429
605
  {
430
606
  name: "git_reflog",
@@ -455,17 +631,29 @@ var TOOL_DEFS = [
455
631
  },
456
632
  {
457
633
  name: "git_merge_rehearse",
458
- description: "\u5B8C\u6574\u5408\u5E76\u9884\u6F14\uFF1A\u51B2\u7A81\u6587\u4EF6 + diff3 \u51B2\u7A81\u6B63\u6587 + ours/theirs/base\uFF08\u4ECD\u4E0D\u6539\u5DE5\u4F5C\u533A\uFF09\u3002\u9009\u8FB9\u540E\u628A files[{path,resolvedContent}] \u4EA4\u7ED9 git_apply_resolve\u3002into/from \u542B\u4E49\u540C git_merge_preview\u3002",
634
+ description: "\u5B8C\u6574\u5408\u5E76\u9884\u6F14\uFF1A\u51B2\u7A81\u6587\u4EF6 + diff3 \u51B2\u7A81\u6B63\u6587 + ours/theirs/base\uFF08\u4ECD\u4E0D\u6539\u5DE5\u4F5C\u533A\uFF09\u3002MCP \u9ED8\u8BA4\u53EA\u56DE\u8DEF\u5F84\u6458\u8981\uFF1B\u8981\u6B63\u6587\u8BF7\u5E26 path \u6216 detail=true\u3002\u9009\u8FB9\u540E\u628A files \u4EA4\u7ED9 git_apply_resolve\u3002into/from \u540C git_merge_preview\u3002",
459
635
  risk: "readonly",
460
636
  schema: GitMergeRehearseSchema,
461
- handler: async (args, ctx) => ctx.git.rehearseMerge({
462
- into: args.into,
463
- from: args.from,
464
- fetch: args.fetch,
465
- remote: args.remote,
466
- path: args.path,
467
- maxFiles: args.maxFiles
468
- })
637
+ handler: async (args, ctx) => {
638
+ const wantBody = args.detail === true || typeof args.path === "string" && args.path.trim().length > 0;
639
+ if (!wantBody) {
640
+ return ctx.git.previewMerge({
641
+ into: args.into,
642
+ from: args.from,
643
+ fetch: args.fetch,
644
+ remote: args.remote,
645
+ path: args.path
646
+ });
647
+ }
648
+ return ctx.git.rehearseMerge({
649
+ into: args.into,
650
+ from: args.from,
651
+ fetch: args.fetch,
652
+ remote: args.remote,
653
+ path: args.path,
654
+ maxFiles: args.maxFiles
655
+ });
656
+ }
469
657
  },
470
658
  {
471
659
  name: "git_merge_blame",
@@ -482,15 +670,31 @@ var TOOL_DEFS = [
482
670
  },
483
671
  {
484
672
  name: "git_merge_survey",
485
- description: "\u6279\u91CF\u9884\u6F14\u77E9\u9635\uFF1Aintos \xD7 froms \u6BCF\u5BF9\u8DD1\u4E00\u6B21 merge-tree\uFF0C\u6574\u6279\u53EA fetch \u4E00\u6B21\u3002\u53EA\u8FD4\u56DE\u7ED3\u8BBA\u4E0E\u51B2\u7A81\u8DEF\u5F84\uFF0C\u4E0D\u751F\u6210\u6B63\u6587\u3002\u9002\u5408\u53D1\u5E03\u524D\u626B\u63CF\u3002\u7981\u6B62\u7528 git_merge \u505A\u9884\u6F14\u3002",
673
+ description: "\u6279\u91CF\u9884\u6F14\u77E9\u9635\uFF1Aintos \xD7 froms \u6BCF\u5BF9\u8DD1\u4E00\u6B21 merge-tree\uFF0C\u6574\u6279\u53EA fetch \u4E00\u6B21\u3002\u53EA\u8FD4\u56DE\u7ED3\u8BBA\u4E0E\u51B2\u7A81\u8DEF\u5F84\u3002\u683C\u5B50>20 \u6216 async=true \u65F6\u540E\u53F0\u8DD1\u5E76\u8FD4\u56DE jobId\uFF08git_job_get\uFF09\u3002\u7981\u6B62\u7528 git_merge \u505A\u9884\u6F14\u3002",
486
674
  risk: "readonly",
487
675
  schema: GitMergeSurveySchema,
488
- handler: async (args, ctx) => ctx.git.surveyMerges({
489
- intos: args.intos,
490
- froms: args.froms,
491
- fetch: args.fetch,
492
- remote: args.remote
493
- })
676
+ handler: async (args, ctx) => {
677
+ const intos = args.intos;
678
+ const froms = args.froms;
679
+ const asyncWanted = args.async === true || intos.length * froms.length > SURVEY_ASYNC_THRESHOLD;
680
+ if (asyncWanted) {
681
+ const job = ctx.runtime.jobs.startSurvey({
682
+ repoPath: ctx.repoPath,
683
+ intos,
684
+ froms,
685
+ fetch: args.fetch,
686
+ remote: args.remote,
687
+ git: ctx.git
688
+ });
689
+ return { jobId: job.id, status: job.status, async: true, title: job.title };
690
+ }
691
+ return ctx.git.surveyMerges({
692
+ intos,
693
+ froms,
694
+ fetch: args.fetch,
695
+ remote: args.remote
696
+ });
697
+ }
494
698
  },
495
699
  {
496
700
  name: "git_merge_order",
@@ -520,6 +724,34 @@ var TOOL_DEFS = [
520
724
  return enrichPrepareMr({ prep, mr, cwd: ctx.git.repoPath });
521
725
  }
522
726
  },
727
+ {
728
+ name: "git_repo_overview",
729
+ description: "\u5DF2\u6253\u5F00\u4ED3\u5E93\u8109\u640F\uFF1A\u5206\u652F\u3001\u810F\u6587\u4EF6\u6570\u3001ahead/behind\u3001\u5DE5\u4F5C\u533A operation\u3001\u672C\u5730 merge/* \u6570\u3002\u4E0D\u56DE diff\u3002",
730
+ risk: "readonly",
731
+ needsRepo: false,
732
+ schema: GitRepoOverviewSchema,
733
+ handler: async (_args, ctx) => ({ repos: await collectRepoOverviews(ctx.runtime) })
734
+ },
735
+ {
736
+ name: "git_job_list",
737
+ description: "\u5217\u51FA\u540E\u53F0\u4EFB\u52A1\u6458\u8981\uFF08\u514B\u9686 / survey / fetch\uFF09\u3002",
738
+ risk: "readonly",
739
+ needsRepo: false,
740
+ schema: GitJobListSchema,
741
+ handler: async (_args, ctx) => ({ jobs: ctx.runtime.jobs.list().map((j) => ctx.runtime.jobs.summary(j)) })
742
+ },
743
+ {
744
+ name: "git_job_get",
745
+ description: "\u67E5\u770B\u4E00\u6761\u540E\u53F0\u4EFB\u52A1\uFF08\u65E5\u5FD7 + \u5B8C\u6210\u65F6\u7684 result \u6458\u8981\uFF09\u3002",
746
+ risk: "readonly",
747
+ needsRepo: false,
748
+ schema: GitJobGetSchema,
749
+ handler: async (args, ctx) => {
750
+ const job = ctx.runtime.jobs.get(args.id);
751
+ if (!job) throw new GitOperationError2("\u4EFB\u52A1\u4E0D\u5B58\u5728", "JOB_NOT_FOUND");
752
+ return ctx.runtime.jobs.detail(job);
753
+ }
754
+ },
523
755
  // ---------------------------------------------------------------------------
524
756
  // 写操作工具(默认开放,需预览/确认)
525
757
  // ---------------------------------------------------------------------------
@@ -579,6 +811,23 @@ var TOOL_DEFS = [
579
811
  schema: GitMergeSchema,
580
812
  handler: async (args, ctx) => ctx.git.merge(args.branch, { dryRun: args.dryRun })
581
813
  },
814
+ {
815
+ name: "git_merge_abort",
816
+ description: "\u4E2D\u6B62\u5F53\u524D\u5DE5\u4F5C\u533A merge\uFF08git merge --abort\uFF09\u3002\u53EA\u7528\u4E8E\u5DF2\u7ECF\u5F00\u59CB\u7684\u5DE5\u4F5C\u533A\u5408\u5E76\uFF0C\u7981\u6B62\u62FF\u6765\u4EE3\u66FF merge-tree \u9884\u6F14\u3002",
817
+ risk: "write",
818
+ schema: GitMergeAbortSchema,
819
+ handler: async (args, ctx) => ctx.git.mergeAbort({ dryRun: args.dryRun })
820
+ },
821
+ {
822
+ name: "git_merge_continue",
823
+ description: "\u7EE7\u7EED\u5F53\u524D\u5DE5\u4F5C\u533A merge\u3002\u53EF\u628A files[{path,resolvedContent}] \u5199\u5165\u5DE5\u4F5C\u533A\u5E76 git add \u540E\u518D continue\u3002\u4E0D\u662F git_apply_resolve\u3002",
824
+ risk: "write",
825
+ schema: GitMergeContinueSchema,
826
+ handler: async (args, ctx) => ctx.git.mergeContinue({
827
+ dryRun: args.dryRun,
828
+ files: args.files
829
+ })
830
+ },
582
831
  {
583
832
  name: "git_pull",
584
833
  description: "\u4ECE\u8FDC\u7A0B\u62C9\u53D6\u5E76\u5408\u5E76\uFF08\u975E\u5F3A\u5236\uFF09\u3002\u8FDC\u7A0B\u6709\u51B2\u7A81\u5C06\u63D0\u793A\u3002\u652F\u6301 dry_run \u9884\u89C8\u3002",
@@ -590,6 +839,16 @@ var TOOL_DEFS = [
590
839
  branch: args.branch
591
840
  })
592
841
  },
842
+ {
843
+ name: "git_fetch",
844
+ description: "\u4ECE\u8FDC\u7A0B\u6293\u53D6\u8DDF\u8E2A\u5206\u652F\uFF08git fetch --prune --no-tags\uFF09\uFF0C\u4E0D\u6539\u5DE5\u4F5C\u533A\u3001\u4E0D\u5408\u5E76\u3002\u66F4\u65B0 origin/xxx \u540E\u518D\u505A\u9884\u6F14\u6216\u5206\u652F\u56FE\u5BF9\u6BD4\u3002\u652F\u6301 dry_run \u9884\u89C8\u3002",
845
+ risk: "write",
846
+ schema: GitFetchSchema,
847
+ handler: async (args, ctx) => ctx.git.fetch({
848
+ dryRun: args.dryRun,
849
+ remote: args.remote
850
+ })
851
+ },
593
852
  {
594
853
  name: "git_push",
595
854
  description: "\u63A8\u9001\u5230\u8FDC\u7A0B\uFF08\u975E\u5F3A\u5236\uFF09\u3002\u63A8\u9001\u88AB\u62D2\u7EDD\uFF08\u8FDC\u7A0B\u6709\u65B0\u63D0\u4EA4\uFF09\u4F1A\u7ED9\u51FA\u63D0\u793A\u3002\u652F\u6301 dry_run \u9884\u89C8\u3002",
@@ -701,6 +960,25 @@ var TOOL_DEFS = [
701
960
  });
702
961
  }
703
962
  },
963
+ {
964
+ name: "git_job_cancel",
965
+ description: "\u53D6\u6D88\u8FDB\u884C\u4E2D\u7684\u540E\u53F0\u4EFB\u52A1\u3002\u514B\u9686\u4F1A\u6740\u8FDB\u7A0B\u5E76\u5220\u9664\u4E0D\u5B8C\u6574\u76EE\u5F55\u3002",
966
+ risk: "write",
967
+ needsRepo: false,
968
+ schema: GitJobCancelSchema,
969
+ handler: async (args, ctx) => {
970
+ if (args.dryRun) {
971
+ return {
972
+ dryRun: true,
973
+ command: "job cancel",
974
+ args: [args.id],
975
+ risk: "low",
976
+ note: "\u5C06\u53D6\u6D88\u8FDB\u884C\u4E2D\u7684\u540E\u53F0\u4EFB\u52A1"
977
+ };
978
+ }
979
+ return ctx.runtime.jobs.summary(ctx.runtime.jobs.cancel(args.id));
980
+ }
981
+ },
704
982
  // ---------------------------------------------------------------------------
705
983
  // 高风险工具(默认禁用,需开启/审批;自动备份)
706
984
  // ---------------------------------------------------------------------------
@@ -742,6 +1020,23 @@ var TOOL_DEFS = [
742
1020
  risk: "dangerous",
743
1021
  schema: GitRebaseSchema,
744
1022
  handler: async (args, ctx) => ctx.git.rebase(args.branch, { dryRun: args.dryRun })
1023
+ },
1024
+ {
1025
+ name: "git_rebase_abort",
1026
+ description: "\u4E2D\u6B62\u5F53\u524D\u5DE5\u4F5C\u533A rebase\uFF08git rebase --abort\uFF09\u3002\u53EA\u7528\u4E8E\u5DF2\u7ECF\u5F00\u59CB\u7684\u53D8\u57FA\u3002",
1027
+ risk: "write",
1028
+ schema: GitRebaseAbortSchema,
1029
+ handler: async (args, ctx) => ctx.git.rebaseAbort({ dryRun: args.dryRun })
1030
+ },
1031
+ {
1032
+ name: "git_rebase_continue",
1033
+ description: "\u7EE7\u7EED\u5F53\u524D\u5DE5\u4F5C\u533A rebase\u3002\u53EF\u628A files \u5199\u5165\u5DE5\u4F5C\u533A\u5E76 git add \u540E\u518D continue\u3002\u51B2\u7A81\u987B\u5148\u89E3\u51B3\u3002",
1034
+ risk: "write",
1035
+ schema: GitRebaseContinueSchema,
1036
+ handler: async (args, ctx) => ctx.git.rebaseContinue({
1037
+ dryRun: args.dryRun,
1038
+ files: args.files
1039
+ })
745
1040
  }
746
1041
  ];
747
1042
  var TOOL_DEF_MAP = new Map(TOOL_DEFS.map((d) => [d.name, d]));
@@ -750,7 +1045,7 @@ function toolSummaries() {
750
1045
  }
751
1046
 
752
1047
  // src/mcpServer.ts
753
- var MCP_SERVER_INFO = { name: "git-cockpit-mcp-server", version: "0.1.0" };
1048
+ var MCP_SERVER_INFO = { name: "git-cockpit-mcp-server", version };
754
1049
  function createMcpServer(runtime) {
755
1050
  const server = new McpServer(
756
1051
  { name: MCP_SERVER_INFO.name, version: MCP_SERVER_INFO.version },
@@ -759,8 +1054,8 @@ function createMcpServer(runtime) {
759
1054
  instructions: [
760
1055
  "Git Cockpit MCP Server\uFF1A\u53EF\u89C6\u5316\u7684 Git \u64CD\u4F5C\u5DE5\u5177\u3002",
761
1056
  "\u4F7F\u7528\u89C4\u5219\uFF1A",
762
- "1. \u5148\u8C03\u7528 git_status \u67E5\u770B\u5DE5\u4F5C\u533A\u72B6\u6001\uFF1B",
763
- "2. \u5199\u64CD\u4F5C\uFF08git_add/git_commit/git_push \u7B49\uFF09\u9ED8\u8BA4\u53EF\u6267\u884C\uFF0C\u4F46\u8BF7\u5148\u4F7F\u7528 dry_run=true \u9884\u89C8\u5F71\u54CD\u8303\u56F4\uFF1B",
1057
+ "1. \u5148\u8C03\u7528 git_status \u67E5\u770B\u5DE5\u4F5C\u533A\u72B6\u6001\uFF08\u542B operation=merge/rebase \u65F6\u8D70\u5DE5\u4F5C\u533A\u6536\u5C3E\uFF0C\u4E0D\u8981\u7528\u9884\u6F14\u5DE5\u5177\u5192\u5145\uFF09\uFF1B",
1058
+ "2. \u5199\u64CD\u4F5C\uFF08git_add/git_commit/git_fetch/git_push \u7B49\uFF09\u9ED8\u8BA4\u53EF\u6267\u884C\uFF0C\u4F46\u8BF7\u5148\u4F7F\u7528 dry_run=true \u9884\u89C8\u5F71\u54CD\u8303\u56F4\uFF1B",
764
1059
  "3. \u9AD8\u98CE\u9669\u5DE5\u5177\uFF08git_reset_hard/git_clean/git_push_force/git_branch_delete_force/git_rebase\uFF09\u9ED8\u8BA4\u7981\u7528\uFF0C",
765
1060
  " \u9700\u7BA1\u7406\u5458\u5728\u914D\u7F6E\u4E2D\u5F00\u542F\uFF1B\u6267\u884C\u65F6\u7CFB\u7EDF\u4F1A\u81EA\u52A8\u5907\u4EFD\u5F53\u524D\u72B6\u6001\uFF1B",
766
1061
  "4. \u672A\u6307\u5B9A repoPath \u65F6\u4F7F\u7528\u6700\u8FD1\u6253\u5F00\u7684\u4ED3\u5E93\uFF1B",
@@ -769,7 +1064,11 @@ function createMcpServer(runtime) {
769
1064
  " \u51B2\u7A81\u884C\u4F5C\u8005\u7528 git_merge_blame\uFF08\u6309\u6587\u4EF6\u3001\u53EA\u8BFB\uFF09\u3002",
770
1065
  " \u591A\u5206\u652F\u626B\u63CF\u7528 git_merge_survey\uFF1B\u5EFA\u8BAE\u5408\u5165\u987A\u5E8F\u7528 git_merge_order\u3002",
771
1066
  "6. \u843D\u76D8\u7528 git_apply_resolve\uFF08\u72EC\u7ACB worktree\uFF0C\u4E3B\u533A\u4E0D\u5207\u6362\uFF09\uFF1B\u51B2\u7A81\u65F6\u628A\u9009\u8FB9\u540E\u7684 files \u4E00\u5E76\u4F20\u5165\u3002",
772
- "7. \u5F00 PR/MR \u7528 git_mr_prepare / git_mr_create\u3002\u65B9\u5F0F\u4E0E Token \u5728\u8BBE\u7F6E MR \u914D\u7F6E\uFF08\u4E0D\u8FDB\u5DE5\u5177\u53C2\u6570\uFF09\uFF1A\u672C\u673A gh\xB7glab / Token / \u6D4F\u89C8\u5668\u9875\u3002\u627E\u4E0D\u5230 CLI \u65F6\u7ED3\u679C\u542B\u5B98\u65B9\u5B89\u88C5\u5730\u5740\u3002"
1067
+ "7. \u5DE5\u4F5C\u533A\u5DF2\u7ECF\u51B2\u7A81\uFF1Agit_merge_continue / git_rebase_continue\uFF08\u53EF\u5E26 files\uFF09\u6216 abort\uFF1B\u4E0D\u662F apply_resolve\u3002",
1068
+ "8. \u66F4\u65B0\u8FDC\u7A0B\u8DDF\u8E2A\u5206\u652F\u7528 git_fetch\u3002\u4E24\u5206\u652F\u5206\u53C9\u5BF9\u6BD4\u7528 git_branch_graph \u5E76\u4F20 into/from\u3002",
1069
+ "9. \u5F00 PR/MR \u7528 git_mr_prepare / git_mr_create\u3002\u65B9\u5F0F\u4E0E Token \u5728\u8BBE\u7F6E MR \u914D\u7F6E\uFF08\u4E0D\u8FDB\u5DE5\u5177\u53C2\u6570\uFF09\uFF1A\u672C\u673A gh\xB7glab / Token / \u6D4F\u89C8\u5668\u9875\u3002\u627E\u4E0D\u5230 CLI \u65F6\u7ED3\u679C\u542B\u5B98\u65B9\u5B89\u88C5\u5730\u5740\u3002",
1070
+ "10. \u5927\u7ED3\u679C\u9ED8\u8BA4\u6458\u8981\uFF1Agit_diff / git_show / git_merge_rehearse \u8981\u6B63\u6587\u8BF7\u52A0 path \u6216 detail=true\u3002",
1071
+ "11. \u5DF2\u6253\u5F00\u4ED3\u5E93\u8109\u640F\u7528 git_repo_overview\u3002\u957F\u4EFB\u52A1\u7528 git_job_list / git_job_get / git_job_cancel\u3002"
773
1072
  ].join("\n")
774
1073
  }
775
1074
  );
@@ -841,8 +1140,10 @@ var McpHttpHandler = class {
841
1140
  };
842
1141
 
843
1142
  export {
844
- executeTool,
1143
+ collectRepoOverviews,
1144
+ version,
845
1145
  formatResultForMcp,
1146
+ executeTool,
846
1147
  TOOL_DEFS,
847
1148
  TOOL_DEF_MAP,
848
1149
  toolSummaries,
@@ -851,4 +1152,4 @@ export {
851
1152
  startMcpStdio,
852
1153
  McpHttpHandler
853
1154
  };
854
- //# sourceMappingURL=chunk-R2D6JZ6H.js.map
1155
+ //# sourceMappingURL=chunk-4LUNMJTB.js.map