From 78873f12e2dd957bc866a82df44fe6ae014e1fb9 Mon Sep 17 00:00:00 2001 From: liguochuan <292761894@qq.com> Date: Thu, 27 Aug 2026 09:56:21 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20feat(init):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=20plugin=20=E6=8F=92=E4=BB=B6=E6=A8=A1=E5=BC=8F=E7=9B=AE?= =?UTF-8?q?=E6=A0=87=EF=BC=8CCLI/Core=20=E7=89=88=E6=9C=AC=E5=AF=B9?= =?UTF-8?q?=E9=BD=90=203.6.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - core:AGENTS 注册 plugin(root:null 虚拟目标);init 跳过配置根镜像;doctor 插件模式降级判定 - cli:help 与 init 完成摘要文案;core 依赖范围提升至 ^3.6.0 - scripts:新增 test-plugin-init 回归;三处版本钉死 fixture 改为动态读包版本 - .Knowledge:f2s-init-defaults / flow2spec-qoder-plugin 主题 rev2 与 index/matcher 同步 - 版本:CLI 3.6.0 / Core 3.6.0 / Template 3.5.0(未动模板) --- .Knowledge/index.md | 2 +- .../matchers/m-flow2spec-init-defaults.json | 4 +- .Knowledge/topics/f2s-init-defaults.md | 12 +++- .Knowledge/topics/flow2spec-qoder-plugin.md | 4 +- package-lock.json | 6 +- package.json | 2 +- packages/cli/cli.js | 3 + packages/cli/package.json | 4 +- packages/core/lib/agents.js | 4 ++ packages/core/lib/doctor.js | 19 ++++-- packages/core/lib/init.js | 2 + packages/core/package.json | 2 +- scripts/build-qoder-plugin.js | 2 +- scripts/test-cli-update.js | 14 ++-- scripts/test-core-api.js | 11 +-- scripts/test-init-gitignore.js | 5 +- scripts/test-plugin-init.js | 68 +++++++++++++++++++ 17 files changed, 131 insertions(+), 33 deletions(-) create mode 100644 scripts/test-plugin-init.js diff --git a/.Knowledge/index.md b/.Knowledge/index.md index 0c9fe49..0caf550 100644 --- a/.Knowledge/index.md +++ b/.Knowledge/index.md @@ -36,7 +36,7 @@ | f2s-req-tech | `.Knowledge/topics/f2s-req-tech.md` | 生成技术方案文档(全端通用,含后端/前端/全栈等) | 模板:[技术方案模版](template/技术方案模版.md);技能:`skills/f2s-req-tech/SKILL.md` | | f2s-dev-workflow-constraints | `.Knowledge/topics/f2s-dev-workflow-constraints.md` | 开发工作流约束(仅 Flow2Spec 本仓自身,不给下游):只改 templates / 不改配置根 / 用户驱动分发 | 长文:`rules/repo-dev-workflow-constraints.*`;自查技能:`skills/repo-dev-check/SKILL.md` | | f2s-kb-distill | `.Knowledge/topics/f2s-kb-distill.md` | Q&A 驱动知识提取;自动判断创建新 topic 或追加现有 topic | 技能:skills/f2s-kb-distill/SKILL.mdc | -| flow2spec-init-defaults | `.Knowledge/topics/f2s-init-defaults.md` | `flow2spec init` 字段默认值、四处落点一致性、老项目缺字段补写、`init` 不动 stock/req/topics/matchers、manifest 两个版本字段(`projectRev` / `pkgRev`)对照 | 包源码:`lib/flow2specConfig.js` `DEFAULTS` / `CONFIG_FIELDS`;模板:`templates/{locale}/flow2spec.config.json` | +| flow2spec-init-defaults | `.Knowledge/topics/f2s-init-defaults.md` | `flow2spec init` 字段默认值、四处落点一致性、老项目缺字段补写、init 目标与插件模式(`init plugin`)、`init` 不动 stock/req/topics/matchers、manifest 两个版本字段(`projectRev` / `pkgRev`)对照 | 包源码:`lib/flow2specConfig.js` `DEFAULTS` / `CONFIG_FIELDS`;模板:`templates/{locale}/flow2spec.config.json` | | flow2spec-doctor | `.Knowledge/topics/flow2spec-doctor.md` | `flow2spec doctor` 只读检查环境、项目初始化、协作上下文与知识库健康 | stock:[Doctor 诊断命令](stock-docs/Flow2Spec-doctor诊断命令.md);中英文命令文档:`docs/命令说明.md` / `docs/en/commands-reference.md` | | flow2spec-dsh-adapter | `.Knowledge/topics/flow2spec-dsh-adapter.md` | `flow2spec init dsh`、DeepSeek Harness 项目技能发现与 `.dsh/` 目录适配 | 用户文档:`docs/使用说明.md` / `docs/en/usage-guide.md`;实现:`lib/dshAgentsAdapter.js` | | flow2spec-core-package | `.Knowledge/topics/flow2spec-core-package.md` | `@double-coding/flow2spec-core` 职责边界:核心实现、发包模板真源、原生插件 API 与 CLI/legacy shim 消费关系 | 开发者文档:`packages/core/README.md`;入口:`packages/core/index.js` | diff --git a/.Knowledge/matchers/m-flow2spec-init-defaults.json b/.Knowledge/matchers/m-flow2spec-init-defaults.json index 0ccc2f7..45f93cf 100644 --- a/.Knowledge/matchers/m-flow2spec-init-defaults.json +++ b/.Knowledge/matchers/m-flow2spec-init-defaults.json @@ -23,7 +23,9 @@ "codexAgentsAdapter", "renderProjectConfigBlock", "init 默认翻面", - "AGENTS init 默认列" + "AGENTS init 默认列", + "init plugin", + "插件模式初始化" ], "id": "m-flow2spec-init-defaults", "version": "1.0.0", diff --git a/.Knowledge/topics/f2s-init-defaults.md b/.Knowledge/topics/f2s-init-defaults.md index 10827c3..ba13519 100644 --- a/.Knowledge/topics/f2s-init-defaults.md +++ b/.Knowledge/topics/f2s-init-defaults.md @@ -1,6 +1,6 @@ --- id: flow2spec-init-defaults -revision: 1 +revision: 2 summary: "flow2spec-init-defaults(路由摘要)" primary: config confidence: inferred @@ -46,6 +46,16 @@ tags: [policy] 「当前默认值」一栏以包模板 `packages/core/templates/zh-CN/flow2spec.config.json` 为锚,作变更前先核对该文件。 +## init 目标与插件模式(plugin) + +`AGENTS` 注册表(`packages/core/lib/agents.js`)含五个 init 目标:`cursor` / `claude` / `codex` / `dsh` / `plugin`。 + +- **`plugin` 是 `root: null` 的虚拟目标**:面向能力由客户端插件提供的场景(如 Qoder 插件市场用户),skills/rules/hooks 随插件安装在用户级,项目侧无需镜像。 +- **`flow2spec init plugin` 行为**:仅落 `.Knowledge/` 模板 + 路由骨架 + `pkgRev`、`flow2spec.config.json` 缺失字段问答与 `.gitignore` 补齐;不创建任何配置根目录,不生成根 `AGENTS.md`,不写 hooks。 +- **实现形态**:`init.js` 主循环对 `root: null` 的 id 直接跳过 agent 专属动作;未来新的插件化客户端复用同一路径,不需新增专属目标。 +- **doctor 配套判定**:无任何配置根时,缺根 `AGENTS.md` 与缺配置根均为 **warning**(提示插件模式属正常);存在 `.codex` / `.dsh` 时缺根 `AGENTS.md` 仍为 error。 +- 回归:`scripts/test-plugin-init.js`。 + ### 默认值变更记录 - **2026-06**:`subAgent` / `switchAgentVerification` / `intentRecognition` 三项默认值由 `false` 翻面为 `true`。新项目 init 默认即开启子 agent 编排、交叉校验与意图识别自动分流。老项目(已写过这三项)不受影响;仅在升级时**仍缺该字段**的老项目会按新默认 `true` 补齐。`changeTracking.fix` 维持 `false`(显式排除翻面)。 diff --git a/.Knowledge/topics/flow2spec-qoder-plugin.md b/.Knowledge/topics/flow2spec-qoder-plugin.md index 16f68dd..1a35dd1 100644 --- a/.Knowledge/topics/flow2spec-qoder-plugin.md +++ b/.Knowledge/topics/flow2spec-qoder-plugin.md @@ -1,6 +1,6 @@ --- id: flow2spec-qoder-plugin -revision: 1 +revision: 2 summary: "Qoder 原生插件构建:从 Core 全量生成 skills/rules/hooks 并打包 zip" primary: feature confidence: inferred @@ -25,7 +25,7 @@ tags: [module] - 构建脚本:`scripts/build-qoder-plugin.js`(npm script `build:qoder-plugin`) - 内容事实源:`packages/core/index.js` 的 `resources` API 与 `packages/core/templates/`(包边界与版本契约见 `flow2spec-core-package` 主题) - 产物目录:`output/qoder-plugin/`(已 gitignore;每次构建先清空重建) -- 插件使用前提:目标项目须先 `npx @double-coding/flow2spec init` 生成 `.Knowledge/` 与 `flow2spec.config.json` +- 插件使用前提:目标项目须先 `npx @double-coding/flow2spec init plugin`(插件模式:仅生成 `.Knowledge/` 与 `flow2spec.config.json`,不写客户端配置根;目标语义见 `flow2spec-init-defaults` 主题) ## 禁止项 diff --git a/package-lock.json b/package-lock.json index e26ce97..de76c19 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,10 +25,10 @@ }, "packages/cli": { "name": "@double-coding/flow2spec", - "version": "3.5.0", + "version": "3.6.0", "license": "ISC", "dependencies": { - "@double-coding/flow2spec-core": "^3.5.0" + "@double-coding/flow2spec-core": "^3.6.0" }, "bin": { "flow2spec": "cli.js" @@ -39,7 +39,7 @@ }, "packages/core": { "name": "@double-coding/flow2spec-core", - "version": "3.5.0", + "version": "3.6.0", "license": "ISC", "engines": { "node": ">=16" diff --git a/package.json b/package.json index fd727d5..1487057 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "node": ">=16" }, "scripts": { - "test": "node cli.js --help && node cli.js kb check && node scripts/sync-package-readme.js --check && node scripts/test-workspace-version.js && node scripts/test-cli-update.js && node scripts/test-knowledge-engine.js && node scripts/test-developer-id.js && node scripts/test-template-knowledge.js && node scripts/test-init-gitignore.js && node scripts/test-dsh-init.js && node scripts/test-doctor.js && node scripts/test-core-api.js && node scripts/test-package-install.js", + "test": "node cli.js --help && node cli.js kb check && node scripts/sync-package-readme.js --check && node scripts/test-workspace-version.js && node scripts/test-cli-update.js && node scripts/test-knowledge-engine.js && node scripts/test-developer-id.js && node scripts/test-template-knowledge.js && node scripts/test-init-gitignore.js && node scripts/test-dsh-init.js && node scripts/test-plugin-init.js && node scripts/test-doctor.js && node scripts/test-core-api.js && node scripts/test-package-install.js", "test:core": "node scripts/test-core-api.js", "test:cli": "node cli.js --help && node cli.js doctor --json", "sync:agents": "node cli.js init cursor claude codex", diff --git a/packages/cli/cli.js b/packages/cli/cli.js index a5645d4..9924d8a 100755 --- a/packages/cli/cli.js +++ b/packages/cli/cli.js @@ -304,6 +304,7 @@ agent(可多个,空格分隔;省略时交互选择): flow2spec init # 同时初始化多个客户端 flow2spec init --locale en-US # 使用英文模板初始化指定客户端 flow2spec init dsh # 初始化 DeepSeek Harness 项目技能 + flow2spec init plugin # 插件模式客户端(如 Qoder 插件):仅初始化知识库与配置 flow2spec init --yes # 跳过所有问答,使用默认值(适合 CI) flow2spec init --reset-knowledge # 强制用模板覆盖 .Knowledge(谨慎) @@ -888,6 +889,8 @@ if (sub === "init") { .then(({ ids, knowledgeResult, routingUpgrade, indexSnapshot, gitignoreResult, projectConfig, locale, claudeHooksResult }) => { const lines = ids.map((id) => { const { root, label } = AGENTS[id]; + if (!root) + return ` - ${label}:仅 .Knowledge/ 与 ${CONFIG_FILENAME}(skills/rules/hooks 由客户端插件提供)`; if (id === "codex") return ` - ${root}/:(${label})skills/、topics/、hooks/、hooks.json、AGENTS.md(指针);仓库根 AGENTS.md(完整)`; if (id === "dsh") diff --git a/packages/cli/package.json b/packages/cli/package.json index 579a48a..a551244 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@double-coding/flow2spec", - "version": "3.5.0", + "version": "3.6.0", "description": "在业务仓库初始化文档驱动、可写回知识库的 AI 协作骨架", "homepage": "https://github.com/double-coding-lab/Flow2Spec#readme", "repository": { @@ -19,7 +19,7 @@ "README.md" ], "dependencies": { - "@double-coding/flow2spec-core": "^3.5.0" + "@double-coding/flow2spec-core": "^3.6.0" }, "publishConfig": { "access": "public", diff --git a/packages/core/lib/agents.js b/packages/core/lib/agents.js index d6937e7..e77e18f 100644 --- a/packages/core/lib/agents.js +++ b/packages/core/lib/agents.js @@ -1,12 +1,15 @@ /** * flow2spec init 支持的 AI 工具配置目录。 * 知识库统一写入项目根 `.Knowledge/`(含 template),rules/skills 保留在各配置根。 + * `root: null` 表示虚拟目标(如 plugin 插件模式):不写任何配置根, + * skills/rules/hooks 由客户端插件提供,项目侧仅落 .Knowledge 与 flow2spec.config.json。 */ const AGENTS = { cursor: { root: ".cursor", label: "Cursor" }, claude: { root: ".claude", label: "Claude" }, codex: { root: ".codex", label: "Codex" }, dsh: { root: ".dsh", label: "DeepSeek Harness" }, + plugin: { root: null, label: "插件模式(仅知识库,能力由客户端插件提供)" }, }; const KNOWLEDGE_ROOT = ".Knowledge"; @@ -16,6 +19,7 @@ const AGENT_SUBDIRS = { claude: ["rules", "skills"], codex: ["skills"], dsh: ["skills", "topics"], + plugin: [], }; /** diff --git a/packages/core/lib/doctor.js b/packages/core/lib/doctor.js index b833ecd..e269fd6 100644 --- a/packages/core/lib/doctor.js +++ b/packages/core/lib/doctor.js @@ -175,15 +175,20 @@ function runDoctor(cwd = process.cwd(), options = {}) { } const agentsPath = path.join(cwd, "AGENTS.md"); + const needsAgentsEntry = [".codex", ".dsh"].some((root) => + fs.existsSync(path.join(cwd, root)), + ); checks.push( fs.existsSync(agentsPath) ? makeCheck("agents-entry", "项目入口", STATUS.pass, "根 AGENTS.md 已就绪。") : makeCheck( "agents-entry", "项目入口", - STATUS.error, - "缺少根 AGENTS.md。", - "运行 flow2spec init codex 或 flow2spec init dsh,或重新初始化所需 Agent。", + needsAgentsEntry ? STATUS.error : STATUS.warning, + needsAgentsEntry + ? "缺少根 AGENTS.md。" + : "未检测到根 AGENTS.md(插件模式项目属正常)。", + "Codex/DSH 用户运行 flow2spec init codex 或 flow2spec init dsh;插件模式(flow2spec init plugin)无需此文件。", ), ); @@ -211,8 +216,8 @@ function runDoctor(cwd = process.cwd(), options = {}) { claude: ["settings.json"], cursor: ["hooks.json"], }; - const initializedAgents = Object.entries(AGENTS).filter(([, agent]) => - fs.existsSync(path.join(cwd, agent.root)), + const initializedAgents = Object.entries(AGENTS).filter( + ([, agent]) => agent.root && fs.existsSync(path.join(cwd, agent.root)), ); if (initializedAgents.length === 0) { checks.push( @@ -220,8 +225,8 @@ function runDoctor(cwd = process.cwd(), options = {}) { "agent-roots", "Agent 配置", STATUS.warning, - "未检测到 .codex、.claude、.cursor 或 .dsh 配置根。", - "运行 flow2spec init 初始化实际使用的 Agent。", + "未检测到 .codex、.claude、.cursor 或 .dsh 配置根(插件模式项目属正常)。", + "使用本地客户端时运行 flow2spec init ;插件模式(flow2spec init plugin)无需配置根。", ), ); } else { diff --git a/packages/core/lib/init.js b/packages/core/lib/init.js index fafb48e..1016b67 100644 --- a/packages/core/lib/init.js +++ b/packages/core/lib/init.js @@ -1277,6 +1277,8 @@ async function run(cwd, agentIds, options = {}) { const claudeHooksResult = {}; for (const id of ids) { + // 虚拟目标(root: null,如 plugin 插件模式):能力由客户端插件提供,不写配置根。 + if (!AGENTS[id].root) continue; ensureAgentDirs(cwd, id); writeAgentArtifacts(cwd, id, templatesDir, projectConfig); if (id === "claude") { diff --git a/packages/core/package.json b/packages/core/package.json index f995495..d267a94 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@double-coding/flow2spec-core", - "version": "3.5.0", + "version": "3.6.0", "templateVersion": "3.5.0", "description": "Flow2Spec Core APIs, knowledge engine, project initialization and shared resources", "homepage": "https://github.com/double-coding-lab/Flow2Spec#readme", diff --git a/scripts/build-qoder-plugin.js b/scripts/build-qoder-plugin.js index 5f268a4..9bdcd07 100644 --- a/scripts/build-qoder-plugin.js +++ b/scripts/build-qoder-plugin.js @@ -248,7 +248,7 @@ ${hookList} ## 使用前提(重要) -1. **目标项目须先初始化 Flow2Spec**:项目根执行 \`npx @double-coding/flow2spec init\`,生成 \`.Knowledge/\`(知识路由)与 \`flow2spec.config.json\`(编排开关);未初始化时技能会提示先执行 init。 +1. **目标项目须先初始化 Flow2Spec**:项目根执行 \`npx @double-coding/flow2spec init plugin\`(插件模式:仅生成 \`.Knowledge/\` 知识路由与 \`flow2spec.config.json\` 编排开关,不写任何客户端配置根);未初始化时技能会提示先执行 init。 2. **Hooks 依赖 Node.js**(>=16)在 PATH 中可用;hooks 仅做提醒/检测,不替代技能正文的 \`Read("flow2spec.config.json")\` 门禁。 ## 来源与说明 diff --git a/scripts/test-cli-update.js b/scripts/test-cli-update.js index b70f9f3..39b5f66 100644 --- a/scripts/test-cli-update.js +++ b/scripts/test-cli-update.js @@ -13,9 +13,9 @@ if (process.platform === "win32") { fs.writeFileSync(path.join(tempDir, "npm.cmd"), [ "@echo off", "if \"%2\"==\"@double-coding/flow2spec-core\" (", - " echo {\"version\":\"3.6.0\",\"templateVersion\":\"3.5.0\"}", + " echo {\"version\":\"3.6.1\",\"templateVersion\":\"3.5.0\"}", ") else (", - " echo 3.5.1", + " echo 3.6.1", ")", "", ].join("\r\n"), "utf8"); @@ -24,9 +24,9 @@ if (process.platform === "win32") { fs.writeFileSync(npmPath, [ "#!/usr/bin/env sh", "if [ \"$2\" = \"@double-coding/flow2spec-core\" ]; then", - " echo '{\"version\":\"3.6.0\",\"templateVersion\":\"3.5.0\"}'", + " echo '{\"version\":\"3.6.1\",\"templateVersion\":\"3.5.0\"}'", "else", - " echo '3.5.1'", + " echo '3.6.1'", "fi", "", ].join("\n"), { encoding: "utf8", mode: 0o755 }); @@ -43,10 +43,10 @@ const check = spawnSync(process.execPath, [cliPath, "update", "--check"], { encoding: "utf8", }); assert.strictEqual(check.status, 0, check.stderr); -assert.match(check.stdout, /CLI:\s+3\.5\.0 -> 3\.5\.1/); -assert.match(check.stdout, /Core:\s+3\.5\.0 -> 3\.6\.0/); +assert.match(check.stdout, /CLI:\s+3\.6\.0 -> 3\.6\.1/); +assert.match(check.stdout, /Core:\s+3\.6\.0 -> 3\.6\.1/); assert.match(check.stdout, /Template:\s+3\.5\.0 -> 3\.5\.0/); -assert.match(check.stdout, /\^3\.5\.0 \(compatible\)/); +assert.match(check.stdout, /\^3\.6\.0 \(compatible\)/); const invalid = spawnSync(process.execPath, [cliPath, "update", "--unknown"], { cwd: path.resolve(__dirname, ".."), diff --git a/scripts/test-core-api.js b/scripts/test-core-api.js index eaac8b6..6c422cc 100644 --- a/scripts/test-core-api.js +++ b/scripts/test-core-api.js @@ -5,6 +5,7 @@ const fs = require("fs"); const os = require("os"); const path = require("path"); const core = require("@double-coding/flow2spec-core"); +const corePkg = require("../packages/core/package.json"); async function main() { const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "flow2spec-core-")); @@ -15,14 +16,14 @@ async function main() { }); assert.strictEqual(typeof api.project.init, "function"); - assert.deepStrictEqual(Object.keys(api.project.agents()), ["cursor", "claude", "codex", "dsh"]); + assert.deepStrictEqual(Object.keys(api.project.agents()), ["cursor", "claude", "codex", "dsh", "plugin"]); assert.deepStrictEqual(api.config.supportedLocales(), ["zh-CN", "en-US"]); assert.strictEqual(typeof api.knowledge.check, "function"); assert.strictEqual(core.getCapabilities().schema, "flow2spec.capabilities.v1"); assert.strictEqual(core.getCapabilities().protocolVersion, 2); assert.deepStrictEqual(core.getVersions(), { - coreVersion: "3.5.0", - templateVersion: "3.5.0", + coreVersion: corePkg.version, + templateVersion: corePkg.templateVersion, protocolVersion: 2, }); assert.deepStrictEqual( @@ -113,10 +114,12 @@ async function main() { assert.strictEqual(update.templateUpdateAvailable, true); assert.ok(update.notice.includes("f2s-kb-upgrade")); + // 模拟「Core-only 更新」:最新 Core 比当前高一个补丁号,模板不变 + const [coreMajor, coreMinor, corePatch] = corePkg.version.split(".").map(Number); fs.writeFileSync( cachePath, `${JSON.stringify({ - latestCoreVersion: "3.6.0", + latestCoreVersion: `${coreMajor}.${coreMinor}.${corePatch + 1}`, latestTemplateVersion: "1.0.0", checkedAt: Date.now(), })}\n`, diff --git a/scripts/test-init-gitignore.js b/scripts/test-init-gitignore.js index 3278d40..ede2cae 100644 --- a/scripts/test-init-gitignore.js +++ b/scripts/test-init-gitignore.js @@ -4,6 +4,7 @@ const os = require("os"); const path = require("path"); const runInit = require("../packages/core/lib/init"); +const corePkg = require("../packages/core/package.json"); (async () => { const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "flow2spec-init-")); @@ -32,8 +33,8 @@ const runInit = require("../packages/core/lib/init"); path.join(tmpRoot, agentRoot, "hooks", "f2s-update-check.js"), "utf8", ); - assert(hook.includes("const GENERATED_CORE_VERSION = '3.5.0';")); - assert(hook.includes("const GENERATED_TEMPLATE_VERSION = '3.5.0';")); + assert(hook.includes(`const GENERATED_CORE_VERSION = '${corePkg.version}';`)); + assert(hook.includes(`const GENERATED_TEMPLATE_VERSION = '${corePkg.templateVersion}';`)); assert(!hook.includes("__FLOW2SPEC_PACKAGE_NAME__")); assert(!hook.includes("__FLOW2SPEC_CORE_VERSION__")); assert(!hook.includes("__FLOW2SPEC_TEMPLATE_VERSION__")); diff --git a/scripts/test-plugin-init.js b/scripts/test-plugin-init.js new file mode 100644 index 0000000..3ee74e8 --- /dev/null +++ b/scripts/test-plugin-init.js @@ -0,0 +1,68 @@ +const assert = require("assert"); +const fs = require("fs"); +const os = require("os"); +const path = require("path"); + +const runInit = require("../packages/core/lib/init"); +const { runDoctor } = require("../packages/core/lib/doctor"); + +const CONFIG_ROOTS = [".cursor", ".claude", ".codex", ".dsh"]; + +(async () => { + for (const locale of ["zh-CN", "en-US"]) { + const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), `flow2spec-plugin-${locale}-`)); + try { + const result = await runInit(tmpRoot, ["plugin"], { + configValues: { locale }, + }); + + assert.deepStrictEqual(result.ids, ["plugin"]); + // 知识库与项目配置照常落盘 + assert(fs.existsSync(path.join(tmpRoot, ".Knowledge", "manifest-routing.json"))); + assert(fs.existsSync(path.join(tmpRoot, ".Knowledge", "template", "index.template.md"))); + assert(fs.existsSync(path.join(tmpRoot, "flow2spec.config.json"))); + // 插件模式不写任何客户端配置根,也不生成根 AGENTS.md + for (const root of CONFIG_ROOTS) { + assert(!fs.existsSync(path.join(tmpRoot, root)), `${root} 不应被创建`); + } + assert(!fs.existsSync(path.join(tmpRoot, "AGENTS.md"))); + + // 幂等:二次 init 不报错、仍不写配置根 + await runInit(tmpRoot, ["plugin"], { configValues: { locale } }); + for (const root of CONFIG_ROOTS) { + assert(!fs.existsSync(path.join(tmpRoot, root)), `${root} 不应被二次 init 创建`); + } + + // doctor:插件模式项目不因缺配置根/AGENTS.md 报 error + const doctorResult = await runDoctor(tmpRoot, {}); + const checks = doctorResult.checks || []; + const agentsEntry = checks.find((c) => c.id === "agents-entry"); + assert(agentsEntry, "doctor 应包含 agents-entry 检查"); + assert.notStrictEqual(agentsEntry.status, "error", "插件模式缺 AGENTS.md 不应为 error"); + const agentRoots = checks.find((c) => c.id === "agent-roots"); + if (agentRoots) { + assert.notStrictEqual(agentRoots.status, "error", "插件模式无配置根不应为 error"); + } + } finally { + fs.rmSync(tmpRoot, { recursive: true, force: true }); + } + } + + // 混合:plugin 与实体 agent 同时传入时,实体侧照常写入 + const mixedRoot = fs.mkdtempSync(path.join(os.tmpdir(), "flow2spec-plugin-mixed-")); + try { + const result = await runInit(mixedRoot, ["plugin", "cursor"], { + configValues: { locale: "zh-CN" }, + }); + assert.deepStrictEqual(result.ids, ["plugin", "cursor"]); + assert(fs.existsSync(path.join(mixedRoot, ".cursor", "skills"))); + assert(!fs.existsSync(path.join(mixedRoot, ".codex"))); + } finally { + fs.rmSync(mixedRoot, { recursive: true, force: true }); + } + + console.log("plugin init tests passed"); +})().catch((error) => { + console.error(error); + process.exit(1); +});