@dipoleml/dcode-cli 1.2.1 → 1.2.3
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/dist/dcode.cjs +2214 -1891
- package/dist/packages/browser-use-plugin/.dcode-plugin/plugin.json +8 -8
- package/dist/packages/dcode-cua-plugin/.dcode-plugin/plugin.json +10 -0
- package/dist/packages/dcode-cua-plugin/docs/computer-use.md +470 -0
- package/dist/packages/dcode-cua-plugin/package.json +12 -0
- package/dist/packages/dcode-cua-plugin/scripts/bump-dcode-cua-producer.mjs +305 -0
- package/dist/packages/dcode-cua-plugin/scripts/check-cua-baseline.mjs +155 -0
- package/dist/packages/dcode-cua-plugin/scripts/check-sdk.mjs +4 -0
- package/dist/packages/dcode-cua-plugin/scripts/check-version-coherence.mjs +478 -0
- package/dist/packages/dcode-cua-plugin/scripts/computer-use-client.mjs +1208 -0
- package/dist/packages/dcode-cua-plugin/scripts/resolve-pnpm-invocation.mjs +26 -0
- package/dist/packages/dcode-cua-plugin/scripts/sync-cache.mjs +116 -0
- package/dist/packages/dcode-cua-plugin/scripts/sync-skill-from-dcode-cua.mjs +115 -0
- package/dist/packages/dcode-cua-plugin/skills/computer-use/SKILL.md +306 -0
- package/dist/packages/node-repl-host/dist/mcp/server.js +3 -3
- package/dist/packages/node-repl-host/package.json +1 -1
- package/dist/provider/dcode-builtin.json +6748 -6748
- package/node_modules/@dcode/contracts/dist/events/event-reducer-helpers.d.ts +1 -1
- package/node_modules/@dcode/contracts/dist/events/event-reducer-helpers.js +5 -1
- package/node_modules/@dcode/contracts/dist/events/event-reducer.js +26 -5
- package/node_modules/@dcode/contracts/dist/interfaces/session.port.d.ts +5 -1
- package/node_modules/@dcode/contracts/dist/tools/bash.js +4 -1
- package/node_modules/@dcode/i18n/dist/locales/en-US.js +11 -23
- package/node_modules/@dcode/i18n/dist/locales/fr-CA.js +9 -21
- package/node_modules/@dcode/i18n/dist/types.d.ts +0 -12
- package/node_modules/@dcode/model-option-map/package.json +1 -1
- package/node_modules/@dcode/shared/package.json +1 -1
- package/node_modules/@dcode/tui/dist/app-model-ref.js +9 -2
- package/node_modules/@dcode/tui/dist/index.js +57 -65
- package/node_modules/@dcode/tui/dist/theme/defaults.js +21 -15
- package/package.json +3 -3
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// 唯一的原子 producer bump 命令 —— 一次调用完成全部 producer 记录的同步更新:
|
|
3
|
+
// pnpm-workspace.yaml catalog(两个别名)
|
|
4
|
+
// pnpm-lock.yaml(通过 pnpm install)
|
|
5
|
+
// bundled Skill(skills/computer-use/SKILL.md)
|
|
6
|
+
// upstream.json(commit/ref/skillSha256 provenance)
|
|
7
|
+
// wrapper 版本三处记录 + 官方插件定义 + 实现契约(复用 check-version-coherence.mjs 写回)
|
|
8
|
+
// 成功后自动跑快速基线门禁与 skill-sync 不可变性测试自校验;--commit 直接产出单个
|
|
9
|
+
// chore(cua) 提交,从机制上消灭“改 catalog、更新 lock、同步 Skill/provenance、
|
|
10
|
+
// 改版本/契约”被拆成多个提交的漂移窗口。
|
|
11
|
+
//
|
|
12
|
+
// 背景(2026-08-20):这两条链路此前是手工分开跑的(改 catalog+install 一个提交,
|
|
13
|
+
// sync:skill + sync:version 另一个提交,甚至漏跑),集成分支出现 catalog=e38999b7 /
|
|
14
|
+
// upstream.json=0bf36086 的漂移。CI(check-cua-baseline.mjs)现在直接拒绝这种状态,
|
|
15
|
+
// 本脚本是唯一合法修复/升级路径。
|
|
16
|
+
//
|
|
17
|
+
// 用法:
|
|
18
|
+
// node scripts/bump-dcode-cua-producer.mjs # bump 到 producer origin/main
|
|
19
|
+
// node scripts/bump-dcode-cua-producer.mjs <ref|sha> # bump 到指定 ref/评审 SHA
|
|
20
|
+
// node scripts/bump-dcode-cua-producer.mjs <ref> --commit # 校验通过后直接产出单个提交
|
|
21
|
+
// 环境变量:DCODE_CUA_REPO 指定 producer 本地克隆(默认同级 ../dcode-cua)。
|
|
22
|
+
|
|
23
|
+
import { createHash } from "node:crypto";
|
|
24
|
+
import { execFile as execFileCallback } from "node:child_process";
|
|
25
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
26
|
+
import { dirname, resolve } from "node:path";
|
|
27
|
+
import { fileURLToPath } from "node:url";
|
|
28
|
+
import { promisify } from "node:util";
|
|
29
|
+
|
|
30
|
+
import { resolvePnpmInvocation } from "./resolve-pnpm-invocation.mjs";
|
|
31
|
+
|
|
32
|
+
const execFile = promisify(execFileCallback);
|
|
33
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
34
|
+
const repoRoot = resolve(packageRoot, "../../../..");
|
|
35
|
+
const workspacePath = resolve(repoRoot, "pnpm-workspace.yaml");
|
|
36
|
+
const producerRoot = resolve(
|
|
37
|
+
process.env.DCODE_CUA_REPO ?? resolve(packageRoot, "../../../../../dcode-cua"),
|
|
38
|
+
);
|
|
39
|
+
const skillPath = "plugin/skills/computer-use/SKILL.md";
|
|
40
|
+
const producerManifestPath = "plugin/.dcode-plugin/plugin.json";
|
|
41
|
+
const targetSkill = resolve(packageRoot, "skills/computer-use/SKILL.md");
|
|
42
|
+
const provenancePath = resolve(packageRoot, "upstream.json");
|
|
43
|
+
const doCommit = process.argv.includes("--commit");
|
|
44
|
+
const positionalArgs = process.argv.slice(2).filter((arg) => !arg.startsWith("--"));
|
|
45
|
+
const producerRefArg = positionalArgs[0] ?? "origin/main";
|
|
46
|
+
|
|
47
|
+
async function run(command, args, options = {}) {
|
|
48
|
+
const result = await execFile(command, args, {
|
|
49
|
+
encoding: "utf8",
|
|
50
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
51
|
+
...options,
|
|
52
|
+
});
|
|
53
|
+
return result.stdout.trim();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 二进制安全的变体:git show 的文件内容必须逐字节等于 producer 对象(含末尾换行),
|
|
57
|
+
// 任何 trim 都会造成 sha256 与不可变性校验漂移。
|
|
58
|
+
async function runBuffer(command, args, options = {}) {
|
|
59
|
+
const result = await execFile(command, args, {
|
|
60
|
+
encoding: "buffer",
|
|
61
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
62
|
+
...options,
|
|
63
|
+
});
|
|
64
|
+
return Buffer.from(result.stdout);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function runOrThrow(label, command, args, options = {}) {
|
|
68
|
+
try {
|
|
69
|
+
return await run(command, args, options);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`${label} 失败:${error.stderr || error.stdout || error.message}`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 1) 解析 producer commit:fetch 后 rev-parse;40 位 SHA 额外要求它已存在于某个远端
|
|
78
|
+
// ref 上,防止把本地实验提交 pin 进产品依赖锁。
|
|
79
|
+
console.log(`[cua-bump] producer 仓库:${producerRoot}`);
|
|
80
|
+
await runOrThrow("producer git fetch", "git", [
|
|
81
|
+
"-C",
|
|
82
|
+
producerRoot,
|
|
83
|
+
"fetch",
|
|
84
|
+
"origin",
|
|
85
|
+
"--prune",
|
|
86
|
+
]);
|
|
87
|
+
let producerCommit;
|
|
88
|
+
try {
|
|
89
|
+
producerCommit = await run("git", [
|
|
90
|
+
"-C",
|
|
91
|
+
producerRoot,
|
|
92
|
+
"rev-parse",
|
|
93
|
+
"--verify",
|
|
94
|
+
`${producerRefArg}^{commit}`,
|
|
95
|
+
]);
|
|
96
|
+
} catch {
|
|
97
|
+
if (/^[0-9a-f]{40}$/u.test(producerRefArg)) {
|
|
98
|
+
await runOrThrow("按 SHA 精确拉取", "git", [
|
|
99
|
+
"-C",
|
|
100
|
+
producerRoot,
|
|
101
|
+
"fetch",
|
|
102
|
+
"origin",
|
|
103
|
+
producerRefArg,
|
|
104
|
+
]);
|
|
105
|
+
producerCommit = producerRefArg;
|
|
106
|
+
} else {
|
|
107
|
+
throw new Error(`producer 仓库里找不到 ref:${producerRefArg}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (!/^[0-9a-f]{40}$/u.test(producerCommit)) {
|
|
111
|
+
throw new Error(`producer commit 不是 40 位 SHA:${producerCommit}`);
|
|
112
|
+
}
|
|
113
|
+
if (/^[0-9a-f]{40}$/u.test(producerRefArg)) {
|
|
114
|
+
const containing = await run("git", [
|
|
115
|
+
"-C",
|
|
116
|
+
producerRoot,
|
|
117
|
+
"branch",
|
|
118
|
+
"-r",
|
|
119
|
+
"--contains",
|
|
120
|
+
producerCommit,
|
|
121
|
+
]);
|
|
122
|
+
if (containing === "") {
|
|
123
|
+
throw new Error(
|
|
124
|
+
`${producerCommit} 不在任何远端 ref 上;先推送 producer 提交再 pin(禁止 pin 本地实验提交)`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const producerSubject = await run("git", [
|
|
129
|
+
"-C",
|
|
130
|
+
producerRoot,
|
|
131
|
+
"log",
|
|
132
|
+
"-1",
|
|
133
|
+
"--format=%s",
|
|
134
|
+
producerCommit,
|
|
135
|
+
]);
|
|
136
|
+
console.log(`[cua-bump] 目标 producer:${producerCommit}(${producerSubject})`);
|
|
137
|
+
|
|
138
|
+
// 2) 从 producer commit 的 git 对象里直接取 Skill 字节与插件 manifest 版本
|
|
139
|
+
// (不 checkout producer 工作区,冻结中的工作分支不受影响)。
|
|
140
|
+
let skillBytes;
|
|
141
|
+
try {
|
|
142
|
+
skillBytes = await runBuffer("git", [
|
|
143
|
+
"-C",
|
|
144
|
+
producerRoot,
|
|
145
|
+
"show",
|
|
146
|
+
`${producerCommit}:${skillPath}`,
|
|
147
|
+
]);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`读取 producer Skill 失败:${error.stderr || error.stdout || error.message}`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
const producerManifestText = await runOrThrow("读取 producer 插件 manifest", "git", [
|
|
154
|
+
"-C",
|
|
155
|
+
producerRoot,
|
|
156
|
+
"show",
|
|
157
|
+
`${producerCommit}:${producerManifestPath}`,
|
|
158
|
+
]);
|
|
159
|
+
const producerVersion = JSON.parse(producerManifestText).version;
|
|
160
|
+
if (typeof producerVersion !== "string" || producerVersion.length === 0) {
|
|
161
|
+
throw new Error(`producer 插件 manifest 缺少合法 version:${producerManifestText}`);
|
|
162
|
+
}
|
|
163
|
+
console.log(`[cua-bump] producer 版本:${producerVersion}`);
|
|
164
|
+
|
|
165
|
+
// 3) 原子更新 catalog 两个别名(各自必须恰好一处,保持 YAML 字节布局不变)。
|
|
166
|
+
const aliases = ["@dcode/dcode-cua", "@dcode/dcode-cua-helper-runtime"];
|
|
167
|
+
let workspaceText = await readFile(workspacePath, "utf8");
|
|
168
|
+
for (const alias of aliases) {
|
|
169
|
+
const pattern = new RegExp(
|
|
170
|
+
`^(\\s+"${alias}":\\s+"git\\+https://dev\\.aminer\\.cn/codegeex/dcode-cua\\.git#)([0-9a-f]{40})("\\s*)$`,
|
|
171
|
+
"gmu",
|
|
172
|
+
);
|
|
173
|
+
const matches = [...workspaceText.matchAll(pattern)];
|
|
174
|
+
if (matches.length !== 1) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`pnpm-workspace.yaml catalog 里 ${alias} 必须恰好 pin 一个 40 位 commit(当前 ${matches.length} 个)`,
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
workspaceText = workspaceText.replace(
|
|
180
|
+
pattern,
|
|
181
|
+
`$1${producerCommit}$3`,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
await writeFile(workspacePath, workspaceText);
|
|
185
|
+
|
|
186
|
+
// 4) 更新 lockfile 与 node_modules(coherence 写回要读 installed producer 契约)。
|
|
187
|
+
console.log("[cua-bump] 运行 pnpm install(更新 lockfile 与 installed producer)…");
|
|
188
|
+
// Bug 原因(2026-09-14):这里原来直接 execFile("pnpm", ...),Windows 上必然
|
|
189
|
+
// `spawn pnpm ENOENT`——pnpm 装出来是 `pnpm.cmd`,execFile 不走 shell 时不补 PATHEXT。
|
|
190
|
+
// 换成 `pnpm.cmd` 又变成 `spawn EINVAL`:Node 自 18.20/20.12 起(CVE-2024-27980)禁止
|
|
191
|
+
// 不带 shell spawn `.cmd`/`.bat`。也就是说这条原子 bump 在 Windows 上从来没跑通过,而
|
|
192
|
+
// 手改 producer SHA 会被 CI 第一站 check-cua-baseline.mjs 拒绝——等于 Windows 上没有
|
|
193
|
+
// 可用的升 pin 路径。决策抽到 resolve-pnpm-invocation.mjs 才能被单测覆盖:本脚本是
|
|
194
|
+
// top-level await,import 它就会真的执行一次 bump。
|
|
195
|
+
const pnpmInvocation = resolvePnpmInvocation({
|
|
196
|
+
npmExecpath: process.env.npm_execpath,
|
|
197
|
+
platform: process.platform,
|
|
198
|
+
});
|
|
199
|
+
await runOrThrow("pnpm install", pnpmInvocation.command, pnpmInvocation.args, {
|
|
200
|
+
cwd: repoRoot,
|
|
201
|
+
...pnpmInvocation.options,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// 5) 写 bundled Skill 与 upstream.json provenance。
|
|
205
|
+
await mkdir(dirname(targetSkill), { recursive: true });
|
|
206
|
+
await writeFile(targetSkill, skillBytes);
|
|
207
|
+
await writeFile(
|
|
208
|
+
provenancePath,
|
|
209
|
+
`${JSON.stringify(
|
|
210
|
+
{
|
|
211
|
+
repository: "https://dev.aminer.cn/codegeex/dcode-cua.git",
|
|
212
|
+
ref: producerCommit,
|
|
213
|
+
commit: producerCommit,
|
|
214
|
+
skillPath,
|
|
215
|
+
skillSha256: createHash("sha256").update(skillBytes).digest("hex"),
|
|
216
|
+
},
|
|
217
|
+
null,
|
|
218
|
+
2,
|
|
219
|
+
)}\n`,
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
// 6) 对齐 wrapper 版本三处记录 + 官方插件定义 + 实现契约(复用既有写回逻辑)。
|
|
223
|
+
console.log("[cua-bump] 对齐 wrapper 版本与实现契约…");
|
|
224
|
+
const coherenceOutput = await runOrThrow(
|
|
225
|
+
"check-version-coherence 写回",
|
|
226
|
+
"node",
|
|
227
|
+
["scripts/check-version-coherence.mjs"],
|
|
228
|
+
{ cwd: packageRoot },
|
|
229
|
+
);
|
|
230
|
+
console.log(coherenceOutput);
|
|
231
|
+
|
|
232
|
+
// 7) 自校验:快速基线门禁 + Skill 不可变性测试必须全绿,否则拒绝产出提交。
|
|
233
|
+
console.log("[cua-bump] 自校验:check-cua-baseline + skill-sync…");
|
|
234
|
+
await runOrThrow("check-cua-baseline", "node", [
|
|
235
|
+
"scripts/check-cua-baseline.mjs",
|
|
236
|
+
], { cwd: packageRoot });
|
|
237
|
+
await runOrThrow("skill-sync node test", "node", [
|
|
238
|
+
"--test",
|
|
239
|
+
"tests/skill-sync.node-test.mjs",
|
|
240
|
+
], { cwd: packageRoot });
|
|
241
|
+
|
|
242
|
+
// 8) 汇总变更并(可选)产出单个原子提交。
|
|
243
|
+
const commitFileSet = [
|
|
244
|
+
"pnpm-workspace.yaml",
|
|
245
|
+
"pnpm-lock.yaml",
|
|
246
|
+
"apps/dcode-cli/packages/dcode-cua-plugin/upstream.json",
|
|
247
|
+
"apps/dcode-cli/packages/dcode-cua-plugin/skills/computer-use/SKILL.md",
|
|
248
|
+
"apps/dcode-cli/packages/dcode-cua-plugin/package.json",
|
|
249
|
+
"apps/dcode-cli/packages/dcode-cua-plugin/.dcode-plugin/plugin.json",
|
|
250
|
+
"apps/dcode-cli/packages/bootstrap/src/app/official-plugin-definitions.ts",
|
|
251
|
+
"docs/superpowers/specs/2026-08-14-cua-final-raster-integrity-contract.md",
|
|
252
|
+
];
|
|
253
|
+
// 注意不能用 trim():porcelain 每行以 "XY " 两列状态开头,未暂存改动的首列是空格,
|
|
254
|
+
// 整体 trim 会吃掉第一行的前导空格,把路径错切 3 个字符。
|
|
255
|
+
const statusOutput = await execFile(
|
|
256
|
+
"git",
|
|
257
|
+
["-C", repoRoot, "status", "--porcelain"],
|
|
258
|
+
{ encoding: "utf8", maxBuffer: 64 * 1024 * 1024 },
|
|
259
|
+
);
|
|
260
|
+
const statusLines = statusOutput.stdout
|
|
261
|
+
.split("\n")
|
|
262
|
+
.filter((line) => line !== "");
|
|
263
|
+
const relevant = statusLines.filter((line) =>
|
|
264
|
+
commitFileSet.some((path) => line.slice(3).replace(/^"|"$/gu, "") === path),
|
|
265
|
+
);
|
|
266
|
+
const unexpected = statusLines.filter(
|
|
267
|
+
(line) =>
|
|
268
|
+
// 未跟踪文件不属于任何提交,不阻塞原子 bump;已跟踪文件的意外改动才阻塞。
|
|
269
|
+
!line.startsWith("??") &&
|
|
270
|
+
!commitFileSet.some((path) => line.slice(3).replace(/^"|"$/gu, "") === path),
|
|
271
|
+
);
|
|
272
|
+
console.log("[cua-bump] 本次原子变更:");
|
|
273
|
+
for (const line of relevant) console.log(` ${line}`);
|
|
274
|
+
if (unexpected.length > 0) {
|
|
275
|
+
console.log("[cua-bump] 工作区里的其他未提交改动(不属于本次 bump,不会被提交):");
|
|
276
|
+
for (const line of unexpected) console.log(` ${line}`);
|
|
277
|
+
}
|
|
278
|
+
const refLabel =
|
|
279
|
+
producerRefArg === "origin/main" ? "main" : producerRefArg;
|
|
280
|
+
const commitMessage = `chore(cua): pin producer to ${refLabel} ${producerCommit.slice(0, 9)}(${producerSubject})`;
|
|
281
|
+
if (!doCommit) {
|
|
282
|
+
console.log(`[cua-bump] 完成。建议提交信息:\n ${commitMessage}`);
|
|
283
|
+
console.log("[cua-bump] 加 --commit 可直接产出这个原子提交。");
|
|
284
|
+
process.exit(0);
|
|
285
|
+
}
|
|
286
|
+
if (unexpected.length > 0) {
|
|
287
|
+
throw new Error(
|
|
288
|
+
"工作区存在不属于本次 bump 的未提交改动;为保持原子性拒绝自动提交,请先处理它们。",
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
if (relevant.length === 0) {
|
|
292
|
+
// 幂等重跑:目标 producer 与工作区已一致(例如修复漂移后重复执行),
|
|
293
|
+
// 不能让 git commit 以 "nothing to commit" 报错。
|
|
294
|
+
console.log(`[cua-bump] 工作区已与目标 producer ${producerCommit} 一致,无需提交。`);
|
|
295
|
+
process.exit(0);
|
|
296
|
+
}
|
|
297
|
+
await runOrThrow("git add", "git", ["-C", repoRoot, "add", ...commitFileSet]);
|
|
298
|
+
await runOrThrow("git commit", "git", [
|
|
299
|
+
"-C",
|
|
300
|
+
repoRoot,
|
|
301
|
+
"commit",
|
|
302
|
+
"-m",
|
|
303
|
+
commitMessage,
|
|
304
|
+
]);
|
|
305
|
+
console.log(`[cua-bump] 已提交:${commitMessage}`);
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// CUA pin/provenance 快速门禁 —— 不需要 pnpm install,秒级拒绝手工改 pin 造成的漂移。
|
|
3
|
+
//
|
|
4
|
+
// 背景(2026-08-20):producer pin bump(catalog + lockfile + 实现契约)与 Skill/provenance
|
|
5
|
+
// 同步(upstream.json + bundled Skill + wrapper 版本)曾是两条可拆开提交的手工链路,
|
|
6
|
+
// 集成分支出现过 catalog=e38999b7 / upstream.json=0bf36086 的漂移;skill-sync.node-test.mjs
|
|
7
|
+
// 虽断言相等,但挂在重测试道,漂移能溜过早期 CI。本脚本挂在 build:desktop:app 的
|
|
8
|
+
// pnpm install 之前(与 ci-lint-pipeline.mjs 同层),把任何手工 producer SHA 修改
|
|
9
|
+
// 导致的不一致变成流水线第一站的硬失败。
|
|
10
|
+
//
|
|
11
|
+
// 唯一合法的修改路径:
|
|
12
|
+
// node apps/dcode-cli/packages/dcode-cua-plugin/scripts/bump-dcode-cua-producer.mjs
|
|
13
|
+
//
|
|
14
|
+
// 本脚本只做“无需安装即可判定”的结构校验;安装后的深度校验(runtime 契约、
|
|
15
|
+
// installed producer 版本)仍由 check-version-coherence.mjs --check 在 build 阶段负责。
|
|
16
|
+
|
|
17
|
+
import { createHash } from "node:crypto";
|
|
18
|
+
import { readFile } from "node:fs/promises";
|
|
19
|
+
import { dirname, resolve } from "node:path";
|
|
20
|
+
import { fileURLToPath } from "node:url";
|
|
21
|
+
|
|
22
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
23
|
+
const repoRoot = resolve(packageRoot, "../../../..");
|
|
24
|
+
const [
|
|
25
|
+
workspaceText,
|
|
26
|
+
lockfileText,
|
|
27
|
+
provenanceText,
|
|
28
|
+
skillBytes,
|
|
29
|
+
contractText,
|
|
30
|
+
] = await Promise.all([
|
|
31
|
+
readFile(resolve(repoRoot, "pnpm-workspace.yaml"), "utf8"),
|
|
32
|
+
readFile(resolve(repoRoot, "pnpm-lock.yaml"), "utf8"),
|
|
33
|
+
readFile(resolve(packageRoot, "upstream.json"), "utf8"),
|
|
34
|
+
readFile(resolve(packageRoot, "skills/computer-use/SKILL.md")),
|
|
35
|
+
readFile(
|
|
36
|
+
resolve(
|
|
37
|
+
repoRoot,
|
|
38
|
+
"docs/superpowers/specs/2026-08-14-cua-final-raster-integrity-contract.md",
|
|
39
|
+
),
|
|
40
|
+
"utf8",
|
|
41
|
+
),
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
const failures = [];
|
|
45
|
+
const fail = (message) => failures.push(message);
|
|
46
|
+
|
|
47
|
+
// 1) catalog:两个别名必须各自恰好出现一次,且指向同一个 40 位 producer commit。
|
|
48
|
+
function readCatalogPin(alias) {
|
|
49
|
+
const pattern = new RegExp(
|
|
50
|
+
`^\\s+"${alias}":\\s+"git\\+https://dev\\.aminer\\.cn/codegeex/dcode-cua\\.git#([0-9a-f]{40})"\\s*$`,
|
|
51
|
+
"gmu",
|
|
52
|
+
);
|
|
53
|
+
return [...workspaceText.matchAll(pattern)].map((match) => match[1]);
|
|
54
|
+
}
|
|
55
|
+
const mainPins = readCatalogPin("@dcode/dcode-cua");
|
|
56
|
+
const runtimePins = readCatalogPin("@dcode/dcode-cua-helper-runtime");
|
|
57
|
+
if (mainPins.length !== 1) {
|
|
58
|
+
fail(
|
|
59
|
+
`pnpm-workspace.yaml catalog 里 @dcode/dcode-cua 必须恰好 pin 一个 40 位 commit(当前 ${mainPins.length} 个)`,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
if (runtimePins.length !== 1) {
|
|
63
|
+
fail(
|
|
64
|
+
`pnpm-workspace.yaml catalog 里 @dcode/dcode-cua-helper-runtime 必须恰好 pin 一个 40 位 commit(当前 ${runtimePins.length} 个)`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
if (
|
|
68
|
+
mainPins.length === 1 &&
|
|
69
|
+
runtimePins.length === 1 &&
|
|
70
|
+
mainPins[0] !== runtimePins[0]
|
|
71
|
+
) {
|
|
72
|
+
fail(
|
|
73
|
+
`catalog 两个 producer 别名漂移:@dcode/dcode-cua=${mainPins[0]},` +
|
|
74
|
+
`@dcode/dcode-cua-helper-runtime=${runtimePins[0]}`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
const catalogCommit = mainPins[0] ?? runtimePins[0] ?? null;
|
|
78
|
+
if (!catalogCommit) {
|
|
79
|
+
// 前面已记录具体失败;这里只避免后续检查打出误导性噪声。
|
|
80
|
+
reportAndExit();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 2) lockfile:所有 dcode-cua.git#<sha> 引用(catalog resolution、importer 直连与
|
|
84
|
+
// resolved version)必须全部等于 catalog commit。任何旧 SHA 残留都算漂移。
|
|
85
|
+
const lockCommits = [
|
|
86
|
+
...new Set(
|
|
87
|
+
[...lockfileText.matchAll(/dcode-cua\.git#([0-9a-f]{40})/gu)].map(
|
|
88
|
+
(match) => match[1],
|
|
89
|
+
),
|
|
90
|
+
),
|
|
91
|
+
];
|
|
92
|
+
if (lockCommits.length === 0) {
|
|
93
|
+
fail("pnpm-lock.yaml 里找不到任何 dcode-cua.git#<sha> producer 引用");
|
|
94
|
+
} else if (lockCommits.length > 1 || lockCommits[0] !== catalogCommit) {
|
|
95
|
+
fail(
|
|
96
|
+
`pnpm-lock.yaml producer 引用与 catalog 漂移:lock={${lockCommits.join(",")}},` +
|
|
97
|
+
`catalog=${catalogCommit}`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 3) upstream.json(provenance 第一事实源):commit === ref === catalog commit,
|
|
102
|
+
// 且记录的 skillSha256 必须等于 bundled Skill 的实际字节摘要。
|
|
103
|
+
let provenance;
|
|
104
|
+
try {
|
|
105
|
+
provenance = JSON.parse(provenanceText);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
fail(`upstream.json 不是合法 JSON:${error.message}`);
|
|
108
|
+
reportAndExit();
|
|
109
|
+
}
|
|
110
|
+
if (provenance.repository !== "https://dev.aminer.cn/codegeex/dcode-cua.git") {
|
|
111
|
+
fail(`upstream.json.repository 必须是 producer 仓库地址(当前 ${provenance.repository})`);
|
|
112
|
+
}
|
|
113
|
+
if (provenance.commit !== catalogCommit || provenance.ref !== catalogCommit) {
|
|
114
|
+
fail(
|
|
115
|
+
`upstream.json 与 catalog 漂移:upstream commit=${provenance.commit},` +
|
|
116
|
+
`ref=${provenance.ref},catalog=${catalogCommit}`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
if (provenance.skillPath !== "plugin/skills/computer-use/SKILL.md") {
|
|
120
|
+
fail(`upstream.json.skillPath 不是 producer Skill 路径(当前 ${provenance.skillPath})`);
|
|
121
|
+
}
|
|
122
|
+
const skillSha256 = createHash("sha256").update(skillBytes).digest("hex");
|
|
123
|
+
if (skillSha256 !== provenance.skillSha256) {
|
|
124
|
+
fail(
|
|
125
|
+
`bundled Skill 字节与 upstream.json.skillSha256 不符:实际 ${skillSha256},` +
|
|
126
|
+
`记录 ${provenance.skillSha256}`,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 4) 实现契约:producer ref 必须与 catalog 一致(SHA pin 场景;该文档同时被
|
|
131
|
+
// check-version-coherence.mjs 在安装后校验,这里让漂移在 install 前就暴露)。
|
|
132
|
+
const contractProducerRef = contractText.match(
|
|
133
|
+
/producer 实现:`dcode-cua@([^`]+)`/u,
|
|
134
|
+
)?.[1];
|
|
135
|
+
if (contractProducerRef !== catalogCommit) {
|
|
136
|
+
fail(
|
|
137
|
+
`实现契约 producer ref 漂移:契约=${contractProducerRef ?? "<missing>"},` +
|
|
138
|
+
`catalog=${catalogCommit}`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function reportAndExit() {
|
|
143
|
+
if (failures.length === 0) {
|
|
144
|
+
console.log(`[cua-baseline] 一致:producer pin = ${catalogCommit}`);
|
|
145
|
+
process.exit(0);
|
|
146
|
+
}
|
|
147
|
+
console.error("[cua-baseline] producer pin/provenance 漂移,拒绝继续:");
|
|
148
|
+
for (const message of failures) console.error(` - ${message}`);
|
|
149
|
+
console.error(
|
|
150
|
+
"修复:node apps/dcode-cli/packages/dcode-cua-plugin/scripts/bump-dcode-cua-producer.mjs" +
|
|
151
|
+
"(原子更新 catalog/lock/upstream.json/Skill/wrapper 版本,禁止手改 producer SHA)",
|
|
152
|
+
);
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
reportAndExit();
|