feat: zcode thinking-mode 0.3.0(state历史+rollback+分写+校验收紧+34用例全绿+文档)

This commit is contained in:
2026-09-21 09:47:20 +08:00
commit 6ed5dda847
41 changed files with 6213 additions and 0 deletions
+179
View File
@@ -0,0 +1,179 @@
/**
* 插件源码 ↔ 安装副本 一致性审计。
*
* 背景:这些插件是"工作区源码 + ~/.zcode/plugins 安装副本"两份并存的手工流程,
* 副本滞后不会被任何东西发现 —— 改了源码没重装,跑的还是旧代码,而且报错会指向
* 一个你刚改好的文件。这个脚本就是拿来看这件事的。
*
* node audit-plugins.mjs # 审计工作区里每个插件(源码 vs 安装副本 + 文档/语法)
* node audit-plugins.mjs --drift # 只报漂移,不跑语法检查
* node audit-plugins.mjs <名字>... # 只审计指定插件(不传=全部)
*
* 环境变量:
* ZCODE_AUDIT_WORKSPACE 工作区路径(默认本脚本所在目录;测试用)
* ZCODE_PLUGINS_DIR 安装目录(默认 ~/.zcode/plugins;测试用)
*
* 只读,不改任何东西。退出码:有问题为 1。
*/
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { execFileSync } from "node:child_process";
const WORKSPACE = process.env.ZCODE_AUDIT_WORKSPACE || import.meta.dirname;
const PLUGINS = process.env.ZCODE_PLUGINS_DIR || path.join(os.homedir(), ".zcode", "plugins");
const DRIFT_ONLY = process.argv.includes("--drift");
/** 位置参数 = 只审计这些插件;不传则全部 */
const WANTED = process.argv.slice(2).filter((a) => !a.startsWith("--"));
/** 建议忽略的条目:本地产物,不属于"该同步的内容" */
const IGNORE = new Set(["node_modules", ".git", "package-lock.json"]);
/**
* 哪些路径算"源码管理的" —— 只有这些位置的文件从源码删掉才算残留。
*
* 不能用扩展名一刀切:根目录的 local-state.json 是操作员放的本机状态,
* 按扩展名判会把它报成"残留代码建议删除",把一个只读审计工具变成删数据的推手。
* 目录归属才是可靠信号:scripts/ 与 commands/ 完全是源码产物,根目录只认 .md 和 package.json。
*/
function isSourceManaged(rel) {
const r = rel.split(path.sep).join("/");
if (r.startsWith("scripts/") || r.startsWith("commands/") || r.startsWith(".zcode-plugin/")) return true;
if (r.includes("/")) return false; // 别的子目录:来源不明,不当残留
return /\.(md)$/i.test(r) || r === "package.json"; // 根目录只认这两种
}
const out = [];
let problems = 0;
let warns = 0;
function walkFiles(root, base = root, acc = []) {
for (const e of fs.readdirSync(base, { withFileTypes: true })) {
if (IGNORE.has(e.name)) continue;
const p = path.join(base, e.name);
if (e.isDirectory()) walkFiles(root, p, acc);
else if (e.isFile()) acc.push(path.relative(root, p).split(path.sep).join("/"));
}
return acc;
}
/** 源码有而副本没有 / 两边内容不同 / 副本有而源码没有 */
function diffTrees(srcRoot, dstRoot) {
const a = new Set(walkFiles(srcRoot)), b = new Set(walkFiles(dstRoot));
const missing = [...a].filter((f) => !b.has(f)).sort(); // 没部署过去
const extra = [...b].filter((f) => !a.has(f)).sort(); // 副本里多出来的
const changed = [...a].filter((f) => b.has(f)
&& fs.readFileSync(path.join(srcRoot, f), "utf8") !== fs.readFileSync(path.join(dstRoot, f), "utf8")).sort();
return { missing, extra, changed };
}
const sources = fs.readdirSync(WORKSPACE, { withFileTypes: true })
.filter((e) => e.isDirectory() && fs.existsSync(path.join(WORKSPACE, e.name, ".zcode-plugin", "plugin.json")))
.map((e) => e.name)
.filter((n) => !WANTED.length || WANTED.includes(n))
.sort();
if (!sources.length) {
console.log(WANTED.length
? `# 没找到插件:${WANTED.join(", ")}\n- 工作区:${WORKSPACE}`
: `# 未在工作区找到插件(需要 .zcode-plugin/plugin.json)\n- 工作区:${WORKSPACE}`);
process.exit(1);
}
out.push(`# 插件审计`);
out.push(`- 工作区:${WORKSPACE}`);
out.push(`- 安装目录:${PLUGINS}`);
for (const name of sources) {
const src = path.join(WORKSPACE, name);
const dst = path.join(PLUGINS, name);
const srcVer = JSON.parse(fs.readFileSync(path.join(src, ".zcode-plugin", "plugin.json"), "utf8")).version ?? "?";
out.push(`\n### ${name} v${srcVer}`);
if (!fs.existsSync(dst)) {
out.push(` ✗ 未安装 —— 装一下才能生效`);
problems++;
continue;
}
const dstVer = JSON.parse(fs.readFileSync(path.join(dst, ".zcode-plugin", "plugin.json"), "utf8")).version ?? "?";
if (dstVer !== srcVer) {
out.push(` ✗ 版本不一致:源码 v${srcVer} / 安装 v${dstVer}`);
problems++;
}
const { missing, extra, changed } = diffTrees(src, dst);
if (!missing.length && !changed.length && !extra.length) {
out.push(` ✓ 与安装副本逐字节一致`);
} else {
// 未部署/内容不同 = 真漂移,失败。
// 副本独有分两种:本机 state 类(有意保留,只警告)vs 源码删掉的旧文件(残留,必须报)。
const deleted = extra.filter(isSourceManaged);
const harmless = extra.filter((f) => !isSourceManaged(f));
if (missing.length || changed.length || deleted.length) problems++;
if (harmless.length) warns++;
out.push(` ${missing.length || changed.length || deleted.length ? "✗" : "⚠"} 副本与源码有差异:`);
for (const f of missing) out.push(` 未部署:${f}`);
for (const f of changed) out.push(` 内容不同:${f}`);
for (const f of deleted) out.push(` 源码已删除但副本还在(残留代码仍会被加载):${f}`);
for (const f of harmless) out.push(` 本地产物(有意保留,不影响):${f}`);
if (missing.length || changed.length) out.push(` → 重装:cp -r "${src}/." "${dst}/"`);
if (deleted.length) out.push(` → 残留文件需手工删除:rm "${dst}/${deleted[0]}"${deleted.length > 1 ? ` (另 ${deleted.length - 1} 个)` : ""}`);
}
if (DRIFT_ONLY) continue;
// manifest 合规
const mf = JSON.parse(fs.readFileSync(path.join(src, ".zcode-plugin", "plugin.json"), "utf8"));
if (!/^[a-z0-9][a-z0-9._-]{0,127}$/.test(mf.name)) { out.push(` ✗ name 不合规:${mf.name}`); problems++; }
// 命令文档:front-matter + 引用脚本存在性
const cmdDir = path.join(src, "commands");
const docs = [];
if (fs.existsSync(cmdDir)) {
const walk = (d) => {
for (const e of fs.readdirSync(d, { withFileTypes: true })) {
const p = path.join(d, e.name);
if (e.isDirectory()) walk(p);
else if (e.name.endsWith(".md")) docs.push(p);
}
};
walk(cmdDir);
}
for (const d of docs.sort()) {
const rel = path.relative(src, d).split(path.sep).join("/");
const body = fs.readFileSync(d, "utf8");
const fm = /^---\r?\n([\s\S]*?)\r?\n---/.exec(body);
const desc = fm ? /description:\s*(.+)/.exec(fm[1])?.[1] : null;
const refs = [...body.matchAll(/-name\s+(\S+\.mjs)/g)].map((m) => m[1]);
const miss = refs.filter((r) => !fs.existsSync(path.join(src, "scripts", r)));
const ok = !!fm && !!desc && miss.length === 0;
if (!ok) problems++;
out.push(` ${ok ? "✓" : "✗"} ${rel}${desc ? "" : " (缺 description)"}${miss.length ? ` 引用脚本缺失:${miss.join(",")}` : ""}`);
}
// 脚本语法自检(--check 只解析不执行)
const scripts = fs.existsSync(path.join(src, "scripts")) ? fs.readdirSync(path.join(src, "scripts")) : [];
for (const s of scripts.filter((x) => x.endsWith(".mjs"))) {
try {
execFileSync(process.execPath, ["--check", path.join(src, "scripts", s)], { stdio: "pipe" });
} catch (e) {
out.push(` ✗ 语法 ${s}:${String(e.stderr ?? e).split("\n").find((l) => l.includes("Error")) ?? ""}`);
problems++;
}
}
const hasTests = fs.existsSync(path.join(src, "tests"));
out.push(` ${hasTests ? "✓" : "⚠"} 文档 ${docs.length} / 脚本 ${scripts.filter((x) => x.endsWith(".mjs")).length} / 测试 ${hasTests ? "有" : "无"}`);
}
out.push("");
if (problems) {
out.push(`结论:${problems} 处需要处理(见上面 ✗)。`);
} else if (warns) {
out.push(`结论:源码与安装副本一致,结构完整;${warns} 处提示不影响运行。`);
} else {
out.push(`结论:源码与安装副本一致,结构完整。`);
}
console.log(out.filter(Boolean).join("\n"));
process.exit(problems ? 1 : 0);