dsh-github-flow 0.6.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +102 -15
- package/README.md +105 -15
- package/docs/UPGRADE-NOTES.md +12 -11
- package/docs/architecture.md +67 -14
- package/lib/api/route-helpers.d.ts +19 -0
- package/lib/api/route-helpers.js +54 -0
- package/lib/api/routes-actions.js +126 -2
- package/lib/api/routes-lifecycle.js +8 -1
- package/lib/api/routes-overview.d.ts +26 -0
- package/lib/api/routes-overview.js +276 -34
- package/lib/api/routes-proxy.js +8 -1
- package/lib/api/routes.d.ts +11 -1
- package/lib/api/routes.js +15 -2
- package/lib/cache.d.ts +0 -21
- package/lib/cache.js +0 -37
- package/lib/client.d.ts +2 -1
- package/lib/client.js +2108 -658
- package/lib/destructive.d.ts +59 -0
- package/lib/destructive.js +285 -0
- package/lib/executor/args-guard.d.ts +17 -0
- package/lib/executor/args-guard.js +23 -0
- package/lib/executor/failure-anchors.d.ts +52 -0
- package/lib/executor/failure-anchors.js +116 -0
- package/lib/executor/pr-review.d.ts +95 -0
- package/lib/executor/pr-review.js +175 -0
- package/lib/executor/run-log.d.ts +36 -0
- package/lib/executor/run-log.js +78 -0
- package/lib/executor.d.ts +204 -3
- package/lib/executor.js +670 -28
- package/lib/github-service.d.ts +1 -1
- package/lib/main.d.ts +92 -18
- package/lib/main.js +186 -95
- package/lib/renderers/common.d.ts +0 -3
- package/lib/renderers/common.js +8 -44
- package/lib/renderers/markdown.d.ts +1 -0
- package/lib/renderers/markdown.js +1 -0
- package/lib/renderers/pr.js +26 -1
- package/lib/renderers/task.d.ts +9 -0
- package/lib/renderers/task.js +120 -0
- package/lib/review-rules.d.ts +60 -0
- package/lib/review-rules.js +204 -0
- package/lib/shared/state-badges.d.ts +42 -0
- package/lib/shared/state-badges.js +102 -0
- package/lib/task-primitives.d.ts +109 -0
- package/lib/task-primitives.js +194 -0
- package/lib/tool-tiers.d.ts +57 -0
- package/lib/tool-tiers.js +168 -0
- package/lib/tools/agent-task.d.ts +4 -0
- package/lib/tools/agent-task.js +22 -9
- package/lib/tools/api.d.ts +4 -0
- package/lib/tools/api.js +48 -12
- package/lib/tools/body-input.d.ts +58 -0
- package/lib/tools/body-input.js +123 -0
- package/lib/tools/cache.js +42 -40
- package/lib/tools/codespace.js +4 -12
- package/lib/tools/discussion.d.ts +4 -0
- package/lib/tools/discussion.js +33 -17
- package/lib/tools/gist.d.ts +1 -39
- package/lib/tools/gist.js +4 -14
- package/lib/tools/help.js +65 -13
- package/lib/tools/issue.d.ts +4 -0
- package/lib/tools/issue.js +37 -38
- package/lib/tools/label.js +4 -15
- package/lib/tools/pr.d.ts +51 -0
- package/lib/tools/pr.js +204 -46
- package/lib/tools/project.js +4 -13
- package/lib/tools/release.d.ts +4 -0
- package/lib/tools/release.js +26 -26
- package/lib/tools/repo.js +4 -4
- package/lib/tools/ruleset.js +9 -6
- package/lib/tools/run.js +76 -24
- package/lib/tools/secret.js +11 -17
- package/lib/tools/skill.js +18 -22
- package/lib/tools/status.js +4 -4
- package/lib/tools/task.d.ts +223 -0
- package/lib/tools/task.js +464 -0
- package/lib/types.d.ts +183 -1
- package/lib/ui/commit-modal.js +108 -4
- package/lib/ui/diff-utils.d.ts +51 -0
- package/lib/ui/diff-utils.js +155 -0
- package/lib/ui/file-diff-modal.d.ts +33 -0
- package/lib/ui/file-diff-modal.js +250 -0
- package/lib/ui/help-modal.d.ts +1 -1
- package/lib/ui/help-modal.js +24 -12
- package/lib/ui/issue-modal.js +7 -3
- package/lib/ui/pr-modal.js +50 -11
- package/lib/ui/run-modal.d.ts +26 -0
- package/lib/ui/run-modal.js +486 -0
- package/lib/ui/styles.d.ts +1 -1
- package/lib/ui/styles.js +376 -44
- package/lib/ui/sync-settings-modal.js +1 -1
- package/lib/ui/workspace-panel.js +78 -23
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +9 -2
package/lib/tools/pr.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { wrapWithGate } from '../destructive.js';
|
|
1
2
|
import { compareGhVersions } from '../executor.js';
|
|
2
3
|
import { renderPrMarkdown } from '../renderers/markdown.js';
|
|
4
|
+
import { assertPositionArg } from '../executor/args-guard.js';
|
|
3
5
|
import { extractImageUrls, downloadImages, buildImageBlocks, filterRequestedUrls, validateAttachPaths } from './image-utils.js';
|
|
6
|
+
import { readReviewRules, summarizeSeverity } from '../review-rules.js';
|
|
7
|
+
import { resolveBodySource } from './body-input.js';
|
|
4
8
|
function normalizeOutput(data) {
|
|
5
9
|
if (data && typeof data === 'object' && !Array.isArray(data)) {
|
|
6
10
|
return data;
|
|
@@ -13,7 +17,7 @@ function normalizeOutput(data) {
|
|
|
13
17
|
// 共享图片工具链的同名再导出:保持路径与单测稳定
|
|
14
18
|
export { extractImageUrls as extractPrImageUrls, validateAttachPaths } from './image-utils.js';
|
|
15
19
|
export function createPrTool(executor) {
|
|
16
|
-
return {
|
|
20
|
+
return wrapWithGate({
|
|
17
21
|
name: 'github_pr',
|
|
18
22
|
description: '管理与审查 GitHub Pull Request (PR)。支持查看列表、详情、差异比对、CI 检查状态、代码审查、合并操作、本地代码检出 (checkout) 与冲突状态检测 (status)。默认针对当前工作区关联的仓库。',
|
|
19
23
|
parameters: {
|
|
@@ -22,7 +26,7 @@ export function createPrTool(executor) {
|
|
|
22
26
|
action: {
|
|
23
27
|
type: 'string',
|
|
24
28
|
enum: ['list', 'view', 'create', 'diff', 'checks', 'review', 'merge', 'checkout', 'status', 'view_images', 'ready', 'revert', 'update_branch', 'comment', 'edit'],
|
|
25
|
-
description: 'PR 操作类型。view_images 下载正文与评论中的图片到本地;create/edit/comment 支持 attach_images 附件上传;merge/revert 需 confirm: true。',
|
|
29
|
+
description: 'PR 操作类型。view_images 下载正文与评论中的图片到本地;create/edit/comment 支持 attach_images 附件上传;merge/revert 以及 checkout 的 force 需 confirm: true。',
|
|
26
30
|
},
|
|
27
31
|
pr_number: {
|
|
28
32
|
type: 'number',
|
|
@@ -38,7 +42,11 @@ export function createPrTool(executor) {
|
|
|
38
42
|
},
|
|
39
43
|
body: {
|
|
40
44
|
type: 'string',
|
|
41
|
-
description: '创建 PR 或提交 Review
|
|
45
|
+
description: '创建 PR 或提交 Review 时的内容描述(create, comment, edit, review 时使用)。与 body_file 二选一。',
|
|
46
|
+
},
|
|
47
|
+
body_file: {
|
|
48
|
+
type: 'string',
|
|
49
|
+
description: '正文来源:本地 Markdown 文件路径(相对路径按当前工作区解析)。与 body 二选一;长正文用本参数,无需把全文读进上下文。',
|
|
42
50
|
},
|
|
43
51
|
base: {
|
|
44
52
|
type: 'string',
|
|
@@ -57,6 +65,33 @@ export function createPrTool(executor) {
|
|
|
57
65
|
enum: ['APPROVE', 'REQUEST_CHANGES', 'COMMENT'],
|
|
58
66
|
description: '代码审查意见判定(review 操作时使用)。',
|
|
59
67
|
},
|
|
68
|
+
/**
|
|
69
|
+
* 行级评注(review 动作使用)。
|
|
70
|
+
*
|
|
71
|
+
* 为什么必须单独一个参数而不是塞进 body:`gh pr review` 没有行级参数
|
|
72
|
+
* (`gh pr review --help` 里 line/side/path 零命中),只能走
|
|
73
|
+
* `POST /pulls/{n}/reviews` 的 `comments[]`。一次 review 一个请求 =
|
|
74
|
+
* 通知只发一轮,也避开 secondary rate limit 的「创建内容过快」。
|
|
75
|
+
*/
|
|
76
|
+
comments: {
|
|
77
|
+
type: 'array',
|
|
78
|
+
items: {
|
|
79
|
+
type: 'object',
|
|
80
|
+
properties: {
|
|
81
|
+
path: { type: 'string', description: '文件相对路径,必须出现在该 PR 的变动文件清单里。' },
|
|
82
|
+
line: { type: 'number', description: '文件内的行号(新侧)。指向未变更行会被 GitHub 拒绝并自动降级为 body 段落。' },
|
|
83
|
+
side: { type: 'string', enum: ['LEFT', 'RIGHT'], description: '评论所在侧,默认 RIGHT(新代码)。' },
|
|
84
|
+
start_line: { type: 'number', description: '多行评论的起始行(可选,需与 line 同侧)。' },
|
|
85
|
+
body: { type: 'string', description: '评注正文。缺省会被补上 [P2] 严重度前缀。' },
|
|
86
|
+
},
|
|
87
|
+
required: ['path', 'body'],
|
|
88
|
+
},
|
|
89
|
+
description: '行级评注列表(review 动作使用,上限 50 条)。传入即走 REST reviews 通道;不传则保持原 gh pr review 行为不变。',
|
|
90
|
+
},
|
|
91
|
+
commit_id: {
|
|
92
|
+
type: 'string',
|
|
93
|
+
description: '可选:把 review 钉在指定 commit SHA 上(默认取 PR 当前 head)。分支会移动,需要精确锚定时传 headRefOid。',
|
|
94
|
+
},
|
|
60
95
|
merge_method: {
|
|
61
96
|
type: 'string',
|
|
62
97
|
enum: ['merge', 'squash', 'rebase'],
|
|
@@ -64,7 +99,11 @@ export function createPrTool(executor) {
|
|
|
64
99
|
},
|
|
65
100
|
delete_branch: {
|
|
66
101
|
type: 'boolean',
|
|
67
|
-
description: '
|
|
102
|
+
description: '合并后是否删除源分支(仅 merge 动作生效,默认 false)。gh pr revert 没有该 flag,传了也不会生效。删除分支不可撤销,只在确认该分支已无用后再开启。',
|
|
103
|
+
},
|
|
104
|
+
force: {
|
|
105
|
+
type: 'boolean',
|
|
106
|
+
description: 'checkout 动作可选:是否强制重置已存在的本地同名分支(映射 gh pr checkout -f)。会丢弃本地未推送提交,必须同时传 confirm: true。默认 false——本地分支与 PR 头已分叉时检出会失败,而不是静默重置。',
|
|
68
107
|
},
|
|
69
108
|
undo: {
|
|
70
109
|
type: 'boolean',
|
|
@@ -85,11 +124,11 @@ export function createPrTool(executor) {
|
|
|
85
124
|
},
|
|
86
125
|
stat_only: {
|
|
87
126
|
type: 'boolean',
|
|
88
|
-
description: '是否仅输出文件变动概览(diff
|
|
127
|
+
description: '是否仅输出文件变动概览(diff 动作使用,改走 gh pr view --json files,防止大 PR 爆 Token)。',
|
|
89
128
|
},
|
|
90
129
|
confirm: {
|
|
91
130
|
type: 'boolean',
|
|
92
|
-
description: '高危破坏性操作确认标志(merge
|
|
131
|
+
description: '高危破坏性操作确认标志(merge 动作、delete_branch、或 checkout 且 force: true 时必需)。若为 false 则仅输出影响评估并拦截。',
|
|
93
132
|
},
|
|
94
133
|
dry_run: {
|
|
95
134
|
type: 'boolean',
|
|
@@ -115,8 +154,11 @@ export function createPrTool(executor) {
|
|
|
115
154
|
},
|
|
116
155
|
},
|
|
117
156
|
async execute(args, execContext) {
|
|
118
|
-
|
|
119
|
-
|
|
157
|
+
// 位置参数守卫:pr_number 会作为 `gh pr <cmd> <number>` 的位置参数进入 argv,
|
|
158
|
+
// `--force` 这类值会被 gh 当成选项而不是编号(复核报告 C-01b)。
|
|
159
|
+
// 放在入口一次校验即可覆盖全部 action,避免 11 处各自写一份。
|
|
160
|
+
assertPositionArg(args.pr_number, 'PR 编号');
|
|
161
|
+
const { cwd, sessionId } = executor.resolveExecScope(execContext);
|
|
120
162
|
let repoTarget = args.repo;
|
|
121
163
|
if (!repoTarget) {
|
|
122
164
|
const meta = await executor.getRepoMetadata(cwd, sessionId);
|
|
@@ -129,6 +171,8 @@ export function createPrTool(executor) {
|
|
|
129
171
|
}
|
|
130
172
|
const repoArgs = ['-R', repoTarget];
|
|
131
173
|
const runOpts = { cwd, sessionId };
|
|
174
|
+
// 正文来自本地文件时经 stdin 注入(`--body-file -`):绕开 Windows 约 32K 的命令行上限
|
|
175
|
+
const stdinPatch = (body) => (body.stdin === undefined ? {} : { stdin: body.stdin });
|
|
132
176
|
const appendAttachFlags = async (cmd, attachImages) => {
|
|
133
177
|
const attachPaths = validateAttachPaths(attachImages, cwd);
|
|
134
178
|
if (attachPaths.length > 0) {
|
|
@@ -160,7 +204,10 @@ export function createPrTool(executor) {
|
|
|
160
204
|
}
|
|
161
205
|
case 'view': {
|
|
162
206
|
const target = args.pr_number ? [String(args.pr_number)] : [];
|
|
163
|
-
const res = await executor.run(
|
|
207
|
+
const res = await executor.run(
|
|
208
|
+
// headRefOid/baseRefOid:分支会移动,Agent 侧也要能「钉在某个 commit 上」审
|
|
209
|
+
// changedFiles + files:清单在 100 条处静默截断,必须成对返回才能判断完整性
|
|
210
|
+
['pr', 'view', ...target, ...repoArgs, '--json', 'number,title,body,state,author,labels,assignees,reviewDecision,mergeable,statusCheckRollup,url,comments,headRefName,baseRefName,headRefOid,baseRefOid,changedFiles,files'], runOpts);
|
|
164
211
|
if (!res.ok)
|
|
165
212
|
throw new Error(res.error);
|
|
166
213
|
const out = normalizeOutput(res.data || res.rawOutput);
|
|
@@ -174,6 +221,19 @@ export function createPrTool(executor) {
|
|
|
174
221
|
}
|
|
175
222
|
}
|
|
176
223
|
out.images = Array.from(new Set(bodies.flatMap((b) => extractImageUrls(b))));
|
|
224
|
+
/*
|
|
225
|
+
* 仓库审查规则注入(AGENTS.md)。
|
|
226
|
+
*
|
|
227
|
+
* 触发条件明文:仅在 view/review 动作、且工作区确实存在 AGENTS.md 时注入。
|
|
228
|
+
* 读不到就**不加这个键**(不是给个空对象)——这样「没有规则」与「规则为空」
|
|
229
|
+
* 在输出里可区分,Agent 不会误以为仓库定义了空规则。
|
|
230
|
+
*/
|
|
231
|
+
const changedPaths = Array.isArray(out.files)
|
|
232
|
+
? out.files.map((f) => (f && typeof f.path === 'string' ? f.path : '')).filter(Boolean)
|
|
233
|
+
: [];
|
|
234
|
+
const rules = readReviewRules(cwd, changedPaths);
|
|
235
|
+
if (rules)
|
|
236
|
+
out.reviewRules = rules;
|
|
177
237
|
return out;
|
|
178
238
|
}
|
|
179
239
|
case 'view_images': {
|
|
@@ -242,11 +302,36 @@ export function createPrTool(executor) {
|
|
|
242
302
|
}
|
|
243
303
|
case 'diff': {
|
|
244
304
|
const target = args.pr_number ? [String(args.pr_number)] : [];
|
|
245
|
-
|
|
305
|
+
// gh pr diff 不提供 --stat(实测 gh 2.96.0 直接 unknown flag / exit 1)。
|
|
306
|
+
// 文件变更概览的合法来源是 pr view --json files(字段含 path/additions/deletions)。
|
|
307
|
+
// 返回值保持 { result: string } 形状——renderPrMarkdown 对 result 字符串直出
|
|
308
|
+
// (src/renderers/pr.ts:15),因此这条路渲染层零改动。
|
|
246
309
|
if (args.stat_only === true) {
|
|
247
|
-
|
|
310
|
+
const statRes = await executor.run(['pr', 'view', ...target, ...repoArgs, '--json', 'files,additions,deletions'], runOpts);
|
|
311
|
+
if (!statRes.ok)
|
|
312
|
+
throw new Error(statRes.error);
|
|
313
|
+
const detail = statRes.data || {};
|
|
314
|
+
// executor.run 在输出被 24KB 阈值截断时会跳过 JSON.parse(executor.ts:341),
|
|
315
|
+
// 此时 data 为空、rawOutput 是被切断的半截 JSON。绝不能把「没解析出来」说成「0 个文件」。
|
|
316
|
+
if (!Array.isArray(detail.files)) {
|
|
317
|
+
return {
|
|
318
|
+
result: '未能解析文件变更清单:gh 输出可能被 24KB 截断阈值切断。请改用 github_pr 的完整 diff 动作,或缩小范围后重试。',
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
const rows = detail.files;
|
|
322
|
+
// 只说是"本次取到的文件":gh 的 --json files 在超过 100 个文件时静默截断
|
|
323
|
+
// (cli/cli#12522),不能对外声称是完整清单。
|
|
324
|
+
const cell = (v) => String(v ?? '').replace(/\|/g, '\\|');
|
|
325
|
+
const lines = [
|
|
326
|
+
`本次取到 ${rows.length} 个变更文件(+${detail.additions ?? 0} / -${detail.deletions ?? 0})`,
|
|
327
|
+
'',
|
|
328
|
+
'| 文件 | 增 | 删 |',
|
|
329
|
+
'| :--- | ---: | ---: |',
|
|
330
|
+
...rows.map((f) => `| ${cell(f.path)} | ${f.additions ?? 0} | ${f.deletions ?? 0} |`),
|
|
331
|
+
];
|
|
332
|
+
return { result: lines.join('\n') };
|
|
248
333
|
}
|
|
249
|
-
const res = await executor.run(
|
|
334
|
+
const res = await executor.run(['pr', 'diff', ...target, ...repoArgs], runOpts);
|
|
250
335
|
if (!res.ok)
|
|
251
336
|
throw new Error(res.error);
|
|
252
337
|
return { result: res.rawOutput || '无差异内容' };
|
|
@@ -254,10 +339,37 @@ export function createPrTool(executor) {
|
|
|
254
339
|
case 'checkout': {
|
|
255
340
|
if (!args.pr_number)
|
|
256
341
|
throw new Error('checkout 操作必须提供 pr_number');
|
|
257
|
-
|
|
342
|
+
// 防线:检出 PR 会切本地分支,与界面切分支同源风险,必须过同一道闸门。
|
|
343
|
+
// 这里用硬阻断而不是 confirm 二次确认——没有哪个参数能让「脏工作区检出」变安全,
|
|
344
|
+
// 给个 confirm 只会变成一个绕不过去的死循环。
|
|
345
|
+
const guard = await executor.ensureCleanWorkspace(cwd);
|
|
346
|
+
if (!guard.ok) {
|
|
347
|
+
const sample = guard.samples.length
|
|
348
|
+
? `\n涉及文件(前 ${guard.samples.length} 条):\n${guard.samples.map((s) => ' ' + s).join('\n')}`
|
|
349
|
+
: '';
|
|
350
|
+
throw new Error(`${guard.error}${sample}\n请先提交或 stash 后再检出 PR。`);
|
|
351
|
+
}
|
|
352
|
+
const checkoutCmd = ['pr', 'checkout', String(args.pr_number), ...repoArgs];
|
|
353
|
+
// 默认不带 -f。-f 的语义是 Reset the existing local branch to the latest state
|
|
354
|
+
// of the pull request(等价 reset --hard,丢弃本地未推送提交)。
|
|
355
|
+
// 不带 -f 时,若本地已有同名分支,gh 的行为是 `git checkout <branch>` 再
|
|
356
|
+
// `git merge --ff-only <remote>`——只有「已分叉」才失败,不会静默重置。
|
|
357
|
+
// 跨 fork 且本地无 remote 时走 `git fetch <ref>:<branch>`(不带 --force),
|
|
358
|
+
// 分叉同样会失败。因此默认路径是安全的,真要重置必须 force + confirm 双门。
|
|
359
|
+
if (args.force === true) {
|
|
360
|
+
checkoutCmd.push('-f');
|
|
361
|
+
}
|
|
258
362
|
const res = await executor.run(checkoutCmd, runOpts);
|
|
259
|
-
if (!res.ok)
|
|
260
|
-
|
|
363
|
+
if (!res.ok) {
|
|
364
|
+
// gh 在这里抛的是裸 git 错误,对用户和模型都没有"下一步"线索。
|
|
365
|
+
// 「PR 被 rebase / force-push 后重新检出」是常见路径,必须给出可操作选项。
|
|
366
|
+
const raw = String(res.error || '');
|
|
367
|
+
if (/fast-forward|non-fast-forward|rejected/i.test(raw)) {
|
|
368
|
+
throw new Error(`本地分支与 PR #${args.pr_number} 的头已分叉,无法快进检出。\n${raw}\n` +
|
|
369
|
+
'如确认用 PR 头覆盖本地分支(会丢弃本地未推送提交),请附加 `force: true, confirm: true` 重新调用。');
|
|
370
|
+
}
|
|
371
|
+
throw new Error(raw);
|
|
372
|
+
}
|
|
261
373
|
return { message: `已成功将 PR #${args.pr_number} 检出至本地分支`, output: res.rawOutput || 'Checked out' };
|
|
262
374
|
}
|
|
263
375
|
case 'status': {
|
|
@@ -268,7 +380,10 @@ export function createPrTool(executor) {
|
|
|
268
380
|
return normalizeOutput(res.data || res.rawOutput);
|
|
269
381
|
}
|
|
270
382
|
case 'create': {
|
|
271
|
-
|
|
383
|
+
// keepEmptyInline:无正文时仍要下发 `--body ''`,否则 gh 会打开编辑器等交互输入,
|
|
384
|
+
// 在非 TTY 环境下直接挂起。正文来源与位置保持在 `--title` 之后,老调用 argv 逐字节不变。
|
|
385
|
+
const body = resolveBodySource({ inline: args.body, file: args.body_file }, { cwd, keepEmptyInline: true });
|
|
386
|
+
const createCmd = ['pr', 'create', ...repoArgs, '--title', args.title || '', ...body.argv];
|
|
272
387
|
if (args.base)
|
|
273
388
|
createCmd.push('--base', args.base);
|
|
274
389
|
// 跨 fork 提 PR 必须显式给出 --head(形如 forkOwner:branch)。
|
|
@@ -282,7 +397,7 @@ export function createPrTool(executor) {
|
|
|
282
397
|
if (dryRun)
|
|
283
398
|
createCmd.push('--dry-run');
|
|
284
399
|
await appendAttachFlags(createCmd, args.attach_images);
|
|
285
|
-
const res = await executor.run(createCmd, runOpts);
|
|
400
|
+
const res = await executor.run(createCmd, { ...runOpts, ...stdinPatch(body) });
|
|
286
401
|
if (!res.ok)
|
|
287
402
|
throw new Error(res.error);
|
|
288
403
|
return dryRun
|
|
@@ -292,6 +407,61 @@ export function createPrTool(executor) {
|
|
|
292
407
|
case 'review': {
|
|
293
408
|
if (!args.pr_number)
|
|
294
409
|
throw new Error('review 操作必须提供 pr_number');
|
|
410
|
+
/*
|
|
411
|
+
* 正文在分支判定之前解析:带 comments 时走的是 REST reviews 通道,那条路没有任何
|
|
412
|
+
* `--body-file` 可用,正文只能由插件读出来塞进 JSON。若把解析放在 gh 分支里,
|
|
413
|
+
* 「带 comments + body_file」就会静默丢正文——所以两条通道共用这一份结果。
|
|
414
|
+
*/
|
|
415
|
+
const body = resolveBodySource({ inline: args.body, file: args.body_file }, { cwd });
|
|
416
|
+
/*
|
|
417
|
+
* 行级评注分支:只有显式传了非空 comments 才走 REST reviews 通道。
|
|
418
|
+
* 不传 → 下面的原 gh pr review 路径一字不动(默认行为零变化,这是本改动的
|
|
419
|
+
* 首要约束:老调用方的 argv 必须逐字节一致)。
|
|
420
|
+
*/
|
|
421
|
+
const lineComments = Array.isArray(args.comments) ? args.comments : [];
|
|
422
|
+
if (lineComments.length > 0) {
|
|
423
|
+
const res = await executor.createPrReview(cwd, args.repo, args.pr_number, {
|
|
424
|
+
event: args.review_decision,
|
|
425
|
+
// createPrReview 侧按 trim 归一,空串与 undefined 等价,直接给权威正文即可
|
|
426
|
+
body: body.text,
|
|
427
|
+
comments: lineComments,
|
|
428
|
+
commit_id: args.commit_id,
|
|
429
|
+
});
|
|
430
|
+
if (!res.ok)
|
|
431
|
+
throw new Error(res.error || 'Review 提交失败');
|
|
432
|
+
/*
|
|
433
|
+
* 严重度分布汇总(plan §2.3 item 14)。
|
|
434
|
+
*
|
|
435
|
+
* 为什么在工具层算:`normalizeSeverity` 幂等,这里对**入参**统计与对
|
|
436
|
+
* 已提交正文统计等价;而在工具层算不必让 executor 回传审计字段。
|
|
437
|
+
* 目的是让调用方一眼看到「这轮意见里有没有 P0」,而不是自己数。
|
|
438
|
+
*/
|
|
439
|
+
const severity = summarizeSeverity([body.text, ...lineComments.map((c) => (c && typeof c.body === 'string' ? c.body : ''))].filter((b) => typeof b === 'string' && b.trim()));
|
|
440
|
+
const lines = [
|
|
441
|
+
res.degraded
|
|
442
|
+
? `⚠️ Review 已提交(行级评注降级为正文段落)`
|
|
443
|
+
: `✅ Review 已提交,含 ${lineComments.length} 条行级评注`,
|
|
444
|
+
];
|
|
445
|
+
if (res.data?.htmlUrl)
|
|
446
|
+
lines.push(`- 地址: ${res.data.htmlUrl}`);
|
|
447
|
+
if (res.data?.state)
|
|
448
|
+
lines.push(`- 判定: ${res.data.state}`);
|
|
449
|
+
lines.push(`- 严重度: P0×${severity.P0} / P1×${severity.P1} / P2×${severity.P2}`);
|
|
450
|
+
if (Array.isArray(res.data?.comments) && res.data.comments.length > 0) {
|
|
451
|
+
lines.push('- 已挂载的行级评注:');
|
|
452
|
+
for (const c of res.data.comments) {
|
|
453
|
+
lines.push(` - \`${c.path}${c.line ? ':' + c.line : ''}\`${c.htmlUrl ? ' → ' + c.htmlUrl : ''}`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if (res.degraded && res.degradedReason)
|
|
457
|
+
lines.push(`\n> ⚠️ ${res.degradedReason}`);
|
|
458
|
+
return {
|
|
459
|
+
result: lines.join('\n'),
|
|
460
|
+
severity,
|
|
461
|
+
degraded: res.degraded === true,
|
|
462
|
+
degradedReason: res.degradedReason,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
295
465
|
const reviewCmd = ['pr', 'review', String(args.pr_number), ...repoArgs];
|
|
296
466
|
if (args.review_decision === 'APPROVE')
|
|
297
467
|
reviewCmd.push('--approve');
|
|
@@ -299,9 +469,9 @@ export function createPrTool(executor) {
|
|
|
299
469
|
reviewCmd.push('--request-changes');
|
|
300
470
|
else
|
|
301
471
|
reviewCmd.push('--comment');
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
const res = await executor.run(reviewCmd, runOpts);
|
|
472
|
+
// 位置与旧代码一致:`--body` 紧跟判定 flag,老调用 argv 逐字节不变
|
|
473
|
+
reviewCmd.push(...body.argv);
|
|
474
|
+
const res = await executor.run(reviewCmd, { ...runOpts, ...stdinPatch(body) });
|
|
305
475
|
if (!res.ok)
|
|
306
476
|
throw new Error(res.error);
|
|
307
477
|
return { message: 'Review 提交成功', detail: res.rawOutput };
|
|
@@ -309,14 +479,6 @@ export function createPrTool(executor) {
|
|
|
309
479
|
case 'merge': {
|
|
310
480
|
if (!args.pr_number)
|
|
311
481
|
throw new Error('merge 操作必须提供 pr_number');
|
|
312
|
-
if (args.confirm !== true) {
|
|
313
|
-
return {
|
|
314
|
-
status: 'AWAITING_CONFIRMATION',
|
|
315
|
-
warning: `⚠️ 高危操作拦截:你即将合并 PR #${args.pr_number}(合并策略: ${args.merge_method || 'squash'}${args.delete_branch ? ',且删除源分支' : ''})。`,
|
|
316
|
-
impact: '合并操作将直接向基准分支写入代码并更新远端 PR 状态。',
|
|
317
|
-
requiredAction: '如确认执行合并,请向用户明确确认并附加 `confirm: true` 重新调用。',
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
482
|
const method = args.merge_method === 'merge' ? '--merge' : (args.merge_method === 'rebase' ? '--rebase' : '--squash');
|
|
321
483
|
const mergeCmd = ['pr', 'merge', String(args.pr_number), ...repoArgs, method];
|
|
322
484
|
// 删分支默认关闭:合并他人仓库的 PR 时删掉源分支是不可撤销的破坏性动作,
|
|
@@ -345,17 +507,9 @@ export function createPrTool(executor) {
|
|
|
345
507
|
if (!args.pr_number)
|
|
346
508
|
throw new Error('revert 操作必须提供 pr_number');
|
|
347
509
|
// revert 在远端创建反向提交与新 PR——虽不碰原 PR,但产生新实体,按 merge 级 confirm(D5)
|
|
348
|
-
if (args.confirm !== true) {
|
|
349
|
-
return {
|
|
350
|
-
status: 'AWAITING_CONFIRMATION',
|
|
351
|
-
warning: `⚠️ 高危操作拦截:你即将对 PR #${args.pr_number} 创建反向提交(revert)。`,
|
|
352
|
-
impact: '将在远端创建反向提交并生成新的 revert PR——这是新增远端实体,不是本地操作。',
|
|
353
|
-
requiredAction: '如确认执行,请向用户明确确认并附加 `confirm: true` 重新调用。',
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
510
|
const revertCmd = ['pr', 'revert', String(args.pr_number), ...repoArgs];
|
|
357
|
-
|
|
358
|
-
|
|
511
|
+
// 注意:gh pr revert 没有 --delete-branch(实测 2.96.0 unknown flag),
|
|
512
|
+
// 不在这里推该 flag;删分支只在 merge 通道支持。
|
|
359
513
|
const res = await executor.run(revertCmd, runOpts);
|
|
360
514
|
if (!res.ok)
|
|
361
515
|
throw new Error(res.error);
|
|
@@ -372,11 +526,15 @@ export function createPrTool(executor) {
|
|
|
372
526
|
return { message: `PR #${args.pr_number} 分支已从基准分支同步更新`, output: res.rawOutput };
|
|
373
527
|
}
|
|
374
528
|
case 'comment': {
|
|
375
|
-
if (!args.pr_number
|
|
376
|
-
throw new Error('comment
|
|
377
|
-
const
|
|
529
|
+
if (!args.pr_number)
|
|
530
|
+
throw new Error('comment 操作必须提供 pr_number');
|
|
531
|
+
const body = resolveBodySource({ inline: args.body, file: args.body_file }, { cwd });
|
|
532
|
+
// 判空看解析后的权威正文字符数:文件来源时 `args.body` 恒为空,不能拿它做必填判定
|
|
533
|
+
if (body.chars === 0)
|
|
534
|
+
throw new Error('comment 必须提供 body 或 body_file(评论正文)');
|
|
535
|
+
const commentCmd = ['pr', 'comment', String(args.pr_number), ...repoArgs, ...body.argv];
|
|
378
536
|
await appendAttachFlags(commentCmd, args.attach_images);
|
|
379
|
-
const res = await executor.run(commentCmd, runOpts);
|
|
537
|
+
const res = await executor.run(commentCmd, { ...runOpts, ...stdinPatch(body) });
|
|
380
538
|
if (!res.ok)
|
|
381
539
|
throw new Error(res.error);
|
|
382
540
|
return { message: 'PR 评论发布成功', output: res.rawOutput };
|
|
@@ -384,13 +542,13 @@ export function createPrTool(executor) {
|
|
|
384
542
|
case 'edit': {
|
|
385
543
|
if (!args.pr_number)
|
|
386
544
|
throw new Error('edit 操作必须提供 pr_number');
|
|
545
|
+
const body = resolveBodySource({ inline: args.body, file: args.body_file }, { cwd });
|
|
387
546
|
const editCmd = ['pr', 'edit', String(args.pr_number), ...repoArgs];
|
|
388
547
|
if (args.title)
|
|
389
548
|
editCmd.push('--title', args.title);
|
|
390
|
-
|
|
391
|
-
editCmd.push('--body', args.body);
|
|
549
|
+
editCmd.push(...body.argv);
|
|
392
550
|
await appendAttachFlags(editCmd, args.attach_images);
|
|
393
|
-
const res = await executor.run(editCmd, runOpts);
|
|
551
|
+
const res = await executor.run(editCmd, { ...runOpts, ...stdinPatch(body) });
|
|
394
552
|
if (!res.ok)
|
|
395
553
|
throw new Error(res.error);
|
|
396
554
|
return { message: `PR #${args.pr_number} 更新成功`, output: res.rawOutput };
|
|
@@ -399,5 +557,5 @@ export function createPrTool(executor) {
|
|
|
399
557
|
throw new Error(`未知的 PR 操作: ${args.action}`);
|
|
400
558
|
}
|
|
401
559
|
},
|
|
402
|
-
};
|
|
560
|
+
});
|
|
403
561
|
}
|
package/lib/tools/project.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { wrapWithGate } from '../destructive.js';
|
|
1
2
|
import { renderProjectMarkdown } from '../renderers/markdown.js';
|
|
2
3
|
function normalizeOutput(data) {
|
|
3
4
|
if (data && typeof data === 'object' && !Array.isArray(data)) {
|
|
@@ -24,7 +25,7 @@ export function checkScopeRequired(errorMsg, scope, command, tool) {
|
|
|
24
25
|
return null;
|
|
25
26
|
}
|
|
26
27
|
export function createProjectTool(executor) {
|
|
27
|
-
return {
|
|
28
|
+
return wrapWithGate({
|
|
28
29
|
name: 'github_project',
|
|
29
30
|
description: '管理 GitHub Projects v2 项目看板与任务规划:查看项目列表与卡片条目、关联 Issue/PR 到看板、创建草稿任务、删除看板条目。未授权时提供平台感知自愈命令。',
|
|
30
31
|
parameters: {
|
|
@@ -85,8 +86,7 @@ export function createProjectTool(executor) {
|
|
|
85
86
|
},
|
|
86
87
|
},
|
|
87
88
|
async execute(args, execContext) {
|
|
88
|
-
const cwd
|
|
89
|
-
const sessionId = execContext?.sessionId || execContext?.session?.id || process.env.DSH_SESSION_ID;
|
|
89
|
+
const { cwd, sessionId } = executor.resolveExecScope(execContext);
|
|
90
90
|
const owner = typeof args.owner === 'string' && args.owner.trim() ? args.owner.trim() : '@me';
|
|
91
91
|
const runOpts = { cwd, sessionId };
|
|
92
92
|
switch (args.action) {
|
|
@@ -206,15 +206,6 @@ export function createProjectTool(executor) {
|
|
|
206
206
|
throw new Error('item-delete 操作必须提供 item_id(条目唯一 ID)');
|
|
207
207
|
}
|
|
208
208
|
const cleanItemId = args.item_id.trim();
|
|
209
|
-
// 确认防线拦截
|
|
210
|
-
if (args.confirm !== true) {
|
|
211
|
-
return {
|
|
212
|
-
status: 'AWAITING_CONFIRMATION',
|
|
213
|
-
warning: `⚠️ 删除确认拦截:你即将从看板中删除条目(ID: ${cleanItemId})。`,
|
|
214
|
-
impact: '该操作将从项目看板中物理移除该条目(若是关联的 Issue/PR,实体不会被删除,仅解除看板关联)。',
|
|
215
|
-
requiredAction: '如确认删除,请在参数中附加 `confirm: true` 重新调用。',
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
209
|
const deleteCmd = [
|
|
219
210
|
'project', 'item-delete',
|
|
220
211
|
'--owner', owner,
|
|
@@ -237,5 +228,5 @@ export function createProjectTool(executor) {
|
|
|
237
228
|
throw new Error(`未知的 Project 操作: ${args.action}`);
|
|
238
229
|
}
|
|
239
230
|
},
|
|
240
|
-
};
|
|
231
|
+
});
|
|
241
232
|
}
|
package/lib/tools/release.d.ts
CHANGED
package/lib/tools/release.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { wrapWithGate } from '../destructive.js';
|
|
1
2
|
import { renderReleaseMarkdown } from '../renderers/markdown.js';
|
|
3
|
+
import { assertPositionArg } from '../executor/args-guard.js';
|
|
4
|
+
import { resolveBodySource } from './body-input.js';
|
|
2
5
|
function normalizeOutput(data) {
|
|
3
6
|
if (data && typeof data === 'object' && !Array.isArray(data)) {
|
|
4
7
|
return data;
|
|
@@ -9,7 +12,7 @@ function normalizeOutput(data) {
|
|
|
9
12
|
return { result: data !== undefined && data !== null ? data : '' };
|
|
10
13
|
}
|
|
11
14
|
export function createReleaseTool(executor) {
|
|
12
|
-
return {
|
|
15
|
+
return wrapWithGate({
|
|
13
16
|
name: 'github_release',
|
|
14
17
|
description: '管理 GitHub Releases 版本发布与构建资产:查看发版历史、根据提交自动生成发版说明、创建 Release、上传/下载发布资产(如 npm .tgz 产物)。高危操作(delete、clobber 覆盖上传)需 confirm: true。',
|
|
15
18
|
parameters: {
|
|
@@ -34,7 +37,11 @@ export function createReleaseTool(executor) {
|
|
|
34
37
|
},
|
|
35
38
|
notes: {
|
|
36
39
|
type: 'string',
|
|
37
|
-
description: 'Release 说明正文(create 动作使用)。留空且 generate_notes 为 true 时将自动由 GitHub
|
|
40
|
+
description: 'Release 说明正文(create 动作使用)。留空且 generate_notes 为 true 时将自动由 GitHub 根据提交记录生成。与 notes_file 二选一。',
|
|
41
|
+
},
|
|
42
|
+
notes_file: {
|
|
43
|
+
type: 'string',
|
|
44
|
+
description: '发版说明来源:本地 Markdown 文件路径(相对路径按当前工作区解析)。与 notes 二选一。',
|
|
38
45
|
},
|
|
39
46
|
generate_notes: {
|
|
40
47
|
type: 'boolean',
|
|
@@ -91,8 +98,9 @@ export function createReleaseTool(executor) {
|
|
|
91
98
|
},
|
|
92
99
|
},
|
|
93
100
|
async execute(args, execContext) {
|
|
94
|
-
|
|
95
|
-
|
|
101
|
+
// 位置参数守卫:tag 是 `gh release view <tag>` / `upload <tag>` 的位置参数
|
|
102
|
+
assertPositionArg(args.tag, 'Release Tag');
|
|
103
|
+
const { cwd, sessionId } = executor.resolveExecScope(execContext);
|
|
96
104
|
let repoTarget = args.repo;
|
|
97
105
|
if (!repoTarget) {
|
|
98
106
|
const meta = await executor.getRepoMetadata(cwd, sessionId);
|
|
@@ -105,6 +113,8 @@ export function createReleaseTool(executor) {
|
|
|
105
113
|
}
|
|
106
114
|
const repoArgs = ['-R', repoTarget];
|
|
107
115
|
const runOpts = { cwd, sessionId };
|
|
116
|
+
// 说明来自本地文件时经 stdin 注入(`--notes-file -`):绕开 Windows 约 32K 的命令行上限
|
|
117
|
+
const stdinPatch = (body) => (body.stdin === undefined ? {} : { stdin: body.stdin });
|
|
108
118
|
switch (args.action) {
|
|
109
119
|
case 'list': {
|
|
110
120
|
// 显式 limit 优先;缺省回退 executor.defaultListLimit(上限 50 防止全量 Release 撑爆输出)
|
|
@@ -130,14 +140,18 @@ export function createReleaseTool(executor) {
|
|
|
130
140
|
if (Array.isArray(args.assets)) {
|
|
131
141
|
for (const a of args.assets) {
|
|
132
142
|
if (typeof a === 'string' && a.trim())
|
|
133
|
-
createCmd.push(a);
|
|
143
|
+
createCmd.push(assertPositionArg(a.trim(), '资产路径'));
|
|
134
144
|
}
|
|
135
145
|
}
|
|
136
146
|
if (args.title)
|
|
137
147
|
createCmd.push('--title', args.title);
|
|
138
148
|
// notes 与 generate_notes 可共存:gh 会在自动生成的说明前 prepend 手写正文
|
|
139
|
-
|
|
140
|
-
|
|
149
|
+
const notes = resolveBodySource({ inline: args.notes, file: args.notes_file },
|
|
150
|
+
// maxChars: 0 —— Release 说明的 GitHub 上限比 Issue/评论正文(65,536)宽得多,
|
|
151
|
+
// 套用正文口径会把合法的长发版说明先拦下来;这里只保留 4MB 字节护栏,
|
|
152
|
+
// 真正超限由 GitHub 侧报错(错误信息带字段名,可定位)。
|
|
153
|
+
{ cwd, flag: '--notes', fileFlag: '--notes-file', inlineField: 'notes', fileField: 'notes_file', maxChars: 0 });
|
|
154
|
+
createCmd.push(...notes.argv);
|
|
141
155
|
if (args.generate_notes !== false)
|
|
142
156
|
createCmd.push('--generate-notes');
|
|
143
157
|
if (args.draft === true)
|
|
@@ -146,7 +160,7 @@ export function createReleaseTool(executor) {
|
|
|
146
160
|
createCmd.push('--prerelease');
|
|
147
161
|
if (args.target)
|
|
148
162
|
createCmd.push('--target', args.target);
|
|
149
|
-
const res = await executor.run(createCmd, runOpts);
|
|
163
|
+
const res = await executor.run(createCmd, { ...runOpts, ...stdinPatch(notes) });
|
|
150
164
|
if (!res.ok)
|
|
151
165
|
throw new Error(res.error);
|
|
152
166
|
return { message: `Release ${args.tag} 创建成功`, url: res.rawOutput };
|
|
@@ -157,18 +171,12 @@ export function createReleaseTool(executor) {
|
|
|
157
171
|
if (!Array.isArray(args.assets) || args.assets.length === 0) {
|
|
158
172
|
throw new Error('upload 操作必须提供 assets(文件路径列表)');
|
|
159
173
|
}
|
|
160
|
-
if (args.clobber === true && args.confirm !== true) {
|
|
161
|
-
return {
|
|
162
|
-
status: 'AWAITING_CONFIRMATION',
|
|
163
|
-
warning: `⚠️ 高危操作拦截:你即将覆盖上传 Release ${args.tag} 的同名旧资产(clobber 模式)。`,
|
|
164
|
-
impact: 'clobber 会先删除既有同名资产再上传;若上传中途失败,旧资产已丢失且无法恢复。',
|
|
165
|
-
requiredAction: '如确认覆盖,请向用户核实并附加 `confirm: true` 重新调用。',
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
174
|
const uploadCmd = ['release', 'upload', args.tag, ...repoArgs];
|
|
169
175
|
for (const a of args.assets) {
|
|
176
|
+
// 每个资产都是位置参数:`--clobber` 混进来会绕过 confirm 闸门覆盖删同名资产
|
|
177
|
+
// (复核报告 C-01a 的复现路径),必须在拼 argv 时就拒掉。
|
|
170
178
|
if (typeof a === 'string' && a.trim())
|
|
171
|
-
uploadCmd.push(a);
|
|
179
|
+
uploadCmd.push(assertPositionArg(a.trim(), '资产路径'));
|
|
172
180
|
}
|
|
173
181
|
if (args.clobber === true)
|
|
174
182
|
uploadCmd.push('--clobber');
|
|
@@ -195,14 +203,6 @@ export function createReleaseTool(executor) {
|
|
|
195
203
|
case 'delete': {
|
|
196
204
|
if (!args.tag)
|
|
197
205
|
throw new Error('delete 操作必须提供 tag');
|
|
198
|
-
if (args.confirm !== true) {
|
|
199
|
-
return {
|
|
200
|
-
status: 'AWAITING_CONFIRMATION',
|
|
201
|
-
warning: `⚠️ 高危操作拦截:你即将删除 Release ${args.tag}${args.cleanup_tag ? ' 及其 Git Tag' : ''}。`,
|
|
202
|
-
impact: '删除 Release 不可逆;若同时清理 Tag,引用该 Tag 的历史版本入口将全部失效。',
|
|
203
|
-
requiredAction: '如确认删除,请向用户核实并附加 `confirm: true` 重新调用。',
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
206
|
const delCmd = ['release', 'delete', args.tag, ...repoArgs, '--yes'];
|
|
207
207
|
if (args.cleanup_tag === true)
|
|
208
208
|
delCmd.push('--cleanup-tag');
|
|
@@ -215,5 +215,5 @@ export function createReleaseTool(executor) {
|
|
|
215
215
|
throw new Error(`未知的 Release 操作: ${args.action}`);
|
|
216
216
|
}
|
|
217
217
|
},
|
|
218
|
-
};
|
|
218
|
+
});
|
|
219
219
|
}
|
package/lib/tools/repo.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { wrapWithGate } from '../destructive.js';
|
|
1
2
|
import { renderRepoMarkdown } from '../renderers/markdown.js';
|
|
2
3
|
import { extractReadmeImageUrls, downloadImages, buildImageBlocks, filterRequestedUrls } from './image-utils.js';
|
|
3
4
|
function normalizeOutput(data) {
|
|
@@ -10,7 +11,7 @@ function normalizeOutput(data) {
|
|
|
10
11
|
return { result: data !== undefined && data !== null ? data : '' };
|
|
11
12
|
}
|
|
12
13
|
export function createRepoTool(executor) {
|
|
13
|
-
return {
|
|
14
|
+
return wrapWithGate({
|
|
14
15
|
name: 'github_repo',
|
|
15
16
|
description: '查看 GitHub 仓库元数据、同步 Fork 仓库主干 (sync) 与跨仓库检索代码。默认查询当前工作区关联的仓库。',
|
|
16
17
|
parameters: {
|
|
@@ -72,8 +73,7 @@ export function createRepoTool(executor) {
|
|
|
72
73
|
},
|
|
73
74
|
},
|
|
74
75
|
async execute(args, execContext) {
|
|
75
|
-
const cwd
|
|
76
|
-
const sessionId = execContext?.sessionId || execContext?.session?.id || process.env.DSH_SESSION_ID;
|
|
76
|
+
const { cwd, sessionId } = executor.resolveExecScope(execContext);
|
|
77
77
|
// 显式 limit 优先;缺省回退 executor.defaultListLimit(用户可在插件配置中调整)
|
|
78
78
|
const limitStr = String(typeof args.limit === 'number' && args.limit > 0 ? args.limit : Math.min(executor.defaultListLimit ?? 10, 50));
|
|
79
79
|
const runOpts = { cwd, sessionId };
|
|
@@ -331,5 +331,5 @@ export function createRepoTool(executor) {
|
|
|
331
331
|
throw new Error(`未知的 Repo 操作: ${args.action}`);
|
|
332
332
|
}
|
|
333
333
|
},
|
|
334
|
-
};
|
|
334
|
+
});
|
|
335
335
|
}
|