dsh-github-flow 0.2.1 → 0.4.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 +234 -130
- package/README.md +217 -112
- package/docs/UPGRADE-NOTES.md +15 -9
- package/docs/architecture.md +120 -108
- package/docs/images/2.png +0 -0
- package/docs/npm-publish-guide.md +12 -1
- package/lib/api/routes-actions.js +117 -0
- package/lib/api/routes-overview.js +198 -3
- package/lib/cache.d.ts +4 -0
- package/lib/cache.js +14 -0
- package/lib/client.d.ts +13 -1
- package/lib/client.js +3162 -2162
- package/lib/executor/git-parser.d.ts +25 -0
- package/lib/executor/git-parser.js +143 -0
- package/lib/executor/platform-guide.d.ts +14 -0
- package/lib/executor/platform-guide.js +40 -0
- package/lib/executor/process-utils.d.ts +51 -0
- package/lib/executor/process-utils.js +162 -0
- package/lib/executor/version-utils.d.ts +12 -0
- package/lib/executor/version-utils.js +27 -0
- package/lib/executor.d.ts +131 -17
- package/lib/executor.js +411 -223
- package/lib/git-watcher.d.ts +36 -0
- package/lib/git-watcher.js +91 -0
- package/lib/main.js +11 -0
- package/lib/renderers/cache.d.ts +8 -0
- package/lib/renderers/cache.js +66 -0
- package/lib/renderers/common.d.ts +24 -0
- package/lib/renderers/common.js +88 -0
- package/lib/renderers/gist.d.ts +15 -0
- package/lib/renderers/gist.js +99 -0
- package/lib/renderers/issue.d.ts +4 -0
- package/lib/renderers/issue.js +70 -0
- package/lib/renderers/label.d.ts +4 -0
- package/lib/renderers/label.js +48 -0
- package/lib/renderers/markdown.d.ts +15 -26
- package/lib/renderers/markdown.js +15 -517
- package/lib/renderers/pr.d.ts +4 -0
- package/lib/renderers/pr.js +107 -0
- package/lib/renderers/release.d.ts +4 -0
- package/lib/renderers/release.js +74 -0
- package/lib/renderers/repo.d.ts +4 -0
- package/lib/renderers/repo.js +149 -0
- package/lib/renderers/ruleset.d.ts +4 -0
- package/lib/renderers/ruleset.js +74 -0
- package/lib/renderers/run.d.ts +4 -0
- package/lib/renderers/run.js +79 -0
- package/lib/renderers/skill.d.ts +4 -0
- package/lib/renderers/skill.js +61 -0
- package/lib/renderers/status.d.ts +23 -0
- package/lib/renderers/status.js +124 -0
- package/lib/tools/cache.d.ts +64 -0
- package/lib/tools/cache.js +186 -0
- package/lib/tools/gist.d.ts +155 -0
- package/lib/tools/gist.js +190 -0
- package/lib/tools/help.js +233 -11
- package/lib/tools/image-utils.d.ts +8 -0
- package/lib/tools/image-utils.js +34 -0
- package/lib/tools/issue.d.ts +17 -1
- package/lib/tools/issue.js +114 -8
- package/lib/tools/label.d.ts +80 -0
- package/lib/tools/label.js +237 -0
- package/lib/tools/pr.d.ts +12 -0
- package/lib/tools/pr.js +96 -4
- package/lib/tools/repo.d.ts +8 -0
- package/lib/tools/repo.js +127 -3
- package/lib/tools/ruleset.d.ts +80 -0
- package/lib/tools/ruleset.js +177 -0
- package/lib/tools/run.d.ts +8 -0
- package/lib/tools/run.js +104 -3
- package/lib/tools/secret.d.ts +5 -0
- package/lib/tools/secret.js +108 -23
- package/lib/tools/status.d.ts +39 -0
- package/lib/tools/status.js +53 -0
- package/lib/types.d.ts +112 -0
- package/lib/ui/auth-guide-card.d.ts +4 -0
- package/lib/ui/auth-guide-card.js +161 -0
- package/lib/ui/branch-selector.d.ts +10 -0
- package/lib/ui/branch-selector.js +163 -0
- package/lib/ui/commit-modal.d.ts +8 -0
- package/lib/ui/commit-modal.js +89 -0
- package/lib/ui/icons.d.ts +7 -0
- package/lib/ui/icons.js +53 -0
- package/lib/ui/network-banner.d.ts +10 -0
- package/lib/ui/network-banner.js +54 -0
- package/lib/ui/storage.d.ts +26 -0
- package/lib/ui/storage.js +134 -0
- package/lib/ui/styles.d.ts +6 -0
- package/lib/ui/styles.js +1285 -0
- package/lib/ui/sync-settings-modal.d.ts +11 -0
- package/lib/ui/sync-settings-modal.js +350 -0
- package/lib/ui/sync-status-badge.d.ts +8 -0
- package/lib/ui/sync-status-badge.js +64 -0
- package/lib/ui/upgrade-card.d.ts +7 -0
- package/lib/ui/upgrade-card.js +197 -0
- package/lib/ui/utils.d.ts +10 -0
- package/lib/ui/utils.js +66 -0
- package/lib/ui/workspace-panel.d.ts +4 -0
- package/lib/ui/workspace-panel.js +464 -0
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { GhExecutor } from '../executor.js';
|
|
2
|
+
/**
|
|
3
|
+
* 校验并归一化 6 位十六进制颜色(去除前导 #)
|
|
4
|
+
*/
|
|
5
|
+
export declare function normalizeHexColor(rawColor?: string): string | undefined;
|
|
6
|
+
export declare function createLabelTool(executor: GhExecutor): {
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
parameters: {
|
|
10
|
+
type: string;
|
|
11
|
+
properties: {
|
|
12
|
+
action: {
|
|
13
|
+
type: string;
|
|
14
|
+
enum: string[];
|
|
15
|
+
description: string;
|
|
16
|
+
};
|
|
17
|
+
repo: {
|
|
18
|
+
type: string;
|
|
19
|
+
description: string;
|
|
20
|
+
};
|
|
21
|
+
name: {
|
|
22
|
+
type: string;
|
|
23
|
+
description: string;
|
|
24
|
+
};
|
|
25
|
+
new_name: {
|
|
26
|
+
type: string;
|
|
27
|
+
description: string;
|
|
28
|
+
};
|
|
29
|
+
color: {
|
|
30
|
+
type: string;
|
|
31
|
+
description: string;
|
|
32
|
+
};
|
|
33
|
+
description: {
|
|
34
|
+
type: string;
|
|
35
|
+
description: string;
|
|
36
|
+
};
|
|
37
|
+
force: {
|
|
38
|
+
type: string;
|
|
39
|
+
description: string;
|
|
40
|
+
};
|
|
41
|
+
source_repo: {
|
|
42
|
+
type: string;
|
|
43
|
+
description: string;
|
|
44
|
+
};
|
|
45
|
+
limit: {
|
|
46
|
+
type: string;
|
|
47
|
+
description: string;
|
|
48
|
+
};
|
|
49
|
+
search: {
|
|
50
|
+
type: string;
|
|
51
|
+
description: string;
|
|
52
|
+
};
|
|
53
|
+
sort: {
|
|
54
|
+
type: string;
|
|
55
|
+
enum: string[];
|
|
56
|
+
description: string;
|
|
57
|
+
};
|
|
58
|
+
order: {
|
|
59
|
+
type: string;
|
|
60
|
+
enum: string[];
|
|
61
|
+
description: string;
|
|
62
|
+
};
|
|
63
|
+
confirm: {
|
|
64
|
+
type: string;
|
|
65
|
+
description: string;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
required: string[];
|
|
69
|
+
};
|
|
70
|
+
output: {
|
|
71
|
+
schema: {
|
|
72
|
+
type: string;
|
|
73
|
+
};
|
|
74
|
+
render(args: any, value: any): {
|
|
75
|
+
type: string;
|
|
76
|
+
text: string;
|
|
77
|
+
}[];
|
|
78
|
+
};
|
|
79
|
+
execute(args: any, execContext: any): Promise<Record<string, any>>;
|
|
80
|
+
};
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { renderLabelMarkdown } from '../renderers/markdown.js';
|
|
2
|
+
/**
|
|
3
|
+
* 校验并归一化 6 位十六进制颜色(去除前导 #)
|
|
4
|
+
*/
|
|
5
|
+
export function normalizeHexColor(rawColor) {
|
|
6
|
+
if (!rawColor)
|
|
7
|
+
return undefined;
|
|
8
|
+
const clean = rawColor.trim().replace(/^#/, '').trim();
|
|
9
|
+
if (!/^[0-9a-fA-F]{6}$/.test(clean)) {
|
|
10
|
+
throw new Error(`标签颜色必须为 6 位十六进制字符串(如 "d73a4a" 或 "#d73a4a"),当前为: "${rawColor}"`);
|
|
11
|
+
}
|
|
12
|
+
return clean;
|
|
13
|
+
}
|
|
14
|
+
function normalizeOutput(data) {
|
|
15
|
+
if (data && typeof data === 'object' && !Array.isArray(data)) {
|
|
16
|
+
return data;
|
|
17
|
+
}
|
|
18
|
+
if (Array.isArray(data)) {
|
|
19
|
+
return { items: data, count: data.length };
|
|
20
|
+
}
|
|
21
|
+
return { result: data !== undefined && data !== null ? data : '' };
|
|
22
|
+
}
|
|
23
|
+
export function createLabelTool(executor) {
|
|
24
|
+
return {
|
|
25
|
+
name: 'github_label',
|
|
26
|
+
description: '管理 GitHub 仓库标签体系 (Labels):查看标签列表、创建新标签、编辑修改现有标签、克隆跨仓库标签体系 (clone) 以及安全删除标签。删除操作不可逆且会解绑议题,需 confirm: true。',
|
|
27
|
+
parameters: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: {
|
|
30
|
+
action: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
enum: ['list', 'create', 'edit', 'delete', 'clone'],
|
|
33
|
+
description: 'Label 操作类型。list 查看列表、create 新建、edit 修改、delete 物理删除(需 confirm)、clone 跨仓库克隆同步。',
|
|
34
|
+
},
|
|
35
|
+
repo: {
|
|
36
|
+
type: 'string',
|
|
37
|
+
description: '目标仓库 [owner/repo],留空则识别当前工作区仓库。',
|
|
38
|
+
},
|
|
39
|
+
name: {
|
|
40
|
+
type: 'string',
|
|
41
|
+
description: '标签名称(create, edit, delete 动作必需)。',
|
|
42
|
+
},
|
|
43
|
+
new_name: {
|
|
44
|
+
type: 'string',
|
|
45
|
+
description: '新标签名称(edit 动作重命名标签时使用)。',
|
|
46
|
+
},
|
|
47
|
+
color: {
|
|
48
|
+
type: 'string',
|
|
49
|
+
description: '6 位十六进制色值(create, edit 动作可选,例如 "d73a4a" 或 "#d73a4a")。',
|
|
50
|
+
},
|
|
51
|
+
description: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
description: '标签描述(create, edit 动作可选)。',
|
|
54
|
+
},
|
|
55
|
+
force: {
|
|
56
|
+
type: 'boolean',
|
|
57
|
+
description: '是否强制覆盖(create 动作同名时更新、clone 动作冲突时覆盖;默认 false)。',
|
|
58
|
+
},
|
|
59
|
+
source_repo: {
|
|
60
|
+
type: 'string',
|
|
61
|
+
description: '源仓库全名 [owner/repo](clone 动作必需,例如 "cli/cli")。',
|
|
62
|
+
},
|
|
63
|
+
limit: {
|
|
64
|
+
type: 'number',
|
|
65
|
+
description: '返回标签数量上限(list 动作使用,默认 30)。',
|
|
66
|
+
},
|
|
67
|
+
search: {
|
|
68
|
+
type: 'string',
|
|
69
|
+
description: '按标签名称或描述过滤检索(list 动作使用)。',
|
|
70
|
+
},
|
|
71
|
+
sort: {
|
|
72
|
+
type: 'string',
|
|
73
|
+
enum: ['created', 'name'],
|
|
74
|
+
description: '排序字段(list 动作使用,默认 created)。',
|
|
75
|
+
},
|
|
76
|
+
order: {
|
|
77
|
+
type: 'string',
|
|
78
|
+
enum: ['asc', 'desc'],
|
|
79
|
+
description: '排序方向(list 动作使用,默认 asc)。',
|
|
80
|
+
},
|
|
81
|
+
confirm: {
|
|
82
|
+
type: 'boolean',
|
|
83
|
+
description: '高危破坏性操作确认标志(delete 动作必需)。未传 true 时输出影响评估报告并拦截。',
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
required: ['action'],
|
|
87
|
+
},
|
|
88
|
+
output: {
|
|
89
|
+
schema: { type: 'object' },
|
|
90
|
+
render(args, value) {
|
|
91
|
+
return [{ type: 'text', text: renderLabelMarkdown(args, value) }];
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
async execute(args, execContext) {
|
|
95
|
+
const cwd = execContext?.cwd || process.cwd();
|
|
96
|
+
const sessionId = execContext?.sessionId || execContext?.session?.id || process.env.DSH_SESSION_ID;
|
|
97
|
+
let repoTarget = args.repo;
|
|
98
|
+
if (!repoTarget) {
|
|
99
|
+
const meta = await executor.getRepoMetadata(cwd, sessionId);
|
|
100
|
+
if (meta?.nameWithOwner) {
|
|
101
|
+
repoTarget = meta.nameWithOwner;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (!repoTarget) {
|
|
105
|
+
throw new Error('未能自动识别当前工作区关联的 GitHub 仓库。请通过 `repo` 参数显式指定目标仓库 [owner/repo](例如: "ItQianChen/dsh-github-flow"),或在当前工作区目录下关联 Git 远程仓库。');
|
|
106
|
+
}
|
|
107
|
+
const repoArgs = ['-R', repoTarget];
|
|
108
|
+
const runOpts = { cwd, sessionId };
|
|
109
|
+
switch (args.action) {
|
|
110
|
+
case 'list': {
|
|
111
|
+
const listCmd = ['label', 'list', ...repoArgs, '--json', 'name,color,description'];
|
|
112
|
+
const limit = typeof args.limit === 'number' && args.limit > 0 ? args.limit : 30;
|
|
113
|
+
listCmd.push('-L', String(limit));
|
|
114
|
+
if (args.search) {
|
|
115
|
+
listCmd.push('-S', String(args.search));
|
|
116
|
+
}
|
|
117
|
+
if (args.sort) {
|
|
118
|
+
listCmd.push('--sort', args.sort);
|
|
119
|
+
}
|
|
120
|
+
if (args.order) {
|
|
121
|
+
listCmd.push('--order', args.order);
|
|
122
|
+
}
|
|
123
|
+
const res = await executor.run(listCmd, runOpts);
|
|
124
|
+
if (!res.ok)
|
|
125
|
+
throw new Error(res.error);
|
|
126
|
+
let rawData = res.data;
|
|
127
|
+
if (!rawData && typeof res.rawOutput === 'string') {
|
|
128
|
+
try {
|
|
129
|
+
rawData = JSON.parse(res.rawOutput);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
rawData = [];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return normalizeOutput(rawData || []);
|
|
136
|
+
}
|
|
137
|
+
case 'create': {
|
|
138
|
+
if (!args.name) {
|
|
139
|
+
throw new Error('创建标签时 `name` 参数为必填项。');
|
|
140
|
+
}
|
|
141
|
+
const color = normalizeHexColor(args.color);
|
|
142
|
+
const createCmd = ['label', 'create', args.name, ...repoArgs];
|
|
143
|
+
if (color) {
|
|
144
|
+
createCmd.push('-c', color);
|
|
145
|
+
}
|
|
146
|
+
if (args.description) {
|
|
147
|
+
createCmd.push('-d', args.description);
|
|
148
|
+
}
|
|
149
|
+
if (args.force) {
|
|
150
|
+
createCmd.push('-f');
|
|
151
|
+
}
|
|
152
|
+
const res = await executor.run(createCmd, runOpts);
|
|
153
|
+
if (!res.ok)
|
|
154
|
+
throw new Error(res.error);
|
|
155
|
+
return {
|
|
156
|
+
success: true,
|
|
157
|
+
name: args.name,
|
|
158
|
+
message: `✅ 成功在仓库 ${repoTarget} 创建标签 "${args.name}"`,
|
|
159
|
+
detail: res.rawOutput || '',
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
case 'edit': {
|
|
163
|
+
if (!args.name) {
|
|
164
|
+
throw new Error('编辑标签时 `name` 参数为必填项。');
|
|
165
|
+
}
|
|
166
|
+
const color = normalizeHexColor(args.color);
|
|
167
|
+
const editCmd = ['label', 'edit', args.name, ...repoArgs];
|
|
168
|
+
if (args.new_name) {
|
|
169
|
+
editCmd.push('-n', args.new_name);
|
|
170
|
+
}
|
|
171
|
+
if (color) {
|
|
172
|
+
editCmd.push('-c', color);
|
|
173
|
+
}
|
|
174
|
+
if (args.description !== undefined) {
|
|
175
|
+
editCmd.push('-d', args.description);
|
|
176
|
+
}
|
|
177
|
+
const res = await executor.run(editCmd, runOpts);
|
|
178
|
+
if (!res.ok)
|
|
179
|
+
throw new Error(res.error);
|
|
180
|
+
return {
|
|
181
|
+
success: true,
|
|
182
|
+
name: args.new_name || args.name,
|
|
183
|
+
message: `✅ 成功更新仓库 ${repoTarget} 标签 "${args.name}"` + (args.new_name ? `(重命名为 "${args.new_name}")` : ''),
|
|
184
|
+
detail: res.rawOutput || '',
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
case 'delete': {
|
|
188
|
+
if (!args.name) {
|
|
189
|
+
throw new Error('删除标签时 `name` 参数为必填项。');
|
|
190
|
+
}
|
|
191
|
+
if (args.confirm !== true) {
|
|
192
|
+
return {
|
|
193
|
+
status: 'AWAITING_CONFIRMATION',
|
|
194
|
+
action: 'delete',
|
|
195
|
+
name: args.name,
|
|
196
|
+
repo: repoTarget,
|
|
197
|
+
warning: `⚠️ 高危破坏性操作:即将删除仓库 ${repoTarget} 的标签 "${args.name}"`,
|
|
198
|
+
impact: `删除标签不可逆,且会导致目标仓库内所有已打上该标签的 Issue 与 PR 自动解绑该标记。`,
|
|
199
|
+
requiredAction: `如确认彻底删除,请在参数中附加 \`confirm: true\` 重新调用。示例:\`github_label(action: 'delete', name: '${args.name}', confirm: true)\``,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
const deleteCmd = ['label', 'delete', args.name, '--yes', ...repoArgs];
|
|
203
|
+
const res = await executor.run(deleteCmd, runOpts);
|
|
204
|
+
if (!res.ok)
|
|
205
|
+
throw new Error(res.error);
|
|
206
|
+
return {
|
|
207
|
+
success: true,
|
|
208
|
+
name: args.name,
|
|
209
|
+
message: `🗑️ 成功在仓库 ${repoTarget} 删除标签 "${args.name}"`,
|
|
210
|
+
detail: res.rawOutput || '',
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
case 'clone': {
|
|
214
|
+
if (!args.source_repo) {
|
|
215
|
+
throw new Error('克隆标签时 `source_repo` 源仓库参数为必填项(格式: [owner/repo])。');
|
|
216
|
+
}
|
|
217
|
+
const cloneCmd = ['label', 'clone', args.source_repo, ...repoArgs];
|
|
218
|
+
if (args.force) {
|
|
219
|
+
cloneCmd.push('-f');
|
|
220
|
+
}
|
|
221
|
+
const res = await executor.run(cloneCmd, runOpts);
|
|
222
|
+
if (!res.ok)
|
|
223
|
+
throw new Error(res.error);
|
|
224
|
+
return {
|
|
225
|
+
success: true,
|
|
226
|
+
source: args.source_repo,
|
|
227
|
+
destination: repoTarget,
|
|
228
|
+
message: `🏷️ 成功将源仓库 ${args.source_repo} 的标签体系克隆同步到 ${repoTarget}` + (args.force ? '(已覆盖同名冲突标签)' : ''),
|
|
229
|
+
detail: res.rawOutput || '',
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
default:
|
|
233
|
+
throw new Error(`不支持的 label 操作: "${args.action}"。可选值: list, create, edit, delete, clone。`);
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
}
|
package/lib/tools/pr.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { GhExecutor } from '../executor.js';
|
|
2
|
+
export { extractImageUrls as extractPrImageUrls, validateAttachPaths } from './image-utils.js';
|
|
2
3
|
export declare function createPrTool(executor: GhExecutor): {
|
|
3
4
|
name: string;
|
|
4
5
|
description: string;
|
|
@@ -52,6 +53,10 @@ export declare function createPrTool(executor: GhExecutor): {
|
|
|
52
53
|
type: string;
|
|
53
54
|
description: string;
|
|
54
55
|
};
|
|
56
|
+
undo: {
|
|
57
|
+
type: string;
|
|
58
|
+
description: string;
|
|
59
|
+
};
|
|
55
60
|
state: {
|
|
56
61
|
type: string;
|
|
57
62
|
enum: string[];
|
|
@@ -84,6 +89,13 @@ export declare function createPrTool(executor: GhExecutor): {
|
|
|
84
89
|
};
|
|
85
90
|
description: string;
|
|
86
91
|
};
|
|
92
|
+
attach_images: {
|
|
93
|
+
type: string;
|
|
94
|
+
items: {
|
|
95
|
+
type: string;
|
|
96
|
+
};
|
|
97
|
+
description: string;
|
|
98
|
+
};
|
|
87
99
|
};
|
|
88
100
|
required: string[];
|
|
89
101
|
};
|
package/lib/tools/pr.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { compareGhVersions } from '../executor.js';
|
|
1
2
|
import { renderPrMarkdown } from '../renderers/markdown.js';
|
|
2
|
-
import { extractImageUrls, downloadImages, buildImageBlocks, filterRequestedUrls } from './image-utils.js';
|
|
3
|
+
import { extractImageUrls, downloadImages, buildImageBlocks, filterRequestedUrls, validateAttachPaths } from './image-utils.js';
|
|
3
4
|
function normalizeOutput(data) {
|
|
4
5
|
if (data && typeof data === 'object' && !Array.isArray(data)) {
|
|
5
6
|
return data;
|
|
@@ -9,6 +10,8 @@ function normalizeOutput(data) {
|
|
|
9
10
|
}
|
|
10
11
|
return { result: data !== undefined && data !== null ? data : '' };
|
|
11
12
|
}
|
|
13
|
+
// 共享图片工具链的同名再导出:保持路径与单测稳定
|
|
14
|
+
export { extractImageUrls as extractPrImageUrls, validateAttachPaths } from './image-utils.js';
|
|
12
15
|
export function createPrTool(executor) {
|
|
13
16
|
return {
|
|
14
17
|
name: 'github_pr',
|
|
@@ -18,8 +21,8 @@ export function createPrTool(executor) {
|
|
|
18
21
|
properties: {
|
|
19
22
|
action: {
|
|
20
23
|
type: 'string',
|
|
21
|
-
enum: ['list', 'view', 'create', 'diff', 'checks', 'review', 'merge', 'checkout', 'status', 'view_images'],
|
|
22
|
-
description: 'PR 操作类型。view_images
|
|
24
|
+
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。',
|
|
23
26
|
},
|
|
24
27
|
pr_number: {
|
|
25
28
|
type: 'number',
|
|
@@ -61,7 +64,11 @@ export function createPrTool(executor) {
|
|
|
61
64
|
},
|
|
62
65
|
delete_branch: {
|
|
63
66
|
type: 'boolean',
|
|
64
|
-
description: '
|
|
67
|
+
description: '合并/revert 后是否删除源分支(merge、revert 操作时使用,默认 false)。删除分支不可撤销,只在确认该分支已无用后再开启。',
|
|
68
|
+
},
|
|
69
|
+
undo: {
|
|
70
|
+
type: 'boolean',
|
|
71
|
+
description: 'ready 动作可选:反向操作,把 PR 转回 Draft(gh 原生 --undo)。',
|
|
65
72
|
},
|
|
66
73
|
state: {
|
|
67
74
|
type: 'string',
|
|
@@ -93,6 +100,11 @@ export function createPrTool(executor) {
|
|
|
93
100
|
items: { type: 'string' },
|
|
94
101
|
description: 'view_images 可选:仅下载指定 URL 的图片(从 view 返回的 images 清单中选取,不在清单内的会被拒绝)。留空下载全部(正文+评论)。',
|
|
95
102
|
},
|
|
103
|
+
attach_images: {
|
|
104
|
+
type: 'array',
|
|
105
|
+
items: { type: 'string' },
|
|
106
|
+
description: '本地图片/附件路径列表(create, edit, comment 动作使用,需 gh ≥ 2.99;禁止 URL,单批上限 50 个)。',
|
|
107
|
+
},
|
|
96
108
|
},
|
|
97
109
|
required: ['action'],
|
|
98
110
|
},
|
|
@@ -117,6 +129,18 @@ export function createPrTool(executor) {
|
|
|
117
129
|
}
|
|
118
130
|
const repoArgs = ['-R', repoTarget];
|
|
119
131
|
const runOpts = { cwd, sessionId };
|
|
132
|
+
const appendAttachFlags = async (cmd, attachImages) => {
|
|
133
|
+
const attachPaths = validateAttachPaths(attachImages, cwd);
|
|
134
|
+
if (attachPaths.length > 0) {
|
|
135
|
+
const version = await executor.getGhVersion();
|
|
136
|
+
if (version && compareGhVersions(version, '2.99.0') < 0) {
|
|
137
|
+
throw new Error(`附件上传能力 (\`--attach\`) 需要 GitHub CLI 版本 ≥ 2.99.0,当前版本为: ${version}。\n请升级 gh(Windows: \`winget upgrade GitHub.cli\`,macOS: \`brew upgrade gh\`)后再试。`);
|
|
138
|
+
}
|
|
139
|
+
for (const p of attachPaths) {
|
|
140
|
+
cmd.push('--attach', p);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
};
|
|
120
144
|
switch (args.action) {
|
|
121
145
|
case 'list': {
|
|
122
146
|
const listCmd = ['pr', 'list', ...repoArgs, '--json', 'number,title,author,headRefName,isDraft,state,updatedAt,mergedAt,url'];
|
|
@@ -257,6 +281,7 @@ export function createPrTool(executor) {
|
|
|
257
281
|
const dryRun = args.dry_run === true;
|
|
258
282
|
if (dryRun)
|
|
259
283
|
createCmd.push('--dry-run');
|
|
284
|
+
await appendAttachFlags(createCmd, args.attach_images);
|
|
260
285
|
const res = await executor.run(createCmd, runOpts);
|
|
261
286
|
if (!res.ok)
|
|
262
287
|
throw new Error(res.error);
|
|
@@ -303,6 +328,73 @@ export function createPrTool(executor) {
|
|
|
303
328
|
throw new Error(res.error);
|
|
304
329
|
return { message: 'PR 合并完成', output: res.rawOutput };
|
|
305
330
|
}
|
|
331
|
+
// ---- 变更面新增(S3)----
|
|
332
|
+
case 'ready': {
|
|
333
|
+
if (!args.pr_number)
|
|
334
|
+
throw new Error('ready 操作必须提供 pr_number');
|
|
335
|
+
// Draft→Ready;gh 原生支持 --undo 反向(转回 draft),完整可逆,免 confirm
|
|
336
|
+
const cmd = ['pr', 'ready', String(args.pr_number), ...repoArgs];
|
|
337
|
+
if (args.undo === true)
|
|
338
|
+
cmd.push('--undo');
|
|
339
|
+
const res = await executor.run(cmd, runOpts);
|
|
340
|
+
if (!res.ok)
|
|
341
|
+
throw new Error(res.error);
|
|
342
|
+
return { message: `PR #${args.pr_number} 已${args.undo === true ? '转回 Draft' : '从 Draft 转为 Ready(进入正式评审)'}` };
|
|
343
|
+
}
|
|
344
|
+
case 'revert': {
|
|
345
|
+
if (!args.pr_number)
|
|
346
|
+
throw new Error('revert 操作必须提供 pr_number');
|
|
347
|
+
// 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
|
+
const revertCmd = ['pr', 'revert', String(args.pr_number), ...repoArgs];
|
|
357
|
+
if (args.delete_branch === true)
|
|
358
|
+
revertCmd.push('--delete-branch');
|
|
359
|
+
const res = await executor.run(revertCmd, runOpts);
|
|
360
|
+
if (!res.ok)
|
|
361
|
+
throw new Error(res.error);
|
|
362
|
+
return { message: `PR #${args.pr_number} 的反向提交已创建`, url: res.rawOutput };
|
|
363
|
+
}
|
|
364
|
+
case 'update_branch': {
|
|
365
|
+
if (!args.pr_number)
|
|
366
|
+
throw new Error('update_branch 操作必须提供 pr_number');
|
|
367
|
+
// 等价 repo sync 的单 PR 版(push merge commit 到 PR 分支);恰是「分支落后」
|
|
368
|
+
// 的自愈动作,confirm 摩擦会阻碍 Agent 自愈闭环(D6),输出同步摘要即可
|
|
369
|
+
const res = await executor.run(['pr', 'update-branch', String(args.pr_number), ...repoArgs], runOpts);
|
|
370
|
+
if (!res.ok)
|
|
371
|
+
throw new Error(res.error);
|
|
372
|
+
return { message: `PR #${args.pr_number} 分支已从基准分支同步更新`, output: res.rawOutput };
|
|
373
|
+
}
|
|
374
|
+
case 'comment': {
|
|
375
|
+
if (!args.pr_number || !args.body)
|
|
376
|
+
throw new Error('comment 必须提供 pr_number 和 body');
|
|
377
|
+
const commentCmd = ['pr', 'comment', String(args.pr_number), ...repoArgs, '--body', args.body];
|
|
378
|
+
await appendAttachFlags(commentCmd, args.attach_images);
|
|
379
|
+
const res = await executor.run(commentCmd, runOpts);
|
|
380
|
+
if (!res.ok)
|
|
381
|
+
throw new Error(res.error);
|
|
382
|
+
return { message: 'PR 评论发布成功', output: res.rawOutput };
|
|
383
|
+
}
|
|
384
|
+
case 'edit': {
|
|
385
|
+
if (!args.pr_number)
|
|
386
|
+
throw new Error('edit 操作必须提供 pr_number');
|
|
387
|
+
const editCmd = ['pr', 'edit', String(args.pr_number), ...repoArgs];
|
|
388
|
+
if (args.title)
|
|
389
|
+
editCmd.push('--title', args.title);
|
|
390
|
+
if (args.body)
|
|
391
|
+
editCmd.push('--body', args.body);
|
|
392
|
+
await appendAttachFlags(editCmd, args.attach_images);
|
|
393
|
+
const res = await executor.run(editCmd, runOpts);
|
|
394
|
+
if (!res.ok)
|
|
395
|
+
throw new Error(res.error);
|
|
396
|
+
return { message: `PR #${args.pr_number} 更新成功`, output: res.rawOutput };
|
|
397
|
+
}
|
|
306
398
|
default:
|
|
307
399
|
throw new Error(`未知的 PR 操作: ${args.action}`);
|
|
308
400
|
}
|
package/lib/tools/repo.d.ts
CHANGED
|
@@ -29,6 +29,14 @@ export declare function createRepoTool(executor: GhExecutor): {
|
|
|
29
29
|
type: string;
|
|
30
30
|
description: string;
|
|
31
31
|
};
|
|
32
|
+
path: {
|
|
33
|
+
type: string;
|
|
34
|
+
description: string;
|
|
35
|
+
};
|
|
36
|
+
ref: {
|
|
37
|
+
type: string;
|
|
38
|
+
description: string;
|
|
39
|
+
};
|
|
32
40
|
limit: {
|
|
33
41
|
type: string;
|
|
34
42
|
description: string;
|