dsh-github-flow 0.6.0 → 0.7.0
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 +98 -15
- package/README.md +101 -15
- package/docs/UPGRADE-NOTES.md +12 -11
- package/docs/architecture.md +56 -13
- package/lib/api/route-helpers.d.ts +19 -0
- package/lib/api/route-helpers.js +54 -0
- package/lib/api/routes-actions.js +75 -1
- 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 +1585 -429
- 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 +156 -3
- package/lib/executor.js +594 -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.js +4 -4
- package/lib/tools/api.js +18 -6
- package/lib/tools/cache.js +42 -40
- package/lib/tools/codespace.js +4 -12
- package/lib/tools/discussion.js +4 -4
- package/lib/tools/gist.d.ts +1 -39
- package/lib/tools/gist.js +4 -14
- package/lib/tools/help.js +52 -13
- package/lib/tools/issue.js +16 -28
- package/lib/tools/label.js +4 -15
- package/lib/tools/pr.d.ts +47 -0
- package/lib/tools/pr.js +170 -33
- package/lib/tools/project.js +4 -13
- package/lib/tools/release.js +11 -22
- 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 +219 -0
- package/lib/tools/task.js +457 -0
- package/lib/types.d.ts +183 -1
- package/lib/ui/help-modal.d.ts +1 -1
- package/lib/ui/help-modal.js +20 -10
- 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 +370 -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,9 @@
|
|
|
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';
|
|
4
7
|
function normalizeOutput(data) {
|
|
5
8
|
if (data && typeof data === 'object' && !Array.isArray(data)) {
|
|
6
9
|
return data;
|
|
@@ -13,7 +16,7 @@ function normalizeOutput(data) {
|
|
|
13
16
|
// 共享图片工具链的同名再导出:保持路径与单测稳定
|
|
14
17
|
export { extractImageUrls as extractPrImageUrls, validateAttachPaths } from './image-utils.js';
|
|
15
18
|
export function createPrTool(executor) {
|
|
16
|
-
return {
|
|
19
|
+
return wrapWithGate({
|
|
17
20
|
name: 'github_pr',
|
|
18
21
|
description: '管理与审查 GitHub Pull Request (PR)。支持查看列表、详情、差异比对、CI 检查状态、代码审查、合并操作、本地代码检出 (checkout) 与冲突状态检测 (status)。默认针对当前工作区关联的仓库。',
|
|
19
22
|
parameters: {
|
|
@@ -22,7 +25,7 @@ export function createPrTool(executor) {
|
|
|
22
25
|
action: {
|
|
23
26
|
type: 'string',
|
|
24
27
|
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。',
|
|
28
|
+
description: 'PR 操作类型。view_images 下载正文与评论中的图片到本地;create/edit/comment 支持 attach_images 附件上传;merge/revert 以及 checkout 的 force 需 confirm: true。',
|
|
26
29
|
},
|
|
27
30
|
pr_number: {
|
|
28
31
|
type: 'number',
|
|
@@ -57,6 +60,33 @@ export function createPrTool(executor) {
|
|
|
57
60
|
enum: ['APPROVE', 'REQUEST_CHANGES', 'COMMENT'],
|
|
58
61
|
description: '代码审查意见判定(review 操作时使用)。',
|
|
59
62
|
},
|
|
63
|
+
/**
|
|
64
|
+
* 行级评注(review 动作使用)。
|
|
65
|
+
*
|
|
66
|
+
* 为什么必须单独一个参数而不是塞进 body:`gh pr review` 没有行级参数
|
|
67
|
+
* (`gh pr review --help` 里 line/side/path 零命中),只能走
|
|
68
|
+
* `POST /pulls/{n}/reviews` 的 `comments[]`。一次 review 一个请求 =
|
|
69
|
+
* 通知只发一轮,也避开 secondary rate limit 的「创建内容过快」。
|
|
70
|
+
*/
|
|
71
|
+
comments: {
|
|
72
|
+
type: 'array',
|
|
73
|
+
items: {
|
|
74
|
+
type: 'object',
|
|
75
|
+
properties: {
|
|
76
|
+
path: { type: 'string', description: '文件相对路径,必须出现在该 PR 的变动文件清单里。' },
|
|
77
|
+
line: { type: 'number', description: '文件内的行号(新侧)。指向未变更行会被 GitHub 拒绝并自动降级为 body 段落。' },
|
|
78
|
+
side: { type: 'string', enum: ['LEFT', 'RIGHT'], description: '评论所在侧,默认 RIGHT(新代码)。' },
|
|
79
|
+
start_line: { type: 'number', description: '多行评论的起始行(可选,需与 line 同侧)。' },
|
|
80
|
+
body: { type: 'string', description: '评注正文。缺省会被补上 [P2] 严重度前缀。' },
|
|
81
|
+
},
|
|
82
|
+
required: ['path', 'body'],
|
|
83
|
+
},
|
|
84
|
+
description: '行级评注列表(review 动作使用,上限 50 条)。传入即走 REST reviews 通道;不传则保持原 gh pr review 行为不变。',
|
|
85
|
+
},
|
|
86
|
+
commit_id: {
|
|
87
|
+
type: 'string',
|
|
88
|
+
description: '可选:把 review 钉在指定 commit SHA 上(默认取 PR 当前 head)。分支会移动,需要精确锚定时传 headRefOid。',
|
|
89
|
+
},
|
|
60
90
|
merge_method: {
|
|
61
91
|
type: 'string',
|
|
62
92
|
enum: ['merge', 'squash', 'rebase'],
|
|
@@ -64,7 +94,11 @@ export function createPrTool(executor) {
|
|
|
64
94
|
},
|
|
65
95
|
delete_branch: {
|
|
66
96
|
type: 'boolean',
|
|
67
|
-
description: '
|
|
97
|
+
description: '合并后是否删除源分支(仅 merge 动作生效,默认 false)。gh pr revert 没有该 flag,传了也不会生效。删除分支不可撤销,只在确认该分支已无用后再开启。',
|
|
98
|
+
},
|
|
99
|
+
force: {
|
|
100
|
+
type: 'boolean',
|
|
101
|
+
description: 'checkout 动作可选:是否强制重置已存在的本地同名分支(映射 gh pr checkout -f)。会丢弃本地未推送提交,必须同时传 confirm: true。默认 false——本地分支与 PR 头已分叉时检出会失败,而不是静默重置。',
|
|
68
102
|
},
|
|
69
103
|
undo: {
|
|
70
104
|
type: 'boolean',
|
|
@@ -85,11 +119,11 @@ export function createPrTool(executor) {
|
|
|
85
119
|
},
|
|
86
120
|
stat_only: {
|
|
87
121
|
type: 'boolean',
|
|
88
|
-
description: '是否仅输出文件变动概览(diff
|
|
122
|
+
description: '是否仅输出文件变动概览(diff 动作使用,改走 gh pr view --json files,防止大 PR 爆 Token)。',
|
|
89
123
|
},
|
|
90
124
|
confirm: {
|
|
91
125
|
type: 'boolean',
|
|
92
|
-
description: '高危破坏性操作确认标志(merge
|
|
126
|
+
description: '高危破坏性操作确认标志(merge 动作、delete_branch、或 checkout 且 force: true 时必需)。若为 false 则仅输出影响评估并拦截。',
|
|
93
127
|
},
|
|
94
128
|
dry_run: {
|
|
95
129
|
type: 'boolean',
|
|
@@ -115,8 +149,11 @@ export function createPrTool(executor) {
|
|
|
115
149
|
},
|
|
116
150
|
},
|
|
117
151
|
async execute(args, execContext) {
|
|
118
|
-
|
|
119
|
-
|
|
152
|
+
// 位置参数守卫:pr_number 会作为 `gh pr <cmd> <number>` 的位置参数进入 argv,
|
|
153
|
+
// `--force` 这类值会被 gh 当成选项而不是编号(复核报告 C-01b)。
|
|
154
|
+
// 放在入口一次校验即可覆盖全部 action,避免 11 处各自写一份。
|
|
155
|
+
assertPositionArg(args.pr_number, 'PR 编号');
|
|
156
|
+
const { cwd, sessionId } = executor.resolveExecScope(execContext);
|
|
120
157
|
let repoTarget = args.repo;
|
|
121
158
|
if (!repoTarget) {
|
|
122
159
|
const meta = await executor.getRepoMetadata(cwd, sessionId);
|
|
@@ -160,7 +197,10 @@ export function createPrTool(executor) {
|
|
|
160
197
|
}
|
|
161
198
|
case 'view': {
|
|
162
199
|
const target = args.pr_number ? [String(args.pr_number)] : [];
|
|
163
|
-
const res = await executor.run(
|
|
200
|
+
const res = await executor.run(
|
|
201
|
+
// headRefOid/baseRefOid:分支会移动,Agent 侧也要能「钉在某个 commit 上」审
|
|
202
|
+
// changedFiles + files:清单在 100 条处静默截断,必须成对返回才能判断完整性
|
|
203
|
+
['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
204
|
if (!res.ok)
|
|
165
205
|
throw new Error(res.error);
|
|
166
206
|
const out = normalizeOutput(res.data || res.rawOutput);
|
|
@@ -174,6 +214,19 @@ export function createPrTool(executor) {
|
|
|
174
214
|
}
|
|
175
215
|
}
|
|
176
216
|
out.images = Array.from(new Set(bodies.flatMap((b) => extractImageUrls(b))));
|
|
217
|
+
/*
|
|
218
|
+
* 仓库审查规则注入(AGENTS.md)。
|
|
219
|
+
*
|
|
220
|
+
* 触发条件明文:仅在 view/review 动作、且工作区确实存在 AGENTS.md 时注入。
|
|
221
|
+
* 读不到就**不加这个键**(不是给个空对象)——这样「没有规则」与「规则为空」
|
|
222
|
+
* 在输出里可区分,Agent 不会误以为仓库定义了空规则。
|
|
223
|
+
*/
|
|
224
|
+
const changedPaths = Array.isArray(out.files)
|
|
225
|
+
? out.files.map((f) => (f && typeof f.path === 'string' ? f.path : '')).filter(Boolean)
|
|
226
|
+
: [];
|
|
227
|
+
const rules = readReviewRules(cwd, changedPaths);
|
|
228
|
+
if (rules)
|
|
229
|
+
out.reviewRules = rules;
|
|
177
230
|
return out;
|
|
178
231
|
}
|
|
179
232
|
case 'view_images': {
|
|
@@ -242,11 +295,36 @@ export function createPrTool(executor) {
|
|
|
242
295
|
}
|
|
243
296
|
case 'diff': {
|
|
244
297
|
const target = args.pr_number ? [String(args.pr_number)] : [];
|
|
245
|
-
|
|
298
|
+
// gh pr diff 不提供 --stat(实测 gh 2.96.0 直接 unknown flag / exit 1)。
|
|
299
|
+
// 文件变更概览的合法来源是 pr view --json files(字段含 path/additions/deletions)。
|
|
300
|
+
// 返回值保持 { result: string } 形状——renderPrMarkdown 对 result 字符串直出
|
|
301
|
+
// (src/renderers/pr.ts:15),因此这条路渲染层零改动。
|
|
246
302
|
if (args.stat_only === true) {
|
|
247
|
-
|
|
303
|
+
const statRes = await executor.run(['pr', 'view', ...target, ...repoArgs, '--json', 'files,additions,deletions'], runOpts);
|
|
304
|
+
if (!statRes.ok)
|
|
305
|
+
throw new Error(statRes.error);
|
|
306
|
+
const detail = statRes.data || {};
|
|
307
|
+
// executor.run 在输出被 24KB 阈值截断时会跳过 JSON.parse(executor.ts:341),
|
|
308
|
+
// 此时 data 为空、rawOutput 是被切断的半截 JSON。绝不能把「没解析出来」说成「0 个文件」。
|
|
309
|
+
if (!Array.isArray(detail.files)) {
|
|
310
|
+
return {
|
|
311
|
+
result: '未能解析文件变更清单:gh 输出可能被 24KB 截断阈值切断。请改用 github_pr 的完整 diff 动作,或缩小范围后重试。',
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
const rows = detail.files;
|
|
315
|
+
// 只说是"本次取到的文件":gh 的 --json files 在超过 100 个文件时静默截断
|
|
316
|
+
// (cli/cli#12522),不能对外声称是完整清单。
|
|
317
|
+
const cell = (v) => String(v ?? '').replace(/\|/g, '\\|');
|
|
318
|
+
const lines = [
|
|
319
|
+
`本次取到 ${rows.length} 个变更文件(+${detail.additions ?? 0} / -${detail.deletions ?? 0})`,
|
|
320
|
+
'',
|
|
321
|
+
'| 文件 | 增 | 删 |',
|
|
322
|
+
'| :--- | ---: | ---: |',
|
|
323
|
+
...rows.map((f) => `| ${cell(f.path)} | ${f.additions ?? 0} | ${f.deletions ?? 0} |`),
|
|
324
|
+
];
|
|
325
|
+
return { result: lines.join('\n') };
|
|
248
326
|
}
|
|
249
|
-
const res = await executor.run(
|
|
327
|
+
const res = await executor.run(['pr', 'diff', ...target, ...repoArgs], runOpts);
|
|
250
328
|
if (!res.ok)
|
|
251
329
|
throw new Error(res.error);
|
|
252
330
|
return { result: res.rawOutput || '无差异内容' };
|
|
@@ -254,10 +332,37 @@ export function createPrTool(executor) {
|
|
|
254
332
|
case 'checkout': {
|
|
255
333
|
if (!args.pr_number)
|
|
256
334
|
throw new Error('checkout 操作必须提供 pr_number');
|
|
257
|
-
|
|
335
|
+
// 防线:检出 PR 会切本地分支,与界面切分支同源风险,必须过同一道闸门。
|
|
336
|
+
// 这里用硬阻断而不是 confirm 二次确认——没有哪个参数能让「脏工作区检出」变安全,
|
|
337
|
+
// 给个 confirm 只会变成一个绕不过去的死循环。
|
|
338
|
+
const guard = await executor.ensureCleanWorkspace(cwd);
|
|
339
|
+
if (!guard.ok) {
|
|
340
|
+
const sample = guard.samples.length
|
|
341
|
+
? `\n涉及文件(前 ${guard.samples.length} 条):\n${guard.samples.map((s) => ' ' + s).join('\n')}`
|
|
342
|
+
: '';
|
|
343
|
+
throw new Error(`${guard.error}${sample}\n请先提交或 stash 后再检出 PR。`);
|
|
344
|
+
}
|
|
345
|
+
const checkoutCmd = ['pr', 'checkout', String(args.pr_number), ...repoArgs];
|
|
346
|
+
// 默认不带 -f。-f 的语义是 Reset the existing local branch to the latest state
|
|
347
|
+
// of the pull request(等价 reset --hard,丢弃本地未推送提交)。
|
|
348
|
+
// 不带 -f 时,若本地已有同名分支,gh 的行为是 `git checkout <branch>` 再
|
|
349
|
+
// `git merge --ff-only <remote>`——只有「已分叉」才失败,不会静默重置。
|
|
350
|
+
// 跨 fork 且本地无 remote 时走 `git fetch <ref>:<branch>`(不带 --force),
|
|
351
|
+
// 分叉同样会失败。因此默认路径是安全的,真要重置必须 force + confirm 双门。
|
|
352
|
+
if (args.force === true) {
|
|
353
|
+
checkoutCmd.push('-f');
|
|
354
|
+
}
|
|
258
355
|
const res = await executor.run(checkoutCmd, runOpts);
|
|
259
|
-
if (!res.ok)
|
|
260
|
-
|
|
356
|
+
if (!res.ok) {
|
|
357
|
+
// gh 在这里抛的是裸 git 错误,对用户和模型都没有"下一步"线索。
|
|
358
|
+
// 「PR 被 rebase / force-push 后重新检出」是常见路径,必须给出可操作选项。
|
|
359
|
+
const raw = String(res.error || '');
|
|
360
|
+
if (/fast-forward|non-fast-forward|rejected/i.test(raw)) {
|
|
361
|
+
throw new Error(`本地分支与 PR #${args.pr_number} 的头已分叉,无法快进检出。\n${raw}\n` +
|
|
362
|
+
'如确认用 PR 头覆盖本地分支(会丢弃本地未推送提交),请附加 `force: true, confirm: true` 重新调用。');
|
|
363
|
+
}
|
|
364
|
+
throw new Error(raw);
|
|
365
|
+
}
|
|
261
366
|
return { message: `已成功将 PR #${args.pr_number} 检出至本地分支`, output: res.rawOutput || 'Checked out' };
|
|
262
367
|
}
|
|
263
368
|
case 'status': {
|
|
@@ -292,6 +397,54 @@ export function createPrTool(executor) {
|
|
|
292
397
|
case 'review': {
|
|
293
398
|
if (!args.pr_number)
|
|
294
399
|
throw new Error('review 操作必须提供 pr_number');
|
|
400
|
+
/*
|
|
401
|
+
* 行级评注分支:只有显式传了非空 comments 才走 REST reviews 通道。
|
|
402
|
+
* 不传 → 下面的原 gh pr review 路径一字不动(默认行为零变化,这是本改动的
|
|
403
|
+
* 首要约束:老调用方的 argv 必须逐字节一致)。
|
|
404
|
+
*/
|
|
405
|
+
const lineComments = Array.isArray(args.comments) ? args.comments : [];
|
|
406
|
+
if (lineComments.length > 0) {
|
|
407
|
+
const res = await executor.createPrReview(cwd, args.repo, args.pr_number, {
|
|
408
|
+
event: args.review_decision,
|
|
409
|
+
body: args.body,
|
|
410
|
+
comments: lineComments,
|
|
411
|
+
commit_id: args.commit_id,
|
|
412
|
+
});
|
|
413
|
+
if (!res.ok)
|
|
414
|
+
throw new Error(res.error || 'Review 提交失败');
|
|
415
|
+
/*
|
|
416
|
+
* 严重度分布汇总(plan §2.3 item 14)。
|
|
417
|
+
*
|
|
418
|
+
* 为什么在工具层算:`normalizeSeverity` 幂等,这里对**入参**统计与对
|
|
419
|
+
* 已提交正文统计等价;而在工具层算不必让 executor 回传审计字段。
|
|
420
|
+
* 目的是让调用方一眼看到「这轮意见里有没有 P0」,而不是自己数。
|
|
421
|
+
*/
|
|
422
|
+
const severity = summarizeSeverity([args.body, ...lineComments.map((c) => (c && typeof c.body === 'string' ? c.body : ''))].filter((b) => typeof b === 'string' && b.trim()));
|
|
423
|
+
const lines = [
|
|
424
|
+
res.degraded
|
|
425
|
+
? `⚠️ Review 已提交(行级评注降级为正文段落)`
|
|
426
|
+
: `✅ Review 已提交,含 ${lineComments.length} 条行级评注`,
|
|
427
|
+
];
|
|
428
|
+
if (res.data?.htmlUrl)
|
|
429
|
+
lines.push(`- 地址: ${res.data.htmlUrl}`);
|
|
430
|
+
if (res.data?.state)
|
|
431
|
+
lines.push(`- 判定: ${res.data.state}`);
|
|
432
|
+
lines.push(`- 严重度: P0×${severity.P0} / P1×${severity.P1} / P2×${severity.P2}`);
|
|
433
|
+
if (Array.isArray(res.data?.comments) && res.data.comments.length > 0) {
|
|
434
|
+
lines.push('- 已挂载的行级评注:');
|
|
435
|
+
for (const c of res.data.comments) {
|
|
436
|
+
lines.push(` - \`${c.path}${c.line ? ':' + c.line : ''}\`${c.htmlUrl ? ' → ' + c.htmlUrl : ''}`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
if (res.degraded && res.degradedReason)
|
|
440
|
+
lines.push(`\n> ⚠️ ${res.degradedReason}`);
|
|
441
|
+
return {
|
|
442
|
+
result: lines.join('\n'),
|
|
443
|
+
severity,
|
|
444
|
+
degraded: res.degraded === true,
|
|
445
|
+
degradedReason: res.degradedReason,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
295
448
|
const reviewCmd = ['pr', 'review', String(args.pr_number), ...repoArgs];
|
|
296
449
|
if (args.review_decision === 'APPROVE')
|
|
297
450
|
reviewCmd.push('--approve');
|
|
@@ -309,14 +462,6 @@ export function createPrTool(executor) {
|
|
|
309
462
|
case 'merge': {
|
|
310
463
|
if (!args.pr_number)
|
|
311
464
|
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
465
|
const method = args.merge_method === 'merge' ? '--merge' : (args.merge_method === 'rebase' ? '--rebase' : '--squash');
|
|
321
466
|
const mergeCmd = ['pr', 'merge', String(args.pr_number), ...repoArgs, method];
|
|
322
467
|
// 删分支默认关闭:合并他人仓库的 PR 时删掉源分支是不可撤销的破坏性动作,
|
|
@@ -345,17 +490,9 @@ export function createPrTool(executor) {
|
|
|
345
490
|
if (!args.pr_number)
|
|
346
491
|
throw new Error('revert 操作必须提供 pr_number');
|
|
347
492
|
// 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
493
|
const revertCmd = ['pr', 'revert', String(args.pr_number), ...repoArgs];
|
|
357
|
-
|
|
358
|
-
|
|
494
|
+
// 注意:gh pr revert 没有 --delete-branch(实测 2.96.0 unknown flag),
|
|
495
|
+
// 不在这里推该 flag;删分支只在 merge 通道支持。
|
|
359
496
|
const res = await executor.run(revertCmd, runOpts);
|
|
360
497
|
if (!res.ok)
|
|
361
498
|
throw new Error(res.error);
|
|
@@ -399,5 +536,5 @@ export function createPrTool(executor) {
|
|
|
399
536
|
throw new Error(`未知的 PR 操作: ${args.action}`);
|
|
400
537
|
}
|
|
401
538
|
},
|
|
402
|
-
};
|
|
539
|
+
});
|
|
403
540
|
}
|
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.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { wrapWithGate } from '../destructive.js';
|
|
1
2
|
import { renderReleaseMarkdown } from '../renderers/markdown.js';
|
|
3
|
+
import { assertPositionArg } from '../executor/args-guard.js';
|
|
2
4
|
function normalizeOutput(data) {
|
|
3
5
|
if (data && typeof data === 'object' && !Array.isArray(data)) {
|
|
4
6
|
return data;
|
|
@@ -9,7 +11,7 @@ function normalizeOutput(data) {
|
|
|
9
11
|
return { result: data !== undefined && data !== null ? data : '' };
|
|
10
12
|
}
|
|
11
13
|
export function createReleaseTool(executor) {
|
|
12
|
-
return {
|
|
14
|
+
return wrapWithGate({
|
|
13
15
|
name: 'github_release',
|
|
14
16
|
description: '管理 GitHub Releases 版本发布与构建资产:查看发版历史、根据提交自动生成发版说明、创建 Release、上传/下载发布资产(如 npm .tgz 产物)。高危操作(delete、clobber 覆盖上传)需 confirm: true。',
|
|
15
17
|
parameters: {
|
|
@@ -91,8 +93,9 @@ export function createReleaseTool(executor) {
|
|
|
91
93
|
},
|
|
92
94
|
},
|
|
93
95
|
async execute(args, execContext) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
+
// 位置参数守卫:tag 是 `gh release view <tag>` / `upload <tag>` 的位置参数
|
|
97
|
+
assertPositionArg(args.tag, 'Release Tag');
|
|
98
|
+
const { cwd, sessionId } = executor.resolveExecScope(execContext);
|
|
96
99
|
let repoTarget = args.repo;
|
|
97
100
|
if (!repoTarget) {
|
|
98
101
|
const meta = await executor.getRepoMetadata(cwd, sessionId);
|
|
@@ -130,7 +133,7 @@ export function createReleaseTool(executor) {
|
|
|
130
133
|
if (Array.isArray(args.assets)) {
|
|
131
134
|
for (const a of args.assets) {
|
|
132
135
|
if (typeof a === 'string' && a.trim())
|
|
133
|
-
createCmd.push(a);
|
|
136
|
+
createCmd.push(assertPositionArg(a.trim(), '资产路径'));
|
|
134
137
|
}
|
|
135
138
|
}
|
|
136
139
|
if (args.title)
|
|
@@ -157,18 +160,12 @@ export function createReleaseTool(executor) {
|
|
|
157
160
|
if (!Array.isArray(args.assets) || args.assets.length === 0) {
|
|
158
161
|
throw new Error('upload 操作必须提供 assets(文件路径列表)');
|
|
159
162
|
}
|
|
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
163
|
const uploadCmd = ['release', 'upload', args.tag, ...repoArgs];
|
|
169
164
|
for (const a of args.assets) {
|
|
165
|
+
// 每个资产都是位置参数:`--clobber` 混进来会绕过 confirm 闸门覆盖删同名资产
|
|
166
|
+
// (复核报告 C-01a 的复现路径),必须在拼 argv 时就拒掉。
|
|
170
167
|
if (typeof a === 'string' && a.trim())
|
|
171
|
-
uploadCmd.push(a);
|
|
168
|
+
uploadCmd.push(assertPositionArg(a.trim(), '资产路径'));
|
|
172
169
|
}
|
|
173
170
|
if (args.clobber === true)
|
|
174
171
|
uploadCmd.push('--clobber');
|
|
@@ -195,14 +192,6 @@ export function createReleaseTool(executor) {
|
|
|
195
192
|
case 'delete': {
|
|
196
193
|
if (!args.tag)
|
|
197
194
|
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
195
|
const delCmd = ['release', 'delete', args.tag, ...repoArgs, '--yes'];
|
|
207
196
|
if (args.cleanup_tag === true)
|
|
208
197
|
delCmd.push('--cleanup-tag');
|
|
@@ -215,5 +204,5 @@ export function createReleaseTool(executor) {
|
|
|
215
204
|
throw new Error(`未知的 Release 操作: ${args.action}`);
|
|
216
205
|
}
|
|
217
206
|
},
|
|
218
|
-
};
|
|
207
|
+
});
|
|
219
208
|
}
|
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
|
}
|
package/lib/tools/ruleset.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { wrapWithGate } from '../destructive.js';
|
|
1
2
|
import { renderRulesetMarkdown } from '../renderers/markdown.js';
|
|
2
3
|
/**
|
|
3
4
|
* 解析 gh ruleset check 的纯文本输出,提取适用规则数、分支、仓库以及具体规则详情
|
|
@@ -37,7 +38,7 @@ export function parseRulesetCheckOutput(output) {
|
|
|
37
38
|
return result;
|
|
38
39
|
}
|
|
39
40
|
export function createRulesetTool(executor) {
|
|
40
|
-
return {
|
|
41
|
+
return wrapWithGate({
|
|
41
42
|
name: 'github_ruleset',
|
|
42
43
|
description: '查看与诊断 GitHub 仓库/组织规则集 (Rulesets):检查分支适用的合并策略规则 (check)、查看规则集列表 (list) 与详情 (view)。是诊断“PR 为什么不能合并”的权威规则策略视角。',
|
|
43
44
|
parameters: {
|
|
@@ -86,8 +87,7 @@ export function createRulesetTool(executor) {
|
|
|
86
87
|
},
|
|
87
88
|
},
|
|
88
89
|
async execute(args, execContext) {
|
|
89
|
-
const cwd
|
|
90
|
-
const sessionId = execContext?.sessionId || execContext?.session?.id || process.env.DSH_SESSION_ID;
|
|
90
|
+
const { cwd, sessionId } = executor.resolveExecScope(execContext);
|
|
91
91
|
// 解析当前工作区仓库(若未传 org 且需要 repo 维度)
|
|
92
92
|
const resolveRepoTarget = async () => {
|
|
93
93
|
if (args.repo)
|
|
@@ -130,7 +130,9 @@ export function createRulesetTool(executor) {
|
|
|
130
130
|
listCmd.push('-R', repoTarget);
|
|
131
131
|
}
|
|
132
132
|
if (args.no_parents) {
|
|
133
|
-
|
|
133
|
+
// gh 只有 -p/--parents(默认 true),没有 --no-parents(实测 2.96.0 unknown flag)。
|
|
134
|
+
// 排除继承的语义等价写法是显式关掉 --parents。
|
|
135
|
+
listCmd.push('--parents=false');
|
|
134
136
|
}
|
|
135
137
|
const limit = typeof args.limit === 'number' && args.limit > 0 ? args.limit : 30;
|
|
136
138
|
listCmd.push('-L', String(limit));
|
|
@@ -157,7 +159,8 @@ export function createRulesetTool(executor) {
|
|
|
157
159
|
viewCmd.push('-R', repoTarget);
|
|
158
160
|
}
|
|
159
161
|
if (args.no_parents) {
|
|
160
|
-
|
|
162
|
+
// 同 list:排除继承用 --parents=false,不存在 --no-parents。
|
|
163
|
+
viewCmd.push('--parents=false');
|
|
161
164
|
}
|
|
162
165
|
const res = await executor.run(viewCmd, { ...runOpts, rawText: true });
|
|
163
166
|
if (!res.ok)
|
|
@@ -173,5 +176,5 @@ export function createRulesetTool(executor) {
|
|
|
173
176
|
throw new Error(`不支持的 ruleset 操作: "${args.action}"。可选值: check, list, view。`);
|
|
174
177
|
}
|
|
175
178
|
},
|
|
176
|
-
};
|
|
179
|
+
});
|
|
177
180
|
}
|