msdevflow 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +219 -0
- package/lib/bootstrap.js +224 -27
- package/package.json +1 -1
- package/skill/msdevflow/README.md +24 -9
- package/skill/msdevflow/SKILL.md +2 -1
package/README.md
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# msdevflow
|
|
2
|
+
|
|
3
|
+
`msdevflow` 是一个用于 GitCode 的 Agent Skill,可以把已授权的 Issue 安全推进到代码实现、普通 PR、CI、检视意见闭环和最终合入,也可以通过显式 `action` 只执行其中一个能力。正式支持 Claude Code、Codex 和 OpenCode;Trae 的 Skills 安装与兼容机制尚未完成可靠验证,因此当前不宣称支持。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
环境要求:
|
|
8
|
+
|
|
9
|
+
- Claude Code、Codex 或 OpenCode 中至少一个;
|
|
10
|
+
- Node.js `>=18` 和 npm;
|
|
11
|
+
- Git;
|
|
12
|
+
- Python `>=3.10` 和 pip;
|
|
13
|
+
- GitCode 账号;
|
|
14
|
+
- Chrome 或 Edge(推荐,用于 openLiBing OAuth)。
|
|
15
|
+
|
|
16
|
+
运行一步安装:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx msdevflow setup
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`setup` 默认检测 PATH 中的 Claude Code、Codex 和 OpenCode,展示完整计划并在确认后:
|
|
23
|
+
|
|
24
|
+
1. 把同一份 `msdevflow` skill 原子安装或更新到所需目标;
|
|
25
|
+
2. 安装或升级官方 `@gitcode-cli/cli@latest`;
|
|
26
|
+
3. 安装受审的 Python Playwright 依赖;
|
|
27
|
+
4. 验收 Issue、PR、结构化评论、行内评论、JSON、显式仓库和正文文件等 CLI 能力;
|
|
28
|
+
5. GitCode CLI 未认证时打开官方浏览器登录。
|
|
29
|
+
|
|
30
|
+
显式选择客户端或只读预检:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx msdevflow setup --targets claude
|
|
34
|
+
npx msdevflow setup --targets codex,opencode
|
|
35
|
+
npx msdevflow setup --targets all
|
|
36
|
+
npx msdevflow setup --dry-run --targets all
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
安装目录映射:
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
Claude Code -> ~/.claude/skills/msdevflow
|
|
43
|
+
Codex -> ~/.agents/skills/msdevflow
|
|
44
|
+
仅 OpenCode -> ~/.agents/skills/msdevflow
|
|
45
|
+
OpenCode + Claude Code -> 复用 ~/.claude/skills/msdevflow
|
|
46
|
+
OpenCode + Codex -> 复用 ~/.agents/skills/msdevflow
|
|
47
|
+
Claude Code + Codex -> 两个受管副本
|
|
48
|
+
三者同时安装 -> 两个受管副本,并提示 OpenCode 可从两处发现同名 skill
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
自定义目录:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npx msdevflow setup --skills-dir "<claude-skills-directory>"
|
|
55
|
+
npx msdevflow setup --agents-skills-dir "<shared-agent-skills-directory>"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`--skills-dir` 未配合 `--targets` 使用时保留旧语义,只安装 Claude Code 目标。没有检测到客户端时,交互运行会要求选择目标;非交互运行必须提供 `--targets`。安装完成后重启或重新加载对应客户端。如果机器已有 Python 版本的 `gitcode`,setup 会保留它,并为 npm CLI 创建 `gitcode-npm` 命令;否则使用 `gitcode`。
|
|
59
|
+
|
|
60
|
+
## 完整端到端流程
|
|
61
|
+
|
|
62
|
+
从目标仓库目录启动已安装目标中的任一客户端,然后调用 skill:
|
|
63
|
+
|
|
64
|
+
- Claude Code:输入 `/msdevflow`;
|
|
65
|
+
- Codex:使用 `$msdevflow`、`/skills` 选择,或让 Codex 按描述隐式匹配;
|
|
66
|
+
- OpenCode:通过其 skill 工具选择,或让 OpenCode 按描述隐式匹配。
|
|
67
|
+
|
|
68
|
+
客户端 UI 和命令入口可能随版本变化,但传给 skill 的任务正文相同:
|
|
69
|
+
|
|
70
|
+
```text
|
|
71
|
+
处理 https://gitcode.com/<canonical-owner>/<repo>/issues/<number>。
|
|
72
|
+
本地仓库:<local-repository-absolute-path>
|
|
73
|
+
canonical:<canonical-owner>/<repo>
|
|
74
|
+
source:<your-account>/<repo>
|
|
75
|
+
使用 guided 模式,按权威远端和本地证据推进完整作者 E2E。
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
未指定 `action` 时,流程为:
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
Issue 发现/核验
|
|
82
|
+
-> 需求分析与方案确认
|
|
83
|
+
-> 建分支、开发、本地验证
|
|
84
|
+
-> commit、push、创建普通 PR
|
|
85
|
+
-> 当前 head 的 CI 通过
|
|
86
|
+
-> 等待外部 reviewer
|
|
87
|
+
-> 处理 feedback 并重新通过 CI(可重复)
|
|
88
|
+
-> 独立审批门禁满足
|
|
89
|
+
-> 用户确认当前 PR 的最终合入
|
|
90
|
+
-> 远端回读 PR 已 merged
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
这是可暂停、可恢复的流程。外部检视或审批有新进展后,可再次运行:
|
|
94
|
+
|
|
95
|
+
```text
|
|
96
|
+
/msdevflow
|
|
97
|
+
恢复 https://gitcode.com/<canonical-owner>/<repo>/pulls/<number> 的完整作者 E2E。
|
|
98
|
+
本地仓库:<local-repository-absolute-path>
|
|
99
|
+
canonical:<canonical-owner>/<repo>
|
|
100
|
+
source:<your-account>/<repo>
|
|
101
|
+
使用 guided 模式,从权威远端和本地事实定位当前位置并继续。
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## 独立 action
|
|
105
|
+
|
|
106
|
+
显式提供 `action=<name>` 时,只运行该 action,完成后立即停止;前置条件不足时不会隐式补跑上游流程。
|
|
107
|
+
|
|
108
|
+
| action | 用途 | 完成状态或结果 |
|
|
109
|
+
|---|---|---|
|
|
110
|
+
| `discover` | 只读发现当前账号负责的候选 Issue | 列出候选和排除证据 |
|
|
111
|
+
| `create-issue` | 按仓库或 community 模板创建 Issue | `issue-created` |
|
|
112
|
+
| `issue` | 接取并核验指定 Issue | `verified` |
|
|
113
|
+
| `develop` | 分析、设计、实现和本地验证 | `locally-verified` |
|
|
114
|
+
| `pr` | commit、push 并创建普通 PR | `pr-open` |
|
|
115
|
+
| `ci` | 监控、诊断并修复当前 PR 的 CI | `ci-passed` |
|
|
116
|
+
| `openlibing-auth` | 验证 GitCode → openLiBing OAuth | `openlibing-authenticated` |
|
|
117
|
+
| `feedback` | 处理自己 PR 收到的检视意见 | `feedback-resolved` 或 `waiting-for-review` |
|
|
118
|
+
| `code-review` | 独立检视他人的 PR | `review-findings`、`review-passed` 或 `review-incomplete` |
|
|
119
|
+
| `merge` | 核验门禁并在最终确认后合入 | `merged`、`waiting` 或 `blocked` |
|
|
120
|
+
|
|
121
|
+
示例:
|
|
122
|
+
|
|
123
|
+
```text
|
|
124
|
+
/msdevflow action=create-issue
|
|
125
|
+
在 Ascend/example 提一个 bug:升级后首次启动失败……
|
|
126
|
+
使用 guided 模式,确认完整预览后再创建。
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
```text
|
|
130
|
+
/msdevflow action=ci
|
|
131
|
+
PR:https://gitcode.com/Ascend/example/pulls/456
|
|
132
|
+
本地仓库:D:\work\example
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
```text
|
|
136
|
+
/msdevflow action=code-review
|
|
137
|
+
PR:https://gitcode.com/Ascend/example/pulls/789
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`code-review` 只能检视其他作者的 PR。发现问题时发布高置信度、可操作的 finding;完整检视未发现问题且没有有效未解决意见时,经用户针对当前 PR/head 明确确认后,先发布带签名摘要,再独立评论精确的 `/lgtm`。它不发送 `/approve` 或 `/merge`,也不修改作者代码。
|
|
141
|
+
|
|
142
|
+
## PR 与检视规则
|
|
143
|
+
|
|
144
|
+
- 创建普通 PR,不创建 Draft PR,也不执行 Ready;
|
|
145
|
+
- 不主动请求检视,不触发检视机器人;
|
|
146
|
+
- 作者流程不会调用 `code-review`,也不会自行制造 LGTM/approve;
|
|
147
|
+
- CI、review 和 merge 结论始终绑定当前 PR head SHA;
|
|
148
|
+
- 最终 merge 无论运行模式如何,都需要用户针对当前 PR 明确确认。
|
|
149
|
+
|
|
150
|
+
## 中文和远端写入
|
|
151
|
+
|
|
152
|
+
Agent 写入的 PR/Issue 正文、评论和 discussion 回复末尾会附加:
|
|
153
|
+
|
|
154
|
+
```text
|
|
155
|
+
——msdevflow
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
所有人类可读远端文本通过 UTF-8 安全通道写入,并从 GitCode 逐字回读。中文变成 `?`、出现乱码、截断或尾签损坏时,流程会停止并优先原地修复。
|
|
159
|
+
|
|
160
|
+
`compile`、`/lgtm`、`/merge` 等严格机器协议保持原样,不附加尾签。
|
|
161
|
+
|
|
162
|
+
## 安全边界
|
|
163
|
+
|
|
164
|
+
`msdevflow`:
|
|
165
|
+
|
|
166
|
+
- 不读取、打印或转存 GitCode Token;
|
|
167
|
+
- 不要求用户把 Token 粘贴到终端、对话或项目文件;
|
|
168
|
+
- openLiBing Token 只在当前 Python 认证进程内短暂使用,不输出、不落盘;
|
|
169
|
+
- 永远不会自动下载 Playwright Chromium;
|
|
170
|
+
- 优先使用系统 Chrome/Edge,其次使用已经安装的 Playwright Chromium;
|
|
171
|
+
- 浏览器不可用时只返回可复制的 OAuth URL,并标记验证未完成;
|
|
172
|
+
- 不跳过 hooks、测试、CI、review 或 approval;
|
|
173
|
+
- 不默认 force push、重写共享历史、关闭 Issue或删除分支;
|
|
174
|
+
- 所有远端写都显式指定目标仓库,并在结果不确定时先回读而不是盲目重试。
|
|
175
|
+
|
|
176
|
+
## 常见阻塞
|
|
177
|
+
|
|
178
|
+
### 客户端找不到 `msdevflow`
|
|
179
|
+
|
|
180
|
+
按目标确认至少一个受支持的 manifest 存在,然后重启或重新加载客户端:
|
|
181
|
+
|
|
182
|
+
```text
|
|
183
|
+
Claude Code: ~/.claude/skills/msdevflow/SKILL.md
|
|
184
|
+
Codex: ~/.agents/skills/msdevflow/SKILL.md
|
|
185
|
+
OpenCode: 上述任一路径(按 setup 计划)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
可运行 `npx msdevflow setup --dry-run --targets <clients>` 检查目标映射。
|
|
189
|
+
|
|
190
|
+
### `blocked: bootstrap-required`
|
|
191
|
+
|
|
192
|
+
重新运行:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
npx msdevflow setup
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### GitCode 尚未登录
|
|
199
|
+
|
|
200
|
+
使用 setup 选定的命令:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
<gitcode-command> auth login --web
|
|
204
|
+
<gitcode-command> auth status --json
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
用户只在官方浏览器页面输入凭证。
|
|
208
|
+
|
|
209
|
+
### `blocked: browser-required`
|
|
210
|
+
|
|
211
|
+
安装或修复系统 Chrome/Edge 后恢复原流程。不要运行 `playwright install chromium` 作为默认解决方案。
|
|
212
|
+
|
|
213
|
+
### `waiting-for-review`
|
|
214
|
+
|
|
215
|
+
这是作者流程等待独立 reviewer 的正常状态。检视意见或审批状态变化后,使用 PR URL 恢复完整 E2E。
|
|
216
|
+
|
|
217
|
+
## 详细文档
|
|
218
|
+
|
|
219
|
+
完整 action、恢复、模板、CI、review、UTF-8 和安全契约见 [`skill/msdevflow/README.md`](skill/msdevflow/README.md)。
|
package/lib/bootstrap.js
CHANGED
|
@@ -25,6 +25,7 @@ const REGISTRY = "https://registry.npmjs.org";
|
|
|
25
25
|
const GITCODE_PACKAGE = "@gitcode-cli/cli@latest";
|
|
26
26
|
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
27
27
|
const BUNDLED_SKILL_DIR = path.join(PACKAGE_ROOT, "skill", "msdevflow");
|
|
28
|
+
const SUPPORTED_CLIENTS = ["claude", "codex", "opencode"];
|
|
28
29
|
const REQUIRED_SCHEMAS = {
|
|
29
30
|
"auth login": ["web"],
|
|
30
31
|
"auth status": ["json"],
|
|
@@ -56,11 +57,18 @@ export function usage() {
|
|
|
56
57
|
msdevflow setup [options]
|
|
57
58
|
|
|
58
59
|
Options:
|
|
59
|
-
--
|
|
60
|
-
--
|
|
61
|
-
--
|
|
62
|
-
--
|
|
63
|
-
|
|
60
|
+
--targets <clients> Comma-separated: claude,codex,opencode,all
|
|
61
|
+
--skills-dir <path> Claude skills directory (default: ~/.claude/skills)
|
|
62
|
+
--agents-skills-dir <path> Shared Agent Skills directory (default: ~/.agents/skills)
|
|
63
|
+
--dry-run Diagnose and print the plan without changing the environment
|
|
64
|
+
--yes Apply the displayed plan without an interactive confirmation
|
|
65
|
+
--json Print the final result as JSON
|
|
66
|
+
-h, --help Show this help
|
|
67
|
+
|
|
68
|
+
Without --targets, setup detects Claude Code, Codex, and OpenCode on PATH. If no
|
|
69
|
+
client is detected, interactive setup asks which clients to install; non-interactive
|
|
70
|
+
setup requires --targets. OpenCode reuses a Claude or shared Agent Skills target
|
|
71
|
+
instead of creating a third copy.
|
|
64
72
|
|
|
65
73
|
The setup installs or updates the bundled msdevflow skill, installs or upgrades
|
|
66
74
|
the official GitCode npm CLI, and installs all reviewed Python runtime
|
|
@@ -69,6 +77,24 @@ authenticated, it starts the CLI's official browser login; setup never reads or
|
|
|
69
77
|
prints a token.`;
|
|
70
78
|
}
|
|
71
79
|
|
|
80
|
+
export function parseTargets(value) {
|
|
81
|
+
const requested = value.split(",").map((target) => target.trim().toLowerCase()).filter(Boolean);
|
|
82
|
+
if (requested.length === 0) {
|
|
83
|
+
throw new BootstrapError("--targets requires at least one client.", 2);
|
|
84
|
+
}
|
|
85
|
+
const unsupported = requested.filter(
|
|
86
|
+
(target) => target !== "all" && !SUPPORTED_CLIENTS.includes(target),
|
|
87
|
+
);
|
|
88
|
+
if (unsupported.length) {
|
|
89
|
+
throw new BootstrapError(
|
|
90
|
+
`Unsupported client target: ${unsupported.join(", ")}. Supported targets: ${SUPPORTED_CLIENTS.join(", ")}, all.`,
|
|
91
|
+
2,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
const expanded = requested.includes("all") ? SUPPORTED_CLIENTS : requested;
|
|
95
|
+
return [...new Set(expanded)];
|
|
96
|
+
}
|
|
97
|
+
|
|
72
98
|
export function parseArgs(argv) {
|
|
73
99
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
74
100
|
return { help: true };
|
|
@@ -78,13 +104,25 @@ export function parseArgs(argv) {
|
|
|
78
104
|
}
|
|
79
105
|
|
|
80
106
|
const options = {
|
|
107
|
+
targets: [],
|
|
108
|
+
targetsSpecified: false,
|
|
81
109
|
skillsDir: "",
|
|
110
|
+
agentsSkillsDir: "",
|
|
82
111
|
dryRun: false,
|
|
83
112
|
yes: false,
|
|
84
113
|
json: false,
|
|
85
114
|
};
|
|
86
115
|
for (let index = 1; index < argv.length; index += 1) {
|
|
87
116
|
const argument = argv[index];
|
|
117
|
+
if (argument === "--targets") {
|
|
118
|
+
index += 1;
|
|
119
|
+
if (!argv[index]) {
|
|
120
|
+
throw new BootstrapError("--targets requires a comma-separated client list.", 2);
|
|
121
|
+
}
|
|
122
|
+
options.targets = parseTargets(argv[index]);
|
|
123
|
+
options.targetsSpecified = true;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
88
126
|
if (argument === "--skills-dir") {
|
|
89
127
|
index += 1;
|
|
90
128
|
if (!argv[index]) {
|
|
@@ -93,6 +131,14 @@ export function parseArgs(argv) {
|
|
|
93
131
|
options.skillsDir = argv[index];
|
|
94
132
|
continue;
|
|
95
133
|
}
|
|
134
|
+
if (argument === "--agents-skills-dir") {
|
|
135
|
+
index += 1;
|
|
136
|
+
if (!argv[index]) {
|
|
137
|
+
throw new BootstrapError("--agents-skills-dir requires a path.", 2);
|
|
138
|
+
}
|
|
139
|
+
options.agentsSkillsDir = argv[index];
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
96
142
|
if (argument === "--dry-run") {
|
|
97
143
|
options.dryRun = true;
|
|
98
144
|
continue;
|
|
@@ -110,16 +156,25 @@ export function parseArgs(argv) {
|
|
|
110
156
|
return options;
|
|
111
157
|
}
|
|
112
158
|
|
|
113
|
-
function
|
|
114
|
-
return
|
|
159
|
+
function defaultClaudeSkillsDir(options, environment, platform) {
|
|
160
|
+
return options.skillsDir
|
|
161
|
+
|| environment.CLAUDE_SKILLS_DIR
|
|
162
|
+
|| path.join(environmentHome(environment, platform), ".claude", "skills");
|
|
115
163
|
}
|
|
116
164
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
165
|
+
function defaultAgentsSkillsDir(options, environment, platform) {
|
|
166
|
+
return options.agentsSkillsDir
|
|
167
|
+
|| path.join(environmentHome(environment, platform), ".agents", "skills");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function createSkillLayout(kind, clients, skillsDir, bundledSkillDir) {
|
|
171
|
+
const resolvedSkillsDir = path.resolve(skillsDir);
|
|
172
|
+
const workflowDir = path.join(resolvedSkillsDir, "msdevflow");
|
|
120
173
|
const bundledWorkflowDir = path.resolve(bundledSkillDir);
|
|
121
174
|
return {
|
|
122
|
-
|
|
175
|
+
kind,
|
|
176
|
+
clients,
|
|
177
|
+
skillsDir: resolvedSkillsDir,
|
|
123
178
|
workflowDir,
|
|
124
179
|
workflowManifest: path.join(workflowDir, "SKILL.md"),
|
|
125
180
|
pythonRequirements: path.join(workflowDir, "scripts", "requirements.txt"),
|
|
@@ -129,6 +184,58 @@ export function resolveLayout(options, environment = process.env, bundledSkillDi
|
|
|
129
184
|
};
|
|
130
185
|
}
|
|
131
186
|
|
|
187
|
+
export function resolveLayout(options, environment = process.env, bundledSkillDir = BUNDLED_SKILL_DIR) {
|
|
188
|
+
return createSkillLayout(
|
|
189
|
+
"claude",
|
|
190
|
+
["claude"],
|
|
191
|
+
defaultClaudeSkillsDir(options, environment, process.platform),
|
|
192
|
+
bundledSkillDir,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function resolveLayouts(
|
|
197
|
+
options,
|
|
198
|
+
clients,
|
|
199
|
+
environment = process.env,
|
|
200
|
+
bundledSkillDir = BUNDLED_SKILL_DIR,
|
|
201
|
+
platform = process.platform,
|
|
202
|
+
) {
|
|
203
|
+
const layouts = [];
|
|
204
|
+
const openCodeUsesClaude = clients.includes("opencode") && clients.includes("claude");
|
|
205
|
+
if (clients.includes("claude")) {
|
|
206
|
+
layouts.push(createSkillLayout(
|
|
207
|
+
"claude",
|
|
208
|
+
openCodeUsesClaude ? ["claude", "opencode"] : ["claude"],
|
|
209
|
+
defaultClaudeSkillsDir(options, environment, platform),
|
|
210
|
+
bundledSkillDir,
|
|
211
|
+
));
|
|
212
|
+
}
|
|
213
|
+
if (clients.includes("codex") || (clients.includes("opencode") && !openCodeUsesClaude)) {
|
|
214
|
+
const sharedClients = clients.includes("codex") ? ["codex"] : [];
|
|
215
|
+
if (clients.includes("opencode")) {
|
|
216
|
+
sharedClients.push("opencode");
|
|
217
|
+
}
|
|
218
|
+
layouts.push(createSkillLayout(
|
|
219
|
+
"agents",
|
|
220
|
+
sharedClients,
|
|
221
|
+
defaultAgentsSkillsDir(options, environment, platform),
|
|
222
|
+
bundledSkillDir,
|
|
223
|
+
));
|
|
224
|
+
}
|
|
225
|
+
const unique = new Map();
|
|
226
|
+
for (const layout of layouts) {
|
|
227
|
+
const key = normalizedPath(layout.workflowDir, platform);
|
|
228
|
+
const existing = unique.get(key);
|
|
229
|
+
if (existing) {
|
|
230
|
+
existing.clients = [...new Set([...existing.clients, ...layout.clients])];
|
|
231
|
+
existing.kind = "shared";
|
|
232
|
+
} else {
|
|
233
|
+
unique.set(key, layout);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return [...unique.values()];
|
|
237
|
+
}
|
|
238
|
+
|
|
132
239
|
function namedCommand(command, args, platform) {
|
|
133
240
|
return executableCommand(platform === "win32" && command === "npm" ? "npm.cmd" : command, args, platform);
|
|
134
241
|
}
|
|
@@ -386,6 +493,51 @@ export function resolveExecutable(name, run, platform) {
|
|
|
386
493
|
return result.status === 0 ? firstOutputLine(result.stdout) : "";
|
|
387
494
|
}
|
|
388
495
|
|
|
496
|
+
export function detectClients(run, platform) {
|
|
497
|
+
return SUPPORTED_CLIENTS.filter((client) => Boolean(resolveExecutable(client, run, platform)));
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
async function selectClientTargets(input, output, promptOutput = output) {
|
|
501
|
+
if (!input.isTTY || !output.isTTY) {
|
|
502
|
+
throw new BootstrapError(
|
|
503
|
+
"No supported agent client was detected. Non-interactive setup requires --targets <clients>.",
|
|
504
|
+
2,
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
promptOutput.write("No supported agent client was detected. Available targets: claude, codex, opencode, all.\n");
|
|
508
|
+
const terminal = createInterface({ input, output: promptOutput });
|
|
509
|
+
try {
|
|
510
|
+
const answer = await terminal.question("Install msdevflow for: ");
|
|
511
|
+
return parseTargets(answer);
|
|
512
|
+
} finally {
|
|
513
|
+
terminal.close();
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
async function resolveClientTargets(options, run, platform, selectClients) {
|
|
518
|
+
if (options.targetsSpecified) {
|
|
519
|
+
return { clients: options.targets, source: "explicit", detected: [] };
|
|
520
|
+
}
|
|
521
|
+
if (options.skillsDir) {
|
|
522
|
+
return { clients: ["claude"], source: "legacy-skills-dir", detected: [] };
|
|
523
|
+
}
|
|
524
|
+
const detected = detectClients(run, platform);
|
|
525
|
+
if (detected.length) {
|
|
526
|
+
return { clients: detected, source: "detected", detected };
|
|
527
|
+
}
|
|
528
|
+
const clients = await selectClients();
|
|
529
|
+
return { clients, source: "selected", detected: [] };
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function clientTargetWarnings(clients, layouts) {
|
|
533
|
+
if (clients.includes("opencode") && layouts.length > 1) {
|
|
534
|
+
return [
|
|
535
|
+
"OpenCode can discover msdevflow from both installed paths; keep both copies managed by this setup to prevent version drift.",
|
|
536
|
+
];
|
|
537
|
+
}
|
|
538
|
+
return [];
|
|
539
|
+
}
|
|
540
|
+
|
|
389
541
|
function normalizedPath(value, platform) {
|
|
390
542
|
const normalized = value.replaceAll("\\", "/").replace(/\/+$/, "");
|
|
391
543
|
return platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
@@ -677,10 +829,16 @@ function safeAuthStatus(output) {
|
|
|
677
829
|
|
|
678
830
|
function printPlan(plan, write) {
|
|
679
831
|
write("Setup plan (no changes made yet):");
|
|
680
|
-
write(`
|
|
681
|
-
write(` Bundled skill: ${plan.
|
|
682
|
-
|
|
683
|
-
|
|
832
|
+
write(` Client targets: ${plan.clientTargets.clients.join(", ")} (${plan.clientTargets.source})`);
|
|
833
|
+
write(` Bundled skill: ${plan.skillInstalls[0].details.source}`);
|
|
834
|
+
for (const { layout, details } of plan.skillInstalls) {
|
|
835
|
+
write(` Skill target: ${details.target}`);
|
|
836
|
+
write(` Clients: ${layout.clients.join(", ")}`);
|
|
837
|
+
write(` Action: ${details.status}`);
|
|
838
|
+
}
|
|
839
|
+
for (const warning of plan.clientTargets.warnings) {
|
|
840
|
+
write(` Warning: ${warning}`);
|
|
841
|
+
}
|
|
684
842
|
write(` Existing gitcode: ${plan.diagnosis.classification}`);
|
|
685
843
|
if (plan.diagnosis.existingExecutable) {
|
|
686
844
|
write(` Existing executable: ${plan.diagnosis.existingExecutable}`);
|
|
@@ -781,7 +939,13 @@ function authenticateGitcode(cliExecutable, authentication, run, platform, write
|
|
|
781
939
|
|
|
782
940
|
function textResult(result, write) {
|
|
783
941
|
write("Setup completed:");
|
|
784
|
-
write(`
|
|
942
|
+
write(` Client targets: ${result.clients.join(", ")}`);
|
|
943
|
+
for (const skill of result.skills) {
|
|
944
|
+
write(` Skill: ${skill.status} at ${skill.target} (${skill.clients.join(", ")})`);
|
|
945
|
+
}
|
|
946
|
+
for (const warning of result.warnings) {
|
|
947
|
+
write(` Warning: ${warning}`);
|
|
948
|
+
}
|
|
785
949
|
write(` Workflow CLI command: ${result.gitcode.command}`);
|
|
786
950
|
write(` GitCode CLI: ${result.gitcode.version}`);
|
|
787
951
|
write(` Required capabilities: ${result.gitcode.capabilities}`);
|
|
@@ -800,8 +964,31 @@ export async function runSetup(options, dependencies = {}) {
|
|
|
800
964
|
const run = dependencies.run || execute;
|
|
801
965
|
const write = dependencies.write || ((line) => console.log(line));
|
|
802
966
|
const writePlan = dependencies.writePlan || write;
|
|
803
|
-
const
|
|
804
|
-
|
|
967
|
+
const targetSelection = await resolveClientTargets(
|
|
968
|
+
options,
|
|
969
|
+
run,
|
|
970
|
+
platform,
|
|
971
|
+
dependencies.selectClients || (() => selectClientTargets(
|
|
972
|
+
process.stdin,
|
|
973
|
+
process.stdout,
|
|
974
|
+
options.json ? process.stderr : process.stdout,
|
|
975
|
+
)),
|
|
976
|
+
);
|
|
977
|
+
const layouts = resolveLayouts(
|
|
978
|
+
options,
|
|
979
|
+
targetSelection.clients,
|
|
980
|
+
environment,
|
|
981
|
+
dependencies.bundledSkillDir,
|
|
982
|
+
platform,
|
|
983
|
+
);
|
|
984
|
+
const skillInstalls = layouts.map((layout) => ({
|
|
985
|
+
layout,
|
|
986
|
+
details: skillInstallDetails(layout),
|
|
987
|
+
}));
|
|
988
|
+
const clientTargets = {
|
|
989
|
+
...targetSelection,
|
|
990
|
+
warnings: clientTargetWarnings(targetSelection.clients, layouts),
|
|
991
|
+
};
|
|
805
992
|
|
|
806
993
|
if (Number(process.versions.node.split(".")[0]) < 18) {
|
|
807
994
|
throw new BootstrapError(`Node.js >=18 is required; found ${process.version}.`, 2);
|
|
@@ -830,11 +1017,12 @@ export async function runSetup(options, dependencies = {}) {
|
|
|
830
1017
|
run({ command: python.executable, args: ["-m", "pip", "--version"] });
|
|
831
1018
|
const pipInvocation = {
|
|
832
1019
|
command: python.executable,
|
|
833
|
-
args: ["-m", "pip", "install", "-r",
|
|
1020
|
+
args: ["-m", "pip", "install", "-r", layouts[0].bundledPythonRequirements],
|
|
834
1021
|
};
|
|
835
1022
|
const plan = {
|
|
836
|
-
|
|
837
|
-
|
|
1023
|
+
clientTargets,
|
|
1024
|
+
layouts,
|
|
1025
|
+
skillInstalls,
|
|
838
1026
|
diagnosis,
|
|
839
1027
|
installInvocation,
|
|
840
1028
|
gitcodeInstall,
|
|
@@ -872,14 +1060,19 @@ export async function runSetup(options, dependencies = {}) {
|
|
|
872
1060
|
options.json ? writePlan : write,
|
|
873
1061
|
options.json,
|
|
874
1062
|
);
|
|
875
|
-
const
|
|
876
|
-
|
|
1063
|
+
const skills = skillInstalls.map(({ layout, details }) => ({
|
|
1064
|
+
kind: layout.kind,
|
|
1065
|
+
clients: layout.clients,
|
|
1066
|
+
status: installBundledSkill(details),
|
|
1067
|
+
target: layout.workflowDir,
|
|
1068
|
+
}));
|
|
1069
|
+
const result = {
|
|
877
1070
|
state: "ready",
|
|
878
1071
|
git: gitVersion,
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
1072
|
+
clients: clientTargets.clients,
|
|
1073
|
+
targetSource: clientTargets.source,
|
|
1074
|
+
warnings: clientTargets.warnings,
|
|
1075
|
+
skills,
|
|
883
1076
|
gitcode,
|
|
884
1077
|
python: {
|
|
885
1078
|
executable: python.executable,
|
|
@@ -888,6 +1081,10 @@ export async function runSetup(options, dependencies = {}) {
|
|
|
888
1081
|
chromium: "not-downloaded",
|
|
889
1082
|
},
|
|
890
1083
|
};
|
|
1084
|
+
if (skills.length === 1) {
|
|
1085
|
+
result.skill = skills[0];
|
|
1086
|
+
}
|
|
1087
|
+
return result;
|
|
891
1088
|
}
|
|
892
1089
|
|
|
893
1090
|
export async function main(argv, dependencies = {}) {
|
package/package.json
CHANGED
|
@@ -55,6 +55,8 @@ discover -> issue -> develop -> pr -> ci
|
|
|
55
55
|
|
|
56
56
|
## 4. 调用示例
|
|
57
57
|
|
|
58
|
+
以下示例使用 Claude Code 的 `/msdevflow` 形式。Codex 使用 `$msdevflow`、`/skills` 或隐式匹配;OpenCode 使用 skill 工具或隐式匹配。无论客户端入口如何,正文中的 `action=<name>` 和其余任务参数保持相同。
|
|
59
|
+
|
|
58
60
|
### 未指定 action:完整 E2E
|
|
59
61
|
|
|
60
62
|
```text
|
|
@@ -174,7 +176,7 @@ PR:https://gitcode.com/Ascend/example/pulls/456
|
|
|
174
176
|
|
|
175
177
|
需要:
|
|
176
178
|
|
|
177
|
-
- Claude Code
|
|
179
|
+
- Claude Code、Codex 或 OpenCode;
|
|
178
180
|
- `git`;
|
|
179
181
|
- GitCode 官方 npm CLI;
|
|
180
182
|
- GitCode CLI 已认证;
|
|
@@ -214,33 +216,46 @@ npm 包同时携带 `msdevflow` skill 和 setup 程序。标准安装只需要
|
|
|
214
216
|
npx msdevflow setup
|
|
215
217
|
```
|
|
216
218
|
|
|
217
|
-
`setup`
|
|
219
|
+
`setup` 默认检测 PATH 中的 Claude Code、Codex 和 OpenCode,先展示客户端、物理 skill 目标、安装或更新状态、GitCode CLI 归属、registry、安装模式、Python 依赖命令和认证方式;用户确认后,在同一次运行中:
|
|
218
220
|
|
|
219
|
-
1.
|
|
221
|
+
1. 从 npm 包内唯一源码 `skill/msdevflow` 原子安装或更新所需受管副本;
|
|
220
222
|
2. 安装或升级官方 GitCode npm CLI;
|
|
221
223
|
3. 安装内置 `scripts/requirements.txt` 中的 Python Playwright 包;
|
|
222
224
|
4. 验收 workflow 所需 schema/API;
|
|
223
225
|
5. CLI 未认证时运行选定命令的 `auth login --web`。
|
|
224
226
|
|
|
225
|
-
|
|
227
|
+
客户端到物理目录的映射:
|
|
226
228
|
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
+
```text
|
|
230
|
+
Claude Code -> ~/.claude/skills/msdevflow
|
|
231
|
+
Codex -> ~/.agents/skills/msdevflow
|
|
232
|
+
仅 OpenCode -> ~/.agents/skills/msdevflow
|
|
233
|
+
OpenCode + Claude Code -> 复用 Claude 目标
|
|
234
|
+
OpenCode + Codex -> 复用 Agent Skills 目标
|
|
235
|
+
Claude Code + Codex -> 两个受管副本
|
|
236
|
+
三者同时安装 -> 两个受管副本,并提示 OpenCode 双路径发现
|
|
229
237
|
```
|
|
230
238
|
|
|
231
|
-
|
|
239
|
+
显式选择客户端、只读预检或自定义目录:
|
|
232
240
|
|
|
233
241
|
```bash
|
|
234
|
-
npx msdevflow setup --
|
|
242
|
+
npx msdevflow setup --targets claude
|
|
243
|
+
npx msdevflow setup --targets codex,opencode
|
|
244
|
+
npx msdevflow setup --targets all
|
|
245
|
+
npx msdevflow setup --dry-run --targets all
|
|
246
|
+
npx msdevflow setup --skills-dir "<claude-skills-directory>"
|
|
247
|
+
npx msdevflow setup --agents-skills-dir "<shared-agent-skills-directory>"
|
|
235
248
|
```
|
|
236
249
|
|
|
250
|
+
没有检测到客户端时,交互运行要求选择目标,非交互运行要求 `--targets`。`--skills-dir` 单独使用时保留旧语义,只安装 Claude Code 目标。Trae 的目录与兼容细节尚未可靠验证,因此当前不属于支持目标。
|
|
251
|
+
|
|
237
252
|
npm 包尚未发布时,可从当前源码/发布目录运行等价入口:
|
|
238
253
|
|
|
239
254
|
```bash
|
|
240
255
|
node ./msdevflow/bin/msdevflow.js setup
|
|
241
256
|
```
|
|
242
257
|
|
|
243
|
-
`setup` 不下载或启动 Chromium。凭证只由 GitCode CLI 和官方浏览器流程接收与保存;setup 不读取、输出或转存 Token,也不要求用户把 Token 传给 Agent
|
|
258
|
+
`setup` 不下载或启动 Chromium。凭证只由 GitCode CLI 和官方浏览器流程接收与保存;setup 不读取、输出或转存 Token,也不要求用户把 Token 传给 Agent。每个已有 skill 目标只有 manifest 明确标识为 `msdevflow` 时才允许更新,来源不明的目录、文件或符号链接会被拒绝覆盖。安装后重启或重新加载对应客户端。
|
|
244
259
|
|
|
245
260
|
## 7. 仓库画像和 PR 模板
|
|
246
261
|
|
package/skill/msdevflow/SKILL.md
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
name: msdevflow
|
|
3
3
|
description: >
|
|
4
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
|
+
compatibility: Requires Git, Node.js >=18, Python >=3.10, GitCode access, and the setup-installed GitCode CLI. Supports Claude Code, Codex, and OpenCode.
|
|
5
6
|
metadata:
|
|
6
|
-
version:
|
|
7
|
+
version: 2.0.0
|
|
7
8
|
source: msdevflow
|
|
8
9
|
---
|
|
9
10
|
|