msdevflow 0.7.5 → 0.7.6
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 +11 -2
- package/lib/bootstrap.js +223 -15
- package/package.json +1 -1
- package/skill/msd/README.md +17 -7
- package/skill/msd/references/ci-and-review.md +1 -1
- package/skill/msd/references/command-capabilities.md +1 -1
- package/skill/msd/references/openlibing-ci.md +9 -2
- package/skill/msd/references/pr-and-ci.md +1 -1
- package/skill/msd/references/state-and-safety.md +1 -1
- package/skill/msd/scripts/openlibing_ci.py +62 -21
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
- Claude Code、Codex 或 OpenCode 中至少一个;
|
|
10
10
|
- Node.js `>=18` 和 npm;
|
|
11
11
|
- Git;
|
|
12
|
-
- Python `>=3.10`
|
|
12
|
+
- Python `>=3.10`,且标准库 `venv` 模块可用;
|
|
13
13
|
- GitCode 账号;
|
|
14
14
|
- Chrome 或 Edge(推荐,用于 openLiBing OAuth)。
|
|
15
15
|
|
|
@@ -31,7 +31,7 @@ npx msdevflow@latest setup
|
|
|
31
31
|
|
|
32
32
|
1. 把一个 `msd` 核心和十个 `msd-<action>` 路由器作为整套事务原子安装或更新到所需目标;
|
|
33
33
|
2. 安装或升级官方 `@gitcode-cli/cli@latest`;
|
|
34
|
-
3.
|
|
34
|
+
3. 创建或修复 msdevflow 受管 Python venv,并在其中安装受审的 Playwright 依赖;
|
|
35
35
|
4. 验收 Issue、PR、结构化评论、行内评论、JSON、显式仓库和正文文件等 CLI 能力;
|
|
36
36
|
5. GitCode CLI 未认证时打开官方 Token 创建页面,并让 CLI 自己的终端提示接收 Token。
|
|
37
37
|
|
|
@@ -67,6 +67,15 @@ npx msdevflow setup --agents-skills-dir "<shared-agent-skills-directory>"
|
|
|
67
67
|
|
|
68
68
|
`--skills-dir` 未配合 `--targets` 使用时保留旧语义,只安装 Claude Code 目标。没有检测到客户端时,交互运行会要求选择目标;非交互运行必须提供 `--targets`。安装完成后重启或重新加载对应客户端。如果机器已有 Python 版本的 `gitcode`,setup 会保留它,并为 npm CLI 创建 `gitcode-npm` 命令;否则使用 `gitcode`。
|
|
69
69
|
|
|
70
|
+
setup 在所有平台使用独立受管 venv,不向系统 Python 安装包:
|
|
71
|
+
|
|
72
|
+
```text
|
|
73
|
+
Windows: %LOCALAPPDATA%\msdevflow\python
|
|
74
|
+
macOS/Linux: ${XDG_DATA_HOME:-$HOME/.local/share}/msdevflow/python
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
可用 `MSDEVFLOW_PYTHON_DIR` 覆盖,值必须是绝对路径或以 `~` 开头;setup 与后续 action 必须使用同一配置。`venv` 属于 CPython 标准库,但精简发行版可能未安装;Debian/Ubuntu 通常需要用户自行安装 `python3-venv`。setup 不执行 sudo,不回退到全局 pip,也不使用 `--break-system-packages`。Windows 官方 Python 和 Homebrew Python 通常已包含 `venv`。
|
|
78
|
+
|
|
70
79
|
## 运行与 action 补全
|
|
71
80
|
|
|
72
81
|
从目标仓库目录启动客户端。直接调用核心 `msd` 会运行完整作者 E2E:
|
package/lib/bootstrap.js
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
readdirSync,
|
|
12
12
|
readSync,
|
|
13
13
|
renameSync,
|
|
14
|
+
rmdirSync,
|
|
14
15
|
rmSync,
|
|
15
16
|
unlinkSync,
|
|
16
17
|
writeFileSync,
|
|
@@ -27,6 +28,8 @@ const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)),
|
|
|
27
28
|
const BUNDLED_SKILLS_DIR = path.join(PACKAGE_ROOT, "skill");
|
|
28
29
|
const CORE_SKILL_NAME = "msd";
|
|
29
30
|
const LEGACY_SKILL_NAME = "msdevflow";
|
|
31
|
+
const PYTHON_RUNTIME_MARKER = ".msdevflow-python-runtime";
|
|
32
|
+
const PYTHON_RUNTIME_MARKER_CONTENT = "managed-by=msdevflow\n";
|
|
30
33
|
export const ACTIONS = [
|
|
31
34
|
"discover",
|
|
32
35
|
"create-issue",
|
|
@@ -946,6 +949,176 @@ function environmentHome(environment, platform) {
|
|
|
946
949
|
return environment.HOME || homedir();
|
|
947
950
|
}
|
|
948
951
|
|
|
952
|
+
function pythonRuntimeAt(directory, python, platform) {
|
|
953
|
+
const pathApi = platform === "win32" ? path.win32 : path.posix;
|
|
954
|
+
return {
|
|
955
|
+
baseExecutable: python.executable,
|
|
956
|
+
baseVersion: python.version,
|
|
957
|
+
directory,
|
|
958
|
+
executable: pathApi.join(
|
|
959
|
+
directory,
|
|
960
|
+
platform === "win32" ? "Scripts" : "bin",
|
|
961
|
+
platform === "win32" ? "python.exe" : "python",
|
|
962
|
+
),
|
|
963
|
+
marker: pathApi.join(directory, PYTHON_RUNTIME_MARKER),
|
|
964
|
+
platform,
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
function expandHomePath(value, home, platform, variable) {
|
|
969
|
+
const pathApi = platform === "win32" ? path.win32 : path.posix;
|
|
970
|
+
let expanded = value;
|
|
971
|
+
if (value === "~") {
|
|
972
|
+
expanded = home;
|
|
973
|
+
} else {
|
|
974
|
+
const homeRelative = platform === "win32" ? /^~[\\/]/ : /^~\//;
|
|
975
|
+
if (homeRelative.test(value)) {
|
|
976
|
+
expanded = pathApi.join(home, value.slice(2));
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
if (!pathApi.isAbsolute(expanded) && !path.isAbsolute(expanded)) {
|
|
980
|
+
throw new BootstrapError(`${variable} must be an absolute path or start with ~.`, 2);
|
|
981
|
+
}
|
|
982
|
+
return expanded;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
export function pythonRuntimeDetails(
|
|
986
|
+
python,
|
|
987
|
+
environment = process.env,
|
|
988
|
+
platform = process.platform,
|
|
989
|
+
) {
|
|
990
|
+
const pathApi = platform === "win32" ? path.win32 : path.posix;
|
|
991
|
+
const home = environmentHome(environment, platform);
|
|
992
|
+
const configured = environment.MSDEVFLOW_PYTHON_DIR;
|
|
993
|
+
const directory = configured
|
|
994
|
+
? expandHomePath(configured, home, platform, "MSDEVFLOW_PYTHON_DIR")
|
|
995
|
+
: platform === "win32"
|
|
996
|
+
? pathApi.join(environment.LOCALAPPDATA || pathApi.join(home, "AppData", "Local"), "msdevflow", "python")
|
|
997
|
+
: pathApi.join(
|
|
998
|
+
environment.XDG_DATA_HOME
|
|
999
|
+
? expandHomePath(environment.XDG_DATA_HOME, home, platform, "XDG_DATA_HOME")
|
|
1000
|
+
: pathApi.join(home, ".local", "share"),
|
|
1001
|
+
"msdevflow",
|
|
1002
|
+
"python",
|
|
1003
|
+
);
|
|
1004
|
+
return pythonRuntimeAt(directory, python, platform);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
function validatePythonRuntime(runtime, run) {
|
|
1008
|
+
let marker;
|
|
1009
|
+
try {
|
|
1010
|
+
const metadata = lstatSync(runtime.directory);
|
|
1011
|
+
if (metadata.isSymbolicLink() || !metadata.isDirectory()) {
|
|
1012
|
+
throw new BootstrapError(
|
|
1013
|
+
`Refusing to use a Python runtime path not owned by msdevflow: ${runtime.directory}`,
|
|
1014
|
+
3,
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
1017
|
+
marker = readOptionalFile(
|
|
1018
|
+
runtime.marker,
|
|
1019
|
+
`Refusing to use an invalid msdevflow Python runtime marker: ${runtime.marker}`,
|
|
1020
|
+
);
|
|
1021
|
+
} catch (error) {
|
|
1022
|
+
if (error?.code === "ENOENT") {
|
|
1023
|
+
return "absent";
|
|
1024
|
+
}
|
|
1025
|
+
throw error;
|
|
1026
|
+
}
|
|
1027
|
+
if (marker !== PYTHON_RUNTIME_MARKER_CONTENT) {
|
|
1028
|
+
throw new BootstrapError(
|
|
1029
|
+
`Refusing to use a Python runtime directory not owned by msdevflow: ${runtime.directory}`,
|
|
1030
|
+
3,
|
|
1031
|
+
);
|
|
1032
|
+
}
|
|
1033
|
+
let descriptor;
|
|
1034
|
+
try {
|
|
1035
|
+
descriptor = openSync(runtime.executable, "r");
|
|
1036
|
+
} catch (error) {
|
|
1037
|
+
if (error?.code === "ENOENT") {
|
|
1038
|
+
return "repair";
|
|
1039
|
+
}
|
|
1040
|
+
throw error;
|
|
1041
|
+
} finally {
|
|
1042
|
+
if (descriptor !== undefined) {
|
|
1043
|
+
closeSync(descriptor);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
if (runOptional(run, {
|
|
1047
|
+
command: runtime.executable,
|
|
1048
|
+
args: ["-m", "pip", "--version"],
|
|
1049
|
+
}).status !== 0) {
|
|
1050
|
+
return "repair";
|
|
1051
|
+
}
|
|
1052
|
+
return "current";
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
function writePythonRuntimeMarker(runtime) {
|
|
1056
|
+
const existing = readOptionalFile(
|
|
1057
|
+
runtime.marker,
|
|
1058
|
+
`Refusing to replace an invalid msdevflow Python runtime marker: ${runtime.marker}`,
|
|
1059
|
+
);
|
|
1060
|
+
if (existing === PYTHON_RUNTIME_MARKER_CONTENT) {
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
if (existing !== null) {
|
|
1064
|
+
throw new BootstrapError(
|
|
1065
|
+
`Refusing to replace an unrelated Python runtime marker: ${runtime.marker}`,
|
|
1066
|
+
3,
|
|
1067
|
+
);
|
|
1068
|
+
}
|
|
1069
|
+
writeFileSync(runtime.marker, PYTHON_RUNTIME_MARKER_CONTENT, { encoding: "utf8", flag: "wx" });
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
async function ensurePythonRuntime(runtime, status, runLong, run) {
|
|
1073
|
+
if (validatePythonRuntime(runtime, run) !== status) {
|
|
1074
|
+
throw new BootstrapError(
|
|
1075
|
+
`Managed Python runtime changed after confirmation: ${runtime.directory}`,
|
|
1076
|
+
3,
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
1079
|
+
if (status === "absent") {
|
|
1080
|
+
mkdirSync(path.dirname(runtime.directory), { recursive: true });
|
|
1081
|
+
try {
|
|
1082
|
+
mkdirSync(runtime.directory);
|
|
1083
|
+
} catch (error) {
|
|
1084
|
+
if (error?.code === "EEXIST") {
|
|
1085
|
+
throw new BootstrapError(
|
|
1086
|
+
`Managed Python runtime changed after confirmation: ${runtime.directory}`,
|
|
1087
|
+
3,
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
1090
|
+
throw error;
|
|
1091
|
+
}
|
|
1092
|
+
try {
|
|
1093
|
+
writePythonRuntimeMarker(runtime);
|
|
1094
|
+
} catch (error) {
|
|
1095
|
+
try {
|
|
1096
|
+
rmdirSync(runtime.directory);
|
|
1097
|
+
} catch {
|
|
1098
|
+
// Preserve the marker failure as the actionable error.
|
|
1099
|
+
}
|
|
1100
|
+
throw error;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
if (status !== "current") {
|
|
1104
|
+
try {
|
|
1105
|
+
await runLong({
|
|
1106
|
+
command: runtime.baseExecutable,
|
|
1107
|
+
args: ["-m", "venv", runtime.directory],
|
|
1108
|
+
});
|
|
1109
|
+
} catch (error) {
|
|
1110
|
+
throw new BootstrapError(
|
|
1111
|
+
`Python venv is unavailable for ${runtime.baseExecutable}. Install the venv component for this Python (for example python3-venv on Debian/Ubuntu) and rerun setup. ${error.message}`,
|
|
1112
|
+
2,
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
verifyFile(
|
|
1117
|
+
runtime.executable,
|
|
1118
|
+
`Managed Python runtime was not created correctly: ${runtime.executable}`,
|
|
1119
|
+
);
|
|
1120
|
+
}
|
|
1121
|
+
|
|
949
1122
|
export function gitcodeInstallDetails(
|
|
950
1123
|
classification,
|
|
951
1124
|
environment = process.env,
|
|
@@ -993,11 +1166,11 @@ export function gitcodeInstallDetails(
|
|
|
993
1166
|
};
|
|
994
1167
|
}
|
|
995
1168
|
|
|
996
|
-
function readOptionalFile(file) {
|
|
1169
|
+
function readOptionalFile(file, invalidMessage = `Refusing to overwrite unrelated wrapper: ${file}`) {
|
|
997
1170
|
try {
|
|
998
1171
|
const metadata = lstatSync(file);
|
|
999
1172
|
if (metadata.isSymbolicLink() || !metadata.isFile()) {
|
|
1000
|
-
throw new BootstrapError(
|
|
1173
|
+
throw new BootstrapError(invalidMessage, 3);
|
|
1001
1174
|
}
|
|
1002
1175
|
return readFileSync(file, "utf8");
|
|
1003
1176
|
} catch (error) {
|
|
@@ -1142,7 +1315,11 @@ function printPlan(plan, write) {
|
|
|
1142
1315
|
write(` Wrapper target: ${plan.gitcodeInstall.cliTarget}`);
|
|
1143
1316
|
}
|
|
1144
1317
|
write(` npm command: ${formatCommand(plan.installInvocation)}`);
|
|
1145
|
-
write(` Python: ${plan.python.executable} (${plan.python.version.join(".")})`);
|
|
1318
|
+
write(` Base Python: ${plan.python.executable} (${plan.python.version.join(".")})`);
|
|
1319
|
+
write(` Managed Python runtime: ${plan.pythonRuntime.directory} (${plan.pythonRuntime.status})`);
|
|
1320
|
+
if (plan.pythonRuntime.status === "absent") {
|
|
1321
|
+
write(` Python venv command: ${formatCommand(plan.venvInvocation)}`);
|
|
1322
|
+
}
|
|
1146
1323
|
write(` Python dependency command: ${formatCommand(plan.pipInvocation)}`);
|
|
1147
1324
|
write(" Browser binary download: disabled");
|
|
1148
1325
|
write(` GitCode authentication command when needed: ${plan.gitcodeInstall.workflowCommand} auth login --web`);
|
|
@@ -1259,8 +1436,9 @@ function textResult(result, write) {
|
|
|
1259
1436
|
if (result.gitcode.authentication.username) {
|
|
1260
1437
|
write(` Username: ${result.gitcode.authentication.username}`);
|
|
1261
1438
|
}
|
|
1262
|
-
write(` Python: ${result.python.
|
|
1263
|
-
write(
|
|
1439
|
+
write(` Base Python: ${result.python.baseExecutable} ${result.python.baseVersion}`);
|
|
1440
|
+
write(` Managed Python runtime: ${result.python.executable}`);
|
|
1441
|
+
write(" Playwright Python package: installed in managed runtime");
|
|
1264
1442
|
write(" Chromium: not downloaded");
|
|
1265
1443
|
}
|
|
1266
1444
|
|
|
@@ -1334,9 +1512,27 @@ export async function runSetup(options, dependencies = {}) {
|
|
|
1334
1512
|
);
|
|
1335
1513
|
|
|
1336
1514
|
const python = detectPython(run, platform);
|
|
1337
|
-
|
|
1338
|
-
const pipInvocation = {
|
|
1515
|
+
const venvCapability = runOptional(run, {
|
|
1339
1516
|
command: python.executable,
|
|
1517
|
+
args: ["-m", "venv", "--help"],
|
|
1518
|
+
});
|
|
1519
|
+
if (venvCapability.status !== 0) {
|
|
1520
|
+
throw new BootstrapError(
|
|
1521
|
+
`Python venv is unavailable for ${python.executable}. Install the venv component for this Python (for example python3-venv on Debian/Ubuntu) and rerun setup.`,
|
|
1522
|
+
2,
|
|
1523
|
+
);
|
|
1524
|
+
}
|
|
1525
|
+
const configuredPythonRuntime = pythonRuntimeDetails(python, environment, platform);
|
|
1526
|
+
const pythonRuntime = dependencies.pythonRuntimeDir
|
|
1527
|
+
? pythonRuntimeAt(dependencies.pythonRuntimeDir, python, platform)
|
|
1528
|
+
: configuredPythonRuntime;
|
|
1529
|
+
pythonRuntime.status = validatePythonRuntime(pythonRuntime, run);
|
|
1530
|
+
const venvInvocation = {
|
|
1531
|
+
command: python.executable,
|
|
1532
|
+
args: ["-m", "venv", pythonRuntime.directory],
|
|
1533
|
+
};
|
|
1534
|
+
const pipInvocation = {
|
|
1535
|
+
command: pythonRuntime.executable,
|
|
1340
1536
|
args: ["-m", "pip", "install", "-r", layouts[0].bundledPythonRequirements],
|
|
1341
1537
|
};
|
|
1342
1538
|
const plan = {
|
|
@@ -1347,6 +1543,8 @@ export async function runSetup(options, dependencies = {}) {
|
|
|
1347
1543
|
installInvocation,
|
|
1348
1544
|
gitcodeInstall,
|
|
1349
1545
|
python,
|
|
1546
|
+
pythonRuntime,
|
|
1547
|
+
venvInvocation,
|
|
1350
1548
|
pipInvocation,
|
|
1351
1549
|
};
|
|
1352
1550
|
printPlan(plan, writePlan);
|
|
@@ -1365,6 +1563,20 @@ export async function runSetup(options, dependencies = {}) {
|
|
|
1365
1563
|
validateWrapper(gitcodeInstall, platform);
|
|
1366
1564
|
}
|
|
1367
1565
|
|
|
1566
|
+
await progress.run(
|
|
1567
|
+
pythonRuntime.status === "absent"
|
|
1568
|
+
? "Creating the managed Python runtime"
|
|
1569
|
+
: pythonRuntime.status === "repair"
|
|
1570
|
+
? "Repairing the managed Python runtime"
|
|
1571
|
+
: "Validating the managed Python runtime",
|
|
1572
|
+
() => ensurePythonRuntime(pythonRuntime, pythonRuntime.status, runLong, run),
|
|
1573
|
+
);
|
|
1574
|
+
await progress.run("Installing reviewed Python dependencies", () => runLong(pipInvocation));
|
|
1575
|
+
await progress.run(
|
|
1576
|
+
"Verifying the Playwright Python package",
|
|
1577
|
+
() => runLong({ command: pythonRuntime.executable, args: ["-c", "import playwright.sync_api"] }),
|
|
1578
|
+
);
|
|
1579
|
+
|
|
1368
1580
|
const installed = await progress.run(
|
|
1369
1581
|
"Installing the official GitCode CLI",
|
|
1370
1582
|
() => installGitcode(plan, runLong, run, platform),
|
|
@@ -1375,12 +1587,6 @@ export async function runSetup(options, dependencies = {}) {
|
|
|
1375
1587
|
);
|
|
1376
1588
|
gitcode.command = installed.command;
|
|
1377
1589
|
gitcode.executable = installed.executable;
|
|
1378
|
-
|
|
1379
|
-
await progress.run("Installing reviewed Python dependencies", () => runLong(pipInvocation));
|
|
1380
|
-
await progress.run(
|
|
1381
|
-
"Verifying the Playwright Python package",
|
|
1382
|
-
() => runLong({ command: python.executable, args: ["-c", "import playwright.sync_api"] }),
|
|
1383
|
-
);
|
|
1384
1590
|
gitcode.authentication = await authenticateGitcode(
|
|
1385
1591
|
installed.executable,
|
|
1386
1592
|
gitcode.authentication,
|
|
@@ -1411,8 +1617,10 @@ export async function runSetup(options, dependencies = {}) {
|
|
|
1411
1617
|
skills,
|
|
1412
1618
|
gitcode,
|
|
1413
1619
|
python: {
|
|
1414
|
-
|
|
1415
|
-
|
|
1620
|
+
baseExecutable: python.executable,
|
|
1621
|
+
baseVersion: python.version.join("."),
|
|
1622
|
+
runtimeDirectory: pythonRuntime.directory,
|
|
1623
|
+
executable: pythonRuntime.executable,
|
|
1416
1624
|
playwright: "installed",
|
|
1417
1625
|
chromium: "not-downloaded",
|
|
1418
1626
|
},
|
package/package.json
CHANGED
package/skill/msd/README.md
CHANGED
|
@@ -146,7 +146,7 @@ PR:https://gitcode.com/Ascend/example/pulls/456
|
|
|
146
146
|
本地仓库:D:\work\example
|
|
147
147
|
```
|
|
148
148
|
|
|
149
|
-
CI 通过后立即停止,不处理 feedback 或 merge。识别为 openLiBing 且只读 API 返回 401/403
|
|
149
|
+
CI 通过后立即停止,不处理 feedback 或 merge。识别为 openLiBing 且只读 API 返回 401/403 时,使用 setup 创建的受管 Python 运行时和其中的 Playwright 打开已有的系统 Chrome/Edge 或 Playwright Chromium,认证成功后继续本 action;运行时不可用时停止并要求运行 `npx msdevflow@latest setup`,不会在 action 内调用 pip,也不会自动下载浏览器。浏览器不可用时输出可复制的 OAuth 链接并停止为 `blocked: browser-required`。可用 `autonomous-ci` 只授权同一 PR 上根因明确的最小 CI 修复循环。
|
|
150
150
|
|
|
151
151
|
### 只验证 openLiBing OAuth
|
|
152
152
|
|
|
@@ -155,7 +155,7 @@ CI 通过后立即停止,不处理 feedback 或 merge。识别为 openLiBing
|
|
|
155
155
|
本地仓库:D:\work\example
|
|
156
156
|
```
|
|
157
157
|
|
|
158
|
-
本 action 从当前工作区唯一识别 canonical,只在该仓库内按“当前分支关联 PR → 最近开放 PR → 最近合入 PR”的顺序选择一个包含 openLiBing run 的验证目标,不要求用户提供 PR
|
|
158
|
+
本 action 从当前工作区唯一识别 canonical,只在该仓库内按“当前分支关联 PR → 最近开放 PR → 最近合入 PR”的顺序选择一个包含 openLiBing run 的验证目标,不要求用户提供 PR。它使用 setup 创建的受管 Python 运行时和其中的 Playwright,并使用已有的系统 Chrome/Edge 或 Playwright Chromium 打开可见窗口;运行时不可用时要求运行 `npx msdevflow@latest setup`,不会在 action 内调用 pip,也不会自动下载浏览器。用户亲自完成 GitCode 登录和授权。浏览器不可用时输出可复制的 OAuth 链接并停止为 `blocked: browser-required`;人工浏览器登录不等于验证完成。只有 OAuth 后对固定 run 的真实只读请求成功才完成,随后立即停止,不进入 `ci`。
|
|
159
159
|
|
|
160
160
|
它不读取或输出 GitCode Token。openLiBing Token 只驻留当前 Python 进程,固定显示为 `redacted`;持久化的只有受管专用 profile 中的 GitCode 浏览器登录态。
|
|
161
161
|
|
|
@@ -241,13 +241,23 @@ npx msdevflow@latest setup
|
|
|
241
241
|
|
|
242
242
|
`setup` 是幂等的:内容一致时保持 `current`,内容不同时原子更新。
|
|
243
243
|
|
|
244
|
-
`setup` 默认检测 PATH 中的 Claude Code、Codex 和 OpenCode,先展示客户端、物理 skill 目标、安装或更新状态、GitCode CLI 归属、registry
|
|
244
|
+
`setup` 默认检测 PATH 中的 Claude Code、Codex 和 OpenCode,先展示客户端、物理 skill 目标、安装或更新状态、GitCode CLI 归属、registry、安装模式、受管 Python 运行时、依赖命令和认证方式;用户确认后,在同一次运行中:
|
|
245
245
|
|
|
246
|
-
1.
|
|
247
|
-
2.
|
|
248
|
-
3.
|
|
246
|
+
1. 创建、修复或复用 msdevflow 受管 Python venv;
|
|
247
|
+
2. 用受管 Python 安装内置 `scripts/requirements.txt` 中的 Playwright 包;
|
|
248
|
+
3. 安装或升级官方 GitCode npm CLI;
|
|
249
249
|
4. 验收 workflow 所需 schema/API;
|
|
250
|
-
5. CLI 未认证时运行选定命令的 `auth login --web
|
|
250
|
+
5. CLI 未认证时运行选定命令的 `auth login --web`;
|
|
251
|
+
6. 从 npm 包内的 `skill/msd` 核心和十个 `skill/msd-<action>` 路由器原子安装或更新整套入口。
|
|
252
|
+
|
|
253
|
+
受管运行时默认位于:
|
|
254
|
+
|
|
255
|
+
```text
|
|
256
|
+
Windows: %LOCALAPPDATA%\msdevflow\python
|
|
257
|
+
macOS/Linux: ${XDG_DATA_HOME:-$HOME/.local/share}/msdevflow/python
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
可用 `MSDEVFLOW_PYTHON_DIR` 覆盖,值必须是绝对路径或以 `~` 开头;setup 与后续 action 必须使用同一配置。`venv` 属于 CPython 标准库,但精简发行版可能缺失;Debian/Ubuntu 通常需要用户自行安装 `python3-venv`。setup 不执行 sudo,不回退到全局 pip,也不使用 `--break-system-packages`。来源不明或 marker 损坏的运行时目录不会被接管。
|
|
251
261
|
|
|
252
262
|
客户端到物理目录的映射:
|
|
253
263
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
不要把一个仓库的 `compile`、label 或机器人协议迁移到另一个仓库。
|
|
13
13
|
|
|
14
|
-
若评论/文档明确为 openLiBing,按需加载 [openlibing-ci.md](openlibing-ci.md);先用 GitCode 评论定位 run/job,再尝试只读 detail/log API。401/403 时,显式 `action=ci` 和完整 E2E 都直接进入安全 OAuth
|
|
14
|
+
若评论/文档明确为 openLiBing,按需加载 [openlibing-ci.md](openlibing-ci.md);先用 GitCode 评论定位 run/job,再尝试只读 detail/log API。401/403 时,显式 `action=ci` 和完整 E2E 都直接进入安全 OAuth 子流程,使用 setup 创建的受管 Python 运行时和其中的 Playwright,并打开已有的系统 Chrome/Edge 或 Playwright Chromium;action 内不调用 pip,也不自动下载浏览器。受管运行时不可用时要求运行 `npx msdevflow@latest setup`;成功后恢复原 CI 诊断,浏览器不可用时输出 OAuth 链接并停止为 `blocked: browser-required`。不得把 GitCode Actions 当作 openLiBing。
|
|
15
15
|
|
|
16
16
|
记录适配器:
|
|
17
17
|
|
|
@@ -42,7 +42,7 @@ CLI 缺失、版本低于推荐下限或所需 schema 不存在时,当前 acti
|
|
|
42
42
|
npx msdevflow setup
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
独立 setup 会在同一次确认后安装或更新 npm 包内置的 `
|
|
45
|
+
独立 setup 会在同一次确认后安装或更新 npm 包内置的 `msd` Skill 套件,安装或升级官方 npm 包 `@gitcode-cli/cli@latest`,保留已有 Python `gitcode`,创建或修复 msdevflow 受管 Python venv,在其中安装 `scripts/requirements.txt` 的 Playwright,并验收 workflow 所需 schema/API。它不修改系统 Python、不使用 `--break-system-packages`、不回退到全局 pip,也不下载 Playwright Chromium。若 Python 发行版缺少 `venv`,setup 明确阻断;Debian/Ubuntu 通常需要用户自行安装 `python3-venv`。若 CLI 未认证,setup 会启动 `<gitcode-command> auth login --web` 的官方浏览器流程;凭证由 GitCode CLI 自己接收和保存,setup 不读取、打印或转存 Token,也不要求用户把 Token 传给 Agent。workflow 不在 action 内隐式安装依赖。setup 成功后重新探测环境并恢复原 action;不要 fallback 到可能安装旧实现的 PyPI `gitcode-cli`。
|
|
46
46
|
|
|
47
47
|
## 能力降级顺序
|
|
48
48
|
|
|
@@ -20,7 +20,14 @@
|
|
|
20
20
|
|
|
21
21
|
### 执行和完成证据
|
|
22
22
|
|
|
23
|
-
setup
|
|
23
|
+
setup 在所有平台创建或修复一个 msdevflow 受管 Python venv,并在其中安装 `scripts/requirements.txt` 的 Playwright;脚本进入 OAuth 流程时自动重启到该受管解释器。受管运行时或 Playwright 不可用时要求运行 `npx msdevflow@latest setup`,脚本自身不调用 pip,不修改系统 Python,也不下载 Playwright Chromium。脚本优先使用系统 Chrome/Edge,其次使用受管 Playwright 环境中已经存在的 Chromium。然后运行:
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
Windows: %LOCALAPPDATA%\msdevflow\python
|
|
27
|
+
macOS/Linux: ${XDG_DATA_HOME:-$HOME/.local/share}/msdevflow/python
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
可用 `MSDEVFLOW_PYTHON_DIR` 覆盖,值必须是绝对路径或以 `~` 开头;setup 与运行 action 时必须保持一致。
|
|
24
31
|
|
|
25
32
|
```bash
|
|
26
33
|
python <skill-dir>/scripts/openlibing_ci.py login-check \
|
|
@@ -85,7 +92,7 @@ python <skill-dir>/scripts/openlibing_ci.py diagnose \
|
|
|
85
92
|
python <skill-dir>/scripts/openlibing_ci.py diagnose ... --oauth
|
|
86
93
|
```
|
|
87
94
|
|
|
88
|
-
`--oauth` 需要 Python Playwright,以及已有的系统 Chrome/Edge
|
|
95
|
+
`--oauth` 需要 setup 已准备好的受管 Python 运行时及其中的 Playwright,以及已有的系统 Chrome/Edge 或受管 Playwright 环境中已经存在的 Chromium。脚本不调用 pip,也不会执行 `playwright install chromium`;受管运行时或依赖缺失时停止并要求运行 `npx msdevflow@latest setup`。浏览器可用时打开本机可见窗口让用户自己完成 GitCode 登录/授权,浏览器不可用时只输出可复制的 OAuth 链接并停止为 `blocked: browser-required`。捕获的 openLiBing token 只驻留当前 Python 进程内,不打印、不落盘。
|
|
89
96
|
|
|
90
97
|
默认使用专用持久浏览器 profile:
|
|
91
98
|
|
|
@@ -90,7 +90,7 @@ Suggested next action: ci
|
|
|
90
90
|
4. 分类当前改动、连带失败、canonical 基线、基础设施、权限或证据不足;
|
|
91
91
|
5. guided 模式确认修复;`autonomous-ci` 只在已授权最小范围内自动修改、验证、新 commit、push 和重触发;
|
|
92
92
|
6. 新 push 后获取新 head,旧 head 结果失效;
|
|
93
|
-
7. openLiBing detail/log 返回 401/403 时,按 [openlibing-ci.md](openlibing-ci.md)
|
|
93
|
+
7. openLiBing detail/log 返回 401/403 时,按 [openlibing-ci.md](openlibing-ci.md) 使用 setup 创建的受管 Python 运行时和其中的 Playwright,并用已有的系统 Chrome/Edge 或 Playwright Chromium 打开可见浏览器;action 内不调用 pip,也不自动下载浏览器。受管运行时不可用时要求运行 `npx msdevflow@latest setup`;用户完成 GitCode 登录/授权且固定 run 验证成功后恢复当前 CI 诊断,浏览器不可用时输出 OAuth 链接并停止为 `blocked: browser-required`;
|
|
94
94
|
8. 循环至当前 head 全绿或形成证据充分的 blocker。
|
|
95
95
|
|
|
96
96
|
重试不能代替根因分析。CI 修复不得扩大 Issue 范围;发现产品实现缺失或方案错误时返回 `blocked` 并建议 `action=develop`,不得把 CI action 变成补开发流程。
|
|
@@ -100,7 +100,7 @@ Agent 创建或修改的 Issue/PR 正文,以及发布的 Issue 评论、PR 普
|
|
|
100
100
|
|
|
101
101
|
`autonomous-ci` 只授权在同一 PR 的 CI 修复循环中:修改与根因直接相关的代码、运行门禁、创建新 commit、push 同一 source branch、重触发 CI。它不授权扩大需求、force push、降低测试、处理 feedback、review、approve 或 merge。
|
|
102
102
|
|
|
103
|
-
显式 `openlibing-auth`、显式 `ci` 和完整 E2E 均授权在需要 openLiBing OAuth
|
|
103
|
+
显式 `openlibing-auth`、显式 `ci` 和完整 E2E 均授权在需要 openLiBing OAuth 时使用 setup 创建的受管 Python 运行时和其中的 Playwright 打开可见浏览器,但不授权在 action 内调用 pip,也不授权下载 Playwright Chromium。受管运行时不可用时要求用户运行 `npx msdevflow@latest setup`。优先使用系统 Chrome/Edge,其次使用已存在的 Playwright Chromium;均不可用或 Playwright 无法启动时输出可复制的 OAuth 链接并停止为 `blocked: browser-required`。该链接只供人工浏览器访问,不能证明当前进程已认证或固定 run 已验证。该授权不允许 Agent 代替用户填写 GitCode 凭证、点击授权同意、读取浏览器 Cookie 值或导出任何 Token。
|
|
104
104
|
|
|
105
105
|
## Guided 检查点
|
|
106
106
|
|
|
@@ -8,7 +8,6 @@ import os
|
|
|
8
8
|
import re
|
|
9
9
|
import shutil
|
|
10
10
|
import stat
|
|
11
|
-
import subprocess
|
|
12
11
|
import sys
|
|
13
12
|
import time
|
|
14
13
|
import urllib.error
|
|
@@ -79,6 +78,59 @@ LEGACY_PROFILE_MARKERS = {
|
|
|
79
78
|
},
|
|
80
79
|
}
|
|
81
80
|
PROFILE_METADATA = "session.json"
|
|
81
|
+
PYTHON_RUNTIME_MARKER = ".msdevflow-python-runtime"
|
|
82
|
+
PYTHON_RUNTIME_MARKER_CONTENT = "managed-by=msdevflow\n"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def configured_directory(value: str, variable: str) -> Path:
|
|
86
|
+
selected = Path(value).expanduser()
|
|
87
|
+
if not selected.is_absolute():
|
|
88
|
+
raise OpenLibingAuthRequired(
|
|
89
|
+
f"{variable} 必须是绝对路径或以 ~ 开头。请运行 npx msdevflow@latest setup。"
|
|
90
|
+
)
|
|
91
|
+
return selected
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def default_python_runtime_dir() -> Path:
|
|
95
|
+
configured = os.getenv("MSDEVFLOW_PYTHON_DIR")
|
|
96
|
+
if configured:
|
|
97
|
+
return configured_directory(configured, "MSDEVFLOW_PYTHON_DIR")
|
|
98
|
+
if os.name == "nt":
|
|
99
|
+
local_app_data = os.getenv("LOCALAPPDATA")
|
|
100
|
+
root = (
|
|
101
|
+
configured_directory(local_app_data, "LOCALAPPDATA")
|
|
102
|
+
if local_app_data
|
|
103
|
+
else Path.home() / "AppData" / "Local"
|
|
104
|
+
)
|
|
105
|
+
return root / "msdevflow" / "python"
|
|
106
|
+
data_home = os.getenv("XDG_DATA_HOME")
|
|
107
|
+
root = (
|
|
108
|
+
configured_directory(data_home, "XDG_DATA_HOME")
|
|
109
|
+
if data_home
|
|
110
|
+
else Path.home() / ".local" / "share"
|
|
111
|
+
)
|
|
112
|
+
return root / "msdevflow" / "python"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def managed_python_executable(runtime_dir: Path | None = None) -> Path:
|
|
116
|
+
selected = (runtime_dir or default_python_runtime_dir()).expanduser()
|
|
117
|
+
executable = selected / ("Scripts/python.exe" if os.name == "nt" else "bin/python")
|
|
118
|
+
marker = selected / PYTHON_RUNTIME_MARKER
|
|
119
|
+
try:
|
|
120
|
+
owned = (
|
|
121
|
+
selected.is_dir()
|
|
122
|
+
and not selected.is_symlink()
|
|
123
|
+
and marker.is_file()
|
|
124
|
+
and not marker.is_symlink()
|
|
125
|
+
and marker.read_text(encoding="utf-8") == PYTHON_RUNTIME_MARKER_CONTENT
|
|
126
|
+
)
|
|
127
|
+
except OSError:
|
|
128
|
+
owned = False
|
|
129
|
+
if not owned or not executable.is_file():
|
|
130
|
+
raise OpenLibingAuthRequired(
|
|
131
|
+
f"msdevflow 受管 Python 运行时不可用:{selected}。请运行 npx msdevflow@latest setup。"
|
|
132
|
+
)
|
|
133
|
+
return executable
|
|
82
134
|
|
|
83
135
|
|
|
84
136
|
def profile_marker(profile_dir: Path) -> Path:
|
|
@@ -181,29 +233,18 @@ def clear_openlibing_storage(context: Any, page: Any, base_url: str) -> None:
|
|
|
181
233
|
raise OpenLibingAuthRequired("无法从持久 profile 清除 openLiBing Cookie,已拒绝保存会话。")
|
|
182
234
|
|
|
183
235
|
|
|
184
|
-
def oauth_requirements_file() -> Path:
|
|
185
|
-
return Path(__file__).with_name("requirements.txt")
|
|
186
|
-
|
|
187
|
-
|
|
188
236
|
def ensure_playwright() -> None:
|
|
237
|
+
executable = managed_python_executable()
|
|
238
|
+
active_prefix = os.path.normcase(str(Path(sys.prefix).resolve()))
|
|
239
|
+
managed_prefix = os.path.normcase(str(default_python_runtime_dir().resolve()))
|
|
240
|
+
if active_prefix != managed_prefix:
|
|
241
|
+
os.execv(str(executable), [str(executable), str(Path(__file__).resolve()), *sys.argv[1:]])
|
|
189
242
|
try:
|
|
190
243
|
importlib.import_module("playwright.sync_api")
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
raise OpenLibingAuthRequired("缺少 openLiBing OAuth 依赖清单,无法自动安装 Playwright。")
|
|
196
|
-
result = subprocess.run(
|
|
197
|
-
[sys.executable, "-m", "pip", "install", "-r", str(requirements)],
|
|
198
|
-
check=False,
|
|
199
|
-
)
|
|
200
|
-
if result.returncode != 0:
|
|
201
|
-
raise OpenLibingAuthRequired("自动安装 Playwright 失败。")
|
|
202
|
-
importlib.invalidate_caches()
|
|
203
|
-
try:
|
|
204
|
-
importlib.import_module("playwright.sync_api")
|
|
205
|
-
except ImportError as error:
|
|
206
|
-
raise OpenLibingAuthRequired("Playwright 安装完成但当前 Python 无法导入。") from error
|
|
244
|
+
except ImportError as error:
|
|
245
|
+
raise OpenLibingAuthRequired(
|
|
246
|
+
"受管 Python 运行时缺少 Playwright。请运行 npx msdevflow@latest setup。"
|
|
247
|
+
) from error
|
|
207
248
|
|
|
208
249
|
|
|
209
250
|
def playwright_chromium_path() -> Path:
|