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 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
- 从目标仓库目录启动已安装目标中的任一客户端,然后调用 skill:
70
+ 从目标仓库目录启动客户端并提交 `msdevflow`:
71
71
 
72
- - Claude Code:输入 `/msdevflow`;
73
- - Codex:使用 `$msdevflow`、`/skills` 选择,或让 Codex 按描述隐式匹配;
74
- - OpenCode:通过其 skill 工具选择,或让 OpenCode 按描述隐式匹配。
72
+ - Claude Code:输入并提交 `/msdevflow`;
73
+ - Codex:使用 `$msdevflow` 或从 `/skills` 选择后提交;
74
+ - OpenCode:打开 `/skills`,选择 `msdevflow`,再提交插入的 `/msdevflow`。`@` 用于文件、reference 或 agent,不用于选择 Skill。
75
75
 
76
- 客户端 UI 和命令入口可能随版本变化,但传给 skill 的任务正文相同:
76
+ 没有字面量 `action=<name>` 时,Agent 会先通过宿主原生单选工具显示分级菜单,选择前不会探测仓库或访问 GitCode:
77
77
 
78
78
  ```text
79
- 处理 https://gitcode.com/<canonical-owner>/<repo>/issues/<number>。
80
- 本地仓库:<local-repository-absolute-path>
81
- canonical:<canonical-owner>/<repo>
82
- source:<your-account>/<repo>
83
- 使用 guided 模式,按权威远端和本地证据推进完整作者 E2E。
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
- 未指定 `action` 时,流程为:
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
- - 不要求用户把 Token 粘贴到终端、对话或项目文件;
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
- run(plan.installInvocation);
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(invoke(["auth", "status", "--json"], { allowFailure: true }).stdout);
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(cliExecutable, authentication, run, platform, write, jsonOutput) {
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 invoke = cliInvoker(cliExecutable, run, platform);
1058
+ const invokeLong = cliInvoker(cliExecutable, runLong, platform);
934
1059
  write("GitCode authentication is required. Opening the official browser login.");
935
- write("Complete the GitCode page shown by the CLI; do not paste a token into this terminal or the conversation.");
936
- invoke(["auth", "login", "--web"], {
937
- inherit: true,
938
- outputToStderr: jsonOutput,
939
- });
940
- authentication = safeAuthStatus(invoke(["auth", "status", "--json"], { allowFailure: true }).stdout);
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 = runOptional(run, namedCommand(
1007
- "npm",
1008
- ["view", "@gitcode-cli/cli", "version", `--registry=${REGISTRY}`],
1009
- platform,
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 = installGitcode(plan, run, platform);
1056
- const gitcode = validateCore(installed.executable, run, platform);
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({ command: python.executable, args: ["-c", "import playwright.sync_api"] });
1062
- gitcode.authentication = authenticateGitcode(
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
- run,
1216
+ runInteractive,
1217
+ runLong,
1066
1218
  platform,
1067
- options.json ? writePlan : write,
1219
+ writeProgress,
1220
+ progress,
1068
1221
  options.json,
1069
1222
  );
1070
- const skills = skillInstalls.map(({ layout, details }) => ({
1071
- kind: layout.kind,
1072
- clients: layout.clients,
1073
- status: installBundledSkill(details),
1074
- target: layout.workflowDir,
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.2",
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,通过显式 `action` 单独执行某一部分;未指定 `action` 时,按远端和本地证据运行完整作者端到端流程。
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
- 未指定 `action` 时,从环境、CLI 能力和仓库画像开始运行完整流程:
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`、`/skills` 或隐式匹配;OpenCode 使用 skill 工具或隐式匹配。无论客户端入口如何,正文中的 `action=<name>` 和其余任务参数保持相同。
72
+ 以下示例使用 Claude Code 的 `/msdevflow` 形式。Codex 使用 `$msdevflow` 或从 `/skills` 选择;OpenCode 使用 `/skills` 选择 `msdevflow`,不要用 `@` 查找 Skill。没有 `action=` 时,提交后先出现分级菜单;显式 `action=<name>` 则不显示菜单,直接路由。无论客户端入口如何,正文中的 action 和其余任务参数保持相同。
59
73
 
60
- ### 未指定 action:完整 E2E
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
- 用户只在 GitCode CLI 打开的官方浏览器页面完成登录。不要把 Token 粘贴到对话、setupREADME、项目文件或命令 URL 中;如需手动恢复认证,也优先在自己的终端运行同一 `--web` 命令。
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。凭证只由 GitCode CLI 和官方浏览器流程接收与保存;setup 不读取、输出或转存 Token,也不要求用户把 Token 传给 Agent。每个已有 skill 目标只有 manifest 明确标识为 `msdevflow` 时才允许更新,来源不明的目录、文件或符号链接会被拒绝覆盖。安装后重启或重新加载对应客户端。
280
+ `setup` 不下载或启动 Chromium。GitCode Token 只由 GitCode CLI 自己的终端提示接收并验证;setup 父进程不读取、输出或转存 Token,也不要求用户把 Token 传给 Agent。每个已有 skill 目标只有 manifest 明确标识为 `msdevflow` 时才允许更新,来源不明的目录、文件或符号链接会被拒绝覆盖。安装后重启或重新加载对应客户端。
267
281
 
268
282
  ## 7. 仓库画像和 PR 模板
269
283
 
@@ -1,20 +1,52 @@
1
1
  ---
2
2
  name: msdevflow
3
3
  description: >
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 仓库。
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.0.0
7
+ version: 2.1.0
8
8
  source: msdevflow
9
9
  ---
10
10
 
11
11
  # msdevflow:GitCode Issue 到 PR 合入
12
12
 
13
- 把 GitCode 工作拆成可显式调用的 action,同时保留未指定 action 时的完整作者 E2E 编排。先探测仓库规范和 CLI 能力,不硬编码组织、默认分支、CI、分支命名、合并方式或机器人协议。
13
+ 把 GitCode 工作拆成可显式调用的 action;无 action 调用先通过选择菜单路由到完整作者 E2E 或独立能力。选定流程后再探测仓库规范和 CLI 能力,不硬编码组织、默认分支、CI、分支命名、合并方式或机器人协议。
14
14
 
15
15
  ## Action 路由
16
16
 
17
- 只识别显式参数 `action=<name>`。未知 action 停止并列出有效值,不做远端写入。
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` 只能显式调用,永不进入作者 E2E,不得检视或批准当前账号自己的 PR。它固定当前 head,完整检视权威 diff、必要上下文和既有 discussions:有高置信度问题时发布经确认且逐字回读的 finding,不发送 `/lgtm`;没有问题、没有有效未解决意见且覆盖完整时,在针对当前 PR/head 明确确认后先发布带尾签摘要,再原样发送 `/lgtm`。它不发送 `/approve`、`/merge`,不修改作者代码。
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
- ## 未指定 action:完整作者 E2E
76
+ ## 完整作者 E2E
45
77
 
46
- 未提供 `action` 时,从 Phase 0 环境、CLI 能力和仓库画像开始运行完整流程;“从头开始”只表示重新读取事实,不表示重复创建资源或重复评论。按证据幂等跳过已完成步骤:
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 规则。未指定 action 也只按当前位置渐进加载。
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`;未指定 action 的完整作者 E2E 由编排器随后路由到 `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 流程,也不进入未指定 action 的完整作者 E2E
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`,或未指定 action 的完整作者 E2E 已达到 `verified` 时读取。显式 action 启动时先只读确认 Issue 已核验且没有重复实现;前置不足时返回 `blocked` 并建议 `action=issue`,不得自动接取或补跑核验。
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`,或未指定 action 的完整作者 E2E 路由到对应位置时读取。任何远端写前同时读取 [state-and-safety.md](state-and-safety.md)。CI 触发或失败时再读取 [ci-and-review.md](ci-and-review.md),不要提前加载。
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. 未指定 action 的完整 E2E 从 Phase 0 重建仓库画像,再路由到下一未完成作者 action;不重复已完成的写操作。
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`,或未指定 action 的完整作者 E2E 路由到对应位置时读取。开始时同时读取 [state-and-safety.md](state-and-safety.md);处理结构化 discussion 或 CI 时按需读取 [ci-and-review.md](ci-and-review.md)。本文件不定义 `action=code-review`。
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。未指定 action 的完整 E2E 由编排器随后路由到 `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`,或未指定 action 的完整作者 E2E 启动时读取;显式 `action=create-issue` 只读取本文件的公共环境、仓库上下文和最小充分画像,模板和写入业务转到 [create-issue.md](create-issue.md),不得执行本文件的发现、接取或重复实现核验。`discover` 全程只读并在候选报告后立即停止;`issue` 只接取和核验指定 Issue,达到 `verified` 后立即停止,不进入开发。
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` 或未指定 action 的完整作者 E2E 尚无目标时,才对“无目标发现”确认出的每个 canonical 查询当前用户负责的开放 Issue:
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