msdevflow 0.7.2 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -17
- package/lib/bootstrap.js +190 -35
- package/package.json +4 -2
- package/skill/msdevflow/README.md +20 -6
- package/skill/msdevflow/SKILL.md +40 -8
- package/skill/msdevflow/references/ci-and-review.md +1 -1
- package/skill/msdevflow/references/create-issue.md +1 -1
- package/skill/msdevflow/references/design-and-development.md +1 -1
- package/skill/msdevflow/references/pr-and-ci.md +1 -1
- package/skill/msdevflow/references/recovery.md +1 -1
- package/skill/msdevflow/references/review-and-merge.md +2 -2
- package/skill/msdevflow/references/setup-and-issue.md +2 -2
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ npx msdevflow@latest setup
|
|
|
33
33
|
2. 安装或升级官方 `@gitcode-cli/cli@latest`;
|
|
34
34
|
3. 安装受审的 Python Playwright 依赖;
|
|
35
35
|
4. 验收 Issue、PR、结构化评论、行内评论、JSON、显式仓库和正文文件等 CLI 能力;
|
|
36
|
-
5. GitCode CLI
|
|
36
|
+
5. GitCode CLI 未认证时打开官方 Token 创建页面,并让 CLI 自己的终端提示接收 Token。
|
|
37
37
|
|
|
38
38
|
显式选择客户端或只读预检:
|
|
39
39
|
|
|
@@ -65,25 +65,29 @@ npx msdevflow setup --agents-skills-dir "<shared-agent-skills-directory>"
|
|
|
65
65
|
|
|
66
66
|
`--skills-dir` 未配合 `--targets` 使用时保留旧语义,只安装 Claude Code 目标。没有检测到客户端时,交互运行会要求选择目标;非交互运行必须提供 `--targets`。安装完成后重启或重新加载对应客户端。如果机器已有 Python 版本的 `gitcode`,setup 会保留它,并为 npm CLI 创建 `gitcode-npm` 命令;否则使用 `gitcode`。
|
|
67
67
|
|
|
68
|
-
##
|
|
68
|
+
## 运行与 action 菜单
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
从目标仓库目录启动客户端并提交 `msdevflow`:
|
|
71
71
|
|
|
72
|
-
- Claude Code
|
|
73
|
-
- Codex:使用 `$msdevflow
|
|
74
|
-
- OpenCode
|
|
72
|
+
- Claude Code:输入并提交 `/msdevflow`;
|
|
73
|
+
- Codex:使用 `$msdevflow` 或从 `/skills` 选择后提交;
|
|
74
|
+
- OpenCode:打开 `/skills`,选择 `msdevflow`,再提交插入的 `/msdevflow`。`@` 用于文件、reference 或 agent,不用于选择 Skill。
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
没有字面量 `action=<name>` 时,Agent 会先通过宿主原生单选工具显示分级菜单,选择前不会探测仓库或访问 GitCode:
|
|
77
77
|
|
|
78
78
|
```text
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
选择运行方式
|
|
80
|
+
├─ 完整端到端流程
|
|
81
|
+
├─ 作者工作流
|
|
82
|
+
│ ├─ Issue 管理 -> discover | create-issue | issue
|
|
83
|
+
│ ├─ 开发与交付 -> develop | pr | ci
|
|
84
|
+
│ └─ PR 后续 -> feedback | merge
|
|
85
|
+
└─ 独立工具 -> openlibing-auth | code-review
|
|
84
86
|
```
|
|
85
87
|
|
|
86
|
-
|
|
88
|
+
Claude Code 使用 `AskUserQuestion`,Codex 使用 `request_user_input`,OpenCode 使用 `question`。每次只显示当前层且不超过 3 项;工具不可用或处于非交互模式时,Agent 只输出当前层编号选项并等待回复,不自行选择默认项。
|
|
89
|
+
|
|
90
|
+
选择“完整端到端流程”后,才会运行:
|
|
87
91
|
|
|
88
92
|
```text
|
|
89
93
|
Issue 发现/核验
|
|
@@ -98,10 +102,11 @@ Issue 发现/核验
|
|
|
98
102
|
-> 远端回读 PR 已 merged
|
|
99
103
|
```
|
|
100
104
|
|
|
101
|
-
|
|
105
|
+
调用正文可预先提供 Issue/PR URL、仓库路径、canonical、source 和模式;菜单选择后这些参数会保留。即使正文写明“运行完整 E2E”或某个具体任务,只要没有字面量 `action=<name>`,仍从顶层菜单开始。
|
|
106
|
+
|
|
107
|
+
这是可暂停、可恢复的流程。外部检视或审批有新进展后,可再次提交 `/msdevflow` 和 PR 信息,并在菜单中选择“完整端到端流程”:
|
|
102
108
|
|
|
103
109
|
```text
|
|
104
|
-
/msdevflow
|
|
105
110
|
恢复 https://gitcode.com/<canonical-owner>/<repo>/pulls/<number> 的完整作者 E2E。
|
|
106
111
|
本地仓库:<local-repository-absolute-path>
|
|
107
112
|
canonical:<canonical-owner>/<repo>
|
|
@@ -172,7 +177,7 @@ Agent 写入的 PR/Issue 正文、评论和 discussion 回复末尾会附加:
|
|
|
172
177
|
`msdevflow`:
|
|
173
178
|
|
|
174
179
|
- 不读取、打印或转存 GitCode Token;
|
|
175
|
-
-
|
|
180
|
+
- Token 只粘贴到 GitCode CLI 自己的终端提示,不放入 Agent 对话、命令参数、项目文件、URL 或日志;
|
|
176
181
|
- openLiBing Token 只在当前 Python 认证进程内短暂使用,不输出、不落盘;
|
|
177
182
|
- 永远不会自动下载 Playwright Chromium;
|
|
178
183
|
- 优先使用系统 Chrome/Edge,其次使用已经安装的 Playwright Chromium;
|
|
@@ -212,7 +217,7 @@ npx msdevflow setup
|
|
|
212
217
|
<gitcode-command> auth status --json
|
|
213
218
|
```
|
|
214
219
|
|
|
215
|
-
|
|
220
|
+
用户在官方页面创建 Token,并只粘贴到 GitCode CLI 自己的终端提示;不要把 Token 交给 Agent 或放入命令参数、项目文件、URL 或日志。
|
|
216
221
|
|
|
217
222
|
### `blocked: browser-required`
|
|
218
223
|
|
package/lib/bootstrap.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
3
|
import {
|
|
4
4
|
closeSync,
|
|
@@ -265,6 +265,22 @@ function executableCommand(executable, args, platform) {
|
|
|
265
265
|
};
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
+
export function interactiveExecutableCommand(executable, args, platform) {
|
|
269
|
+
if (platform !== "win32" || !/\.(?:cmd|bat)$/i.test(executable)) {
|
|
270
|
+
return executableCommand(executable, args, platform);
|
|
271
|
+
}
|
|
272
|
+
const values = [executable, ...args];
|
|
273
|
+
if (values.some((value) => /["\r\n]/.test(value))) {
|
|
274
|
+
throw new BootstrapError("Interactive Windows command contains unsupported characters.", 3);
|
|
275
|
+
}
|
|
276
|
+
const commandLine = `"${values.map((value) => `"${value}"`).join(" ")}"`;
|
|
277
|
+
return {
|
|
278
|
+
command: "cmd.exe",
|
|
279
|
+
args: ["/d", "/s", "/c", commandLine],
|
|
280
|
+
windowsVerbatimArguments: true,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
268
284
|
export function formatCommand(invocation) {
|
|
269
285
|
return [invocation.command, ...invocation.args]
|
|
270
286
|
.map((value) => (/^[A-Za-z0-9_./:@=\\-]+$/.test(value) ? value : JSON.stringify(value)))
|
|
@@ -279,6 +295,7 @@ function execute(invocation, { allowFailure = false, inherit = false, outputToSt
|
|
|
279
295
|
: "pipe",
|
|
280
296
|
maxBuffer: 20 * 1024 * 1024,
|
|
281
297
|
windowsHide: false,
|
|
298
|
+
windowsVerbatimArguments: invocation.windowsVerbatimArguments === true,
|
|
282
299
|
});
|
|
283
300
|
if (result.error) {
|
|
284
301
|
throw new BootstrapError(`Failed to run ${invocation.command}: ${result.error.message}`);
|
|
@@ -295,6 +312,54 @@ function execute(invocation, { allowFailure = false, inherit = false, outputToSt
|
|
|
295
312
|
};
|
|
296
313
|
}
|
|
297
314
|
|
|
315
|
+
function executeAsync(invocation, { allowFailure = false, inherit = false, outputToStderr = false } = {}) {
|
|
316
|
+
return new Promise((resolve, reject) => {
|
|
317
|
+
const child = spawn(invocation.command, invocation.args, {
|
|
318
|
+
stdio: inherit
|
|
319
|
+
? ["inherit", outputToStderr ? process.stderr : "inherit", "inherit"]
|
|
320
|
+
: ["ignore", "pipe", "pipe"],
|
|
321
|
+
windowsHide: false,
|
|
322
|
+
windowsVerbatimArguments: invocation.windowsVerbatimArguments === true,
|
|
323
|
+
});
|
|
324
|
+
const stdout = [];
|
|
325
|
+
const stderr = [];
|
|
326
|
+
let outputBytes = 0;
|
|
327
|
+
let outputError = null;
|
|
328
|
+
const capture = (chunks) => (chunk) => {
|
|
329
|
+
outputBytes += chunk.length;
|
|
330
|
+
if (outputBytes > 20 * 1024 * 1024) {
|
|
331
|
+
outputError = new BootstrapError(`Command output exceeded 20 MiB: ${formatCommand(invocation)}`);
|
|
332
|
+
child.kill();
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
chunks.push(chunk);
|
|
336
|
+
};
|
|
337
|
+
child.stdout?.on("data", capture(stdout));
|
|
338
|
+
child.stderr?.on("data", capture(stderr));
|
|
339
|
+
child.once("error", (error) => {
|
|
340
|
+
reject(new BootstrapError(`Failed to run ${invocation.command}: ${error.message}`));
|
|
341
|
+
});
|
|
342
|
+
child.once("close", (code) => {
|
|
343
|
+
if (outputError) {
|
|
344
|
+
reject(outputError);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
const status = code ?? 1;
|
|
348
|
+
const result = {
|
|
349
|
+
status,
|
|
350
|
+
stdout: Buffer.concat(stdout).toString("utf8"),
|
|
351
|
+
stderr: Buffer.concat(stderr).toString("utf8"),
|
|
352
|
+
};
|
|
353
|
+
if (status !== 0 && !allowFailure) {
|
|
354
|
+
const detail = inherit ? "" : `\n${(result.stderr || result.stdout).trim()}`;
|
|
355
|
+
reject(new BootstrapError(`Command failed (${status}): ${formatCommand(invocation)}${detail}`));
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
resolve(result);
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
298
363
|
function runOptional(run, invocation) {
|
|
299
364
|
try {
|
|
300
365
|
return run(invocation, { allowFailure: true });
|
|
@@ -306,6 +371,55 @@ function runOptional(run, invocation) {
|
|
|
306
371
|
}
|
|
307
372
|
}
|
|
308
373
|
|
|
374
|
+
async function runOptionalAsync(run, invocation) {
|
|
375
|
+
try {
|
|
376
|
+
return await run(invocation, { allowFailure: true });
|
|
377
|
+
} catch (error) {
|
|
378
|
+
if (error instanceof BootstrapError && error.message.startsWith("Failed to run ")) {
|
|
379
|
+
return { status: 1, stdout: "", stderr: error.message };
|
|
380
|
+
}
|
|
381
|
+
throw error;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export function createProgress(output, writeLine, timers = { setInterval, clearInterval }) {
|
|
386
|
+
const interactive = Boolean(output?.isTTY && typeof output.write === "function");
|
|
387
|
+
const frames = ["|", "/", "-", "\\"];
|
|
388
|
+
return {
|
|
389
|
+
async run(label, operation) {
|
|
390
|
+
if (!interactive) {
|
|
391
|
+
writeLine(`${label}...`);
|
|
392
|
+
try {
|
|
393
|
+
const result = await operation();
|
|
394
|
+
writeLine(`${label}: done`);
|
|
395
|
+
return result;
|
|
396
|
+
} catch (error) {
|
|
397
|
+
writeLine(`${label}: failed`);
|
|
398
|
+
throw error;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
let frame = 0;
|
|
403
|
+
const render = () => {
|
|
404
|
+
output.write(`\r${frames[frame]} ${label}`);
|
|
405
|
+
frame = (frame + 1) % frames.length;
|
|
406
|
+
};
|
|
407
|
+
render();
|
|
408
|
+
const timer = timers.setInterval(render, 80);
|
|
409
|
+
try {
|
|
410
|
+
const result = await operation();
|
|
411
|
+
timers.clearInterval(timer);
|
|
412
|
+
output.write(`\r[done] ${label}\n`);
|
|
413
|
+
return result;
|
|
414
|
+
} catch (error) {
|
|
415
|
+
timers.clearInterval(timer);
|
|
416
|
+
output.write(`\r[failed] ${label}\n`);
|
|
417
|
+
throw error;
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
309
423
|
function verifyFile(file, missingMessage) {
|
|
310
424
|
let descriptor;
|
|
311
425
|
try {
|
|
@@ -792,8 +906,8 @@ function writeWrapper(details, platform) {
|
|
|
792
906
|
}
|
|
793
907
|
}
|
|
794
908
|
|
|
795
|
-
function installGitcode(plan, run, platform) {
|
|
796
|
-
|
|
909
|
+
async function installGitcode(plan, runLong, run, platform) {
|
|
910
|
+
await runLong(plan.installInvocation);
|
|
797
911
|
if (plan.gitcodeInstall.mode === "coexist") {
|
|
798
912
|
verifyFile(
|
|
799
913
|
plan.gitcodeInstall.cliTarget,
|
|
@@ -885,10 +999,10 @@ function cliInvoker(cliExecutable, run, platform) {
|
|
|
885
999
|
return (args, options) => run(executableCommand(cliExecutable, args, platform), options);
|
|
886
1000
|
}
|
|
887
1001
|
|
|
888
|
-
function validateCore(cliExecutable, run, platform) {
|
|
1002
|
+
async function validateCore(cliExecutable, run, platform) {
|
|
889
1003
|
const invoke = cliInvoker(cliExecutable, run, platform);
|
|
890
|
-
const version = invoke(["version"]).stdout.trim();
|
|
891
|
-
const doctor = invoke(["doctor", "install", "--json"], { allowFailure: true });
|
|
1004
|
+
const version = (await invoke(["version"])).stdout.trim();
|
|
1005
|
+
const doctor = await invoke(["doctor", "install", "--json"], { allowFailure: true });
|
|
892
1006
|
const missing = [];
|
|
893
1007
|
try {
|
|
894
1008
|
const metadata = JSON.parse(doctor.stdout);
|
|
@@ -899,7 +1013,7 @@ function validateCore(cliExecutable, run, platform) {
|
|
|
899
1013
|
missing.push("doctor install (invalid JSON)");
|
|
900
1014
|
}
|
|
901
1015
|
for (const [schema, requiredFlags] of Object.entries(REQUIRED_SCHEMAS)) {
|
|
902
|
-
const result = invoke(["schema", schema], { allowFailure: true });
|
|
1016
|
+
const result = await invoke(["schema", schema], { allowFailure: true });
|
|
903
1017
|
if (result.status !== 0) {
|
|
904
1018
|
missing.push(schema);
|
|
905
1019
|
continue;
|
|
@@ -916,28 +1030,45 @@ function validateCore(cliExecutable, run, platform) {
|
|
|
916
1030
|
missing.push(`${schema} (invalid schema JSON)`);
|
|
917
1031
|
}
|
|
918
1032
|
}
|
|
919
|
-
if (invoke(["api", "--help"], { allowFailure: true }).status !== 0) {
|
|
1033
|
+
if ((await invoke(["api", "--help"], { allowFailure: true })).status !== 0) {
|
|
920
1034
|
missing.push("api");
|
|
921
1035
|
}
|
|
922
1036
|
if (missing.length) {
|
|
923
1037
|
throw new BootstrapError(`GitCode CLI is missing required capabilities: ${missing.join(", ")}.`);
|
|
924
1038
|
}
|
|
925
|
-
const authentication = safeAuthStatus(
|
|
1039
|
+
const authentication = safeAuthStatus(
|
|
1040
|
+
(await invoke(["auth", "status", "--json"], { allowFailure: true })).stdout,
|
|
1041
|
+
);
|
|
926
1042
|
return { version, authentication, capabilities: "passed" };
|
|
927
1043
|
}
|
|
928
1044
|
|
|
929
|
-
function authenticateGitcode(
|
|
1045
|
+
async function authenticateGitcode(
|
|
1046
|
+
cliExecutable,
|
|
1047
|
+
authentication,
|
|
1048
|
+
runInteractive,
|
|
1049
|
+
runLong,
|
|
1050
|
+
platform,
|
|
1051
|
+
write,
|
|
1052
|
+
progress,
|
|
1053
|
+
jsonOutput,
|
|
1054
|
+
) {
|
|
930
1055
|
if (authentication.logged_in) {
|
|
931
1056
|
return authentication;
|
|
932
1057
|
}
|
|
933
|
-
const
|
|
1058
|
+
const invokeLong = cliInvoker(cliExecutable, runLong, platform);
|
|
934
1059
|
write("GitCode authentication is required. Opening the official browser login.");
|
|
935
|
-
write("
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1060
|
+
write("Create the token in the browser, paste it at the CLI prompt, then press Enter.");
|
|
1061
|
+
write("Waiting for the GitCode CLI login process to finish; token verification can take up to 30 seconds.");
|
|
1062
|
+
await runInteractive(
|
|
1063
|
+
interactiveExecutableCommand(cliExecutable, ["auth", "login", "--web"], platform),
|
|
1064
|
+
{
|
|
1065
|
+
inherit: true,
|
|
1066
|
+
outputToStderr: jsonOutput,
|
|
1067
|
+
},
|
|
1068
|
+
);
|
|
1069
|
+
authentication = await progress.run("Verifying GitCode authentication", async () => safeAuthStatus(
|
|
1070
|
+
(await invokeLong(["auth", "status", "--json"], { allowFailure: true })).stdout,
|
|
1071
|
+
));
|
|
941
1072
|
if (!authentication.logged_in) {
|
|
942
1073
|
throw new BootstrapError("GitCode browser login did not produce an authenticated CLI session.", 2);
|
|
943
1074
|
}
|
|
@@ -969,8 +1100,16 @@ export async function runSetup(options, dependencies = {}) {
|
|
|
969
1100
|
const platform = dependencies.platform || process.platform;
|
|
970
1101
|
const environment = dependencies.environment || process.env;
|
|
971
1102
|
const run = dependencies.run || execute;
|
|
1103
|
+
const runLong = dependencies.runLong || dependencies.run || executeAsync;
|
|
1104
|
+
const runInteractive = dependencies.runInteractive || dependencies.run || executeAsync;
|
|
972
1105
|
const write = dependencies.write || ((line) => console.log(line));
|
|
973
1106
|
const writePlan = dependencies.writePlan || write;
|
|
1107
|
+
const writeProgress = options.json ? writePlan : write;
|
|
1108
|
+
const progress = dependencies.progress || createProgress(
|
|
1109
|
+
dependencies.progressOutput || (options.json ? process.stderr : process.stdout),
|
|
1110
|
+
writeProgress,
|
|
1111
|
+
dependencies.timers,
|
|
1112
|
+
);
|
|
974
1113
|
const targetSelection = await resolveClientTargets(
|
|
975
1114
|
options,
|
|
976
1115
|
run,
|
|
@@ -1003,11 +1142,14 @@ export async function runSetup(options, dependencies = {}) {
|
|
|
1003
1142
|
run(namedCommand("npm", ["--version"], platform));
|
|
1004
1143
|
const npmPrefix = run(namedCommand("npm", ["config", "get", "prefix"], platform)).stdout.trim();
|
|
1005
1144
|
const gitVersion = run(namedCommand("git", ["--version"], platform)).stdout.trim();
|
|
1006
|
-
const latestResult =
|
|
1007
|
-
"
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1145
|
+
const latestResult = await progress.run(
|
|
1146
|
+
"Checking the latest GitCode CLI version",
|
|
1147
|
+
() => runOptionalAsync(runLong, namedCommand(
|
|
1148
|
+
"npm",
|
|
1149
|
+
["view", "@gitcode-cli/cli", "version", `--registry=${REGISTRY}`],
|
|
1150
|
+
platform,
|
|
1151
|
+
)),
|
|
1152
|
+
);
|
|
1011
1153
|
const diagnosis = diagnoseGitcode(run, platform);
|
|
1012
1154
|
diagnosis.officialLatest = latestResult.status === 0 ? latestResult.stdout.trim() || null : null;
|
|
1013
1155
|
const gitcodeInstall = gitcodeInstallDetails(diagnosis.classification, environment, platform, npmPrefix);
|
|
@@ -1052,27 +1194,40 @@ export async function runSetup(options, dependencies = {}) {
|
|
|
1052
1194
|
validateWrapper(gitcodeInstall, platform);
|
|
1053
1195
|
}
|
|
1054
1196
|
|
|
1055
|
-
const installed =
|
|
1056
|
-
|
|
1197
|
+
const installed = await progress.run(
|
|
1198
|
+
"Installing the official GitCode CLI",
|
|
1199
|
+
() => installGitcode(plan, runLong, run, platform),
|
|
1200
|
+
);
|
|
1201
|
+
const gitcode = await progress.run(
|
|
1202
|
+
"Validating GitCode CLI capabilities",
|
|
1203
|
+
() => validateCore(installed.executable, runLong, platform),
|
|
1204
|
+
);
|
|
1057
1205
|
gitcode.command = installed.command;
|
|
1058
1206
|
gitcode.executable = installed.executable;
|
|
1059
1207
|
|
|
1060
|
-
run(pipInvocation);
|
|
1061
|
-
run(
|
|
1062
|
-
|
|
1208
|
+
await progress.run("Installing reviewed Python dependencies", () => runLong(pipInvocation));
|
|
1209
|
+
await progress.run(
|
|
1210
|
+
"Verifying the Playwright Python package",
|
|
1211
|
+
() => runLong({ command: python.executable, args: ["-c", "import playwright.sync_api"] }),
|
|
1212
|
+
);
|
|
1213
|
+
gitcode.authentication = await authenticateGitcode(
|
|
1063
1214
|
installed.executable,
|
|
1064
1215
|
gitcode.authentication,
|
|
1065
|
-
|
|
1216
|
+
runInteractive,
|
|
1217
|
+
runLong,
|
|
1066
1218
|
platform,
|
|
1067
|
-
|
|
1219
|
+
writeProgress,
|
|
1220
|
+
progress,
|
|
1068
1221
|
options.json,
|
|
1069
1222
|
);
|
|
1070
|
-
const skills =
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1223
|
+
const skills = await progress.run("Installing msdevflow skill targets", async () => (
|
|
1224
|
+
skillInstalls.map(({ layout, details }) => ({
|
|
1225
|
+
kind: layout.kind,
|
|
1226
|
+
clients: layout.clients,
|
|
1227
|
+
status: installBundledSkill(details),
|
|
1228
|
+
target: layout.workflowDir,
|
|
1229
|
+
}))
|
|
1230
|
+
));
|
|
1076
1231
|
const result = {
|
|
1077
1232
|
state: "ready",
|
|
1078
1233
|
git: gitVersion,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "msdevflow",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
4
4
|
"description": "Install the msdevflow GitCode skill and its runtime dependencies",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
"bin/",
|
|
11
11
|
"lib/",
|
|
12
12
|
"skill/",
|
|
13
|
-
"!skill/msdevflow/scripts/test_openlibing_ci.py"
|
|
13
|
+
"!skill/msdevflow/scripts/test_openlibing_ci.py",
|
|
14
|
+
"!skill/**/__pycache__/**",
|
|
15
|
+
"!skill/**/*.pyc"
|
|
14
16
|
],
|
|
15
17
|
"scripts": {
|
|
16
18
|
"check": "node --check bin/msdevflow.js && node --check lib/bootstrap.js",
|
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
# msdevflow
|
|
2
2
|
|
|
3
|
-
通用 GitCode Issue 到 PR 合入 workflow skill。它保留一个统一 skill
|
|
3
|
+
通用 GitCode Issue 到 PR 合入 workflow skill。它保留一个统一 skill:显式 `action=<name>` 直接执行单项能力;未提供 action 时先通过客户端原生选择菜单路由,再读取仓库或访问 GitCode。
|
|
4
4
|
|
|
5
5
|
## 1. Action 模型
|
|
6
6
|
|
|
7
|
+
无 action 调用的菜单为:
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
选择运行方式
|
|
11
|
+
├─ 完整端到端流程
|
|
12
|
+
├─ 作者工作流
|
|
13
|
+
│ ├─ Issue 管理 -> discover | create-issue | issue
|
|
14
|
+
│ ├─ 开发与交付 -> develop | pr | ci
|
|
15
|
+
│ └─ PR 后续 -> feedback | merge
|
|
16
|
+
└─ 独立工具 -> openlibing-auth | code-review
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Claude Code 使用 `AskUserQuestion`,Codex 使用 `request_user_input`,OpenCode 使用 `question`。每次只显示当前层且不超过 3 项;没有可用选择工具时只输出当前层编号选项并等待回复。菜单选择前不探测仓库、不调用 GitCode、不读写远端,也不加载 action reference。选中叶子后等同于显式 action,且保留原调用正文中的 URL、路径、仓库和模式。
|
|
20
|
+
|
|
7
21
|
支持:
|
|
8
22
|
|
|
9
23
|
| action | 能力 | 完成后行为 |
|
|
@@ -28,7 +42,7 @@
|
|
|
28
42
|
|
|
29
43
|
## 2. 完整作者 E2E
|
|
30
44
|
|
|
31
|
-
|
|
45
|
+
无 action 菜单选中“完整端到端流程”后,从环境、CLI 能力和仓库画像开始运行完整流程:
|
|
32
46
|
|
|
33
47
|
```text
|
|
34
48
|
discover -> issue -> develop -> pr -> ci
|
|
@@ -55,9 +69,9 @@ discover -> issue -> develop -> pr -> ci
|
|
|
55
69
|
|
|
56
70
|
## 4. 调用示例
|
|
57
71
|
|
|
58
|
-
以下示例使用 Claude Code 的 `/msdevflow` 形式。Codex 使用 `$msdevflow
|
|
72
|
+
以下示例使用 Claude Code 的 `/msdevflow` 形式。Codex 使用 `$msdevflow` 或从 `/skills` 选择;OpenCode 使用 `/skills` 选择 `msdevflow`,不要用 `@` 查找 Skill。没有 `action=` 时,提交后先出现分级菜单;显式 `action=<name>` 则不显示菜单,直接路由。无论客户端入口如何,正文中的 action 和其余任务参数保持相同。
|
|
59
73
|
|
|
60
|
-
###
|
|
74
|
+
### 通过菜单选择完整 E2E
|
|
61
75
|
|
|
62
76
|
```text
|
|
63
77
|
/msdevflow
|
|
@@ -206,7 +220,7 @@ git --version
|
|
|
206
220
|
<gitcode-command> auth login --web
|
|
207
221
|
```
|
|
208
222
|
|
|
209
|
-
|
|
223
|
+
GitCode CLI 会打开官方页面让用户创建 Token,再在 CLI 自己的终端提示中接收粘贴内容并验证。Token 只能粘贴到该 CLI 提示;不要放入 Agent 对话、命令参数、项目文件、README、URL 或日志。`msdevflow` 父进程不读取或输出 Token;如需手动恢复认证,在自己的终端运行同一 `--web` 命令。
|
|
210
224
|
|
|
211
225
|
## 6. 安装
|
|
212
226
|
|
|
@@ -263,7 +277,7 @@ npm 包尚未发布时,可从当前源码/发布目录运行等价入口:
|
|
|
263
277
|
node ./msdevflow/bin/msdevflow.js setup
|
|
264
278
|
```
|
|
265
279
|
|
|
266
|
-
`setup` 不下载或启动 Chromium
|
|
280
|
+
`setup` 不下载或启动 Chromium。GitCode Token 只由 GitCode CLI 自己的终端提示接收并验证;setup 父进程不读取、输出或转存 Token,也不要求用户把 Token 传给 Agent。每个已有 skill 目标只有 manifest 明确标识为 `msdevflow` 时才允许更新,来源不明的目录、文件或符号链接会被拒绝覆盖。安装后重启或重新加载对应客户端。
|
|
267
281
|
|
|
268
282
|
## 7. 仓库画像和 PR 模板
|
|
269
283
|
|
package/skill/msdevflow/SKILL.md
CHANGED
|
@@ -1,20 +1,52 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: msdevflow
|
|
3
3
|
description: >
|
|
4
|
-
通用 GitCode Issue 到 PR
|
|
4
|
+
通用 GitCode Issue 到 PR 合入工作流。无 action 调用时通过宿主原生选择菜单路由到完整作者流程或独立能力;也支持通过 action=discover、create-issue、issue、develop、pr、ci、openlibing-auth、feedback、code-review 或 merge 直接执行单个能力。适用于发现、新建或接取 GitCode Issue、开发并创建 PR、处理 CI、验证 openLiBing OAuth、处理自己 PR 的检视意见、检视他人 PR,以及在独立审批和显式确认后合入。仅适用于已授权的 GitCode 仓库。
|
|
5
5
|
compatibility: Requires Git, Node.js >=18, Python >=3.10, GitCode access, and the setup-installed GitCode CLI. Supports Claude Code, Codex, and OpenCode.
|
|
6
6
|
metadata:
|
|
7
|
-
version: 2.
|
|
7
|
+
version: 2.1.0
|
|
8
8
|
source: msdevflow
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# msdevflow:GitCode Issue 到 PR 合入
|
|
12
12
|
|
|
13
|
-
把 GitCode 工作拆成可显式调用的 action
|
|
13
|
+
把 GitCode 工作拆成可显式调用的 action;无 action 调用先通过选择菜单路由到完整作者 E2E 或独立能力。选定流程后再探测仓库规范和 CLI 能力,不硬编码组织、默认分支、CI、分支命名、合并方式或机器人协议。
|
|
14
14
|
|
|
15
15
|
## Action 路由
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
识别显式参数 `action=<name>`;显式 action 直接路由,不显示菜单。未知 action 停止并列出有效值,不做远端写入。仅当用户调用本 skill 且没有提供 `action=` 时,必须先执行下述菜单路由,不得在选择完成前探测仓库、调用 GitCode、读写远端或加载 action reference。
|
|
18
|
+
|
|
19
|
+
### 无 action 菜单路由
|
|
20
|
+
|
|
21
|
+
优先使用当前宿主提供的原生单选工具:Claude Code 使用 `AskUserQuestion`,Codex 使用 `request_user_input`,OpenCode 使用 `question`;不要仅因工具名称不同而跳过菜单。每次只问一个问题,选项不超过 3 个,等待用户选择后再进入下一层:
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
选择运行方式
|
|
25
|
+
├─ 完整端到端流程 -> full-e2e
|
|
26
|
+
├─ 作者工作流
|
|
27
|
+
│ ├─ Issue 管理
|
|
28
|
+
│ │ ├─ discover
|
|
29
|
+
│ │ ├─ create-issue
|
|
30
|
+
│ │ └─ issue
|
|
31
|
+
│ ├─ 开发与交付
|
|
32
|
+
│ │ ├─ develop
|
|
33
|
+
│ │ ├─ pr
|
|
34
|
+
│ │ └─ ci
|
|
35
|
+
│ └─ PR 后续
|
|
36
|
+
│ ├─ feedback
|
|
37
|
+
│ └─ merge
|
|
38
|
+
└─ 独立工具
|
|
39
|
+
├─ openlibing-auth
|
|
40
|
+
└─ code-review
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
菜单规则:
|
|
44
|
+
|
|
45
|
+
1. 顶层“完整端到端流程”不是 action;选中后按“完整作者 E2E”执行,并在内部将本次路由记为 `full-e2e`。
|
|
46
|
+
2. 每级原生菜单都把推荐项放在首位,并按宿主约定标明推荐:顶层推荐“完整端到端流程”;作者工作流分类推荐“Issue 管理”;各叶子层分别推荐 `discover`、`develop`、`feedback` 和 `openlibing-auth`。不得为了改变推荐项而提前探测仓库或远端。
|
|
47
|
+
3. 选中叶子 action 后,等同于用户显式提供了对应 `action=<name>`,严格遵守显式 action 契约;保留调用正文中已经给出的 URL、路径、仓库和模式等参数。
|
|
48
|
+
4. 宿主没有可用选择工具、选择工具在当前模式被禁用,或处于非交互运行时,只输出当前层的编号文本选项并停止等待用户回复;不得自行选择默认项,也不得把多层菜单一次性展开。
|
|
49
|
+
5. 只要调用中没有字面量 `action=<name>`,即使自然语言看似指向某个流程,也必须从顶层菜单开始;不得根据意图猜测跳过菜单。菜单选择属于当前 skill 调用,选定后立即继续,不要求用户重新输入 `/msdevflow`。
|
|
18
50
|
|
|
19
51
|
| action | 目标 | 主要输入 | 完成后立即停止于 | 必读文件 |
|
|
20
52
|
|---|---|---|---|---|
|
|
@@ -29,7 +61,7 @@ metadata:
|
|
|
29
61
|
| `code-review` | 检视他人的 PR,发布 finding 或 `/lgtm` | 他人 PR | `review-findings`、`review-passed`、`review-incomplete`、`waiting` 或 `blocked` | [references/code-review.md](references/code-review.md) |
|
|
30
62
|
| `merge` | 核验门禁并在最终确认后合入 | canonical PR | `merged`、`waiting` 或 `blocked` | [references/review-and-merge.md](references/review-and-merge.md) |
|
|
31
63
|
|
|
32
|
-
`code-review`
|
|
64
|
+
`code-review` 只能通过显式 `action=code-review` 或“独立工具”菜单叶子独立调用,永不进入作者 E2E,不得检视或批准当前账号自己的 PR。它固定当前 head,完整检视权威 diff、必要上下文和既有 discussions:有高置信度问题时发布经确认且逐字回读的 finding,不发送 `/lgtm`;没有问题、没有有效未解决意见且覆盖完整时,在针对当前 PR/head 明确确认后先发布带尾签摘要,再原样发送 `/lgtm`。它不发送 `/approve`、`/merge`,不修改作者代码。
|
|
33
65
|
|
|
34
66
|
## 显式 action 契约
|
|
35
67
|
|
|
@@ -41,9 +73,9 @@ metadata:
|
|
|
41
73
|
6. `ci` 和完整 E2E 在 openLiBing 只读接口返回 401/403 时可运行 OAuth 认证子流程并恢复原流程;这是读取当前 CI 证据的必要步骤,不视为越界执行显式 `openlibing-auth` action。
|
|
42
74
|
7. 所有远端写仍遵循 guided/autonomous 授权、UTF-8、尾签、幂等和最终 merge 确认规则。
|
|
43
75
|
|
|
44
|
-
##
|
|
76
|
+
## 完整作者 E2E
|
|
45
77
|
|
|
46
|
-
|
|
78
|
+
只有无 action 菜单选中 `full-e2e` 后,才从 Phase 0 环境、CLI 能力和仓库画像开始运行完整流程;调用正文可以提供完整 E2E 所需的 URL、路径、仓库和模式,但不能替代菜单选择。“从头开始”只表示重新读取事实,不表示重复创建资源或重复评论。按证据幂等跳过已完成步骤:
|
|
47
79
|
|
|
48
80
|
```text
|
|
49
81
|
discover -> issue -> develop -> pr -> ci
|
|
@@ -65,7 +97,7 @@ discover -> issue -> develop -> pr -> ci
|
|
|
65
97
|
1. 固定 `gitcode_command`:无 Python 同名工具时为 `gitcode`;有 Python `gitcode` 时保留它并使用 `gitcode-npm`。来源不明时停止,不覆盖。
|
|
66
98
|
2. 检查当前目录;若不是 Git 仓库,自动探测直接子目录中的 Git 仓库及 remotes。
|
|
67
99
|
3. 确认 canonical repository、source repository、operation target、当前账号和授权范围。
|
|
68
|
-
4. 除纯只读 `discover` 外,显式 action 先从 [references/setup-and-issue.md](references/setup-and-issue.md) 只读取公共环境、仓库上下文和最小充分仓库画像,不执行其中的 Issue 接取业务;随后只加载该 action 对应 reference 及实际触发的深层规则。`create-issue` 的模板选择和写入只按 [references/create-issue.md](references/create-issue.md),不执行候选发现或既有 Issue 核验。`code-review` 只使用公共只读画像和 [references/code-review.md](references/code-review.md),不加载作者 feedback/merge
|
|
100
|
+
4. 除纯只读 `discover` 外,显式 action 先从 [references/setup-and-issue.md](references/setup-and-issue.md) 只读取公共环境、仓库上下文和最小充分仓库画像,不执行其中的 Issue 接取业务;随后只加载该 action 对应 reference 及实际触发的深层规则。`create-issue` 的模板选择和写入只按 [references/create-issue.md](references/create-issue.md),不执行候选发现或既有 Issue 核验。`code-review` 只使用公共只读画像和 [references/code-review.md](references/code-review.md),不加载作者 feedback/merge 规则。`full-e2e` 也只按当前位置渐进加载。
|
|
69
101
|
5. CLI 缺失、命令不兼容或版本不足时读 [references/command-capabilities.md](references/command-capabilities.md),停止当前 action 并提示用户运行独立 setup;setup 成功后恢复原 action。
|
|
70
102
|
6. 进入 autonomous、远端写、危险动作或恢复时读 [references/state-and-safety.md](references/state-and-safety.md)。
|
|
71
103
|
7. 中断恢复、写结果不确定或最终报告时读 [references/recovery.md](references/recovery.md)。
|
|
@@ -107,7 +107,7 @@ commit/head SHA
|
|
|
107
107
|
- 不采纳:spec/代码/测试依据;
|
|
108
108
|
6. 统一换行为 `\n` 后逐字比较完整远端回复与预期正文,并验证末尾恰好存在一次尾签;发现 `?`、乱码、截断、尾签缺失、重复或其他不一致时先原地修复,未修复前不得 resolve;
|
|
109
109
|
7. 正文回读通过后,仓库规则允许且权限具备时 resolve,再回读确认 `resolved=true`;
|
|
110
|
-
8. 产生新 head 时,显式 `action=feedback` 停止并建议 `action=ci
|
|
110
|
+
8. 产生新 head 时,显式 `action=feedback` 停止并建议 `action=ci`;菜单选中的完整作者 E2E(`full-e2e`) 由编排器随后路由到 `ci`。
|
|
111
111
|
|
|
112
112
|
不要只发一条总体总结替代逐条回复。汇总应在逐条闭环后发布。
|
|
113
113
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Action `create-issue`
|
|
2
2
|
|
|
3
|
-
显式 `action=create-issue` 用于在一个已授权的 canonical repository 中创建一个 GitCode Issue。它是独立 action:创建并回读确认后立即停止,不执行 `discover`、`issue`、接取、查重、代码核验、开发或 PR
|
|
3
|
+
显式 `action=create-issue` 用于在一个已授权的 canonical repository 中创建一个 GitCode Issue。它是独立 action:创建并回读确认后立即停止,不执行 `discover`、`issue`、接取、查重、代码核验、开发或 PR 流程,也不进入菜单选中的完整作者 E2E(`full-e2e`)。
|
|
4
4
|
|
|
5
5
|
## 输入和边界
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Action `develop`:分析、设计与开发
|
|
2
2
|
|
|
3
|
-
只在显式 `action=develop
|
|
3
|
+
只在显式 `action=develop`,或菜单选中的完整作者 E2E(`full-e2e`) 已达到 `verified` 时读取。显式 action 启动时先只读确认 Issue 已核验且没有重复实现;前置不足时返回 `blocked` 并建议 `action=issue`,不得自动接取或补跑核验。
|
|
4
4
|
|
|
5
5
|
## Phase 3:需求分析与设计决策树
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Action `pr` 与 `ci`
|
|
2
2
|
|
|
3
|
-
只在显式 `action=pr`、`action=ci
|
|
3
|
+
只在显式 `action=pr`、`action=ci`,或菜单选中的完整作者 E2E(`full-e2e`) 路由到对应位置时读取。任何远端写前同时读取 [state-and-safety.md](state-and-safety.md)。CI 触发或失败时再读取 [ci-and-review.md](ci-and-review.md),不要提前加载。
|
|
4
4
|
|
|
5
5
|
## Action `pr`:Commit、Push、普通 PR
|
|
6
6
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
4. 根据 action 完成条件识别最后一个有证据的状态;不能只信先前摘要或旧 run 产物。
|
|
15
15
|
5. 验证本地分支与远端 source head 是否一致。
|
|
16
16
|
6. 显式 action 只判断自身前置和完成条件:前置缺失时 `blocked`,不得运行上游;已完成时报告证据并立即停止。
|
|
17
|
-
7.
|
|
17
|
+
7. 菜单选中的完整作者 E2E(`full-e2e`) 从 Phase 0 重建仓库画像,再路由到下一未完成作者 action;不重复已完成的写操作。
|
|
18
18
|
8. `create-issue` 不执行产品查重;仅在本次创建结果不确定时按目标仓库、当前账号、启动时间、标题和完整正文做有界恢复,无法唯一证明时停止。`openlibing-auth` 的历史 profile 或旧认证摘要不证明当前授权有效;必须用本次固定 run 做真实只读请求。
|
|
19
19
|
9. `code-review` 不参与作者 E2E,也不从作者 feedback 状态恢复。恢复时重新确认 reviewer、author、PR 状态和当前 head;旧 `review_head_sha` 的分析、摘要、finding 或 `/lgtm` 不证明当前 head 已检视。
|
|
20
20
|
10. 当前 head 等于旧 `review_head_sha` 时,回读结构化 comments/discussions,并按 reviewer、head、path/position 和完整正文恢复已发布 finding;检视通过必须同时回读到绑定当前 head 的带尾签摘要和同一 reviewer 的精确 `/lgtm`。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Action `feedback` 与 `merge`
|
|
2
2
|
|
|
3
|
-
只在显式 `action=feedback`、`action=merge
|
|
3
|
+
只在显式 `action=feedback`、`action=merge`,或菜单选中的完整作者 E2E(`full-e2e`) 路由到对应位置时读取。开始时同时读取 [state-and-safety.md](state-and-safety.md);处理结构化 discussion 或 CI 时按需读取 [ci-and-review.md](ci-and-review.md)。本文件不定义 `action=code-review`。
|
|
4
4
|
|
|
5
5
|
## Action `feedback`:处理自己 PR 的检视意见
|
|
6
6
|
|
|
@@ -31,7 +31,7 @@ PR 不存在时返回 `blocked` 并建议 `action=pr`;source branch 不可写
|
|
|
31
31
|
6. 创建新 commit 并 push,不 amend、不 force push;
|
|
32
32
|
7. 每条 discussion 在线程内回复:已修复引用 commit;部分采纳、不采纳或延期说明依据;每条回复按 [state-and-safety.md](state-and-safety.md) 使用 UTF-8 安全通道并附加唯一尾签,逐字回读完整正文;
|
|
33
33
|
8. 只有回复正文和尾签回读一致后,仓库规则和权限允许时才 resolve;乱码、`?`、截断或不一致时先原地修复;
|
|
34
|
-
9. 产生新 head 时记录旧 CI 结果已失效,停止后建议 `action=ci`;不得在显式 `feedback` 内触发或监控远端 CI
|
|
34
|
+
9. 产生新 head 时记录旧 CI 结果已失效,停止后建议 `action=ci`;不得在显式 `feedback` 内触发或监控远端 CI。菜单选中的完整作者 E2E(`full-e2e`) 由编排器随后路由到 `ci`。
|
|
35
35
|
|
|
36
36
|
不要以一条总体评论替代逐条回复。处理完成后不请求重新检视、不触发检视机器人;外部 reviewer 是否重新检视由仓库和人员流程决定。
|
|
37
37
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Action `discover` 与 `issue`
|
|
2
2
|
|
|
3
|
-
在显式 `action=discover`、`action=issue
|
|
3
|
+
在显式 `action=discover`、`action=issue`,或菜单选中的完整作者 E2E(`full-e2e`) 启动时读取;显式 `action=create-issue` 只读取本文件的公共环境、仓库上下文和最小充分画像,模板和写入业务转到 [create-issue.md](create-issue.md),不得执行本文件的发现、接取或重复实现核验。`discover` 全程只读并在候选报告后立即停止;`issue` 只接取和核验指定 Issue,达到 `verified` 后立即停止,不进入开发。
|
|
4
4
|
|
|
5
5
|
显式 `action=issue` 必须提供 Issue URL 或编号。缺少目标时只做确认输入缺失所需的只读核验,随后返回 `blocked` 并建议先运行 `action=discover`;不得在本 action 内查询候选、让用户选择 Issue 或执行 `discover` 的业务。
|
|
6
6
|
|
|
@@ -98,7 +98,7 @@ pr_number(如已有)
|
|
|
98
98
|
|
|
99
99
|
## Phase 1:发现与接取
|
|
100
100
|
|
|
101
|
-
显式 `action=issue` 直接读取输入中的 Issue URL/编号;缺少目标时按本文件入口规则返回 `blocked`。显式 `action=discover`
|
|
101
|
+
显式 `action=issue` 直接读取输入中的 Issue URL/编号;缺少目标时按本文件入口规则返回 `blocked`。显式 `action=discover` 或菜单选中的完整作者 E2E(`full-e2e`) 尚无目标时,才对“无目标发现”确认出的每个 canonical 查询当前用户负责的开放 Issue:
|
|
102
102
|
|
|
103
103
|
```bash
|
|
104
104
|
<gitcode-command> issue list -R <canonical> --state open --assignee <username> --json
|