diff --git a/.Knowledge/topics/f2s-init-defaults.md b/.Knowledge/topics/f2s-init-defaults.md index bdf7303..dafba2e 100644 --- a/.Knowledge/topics/f2s-init-defaults.md +++ b/.Knowledge/topics/f2s-init-defaults.md @@ -81,11 +81,11 @@ tags: [policy] ### 跑 `f2s-kb-upgrade` 时的版本预检 -步骤 -1 执行 `flow2spec version` 与 `flow2spec update --check`,分别读取 CLI、Core、Core Range、Template、Protocol 和 npm 最新 Core/Template: +步骤 -1 执行 `flow2spec version` 与 `flow2spec update --check`,分别读取 CLI、Core、Core Pinned、Template、Protocol 和 npm 最新 Core/Template(CLI 对 Core 为精确 pin,两包联动发布): -- Core-only 更新:`flow2spec update --core` 后幂等 init 刷新 Hook,不进入完整知识库升级。 -- Template 更新且 Core 兼容:更新 Core 后继续 init 与 `projectRev` / `pkgRev` 分流。 -- 本地版本不可用或 Core 超出范围:显式组合 latest CLI/Core 执行 init,避免 npx 复用旧 Core。 +- CLI/Core 有更新:`flow2spec update --cli`(CLI 与配套 Core 联动更新,自带生效校验)后幂等 init 刷新 Hook。 +- Template 更新:`flow2spec update --cli` 到位后继续 init 与 `projectRev` / `pkgRev` 分流。 +- 本地版本不可用:`npx --yes @latest init`(latest CLI 自带 pin 的配套 Core),避免 npx 复用旧版缓存。 - `manifest-routing.json.version` 表示 Template Version,不能与 Core Version 混用。 ## init 不动哪些目录 diff --git a/.Knowledge/topics/flow2spec-core-package.md b/.Knowledge/topics/flow2spec-core-package.md index 4cee7ac..6d6e0e1 100644 --- a/.Knowledge/topics/flow2spec-core-package.md +++ b/.Knowledge/topics/flow2spec-core-package.md @@ -34,22 +34,22 @@ Template Version packages/core/package.json.templateVersion Protocol Version packages/core/capabilities.json.protocolVersion ``` -- `version:set:cli` 只更新 CLI,可显式提高 `--core-range`。 -- `version:set:core` 只更新 Core,并拒绝落到当前 CLI 范围之外。 +- `version:set:cli` 只更新 CLI,Core pin 自动同步为当前 Core 版本。 +- `version:set:core` 更新 Core 并联动把 CLI 依赖 pin 到同版本(需配套 bump CLI patch 联动发布)。 - `version:set:template` 更新 Core 元数据及中英文 `manifest-routing.json.version`。 -- `version:check` 校验 semver range、lockfile、双语 Template Version、Protocol Version 与 release tag。 +- `version:check` 强制校验 CLI pin 与 Core 版本精确一致,另校验 lockfile、双语 Template Version、Protocol Version 与 release tag。 ## 发布与更新 -- `core-vX.Y.Z` 只发布 Core;`cli-vX.Y.Z` 只发布 CLI。同时发布时先 Core 后 CLI。 -- `flow2spec version` 展示 CLI/Core/Core Range/Template/Protocol。 -- `flow2spec update --check|--cli|--core` 分别检查、更新 CLI、更新兼容 Core。 +- `core-vX.Y.Z` 只发布 Core;`cli-vX.Y.Z` 只发布 CLI。Core/Template 发版必带 CLI patch 联动发布,顺序先 Core 后 CLI。 +- `flow2spec version` 展示 CLI/Core/Core Pinned/Template/Protocol。 +- `flow2spec update --check|--cli|--core` 均以 CLI 为入口整体更新(`--core` 为别名),安装后校验全局生效 Core 版本,失败时提示手动重装命令。 - Hook 与 `update.check()` 同时返回 Core 与 Template 状态。 -- Core-only 更新:更新 Core 后幂等 init 刷新 Hook,不进入 `f2s-kb-upgrade`。 -- Template 更新:更新 Core、执行 init,再由 `projectRev` / `pkgRev` 决定是否进入完整知识库升级。 +- CLI/Core 更新:`update --cli` 联动到位后幂等 init 刷新 Hook。 +- Template 更新:`update --cli` 后执行 init,再由 `projectRev` / `pkgRev` 决定是否进入完整知识库升级。 ## 边界 -- Core 新版必须落在 CLI caret range 内;超出范围先升级 CLI。 +- CLI 对 Core 为精确 pin;用户只需关心 CLI 一个包,`npm i -g @latest` 即得配套 Core。 - `.Knowledge/manifest-routing.json.version` 表示 Template Version,不能与 Core Version 混用。 - 包安装验收使用两包 tarball,并验证 Core templates、类型声明、CLI README 与启动行为。 diff --git a/docs/en/upgrade-guide.md b/docs/en/upgrade-guide.md index b0c440b..dca7a53 100644 --- a/docs/en/upgrade-guide.md +++ b/docs/en/upgrade-guide.md @@ -1,4 +1,4 @@ -# Flow2Spec Upgrade Guide (CLI 3.6.1 / Core 3.7.1 / Template 3.6.1) +# Flow2Spec Upgrade Guide (CLI 3.6.2 / Core 3.7.2 / Template 3.6.2) > Highlight of this release: **routing-summary recall anchors**. Every routing rule in `manifest-routing.json` now carries a `summary` semantic digest (synced automatically from topic frontmatter), which greatly improves knowledge-base hit rates for natural phrasings such as "where are the prototypes" or "which folder holds the flowcharts". `kb check` gains summary quality validation accordingly. @@ -6,10 +6,10 @@ | Dimension | Latest | Notes | | --- | --- | --- | -| CLI (`@double-coding/flow2spec`) | 3.6.1 | `kb check` prints warning details | -| Core (`@double-coding/flow2spec-core`) | 3.7.1 | summary sync engine + quality validation; upgrade-skill project-side alignment check | -| Template Version | 3.6.1 | templates carry topic-layer changes (projectRev 3) | -| Qoder plugin | 3.7.1 | self-built (`npm run build:qoder-plugin`), named after the Core version | +| CLI (`@double-coding/flow2spec`) | 3.6.2 | the only package you need to care about; pins its exact Core, released in lockstep | +| Core (`@double-coding/flow2spec-core`) | 3.7.2 | installed automatically with the CLI, no separate action needed | +| Template Version | 3.6.2 | templates carry topic-layer changes (projectRev 3) | +| Qoder plugin | 3.7.2 | self-built (`npm run build:qoder-plugin`), named after the Core version | --- @@ -22,7 +22,7 @@ npm install -g @double-coding/flow2spec flow2spec init # multi-select, follow the prompts ``` -Installing the CLI automatically brings the latest compatible Core (`^3.6.0` → 3.7.1); no separate install is needed. After init you are on the latest knowledge-base templates with summary-based first-pass recall built in — nothing extra to do. +Installing the CLI automatically brings its exactly pinned Core (3.7.2); no separate install is needed. After init you are on the latest knowledge-base templates with summary-based first-pass recall built in — nothing extra to do. ### Option 2: Qoder plugin (self-built install) @@ -33,7 +33,7 @@ The Qoder plugin marketplace hosts official plugins only; build the Flow2Spec pl ```bash git clone https://github.com/double-coding-lab/Flow2Spec.git cd Flow2Spec && npm install && npm run build:qoder-plugin - # produces output/flow2spec-3.7.1.zip + # produces output/flow2spec-3.7.2.zip ``` 2. In Qoder's plugin management, choose local install and import the zip; @@ -47,25 +47,18 @@ Then tell the agent things like "f2s-kb-build / f2s-kb-add" to start building th Template 3.5.0 → 3.6.x **includes topic-layer changes** (projectRev 2 → 3), so updating the packages alone is not enough — run one knowledge-base upgrade. Three steps: -### Step 1: Update the packages +### Step 1: Update the package -Check current versions and available updates: +The CLI and Core release in lockstep (the CLI pins its exact Core), so one command updates everything: ```bash -flow2spec version -flow2spec update --check +npm install -g @double-coding/flow2spec@latest +flow2spec version # confirm CLI 3.6.2 / Core 3.7.2 ``` -Two cases depending on your current CLI: +Once on 3.6.2 or later, future upgrades can also use `flow2spec update --cli` (it verifies the effective Core version and auto-repairs a broken dependency tree by reinstalling). -- **CLI is already 3.6.x**: run `flow2spec update --core` to get Core 3.7.1 (inside the `^3.6.0` range); if your CLI is below 3.6.1, also run `flow2spec update --cli` (its `kb check` prints warning details, handy for watching step 2 progress). -- **CLI below 3.6.0**: reinstall the latest: - - ```bash - npm install -g @double-coding/flow2spec@latest - ``` - -**Qoder plugin users**: pull the latest code and rebuild the plugin package (`npm run build:qoder-plugin`, producing `output/flow2spec-3.7.1.zip`), then re-import it in Qoder's plugin management; no global npm package needed. +**Qoder plugin users**: pull the latest code and rebuild the plugin package (`npm run build:qoder-plugin`, producing `output/flow2spec-3.7.2.zip`), then re-import it in Qoder's plugin management; no global npm package needed. ### Step 2: Knowledge-base upgrade (the key step — let the agent do it) @@ -87,7 +80,7 @@ No manual edits to any `.Knowledge` file are needed. ### Step 3: Verify ```bash -flow2spec version # CLI 3.6.1 / Core 3.7.1 / Template 3.6.1 +flow2spec version # CLI 3.6.2 / Core 3.7.2 / Template 3.6.2 flow2spec kb check --strict # expect: knowledge check: ok, no summary warnings ``` @@ -97,6 +90,12 @@ Then try one natural question (e.g. "where do the prototypes / requirement docs ## FAQ +**Q: Why does every Core update come with a new CLI version? Which package should I care about?** +Only the CLI (`@double-coding/flow2spec`). It pins its exact Core version and the two packages release in lockstep: any Core update produces a new CLI version, so `npm install -g @double-coding/flow2spec@latest` always gets you the complete latest pair. + +**Q: On an older CLI, `flow2spec update --core` said "updated" but `flow2spec version` did not change?** +A known defect in CLI ≤ 3.6.1: that command installed Core into an orphaned top-level global location, while the CLI actually loads its own nested copy — which never got updated. Fix: reinstall the CLI once (`npm uninstall -g @double-coding/flow2spec && npm install -g @double-coding/flow2spec@latest`). Since CLI 3.6.2, `update --cli/--core` performs the lockstep update and verifies the effective Core version — no more false success. + **Q: Will the upgrade overwrite the knowledge base I already wrote?** No. The init run by `f2s-kb-upgrade` is incremental and only updates template-owned routing structure and rules; your business content in `stock-docs` / `req-docs` / topic bodies is untouched. `--reset-knowledge` is used only when you explicitly ask for an overwrite reset. @@ -114,7 +113,7 @@ f2s-kb-upgrade, force the full flow: run flow2spec init first, then kb build --f ``` **Q: After upgrading, git shows lots of changes under `.cursor/` and `.codex/` — is that normal?** -Yes. Those are real updates to rule and skill bodies in the new templates (including the wholesale removal of `f2s-kb-migrate`), not empty diffs; use `git diff -w` to verify. The upgrade only touches `.Knowledge/`, the agent config roots (`.cursor/` `.codex/` etc.), `AGENTS.md`, and `flow2spec.config.json` — **never your business source code**; commit the upgrade changes separately from business changes. Also, the manifest `version` is the Template Version (3.6.1 after upgrading) and will not equal the Core version — that is expected. +Yes. Those are real updates to rule and skill bodies in the new templates (including the wholesale removal of `f2s-kb-migrate`), not empty diffs; use `git diff -w` to verify. The upgrade only touches `.Knowledge/`, the agent config roots (`.cursor/` `.codex/` etc.), `AGENTS.md`, and `flow2spec.config.json` — **never your business source code**; commit the upgrade changes separately from business changes. Also, the manifest `version` is the Template Version (3.6.2 after upgrading) and will not equal the Core version — that is expected. **Q: After upgrading, my commit is blocked by `kb check` reporting routing drift?** The manifest and topics are out of sync (usually after hand-editing the manifest). Ask the agent to run `flow2spec kb build` once (idempotent) and it self-heals. diff --git a/docs/en/usage-guide.md b/docs/en/usage-guide.md index 0debed3..a9c8a55 100644 --- a/docs/en/usage-guide.md +++ b/docs/en/usage-guide.md @@ -135,7 +135,7 @@ Template update: after init, projectRev == pkgRev takes the fast path; a differe Legacy layout (V1): built-in migration removed; use a historical package version (@3.4.x or earlier) for a one-time migration, or move into .Knowledge manually ``` -`flow2spec version` shows CLI, Core, Core Range, Template, and Protocol. `flow2spec update --check|--cli|--core` checks updates, updates CLI, or updates a compatible Core. SessionStart Hooks compare Core and Template independently: Core-only updates do not trigger knowledge upgrade; Template updates use `projectRev` / `pkgRev` after init to decide whether to run `f2s-kb-upgrade`. Failed checks are skipped silently; CLI self-checks do not interrupt `CI` or runs with `FLOW2SPEC_SKIP_UPDATE_CHECK=1`. +`flow2spec version` shows CLI, Core, Core Pinned, Template, and Protocol. `flow2spec update --check` checks for updates; `flow2spec update --cli` performs the lockstep update (CLI plus its pinned Core arrive together; `--core` is an equivalent alias). SessionStart Hooks compare Core and Template independently: Core-only updates do not trigger knowledge upgrade; Template updates use `projectRev` / `pkgRev` after init to decide whether to run `f2s-kb-upgrade`. Failed checks are skipped silently; CLI self-checks do not interrupt `CI` or runs with `FLOW2SPEC_SKIP_UPDATE_CHECK=1`. After `flow2spec init codex`, Codex projects include `.codex/hooks.json`, `.codex/hooks/f2s-config-session.js`, and `.codex/hooks/f2s-update-check.js`. On Codex `SessionStart` for `startup|resume`, the first script injects one configuration summary and the second checks the knowledge-base version automatically. When the hook is first generated or changed, trust it through `/hooks` in Codex. Set `updateCheck.enabled=false` in `flow2spec.config.json` to skip only the version check. diff --git "a/docs/\344\275\277\347\224\250\350\257\264\346\230\216.md" "b/docs/\344\275\277\347\224\250\350\257\264\346\230\216.md" index 7127d6d..a459411 100644 --- "a/docs/\344\275\277\347\224\250\350\257\264\346\230\216.md" +++ "b/docs/\344\275\277\347\224\250\350\257\264\346\230\216.md" @@ -122,7 +122,7 @@ Template 更新:init 后按 projectRev == pkgRev 走快速路径,不等时 旧版布局(流程 V1):不再内置迁移;用历史版本包(@3.4.x 及更早)一次性迁移或手动迁入 .Knowledge ``` -`flow2spec version` 展示 CLI、Core、Core Range、Template、Protocol;`flow2spec update --check|--cli|--core` 分别检查、更新 CLI、更新兼容 Core。SessionStart Hook 同时检查 Core 与 Template:Core-only 更新不触发知识库升级;Template 更新才在 init 后按 `projectRev` / `pkgRev` 决定是否执行 `f2s-kb-upgrade`。更新检查失败会静默跳过,不影响当前命令;`CI` 或设置 `FLOW2SPEC_SKIP_UPDATE_CHECK=1` 时 CLI 自检不打扰当前流程。 +`flow2spec version` 展示 CLI、Core、Core Pinned、Template、Protocol;`flow2spec update --check` 检查更新,`flow2spec update --cli` 整体更新(CLI 与配套 Core 联动发布、一起到位,`--core` 为其等价别名)。SessionStart Hook 同时检查 Core 与 Template:Core-only 更新不触发知识库升级;Template 更新才在 init 后按 `projectRev` / `pkgRev` 决定是否执行 `f2s-kb-upgrade`。更新检查失败会静默跳过,不影响当前命令;`CI` 或设置 `FLOW2SPEC_SKIP_UPDATE_CHECK=1` 时 CLI 自检不打扰当前流程。 Codex 项目执行 `flow2spec init codex` 后会写入 `.codex/hooks.json`、`.codex/hooks/f2s-config-session.js` 与 `.codex/hooks/f2s-update-check.js`:前者在 Codex `SessionStart` 的 `startup|resume` 事件注入一次配置摘要,后者自动检查知识库版本;首次生成或 hook 内容变化后,需要在 Codex 中通过 `/hooks` 信任该项目 hook。`flow2spec.config.json` 中 `updateCheck.enabled=false` 时仅跳过版本检查,不影响配置摘要注入。 diff --git "a/docs/\345\215\207\347\272\247\346\214\207\345\215\227.md" "b/docs/\345\215\207\347\272\247\346\214\207\345\215\227.md" index e70f549..35e39d1 100644 --- "a/docs/\345\215\207\347\272\247\346\214\207\345\215\227.md" +++ "b/docs/\345\215\207\347\272\247\346\214\207\345\215\227.md" @@ -1,4 +1,4 @@ -# Flow2Spec 升级指南(CLI 3.6.1 / Core 3.7.1 / Template 3.6.1) +# Flow2Spec 升级指南(CLI 3.6.2 / Core 3.7.2 / Template 3.6.2) > 本次版本核心能力:**路由初筛 summary 召回锚**。`manifest-routing.json` 的每条路由规则新增 `summary` 语义摘要(由 topic frontmatter 自动同步),大幅提升自然问法(如「原型放在哪」「流程图在哪个目录」)的知识库命中率;`kb check` 同步新增 summary 质量校验。 @@ -6,10 +6,10 @@ | 维度 | 最新版本 | 说明 | | --- | --- | --- | -| CLI(`@double-coding/flow2spec`) | 3.6.1 | `kb check` 输出 warning 明细 | -| Core(`@double-coding/flow2spec-core`) | 3.7.1 | summary 同步引擎 + 质量校验;升级技能项目侧对齐检查 | -| Template Version | 3.6.1 | 模板含主题层变更(projectRev 3) | -| Qoder 插件 | 3.7.1 | 自行构建(`npm run build:qoder-plugin`),随 Core 版本号命名 | +| CLI(`@double-coding/flow2spec`) | 3.6.2 | 用户唯一需要关心的包;精确锁定配套 Core,两包联动发布 | +| Core(`@double-coding/flow2spec-core`) | 3.7.2 | 随 CLI 自动安装,无需单独操作 | +| Template Version | 3.6.2 | 模板含主题层变更(projectRev 3) | +| Qoder 插件 | 3.7.2 | 自行构建(`npm run build:qoder-plugin`),随 Core 版本号命名 | --- @@ -22,7 +22,7 @@ npm install -g @double-coding/flow2spec flow2spec init # 可多选,按提示回答 ``` -安装 CLI 会自动带上兼容的最新 Core(`^3.6.0` → 3.7.1),无需单独安装。init 完成后即为最新知识库模板,天然具备 summary 初筛能力,无需额外操作。 +安装 CLI 会自动带上精确锁定的配套 Core(3.7.2),无需单独安装。init 完成后即为最新知识库模板,天然具备 summary 初筛能力,无需额外操作。 ### 方式二:Qoder 插件(自行构建安装) @@ -33,7 +33,7 @@ Qoder 插件市场为官方插件,Flow2Spec 插件需自行构建后本地安 ```bash git clone https://github.com/double-coding-lab/Flow2Spec.git cd Flow2Spec && npm install && npm run build:qoder-plugin - # 产出 output/flow2spec-3.7.1.zip + # 产出 output/flow2spec-3.7.2.zip ``` 2. 在 Qoder 插件管理中选择本地安装,导入该 zip; @@ -49,23 +49,16 @@ Qoder 插件市场为官方插件,Flow2Spec 插件需自行构建后本地安 ### 第 1 步:更新包 -先看当前版本与可用更新: +CLI 与 Core 联动发布(CLI 精确锁定配套 Core),一条命令整体更新: ```bash -flow2spec version -flow2spec update --check +npm install -g @double-coding/flow2spec@latest +flow2spec version # 确认 CLI 3.6.2 / Core 3.7.2 ``` -按当前 CLI 版本分两种情况: +已在 3.6.2 及以上的后续升级,也可直接用 `flow2spec update --cli`(自带生效校验,检测到依赖树异常会自动重装修复)。 -- **CLI 已是 3.6.x**:执行 `flow2spec update --core` 更新 Core 到 3.7.1(在 `^3.6.0` 兼容范围内);若 CLI 低于 3.6.1,建议再 `flow2spec update --cli` 升到 3.6.1(`kb check` 可打印 warning 明细,便于第 2 步观察补写进度)。 -- **CLI 低于 3.6.0**:直接重装最新: - - ```bash - npm install -g @double-coding/flow2spec@latest - ``` - -**Qoder 插件用户**:拉取最新代码重新构建插件包(`npm run build:qoder-plugin`,产出 `output/flow2spec-3.7.1.zip`),在 Qoder 插件管理中重新导入即可,无需操作 npm 全局包。 +**Qoder 插件用户**:拉取最新代码重新构建插件包(`npm run build:qoder-plugin`,产出 `output/flow2spec-3.7.2.zip`),在 Qoder 插件管理中重新导入即可,无需操作 npm 全局包。 ### 第 2 步:知识库升级(关键,交给 agent 做) @@ -87,7 +80,7 @@ agent 会代跑完整流程,其中与本次版本直接相关的是: ### 第 3 步:验证 ```bash -flow2spec version # CLI 3.6.1 / Core 3.7.1 / Template 3.6.1 +flow2spec version # CLI 3.6.2 / Core 3.7.2 / Template 3.6.2 flow2spec kb check --strict # 期望:knowledge check: ok,无 summary warning ``` @@ -97,6 +90,12 @@ flow2spec kb check --strict # 期望:knowledge check: ok,无 summary warni ## 常见问题 +**Q:为什么 Core 更新了,CLI 也会跟着发新版?我该关心哪个包?** +只需关心 CLI(`@double-coding/flow2spec`)一个包。CLI 精确锁定配套 Core 版本,两包联动发布:Core 有任何更新都会产生一个新的 CLI 版本号,`npm install -g @double-coding/flow2spec@latest` 永远能拿到完整的最新组合。 + +**Q:旧版跑 `flow2spec update --core` 显示「已更新」但 `flow2spec version` 版本没变?** +CLI ≤ 3.6.1 的已知缺陷:那条命令把 Core 装到了全局顶级孤儿位置,CLI 实际加载的是自己包内的嵌套副本,永远更新不到。修复方式:重装一次 CLI(`npm uninstall -g @double-coding/flow2spec && npm install -g @double-coding/flow2spec@latest`)。CLI 3.6.2 起 `update --cli/--core` 已改为联动整体更新并验证实际生效版本,不再假报成功。 + **Q:升级会覆盖我已经写好的知识库吗?** 不会。`f2s-kb-upgrade` 代跑的 init 是增量对齐,只更新模板承载的路由结构与规则;`stock-docs` / `req-docs` / topic 正文的业务内容不受影响。只有明确要求「覆盖重置」时才会带 `--reset-knowledge`。 @@ -114,7 +113,7 @@ f2s-kb-upgrade,强制走完整流程:先代跑 flow2spec init,再 kb build ``` **Q:升级后 git 里 `.cursor/`、`.codex/` 出现大量文件变化,正常吗?** -正常。这些是新模板的规则与技能正文真实更新(含已移除的 `f2s-kb-migrate` 整文件删除),不是空改动;可用 `git diff -w` 验证实质变更占比。升级只会动 `.Knowledge/`、各 agent 配置根(`.cursor/` `.codex/` 等)、`AGENTS.md`、`flow2spec.config.json`,**不碰业务源码**;建议把升级改动与业务改动分开提交。另外 manifest 的 `version` 是 Template Version(升级后为 3.6.1),不会等于 Core 版本号,属正常现象。 +正常。这些是新模板的规则与技能正文真实更新(含已移除的 `f2s-kb-migrate` 整文件删除),不是空改动;可用 `git diff -w` 验证实质变更占比。升级只会动 `.Knowledge/`、各 agent 配置根(`.cursor/` `.codex/` 等)、`AGENTS.md`、`flow2spec.config.json`,**不碰业务源码**;建议把升级改动与业务改动分开提交。另外 manifest 的 `version` 是 Template Version(升级后为 3.6.2),不会等于 Core 版本号,属正常现象。 **Q:升级后提交代码被 `kb check` 拦住报 routing drift?** 说明 manifest 与 topic 不同步(常见于手改过 manifest)。让 agent 跑一次 `flow2spec kb build`(幂等)即可自愈。 diff --git a/package-lock.json b/package-lock.json index 673d467..a6fea12 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,10 +25,10 @@ }, "packages/cli": { "name": "@double-coding/flow2spec", - "version": "3.6.1", + "version": "3.6.2", "license": "ISC", "dependencies": { - "@double-coding/flow2spec-core": "^3.6.0" + "@double-coding/flow2spec-core": "3.7.2" }, "bin": { "flow2spec": "cli.js" @@ -39,7 +39,7 @@ }, "packages/core": { "name": "@double-coding/flow2spec-core", - "version": "3.7.1", + "version": "3.7.2", "license": "ISC", "engines": { "node": ">=16" diff --git a/packages/cli/cli.js b/packages/cli/cli.js index 022fb98..da01ad8 100755 --- a/packages/cli/cli.js +++ b/packages/cli/cli.js @@ -56,18 +56,6 @@ function compareVersions(a, b) { return 0; } -function satisfiesCoreRange(version, range) { - const match = /^\^(\d+)\.(\d+)\.(\d+)/.exec(String(range || "")); - if (!match || compareVersions(version, `${match[1]}.${match[2]}.${match[3]}`) < 0) return false; - const [major, minor, patch] = match.slice(1).map(Number); - const upper = major > 0 - ? `${major + 1}.0.0` - : minor > 0 - ? `0.${minor + 1}.0` - : `0.0.${patch + 1}`; - return compareVersions(version, upper) < 0; -} - function runCommandSync(command, commandArgs, options = {}) { return execFileSync(command, commandArgs, { windowsHide: true, @@ -175,6 +163,44 @@ function getGlobalInstalledVersion() { } } +/** + * 读取全局 CLI 实际加载到的 Core 版本(按 Node 模块解析优先级:嵌套副本优先)。 + * + * npm 全局安装时 CLI 的 core 依赖位于 CLI 包自己的 node_modules(嵌套); + * 单独 `npm i -g core@x` 只会装出顶级孤儿副本,不影响 CLI 加载。 + * 因此验证时先查嵌套路径,再回退顶级路径。 + * + * @returns {string|null} 全局 CLI 实际解析到的 core 版本;无法确定时返回 null + */ +function getGlobalEffectiveCoreVersion() { + if (!pkg.name) return null; + let globalRoot; + try { + globalRoot = runCommandSync("npm", ["root", "-g"], { + encoding: "utf8", + timeout: 2000, + stdio: ["ignore", "pipe", "ignore"], + }).trim(); + } catch { + return null; + } + if (!globalRoot) return null; + const candidates = [ + path.join(globalRoot, pkg.name, "node_modules", CORE_PACKAGE, "package.json"), + path.join(globalRoot, CORE_PACKAGE, "package.json"), + ]; + for (const candidate of candidates) { + try { + if (!fs.existsSync(candidate)) continue; + const data = JSON.parse(fs.readFileSync(candidate, "utf8")); + if (typeof data.version === "string") return data.version; + } catch { + // 继续尝试下一个候选路径。 + } + } + return null; +} + /** * init 收尾时自动把全局 flow2spec 升到 latest。 * @@ -291,8 +317,8 @@ Flow2Spec - 统一知识库工作流(AI 配置入口) v${pkg.version} flow2spec kb 知识库协作引擎:status / check / plan / apply / build flow2spec version 显示 CLI / Core / Template / Protocol 版本 flow2spec update --check 检查 CLI 与 Core 更新 - flow2spec update --cli 更新 CLI - flow2spec update --core 在兼容范围内更新 Core + flow2spec update --cli 整体更新(CLI 与配套 Core 联动) + flow2spec update --core 同 --cli:Core 随 CLI 联动发布,执行整体更新 flow2spec --help 显示本说明 agent(可多个,空格分隔;省略时交互选择): @@ -332,7 +358,7 @@ if (sub === "version" || sub === "--version" || sub === "-v") { console.log([ `Flow2Spec CLI: ${pkg.version}`, `Flow2Spec Core: ${coreVersions.coreVersion}`, - `Core Range: ${coreRange}`, + `Core Pinned: ${coreRange}`, `Template Version: ${coreVersions.templateVersion}`, `Protocol Version: ${getCapabilities().protocolVersion}`, ].join("\n")); @@ -355,51 +381,59 @@ if (sub === "update") { stdio: ["ignore", "pipe", "ignore"], }).trim(); const latestCore = queryLatestCoreMetadata(); - const coreCompatible = satisfiesCoreRange(latestCore.version, coreRange); if (mode === "--check") { console.log([ `CLI: ${pkg.version} -> ${latestCli}`, `Core: ${coreVersions.coreVersion} -> ${latestCore.version}`, `Template: ${coreVersions.templateVersion} -> ${latestCore.templateVersion}`, - `Range: ${coreRange} (${coreCompatible ? "compatible" : "incompatible"})`, + `Policy: Core 随 CLI 联动发布(当前 CLI pin Core ${coreRange})`, ].join("\n")); + if (compareVersions(latestCli, pkg.version) > 0) { + console.log("\n可运行 flow2spec update --cli 一键更新(CLI 与配套 Core 一起到位)。"); + } process.exit(0); } - if (mode === "--cli") { - if (compareVersions(latestCli, pkg.version) <= 0) { - console.log(`CLI 已是最新版本 v${pkg.version}`); - process.exit(0); - } - if (getGlobalInstalledVersion()) { - runCommandSync("npm", ["install", "-g", `${pkg.name}@latest`], { stdio: "inherit" }); - console.log(`\n✓ CLI 已更新到 v${latestCli}`); - } else { - runCommandSync("npx", ["--yes", `${pkg.name}@latest`, "version"], { stdio: "inherit" }); - console.log("\n✓ 当前为 npx 场景;已用 latest CLI 启动并验证,无需写入全局安装。"); - } + // --cli 与 --core 统一为整体更新:CLI pin 精确 Core 版本,更新 CLI 即同时拿到配套 Core。 + if (mode === "--core") { + console.log("Core 随 CLI 联动发布;执行整体更新(等价 update --cli)。"); + } + if (!getGlobalInstalledVersion()) { + runCommandSync("npx", ["--yes", `${pkg.name}@latest`, "version"], { stdio: "inherit" }); + console.log("\n✓ 当前为 npx 场景;已用 latest CLI 启动并验证(自带配套 Core),无需写入全局安装。"); process.exit(0); } - if (!coreCompatible) { - console.error(`Core v${latestCore.version} 超出当前 CLI 兼容范围 ${coreRange};请先更新支持该 Core 的 CLI。`); - process.exit(1); - } - if (compareVersions(latestCore.version, coreVersions.coreVersion) <= 0) { - console.log(`Core 已是最新兼容版本 v${coreVersions.coreVersion}`); + const cliUpToDate = compareVersions(latestCli, pkg.version) <= 0; + const effectiveBefore = getGlobalEffectiveCoreVersion(); + const coreHealthy = Boolean(effectiveBefore) && compareVersions(effectiveBefore, latestCore.version) >= 0; + if (cliUpToDate && coreHealthy) { + console.log(`CLI v${pkg.version} 与 Core v${effectiveBefore} 均已是最新。`); process.exit(0); } - if (getGlobalInstalledVersion()) { - runCommandSync("npm", ["install", "-g", `${CORE_PACKAGE}@${latestCore.version}`], { stdio: "inherit" }); - console.log(`\n✓ Core 已更新到 v${latestCore.version}`); - } else { - runCommandSync( - "npx", - ["--yes", "--package", `${pkg.name}@${pkg.version}`, "--package", `${CORE_PACKAGE}@${latestCore.version}`, "flow2spec", "version"], - { stdio: "inherit" }, - ); - console.log("\n✓ 当前为 npx 场景;已显式安装并验证 CLI/Core 组合,后续 init 请继续使用相同的 --package 组合。"); + if (cliUpToDate && !coreHealthy) { + // CLI 已是 latest 但实际生效的 Core 落后(历史孤儿副本 / 嵌套遮蔽):先卸再装强制重建依赖树。 + console.log(`检测到 Core 实际生效版本 v${effectiveBefore || "未知"} 落后于 v${latestCore.version},重装 CLI 修复依赖树…`); + try { + runCommandSync("npm", ["uninstall", "-g", pkg.name], { stdio: "inherit" }); + } catch { + // 卸载失败不阻断,继续安装。 + } + } + runCommandSync("npm", ["install", "-g", `${pkg.name}@latest`], { stdio: "inherit" }); + + // 生效验证:以实际解析到的 Core 为准,不再仅凭 npm 退出码报成功。 + const installedCli = getGlobalInstalledVersion(); + const effectiveAfter = getGlobalEffectiveCoreVersion(); + console.log(`\n✓ CLI 已更新到 v${installedCli || latestCli};Core 实际生效版本 v${effectiveAfter || "未知"}`); + if (!effectiveAfter || compareVersions(effectiveAfter, latestCore.version) < 0) { + console.error([ + `⚠ Core 生效版本仍为 v${effectiveAfter || "未知"}(期望 v${latestCore.version})。`, + `若刚发布新版,可能处于 CLI/Core 联动发布窗口,稍后重试;否则请手动执行:`, + ` npm uninstall -g ${pkg.name} && npm install -g ${pkg.name}@latest`, + ].join("\n")); + process.exit(1); } console.log(latestCore.templateVersion === coreVersions.templateVersion ? "Template Version 未变化,无需执行 f2s-kb-upgrade。" diff --git a/packages/cli/package.json b/packages/cli/package.json index 2da41a2..7eeb9cf 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@double-coding/flow2spec", - "version": "3.6.1", + "version": "3.6.2", "description": "在业务仓库初始化文档驱动、可写回知识库的 AI 协作骨架", "homepage": "https://github.com/double-coding-lab/Flow2Spec#readme", "repository": { @@ -19,7 +19,7 @@ "README.md" ], "dependencies": { - "@double-coding/flow2spec-core": "^3.6.0" + "@double-coding/flow2spec-core": "3.7.2" }, "publishConfig": { "access": "public", diff --git a/packages/core/package.json b/packages/core/package.json index 9828f6b..aef2bec 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@double-coding/flow2spec-core", - "version": "3.7.1", - "templateVersion": "3.6.1", + "version": "3.7.2", + "templateVersion": "3.6.2", "description": "Flow2Spec Core APIs, knowledge engine, project initialization and shared resources", "homepage": "https://github.com/double-coding-lab/Flow2Spec#readme", "repository": { diff --git a/packages/core/templates/en-US/hooks/f2s-update-check.js b/packages/core/templates/en-US/hooks/f2s-update-check.js index a1b77d3..1165600 100644 --- a/packages/core/templates/en-US/hooks/f2s-update-check.js +++ b/packages/core/templates/en-US/hooks/f2s-update-check.js @@ -95,7 +95,7 @@ function buildNotice(state) { '', `📦 [flow2spec] Project "${project}" has a template update (${versionSummary}).`, '', - '[flow2spec/agent-instruction] You may run `flow2spec update --core`, then `flow2spec init `. After init, read `.Knowledge/manifest-routing.json`: if projectRev equals pkgRev, delete `.Knowledge/update-check.json`; otherwise continue with f2s-kb-upgrade from step 2c.', + '[flow2spec/agent-instruction] You may run `flow2spec update --cli` (CLI and its pinned Core update in lockstep), then `flow2spec init `. After init, read `.Knowledge/manifest-routing.json`: if projectRev equals pkgRev, delete `.Knowledge/update-check.json`; otherwise continue with f2s-kb-upgrade from step 2c.', ].join('\n'); } return [ @@ -103,7 +103,7 @@ function buildNotice(state) { '', `📦 [flow2spec] Project "${project}" has a Core-only update (${versionSummary}); the template version is unchanged.`, '', - '[flow2spec/agent-instruction] You may run `flow2spec update --core`, then one idempotent `flow2spec init ` to refresh the Hook. Do not enter f2s-kb-upgrade when Template Version is unchanged; delete `.Knowledge/update-check.json` afterwards.', + '[flow2spec/agent-instruction] You may run `flow2spec update --cli` (CLI and its pinned Core update in lockstep), then one idempotent `flow2spec init ` to refresh the Hook. Do not enter f2s-kb-upgrade when Template Version is unchanged; delete `.Knowledge/update-check.json` afterwards.', ].join('\n'); } diff --git a/packages/core/templates/en-US/knowledge/manifest-routing.json b/packages/core/templates/en-US/knowledge/manifest-routing.json index a7c0076..6116e5d 100644 --- a/packages/core/templates/en-US/knowledge/manifest-routing.json +++ b/packages/core/templates/en-US/knowledge/manifest-routing.json @@ -1,5 +1,5 @@ { - "version": "3.6.1", + "version": "3.6.2", "projectRev": 3, "knowledgeRoot": ".Knowledge", "matcherKey": "matcherId", diff --git a/packages/core/templates/en-US/rules/f2s-flow2spec-unified-entry.md b/packages/core/templates/en-US/rules/f2s-flow2spec-unified-entry.md index 86e618f..fef0f9b 100644 --- a/packages/core/templates/en-US/rules/f2s-flow2spec-unified-entry.md +++ b/packages/core/templates/en-US/rules/f2s-flow2spec-unified-entry.md @@ -98,7 +98,7 @@ Each initialized client uses its own startup/update mechanism when supported; th **Rule-layer fallback check** (backup for script cache): 1. Read `flow2spec.config.json` -> if `updateCheck.enabled` is not `true`, skip and show no notice. -2. Read `.Knowledge/update-check.json` -> if the file exists and `checkedAt` is on the same local calendar day, do not query npm again. When `coreUpdateAvailable=true`, the agent may run `flow2spec update --core`. If `templateUpdateAvailable=false`, run one idempotent `flow2spec init ` to refresh the Hook, delete the cache, and do not enter `f2s-kb-upgrade`. If `templateUpdateAvailable=true`, update Core, run init, then use `projectRev` / `pkgRev` to choose the fast path or full flow. `.Knowledge/manifest-routing.json.version` is Template Version and must not be compared directly with Core Version. +2. Read `.Knowledge/update-check.json` -> if the file exists and `checkedAt` is on the same local calendar day, do not query npm again. When `coreUpdateAvailable=true`, the agent may run `flow2spec update --cli` (CLI and its pinned Core update in lockstep). If `templateUpdateAvailable=false`, run one idempotent `flow2spec init ` to refresh the Hook, delete the cache, and do not enter `f2s-kb-upgrade`. If `templateUpdateAvailable=true`, update first, run init, then use `projectRev` / `pkgRev` to choose the fast path or full flow. `.Knowledge/manifest-routing.json.version` is Template Version and must not be compared directly with Core Version. 3. If neither of the two steps above skipped the check: run the update-check script under the current agent configuration root (Claude: `node .claude/hooks/f2s-update-check.js`; Cursor: `node .cursor/hooks/f2s-update-check.js`; Codex: `node .codex/hooks/f2s-update-check.js`) and parse JSON from stdout: - If it contains `hookSpecificOutput.additionalContext`: **tell the user** that content and follow its separate Core-only or Template-update instructions. - If there is no output or parsing fails: stay silent. diff --git a/packages/core/templates/en-US/skills/f2s-kb-upgrade/SKILL.md b/packages/core/templates/en-US/skills/f2s-kb-upgrade/SKILL.md index 814a775..8c7285a 100644 --- a/packages/core/templates/en-US/skills/f2s-kb-upgrade/SKILL.md +++ b/packages/core/templates/en-US/skills/f2s-kb-upgrade/SKILL.md @@ -107,13 +107,13 @@ flow2spec version flow2spec update --check ``` -Record CLI Version, Core Version, Core Range, Template Version, Protocol Version, and the latest npm Core/Template values: +Record CLI Version, Core Version, Core Pinned, Template Version, Protocol Version, and the latest npm Core/Template values (the CLI pins Core to an exact version; the two packages release in lockstep): | Case | Action | Default step 2 command | | --- | --- | --- | -| **A. Template is current** | If only Core changed, run `flow2spec update --core`, then one idempotent init to refresh the Hook. Afterwards **do not stop immediately**: first run the "**project-side alignment check**" below, and only stop this skill (clearing the cache) after it passes. | `flow2spec init ` | -| **B. Template changed and latest Core is inside Core Range** | Run `flow2spec update --core`, then continue to step 0. A compatible Core update does not require a CLI release or upgrade. | `flow2spec init ` | -| **C. Not installed, unknown, or latest Core is outside the range** | Use an explicit latest CLI/Core combination so npx cannot reuse an old Core. If latest Core still is not compatible, upgrade to a CLI that supports it first. | `npx --yes --package @latest --package @latest flow2spec init ` | +| **A. Template is current** | If the CLI/Core has an update, run `flow2spec update --cli` (CLI and its pinned Core update together), then one idempotent init to refresh the Hook. Afterwards **do not stop immediately**: first run the "**project-side alignment check**" below, and only stop this skill (clearing the cache) after it passes. | `flow2spec init ` | +| **B. Template changed** | Run `flow2spec update --cli` (CLI and its pinned Core arrive together), then continue to step 0. | `flow2spec init ` | +| **C. Not installed or unknown** | Use the latest CLI (it carries its pinned Core), avoiding stale npx caches. | `npx --yes @latest init ` | If the preflight fails, case C is allowed as fallback, but never treat Core Version as Template Version. This step does not mandate a sub-agent or a background global install. @@ -159,8 +159,8 @@ Run one of the following in the target project root (**choose the default form b 1. **Step -1 returned A/B (local CLI/Core is usable)**: use the current CLI: - `flow2spec init ` -2. **Step -1 returned C**: explicitly combine latest CLI/Core so this session gets the latest Core and templates: - - `npx --yes --package @latest --package @latest flow2spec init ` +2. **Step -1 returned C**: use the latest CLI (it carries its pinned Core and templates): + - `npx --yes @latest init ` 3. For overwrite reset: - Append `--reset-knowledge` to the above command. 4. If the user explicitly requests a template-language switch: @@ -169,7 +169,7 @@ Run one of the following in the target project root (**choose the default form b > `` example: `cursor claude codex`. -> **Helper commands (user self-inspection)**: `flow2spec version` shows the five version dimensions; `flow2spec update --check|--cli|--core` checks updates, updates CLI, or updates a compatible Core. These commands do not replace this skill's full flow after Template Version changes. +> **Helper commands (user self-inspection)**: `flow2spec version` shows the five version dimensions; `flow2spec update --check` checks updates, and `flow2spec update --cli` performs the lockstep update (CLI plus its pinned Core; `--core` is an equivalent alias). These commands do not replace this skill's full flow after Template Version changes. **After step 2 completes**: immediately execute the above **"init and skill self-update"** loop: re-read **`skills/f2s-kb-upgrade/SKILL.md`**. If updated, **rerun from step 2c per the new literal text** (**do not run `init` a second time**; avoid using the old SKILL for subsequent verification). @@ -346,7 +346,7 @@ Output: ## Completion Self-Check -1. **Step -1** was performed: `flow2spec version` and `flow2spec update --check` recorded CLI/Core/Core Range/Template/Protocol; Core-only updates refreshed Core/Hook, while Template updates selected the current CLI or an explicit latest CLI/Core combination through A/B/C; **before stopping on branch A, the "project-side alignment check" was completed** (manifest `version`/`pkgRev` comparison + `kb check --strict`), and when not aligned the skill switched to the full flow instead of stopping. +1. **Step -1** was performed: `flow2spec version` and `flow2spec update --check` recorded CLI/Core/Core Pinned/Template/Protocol; when updates existed, `flow2spec update --cli` refreshed the CLI and its pinned Core in lockstep, and Template updates selected the current CLI or the latest CLI through A/B/C; **before stopping on branch A, the "project-side alignment check" was completed** (manifest `version`/`pkgRev` comparison + `kb check --strict`), and when not aligned the skill switched to the full flow instead of stopping. 2. **Step 0** was performed: on V1 the skill stopped and told the user how to proceed, and **current repositories (V2+)** entered the `init` flow normally. 3. **Before step 2** recorded the project-side `projectRev` (`projectRev`), and **after step 2 `init`** re-read `pkgRev` and executed **step 2c** judgment. 4. After **step 2 `init`**, **`f2s-kb-upgrade/SKILL.md`** was re-read: on full flow, a change must trigger **a rerun from step 2c per the new literal text** (**no second `init`**); on fast path, the loop can be skipped (see "init and skill self-update" / "fast-path exception"). diff --git a/packages/core/templates/zh-CN/hooks/f2s-update-check.js b/packages/core/templates/zh-CN/hooks/f2s-update-check.js index b28b717..bb8e36c 100644 --- a/packages/core/templates/zh-CN/hooks/f2s-update-check.js +++ b/packages/core/templates/zh-CN/hooks/f2s-update-check.js @@ -95,7 +95,7 @@ function buildNotice(state) { '', `📦 [flow2spec] 当前项目「${project}」有模板更新(${versionSummary})。`, '', - '[flow2spec/agent-instruction] 可先执行 `flow2spec update --core`,再执行 `flow2spec init <已初始化的 agents>`。init 后读取 `.Knowledge/manifest-routing.json`:projectRev 与 pkgRev 相等则更新完成并删除 `.Knowledge/update-check.json`;不等时再执行 f2s-kb-upgrade(可从步骤 2c 继续)。', + '[flow2spec/agent-instruction] 可先执行 `flow2spec update --cli`(CLI 与配套 Core 联动更新),再执行 `flow2spec init <已初始化的 agents>`。init 后读取 `.Knowledge/manifest-routing.json`:projectRev 与 pkgRev 相等则更新完成并删除 `.Knowledge/update-check.json`;不等时再执行 f2s-kb-upgrade(可从步骤 2c 继续)。', ].join('\n'); } return [ @@ -103,7 +103,7 @@ function buildNotice(state) { '', `📦 [flow2spec] 当前项目「${project}」仅有 Core 程序更新(${versionSummary}),模板版本未变化。`, '', - '[flow2spec/agent-instruction] 可执行 `flow2spec update --core`,随后执行一次幂等 `flow2spec init <已初始化的 agents>` 刷新 Hook。Template Version 未变化,不进入 f2s-kb-upgrade;完成后删除 `.Knowledge/update-check.json`。', + '[flow2spec/agent-instruction] 可执行 `flow2spec update --cli`(CLI 与配套 Core 联动更新),随后执行一次幂等 `flow2spec init <已初始化的 agents>` 刷新 Hook。Template Version 未变化,不进入 f2s-kb-upgrade;完成后删除 `.Knowledge/update-check.json`。', ].join('\n'); } diff --git a/packages/core/templates/zh-CN/knowledge/manifest-routing.json b/packages/core/templates/zh-CN/knowledge/manifest-routing.json index f694bd3..28e5f16 100644 --- a/packages/core/templates/zh-CN/knowledge/manifest-routing.json +++ b/packages/core/templates/zh-CN/knowledge/manifest-routing.json @@ -1,5 +1,5 @@ { - "version": "3.6.1", + "version": "3.6.2", "projectRev": 3, "knowledgeRoot": ".Knowledge", "matcherKey": "matcherId", diff --git a/packages/core/templates/zh-CN/rules/f2s-flow2spec-unified-entry.md b/packages/core/templates/zh-CN/rules/f2s-flow2spec-unified-entry.md index b35cb67..04006c2 100644 --- a/packages/core/templates/zh-CN/rules/f2s-flow2spec-unified-entry.md +++ b/packages/core/templates/zh-CN/rules/f2s-flow2spec-unified-entry.md @@ -98,7 +98,7 @@ alwaysApply: true **规则层双保险**(与脚本缓存互为备份): 1. 读 `flow2spec.config.json` → 若 `updateCheck.enabled` 不为 `true`,跳过,不做任何提示。 -2. 读 `.Knowledge/update-check.json` → 若文件存在且 `checkedAt` 与今日为同一自然日,不重复查 npm;`coreUpdateAvailable=true` 时可执行 `flow2spec update --core`。若 `templateUpdateAvailable=false`,随后只执行一次幂等 `flow2spec init <已初始化的 agents>` 刷新 Hook,删除缓存,不进入 `f2s-kb-upgrade`;若 `templateUpdateAvailable=true`,更新 Core 后执行 init,再以 `projectRev` / `pkgRev` 判定快速路径或完整流程。`.Knowledge/manifest-routing.json.version` 表示 Template Version,禁止与 Core Version 直接比较。 +2. 读 `.Knowledge/update-check.json` → 若文件存在且 `checkedAt` 与今日为同一自然日,不重复查 npm;`coreUpdateAvailable=true` 时可执行 `flow2spec update --cli`(CLI 与配套 Core 联动更新)。若 `templateUpdateAvailable=false`,随后只执行一次幂等 `flow2spec init <已初始化的 agents>` 刷新 Hook,删除缓存,不进入 `f2s-kb-upgrade`;若 `templateUpdateAvailable=true`,更新后执行 init,再以 `projectRev` / `pkgRev` 判定快速路径或完整流程。`.Knowledge/manifest-routing.json.version` 表示 Template Version,禁止与 Core Version 直接比较。 3. 上述两步均未跳过时:执行当前 agent 配置根下的更新检测脚本(Claude:`node .claude/hooks/f2s-update-check.js`;Cursor:`node .cursor/hooks/f2s-update-check.js`;Codex:`node .codex/hooks/f2s-update-check.js`),解析标准输出的 JSON: - 若含 `hookSpecificOutput.additionalContext`:**告知用户**该内容,并按其中 agent-instruction 分别处理 Core-only 与 Template 更新。 - 无输出或解析失败:静默,不提示。 diff --git a/packages/core/templates/zh-CN/skills/f2s-kb-upgrade/SKILL.md b/packages/core/templates/zh-CN/skills/f2s-kb-upgrade/SKILL.md index 2f30e79..1871347 100644 --- a/packages/core/templates/zh-CN/skills/f2s-kb-upgrade/SKILL.md +++ b/packages/core/templates/zh-CN/skills/f2s-kb-upgrade/SKILL.md @@ -107,13 +107,13 @@ flow2spec version flow2spec update --check ``` -按输出记录 CLI Version、Core Version、Core Range、Template Version、Protocol Version,以及 npm 最新 Core/Template: +按输出记录 CLI Version、Core Version、Core Pinned、Template Version、Protocol Version,以及 npm 最新 Core/Template(CLI 对 Core 为精确 pin,两包联动发布): | 情况 | 行动 | 步骤 2 默认命令 | | --- | --- | --- | -| **A. Template 已是最新** | 若只有 Core 更新,执行 `flow2spec update --core` 后幂等 init 刷新 Hook;随后**不得直接停止**,必须先做下方「**项目侧对齐检查**」,通过后才删除缓存并停止本技能 | `flow2spec init ` | -| **B. Template 有更新且最新 Core 落在当前 Core Range** | 执行 `flow2spec update --core`,继续步骤 0;CLI 无需因 Core 兼容更新而发版或升级 | `flow2spec init ` | -| **C. 未安装、版本未知或最新 Core 超出范围** | 使用显式 latest CLI/Core 组合,避免 npx 复用旧 Core;若最新 Core 仍不兼容,先升级支持它的 CLI | `npx --yes --package @latest --package @latest flow2spec init ` | +| **A. Template 已是最新** | 若 CLI/Core 有更新,执行 `flow2spec update --cli`(CLI 与配套 Core 联动更新)后幂等 init 刷新 Hook;随后**不得直接停止**,必须先做下方「**项目侧对齐检查**」,通过后才删除缓存并停止本技能 | `flow2spec init ` | +| **B. Template 有更新** | 执行 `flow2spec update --cli`(CLI 与配套 Core 一起到位),继续步骤 0 | `flow2spec init ` | +| **C. 未安装或版本未知** | 使用 latest CLI(自带 pin 的配套 Core),避免 npx 复用旧版缓存 | `npx --yes @latest init ` | 预检失败时允许回退 C,但不得把 Core Version 当作 Template Version。此步骤不强制创建子 agent,也不后台安装全局包。 @@ -159,8 +159,8 @@ flow2spec update --check 1. **步骤 -1 判定为 A/B(本地 CLI/Core 可用)**:直接使用当前 CLI: - `flow2spec init ` -2. **步骤 -1 判定为 C**:显式组合 latest CLI/Core,保证本次拿到最新 Core 与模板: - - `npx --yes --package @latest --package @latest flow2spec init ` +2. **步骤 -1 判定为 C**:用 latest CLI(自带 pin 的配套 Core 与模板): + - `npx --yes @latest init ` 3. 覆盖重置时: - 在上述命令末尾追加 `--reset-knowledge` 4. 用户显式要求切换模板语言时: @@ -169,7 +169,7 @@ flow2spec update --check > `` 示例:`cursor claude codex`。 -> **辅助命令(用户可自查)**:`flow2spec version` 查看五维版本;`flow2spec update --check|--cli|--core` 分别检查、更新 CLI、更新兼容 Core。这些命令不替代 Template Version 变化后的本技能完整流程。 +> **辅助命令(用户可自查)**:`flow2spec version` 查看五维版本;`flow2spec update --check` 检查更新,`flow2spec update --cli` 整体更新(CLI 与配套 Core 联动,`--core` 为其等价别名)。这些命令不替代 Template Version 变化后的本技能完整流程。 **步骤 2 完成后**:立刻执行上文 **「init 与技能自更新」**:重读 **`skills/f2s-kb-upgrade/SKILL.md`**;若有更新则**按新版字面从步骤 2c 起重跑**(**不再次 init**;避免用旧版 SKILL 做后续校验)。 @@ -346,7 +346,7 @@ flow2spec update --check ## 完成后自检 -1. 是否已做 **步骤 -1**:执行 `flow2spec version` 与 `flow2spec update --check`,记录 CLI/Core/Core Range/Template/Protocol;Core-only 更新是否直接刷新 Core/Hook,Template 更新是否按 A/B/C 选择当前 CLI 或显式 latest CLI/Core 组合;**A 分支停止前是否完成「项目侧对齐检查」**(manifest `version`/`pkgRev` 对比 + `kb check --strict`),未对齐时是否已转完整流程而非直接停止。 +1. 是否已做 **步骤 -1**:执行 `flow2spec version` 与 `flow2spec update --check`,记录 CLI/Core/Core Pinned/Template/Protocol;有更新时是否执行 `flow2spec update --cli` 联动刷新 CLI 与配套 Core,Template 更新是否按 A/B/C 选择当前 CLI 或 latest CLI;**A 分支停止前是否完成「项目侧对齐检查」**(manifest `version`/`pkgRev` 对比 + `kb check --strict`),未对齐时是否已转完整流程而非直接停止。 2. 是否已做 **步骤 0**:V1 已停止执行并告知用户处理方式、**现行库(V2+)** 正常进入 `init` 流程。 3. 是否在 **步骤 2 开始前** 记录了项目侧 `projectRev`(`projectRev`),并在 **步骤 2 的 `init` 之后** 重读 `pkgRev`、执行 **步骤 2c** 判定。 4. 是否在 **步骤 2 的 `init` 之后**重读过 **`f2s-kb-upgrade/SKILL.md`**:完整流程下有变化必须**按新版字面从步骤 2c 起重跑**(**不再次 init**);快速路径下可跳过该闭环(见「init 与技能自更新」「快速路径例外」)。 diff --git a/scripts/test-cli-update.js b/scripts/test-cli-update.js index 92e2e94..32e8aa9 100644 --- a/scripts/test-cli-update.js +++ b/scripts/test-cli-update.js @@ -61,7 +61,8 @@ assert.strictEqual(check.status, 0, check.stderr); assert.match(check.stdout, new RegExp(`CLI:\\s+${escapeRe(cliVersion)} -> ${escapeRe(latestCli)}`)); assert.match(check.stdout, new RegExp(`Core:\\s+${escapeRe(coreVersion)} -> ${escapeRe(latestCore)}`)); assert.match(check.stdout, new RegExp(`Template:\\s+${escapeRe(templateVersion)} -> ${escapeRe(templateVersion)}`)); -assert.match(check.stdout, new RegExp(`${escapeRe(coreRange)} \\(compatible\\)`)); +assert.match(check.stdout, new RegExp(`pin Core ${escapeRe(coreRange)}`)); +assert.match(check.stdout, /update --cli 一键更新/); const invalid = spawnSync(process.execPath, [cliPath, "update", "--unknown"], { cwd: path.resolve(__dirname, ".."), diff --git a/scripts/test-workspace-version.js b/scripts/test-workspace-version.js index 1eaaeec..00f0191 100644 --- a/scripts/test-workspace-version.js +++ b/scripts/test-workspace-version.js @@ -6,9 +6,8 @@ const os = require("os"); const path = require("path"); const { checkWorkspaceVersion, - normalizeCoreRange, + normalizeCorePin, normalizeVersion, - satisfiesCaret, setCliVersion, setCoreVersion, setTemplateVersion, @@ -34,30 +33,31 @@ for (const relativePath of [ assert.strictEqual(normalizeVersion("v4.1.0-beta.2"), "4.1.0-beta.2"); assert.throws(() => normalizeVersion("4.01.0"), /invalid semantic version/); -assert.strictEqual(normalizeCoreRange("^3.5.0"), "^3.5.0"); -assert.throws(() => normalizeCoreRange(">3.5.0"), /caret semantic range/); -assert.strictEqual(satisfiesCaret("3.6.0", "^3.5.0"), true); -assert.strictEqual(satisfiesCaret("4.0.0", "^3.5.0"), false); +assert.strictEqual(normalizeCorePin("3.5.0"), "3.5.0"); +assert.throws(() => normalizeCorePin("^3.5.0"), /pinned to an exact version/); +assert.throws(() => normalizeCorePin(">3.5.0"), /pinned to an exact version/); setCoreVersion("3.6.0", { rootDir: tempRoot }); -setCliVersion("3.5.1", { rootDir: tempRoot, coreRange: "^3.6.0" }); +setCliVersion("3.5.1", { rootDir: tempRoot }); setTemplateVersion("3.5.2", { rootDir: tempRoot }); assert.deepStrictEqual(checkWorkspaceVersion({ rootDir: tempRoot, tag: "core-v3.6.0" }), { cliVersion: "3.5.1", coreVersion: "3.6.0", templateVersion: "3.5.2", - coreRange: "^3.6.0", + corePin: "3.6.0", protocolVersion: 2, }); assert.deepStrictEqual(checkWorkspaceVersion({ rootDir: tempRoot, tag: "cli-v3.5.1" }), { cliVersion: "3.5.1", coreVersion: "3.6.0", templateVersion: "3.5.2", - coreRange: "^3.6.0", + corePin: "3.6.0", protocolVersion: 2, }); assert.throws(() => checkWorkspaceVersion({ rootDir: tempRoot, tag: "v3.6.0" }), /release tag must match/); -assert.throws(() => setCoreVersion("4.0.0", { rootDir: tempRoot }), /outside the CLI dependency range/); +// set-core 联动同步 pin:任意新版本都应成功并把 CLI 依赖 pin 到同版本。 +assert.deepStrictEqual(setCoreVersion("4.0.0", { rootDir: tempRoot }), { coreVersion: "4.0.0", corePin: "4.0.0" }); +setCoreVersion("3.6.0", { rootDir: tempRoot }); const corePackage = require(path.join(tempRoot, "packages/core/package.json")); const cliPackage = require(path.join(tempRoot, "packages/cli/package.json")); @@ -65,8 +65,9 @@ const lockfile = require(path.join(tempRoot, "package-lock.json")); assert.strictEqual(corePackage.version, "3.6.0"); assert.strictEqual(corePackage.templateVersion, "3.5.2"); assert.strictEqual(cliPackage.version, "3.5.1"); -assert.strictEqual(cliPackage.dependencies["@double-coding/flow2spec-core"], "^3.6.0"); +assert.strictEqual(cliPackage.dependencies["@double-coding/flow2spec-core"], "3.6.0"); assert.strictEqual(lockfile.packages["packages/core"].version, "3.6.0"); assert.strictEqual(lockfile.packages["packages/cli"].version, "3.5.1"); +assert.strictEqual(lockfile.packages["packages/cli"].dependencies["@double-coding/flow2spec-core"], "3.6.0"); console.log("test-workspace-version: ok"); diff --git a/scripts/workspace-version.js b/scripts/workspace-version.js index 9817588..dcc0361 100644 --- a/scripts/workspace-version.js +++ b/scripts/workspace-version.js @@ -7,7 +7,7 @@ const path = require("path"); const CORE_PACKAGE = "@double-coding/flow2spec-core"; const SEMVER_SOURCE = "(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\\+([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?"; const SEMVER_PATTERN = new RegExp(`^${SEMVER_SOURCE}$`); -const CORE_RANGE_PATTERN = new RegExp(`^\\^(${SEMVER_SOURCE})$`); +const CORE_PIN_PATTERN = new RegExp(`^(${SEMVER_SOURCE})$`); function readJson(filePath) { return JSON.parse(fs.readFileSync(filePath, "utf8")); @@ -78,23 +78,11 @@ function compareVersions(left, right) { return a.prerelease.localeCompare(b.prerelease, "en", { numeric: true }); } -function normalizeCoreRange(input) { +function normalizeCorePin(input) { const raw = String(input || "").trim(); - const match = CORE_RANGE_PATTERN.exec(raw); - if (!match) throw new Error(`Core dependency must use a caret semantic range, received: ${raw || ""}`); - return `^${normalizeVersion(match[1])}`; -} - -function satisfiesCaret(versionInput, rangeInput) { - const version = parseVersion(versionInput); - const minimum = parseVersion(normalizeCoreRange(rangeInput).slice(1)); - if (compareVersions(version.version, minimum.version) < 0) return false; - const [major, minor, patch] = minimum.numbers; - let upper; - if (major > 0) upper = `${major + 1}.0.0`; - else if (minor > 0) upper = `0.${minor + 1}.0`; - else upper = `0.0.${patch + 1}`; - return compareVersions(version.version, upper) < 0; + const match = CORE_PIN_PATTERN.exec(raw); + if (!match) throw new Error(`Core dependency must be pinned to an exact version (release-in-lockstep policy), received: ${raw || ""}`); + return normalizeVersion(match[1]); } function collectVersionErrors(workspace, tag) { @@ -102,7 +90,7 @@ function collectVersionErrors(workspace, tag) { const cliVersion = String(cli.version || "").trim(); const coreVersion = String(core.version || "").trim(); const templateVersion = String(core.templateVersion || "").trim(); - const coreRange = String(cli.dependencies?.[CORE_PACKAGE] || "").trim(); + const corePin = String(cli.dependencies?.[CORE_PACKAGE] || "").trim(); const protocolVersion = capabilities.protocolVersion; const errors = []; const expect = (actual, wanted, label) => { @@ -123,9 +111,10 @@ function collectVersionErrors(workspace, tag) { } try { - normalizeCoreRange(coreRange); - if (!satisfiesCaret(coreVersion, coreRange)) { - errors.push(`Core ${coreVersion} is outside the CLI dependency range ${coreRange}`); + normalizeCorePin(corePin); + // 联动发版硬约束:CLI 必须 pin 到当前 Core 版本,Core 发版必带 CLI patch。 + if (compareVersions(corePin, coreVersion) !== 0) { + errors.push(`CLI must pin Core exactly: pinned ${corePin}, Core version ${coreVersion} (run version:set:core to sync, then bump CLI)`); } } catch (error) { errors.push(`packages/cli/package.json dependency ${CORE_PACKAGE}: ${error.message}`); @@ -139,7 +128,7 @@ function collectVersionErrors(workspace, tag) { expect(String(lock.packages?.[""]?.version || "").trim(), String(root.version || "").trim(), "package-lock.json root version"); expect(String(lock.packages?.["packages/core"]?.version || "").trim(), coreVersion, "package-lock.json Core version"); expect(String(lock.packages?.["packages/cli"]?.version || "").trim(), cliVersion, "package-lock.json CLI version"); - expect(String(lock.packages?.["packages/cli"]?.dependencies?.[CORE_PACKAGE] || "").trim(), coreRange, `package-lock.json CLI dependency ${CORE_PACKAGE}`); + expect(String(lock.packages?.["packages/cli"]?.dependencies?.[CORE_PACKAGE] || "").trim(), corePin, `package-lock.json CLI dependency ${CORE_PACKAGE}`); if (lock.packages?.[""]?.dependencies?.[CORE_PACKAGE]) errors.push(`package-lock.json root must not depend on ${CORE_PACKAGE}`); if (root.dependencies?.[CORE_PACKAGE]) errors.push(`package.json root must not depend on ${CORE_PACKAGE}`); @@ -162,7 +151,7 @@ function collectVersionErrors(workspace, tag) { } } - return { errors, cliVersion, coreVersion, templateVersion, coreRange, protocolVersion }; + return { errors, cliVersion, coreVersion, templateVersion, corePin, protocolVersion }; } function checkWorkspaceVersion(options = {}) { @@ -179,36 +168,32 @@ function setCliVersion(input, options = {}) { const rootDir = path.resolve(options.rootDir || path.join(__dirname, "..")); const version = normalizeVersion(input); const workspace = loadWorkspace(rootDir); - const coreRange = options.coreRange - ? normalizeCoreRange(options.coreRange) - : normalizeCoreRange(workspace.cli.dependencies?.[CORE_PACKAGE]); - if (!satisfiesCaret(workspace.core.version, coreRange)) { - throw new Error(`Core ${workspace.core.version} is outside the requested CLI dependency range ${coreRange}`); - } + // pin 自动对齐当前 Core 版本(联动发版策略)。 + const corePin = normalizeVersion(workspace.core.version); workspace.cli.version = version; - workspace.cli.dependencies[CORE_PACKAGE] = coreRange; + workspace.cli.dependencies[CORE_PACKAGE] = corePin; workspace.lock.packages["packages/cli"].version = version; - workspace.lock.packages["packages/cli"].dependencies[CORE_PACKAGE] = coreRange; + workspace.lock.packages["packages/cli"].dependencies[CORE_PACKAGE] = corePin; writeJson(workspace.paths.cli, workspace.cli); writeJson(workspace.paths.lock, workspace.lock); checkWorkspaceVersion({ rootDir }); - return { cliVersion: version, coreRange }; + return { cliVersion: version, corePin }; } function setCoreVersion(input, options = {}) { const rootDir = path.resolve(options.rootDir || path.join(__dirname, "..")); const version = normalizeVersion(input); const workspace = loadWorkspace(rootDir); - const coreRange = normalizeCoreRange(workspace.cli.dependencies?.[CORE_PACKAGE]); - if (!satisfiesCaret(version, coreRange)) { - throw new Error(`Core ${version} is outside the CLI dependency range ${coreRange}; update the CLI range first`); - } workspace.core.version = version; workspace.lock.packages["packages/core"].version = version; + // 联动同步 CLI 的 pin;Core 发版必须随后 bump CLI patch(check 会强制兼容校验)。 + workspace.cli.dependencies[CORE_PACKAGE] = version; + workspace.lock.packages["packages/cli"].dependencies[CORE_PACKAGE] = version; writeJson(workspace.paths.core, workspace.core); + writeJson(workspace.paths.cli, workspace.cli); writeJson(workspace.paths.lock, workspace.lock); checkWorkspaceVersion({ rootDir }); - return { coreVersion: version }; + return { coreVersion: version, corePin: version }; } function setTemplateVersion(input, options = {}) { @@ -235,7 +220,7 @@ function readOption(args, name) { } function versionArgument(args, usage) { - const value = args.find((arg, index) => arg !== "--" && (index === 0 || args[index - 1] !== "--core-range")); + const value = args.find((arg) => arg !== "--"); if (!value) throw new Error(usage); return value; } @@ -249,15 +234,13 @@ function main(args = process.argv.slice(2)) { return; } if (command === "set-cli") { - const result = setCliVersion(versionArgument(rest, "usage: npm run version:set:cli -- [--core-range ^x.y.z]"), { - coreRange: readOption(rest, "--core-range"), - }); - console.log(`CLI version updated: ${result.cliVersion} (Core ${result.coreRange})`); + const result = setCliVersion(versionArgument(rest, "usage: npm run version:set:cli -- ")); + console.log(`CLI version updated: ${result.cliVersion} (Core pinned ${result.corePin})`); return; } if (command === "set-core") { const result = setCoreVersion(versionArgument(rest, "usage: npm run version:set:core -- ")); - console.log(`Core version updated: ${result.coreVersion}`); + console.log(`Core version updated: ${result.coreVersion} (CLI pin synced; remember to bump CLI patch — release in lockstep)`); return; } if (command === "set-template") { @@ -281,9 +264,8 @@ module.exports = { checkWorkspaceVersion, collectVersionErrors, compareVersions, - normalizeCoreRange, + normalizeCorePin, normalizeVersion, - satisfiesCaret, setCliVersion, setCoreVersion, setTemplateVersion,