From 990923326ae2355ea384ec9a492d13b53eabcb04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=B0=E6=B6=9B?= <550947002@qq.com> Date: Wed, 23 Sep 2026 19:23:49 +0800 Subject: [PATCH] =?UTF-8?q?[tech]=20feat:=20=E6=94=AF=E6=8C=81=20Core=20?= =?UTF-8?q?=E5=85=BC=E5=AE=B9=E8=8C=83=E5=9B=B4=E4=BE=9D=E8=B5=96=E4=B8=8E?= =?UTF-8?q?=E7=8B=AC=E7=AB=8B=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [摘要] 将核心包依赖改为兼容范围,支持保留命令行版本独立更新核心包,并校验实际生效版本。 --- .Knowledge/index.md | 3 +- .Knowledge/manifest-routing.json | 16 +- .../m-architecture-document-naming.json | 13 ++ ...4\346\226\260_\347\273\210\347\250\277.md" | 12 ++ ...6\345\256\232_\347\273\210\347\250\277.md" | 18 ++ .../topics/architecture-document-naming.md | 25 +++ .../topics/f2s-dev-workflow-constraints.md | 6 +- .Knowledge/topics/flow2spec-core-package.md | 20 +- .../rules/repo-dev-workflow-constraints.md | 16 +- .../topics/repo-dev-workflow-constraints.md | 16 +- .../rules/repo-dev-workflow-constraints.mdc | 16 +- docs/en/upgrade-guide.md | 26 ++- docs/en/usage-guide.md | 2 +- ...77\347\224\250\350\257\264\346\230\216.md" | 2 +- ...07\347\272\247\346\214\207\345\215\227.md" | 26 ++- ...03\344\270\216\351\203\250\347\275\262.md" | 12 +- package-lock.json | 6 +- packages/cli/README.md | 184 +++++++++--------- packages/cli/cli.js | 80 +++++--- packages/cli/package.json | 4 +- packages/core/package.json | 4 +- .../en-US/knowledge/manifest-routing.json | 2 +- .../en-US/rules/f2s-topic-authoring.md | 3 + .../en-US/skills/f2s-doc-arch/SKILL.md | 18 +- .../en-US/skills/f2s-doc-final/SKILL.md | 8 + .../en-US/skills/f2s-kb-build/SKILL.md | 6 +- .../en-US/skills/f2s-kb-upgrade/SKILL.md | 14 +- .../zh-CN/knowledge/manifest-routing.json | 2 +- .../zh-CN/rules/f2s-topic-authoring.md | 5 +- .../zh-CN/skills/f2s-doc-arch/SKILL.md | 20 +- .../zh-CN/skills/f2s-doc-final/SKILL.md | 8 + .../zh-CN/skills/f2s-kb-build/SKILL.md | 6 +- .../zh-CN/skills/f2s-kb-upgrade/SKILL.md | 14 +- scripts/test-cli-update.js | 172 +++++++++------- scripts/test-workspace-version.js | 79 +++++++- scripts/workspace-version.js | 81 +++++--- 36 files changed, 613 insertions(+), 332 deletions(-) create mode 100644 .Knowledge/matchers/m-architecture-document-naming.json create mode 100644 ".Knowledge/stock-docs/Core\345\205\274\345\256\271\344\276\235\350\265\226\344\270\216\347\213\254\347\253\213\346\233\264\346\226\260_\347\273\210\347\250\277.md" create mode 100644 ".Knowledge/stock-docs/\346\236\266\346\236\204\346\226\207\346\241\243\345\221\275\345\220\215\347\272\246\345\256\232_\347\273\210\347\250\277.md" create mode 100644 .Knowledge/topics/architecture-document-naming.md diff --git a/.Knowledge/index.md b/.Knowledge/index.md index d2b9f43..4e4d4c8 100644 --- a/.Knowledge/index.md +++ b/.Knowledge/index.md @@ -22,6 +22,7 @@ | 主题 | 路径 | 适用场景 | 关联文档(摘要) | | --- | --- | --- | --- | +| architecture-document-naming | `.Knowledge/topics/architecture-document-naming.md` | 架构初稿、终稿固定名称及业务主题无项目名前缀 | stock:[架构文档命名约定](stock-docs/架构文档命名约定_终稿.md) | | implement-tech-design | `.Knowledge/topics/f2s-implement-tech-design.md` | 按技术方案实现代码 | req:[技术方案](.Knowledge/req-docs/<技术方案>.md)(必填) | | f2s-doc-routing | `.Knowledge/topics/f2s-stock-docs-vs-req-docs.md` | stock-docs / req-docs 目录分工 | stock:[目录边界说明](.Knowledge/stock-docs/<目录边界说明>.md)(可选) | | fallback-triage | `.Knowledge/topics/f2s-fallback-triage.md` | 未命中或低置信度:分诊与澄清 | stock:[路由分诊说明](.Knowledge/stock-docs/<分诊说明>.md)(可选) | @@ -39,7 +40,7 @@ | 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` | +| flow2spec-core-package | `.Knowledge/topics/flow2spec-core-package.md` | Core/CLI 所有权、caret 兼容依赖、独立发布与更新 | stock:[兼容依赖与独立更新](stock-docs/Core兼容依赖与独立更新_终稿.md);开发者文档:`packages/core/README.md` | | flow2spec-qoder-plugin | `.Knowledge/topics/flow2spec-qoder-plugin.md` | Qoder 插件构建、`build:qoder-plugin`、zip 打包与插件市场分发 | 构建脚本:`scripts/build-qoder-plugin.js`;内容源:`packages/core/index.js`(resources API) | | kb-routing-summary | `.Knowledge/topics/kb-routing-summary.md` | 路由初筛 `taskToTopicRules[].summary` 字段与 matcher 4 字段(`includeAny`/`includeAll`/`excludeAny`/`excludeAll`)语义 | 引擎:`packages/core/lib/knowledgeEngine.js`、`packages/core/lib/routing.js`;创作规范:配置根 `rules/f2s-topic-authoring.*` | diff --git a/.Knowledge/manifest-routing.json b/.Knowledge/manifest-routing.json index 1ad8d22..71503b8 100644 --- a/.Knowledge/manifest-routing.json +++ b/.Knowledge/manifest-routing.json @@ -35,7 +35,8 @@ "flow2spec-doctor": ".Knowledge/topics/flow2spec-doctor.md", "flow2spec-core-package": ".Knowledge/topics/flow2spec-core-package.md", "flow2spec-qoder-plugin": ".Knowledge/topics/flow2spec-qoder-plugin.md", - "kb-routing-summary": ".Knowledge/topics/kb-routing-summary.md" + "kb-routing-summary": ".Knowledge/topics/kb-routing-summary.md", + "architecture-document-naming": ".Knowledge/topics/architecture-document-naming.md" }, "taskToTopicRules": [ { @@ -209,6 +210,15 @@ "kb-routing-summary" ], "summary": "初筛 summary 与 matcher 分片 4 字段(资格/否决门)语义" + }, + { + "task": "architecture-document-naming", + "matcherId": "m-architecture-document-naming", + "matcherPath": ".Knowledge/matchers/m-architecture-document-naming.json", + "topics": [ + "architecture-document-naming" + ], + "summary": "架构文档固定命名与业务主题去项目名前缀" } ], "projectRev": 3, @@ -311,6 +321,10 @@ "kb-routing-summary": { "primary": "feature", "confidence": "manual" + }, + "architecture-document-naming": { + "primary": "policy", + "confidence": "inferred" } } } diff --git a/.Knowledge/matchers/m-architecture-document-naming.json b/.Knowledge/matchers/m-architecture-document-naming.json new file mode 100644 index 0000000..970351c --- /dev/null +++ b/.Knowledge/matchers/m-architecture-document-naming.json @@ -0,0 +1,13 @@ +{ + "id": "m-architecture-document-naming", + "version": "1.0.0", + "schema": "flow2spec.matcher.v1", + "includeAny": [ + "项目架构初稿", + "项目架构终稿", + "架构文档命名", + "主题命名前缀", + "下游项目名前缀", + "project-architecture" + ] +} diff --git "a/.Knowledge/stock-docs/Core\345\205\274\345\256\271\344\276\235\350\265\226\344\270\216\347\213\254\347\253\213\346\233\264\346\226\260_\347\273\210\347\250\277.md" "b/.Knowledge/stock-docs/Core\345\205\274\345\256\271\344\276\235\350\265\226\344\270\216\347\213\254\347\253\213\346\233\264\346\226\260_\347\273\210\347\250\277.md" new file mode 100644 index 0000000..9cf84ab --- /dev/null +++ "b/.Knowledge/stock-docs/Core\345\205\274\345\256\271\344\276\235\350\265\226\344\270\216\347\213\254\347\253\213\346\233\264\346\226\260_\347\273\210\347\250\277.md" @@ -0,0 +1,12 @@ +# Core 兼容依赖与独立更新 + +CLI 通过 caret 范围消费 Core(当前 `^3.8.2`)。Core 的兼容修复和模板更新可独立发布,CLI 调用新 API 时按需提高范围下限并发版。 + +## 用户路径 + +- 保留 CLI:`flow2spec update --core` 刷新兼容 Core,并验证实际加载版本。 +- 更新 CLI:`flow2spec update --cli` 获取 latest CLI 及其兼容 Core。 +- 模板更新后运行 `flow2spec init `;按 `projectRev` / `pkgRev` 判断是否需要知识库升级。 +- 已安装包与项目锁文件不会随发版静默变化。旧 CLI 的精确依赖需要一次 CLI 更新才能解除。 + +实现位于 `scripts/workspace-version.js`、`packages/cli/cli.js`;规则摘要见 [Core 包](../topics/flow2spec-core-package.md),发布门禁见 [发布与部署](../../docs/发布与部署.md)。 diff --git "a/.Knowledge/stock-docs/\346\236\266\346\236\204\346\226\207\346\241\243\345\221\275\345\220\215\347\272\246\345\256\232_\347\273\210\347\250\277.md" "b/.Knowledge/stock-docs/\346\236\266\346\236\204\346\226\207\346\241\243\345\221\275\345\220\215\347\272\246\345\256\232_\347\273\210\347\250\277.md" new file mode 100644 index 0000000..1a27543 --- /dev/null +++ "b/.Knowledge/stock-docs/\346\236\266\346\236\204\346\226\207\346\241\243\345\221\275\345\220\215\347\272\246\345\256\232_\347\273\210\347\250\277.md" @@ -0,0 +1,18 @@ +# 架构文档命名约定 + +## 能力与入口 + +`f2s-doc-arch` 生成项目架构初稿,交接 `f2s-doc-final` 形成终稿,再由 `f2s-kb-build` 形成可路由主题。文档与主题采用跨项目一致的职责命名,项目身份在正文说明。 + +## 实现位置 + +- `packages/core/templates/{zh-CN,en-US}/skills/f2s-doc-arch/SKILL.md`:固定初稿文件名、标题与后续交接。 +- 同目录 `f2s-doc-final/SKILL.md`:整体架构专用命名优先于通用方案命名,覆盖 MD/PDF 输入。 +- 同目录 `f2s-kb-build/SKILL.md`:终稿输入门禁、架构主题命名及旧主题迁移确认。 +- `packages/core/templates/{zh-CN,en-US}/rules/f2s-topic-authoring.md`:主题按职责命名。 + +## 适用边界 + +输出目录可指定,架构文件名按语言固定;已有同名文档保留有效内容并增量更新。已有带项目名前缀的主题迁移须确认范围、冲突及引用,不自动删除旧文件。其他能力文档仍按能力命名。 + +具体命名见 [架构文档命名主题](../topics/architecture-document-naming.md)。变更从 Core 模板分发;当前仓配置根不随模板编辑自动更新。 diff --git a/.Knowledge/topics/architecture-document-naming.md b/.Knowledge/topics/architecture-document-naming.md new file mode 100644 index 0000000..39b4be5 --- /dev/null +++ b/.Knowledge/topics/architecture-document-naming.md @@ -0,0 +1,25 @@ +--- +id: architecture-document-naming +revision: 0 +summary: "架构文档固定命名与业务主题去项目名前缀" +primary: policy +confidence: inferred +sourceDoc: ".Knowledge/stock-docs/架构文档命名约定_终稿.md" +--- +# 架构文档与主题命名 + +## 执行约定 + +- 中文架构初稿:`项目架构初稿.md`,标题 `项目架构初稿`;终稿:`项目架构终稿.md`,标题 `项目架构终稿`。默认位于 `.Knowledge/stock-docs/`。 +- 英文对应 `project-architecture_draft.md` / `project-architecture_final.md`,标题 `Project Architecture Draft` / `Project Architecture Final`。 +- `f2s-doc-arch → f2s-doc-final → f2s-kb-build` 按上述名称交接;建库拒绝含 `初稿` 或 `_draft` 的输入,要求先完成终稿。 +- 架构概览 topic id 为 `project-architecture`,标题 `项目架构`(英文 `Project Architecture`)。业务 topic id、文件名、标题及派生 matcher id 按职责命名,项目名仅用于正文;保留既有 `f2s-*` 技能/规则标识。 +- 指定输出路径时保留目录,归一化架构文件名并告知实际路径;已有同名文档增量更新。带前缀旧主题迁移须确认范围和冲突后同步引用,不自动删除。 + +## 实现与边界 + +模板真源为 `packages/core/templates/{zh-CN,en-US}/skills/{f2s-doc-arch,f2s-doc-final,f2s-kb-build}/SKILL.md` 与 `rules/f2s-topic-authoring.md`;普通能力文档沿用通用方案命名。 + +## 详细资料 + +[架构文档命名约定](../stock-docs/架构文档命名约定_终稿.md) diff --git a/.Knowledge/topics/f2s-dev-workflow-constraints.md b/.Knowledge/topics/f2s-dev-workflow-constraints.md index 880ae40..ee3bccb 100644 --- a/.Knowledge/topics/f2s-dev-workflow-constraints.md +++ b/.Knowledge/topics/f2s-dev-workflow-constraints.md @@ -1,6 +1,6 @@ --- id: f2s-dev-workflow-constraints -revision: 3 +revision: 4 summary: "Flow2Spec 本仓的模板真源、配置根、版本发布与分发边界" primary: policy confidence: inferred @@ -29,9 +29,9 @@ Flow2Spec 本仓开发时判断应改 Core templates、配置根、本仓知识 ## 版本与发布 -- CLI、Core、Template、Protocol 独立版本;CLI 用 caret range 约束 Core。 +- CLI、Core、Template、Protocol 独立版本;CLI 用 caret range 约束 Core,版本检查要求当前 Core 在范围内。 - Core-only 兼容更新不升 Template Version,也不触发知识库升级。 -- `core-vX.Y.Z` 与 `cli-vX.Y.Z` 分别发布对应包。 +- `core-vX.Y.Z` 与 `cli-vX.Y.Z` 分别发布对应包;兼容 Core/Template 变更可独立发布,双包发布先 Core 后 CLI。 - 发布前执行版本、打包、tarball 安装与 README 门禁。 ## 边界与禁止项 diff --git a/.Knowledge/topics/flow2spec-core-package.md b/.Knowledge/topics/flow2spec-core-package.md index 6d6e0e1..258cf89 100644 --- a/.Knowledge/topics/flow2spec-core-package.md +++ b/.Knowledge/topics/flow2spec-core-package.md @@ -1,6 +1,6 @@ --- id: flow2spec-core-package -revision: 2 +revision: 3 summary: "Core/CLI 所有权、公共 API、独立版本、独立发布与更新兼容契约" primary: module confidence: inferred @@ -16,7 +16,7 @@ confidence: inferred - 根 private workspace 只负责开发编排,不作为 Core 运行时依赖声明位置。 - `packages/core/lib/` 承载核心实现;`packages/core/templates/{zh-CN,en-US}/` 是受 Git 管理的唯一模板真源并随 Core tarball 发布。 - 根 `lib/`、根 `templates/` 与 `scripts/sync-core-templates.js` 均不存在。 -- CLI 是薄壳,只通过 `@double-coding/flow2spec-core` 的公共 API 工作;当前运行时范围为 `^3.5.0`。 +- CLI 是薄壳,只通过 `@double-coding/flow2spec-core` 的公共 API 工作;当前运行时范围为 `^3.8.2`。 ## 公共 API @@ -34,22 +34,22 @@ Template Version packages/core/package.json.templateVersion Protocol Version packages/core/capabilities.json.protocolVersion ``` -- `version:set:cli` 只更新 CLI,Core pin 自动同步为当前 Core 版本。 -- `version:set:core` 更新 Core 并联动把 CLI 依赖 pin 到同版本(需配套 bump CLI patch 联动发布)。 +- `version:set:cli` 更新 CLI 并保留原 Core 范围;传 `--core-range ^x.y.z` 时显式调整兼容下限。 +- `version:set:core` 只更新 Core 与 lockfile,保留 CLI 版本和范围;不兼容时写盘前报错。 - `version:set:template` 更新 Core 元数据及中英文 `manifest-routing.json.version`。 -- `version:check` 强制校验 CLI pin 与 Core 版本精确一致,另校验 lockfile、双语 Template Version、Protocol Version 与 release tag。 +- `version:check` 校验当前 Core 满足 CLI caret 范围,另校验 lockfile、双语 Template Version、Protocol Version 与 release tag。 ## 发布与更新 -- `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 版本,失败时提示手动重装命令。 +- `core-vX.Y.Z` 只发布 Core;`cli-vX.Y.Z` 只发布 CLI。Core/Template 兼容更新可独立发布;双包更新时先 Core 后 CLI。 +- `flow2spec version` 展示 CLI/Core/Core Range/Template/Protocol。 +- `flow2spec update --check` 展示兼容 Core 目标;`--cli` 更新 latest CLI 及其兼容 Core;`--core` 保留当前 CLI 版本,刷新兼容 Core。更新后校验 CLI 实际解析的 Core,失败返回错误。 - Hook 与 `update.check()` 同时返回 Core 与 Template 状态。 -- CLI/Core 更新:`update --cli` 联动到位后幂等 init 刷新 Hook。 +- CLI/Core 更新:`update --cli` 到位后幂等 init 刷新 Hook。 - Template 更新:`update --cli` 后执行 init,再由 `projectRev` / `pkgRev` 决定是否进入完整知识库升级。 ## 边界 -- CLI 对 Core 为精确 pin;用户只需关心 CLI 一个包,`npm i -g @latest` 即得配套 Core。 +- CLI 对 Core 为 caret 兼容范围;已有安装需显式更新,项目锁文件仍固定解析版本。跨兼容范围需先升级支持该 Core 的 CLI。旧精确依赖 CLI 需先升级一次。 - `.Knowledge/manifest-routing.json.version` 表示 Template Version,不能与 Core Version 混用。 - 包安装验收使用两包 tarball,并验证 Core templates、类型声明、CLI README 与启动行为。 diff --git a/.claude/rules/repo-dev-workflow-constraints.md b/.claude/rules/repo-dev-workflow-constraints.md index 5f503b3..fc0ab2b 100644 --- a/.claude/rules/repo-dev-workflow-constraints.md +++ b/.claude/rules/repo-dev-workflow-constraints.md @@ -47,10 +47,10 @@ Template Version packages/core/package.json.templateVersion Protocol Version packages/core/capabilities.json.protocolVersion ``` -- CLI 对 Core 使用运行时依赖**精确 pin**(`packages/cli/package.json` 里写死 Core 版本号,不是 caret range);`scripts/workspace-version.js` 会自动同步。 -- Core 兼容修复/新增 API 也**必须 CLI 锁步 patch bump 并同发**——CLI 精确 pin 决定的:Core 一升,CLI 依赖字段就变,CLI 版本号必须跟着走(`version:set:core` 回执会提示 `remember to bump CLI patch — release in lockstep`)。仅改 CLI 自身代码(不动 Core)时才允许 CLI 单独发。 -- Rule、Skill、Hook、知识模板变化时升 Core,并显式执行 `version:set:template`(同样锁步 CLI patch)。 -- CLI 开始调用新版 Core API 时,升 CLI 并同步 Core 精确 pin。 +- CLI 对 Core 使用运行时 **caret 兼容范围**(如 `^3.8.2`);已安装依赖通过显式更新刷新,不会静默变化。 +- Core 在 CLI 兼容范围内的更新可独立发布,`version:set:core` 保留 CLI 版本和依赖范围;不兼容时在写盘前报错。 +- Rule、Skill、Hook、知识模板变化时升 Core,并显式执行 `version:set:template`;兼容变更无需为此升 CLI。 +- CLI 开始调用新版 Core API 时,升 CLI 并通过 `version:set:cli --core-range ^x.y.z` 调整兼容下限;不兼容升级须显式评估。 - Protocol Version 只在公共协议不兼容时调整。 - 根 private workspace version 不参与 npm 发布匹配。 @@ -67,7 +67,7 @@ npm run version:check - `core-vX.Y.Z` 只测试、打包并发布 `@double-coding/flow2spec-core`。 - `cli-vX.Y.Z` 只测试、打包并发布 `@double-coding/flow2spec`。 -- 同时发布时先 Core 后 CLI(CLI 精确 pin Core);禁止发布没有版本变化的包。 +- 同时发布时先 Core 后 CLI(先确保其兼容下限可安装);仅 Core 变化时只创建 Core Release,禁止发布没有版本变化的包。 - CLI README 与根 README 保持一致;Core README 独立维护。 - 发布前运行 `npm run version:check`、`npm run pack:check`、`node scripts/test-package-install.js`。 - `packages/core/templates/` 必须直接进入 Core tarball;不存在模板复制或漂移检查步骤。 @@ -79,8 +79,8 @@ npm run version:check 标准流程: 1. 在版本 PR(如 `chore/release-core-3.8.0`) merge 到 main 后,本地 `git checkout main && git pull`; -2. 打两个 tag 并推送:`git tag core-vX.Y.Z && git tag cli-vX.Y.Z && git push --tags`; -3. **到 GitHub Releases 页面**为每个 tag **创建 Release**(标题 `core-vX.Y.Z` / `cli-vX.Y.Z`);Release published 事件触发 `publish-npm.yml`; +2. 只为实际升版的包创建并推送对应 tag:`core-vX.Y.Z` 或 `cli-vX.Y.Z`; +3. 为对应 tag 创建 GitHub Release,触发 `publish-npm.yml`;双包发布时先等 Core 发布成功,再发布 CLI; 4. workflow 内已包含 `npm run version:check --tag`、`npm test`、`pack:check`、`npm publish --provenance`,失败即中止。 **禁止本地跑 `npm publish`**——即便临时需要 hotfix,也应通过 workflow 走。若确因意外走了本地发布(如本次 3.8.0 / 3.6.3),须在发版 PR / Release notes 中显式记录「本次发布无 provenance」。 @@ -88,7 +88,7 @@ npm run version:check ## 更新语义 - `flow2spec version` 展示 CLI、Core、Core Range、Template、Protocol。 -- `flow2spec update --check|--cli|--core` 分别检查、更新 CLI、更新兼容 Core。 +- `flow2spec update --check|--cli|--core` 分别检查、更新 latest CLI 及其兼容 Core、保持当前 CLI 版本只刷新兼容 Core,并验证实际生效版本。 - Hook 同时比较 Core Version 与 Template Version。 - Core 变化且 Template 不变:更新 Core 并执行一次幂等 init 刷新 Hook,不进入 `f2s-kb-upgrade`。 - Template 变化:更新 Core、执行 init,再按 `projectRev` / `pkgRev` 判断是否进入 `f2s-kb-upgrade`。 diff --git a/.codex/topics/repo-dev-workflow-constraints.md b/.codex/topics/repo-dev-workflow-constraints.md index 5f503b3..fc0ab2b 100644 --- a/.codex/topics/repo-dev-workflow-constraints.md +++ b/.codex/topics/repo-dev-workflow-constraints.md @@ -47,10 +47,10 @@ Template Version packages/core/package.json.templateVersion Protocol Version packages/core/capabilities.json.protocolVersion ``` -- CLI 对 Core 使用运行时依赖**精确 pin**(`packages/cli/package.json` 里写死 Core 版本号,不是 caret range);`scripts/workspace-version.js` 会自动同步。 -- Core 兼容修复/新增 API 也**必须 CLI 锁步 patch bump 并同发**——CLI 精确 pin 决定的:Core 一升,CLI 依赖字段就变,CLI 版本号必须跟着走(`version:set:core` 回执会提示 `remember to bump CLI patch — release in lockstep`)。仅改 CLI 自身代码(不动 Core)时才允许 CLI 单独发。 -- Rule、Skill、Hook、知识模板变化时升 Core,并显式执行 `version:set:template`(同样锁步 CLI patch)。 -- CLI 开始调用新版 Core API 时,升 CLI 并同步 Core 精确 pin。 +- CLI 对 Core 使用运行时 **caret 兼容范围**(如 `^3.8.2`);已安装依赖通过显式更新刷新,不会静默变化。 +- Core 在 CLI 兼容范围内的更新可独立发布,`version:set:core` 保留 CLI 版本和依赖范围;不兼容时在写盘前报错。 +- Rule、Skill、Hook、知识模板变化时升 Core,并显式执行 `version:set:template`;兼容变更无需为此升 CLI。 +- CLI 开始调用新版 Core API 时,升 CLI 并通过 `version:set:cli --core-range ^x.y.z` 调整兼容下限;不兼容升级须显式评估。 - Protocol Version 只在公共协议不兼容时调整。 - 根 private workspace version 不参与 npm 发布匹配。 @@ -67,7 +67,7 @@ npm run version:check - `core-vX.Y.Z` 只测试、打包并发布 `@double-coding/flow2spec-core`。 - `cli-vX.Y.Z` 只测试、打包并发布 `@double-coding/flow2spec`。 -- 同时发布时先 Core 后 CLI(CLI 精确 pin Core);禁止发布没有版本变化的包。 +- 同时发布时先 Core 后 CLI(先确保其兼容下限可安装);仅 Core 变化时只创建 Core Release,禁止发布没有版本变化的包。 - CLI README 与根 README 保持一致;Core README 独立维护。 - 发布前运行 `npm run version:check`、`npm run pack:check`、`node scripts/test-package-install.js`。 - `packages/core/templates/` 必须直接进入 Core tarball;不存在模板复制或漂移检查步骤。 @@ -79,8 +79,8 @@ npm run version:check 标准流程: 1. 在版本 PR(如 `chore/release-core-3.8.0`) merge 到 main 后,本地 `git checkout main && git pull`; -2. 打两个 tag 并推送:`git tag core-vX.Y.Z && git tag cli-vX.Y.Z && git push --tags`; -3. **到 GitHub Releases 页面**为每个 tag **创建 Release**(标题 `core-vX.Y.Z` / `cli-vX.Y.Z`);Release published 事件触发 `publish-npm.yml`; +2. 只为实际升版的包创建并推送对应 tag:`core-vX.Y.Z` 或 `cli-vX.Y.Z`; +3. 为对应 tag 创建 GitHub Release,触发 `publish-npm.yml`;双包发布时先等 Core 发布成功,再发布 CLI; 4. workflow 内已包含 `npm run version:check --tag`、`npm test`、`pack:check`、`npm publish --provenance`,失败即中止。 **禁止本地跑 `npm publish`**——即便临时需要 hotfix,也应通过 workflow 走。若确因意外走了本地发布(如本次 3.8.0 / 3.6.3),须在发版 PR / Release notes 中显式记录「本次发布无 provenance」。 @@ -88,7 +88,7 @@ npm run version:check ## 更新语义 - `flow2spec version` 展示 CLI、Core、Core Range、Template、Protocol。 -- `flow2spec update --check|--cli|--core` 分别检查、更新 CLI、更新兼容 Core。 +- `flow2spec update --check|--cli|--core` 分别检查、更新 latest CLI 及其兼容 Core、保持当前 CLI 版本只刷新兼容 Core,并验证实际生效版本。 - Hook 同时比较 Core Version 与 Template Version。 - Core 变化且 Template 不变:更新 Core 并执行一次幂等 init 刷新 Hook,不进入 `f2s-kb-upgrade`。 - Template 变化:更新 Core、执行 init,再按 `projectRev` / `pkgRev` 判断是否进入 `f2s-kb-upgrade`。 diff --git a/.cursor/rules/repo-dev-workflow-constraints.mdc b/.cursor/rules/repo-dev-workflow-constraints.mdc index 5f503b3..fc0ab2b 100644 --- a/.cursor/rules/repo-dev-workflow-constraints.mdc +++ b/.cursor/rules/repo-dev-workflow-constraints.mdc @@ -47,10 +47,10 @@ Template Version packages/core/package.json.templateVersion Protocol Version packages/core/capabilities.json.protocolVersion ``` -- CLI 对 Core 使用运行时依赖**精确 pin**(`packages/cli/package.json` 里写死 Core 版本号,不是 caret range);`scripts/workspace-version.js` 会自动同步。 -- Core 兼容修复/新增 API 也**必须 CLI 锁步 patch bump 并同发**——CLI 精确 pin 决定的:Core 一升,CLI 依赖字段就变,CLI 版本号必须跟着走(`version:set:core` 回执会提示 `remember to bump CLI patch — release in lockstep`)。仅改 CLI 自身代码(不动 Core)时才允许 CLI 单独发。 -- Rule、Skill、Hook、知识模板变化时升 Core,并显式执行 `version:set:template`(同样锁步 CLI patch)。 -- CLI 开始调用新版 Core API 时,升 CLI 并同步 Core 精确 pin。 +- CLI 对 Core 使用运行时 **caret 兼容范围**(如 `^3.8.2`);已安装依赖通过显式更新刷新,不会静默变化。 +- Core 在 CLI 兼容范围内的更新可独立发布,`version:set:core` 保留 CLI 版本和依赖范围;不兼容时在写盘前报错。 +- Rule、Skill、Hook、知识模板变化时升 Core,并显式执行 `version:set:template`;兼容变更无需为此升 CLI。 +- CLI 开始调用新版 Core API 时,升 CLI 并通过 `version:set:cli --core-range ^x.y.z` 调整兼容下限;不兼容升级须显式评估。 - Protocol Version 只在公共协议不兼容时调整。 - 根 private workspace version 不参与 npm 发布匹配。 @@ -67,7 +67,7 @@ npm run version:check - `core-vX.Y.Z` 只测试、打包并发布 `@double-coding/flow2spec-core`。 - `cli-vX.Y.Z` 只测试、打包并发布 `@double-coding/flow2spec`。 -- 同时发布时先 Core 后 CLI(CLI 精确 pin Core);禁止发布没有版本变化的包。 +- 同时发布时先 Core 后 CLI(先确保其兼容下限可安装);仅 Core 变化时只创建 Core Release,禁止发布没有版本变化的包。 - CLI README 与根 README 保持一致;Core README 独立维护。 - 发布前运行 `npm run version:check`、`npm run pack:check`、`node scripts/test-package-install.js`。 - `packages/core/templates/` 必须直接进入 Core tarball;不存在模板复制或漂移检查步骤。 @@ -79,8 +79,8 @@ npm run version:check 标准流程: 1. 在版本 PR(如 `chore/release-core-3.8.0`) merge 到 main 后,本地 `git checkout main && git pull`; -2. 打两个 tag 并推送:`git tag core-vX.Y.Z && git tag cli-vX.Y.Z && git push --tags`; -3. **到 GitHub Releases 页面**为每个 tag **创建 Release**(标题 `core-vX.Y.Z` / `cli-vX.Y.Z`);Release published 事件触发 `publish-npm.yml`; +2. 只为实际升版的包创建并推送对应 tag:`core-vX.Y.Z` 或 `cli-vX.Y.Z`; +3. 为对应 tag 创建 GitHub Release,触发 `publish-npm.yml`;双包发布时先等 Core 发布成功,再发布 CLI; 4. workflow 内已包含 `npm run version:check --tag`、`npm test`、`pack:check`、`npm publish --provenance`,失败即中止。 **禁止本地跑 `npm publish`**——即便临时需要 hotfix,也应通过 workflow 走。若确因意外走了本地发布(如本次 3.8.0 / 3.6.3),须在发版 PR / Release notes 中显式记录「本次发布无 provenance」。 @@ -88,7 +88,7 @@ npm run version:check ## 更新语义 - `flow2spec version` 展示 CLI、Core、Core Range、Template、Protocol。 -- `flow2spec update --check|--cli|--core` 分别检查、更新 CLI、更新兼容 Core。 +- `flow2spec update --check|--cli|--core` 分别检查、更新 latest CLI 及其兼容 Core、保持当前 CLI 版本只刷新兼容 Core,并验证实际生效版本。 - Hook 同时比较 Core Version 与 Template Version。 - Core 变化且 Template 不变:更新 Core 并执行一次幂等 init 刷新 Hook,不进入 `f2s-kb-upgrade`。 - Template 变化:更新 Core、执行 init,再按 `projectRev` / `pkgRev` 判断是否进入 `f2s-kb-upgrade`。 diff --git a/docs/en/upgrade-guide.md b/docs/en/upgrade-guide.md index dca7a53..37f6bae 100644 --- a/docs/en/upgrade-guide.md +++ b/docs/en/upgrade-guide.md @@ -1,12 +1,22 @@ -# Flow2Spec Upgrade Guide (CLI 3.6.2 / Core 3.7.2 / Template 3.6.2) +# Flow2Spec Upgrade Guide + +## Current update policy (CLI 3.6.5 onward) + +The CLI declares a caret Core range such as `^3.8.2`, allowing compatible Core releases independently. Existing CLI dependency declarations stay unchanged until users update the CLI once. + +- `flow2spec update --core` retains the current CLI version, refreshes its compatible Core, and verifies the actually resolved version. +- `flow2spec update --cli` refreshes the latest CLI and its compatible Core; use it to enter a new compatibility range. +- Installed dependencies do not change silently; lockfiles retain resolved versions. After template updates, run `flow2spec init ` and compare `projectRev` / `pkgRev` for knowledge upgrade. + +## Historical migration example (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. ## Version matrix -| Dimension | Latest | Notes | +| Dimension | Example version | Notes | | --- | --- | --- | -| CLI (`@double-coding/flow2spec`) | 3.6.2 | the only package you need to care about; pins its exact Core, released in lockstep | +| CLI (`@double-coding/flow2spec`) | 3.6.2 | historical release; see the current update policy above | | 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 +32,7 @@ npm install -g @double-coding/flow2spec flow2spec init # multi-select, follow the prompts ``` -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. +Installing the CLI automatically brings Core within its dependency range; no separate install is needed. Init enables the bundled knowledge templates and summary-based first-pass recall. ### Option 2: Qoder plugin (self-built install) @@ -49,7 +59,7 @@ Template 3.5.0 → 3.6.x **includes topic-layer changes** (projectRev 2 → 3), ### Step 1: Update the package -The CLI and Core release in lockstep (the CLI pins its exact Core), so one command updates everything: +Update to the latest CLI and its compatible Core: ```bash npm install -g @double-coding/flow2spec@latest @@ -90,11 +100,11 @@ 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: Does a Core release require a CLI release?** +Not within the compatibility range. CLI 3.6.5 onward uses a caret range; run `flow2spec update --core` for compatible Core releases, or update to a supporting CLI before entering a new compatibility range. **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. +A known defect in CLI ≤ 3.6.1: that command installed Core globally at the top level while the CLI loads a nested copy. Reinstall the CLI once (`npm uninstall -g @double-coding/flow2spec && npm install -g @double-coding/flow2spec@latest`). Current update commands refresh the CLI dependency tree, verify the effective Core version, and fail if verification does not pass. **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. diff --git a/docs/en/usage-guide.md b/docs/en/usage-guide.md index a9c8a55..9439b8f 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 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`. +`flow2spec version` shows CLI, Core, Core Range, Template, and Protocol. `flow2spec update --check` checks updates; `flow2spec update --cli` refreshes the latest CLI and its compatible Core, while `flow2spec update --core` keeps the current CLI version and refreshes Core within its compatibility range. Compatible Core releases are independent; installed dependencies do not update silently and project lockfiles still fix resolved versions. 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 a459411..8a2d9d6 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 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 自检不打扰当前流程。 +`flow2spec version` 展示 CLI、Core、Core Range、Template、Protocol;`flow2spec update --check` 检查更新,`flow2spec update --cli` 更新 latest CLI 及其兼容 Core,`flow2spec update --core` 保持当前 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 35e39d1..b216f24 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,12 +1,22 @@ -# Flow2Spec 升级指南(CLI 3.6.2 / Core 3.7.2 / Template 3.6.2) +# Flow2Spec 升级指南 + +## 当前更新策略(CLI 3.6.5 起) + +CLI 对 Core 使用 caret 兼容范围(如 `^3.8.2`),兼容 Core 更新可以独立发布。旧 CLI 的依赖声明不会被新 Core 改写,需先更新一次 CLI。 + +- `flow2spec update --core`:保留当前 CLI 版本,刷新其兼容范围内的 Core,并验证实际加载版本。 +- `flow2spec update --cli`:更新 latest CLI,并刷新它兼容范围内的 Core;用于升级 CLI 或进入新的兼容范围。 +- 安装后的依赖不会自动变化;锁文件仍保留解析版本。更新模板后执行 `flow2spec init `,再按 `projectRev` / `pkgRev` 判断知识库升级。 + +## 历史迁移案例(CLI 3.6.2 / Core 3.7.2 / Template 3.6.2) > 本次版本核心能力:**路由初筛 summary 召回锚**。`manifest-routing.json` 的每条路由规则新增 `summary` 语义摘要(由 topic frontmatter 自动同步),大幅提升自然问法(如「原型放在哪」「流程图在哪个目录」)的知识库命中率;`kb check` 同步新增 summary 质量校验。 ## 版本对照 -| 维度 | 最新版本 | 说明 | +| 维度 | 案例版本 | 说明 | | --- | --- | --- | -| CLI(`@double-coding/flow2spec`) | 3.6.2 | 用户唯一需要关心的包;精确锁定配套 Core,两包联动发布 | +| CLI(`@double-coding/flow2spec`) | 3.6.2 | 历史发布版本;现行更新策略见上文 | | 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 +32,7 @@ npm install -g @double-coding/flow2spec flow2spec init # 可多选,按提示回答 ``` -安装 CLI 会自动带上精确锁定的配套 Core(3.7.2),无需单独安装。init 完成后即为最新知识库模板,天然具备 summary 初筛能力,无需额外操作。 +安装 CLI 会自动带上其依赖范围内的 Core,无需单独安装。init 完成后可使用随包知识库模板和 summary 初筛能力。 ### 方式二:Qoder 插件(自行构建安装) @@ -49,7 +59,7 @@ Qoder 插件市场为官方插件,Flow2Spec 插件需自行构建后本地安 ### 第 1 步:更新包 -CLI 与 Core 联动发布(CLI 精确锁定配套 Core),一条命令整体更新: +更新到最新 CLI 及其兼容 Core: ```bash npm install -g @double-coding/flow2spec@latest @@ -90,11 +100,11 @@ 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:Core 更新需要 CLI 同时发版吗?** +兼容范围内不需要。CLI 3.6.5 起使用 caret 范围;已有用户运行 `flow2spec update --core` 获取兼容 Core,跨兼容范围则先更新支持它的 CLI。 **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` 已改为联动整体更新并验证实际生效版本,不再假报成功。 +CLI ≤ 3.6.1 的已知缺陷:那条命令把 Core 装到了全局顶级位置,CLI 实际加载的是包内嵌套副本。修复方式:重装一次 CLI(`npm uninstall -g @double-coding/flow2spec && npm install -g @double-coding/flow2spec@latest`)。现行更新命令刷新 CLI 的依赖树并校验实际生效版本,失败时返回错误。 **Q:升级会覆盖我已经写好的知识库吗?** 不会。`f2s-kb-upgrade` 代跑的 init 是增量对齐,只更新模板承载的路由结构与规则;`stock-docs` / `req-docs` / topic 正文的业务内容不受影响。只有明确要求「覆盖重置」时才会带 `--reset-knowledge`。 diff --git "a/docs/\345\217\221\345\270\203\344\270\216\351\203\250\347\275\262.md" "b/docs/\345\217\221\345\270\203\344\270\216\351\203\250\347\275\262.md" index 5d300e1..109e0cd 100644 --- "a/docs/\345\217\221\345\270\203\344\270\216\351\203\250\347\275\262.md" +++ "b/docs/\345\217\221\345\270\203\344\270\216\351\203\250\347\275\262.md" @@ -20,12 +20,12 @@ Flow2Spec 使用 GitHub Actions 分别处理持续集成、网站部署和 npm `.github/workflows/publish-npm.yml` 只在 GitHub Release 发布时触发。发布前依次校验: 1. Release 对应的提交属于 `main`; -2. 根 workspace、Core、CLI 三处版本一致,且 CLI 对 Core 的依赖版本一致; -3. Tag(`v3.3.0` 或 `V3.3.0`)与 workspace 版本一致; +2. CLI 的 caret 依赖范围包含当前 Core,lockfile、双语 Template Version 与包元数据一致;根 private workspace 版本独立; +3. Tag(`core-vX.Y.Z` 或 `cli-vX.Y.Z`)与对应包版本一致; 4. CLI、Core API 和安装回归测试通过; 5. 两个包的 `npm pack --dry-run` 通过。 -全部通过后,工作流使用 npm Trusted Publishing(OIDC)执行带 provenance 的公开发布,不需要在 GitHub 保存长期 `NPM_TOKEN`。发布顺序固定为先 `@double-coding/flow2spec-core`,再 `@double-coding/flow2spec`,确保 CLI 安装时可以解析同版本 Core。 +全部通过后,工作流使用 npm Trusted Publishing(OIDC)执行带 provenance 的公开发布,不需要在 GitHub 保存长期 `NPM_TOKEN`。每个 Release 仅发布对应包。Core 兼容更新可独立发布;双包更新时先发布 Core 并确认成功,再发布 CLI,确保兼容下限可安装。 首次启用时,在 npmjs.com 的 `@double-coding/flow2spec-core` 和 `@double-coding/flow2spec` 两个包设置中分别添加 GitHub Actions Trusted Publisher: @@ -38,11 +38,11 @@ Flow2Spec 使用 GitHub Actions 分别处理持续集成、网站部署和 npm 正式发布顺序: -1. 在分支中同步修改根 `package.json`、`packages/core/package.json` 和 `packages/cli/package.json` 版本,并完成 PR; +1. 在分支中用 `version:set:core` / `version:set:cli` 更新实际变更包的版本;模板变化另执行 `version:set:template`,并完成 PR; 2. PR 合并到 `main`; -3. 在合并提交上创建匹配版本的 Git Tag(可运行 `npm run tag:version`); +3. 在合并提交上只为升版包创建匹配版本的 Git Tag(可运行 `npm run tag:version:core` 或 `npm run tag:version:cli`); 4. 基于该 Tag 创建并发布 GitHub Release; -5. 在 Actions 中确认 Core 与 CLI 两个发布步骤均成功。 +5. 在 Actions 中确认各个待发布包的发布工作流成功。 根目录 workspace 仅用于统一开发、测试和版本校验,不会发布到 npm。普通用户继续安装 `@double-coding/flow2spec`;原生开发工具插件直接依赖 `@double-coding/flow2spec-core`。 diff --git a/package-lock.json b/package-lock.json index 76ac4bf..64e0c1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,10 +25,10 @@ }, "packages/cli": { "name": "@double-coding/flow2spec", - "version": "3.6.4", + "version": "3.6.5", "license": "ISC", "dependencies": { - "@double-coding/flow2spec-core": "3.8.1" + "@double-coding/flow2spec-core": "^3.8.2" }, "bin": { "flow2spec": "cli.js" @@ -39,7 +39,7 @@ }, "packages/core": { "name": "@double-coding/flow2spec-core", - "version": "3.8.1", + "version": "3.8.2", "license": "ISC", "engines": { "node": ">=16" diff --git a/packages/cli/README.md b/packages/cli/README.md index de3ccbf..e21954e 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -1,20 +1,21 @@ # Flow2Spec

- Flow2Spec routes a natural language coding request into compact project facts before code edits + Flow2Spec 将自然语言编码需求路由到紧凑项目事实后再修改代码

- Give each initialized AI coding client the project facts it needs before editing. + 让每个已初始化的 AI 编程客户端在动手改代码前,先读到正确的项目事实。

- 中文 · - Live demo · - Introduction · - Usage guide · - Commands · - Roadmap + English · + 在线演示 · + 产品演示 PPT · + 基础介绍 · + 使用说明 · + 命令说明 · + 路线图

@@ -23,143 +24,150 @@ license

-Flow2Spec adds a spec-driven workflow layer to AI coding agents. It creates a small, routable `.Knowledge/` knowledge base, installs agent-specific `f2s-*` skills, and keeps optional local task state separate from product knowledge. A new session can load the facts relevant to a request instead of rediscovering the repository. +Flow2Spec 是给 AI 编码工具使用的 Spec-driven 工作流层。它会在项目里建立小而可路由的 `.Knowledge/` 知识库,安装面向 agent 的 `f2s-*` 技能,并把可选的本地任务状态和产品知识分开保存。新的会话可以按需求加载相关事实,而不是重新翻完整个仓库。 ```bash -# Recommended: install globally, then initialize -# (keeps the `flow2spec` command available for kb maintenance and upgrades) +# 推荐:全局安装后初始化(保留 flow2spec 命令,便于后续知识库维护与升级) npm install -g @double-coding/flow2spec flow2spec init -# One-off trial without installing (always resolves the latest version): +# 免安装一次性体验(始终解析最新版): # npx @double-coding/flow2spec@latest init -# Native DeepSeek Harness plugin: +# DeepSeek Harness 原生插件: # https://github.com/double-coding-lab/Flow2Spec-DeepSeek-Harness -# Project-level adapter without the plugin: +# 未装插件时的项目级适配: flow2spec init dsh ``` -## Why it exists +## 为什么需要它 -Without a maintained, routable project memory, an agent has to rediscover the same constraints on every request. Flow2Spec keeps those facts in compact topic shards and routes each request to the topics it needs. +如果项目记忆不能维护、不能路由,agent 每次处理需求都要重新确认同一批约束。Flow2Spec 把这些事实整理成紧凑的 topic 分片,再把需求路由到需要读取的主题。 -| Without Flow2Spec | With Flow2Spec | +| 没有 Flow2Spec | 有 Flow2Spec | | --- | --- | -| “Which module owns this table?” | `[matcher hit] m-product-review-template-library` | -| “Is batchReScore sync or async?” | `[loading deps] 4 topics · ~300 lines` | -| “Is there a lock? What is the idempotency key?” | `Redis lock ... TTL 10 min` | -| Agent searches 416 APIs, 796 files, and 4.7 MB of source before editing. | Agent reads the verified constraints first and opens the relevant files. | +| “这个模块的表在哪?” | `[matcher 命中] m-product-review-template-library` | +| “batchReScore 是同步还是异步?” | `[加载依赖] 4 个 topic · 约 300 行` | +| “有没有锁?幂等键是什么?” | `Redis lock ... TTL 10 min` | +| Agent 在修改前搜索 416 个接口、796 份文件、4.7 MB 源码。 | Agent 先读取已验证约束,再打开相关文件。 | -Flow2Spec does not add documentation for its own sake. It keeps a small, machine-readable knowledge layer alongside the code, and lets the same skills update it when verified facts change. +Flow2Spec 不是为了增加文档数量。它把项目事实保存在一层小而准的机读知识里,并让同一套技能在事实变化后同步更新它。 -## What you get +## 它提供什么 -| Layer | What it does | Files | +| 层 | 作用 | 文件 | | --- | --- | --- | -| Knowledge routing | Maps a request to the few topics the agent needs to read. | `.Knowledge/manifest-routing.json`, `.Knowledge/matchers/*.json` | -| Topic shards | Stores project facts such as APIs, limits, locks, data rules, and workflows. | `.Knowledge/topics/*.md` | -| Agent entrypoints | Installs rules and skills for the selected AI coding clients. | client configuration roots, `.dsh/`, `AGENTS.md` | -| Skill workflows | Clarifies requirements, writes specs, implements, fixes, syncs knowledge, and commits. | `f2s-*` skills | -| Team collaboration | Keeps each developer's task state local while merging reviewed knowledge through structured deltas and topic revisions. | `.task//`, `.Knowledge/` | +| 知识路由 | 把一次需求映射到 agent 需要读取的少量 topics。 | `.Knowledge/manifest-routing.json`, `.Knowledge/matchers/*.json` | +| 主题分片 | 保存 API、上限、锁、数据规则、业务流程等项目事实。 | `.Knowledge/topics/*.md` | +| Agent 入口 | 为选中的 AI 编程客户端安装规则和技能。 | 客户端配置根、`.dsh/`、`AGENTS.md` | +| 技能工作流 | 澄清需求、编写方案、实现、修复、同步知识、提交。 | `f2s-*` skills | +| 团队协作 | 每个人的任务现场留在本地,确认后的知识通过结构化 delta 与 topic revision 合入共享仓库。 | `.task//`, `.Knowledge/` | -## Built for shared repositories +## 多人共用一份知识库 -Flow2Spec separates collaboration state by ownership. Checklists, session context, and user todos stay under each developer's local `TASK_ROOT` and do not enter Git. Confirmed project knowledge remains shared in `.Knowledge/`. +Flow2Spec 按所有权拆分协作状态。checklist、会话上下文和用户代办保存在每名开发者自己的 `TASK_ROOT`,默认不进 Git;已经确认的项目知识统一进入 `.Knowledge/`。 -Knowledge-producing skills write a structured `kb-delta.json` instead of editing topic files directly. Before apply, the CLI compares the delta's `baseRevisions` with the topic revisions on disk. Different topics can merge independently; concurrent changes to the same topic stop for a semantic review after the latest branch state is pulled. +知识类技能先生成结构化 `kb-delta.json`,不直接改 topic。真正 apply 前,CLI 会比较 delta 的 `baseRevisions` 与磁盘上的 topic revision。修改不同 topic 可以分别合入;两个人同时修改同一 topic 时,后合入的一方需要先拉取最新版本、重读语义,再改写 delta。 -Read the full model in [Team Collaboration](./docs/en/team-collaboration.md). +完整流程见 [团队协作](./docs/团队协作.md)。 -## First use +## 第一次怎么用 -After initialization, you do not need to document the whole project upfront. Start with the change you actually need. The agent reads the relevant code and existing docs while it works, then saves confirmed project facts back into the knowledge base. +初始化以后,不需要先把整个项目文档补齐。更推荐的方式是从当前要处理的需求开始,让 Agent 在开发过程中读取真实代码和已有文档,再把确认过的项目事实沉淀下来。 -For an existing project, you can ask the agent to draft the project structure first: +如果这是一个已有项目,可以先让 Agent 整理一次项目结构: ```text /f2s-doc-arch ``` -This helps the agent understand the main directories, module boundaries, and existing conventions. It is optional. For a small change, you can start directly from the request. +这一步会帮助 Agent 理解主要目录、模块边界和已有约定。它不是必选步骤;如果只是处理一个很小的修改,也可以直接从需求开始。 -## Daily development +跑完 `/f2s-doc-arch` 之后,如果你已经知道**当前需求会动到哪些模块**,可以顺手让 Agent 把这几个模块的存量说明也提前入库,例如: -Most of the time, describe the task in natural language: +```text +/f2s-kb-add src/services/product-review src/functions/batch-rescore +``` + +这样第一次真正开发前,`.Knowledge` 里就已经有:项目全景(`f2s-doc-arch` 出的架构文档)+ 命中模块的内部约束(`f2s-kb-add` 生成的 topic + matcher)。Agent 在后续对话里读到的是聚焦的项目事实,而不是零散的源码。 + +## 日常开发怎么用 + +大多数时候,直接用自然语言说明要处理的事情即可: ```text -Add batch recalculation. It should retry failed items and avoid running the same batch twice. +帮我新增一个批量重算功能,需要支持失败重试,并且不要重复执行同一批任务。 ``` -The agent should look for relevant project knowledge first. If something is missing, it should explain the gap, then read the necessary code or ask you a follow-up question. Confirmed facts such as APIs, limits, locks, data rules, and workflows can be synced back into `.Knowledge`. +Agent 会先根据规则查找相关项目知识。如果信息不够,它应该先说明缺口,再读取必要代码或反问你。实现过程中确认下来的接口、限制、锁、数据规则等事实,会在合适的时候同步回 `.Knowledge`。 -A larger change usually follows this path: +较大的需求通常按这个顺序推进: ```text -describe the requirement - → agent fills in missing details - → generate or review the technical spec - → implement / fix - → sync verified project facts - → check knowledge coverage before commit +说明需求 + → Agent 补齐缺失信息 + → 生成或复核技术方案 + → 实现 / 修复 + → 同步已验证的项目事实 + → 提交前检查知识库覆盖情况 ``` -If you already know which workflow you want, use one of the explicit entrypoints below. +如果你已经知道要走哪个流程,可以直接输入下面的显式入口。 -## How the knowledge base grows +## 知识库会怎么增长 -Flow2Spec's knowledge base is not meant to be finished in one pass. It grows with development: +Flow2Spec 的知识库不是一次性整理完的。它会随着开发逐步变完整: -1. `init` creates the base skeleton. -2. The first time a module matters, the agent reads the relevant code and docs. -3. Confirmed facts from the development process become routable topics. -4. Later similar requests can hit those topics directly instead of searching the whole repository again. +1. `init` 先生成基础骨架。 +2. 第一次处理某个模块时,Agent 读取相关代码和文档。 +3. 开发过程中确认下来的事实,会被整理成可路由的主题。 +4. 后续再处理相似需求时,Agent 可以直接命中这些主题,不需要重新翻完整个仓库。 -The directories can be read this way: +目录可以简单理解为: -- `req-docs/`: technical specs and implementation plans for concrete changes. -- `stock-docs/`: stable project background, architecture notes, and imported source material. -- `topics/`: compact facts the agent should actually load. -- `matchers/`: rules that route a user request to the right topics. +- `req-docs/`:某次具体变更的技术方案和实现计划。 +- `stock-docs/`:稳定的项目背景、架构说明和导入材料。 +- `topics/`:Agent 实际会读取的精简事实。 +- `matchers/`:把用户需求路由到对应 topics 的匹配规则。 -## Explicit skill entrypoints +## 显式技能入口 -Natural-language requests can select these workflows automatically when intent recognition is enabled. Use the entrypoints below when you want to choose one directly. +开启意图识别后,自然语言需求可以自动选择这些工作流。下面这些入口适合在你想明确指定流程时使用。 -| Command | Purpose | +| 命令 | 用途 | | --- | --- | -| `/f2s-req-clarify` | Clarify missing requirements until the change is unambiguous. | -| `/f2s-req-tech` | Turn confirmed requirements into an implementation-ready technical proposal. | -| `/f2s-kb-feat` | Add a capability and update project knowledge. | -| `/f2s-kb-fix` | Fix behavior and correct the matching knowledge. | -| `/f2s-kb-sync` | Sync already implemented facts into `.Knowledge/`. | -| `/f2s-kb-add ` | Import an existing module or document set. | -| `/f2s-git-commit` | Check changed files and knowledge coverage before committing. | +| `/f2s-req-clarify` | 补齐缺失信息,直到变更目标没有明显歧义。 | +| `/f2s-req-tech` | 把已确认的需求整理成可实现的技术方案。 | +| `/f2s-kb-feat` | 新增能力,并同步项目知识。 | +| `/f2s-kb-fix` | 修复行为,并更正对应知识。 | +| `/f2s-kb-sync` | 把已实现事实同步进 `.Knowledge/`。 | +| `/f2s-kb-add ` | 导入已有模块或文档集。 | +| `/f2s-git-commit` | 提交前检查变更文件和知识覆盖情况。 | -Full references: +完整参考: -- [Usage guide](./docs/en/usage-guide.md) -- [Commands reference](./docs/en/commands-reference.md) -- [Directory conventions](./docs/en/directory-conventions.md) -- [Architecture and principles](./docs/en/architecture.md) -- [Team collaboration](./docs/en/team-collaboration.md) -- [Design principles](./docs/en/design-principles.md) -- [Project milestones](./docs/en/milestones.md) +- [使用说明](./docs/使用说明.md) +- [命令说明](./docs/命令说明.md) +- [目录与路径约定](./docs/目录与路径约定.md) +- [体系与原理](./docs/体系与原理.md) +- [团队协作](./docs/团队协作.md) +- [设计说明](./docs/设计说明.md) +- [项目里程碑](./docs/项目里程碑.md) -## When not to use it +## 什么时候不适合 -Flow2Spec is useful when context drift is expensive. It may be unnecessary for: +Flow2Spec 适合上下文漂移成本较高的项目。下面这些场景可能不需要它: -- throwaway one-off scripts; -- tiny solo projects where one `CLAUDE.md` is enough; -- teams that will not keep `.Knowledge/` aligned with the code. +- 写完就删的一次性脚本; +- 很小的个人项目,一份 `CLAUDE.md` 已经够用; +- 团队不愿意让 `.Knowledge/` 和代码保持同步。 -## Learn more +## 继续了解 -- [Flow2Spec Introduction](./docs/en/Flow2Spec-Introduction.md) — product narrative, diagrams, and comparison with ordinary project memory. -- [Flow2Spec 基础介绍](./docs/Flow2Spec基础介绍.md) — Chinese long-form introduction. -- [Product website](https://double-coding-lab.github.io/Flow2Spec/en/) — a website-style guide to Flow2Spec's core capabilities and workflow. +- [Flow2Spec 基础介绍](./docs/Flow2Spec基础介绍.md) — 产品叙事、配图、与普通项目记忆的区别。 +- [Flow2Spec Introduction](./docs/en/Flow2Spec-Introduction.md) — 英文长文介绍。 +- [在线产品介绍](https://double-coding-lab.github.io/Flow2Spec) — 网站式产品导览,快速了解核心能力与使用路径。 -## License +## 协议 [MIT](./LICENSE) diff --git a/packages/cli/cli.js b/packages/cli/cli.js index da01ad8..1e154c6 100755 --- a/packages/cli/cli.js +++ b/packages/cli/cli.js @@ -64,13 +64,16 @@ function runCommandSync(command, commandArgs, options = {}) { }); } -function queryLatestCoreMetadata() { +function queryLatestCoreMetadata(range = coreRange) { const output = runCommandSync( "npm", - ["view", CORE_PACKAGE, "version", "templateVersion", "--json", "--registry=https://registry.npmjs.org"], + ["view", `${CORE_PACKAGE}@${range}`, "version", "templateVersion", "--json", "--registry=https://registry.npmjs.org"], { encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] }, ); - const metadata = JSON.parse(output); + const candidates = [].concat(JSON.parse(output)).filter((item) => /^\d+\.\d+\.\d+$/.test(typeof item === "string" ? item : item.version)); + candidates.sort((a, b) => compareVersions(typeof a === "string" ? a : a.version, typeof b === "string" ? b : b.version)); + const metadata = candidates.pop(); + if (!metadata) throw new Error(`没有找到兼容 ${range} 的稳定 Core 版本`); return { version: typeof metadata === "string" ? metadata : metadata.version, templateVersion: typeof metadata === "string" @@ -317,8 +320,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 与配套 Core 联动) - flow2spec update --core 同 --cli:Core 随 CLI 联动发布,执行整体更新 + flow2spec update --cli 更新 CLI,并刷新其兼容范围内的 Core + flow2spec update --core 保持当前 CLI 版本,刷新兼容范围内的 Core flow2spec --help 显示本说明 agent(可多个,空格分隔;省略时交互选择): @@ -358,7 +361,7 @@ if (sub === "version" || sub === "--version" || sub === "-v") { console.log([ `Flow2Spec CLI: ${pkg.version}`, `Flow2Spec Core: ${coreVersions.coreVersion}`, - `Core Pinned: ${coreRange}`, + `Core Range: ${coreRange}`, `Template Version: ${coreVersions.templateVersion}`, `Protocol Version: ${getCapabilities().protocolVersion}`, ].join("\n")); @@ -380,61 +383,74 @@ if (sub === "update") { timeout: 5000, stdio: ["ignore", "pipe", "ignore"], }).trim(); - const latestCore = queryLatestCoreMetadata(); + const targetCli = mode === "--cli" ? latestCli : pkg.version; + const targetRange = mode === "--cli" ? JSON.parse(runCommandSync("npm", ["view", `${pkg.name}@${targetCli}`, "dependencies", "--json"], { + encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"], + }))[CORE_PACKAGE] : coreRange; + if (!targetRange) throw new Error("目标 CLI 未声明 Core 兼容范围"); + const latestCore = queryLatestCoreMetadata(targetRange); if (mode === "--check") { console.log([ `CLI: ${pkg.version} -> ${latestCli}`, `Core: ${coreVersions.coreVersion} -> ${latestCore.version}`, `Template: ${coreVersions.templateVersion} -> ${latestCore.templateVersion}`, - `Policy: Core 随 CLI 联动发布(当前 CLI pin Core ${coreRange})`, + `Policy: 当前 CLI Core 兼容范围 ${coreRange}(Core 目标仅限该范围)`, ].join("\n")); if (compareVersions(latestCli, pkg.version) > 0) { - console.log("\n可运行 flow2spec update --cli 一键更新(CLI 与配套 Core 一起到位)。"); + console.log("\n可运行 flow2spec update --cli 一键更新 CLI 及兼容 Core。"); } + if (compareVersions(latestCore.version, coreVersions.coreVersion) > 0) console.log("可运行 flow2spec update --core,仅更新兼容 Core,保持 CLI 版本。"); process.exit(0); } - // --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),无需写入全局安装。"); + const globalRoot = runCommandSync("npm", ["root", "-g"], { encoding: "utf8", timeout: 5000 }).trim(); + const globalCliDir = path.join(globalRoot, pkg.name); + const isGlobalInvocation = fs.existsSync(globalCliDir) && fs.realpathSync(globalCliDir) === fs.realpathSync(__dirname); + if (!isGlobalInvocation) { + // A fresh temporary cache avoids reusing a stale npx dependency tree; never change a separate global install. + const cacheDir = fs.mkdtempSync(path.join(os.tmpdir(), "flow2spec-update-")); + try { + const output = runCommandSync("npx", ["--yes", "--cache", cacheDir, `${pkg.name}@${targetCli}`, "version"], { + encoding: "utf8", env: { ...process.env, FLOW2SPEC_SKIP_UPDATE_CHECK: "1" }, + }); + const actualCli = output.match(/Flow2Spec CLI:\s+(\S+)/)?.[1]; + const actualCore = output.match(/Flow2Spec Core:\s+(\S+)/)?.[1]; + if (actualCli !== targetCli || actualCore !== latestCore.version) throw new Error(`临时安装验证失败:CLI ${actualCli || "未知"} / Core ${actualCore || "未知"},期望 ${targetCli} / ${latestCore.version}`); + console.log(output.trim()); + console.log("\n✓ 临时运行验证通过;未写入全局安装。后续 npx 缓存可能仍需刷新。"); + } finally { + fs.rmSync(cacheDir, { recursive: true, force: true }); + } process.exit(0); } - const cliUpToDate = compareVersions(latestCli, pkg.version) <= 0; + const cliUpToDate = getGlobalInstalledVersion() === targetCli; const effectiveBefore = getGlobalEffectiveCoreVersion(); - const coreHealthy = Boolean(effectiveBefore) && compareVersions(effectiveBefore, latestCore.version) >= 0; + const coreHealthy = effectiveBefore === latestCore.version; if (cliUpToDate && coreHealthy) { - console.log(`CLI v${pkg.version} 与 Core v${effectiveBefore} 均已是最新。`); + console.log(`CLI v${targetCli} 的 Core v${effectiveBefore} 已是兼容范围 ${targetRange} 内最新稳定版。`); process.exit(0); } - 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 { - // 卸载失败不阻断,继续安装。 - } + if (!coreHealthy) { + // Installing a top-level Core does not replace CLI's nested copy. Rebuild the target CLI tree. + console.log(`Core 实际生效版本 v${effectiveBefore || "未知"} 与兼容目标 v${latestCore.version} 不同,重装 CLI v${targetCli} 刷新依赖树…`); + runCommandSync("npm", ["uninstall", "-g", pkg.name], { stdio: "inherit" }); } - runCommandSync("npm", ["install", "-g", `${pkg.name}@latest`], { stdio: "inherit" }); + runCommandSync("npm", ["install", "-g", `${pkg.name}@${targetCli}`], { 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) { + if (installedCli !== targetCli || effectiveAfter !== latestCore.version) { console.error([ `⚠ Core 生效版本仍为 v${effectiveAfter || "未知"}(期望 v${latestCore.version})。`, - `若刚发布新版,可能处于 CLI/Core 联动发布窗口,稍后重试;否则请手动执行:`, - ` npm uninstall -g ${pkg.name} && npm install -g ${pkg.name}@latest`, + `CLI 实际版本 v${installedCli || "未知"}(期望 v${targetCli});请检查 registry 或手动重装:`, + ` npm uninstall -g ${pkg.name} && npm install -g ${pkg.name}@${targetCli}`, ].join("\n")); process.exit(1); } + console.log(`\n✓ CLI v${installedCli};Core 实际生效版本 v${effectiveAfter},验证通过。`); console.log(latestCore.templateVersion === coreVersions.templateVersion ? "Template Version 未变化,无需执行 f2s-kb-upgrade。" : "Template Version 已变化;请运行 init,并仅在 projectRev 与 pkgRev 不等时进入 f2s-kb-upgrade。" diff --git a/packages/cli/package.json b/packages/cli/package.json index 2c23926..1414763 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@double-coding/flow2spec", - "version": "3.6.4", + "version": "3.6.5", "description": "在业务仓库初始化文档驱动、可写回知识库的 AI 协作骨架", "homepage": "https://github.com/double-coding-lab/Flow2Spec#readme", "repository": { @@ -19,7 +19,7 @@ "README.md" ], "dependencies": { - "@double-coding/flow2spec-core": "3.8.1" + "@double-coding/flow2spec-core": "^3.8.2" }, "publishConfig": { "access": "public", diff --git a/packages/core/package.json b/packages/core/package.json index e260310..a975936 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@double-coding/flow2spec-core", - "version": "3.8.1", - "templateVersion": "3.8.0", + "version": "3.8.2", + "templateVersion": "3.8.1", "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/knowledge/manifest-routing.json b/packages/core/templates/en-US/knowledge/manifest-routing.json index 966a8e1..7dec70e 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.8.0", + "version": "3.8.1", "projectRev": 3, "knowledgeRoot": ".Knowledge", "matcherKey": "matcherId", diff --git a/packages/core/templates/en-US/rules/f2s-topic-authoring.md b/packages/core/templates/en-US/rules/f2s-topic-authoring.md index 3ca771f..ddf7385 100644 --- a/packages/core/templates/en-US/rules/f2s-topic-authoring.md +++ b/packages/core/templates/en-US/rules/f2s-topic-authoring.md @@ -21,9 +21,12 @@ This rule is touched when any of the following is true: ## 1. Topic Naming - **id**: `kebab-case`, matching the key in `manifest-routing.topicPaths`. +- **Business naming**: name topic ids, filenames, headings and derived matcher ids by responsibility, without prefixes from downstream project names, repository names or `package.json.name`; keep project identity in the body. Fix the architecture overview to `project-architecture` / `project-architecture.md` / `Project Architecture`, sourced from `project-architecture_final.md` (Chinese heading/source: `项目架构` / `项目架构终稿.md`). Preserve existing `f2s-*` identifiers shared with skills/rules. - **Filename**: `.Knowledge/topics/.md`. If the topic is strongly bound to an `f2s-*` skill / rule of the same name (for example `f2s-task` / `f2s-req-plan`), the filename may include the `f2s-` prefix to show shared origin. - **Avoid**: version suffixes (`-v2` / `-new`), personal nicknames, and synonyms that conflict with heading-level titles in `index.md`. +References to `*_终稿.md` below also include the fixed architecture final names `project-architecture_final.md` and `项目架构终稿.md`; both are valid final sources without a project-name prefix. + ## 2. Topic Positioning and Body Skeleton **Topic positioning**: executable routing summary + key boundaries. A topic may contain necessary boundary notes, key flow steps, prohibited items, and configuration summaries. After reading it, the Agent should be able to execute or decide whether more drilling is needed. It **should not carry** complete implementation details, long-form background, or raw content that can be found in a stock-doc. Stock-docs carry full background and long-form details; topics point to them. diff --git a/packages/core/templates/en-US/skills/f2s-doc-arch/SKILL.md b/packages/core/templates/en-US/skills/f2s-doc-arch/SKILL.md index a3ba65f..5cbf802 100644 --- a/packages/core/templates/en-US/skills/f2s-doc-arch/SKILL.md +++ b/packages/core/templates/en-US/skills/f2s-doc-arch/SKILL.md @@ -27,7 +27,7 @@ This skill helps users generate **project architecture documentation** in a **dr | Parameter | Description | | -------------- | -------------------------- | | **First argument** | Optional. One of: **a plain-text description** written after the command, or **a local document path** such as `.Knowledge/stock-docs/xxx.md`, `.Knowledge/req-docs/README.md`, or `README.md`. If omitted, enter the "no input" flow. | -| **Second argument** | Optional. Output file path. If omitted, default to `.Knowledge/stock-docs/architecture-overview_draft.md` (the project name may be inferred from `package.json` `name` or the directory name, then sanitized for a valid filename). | +| **Second argument** | Optional output path. It may select a directory; the filename is fixed as `project-architecture_draft.md`. Default: `.Knowledge/stock-docs/project-architecture_draft.md`. | **Note**: when no description or document is provided, the skill uses **AI scanning of project code and directories** to generate the architecture draft, and **quality is not guaranteed**. Before executing, you **must first ask the user**: "Do you confirm that no arguments will be provided and that AI should still scan the code to generate the draft? (quality not guaranteed)" Continue only after the user explicitly confirms. @@ -48,7 +48,7 @@ This skill helps users generate **project architecture documentation** in a **dr - Produce a **project architecture document**. It may include, but is not limited to: project positioning, technology stack, directory/module split, key paths and entry points, configuration and deployment notes, and how this document maps to documentation artifact stages if applicable. - **No fixed format**: clear headings and paragraphs are enough. Do not force the `final-overview-template`. 4. **Output** - - Default output: `.Knowledge/stock-docs/architecture-overview_draft.md`. If the user provides a second argument, write to that path. + - Default output: `.Knowledge/stock-docs/project-architecture_draft.md`, with the fixed H1 `# Project Architecture Draft`. Normalize the second argument's basename using the naming convention below. - If the directory does not exist, create it first. ### 2. If the User Provides No Notes or Document @@ -61,7 +61,7 @@ This skill helps users generate **project architecture documentation** in a **dr - Based on the parent directory of the config root: list main directories and representative files (with package.json, common entry names, and config filenames when useful), and summarize "directory structure, likely modules, entry points, and configuration". - Generate an **architecture draft**, and state inside the document: "This draft was generated by scanning the project structure; it should be further completed with business notes and code details." 3. **Output** - - Same as above: default `.Knowledge/stock-docs/architecture-overview_draft.md`, or the second argument specified by the user. + - Same as above: default `.Knowledge/stock-docs/project-architecture_draft.md`; a custom directory retains the fixed filename and heading. --- @@ -95,15 +95,15 @@ After splitting, each sub-topic is independently matched through its own matcher Do not chain "overview -> details" through topicDependencies (see f2s-topic-authoring section 5). ``` -The user may choose: **A) run `f2s-doc-arch` separately for each split recommendation** (recommended), or **B) continue with the current single draft** for later steps. +Name these split documents after capabilities, without a downstream project-name prefix; use `f2s-kb-add` to consolidate each implemented capability. `f2s-doc-arch` maintains one project architecture draft, so repeated runs do not overwrite it with unrelated capability documents. ## Next Step After Completion (Hard Constraint) This skill **only produces a draft**. At the end, guide the user in the following order. **Do not** let the user skip the final version and directly run `f2s-kb-build`: 1. Tell the user the draft path and recommend reviewing and completing it first. -2. **The next step must be `f2s-doc-final`**: use the draft path as input and produce `.Knowledge/stock-docs/_final.md` in the `final-overview-template` standard format. -3. **Only after the final document is written** guide the user to **`f2s-kb-build`**, and its input must be the final path (containing `_final` or just generated by `f2s-doc-final`). +2. **The next step must be `f2s-doc-final`**: use the draft path as input and produce `.Knowledge/stock-docs/project-architecture_final.md` with H1 `# Project Architecture Final`. +3. **Only after the final document is written** guide the user to **`f2s-kb-build`**, using `project-architecture_final.md` (use its actual path for a custom directory). 4. **Do not** write only "please run `f2s-kb-build`" in the completion reply with input pointing to `*_draft.md`; **do not** present `f2s-kb-build` and `f2s-doc-final` as alternatives. 5. **Only exception**: the user **explicitly requests** skipping the final step, and the draft has already been manually made compliant with the `final-overview-template`. First explain the risk of skipping finalization, then allow `f2s-kb-build`. @@ -116,8 +116,10 @@ This skill **only produces a draft**. At the end, guide the user in the followin ## Path and Output Conventions - All paths are relative to the **parent directory of the config root**. -- **Default output**: `.Knowledge/stock-docs/architecture-overview_draft.md`; the project name is taken from `package.json` `name` (with scope and illegal characters removed) or the current directory name. -- If the user provides the second argument as an output path, use it first. If the directory does not exist, create it first. +- **Fixed naming**: draft filename/H1 are `project-architecture_draft.md` / `Project Architecture Draft`; final filename/H1 are `project-architecture_final.md` / `Project Architecture Final`. The default directory is `.Knowledge/stock-docs/`. Their Chinese equivalents are `项目架构初稿.md` / `项目架构初稿` and `项目架构终稿.md` / `项目架构终稿`. +- Use `package.json.name`, repository names and directory names only in project-background prose, never as document, heading or topic prefixes. +- The second argument only changes the output directory. Normalize a different basename to `project-architecture_draft.md` and report the actual path. Check for an existing file and update incrementally, preserving valid content. +- The architecture topic uses id `project-architecture`, file `.Knowledge/topics/project-architecture.md` and title `Project Architecture`. Name other split topics by responsibility, without downstream project-name prefixes. `f2s-kb-build` maintains topics and references; this skill only hands off the convention. --- diff --git a/packages/core/templates/en-US/skills/f2s-doc-final/SKILL.md b/packages/core/templates/en-US/skills/f2s-doc-final/SKILL.md index ab52a9b..d3a1331 100644 --- a/packages/core/templates/en-US/skills/f2s-doc-final/SKILL.md +++ b/packages/core/templates/en-US/skills/f2s-doc-final/SKILL.md @@ -19,6 +19,14 @@ The user provides **at least one argument** after this skill: the **first argume **The `final-overview-template` is only guidance**: if `.Knowledge/template/final-overview-template.md` exists, read it as a structural reference. Do not force an exact template fit. +## Project Architecture Naming (Overrides Generic Design Naming) + +- For `f2s-doc-arch` output or content explicitly describing the overall project architecture, fix draft filename/H1 to `project-architecture_draft.md` / `Project Architecture Draft` and final filename/H1 to `project-architecture_final.md` / `Project Architecture Final`. Default directory: `.Knowledge/stock-docs/`. Chinese equivalents are `项目架构初稿.md` / `项目架构初稿` and `项目架构终稿.md` / `项目架构终稿`. +- Apply this branch to both MD and PDF flows. Do not prefix names with the downstream project name inferred from an old input filename, source heading, `package.json.name` or repository directory; do not produce `project-architecture_draft_final.md`. The second argument can change the directory, but normalize the basename for the artifact stage and report the actual path. +- Keep project names in body prose. Incrementally update an existing target while preserving valid content. When converting an old-named input, do not automatically delete it or overwrite a document of a different scope; confirm reference migrations. +- Hand off `f2s-kb-build /project-architecture_final.md`. Use architecture topic id `project-architecture` and title `Project Architecture`; name split topics by responsibility, without downstream project-name prefixes. +- Other capability/design documents retain the generic `_draft.md` / `_final.md` convention below. + ## Embedded Template Structure (Use When `.Knowledge/template/final-overview-template.md` Does Not Exist) Standard requirements: diff --git a/packages/core/templates/en-US/skills/f2s-kb-build/SKILL.md b/packages/core/templates/en-US/skills/f2s-kb-build/SKILL.md index 8c4683b..1742629 100644 --- a/packages/core/templates/en-US/skills/f2s-kb-build/SKILL.md +++ b/packages/core/templates/en-US/skills/f2s-kb-build/SKILL.md @@ -25,8 +25,8 @@ description: Generate knowledge-routing topics and indexes from `.Knowledge/stoc - Accepts one argument: a URL or local path. - Local paths must be under `.Knowledge/stock-docs/`. -- **Must be a final draft**: recommended filename contains `_final.md`, or has been normalized by **`f2s-doc-final`**. It is **forbidden** to execute this skill directly with a `*_draft.md` produced by `f2s-doc-arch`. -- If the input path contains **`_draft`**, or the user has just completed an architecture draft but has not run `f2s-doc-final`: **stop** and reply that they must first run **`f2s-doc-final `**, then call this skill with the final-draft path after it is written. +- **Must be finalized**: project architecture uses `project-architecture_final.md` (Chinese: `项目架构终稿.md`); other documents should contain `_final.md` or have been normalized by **`f2s-doc-final`**. Never build directly from an architecture draft, including `project-architecture_draft.md` or `项目架构初稿.md`. +- If the input filename contains **`_draft`** or **`初稿`** (including `项目架构初稿.md` without an underscore), or the user has just completed an architecture draft but has not run `f2s-doc-final`: **stop** and reply that they must first run **`f2s-doc-final `**, then call this skill with the final-draft path after it is written. - If `.Knowledge/req-docs/` is passed, tell the user to organize it into a `stock-docs` final draft before executing. ## Generation Principles @@ -60,6 +60,8 @@ Extract from the document: ## Step 3: Write topics - Target path: `.Knowledge/topics/.md` +- Fix the architecture overview topic id to `project-architecture`, filename to `project-architecture.md`, heading to `Project Architecture` (Chinese: `项目架构`), and `sourceDoc` to the actual architecture final document. Name split topics by responsibility. Do not add downstream project-name prefixes to topic ids, filenames, headings or derived matcher ids. +- If an equivalent project-prefixed topic already exists, confirm migration scope and naming conflicts before updating its topic, matcher, index, routing and incoming references together. Until confirmed, report the pending migration; do not create duplicate architecture topics or automatically delete old files. - If the same topic already exists: prefer incremental updates to avoid duplicate topics. - If it is a new topic: add the file with a clear title, applicable scenarios, rules, and workflow. 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 8c7285a..9c01b1a 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 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): +Record CLI Version, Core Version, Core Range, Template Version, Protocol Version, and the latest npm Core/Template values (the CLI uses a caret compatibility range; Core can release independently): | Case | Action | Default step 2 command | | --- | --- | --- | -| **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 ` | +| **A. Template is current** | If the CLI/Core has an update, run `flow2spec update --cli` (CLI and its compatible 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 compatible Core arrive together), then continue to step 0. | `flow2spec init ` | +| **C. Not installed or unknown** | Use the latest CLI (it resolves compatible 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,7 +159,7 @@ 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**: use the latest CLI (it carries its pinned Core and templates): +2. **Step -1 returned C**: use the latest CLI (it resolves compatible Core and templates): - `npx --yes @latest init ` 3. For overwrite reset: - Append `--reset-knowledge` to the above command. @@ -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` 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. +> **Helper commands (user self-inspection)**: `flow2spec version` shows the five version dimensions; `flow2spec update --check` checks updates, and `flow2spec update --cli` updates the latest CLI and its compatible Core; `--core` retains the current CLI version and refreshes only compatible Core. 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 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. +1. **Step -1** was performed: `flow2spec version` and `flow2spec update --check` recorded CLI/Core/Core Range/Template/Protocol; when updates existed, `flow2spec update --cli` refreshed the CLI and its compatible Core, 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/knowledge/manifest-routing.json b/packages/core/templates/zh-CN/knowledge/manifest-routing.json index ec91f42..e54649a 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.8.0", + "version": "3.8.1", "projectRev": 3, "knowledgeRoot": ".Knowledge", "matcherKey": "matcherId", diff --git a/packages/core/templates/zh-CN/rules/f2s-topic-authoring.md b/packages/core/templates/zh-CN/rules/f2s-topic-authoring.md index 2cae945..bd56fd8 100644 --- a/packages/core/templates/zh-CN/rules/f2s-topic-authoring.md +++ b/packages/core/templates/zh-CN/rules/f2s-topic-authoring.md @@ -20,11 +20,14 @@ alwaysApply: false ## 1. topic 命名 - **id**:`kebab-case`,与 `manifest-routing.topicPaths` 的 key 一致。 +- **业务命名**:topic id、文件名、标题以及派生 matcher id 按职责命名,不拼接下游项目名、仓库名或 `package.json.name` 前缀;项目身份放在正文。项目架构概览固定为 `project-architecture` / `project-architecture.md` / `项目架构`,其源文档为 `项目架构终稿.md`。既有 `f2s-*` 技能/规则同源标识保留。 - **文件名**:`.Knowledge/topics/.md`;若该 topic 与同名 `f2s-*` 技能 / 规则强绑定(如 `f2s-task` / `f2s-req-plan`),文件名可加 `f2s-` 前缀以示同源。 - **不要**:版本后缀(`-v2` / `-new`)、个人花名、与 `index.md` 行级标题冲突的同义词。 ## 2. topic 定位与正文骨架 +本规则下文的 `*_终稿.md` 同时包含固定命名的 `项目架构终稿.md`;它是合法的终稿事实源,无需补下划线或项目名前缀。 + **topic 的定位**:可执行路由摘要 + 关键边界。topic 可以包含必要的边界说明、关键流程步骤、禁止项、配置摘要——Agent 读完即可执行或判断是否需要继续下钻;**不应承载**完整实现细节、长文背景或可在 stock-doc 里查的原始内容。stock-doc 承载完整背景与长文细节,topic 指向它。 **长文背景引用的目录边界(硬约束)**:topic 中「详细背景 / 相关资料 / 长文来源 / 参考文档」等**指向长文源**的引用槽位,**只允许**指向 `.Knowledge/stock-docs/*_终稿.md` 或已被归档为长文事实的 `stock-docs/*`;**禁止**把这类槽位挂到 `.Knowledge/req-docs/*`(含澄清 / 技术方案 / SQL / PRD 等)——`req-docs` 是本次交付的**临时输入**,用完会随任务归档或迁移,作为 topic 的长文事实源是悬空引用。若同步 / 新建 topic 时相应 `stock-docs/*_终稿.md` 尚未生成,**必须先触发 `f2s-doc-final` 沉淀终稿**(或与用户确认由手写补齐),再让 topic 指向终稿;不得跳过终稿直接把 topic 挂在 `req-docs` 上。**允许**:topic 正文可**短引**方案里的一句结论或一个字段名作为佐证(如「见 `.Knowledge/req-docs/xxx_技术方案.md`」的偶发点引),但**长文背景槽位**("详细背景 / 相关资料"整节)仍须指向 stock-doc。 @@ -160,4 +163,4 @@ matcher 分片(`.Knowledge/matchers/.json`)有 4 个字段参与 `routing.ma - 把"重要的规则"硬塞进 `taskToTopicRules`(参见第 7 条)。 - 用 `topicDependencies` 表达"信息相关"(应通过 `index.md` 语义边界 + matcher 关键词补召回,而非依赖边)。 - 在 `topicDependencies` 中写传递冗余边或形成环。 -- **在 topic 的「长文背景 / 详细资料 / 相关资料 / 长文来源 / 参考文档」等指向长文源的整节引用槽位里,列出 `.Knowledge/req-docs/*`**(含澄清 / 技术方案 / SQL / PRD)。此类槽位只允许指向 `.Knowledge/stock-docs/*_终稿.md`;无终稿时须先生成再回填。短句/佐证式的偶发点引不受此约束。 \ No newline at end of file +- **在 topic 的「长文背景 / 详细资料 / 相关资料 / 长文来源 / 参考文档」等指向长文源的整节引用槽位里,列出 `.Knowledge/req-docs/*`**(含澄清 / 技术方案 / SQL / PRD)。此类槽位只允许指向 `.Knowledge/stock-docs/*_终稿.md`;无终稿时须先生成再回填。短句/佐证式的偶发点引不受此约束。 diff --git a/packages/core/templates/zh-CN/skills/f2s-doc-arch/SKILL.md b/packages/core/templates/zh-CN/skills/f2s-doc-arch/SKILL.md index 23b7c76..e9c1792 100644 --- a/packages/core/templates/zh-CN/skills/f2s-doc-arch/SKILL.md +++ b/packages/core/templates/zh-CN/skills/f2s-doc-arch/SKILL.md @@ -27,7 +27,7 @@ description: 根据用户说明或文档(或扫描代码)生成项目架构 | 参数 | 说明 | | -------------- | -------------------------- | | **第一个参数** | 可选。可为以下之一:**一段纯文字说明**(直接写在命令后)、**本地文档路径**(如 `.Knowledge/stock-docs/xxx.md`、`.Knowledge/req-docs/README.md`、`README.md`)。不传则进入「无输入」流程。 | -| **第二个参数** | 可选。输出文件路径;若不传,默认写入 `.Knowledge/stock-docs/架构说明_初稿.md`(项目名可从 package.json 的 name 或目录名推断,做合法文件名处理)。 | +| **第二个参数** | 可选。输出路径;可指定目录,文件名固定为 `项目架构初稿.md`。默认 `.Knowledge/stock-docs/项目架构初稿.md`。 | **注意**:不传任何说明或文档时,将使用 **AI 扫描项目代码与目录** 生成架构说明初稿,**不保证质量**。执行时**必须先提示用户**:「是否确认不传递参数,仍使用 AI 扫描代码生成?(不保证质量)」,仅当用户明确确认后才继续。 @@ -48,7 +48,7 @@ description: 根据用户说明或文档(或扫描代码)生成项目架构 - 产出一份**项目架构说明**:可包含但不限于:项目定位、技术栈、目录/模块划分、关键路径与入口、配置与部署要点、与文档产物阶段的对应说明(若适用)。 - **无固定格式**:采用清晰的标题与段落即可,不强制套用《终稿模版》。 4. **输出** - - 默认写入 `.Knowledge/stock-docs/架构说明_初稿.md`;若用户传入第二参数则写入该路径。 + - 默认写入 `.Knowledge/stock-docs/项目架构初稿.md`,一级标题固定为 `# 项目架构初稿`;第二参数的文件名按下文命名约定归一化。 - 若目录不存在则先创建。 ### 2. 若用户未提供任何说明或文档 @@ -61,7 +61,7 @@ description: 根据用户说明或文档(或扫描代码)生成项目架构 - 基于配置根的父目录:列出主要目录与代表性文件(可结合 package.json、常见入口与配置文件名),归纳出「目录结构、疑似模块、入口与配置」等。 - 生成一份**架构说明初稿**,并在文中注明「本初稿由扫描项目结构生成,建议结合业务说明与代码细节进一步补充」。 3. **输出** - - 同上,默认 `.Knowledge/stock-docs/架构说明_初稿.md`,或用户指定的第二参数。 + - 同上,默认 `.Knowledge/stock-docs/项目架构初稿.md`;指定目录时仍使用固定文件名与标题。 --- @@ -95,16 +95,16 @@ description: 根据用户说明或文档(或扫描代码)生成项目架构 不通过 topicDependencies 串联"概述 → 详情"(见 f2s-topic-authoring 第 5 节)。 ``` -用户可选择:**A) 按拆分建议分别执行 `f2s-doc-arch`**(推荐),或 **B) 继续用当前单份初稿**进入后续流程。 +上述拆分文档按能力命名,不加下游项目名前缀;可使用 `f2s-kb-add` 分别沉淀已实现的能力。`f2s-doc-arch` 保持单份项目架构初稿,避免多次执行覆盖为不同能力文档。 ## 完成后的下一步(硬约束) 本技能**只产出初稿**;结束时须按下列顺序引导,**禁止**让用户跳过终稿直接 `f2s-kb-build`: 1. 告知初稿路径,建议用户先审阅、补充内容。 -2. **下一步必须为 `f2s-doc-final`**:以初稿路径为入参,产出 `.Knowledge/stock-docs/<方案名>_终稿.md`(《终稿模版》规范格式)。 -3. **仅在终稿落盘后**再引导 **`f2s-kb-build`**,且入参须为终稿路径(含 `_终稿` 或由 `f2s-doc-final` 刚生成)。 -4. **禁止**在完成回复中单独写「请执行 `f2s-kb-build`」且入参指向 `*_初稿.md`;**禁止**将 `f2s-kb-build` 与 `f2s-doc-final` 并列成「二选一」。 +2. **下一步必须为 `f2s-doc-final`**:以初稿路径为入参,产出 `.Knowledge/stock-docs/项目架构终稿.md`,一级标题为 `# 项目架构终稿`。 +3. **仅在终稿落盘后**再引导 **`f2s-kb-build`**,且入参须为 `项目架构终稿.md`(指定目录时使用实际终稿路径)。 +4. **禁止**在完成回复中单独写「请执行 `f2s-kb-build`」且入参指向 `项目架构初稿.md` 或其他初稿;**禁止**将 `f2s-kb-build` 与 `f2s-doc-final` 并列成「二选一」。 5. **唯一例外**:用户**明确要求**跳过终稿、且初稿已人工符合终稿模版——须先说明跳过终稿的风险,再允许指向 `f2s-kb-build`。 **完成回复模板**(须同时包含 `f2s-doc-final` 与 `f2s-kb-build`,且 ctx-build 在终稿之后): @@ -116,8 +116,10 @@ description: 根据用户说明或文档(或扫描代码)生成项目架构 ## 路径与输出约定 - 所有路径均相对于**配置根的父目录**。 -- **默认输出**:`.Knowledge/stock-docs/架构说明_初稿.md`;项目名取自 `package.json` 的 `name`(去掉 scope 与非法字符)或当前目录名。 -- 若用户传入第二参数为输出路径,则优先使用该路径;若目录不存在则先创建。 +- **固定命名**:初稿文件名/一级标题为 `项目架构初稿.md` / `项目架构初稿`;后续终稿为 `项目架构终稿.md` / `项目架构终稿`。默认目录为 `.Knowledge/stock-docs/`。 +- `package.json.name`、仓库名、目录名仅用于正文中的项目背景,不用于文件名、标题或主题前缀。 +- 第二参数仅改变输出目录;若文件名不符,保留其目录并归一为 `项目架构初稿.md`,向用户说明实际路径。写入前检查同名文件,增量更新并保留已有有效内容。 +- 后续架构主题使用 `project-architecture`,文件为 `.Knowledge/topics/project-architecture.md`,标题为 `项目架构`;其他拆分主题按职责命名,均不加下游项目名前缀。由 `f2s-kb-build` 维护主题及引用,本技能只传递此命名约定。 --- diff --git a/packages/core/templates/zh-CN/skills/f2s-doc-final/SKILL.md b/packages/core/templates/zh-CN/skills/f2s-doc-final/SKILL.md index 27d804f..2cff2b7 100644 --- a/packages/core/templates/zh-CN/skills/f2s-doc-final/SKILL.md +++ b/packages/core/templates/zh-CN/skills/f2s-doc-final/SKILL.md @@ -19,6 +19,14 @@ description: 将 PDF 或 MD 转为《终稿模版》规范格式,便于后续 **终稿模版仅作提示**:若存在 `.Knowledge/template/终稿模版.md`,可读取作为结构参考;不强制套用。 +## 项目架构命名(优先于通用方案命名) + +- 输入为 `f2s-doc-arch` 产物,或内容明确为项目整体架构时,初稿文件名/一级标题固定为 `项目架构初稿.md` / `项目架构初稿`,终稿固定为 `项目架构终稿.md` / `项目架构终稿`;默认目录 `.Knowledge/stock-docs/`。 +- 此分支适用于 MD 与 PDF 两种流程;不从旧输入文件名、正文方案名、`package.json.name` 或仓库目录名拼接项目名前缀,也不生成 `项目架构初稿_终稿.md`。第二参数可改变目录,文件名按对应阶段归一化,并说明实际路径。 +- 项目名称只在正文描述;同名目标已存在时增量更新并保留有效内容。转换旧名称输入时不自动删除原文件或覆盖另一份不同范围文档,引用迁移需确认。 +- 完成后交接 `f2s-kb-build <实际目录>/项目架构终稿.md`;架构 topic id 为 `project-architecture`、标题为 `项目架构`,拆分主题按职责命名,均不加下游项目名前缀。 +- 其他能力/方案文档继续使用下述 `<方案名>_初稿.md` / `<方案名>_终稿.md` 约定。 + ## 内嵌模板结构(当项目内无 `.Knowledge/template/终稿模版.md` 时使用) 规范要求: diff --git a/packages/core/templates/zh-CN/skills/f2s-kb-build/SKILL.md b/packages/core/templates/zh-CN/skills/f2s-kb-build/SKILL.md index 15b5ec2..a00fd8b 100644 --- a/packages/core/templates/zh-CN/skills/f2s-kb-build/SKILL.md +++ b/packages/core/templates/zh-CN/skills/f2s-kb-build/SKILL.md @@ -25,8 +25,8 @@ description: 根据 .Knowledge/stock-docs 文档生成知识路由主题与索 - 接收一个参数:URL 或本地路径。 - 本地路径必须位于 `.Knowledge/stock-docs/`。 -- **须为终稿**:推荐文件名含 `_终稿.md`,或已由 **`f2s-doc-final`** 规范化;**禁止**以 `f2s-doc-arch` 产出的 `*_初稿.md` 作为入参直接执行本技能。 -- 若入参路径含 **`_初稿`**、或用户刚完成架构初稿尚未执行 `f2s-doc-final`:**停止**,回复须先执行 **`f2s-doc-final <初稿路径>`**,待终稿落盘后再以终稿路径调用本技能。 +- **须为终稿**:项目架构使用 `项目架构终稿.md`;其他文档推荐文件名含 `_终稿.md`,或已由 **`f2s-doc-final`** 规范化。**禁止**直接以 `项目架构初稿.md`、`*_初稿.md` 或其他尚未定稿的架构文档执行本技能。 +- 若入参文件名含 **`初稿`**(包括不带下划线的 `项目架构初稿.md`)或 **`_draft`**、或用户刚完成架构初稿尚未执行 `f2s-doc-final`:**停止**,回复须先执行 **`f2s-doc-final <初稿路径>`**,待终稿落盘后再以终稿路径调用本技能。 - 若传入 `.Knowledge/req-docs/`,提示用户先整理为 `stock-docs` 终稿后再执行。 ## 生成原则 @@ -60,6 +60,8 @@ description: 根据 .Knowledge/stock-docs 文档生成知识路由主题与索 ## 步骤 3:写入 topics - 目标路径:`.Knowledge/topics/.md` +- 项目架构概览的 topic id 固定为 `project-architecture`,文件 `project-architecture.md`,标题 `项目架构`,`sourceDoc` 指向实际的 `项目架构终稿.md`。拆分主题按职责命名;topic id、文件名、标题及派生 matcher id 均不拼接下游项目名前缀。 +- 若发现同义的带项目名前缀旧主题,先确认迁移范围与同名冲突,再同步 topic、matcher、index、路由和入站引用;未确认前报告待迁移项,不并存新旧两套架构主题、不自动删除旧文件。 - 若已存在同主题:优先增量更新,避免重复主题。 - 若为新主题:新增文件并补充清晰标题、适用场景、规则与流程。 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 1871347..7528b6a 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 Pinned、Template Version、Protocol Version,以及 npm 最新 Core/Template(CLI 对 Core 为精确 pin,两包联动发布): +按输出记录 CLI Version、Core Version、Core Range、Template Version、Protocol Version,以及 npm 最新 Core/Template(CLI 对 Core 使用 caret 兼容范围,Core 可独立发布): | 情况 | 行动 | 步骤 2 默认命令 | | --- | --- | --- | -| **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 ` | +| **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(自动解析兼容 Core),避免 npx 复用旧版缓存 | `npx --yes @latest init ` | 预检失败时允许回退 C,但不得把 Core Version 当作 Template Version。此步骤不强制创建子 agent,也不后台安装全局包。 @@ -159,7 +159,7 @@ flow2spec update --check 1. **步骤 -1 判定为 A/B(本地 CLI/Core 可用)**:直接使用当前 CLI: - `flow2spec init ` -2. **步骤 -1 判定为 C**:用 latest CLI(自带 pin 的配套 Core 与模板): +2. **步骤 -1 判定为 C**:用 latest CLI(自动解析兼容 Core 与模板): - `npx --yes @latest init ` 3. 覆盖重置时: - 在上述命令末尾追加 `--reset-knowledge` @@ -169,7 +169,7 @@ flow2spec update --check > `` 示例:`cursor claude codex`。 -> **辅助命令(用户可自查)**:`flow2spec version` 查看五维版本;`flow2spec update --check` 检查更新,`flow2spec update --cli` 整体更新(CLI 与配套 Core 联动,`--core` 为其等价别名)。这些命令不替代 Template Version 变化后的本技能完整流程。 +> **辅助命令(用户可自查)**:`flow2spec version` 查看五维版本;`flow2spec update --check` 检查更新,`flow2spec update --cli` 更新 latest CLI 及其兼容 Core,`--core` 保持当前 CLI 版本只刷新兼容 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 Pinned/Template/Protocol;有更新时是否执行 `flow2spec update --cli` 联动刷新 CLI 与配套 Core,Template 更新是否按 A/B/C 选择当前 CLI 或 latest CLI;**A 分支停止前是否完成「项目侧对齐检查」**(manifest `version`/`pkgRev` 对比 + `kb check --strict`),未对齐时是否已转完整流程而非直接停止。 +1. 是否已做 **步骤 -1**:执行 `flow2spec version` 与 `flow2spec update --check`,记录 CLI/Core/Core Range/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 32e8aa9..db33ebf 100644 --- a/scripts/test-cli-update.js +++ b/scripts/test-cli-update.js @@ -1,75 +1,113 @@ "use strict"; - const assert = require("assert"); const fs = require("fs"); const os = require("os"); const path = require("path"); const { spawnSync } = require("child_process"); - -const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "flow2spec-cli-update-")); -const cliPath = path.resolve(__dirname, "..", "cli.js"); - -// 动态读取本地版本,stub 返回「补丁号 +1」的新版本,避免发版后钉死 fixture -const cliPkg = require(path.resolve(__dirname, "..", "packages", "cli", "package.json")); -const corePkg = require(path.resolve(__dirname, "..", "packages", "core", "package.json")); -const cliVersion = cliPkg.version; -const coreVersion = corePkg.version; -const templateVersion = corePkg.templateVersion; -const coreRange = cliPkg.dependencies["@double-coding/flow2spec-core"]; -const bumpPatch = (v) => { - const [major, minor, patch] = v.split(".").map(Number); - return `${major}.${minor}.${patch + 1}`; -}; -const latestCli = bumpPatch(cliVersion); -const latestCore = bumpPatch(coreVersion); -const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - if (process.platform === "win32") { - fs.writeFileSync(path.join(tempDir, "npm.cmd"), [ - "@echo off", - "if \"%2\"==\"@double-coding/flow2spec-core\" (", - ` echo {"version":"${latestCore}","templateVersion":"${templateVersion}"}`, - ") else (", - ` echo ${latestCli}`, - ")", - "", - ].join("\r\n"), "utf8"); -} else { - const npmPath = path.join(tempDir, "npm"); - fs.writeFileSync(npmPath, [ - "#!/usr/bin/env sh", - "if [ \"$2\" = \"@double-coding/flow2spec-core\" ]; then", - ` echo '{"version":"${latestCore}","templateVersion":"${templateVersion}"}'`, - "else", - ` echo '${latestCli}'`, - "fi", - "", - ].join("\n"), { encoding: "utf8", mode: 0o755 }); + const cli = path.resolve(__dirname, "../packages/cli/cli.js"); + const options = { env: { ...process.env, FLOW2SPEC_SKIP_UPDATE_CHECK: "1" }, encoding: "utf8" }; + const help = spawnSync(process.execPath, [cli, "--help"], options); + assert.strictEqual(help.status, 0, help.stderr); + assert.match(help.stdout, /--core\s+保持当前 CLI 版本/); + const invalid = spawnSync(process.execPath, [cli, "update", "--unknown"], options); + assert.strictEqual(invalid.status, 1); + assert.match(invalid.stderr, /update --check\|--cli\|--core/); + console.log("test-cli-update: help/invalid-argument smoke ok; skipped POSIX package-manager installation fixture"); + process.exit(0); +} +// Exercise the shipped CLI, mocking only npm/npx and their installation directories. +const temp = fs.mkdtempSync(path.join(os.tmpdir(), "flow2spec-cli-update-")); +const cliName = "@double-coding/flow2spec"; +const coreName = "@double-coding/flow2spec-core"; +const globalRoot = path.join(temp, "global"); +const globalCli = path.join(globalRoot, cliName); +const localCli = path.join(temp, "local"); +const log = path.join(temp, "calls"); +function json(file, value) { + fs.mkdirSync(path.dirname(file), { recursive: true }); + fs.writeFileSync(file, JSON.stringify(value)); +} +function fixture(dir, range = "^3.8.2") { + json(path.join(dir, "package.json"), { name: cliName, version: "3.6.5", dependencies: { [coreName]: range } }); + fs.copyFileSync(path.resolve(__dirname, "../packages/cli/cli.js"), path.join(dir, "cli.js")); + const core = path.join(dir, "node_modules", coreName); + json(path.join(core, "package.json"), { version: "3.8.2", main: "index.js" }); + fs.writeFileSync(path.join(core, "index.js"), `exports.createFlow2Spec=()=>({project:{agents:()=>({})},config:{supportedLocales:()=>[]}});exports.getVersions=()=>({coreVersion:require('./package.json').version,templateVersion:'3.8.1'});exports.getCapabilities=()=>({protocolVersion:1});`); +} +const mock = `#!/usr/bin/env node +const fs=require('fs'),path=require('path'),a=process.argv.slice(2),e=process.env; +const cli='${cliName}',core='${coreName}'; +fs.appendFileSync(e.MOCK_LOG,JSON.stringify({cmd:path.basename(process.argv[1]),args:a})+'\\n'); +if(path.basename(process.argv[1])==='npx') { + console.log('Flow2Spec CLI: '+a.find(x=>x.startsWith(cli+'@')).slice(cli.length+1)+'\\nFlow2Spec Core: '+(e.MOCK_STALE?'3.8.2':'3.9.0'));process.exit(0); +} +if(a[0]==='root') console.log(e.MOCK_GLOBAL); +else if(a[0]==='view') { + if(a[1]===cli) console.log('3.6.6'); + else if(a[1].startsWith(cli+'@')) console.log(JSON.stringify({[core]:'^4.0.0'})); + else if(a[1]===core+'@^3.8.2') console.log(JSON.stringify([{version:'3.8.2',templateVersion:'3.8.1'},{version:'3.9.0',templateVersion:'3.9.0'},{version:'3.10.0-beta.1'}])); + else if(a[1]===core+'@^4.0.0') console.log(JSON.stringify({version:'4.1.0',templateVersion:'4.0.0'})); + else if(a[1]===core+'@^0.2.3') console.log(JSON.stringify([{version:'0.2.4'},{version:'0.2.5-beta.1'}])); + else throw Error('Unexpected query '+a.join(' ')); +} else if(a[0]==='uninstall') { if(e.MOCK_FAIL) process.exit(1); } +else if(a[0]==='install') { + if(e.MOCK_INSTALL_FAIL) process.exit(1); + const dir=path.join(e.MOCK_GLOBAL,cli),file=path.join(dir,'package.json'),pkg=JSON.parse(fs.readFileSync(file)); + pkg.version=a[2].slice(cli.length+1);fs.writeFileSync(file,JSON.stringify(pkg)); + const cf=path.join(dir,'node_modules',core,'package.json'),cp=JSON.parse(fs.readFileSync(cf)); + cp.version=e.MOCK_STALE?'3.8.2':pkg.version==='3.6.6'?'4.1.0':'3.9.0';fs.writeFileSync(cf,JSON.stringify(cp)); +} else throw Error('Unexpected operation'); +`; +for (const name of ["npm", "npx"]) fs.writeFileSync(path.join(temp, name), mock, { mode: 0o755 }); +const env = { ...process.env, PATH: `${temp}${path.delimiter}${process.env.PATH}`, FLOW2SPEC_SKIP_UPDATE_CHECK: "1", MOCK_GLOBAL: globalRoot, MOCK_LOG: log }; +function run(mode, extra = {}, dir = globalCli) { + fixture(globalCli, extra.MOCK_RANGE); + if (dir !== globalCli) fixture(dir); + fs.writeFileSync(log, ""); + const result = spawnSync(process.execPath, [path.join(dir, "cli.js"), "update", mode], { env: { ...env, ...extra }, encoding: "utf8" }); + result.calls = fs.readFileSync(log, "utf8").trim().split("\n").filter(Boolean).map(JSON.parse); + return result; +} +try { + const check = run("--check"); + assert.strictEqual(check.status, 0, check.stderr); + assert.match(check.stdout, /Core:\s+3.8.2 -> 3.9.0/); + assert.match(check.stdout, /兼容范围 \^3.8.2/); + assert.doesNotMatch(check.stdout, /4.1.0|3.10.0-beta/); + assert.match(check.stdout, /update --cli 一键更新/); + const zero = run("--check", { MOCK_RANGE: "^0.2.3" }); + assert.strictEqual(zero.status, 0, zero.stderr); + assert.match(zero.stdout, /Core:\s+3.8.2 -> 0.2.4/); + assert(zero.calls.some(c => c.args[1] === `${coreName}@^0.2.3`)); + const core = run("--core"); + assert.strictEqual(core.status, 0, core.stderr); + assert.deepStrictEqual(core.calls.filter(c => c.args[0] === "install").map(c => c.args), [["install", "-g", `${cliName}@3.6.5`]]); + assert(core.calls.some(c => c.args[0] === "uninstall")); + assert.match(core.stdout, /Core 实际生效版本 v3.9.0,验证通过/); + const cli = run("--cli"); + assert.strictEqual(cli.status, 0, cli.stderr); + assert.match(cli.stdout, /CLI v3.6.6;Core 实际生效版本 v4.1.0/); + const stale = run("--core", { MOCK_STALE: "1" }); + assert.strictEqual(stale.status, 1); + assert.match(stale.stderr, /期望 v3.9.0/); + assert.doesNotMatch(stale.stdout, /✓/); + const failed = run("--core", { MOCK_FAIL: "1" }); + assert.strictEqual(failed.status, 1); + assert(!failed.calls.some(c => c.args[0] === "install")); + const installFailed = run("--core", { MOCK_INSTALL_FAIL: "1" }); + assert.strictEqual(installFailed.status, 1); + assert.doesNotMatch(installFailed.stdout, /✓/); + const npx = run("--core", {}, localCli); + assert.strictEqual(npx.status, 0, npx.stderr); + assert(npx.calls.some(c => c.cmd === "npx" && c.args.includes(`${cliName}@3.6.5`) && c.args.includes("--cache"))); + assert(!npx.calls.some(c => ["install", "uninstall"].includes(c.args[0]))); + assert.strictEqual(run("--core", { MOCK_STALE: "1" }, localCli).status, 1); + assert.strictEqual(run("--unknown").status, 1); + const help = spawnSync(process.execPath, [path.join(globalCli, "cli.js"), "--help"], { env, encoding: "utf8" }); + assert.strictEqual(help.status, 0, help.stderr); + assert.match(help.stdout, /--core\s+保持当前 CLI 版本/); + console.log("test-cli-update: ok"); +} finally { + fs.rmSync(temp, { recursive: true, force: true }); } - -const env = { - ...process.env, - PATH: `${tempDir}${path.delimiter}${process.env.PATH || ""}`, - FLOW2SPEC_SKIP_UPDATE_CHECK: "1", -}; -const check = spawnSync(process.execPath, [cliPath, "update", "--check"], { - cwd: path.resolve(__dirname, ".."), - env, - encoding: "utf8", -}); -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(`pin Core ${escapeRe(coreRange)}`)); -assert.match(check.stdout, /update --cli 一键更新/); - -const invalid = spawnSync(process.execPath, [cliPath, "update", "--unknown"], { - cwd: path.resolve(__dirname, ".."), - env, - encoding: "utf8", -}); -assert.strictEqual(invalid.status, 1); -assert.match(invalid.stderr, /update --check\|--cli\|--core/); - -console.log("test-cli-update: ok"); diff --git a/scripts/test-workspace-version.js b/scripts/test-workspace-version.js index 00f0191..098bbaf 100644 --- a/scripts/test-workspace-version.js +++ b/scripts/test-workspace-version.js @@ -6,7 +6,9 @@ const os = require("os"); const path = require("path"); const { checkWorkspaceVersion, - normalizeCorePin, + normalizeCoreRange, + satisfiesCoreRange, + compareVersions, normalizeVersion, setCliVersion, setCoreVersion, @@ -33,9 +35,39 @@ 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(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/); +assert.strictEqual(normalizeCoreRange("^3.5.0"), "^3.5.0"); +for (const invalid of ["3.5.0", ">3.5.0", "~3.5.0", "*", "^3.5", "^3.05.0"]) { + assert.throws(() => normalizeCoreRange(invalid), /caret compatibility range/); +} +for (const [version, range, expected] of [ + ["3.5.0", "^3.5.0", true], + ["3.9.9", "^3.5.0", true], + ["4.0.0", "^3.5.0", false], + ["3.4.9", "^3.5.0", false], + ["3.6.0-beta.1", "^3.5.0", false], + ["4.0.0-beta.1", "^3.5.0", false], + ["3.5.0-beta.2", "^3.5.0-beta.1", true], + ["3.5.0", "^3.5.0-beta.1", true], + ["3.5.1-beta.1", "^3.5.0-beta.1", false], + ["3.5.0-beta.1", "^3.5.0-beta.2", false], + ["3.5.0+build.4", "^3.5.0+build.1", true], + ["0.2.9", "^0.2.3", true], + ["0.3.0", "^0.2.3", false], + ["0.2.2", "^0.2.3", false], + ["0.0.3", "^0.0.3", true], + ["0.0.4", "^0.0.3", false], + ["0.0.3-beta.2", "^0.0.3-beta.1", true], + ["0.0.4-beta.1", "^0.0.3-beta.1", false], +]) assert.strictEqual(satisfiesCoreRange(version, range), expected, `${version} satisfies ${range}`); +assert(compareVersions("1.0.0-beta.10", "1.0.0-beta.2") > 0); +assert(compareVersions("1.0.0-1", "1.0.0-alpha") < 0); +assert(compareVersions("1.0.0-alpha", "1.0.0-alpha.1") < 0); +assert(compareVersions("1.0.0-B", "1.0.0-a") < 0); + +// Normalize the copied fixture explicitly; tests do not depend on the live dependency floor. +setCliVersion("3.5.0", { rootDir: tempRoot, coreRange: "^3.0.0" }); +setCoreVersion("3.5.0", { rootDir: tempRoot }); +setCliVersion("3.5.0", { rootDir: tempRoot, coreRange: "^3.5.0" }); setCoreVersion("3.6.0", { rootDir: tempRoot }); setCliVersion("3.5.1", { rootDir: tempRoot }); @@ -44,19 +76,28 @@ assert.deepStrictEqual(checkWorkspaceVersion({ rootDir: tempRoot, tag: "core-v3. cliVersion: "3.5.1", coreVersion: "3.6.0", templateVersion: "3.5.2", - corePin: "3.6.0", + coreRange: "^3.5.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", - corePin: "3.6.0", + coreRange: "^3.5.0", protocolVersion: 2, }); assert.throws(() => checkWorkspaceVersion({ rootDir: tempRoot, tag: "v3.6.0" }), /release tag must match/); -// set-core 联动同步 pin:任意新版本都应成功并把 CLI 依赖 pin 到同版本。 -assert.deepStrictEqual(setCoreVersion("4.0.0", { rootDir: tempRoot }), { coreVersion: "4.0.0", corePin: "4.0.0" }); +const mutablePaths = ["packages/core/package.json", "packages/cli/package.json", "package-lock.json"]; +const snapshot = () => mutablePaths.map((file) => fs.readFileSync(path.join(tempRoot, file), "utf8")); +const beforeRejectedUpdates = snapshot(); +for (const incompatible of ["4.0.0", "3.4.9", "3.7.0-beta.1"]) { + assert.throws(() => setCoreVersion(incompatible, { rootDir: tempRoot }), /does not satisfy CLI dependency/); + assert.deepStrictEqual(snapshot(), beforeRejectedUpdates, "incompatible Core must fail before writes"); +} +assert.throws(() => setCliVersion("3.5.2", { rootDir: tempRoot, coreRange: "^4.0.0" }), /does not satisfy CLI dependency/); +assert.deepStrictEqual(snapshot(), beforeRejectedUpdates, "incompatible range must fail before writes"); +assert.deepStrictEqual(setCoreVersion("3.6.1", { rootDir: tempRoot }), { coreVersion: "3.6.1", coreRange: "^3.5.0" }); +assert.strictEqual(fs.readFileSync(path.join(tempRoot, "packages/cli/package.json"), "utf8"), beforeRejectedUpdates[1]); setCoreVersion("3.6.0", { rootDir: tempRoot }); const corePackage = require(path.join(tempRoot, "packages/core/package.json")); @@ -65,9 +106,27 @@ 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.5.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"); +assert.strictEqual(lockfile.packages["packages/cli"].dependencies["@double-coding/flow2spec-core"], "^3.5.0"); + +assert.throws(() => checkWorkspaceVersion({ rootDir: tempRoot, tag: "core-v3.6.1" }), /release tag version/); +lockfile.packages["packages/cli"].dependencies["@double-coding/flow2spec-core"] = "^3.4.0"; +fs.writeFileSync(path.join(tempRoot, "package-lock.json"), JSON.stringify(lockfile)); +assert.throws(() => checkWorkspaceVersion({ rootDir: tempRoot }), /package-lock.json CLI dependency/); +lockfile.packages["packages/cli"].dependencies["@double-coding/flow2spec-core"] = "^3.5.0"; +fs.writeFileSync(path.join(tempRoot, "package-lock.json"), JSON.stringify(lockfile)); +for (const locale of ["zh-CN", "en-US"]) { + const manifestPath = path.join(tempRoot, "packages/core/templates", locale, "knowledge/manifest-routing.json"); + const original = fs.readFileSync(manifestPath, "utf8"); + const manifest = JSON.parse(original); + manifest.version = "0.0.1"; + fs.writeFileSync(manifestPath, JSON.stringify(manifest)); + assert.throws(() => checkWorkspaceVersion({ rootDir: tempRoot }), /manifest-routing.json version/); + fs.writeFileSync(manifestPath, original); +} +checkWorkspaceVersion({ rootDir: tempRoot }); +fs.rmSync(tempRoot, { recursive: true, force: true }); console.log("test-workspace-version: ok"); diff --git a/scripts/workspace-version.js b/scripts/workspace-version.js index dcc0361..0d0bccc 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_PIN_PATTERN = new RegExp(`^(${SEMVER_SOURCE})$`); +const CORE_RANGE_PATTERN = new RegExp(`^\\^(${SEMVER_SOURCE})$`); function readJson(filePath) { return JSON.parse(fs.readFileSync(filePath, "utf8")); @@ -75,14 +75,39 @@ function compareVersions(left, right) { if (a.prerelease === b.prerelease) return 0; if (!a.prerelease) return 1; if (!b.prerelease) return -1; - return a.prerelease.localeCompare(b.prerelease, "en", { numeric: true }); + const leftParts = a.prerelease.split("."); + const rightParts = b.prerelease.split("."); + for (let index = 0; index < Math.max(leftParts.length, rightParts.length); index += 1) { + const leftPart = leftParts[index]; + const rightPart = rightParts[index]; + if (leftPart === undefined) return -1; + if (rightPart === undefined) return 1; + if (leftPart === rightPart) continue; + const leftNumeric = /^\d+$/.test(leftPart); + const rightNumeric = /^\d+$/.test(rightPart); + if (leftNumeric && rightNumeric) return BigInt(leftPart) < BigInt(rightPart) ? -1 : 1; + if (leftNumeric !== rightNumeric) return leftNumeric ? -1 : 1; + return leftPart < rightPart ? -1 : 1; + } + return 0; } -function normalizeCorePin(input) { +function normalizeCoreRange(input) { const raw = String(input || "").trim(); - 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]); + const match = CORE_RANGE_PATTERN.exec(raw); + if (!match) throw new Error(`Core dependency must use a caret compatibility range (^x.y.z), received: ${raw || ""}`); + return `^${normalizeVersion(match[1])}`; +} + +function satisfiesCoreRange(version, input) { + const lower = parseVersion(normalizeCoreRange(input).slice(1)); + const candidate = parseVersion(version); + if (compareVersions(candidate.version, lower.version) < 0) return false; + // Prereleases are eligible only when the range explicitly opts into the same tuple. + if (candidate.prerelease && (!lower.prerelease || candidate.numbers.some((value, index) => value !== lower.numbers[index]))) return false; + const [major, minor, patch] = lower.numbers; + const upper = major > 0 ? `${major + 1}.0.0` : minor > 0 ? `0.${minor + 1}.0` : `0.0.${patch + 1}`; + return compareVersions(candidate.version, upper) < 0; } function collectVersionErrors(workspace, tag) { @@ -90,7 +115,7 @@ function collectVersionErrors(workspace, tag) { const cliVersion = String(cli.version || "").trim(); const coreVersion = String(core.version || "").trim(); const templateVersion = String(core.templateVersion || "").trim(); - const corePin = String(cli.dependencies?.[CORE_PACKAGE] || "").trim(); + const coreRange = String(cli.dependencies?.[CORE_PACKAGE] || "").trim(); const protocolVersion = capabilities.protocolVersion; const errors = []; const expect = (actual, wanted, label) => { @@ -111,10 +136,8 @@ function collectVersionErrors(workspace, tag) { } try { - 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)`); + if (!satisfiesCoreRange(coreVersion, coreRange)) { + errors.push(`Core version ${coreVersion} does not satisfy CLI dependency ${coreRange} (review compatibility and use set-cli --core-range explicitly)`); } } catch (error) { errors.push(`packages/cli/package.json dependency ${CORE_PACKAGE}: ${error.message}`); @@ -128,7 +151,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(), corePin, `package-lock.json CLI dependency ${CORE_PACKAGE}`); + expect(String(lock.packages?.["packages/cli"]?.dependencies?.[CORE_PACKAGE] || "").trim(), coreRange, `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}`); @@ -151,7 +174,12 @@ function collectVersionErrors(workspace, tag) { } } - return { errors, cliVersion, coreVersion, templateVersion, corePin, protocolVersion }; + return { errors, cliVersion, coreVersion, templateVersion, coreRange, protocolVersion }; +} + +function validateWorkspace(workspace) { + const { errors } = collectVersionErrors(workspace); + if (errors.length) throw new Error(`workspace version check failed:\n- ${errors.join("\n- ")}`); } function checkWorkspaceVersion(options = {}) { @@ -168,16 +196,16 @@ function setCliVersion(input, options = {}) { const rootDir = path.resolve(options.rootDir || path.join(__dirname, "..")); const version = normalizeVersion(input); const workspace = loadWorkspace(rootDir); - // pin 自动对齐当前 Core 版本(联动发版策略)。 - const corePin = normalizeVersion(workspace.core.version); + const coreRange = normalizeCoreRange(options.coreRange === undefined ? workspace.cli.dependencies[CORE_PACKAGE] : options.coreRange); workspace.cli.version = version; - workspace.cli.dependencies[CORE_PACKAGE] = corePin; + workspace.cli.dependencies[CORE_PACKAGE] = coreRange; workspace.lock.packages["packages/cli"].version = version; - workspace.lock.packages["packages/cli"].dependencies[CORE_PACKAGE] = corePin; + workspace.lock.packages["packages/cli"].dependencies[CORE_PACKAGE] = coreRange; + validateWorkspace(workspace); writeJson(workspace.paths.cli, workspace.cli); writeJson(workspace.paths.lock, workspace.lock); checkWorkspaceVersion({ rootDir }); - return { cliVersion: version, corePin }; + return { cliVersion: version, coreRange }; } function setCoreVersion(input, options = {}) { @@ -186,14 +214,12 @@ function setCoreVersion(input, options = {}) { const workspace = loadWorkspace(rootDir); 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; + // Core can advance independently within the existing CLI compatibility range. + validateWorkspace(workspace); writeJson(workspace.paths.core, workspace.core); - writeJson(workspace.paths.cli, workspace.cli); writeJson(workspace.paths.lock, workspace.lock); checkWorkspaceVersion({ rootDir }); - return { coreVersion: version, corePin: version }; + return { coreVersion: version, coreRange: workspace.cli.dependencies[CORE_PACKAGE] }; } function setTemplateVersion(input, options = {}) { @@ -234,13 +260,13 @@ function main(args = process.argv.slice(2)) { return; } if (command === "set-cli") { - const result = setCliVersion(versionArgument(rest, "usage: npm run version:set:cli -- ")); - console.log(`CLI version updated: ${result.cliVersion} (Core pinned ${result.corePin})`); + 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 compatibility ${result.coreRange})`); return; } if (command === "set-core") { const result = setCoreVersion(versionArgument(rest, "usage: npm run version:set:core -- ")); - console.log(`Core version updated: ${result.coreVersion} (CLI pin synced; remember to bump CLI patch — release in lockstep)`); + console.log(`Core version updated: ${result.coreVersion} (CLI compatibility unchanged: ${result.coreRange})`); return; } if (command === "set-template") { @@ -264,7 +290,8 @@ module.exports = { checkWorkspaceVersion, collectVersionErrors, compareVersions, - normalizeCorePin, + normalizeCoreRange, + satisfiesCoreRange, normalizeVersion, setCliVersion, setCoreVersion,