dsh-github-flow 0.3.0 → 0.5.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 +287 -143
- package/README.md +270 -125
- package/docs/UPGRADE-NOTES.md +12 -9
- package/docs/architecture.md +126 -126
- package/lib/api/routes-actions.js +249 -0
- package/lib/api/routes-overview.js +94 -2
- package/lib/cache.d.ts +4 -0
- package/lib/cache.js +14 -0
- package/lib/client.d.ts +14 -1
- package/lib/client.js +4287 -2500
- package/lib/commands/gh.d.ts +6 -1
- package/lib/commands/gh.js +144 -71
- package/lib/executor/git-parser.d.ts +57 -0
- package/lib/executor/git-parser.js +255 -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 +70 -48
- package/lib/executor.js +371 -356
- package/lib/git-watcher.d.ts +36 -0
- package/lib/git-watcher.js +91 -0
- package/lib/main.js +37 -3
- package/lib/renderers/agent-task.d.ts +16 -0
- package/lib/renderers/agent-task.js +107 -0
- package/lib/renderers/cache.d.ts +8 -0
- package/lib/renderers/cache.js +66 -0
- package/lib/renderers/codespace.d.ts +16 -0
- package/lib/renderers/codespace.js +103 -0
- package/lib/renderers/common.d.ts +24 -0
- package/lib/renderers/common.js +88 -0
- package/lib/renderers/discussion.d.ts +16 -0
- package/lib/renderers/discussion.js +101 -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 +19 -32
- package/lib/renderers/markdown.js +19 -624
- package/lib/renderers/pr.d.ts +4 -0
- package/lib/renderers/pr.js +107 -0
- package/lib/renderers/project.d.ts +20 -0
- package/lib/renderers/project.js +128 -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 +34 -0
- package/lib/renderers/skill.js +241 -0
- package/lib/renderers/status.d.ts +28 -0
- package/lib/renderers/status.js +176 -0
- package/lib/telemetry.d.ts +70 -0
- package/lib/telemetry.js +161 -0
- package/lib/tools/agent-task.d.ts +54 -0
- package/lib/tools/agent-task.js +135 -0
- package/lib/tools/cache.d.ts +64 -0
- package/lib/tools/cache.js +186 -0
- package/lib/tools/codespace.d.ts +51 -0
- package/lib/tools/codespace.js +154 -0
- package/lib/tools/discussion.d.ts +70 -0
- package/lib/tools/discussion.js +195 -0
- package/lib/tools/gist.d.ts +155 -0
- package/lib/tools/gist.js +190 -0
- package/lib/tools/help.js +214 -13
- package/lib/tools/image-utils.d.ts +8 -0
- package/lib/tools/image-utils.js +34 -0
- package/lib/tools/issue.d.ts +8 -1
- package/lib/tools/issue.js +26 -4
- package/lib/tools/label.d.ts +80 -0
- package/lib/tools/label.js +237 -0
- package/lib/tools/pr.d.ts +8 -0
- package/lib/tools/pr.js +48 -3
- package/lib/tools/project.d.ts +80 -0
- package/lib/tools/project.js +241 -0
- package/lib/tools/repo.js +13 -1
- package/lib/tools/ruleset.d.ts +80 -0
- package/lib/tools/ruleset.js +177 -0
- package/lib/tools/skill.d.ts +24 -1
- package/lib/tools/skill.js +115 -5
- package/lib/tools/status.d.ts +40 -0
- package/lib/tools/status.js +58 -0
- package/lib/types.d.ts +44 -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 +219 -0
- package/lib/ui/help-modal.d.ts +15 -0
- package/lib/ui/help-modal.js +202 -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 +35 -0
- package/lib/ui/storage.js +158 -0
- package/lib/ui/styles.d.ts +6 -0
- package/lib/ui/styles.js +1544 -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 +222 -0
- package/lib/ui/utils.d.ts +45 -0
- package/lib/ui/utils.js +226 -0
- package/lib/ui/workspace-panel.d.ts +4 -0
- package/lib/ui/workspace-panel.js +489 -0
- package/lib/upgrade-manager.d.ts +5 -1
- package/lib/upgrade-manager.js +24 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +2 -2
|
@@ -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;
|
|
@@ -88,6 +89,13 @@ export declare function createPrTool(executor: GhExecutor): {
|
|
|
88
89
|
};
|
|
89
90
|
description: string;
|
|
90
91
|
};
|
|
92
|
+
attach_images: {
|
|
93
|
+
type: string;
|
|
94
|
+
items: {
|
|
95
|
+
type: string;
|
|
96
|
+
};
|
|
97
|
+
description: string;
|
|
98
|
+
};
|
|
91
99
|
};
|
|
92
100
|
required: string[];
|
|
93
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', 'ready', 'revert', 'update_branch'],
|
|
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',
|
|
@@ -97,6 +100,11 @@ export function createPrTool(executor) {
|
|
|
97
100
|
items: { type: 'string' },
|
|
98
101
|
description: 'view_images 可选:仅下载指定 URL 的图片(从 view 返回的 images 清单中选取,不在清单内的会被拒绝)。留空下载全部(正文+评论)。',
|
|
99
102
|
},
|
|
103
|
+
attach_images: {
|
|
104
|
+
type: 'array',
|
|
105
|
+
items: { type: 'string' },
|
|
106
|
+
description: '本地图片/附件路径列表(create, edit, comment 动作使用,需 gh ≥ 2.99;禁止 URL,单批上限 50 个)。',
|
|
107
|
+
},
|
|
100
108
|
},
|
|
101
109
|
required: ['action'],
|
|
102
110
|
},
|
|
@@ -121,6 +129,18 @@ export function createPrTool(executor) {
|
|
|
121
129
|
}
|
|
122
130
|
const repoArgs = ['-R', repoTarget];
|
|
123
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
|
+
};
|
|
124
144
|
switch (args.action) {
|
|
125
145
|
case 'list': {
|
|
126
146
|
const listCmd = ['pr', 'list', ...repoArgs, '--json', 'number,title,author,headRefName,isDraft,state,updatedAt,mergedAt,url'];
|
|
@@ -261,6 +281,7 @@ export function createPrTool(executor) {
|
|
|
261
281
|
const dryRun = args.dry_run === true;
|
|
262
282
|
if (dryRun)
|
|
263
283
|
createCmd.push('--dry-run');
|
|
284
|
+
await appendAttachFlags(createCmd, args.attach_images);
|
|
264
285
|
const res = await executor.run(createCmd, runOpts);
|
|
265
286
|
if (!res.ok)
|
|
266
287
|
throw new Error(res.error);
|
|
@@ -350,6 +371,30 @@ export function createPrTool(executor) {
|
|
|
350
371
|
throw new Error(res.error);
|
|
351
372
|
return { message: `PR #${args.pr_number} 分支已从基准分支同步更新`, output: res.rawOutput };
|
|
352
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
|
+
}
|
|
353
398
|
default:
|
|
354
399
|
throw new Error(`未知的 PR 操作: ${args.action}`);
|
|
355
400
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { GhExecutor } from '../executor.js';
|
|
2
|
+
/**
|
|
3
|
+
* 检测错误信息是否属于 Scope 权限不足,并返回标准自愈对象
|
|
4
|
+
*/
|
|
5
|
+
export declare function checkScopeRequired(errorMsg: string, scope: string, command: string, tool: string): {
|
|
6
|
+
status: string;
|
|
7
|
+
scope: string;
|
|
8
|
+
command: string;
|
|
9
|
+
tool: string;
|
|
10
|
+
} | null;
|
|
11
|
+
export declare function createProjectTool(executor: GhExecutor): {
|
|
12
|
+
name: string;
|
|
13
|
+
description: string;
|
|
14
|
+
parameters: {
|
|
15
|
+
type: string;
|
|
16
|
+
properties: {
|
|
17
|
+
action: {
|
|
18
|
+
type: string;
|
|
19
|
+
enum: string[];
|
|
20
|
+
description: string;
|
|
21
|
+
};
|
|
22
|
+
owner: {
|
|
23
|
+
type: string;
|
|
24
|
+
description: string;
|
|
25
|
+
};
|
|
26
|
+
project_number: {
|
|
27
|
+
type: string;
|
|
28
|
+
description: string;
|
|
29
|
+
};
|
|
30
|
+
item_id: {
|
|
31
|
+
type: string;
|
|
32
|
+
description: string;
|
|
33
|
+
};
|
|
34
|
+
url: {
|
|
35
|
+
type: string;
|
|
36
|
+
description: string;
|
|
37
|
+
};
|
|
38
|
+
title: {
|
|
39
|
+
type: string;
|
|
40
|
+
description: string;
|
|
41
|
+
};
|
|
42
|
+
body: {
|
|
43
|
+
type: string;
|
|
44
|
+
description: string;
|
|
45
|
+
};
|
|
46
|
+
query: {
|
|
47
|
+
type: string;
|
|
48
|
+
description: string;
|
|
49
|
+
};
|
|
50
|
+
closed: {
|
|
51
|
+
type: string;
|
|
52
|
+
description: string;
|
|
53
|
+
};
|
|
54
|
+
limit: {
|
|
55
|
+
type: string;
|
|
56
|
+
description: string;
|
|
57
|
+
};
|
|
58
|
+
confirm: {
|
|
59
|
+
type: string;
|
|
60
|
+
description: string;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
required: string[];
|
|
64
|
+
};
|
|
65
|
+
output: {
|
|
66
|
+
schema: {
|
|
67
|
+
type: string;
|
|
68
|
+
};
|
|
69
|
+
render(args: any, value: any): {
|
|
70
|
+
type: string;
|
|
71
|
+
text: string;
|
|
72
|
+
}[];
|
|
73
|
+
};
|
|
74
|
+
execute(args: any, execContext: any): Promise<Record<string, any> | {
|
|
75
|
+
status: string;
|
|
76
|
+
scope: string;
|
|
77
|
+
command: string;
|
|
78
|
+
tool: string;
|
|
79
|
+
}>;
|
|
80
|
+
};
|