@rowansenne/workflow-run 0.22.2 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,23 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.24.0] - 2026-10-21
8
+
9
+ ### 变更
10
+ - dev 新增规模确认:功能开发和缺陷修复在判定 S / M / L 后都需说明依据、快速通道及落盘安排,等用户明确确认再继续;可以和理解 / 方案确认同轮提出。实现中规模变化需重新确认,单纯的规模确认不影响 S 规模快速通道资格。
11
+
12
+ ### 变更(破坏性)
13
+ - DocNexus 按当前 format 7 重新接入:在业务项目根安装到 `.docnexus/`,运行 `init --output-dir workflow-run --agent none`,知识输出在 `workflow-run/docnexus/`。安装、包装脚本、模板导入、知识检索、doctor、卸载入口和所有 Agent 路径说明同步更新;Qwen 自动写入 `QWEN.md`。
14
+ - 强制删除旧版 `workflow-run/docnexus/` 的旧知识和内部运行文件、`workflow-run/.docnexus/`、旧重复备份及能识别的根目录旧安装,不迁移、不备份。新版输出的知识在重装后保留,工作流需求产物不受影响。移除此前的旧版调用与分类检索降级。
15
+
16
+ ## [0.23.0] - 2026-10-19
17
+
18
+ ### 新增
19
+ - project-review 的审查范围改由脚本决定(借鉴 alibaba/open-code-review 的做法):新脚本 `workflow/scripts/review-scope.mjs` 列出要审的文件和跳过的文件(删除、二进制、敏感文件、依赖目录、生成文件与锁文件、文档、本工具产物,各带原因;`--base` 含未提交和未跟踪的新文件),给出每个文件的风险级别、分批,以及按文件类型和改动内容该看审查清单的哪几节;报告里每个文件都要有交代。`rules <文件…>` 只打印这些小节的原文,团队在 DocNexus 改过的清单也适用。按改动内容判断风险和小节时不看注释和 JS / TS 的正则字面量;`*.md.tpl` 算文档;画像里写错的 glob(如没闭合的 `{`)按普通字符处理。
20
+ - 审查发现按原代码片段定位行号:新脚本 `workflow/scripts/locate.mjs` 读发现文件(每条带 `file` 和 `code`),在文件里查找片段算出起止行,标出是否落在本次改动上;找不到的要回去重读代码,仍找不到就降级为待确认。报告里的行号一律用它的结果。
21
+ - 画像新增可选的 `review` 字段:`exclude` / `include` 调整审查范围,`rules` 按路径 glob 给文件加项目规则,和审查清单一起检查。
22
+ - dev 自审用 `review-scope.mjs --base <主分支>`,Step 0 确认与本需求无关的文件用 `--exclude` 排除。
23
+
7
24
  ## [0.22.2] - 2026-10-19
8
25
 
9
26
  ### 文档
package/README.md CHANGED
@@ -179,9 +179,9 @@ npx @rowansenne/workflow-run docs-repo git@git.example.com:team/team-docs.git
179
179
  /dev --bug <现象 | 报错 | 日志> # 缺陷修复
180
180
  ```
181
181
 
182
- * **功能开发**:理解需求(可读 PRD、查 DocNexus 历史决策)→ 调研现有代码 → 需求澄清(借鉴 grill-me:按决策树分轮提问,每题给推荐答案;事实自己查、决定交给用户;S 规模无未定决定时跳过)→ 按规模出方案(S 直接做;M 对话内方案待确认;L 落盘方案文档待确认)→ 小步实现并编译 → 补测试并运行 → 用 project-review 自审 → 交付说明落盘到 `workflow-run/docs/requirements/<需求名>/<项目名>/delivery.md`(带分支元信息;改动、验证结果、配置清单、未执行的 SQL),已接入 DocNexus 时每次都问要不要沉淀(同意后以 `dev-notes` 分类写入,并把库内路径记回交付说明)。审查和上线方案会按分支自动读取这份交付说明。
183
- * **S 规模快速通道**:S 规模且不涉及配置、SQL / DDL、对外接口、表结构,也没有需要澄清的决定时,编译、测试、自审照常,但只在对话里交付,不落盘交付说明、不问沉淀;实现中碰到上述任何一项或改动超过 3 个文件,就退回完整流程。缺陷修复同样适用(根因仍须在对话里写清楚)。
184
- * **缺陷修复**:收集现象 → 用失败测试复现 → 用证据定位根因 → 最小修复让测试转绿 → 自审 → 交付(必须写明根因)。
182
+ * **功能开发**:理解需求(可读 PRD、查 DocNexus 历史决策)→ 调研现有代码 → 需求澄清(借鉴 grill-me:按决策树分轮提问,每题给推荐答案;事实自己查、决定交给用户;S 规模无未定决定时跳过澄清提问)→ **规模确认(S / M / L 都说明判定依据、对应流程并等用户确认)** → 按规模出方案(S 列步骤开始;M 对话内方案待确认;L 落盘方案文档待确认)→ 小步实现并编译 → 补测试并运行 → 用 project-review 自审 → 交付说明落盘到 `workflow-run/docs/requirements/<需求名>/<项目名>/delivery.md`(带分支元信息;改动、验证结果、配置清单、未执行的 SQL),已接入 DocNexus 时每次都问要不要沉淀(同意后以 `dev-notes` 分类写入,并把库内路径记回交付说明)。审查和上线方案会按分支自动读取这份交付说明。
183
+ * **S 规模快速通道**:先经用户确认 S 规模及快速通道,再按条件执行:不涉及配置、SQL / DDL、对外接口、表结构,也没有需要澄清的业务决定时,编译、测试、自审照常,但只在对话里交付,不落盘交付说明、不问沉淀;实现中碰到上述任何一项或改动超过 3 个文件,就退回完整流程,规模变化时先确认新规模。规模确认本身不影响快速通道资格。缺陷修复同样适用(根因仍须在对话里写清楚)。
184
+ * **缺陷修复**:收集现象 → 用失败测试复现 → 用证据定位根因 → **判定规模并等用户确认** → 最小修复让测试转绿 → 自审 → 交付(必须写明根因)。
185
185
  * 守则:不写库、不改凭据、不擅自 commit / push、不装新依赖;测试没跑就写「未验证」。
186
186
  * 其他 skill 都是可选增强:wiki、DocNexus、数据库没配置时自动跳过,并在交付说明中注明。
187
187
 
@@ -207,38 +207,71 @@ npx @rowansenne/workflow-run docs-repo git@git.example.com:team/team-docs.git
207
207
 
208
208
  ## DocNexus 文档库(可选)
209
209
 
210
- 加 `--with-docnexus` 时,脚本在装完本仓库的 skills 后额外执行:
210
+ 接入支持 `--output-dir` 的 DocNexus format 7,在**业务项目根**执行安装和初始化:
211
211
 
212
- 1. 检查 Node >= 22.13 与 npm(不满足时**在安装任何文件之前**报错退出);
213
- 2. 在 `workflow-run/` 下执行 `npm install --prefix ./docnexus --save-exact @rowansenne/docnexus`:装在 `workflow-run/docnexus/` 下,npm 清单、锁文件和依赖不进宿主项目的 `package.json`;
214
- 3. 在 `workflow-run/` 下执行 `./docnexus/node_modules/.bin/docnexus init --agent none`:创建 `workflow-run/docnexus/` 工作区,把 DocNexus 的 skills 同步到 `workflow-run/docnexus/skills/`;再由本工具把它们软链接到所选环境的 skills 目录、写入 `CLAUDE.md` / `AGENTS.md` 的段落(见下方「DocNexus 的位置」)。
212
+ ```bash
213
+ npm install --prefix ./.docnexus --save-exact @rowansenne/docnexus
214
+ ./.docnexus/node_modules/.bin/docnexus init --output-dir workflow-run --agent none
215
+ ```
216
+
217
+ `workflow-run --with-docnexus` 自动执行这两步,并链接所选 Agent 的 skills、写入项目说明、导入文档模板。
218
+
219
+ ### 运行环境与知识输出
220
+
221
+ ```text
222
+ <业务项目>/
223
+ ├── .docnexus/ # npm 运行环境与内部状态
224
+ │ ├── package.json / package-lock.json
225
+ │ ├── project.json # format_version=7,output_dir=workflow-run
226
+ │ ├── node_modules/ # CLI 和依赖
227
+ │ ├── skills/ / schemas/
228
+ │ └── drafts/ / store/ / .runtime/
229
+ └── workflow-run/
230
+ ├── docnexus/ # 知识输出,内部不安装 npm
231
+ │ ├── CONCEPTS.md / concepts/
232
+ │ └── library/ / records/ / impacts/
233
+ ├── docs/requirements/ # 工作流产物
234
+ └── tmp/
235
+ ```
215
236
 
216
- 依赖和检索库(`node_modules/`、`store/`)由 DocNexus 自带的 `docnexus/.gitignore` 忽略,它随工作区一起提交,所以随代码提交和独立文档仓库两种模式都不用另加规则。
237
+ 所有 DocNexus 命令均从业务项目根运行,CLI 返回的路径直接用于读取和引用,不添加或删除前缀:
238
+
239
+ ```bash
240
+ node .agents/skills/workflow/scripts/docnexus.mjs status
241
+ node .agents/skills/workflow/scripts/docnexus.mjs view
242
+ node .agents/skills/workflow/scripts/docnexus.mjs usage export > workflow-run/docnexus-usage.json
243
+ ```
217
244
 
218
- 环境映射(本工具链接 DocNexus 的 skills、写段落):
245
+ `status` 返回 `workspace_path`、`library_path`、`internal_directory`。知识输出固定为 `workflow-run/docnexus/`;草稿与辅助脚本位于 `.docnexus/`。模板保存在 `workflow-run/docnexus/library/templates/<skill>/`。
219
246
 
220
- | 所选环境 | 链接到 | 写段落 |
247
+ | 环境 | DocNexus skill 入口 | 项目说明 |
221
248
  | --- | --- | --- |
222
- | claude | `.claude/skills/` | `CLAUDE.md` |
223
- | codex / doubao | `.agents/skills/`(两者共用) | `AGENTS.md` |
224
- | workbuddy | `.codebuddy/skills/` | `AGENTS.md`(WorkBuddy 读项目的 `AGENTS.md`,与 `CODEBUDDY.md` 并存) |
225
- | qwen | `.qwen/skills/` | 不写 `QWEN.md`;需要自动召回时可在 `QWEN.md` 引用 `AGENTS.md` 的 DocNexus 段落,或在 `.qwen/settings.json` 的 `context.fileName` 加入 `AGENTS.md` |
249
+ | claude | `.claude/skills/` → `.docnexus/skills/` | `CLAUDE.md` |
250
+ | codex / doubao | `.agents/skills/` → `.docnexus/skills/` | `AGENTS.md` |
251
+ | workbuddy | `.codebuddy/skills/` → `.docnexus/skills/` | `AGENTS.md` |
252
+ | qwen | `.qwen/skills/` → `.docnexus/skills/` | `QWEN.md` |
226
253
 
227
- 说明:
254
+ 升级 / 重装刷新入口与说明,不改手写段落。知识写入仍走 DocNexus 的授权与 capture 流程。普通 `uninstall` 移除 Agent 入口和说明,保留 `.docnexus/` 运行环境与知识输出。
228
255
 
229
- * DocNexus 的 skills 不在本仓库中,始终以安装的 npm 包为准;重跑脚本会按包版本重新同步与链接(`docnexus init` 可重复执行,不影响已有记忆)。
230
- * `.qwen/skills/`、`.codebuddy/skills/` 下已存在的同名**真实目录**不会被替换,脚本会提示跳过。
231
- * npm 安装或 `docnexus init` 失败时只跳过 DocNexus,本仓库的 skills 照常安装,脚本最后以退出码 1 结束;修好后重跑即可。
232
- * 安装后 `dev`、`project-review` 会先按 `docnexus-recall` 查项目约定;`dev`、`project-review` 交付时**每次都问**要不要沉淀(写入后把库内路径记回交付说明 / 审查报告的「DocNexus」元信息行),`create-tech-md-doc`、`create-release-md-doc` 结束时有值得记的内容才**提议**沉淀;**任何写入 DocNexus 的操作都必须经你授权**。
233
- * 离线或测试时可用环境变量指定安装来源,例如 `DOCNEXUS_PKG=/path/to/rowansenne-docnexus-0.13.0.tgz npx @rowansenne/workflow-run --with-docnexus`(PowerShell:`$env:DOCNEXUS_PKG=...`)。
234
- * Windows 上的链接使用目录软链接(需管理员终端)。
256
+ `.docnexus/.gitignore` 由 DocNexus 维护,忽略运行依赖、索引、草稿和内置资源;`.docnexus/project.json`、npm 清单和锁文件应随代码提交。知识输出随 `workflow-run/` 提交;独立文档仓库模式下只在文档仓库提交知识,本项目仍保存自己的 `.docnexus/` 配置和运行环境。
235
257
 
236
- ### DocNexus 的位置
258
+ ### 破坏性更新:旧布局强制清理
259
+
260
+ 本次不兼容旧 DocNexus,不迁移、不备份旧知识。`init` / `upgrade` 在安装前强制删除:
261
+
262
+ - 旧版 `workflow-run/docnexus/`(含旧 `project.json`、npm / skills / store 等内部文件的布局);
263
+ - 嵌套的 `workflow-run/.docnexus/` 及 `workflow-run/tmp/duplicate-docnexus/` 旧备份;
264
+ - 能识别的根目录旧 `docnexus/` 安装、旧 Agent 链接与不兼容的内部项目状态。
265
+
266
+ 新版知识输出不含这些旧内部文件,后续重装 / 升级会保留新版 `library/`、`records/` 等知识。工作流的需求文档、其他临时文件和普通同名业务目录保留。清理失败会停止;npm / init 失败返回非零。
267
+
268
+ 已安装项目重新接入:
269
+
270
+ ```bash
271
+ npx @rowansenne/workflow-run upgrade --with-docnexus
272
+ ```
237
273
 
238
- * DocNexus 装在 `workflow-run/docnexus/`,以 `workflow-run/` 为项目根运行(`docnexus init --agent none`:它自己不往 `workflow-run/` 里链接 skills、写 `CLAUDE.md`)。
239
- * 本项目里由 workflow-run 接上:把 `workflow-run/docnexus/skills/*` 相对链接到 `.claude/skills` / `.agents/skills` / `.qwen/skills` / `.codebuddy/skills`,在 `CLAUDE.md` / `AGENTS.md` 写一段说明(概念索引 `@workflow-run/docnexus/CONCEPTS.md`;docnexus 系列 skill 的命令改用 `node <skills>/workflow/scripts/docnexus.mjs` 运行,不用先 `cd`),用自己的标记,不碰手写内容。升级时刷新,卸载时撤掉,`workflow-run/docnexus/` 本身不动。
240
- * 各 skill 的脚本已按位置处理:`resolve.mjs status` 给出 `docnexus.root` 与 `run`(`node <skills>/workflow/scripts/docnexus.mjs`),`knowledge.mjs` 在那里运行并给出从项目根读正文的路径。
241
- * `workflow-run/` 是独立文档仓库时,知识库随文档仓库提交,各服务共用;沉淀后的改动和产物一样由你确认后提交(`requirement.mjs pending` 单独列出)。DocNexus 的依赖和检索库(`node_modules/`、`store/`)由 `docnexus/.gitignore` 忽略、不进仓库,各自 `--with-docnexus` 装一次。
274
+ 新项目用 `npx @rowansenne/workflow-run --with-docnexus`。离线或本地验证可设 `DOCNEXUS_PKG=/path/to/package-or-source` 指定安装来源;默认从 npm 安装,必须使用支持 format 7 和 `--output-dir` 的版本。
242
275
 
243
276
  ## CodeGraph 代码图谱与 code-graph skill(可选)
244
277
 
@@ -397,8 +430,8 @@ npx @rowansenne/workflow-run uninstall
397
430
  | `workflow/scripts/resolve.mjs template <skill> <文件名>` | 判定用哪份模板:已接入 DocNexus 只用知识库,缺失时退出码 2 并给出导入来源;未接入时项目文件优先,其次默认模板 |
398
431
  | `workflow/scripts/resolve.mjs profile` | 读项目画像,列出空字段 |
399
432
  | `workflow/scripts/resolve.mjs env` | 本机系统与终端(安装时记录在 `profile.local.json`),写终端命令按它来 |
400
- | `workflow/scripts/docnexus.mjs <参数>` / `--node <脚本> <参数>` | 在 `workflow-run/` 下运行 DocNexus CLI 或它的 node 脚本,不用先 `cd`,各终端写法一样;Windows 上直接用 node 跑 CLI 入口,不经过 `.cmd` |
401
- | `workflow/scripts/knowledge.mjs <skill> [--match 词] [--limit n] [--query 问题]` | 按 `readFrom` 的分类列出 DocNexus 文档(标题、摘要、路径,不含正文);`--query` 再在每个分类内 `recall --category`(DocNexus 0.16.0 起),旧版自动退回只列文档并提示。未接入时退出码 2 |
433
+ | `workflow/scripts/docnexus.mjs <参数>` / `--node <脚本> <参数>` | 在业务项目根运行 `.docnexus/` 中的 CLI 或它的 node 脚本,知识输出到 `workflow-run/docnexus/`;Windows 上直接用 node 跑 CLI 入口,不经过 `.cmd` |
434
+ | `workflow/scripts/knowledge.mjs <skill> [--match 词] [--limit n] [--query 问题]` | 按 `readFrom` 的分类列出 DocNexus 文档(标题、摘要、路径,不含正文);`--query` 再在每个分类内 `recall --category`,直接使用新版按分类检索能力。未接入时退出码 2 |
402
435
  | `workflow/scripts/resolve.mjs category <skill>` | 该 skill 写入 DocNexus 时的显式分类(画像 `docnexus_categories` 优先,其次 skill.json),输出 `seal_args` 与 `metadata`;未声明时退出码 1 |
403
436
  | `workflow/scripts/resolve.mjs status` | `/workflow status` 的能力探测(不输出任何凭据) |
404
437
  | `workflow/scripts/resolve.mjs infer-profile` | 按固定规则从构建文件、Makefile、依赖与配置文件、git 推断项目画像草稿,每个字段附依据;不写文件 |
@@ -408,6 +441,8 @@ npx @rowansenne/workflow-run uninstall
408
441
  | `workflow/scripts/artifacts.mjs [--branch b \| --all]` | 按分支汇总 `workflow-run/docs/requirements/` 下的开发方案、交付说明、审查报告、技术方案、上线方案及其 wiki 发布记录,给出下一步建议;分支取自文档元信息行,不另存记录。上线方案没传 `--tech` 时据此自动引用同分支的技术方案;project-review 据此对照本分支方案、识别复审 |
409
442
  | `workflow/scripts/report.mjs [--since d] [--all-branches] [--json]` | 本地效果报告(`/workflow report`、`npx @rowansenne/workflow-run report`):审查发现与修复率、复核率、需求澄清、沉淀率、知识引用、各分支链路 |
410
443
  | `workflow/scripts/feedback.mjs add … / summary [--json]` | 运行反馈:dev / project-review 交付后追加一行过程记录到 `workflow-run/feedback/<用户>.jsonl`;`summary` 按 skill 汇总,跳过的步骤和不清楚的规则按 SKILL.md 章节归类(`npx @rowansenne/workflow-run feedback`);`init` / `upgrade` 装了新版本时清掉本项目旧版本的记录 |
444
+ | `workflow/scripts/review-scope.mjs [--diff b...h \| --base b] [--exclude a,b]` / `rules <文件…>` | 审查范围:由脚本决定审哪些文件、跳过哪些(删除、二进制、敏感文件、依赖目录、生成文件、文档、本工具产物,各带原因)、每个文件的风险级别和分批,以及按文件类型和改动内容该看审查清单的哪几节;`--base` 含未提交和未跟踪的新文件。`rules` 打印这些小节的原文和画像 `review.rules` 里匹配的项目规则。project-review 第 1、3、4 步与 dev 自审使用 |
445
+ | `workflow/scripts/locate.mjs <发现.json> [--diff b...h \| --base b]` | 给审查发现定位行号:按每条发现引用的原代码片段在文件里查找(精确 → 忽略空白 → 单行部分匹配),多处时优先本次改动的行,输出起止行、是否落在本次改动上;找不到的标出来要求重读代码。project-review 第 6 步复核使用 |
411
446
  | `workflow/scripts/tables.mjs [--diff b...h \| --base b \| --files a,b]` | 找出改动涉及哪些表:实体注解(`@TableName` / `@Table`,没写表名时按类名推断)、Mapper XML 与代码里的 SQL、DDL 脚本;标出 DDL、是否在 `schema.md` 里,并给出拉表结构的命令。dev 调研 / 自检与 project-review 审 SQL / DDL 时使用 |
412
447
  | `workflow/scripts/template.mjs list / draft / check / save <模板>` | 修改 / 创建模板(`/workflow template`):把当前模板复制成草稿(或 `--from <样例>` 抽骨架),`check` 校验格式、列出章节变化、拿最近几份已生成文档试跑 lint 看影响,`save` 给出写入 DocNexus 知识库的 capture 指引,不直接写知识库;未接入 DocNexus 时 `draft` / `save` 提示先接入(默认模板只读) |
413
448
  | `workflow/scripts/pr-desc.mjs [--branch b] [--base m] [--out f]` | 按本分支产物生成 PR / MR 描述;没有交付说明时退回用 git 记录 |
@@ -453,7 +488,8 @@ CI(`.github/workflows/test.yml`)在 push 到 main 和每个 PR 上运行:m
453
488
  | `.agents/skills/database-query/references/schema.md` | 表结构速查,补齐后提交 |
454
489
  | `workflow-run/docs/requirements/` | 需求产物;独立文档仓库模式下提交到文档仓库 |
455
490
  | `workflow-run/feedback/` | 运行反馈,每人一个文件;独立文档仓库模式下提交到文档仓库 |
456
- | `workflow-run/docnexus/` | DocNexus 工作区与定制的文档模板;依赖和检索库由它自带的 `docnexus/.gitignore` 忽略。独立文档仓库模式下提交到文档仓库 |
491
+ | `.docnexus/project.json`、npm 清单与锁文件 | DocNexus 项目配置与运行依赖版本,随代码提交 |
492
+ | `workflow-run/docnexus/` | DocNexus 知识输出与定制的文档模板。独立文档仓库模式下提交到文档仓库 |
457
493
  | `.ignore` | 让代码搜索跳过需求目录(不影响 git) |
458
494
  | `.gitignore`、`CLAUDE.md` / `AGENTS.md` | 安装器追加的忽略规则、DocNexus 说明段落 |
459
495
 
@@ -686,7 +722,7 @@ npx @rowansenne/workflow-run seed-templates --diff # 团队改过
686
722
  ```
687
723
 
688
724
  * 只导入 DocNexus 里还没有的、有默认版本的模板(技术方案、上线方案不在内),已有的不覆盖;项目已接入但缺模板时,安装结束会列出缺哪几份。
689
- * 之后模板随知识库更新:直接编辑 `workflow-run/docnexus/library/templates/...` 后在 `workflow-run/` 下执行 `./docnexus/node_modules/.bin/docnexus document sync --id <id>` 采纳;或者在生成文档后,skill 发现你做了结构性调整时会提议同步回模板(你确认后才写入)。每次变更都记录在 DocNexus 的 timeline 里,可追溯。
725
+ * 之后模板随知识库更新:直接编辑 `workflow-run/docnexus/library/templates/...` 后在业务项目根执行 `./.docnexus/node_modules/.bin/docnexus document sync --id <id>` 采纳;或者在生成文档后,skill 发现你做了结构性调整时会提议同步回模板(你确认后才写入)。每次变更都记录在 DocNexus 的 timeline 里,可追溯。
690
726
  * 模板里的 `{{...}}` 是给 agent 的填写提示,DocNexus 原样保存;不要写成单独一行的 `{{fact:...}}`(那是 DocNexus 的事实引用语法)。
691
727
  * 以前放在 `workflow-run/templates/` 下的项目模板:接入 DocNexus 后,缺模板时 skill 会把它作为 `import_from` 提议导入知识库;导入后删除这些文件。
692
728
 
@@ -717,4 +753,4 @@ node scripts/eval-triggers.mjs --only dev # 只跑期望为 dev 的用例
717
753
 
718
754
  * **Qwen Code** 项目级为 `.qwen/skills/`,自动发现。
719
755
 
720
- * **腾讯 WorkBuddy** 读项目工作目录下的 `.codebuddy/`(与 CodeBuddy Code 一致),项目级技能在 `.codebuddy/skills/`,同名时优先于用户级;它也读 `AGENTS.md`。
756
+ * **腾讯 WorkBuddy** 读项目工作目录下的 `.codebuddy/`(与 CodeBuddy Code 一致),项目级技能在 `.codebuddy/skills/`,同名时优先于用户级;它也读 `AGENTS.md`。
package/bin/lib/core.mjs CHANGED
@@ -6,6 +6,7 @@ import path from 'node:path';
6
6
  import { spawnSync } from 'node:child_process';
7
7
  import { fileURLToPath } from 'node:url';
8
8
  import { loadSkills, deriveMeta, withDependencies } from '../skills.mjs';
9
+ export { docnexusRoot, docnexusWorkspace, docnexusInternal, docnexusBinPath as docnexusBin } from '../../workflow/scripts/docnexus-layout.mjs';
9
10
 
10
11
  export const TPL_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
11
12
  export const PKG = JSON.parse(fs.readFileSync(path.join(TPL_DIR, 'package.json'), 'utf8'));
@@ -25,20 +26,10 @@ export const SKILLS = META.skills;
25
26
  export const USER_FILES = META.userFiles;
26
27
  export const DOCNEXUS_PKG = process.env.DOCNEXUS_PKG || '@rowansenne/docnexus';
27
28
  export const CODEGRAPH_PKG = process.env.CODEGRAPH_PKG || '@colbymchenry/codegraph@1'; // code-graph skill 按 1.x 的库接口编写
28
- // 本工具在项目里的唯一目录:需求产物、DocNexus 工作区、项目模板、临时文件都在这里(与 workflow/scripts/requirement.mjs 的 HOME 一致)
29
+ // 工作流产物与 DocNexus 知识输出的父目录;DocNexus 运行环境另在项目根 .docnexus/。
29
30
  export const HOME = 'workflow-run';
30
- // DocNexus 的项目根是 workflow-run/(工作区 workflow-run/docnexus/),CLI 在 workflow-run/docnexus/node_modules/.bin。
31
- // workflow/scripts/resolve.mjs 的 docnexusRoot / docnexusBin 与此一致
32
- export const DOCNEXUS_BIN_DIRS = [['docnexus', 'node_modules', '.bin']];
33
- export const docnexusRoot = (root) => path.resolve(root, HOME);
34
- export function docnexusBin(root) {
35
- const name = IS_WIN ? 'docnexus.cmd' : 'docnexus';
36
- const dn = docnexusRoot(root);
37
- for (const dir of DOCNEXUS_BIN_DIRS) { const p = path.join(dn, ...dir, name); if (fs.existsSync(p)) return p; }
38
- return null;
39
- }
40
31
  // 升级 DocNexus 的命令(在项目根执行;不用 cd / &&,PowerShell 5.1 也能直接运行)
41
- export const docnexusUpgradeCmd = () => `npm install --prefix ./${HOME}/docnexus --save-exact ${DOCNEXUS_PKG}@latest`;
32
+ export const docnexusUpgradeCmd = () => `npm install --prefix ./.docnexus --save-exact ${DOCNEXUS_PKG}@latest`;
42
33
  export const MANIFEST = '.workflow-run.json'; // 安装清单,位于每个安装目录(单一来源时只有 .agents/skills 一份)
43
34
 
44
35
  // ---------------------------------------------------------------- 输出
@@ -4,7 +4,74 @@ import fs from 'node:fs';
4
4
  import path from 'node:path';
5
5
  import { spawnSync } from 'node:child_process';
6
6
  import { templateStatus, STATUS_TEXT, TEMPLATES } from '../seed-templates.mjs';
7
- import { TPL_DIR, IS_WIN, DOCNEXUS_PKG, MANIFEST, PKG, CANON_DIR, step, ok, warn, fail, writeFile, readJson, toPosix, lstat, findManifests, removeLink, docnexusBin, docnexusRoot, HOME } from './core.mjs';
7
+ import { TPL_DIR, IS_WIN, DOCNEXUS_PKG, MANIFEST, PKG, CANON_DIR, step, ok, warn, fail, writeFile, readJson, toPosix, lstat, findManifests, removeLink, docnexusBin, docnexusWorkspace, docnexusInternal, HOME, INSTALL_BASES } from './core.mjs';
8
+
9
+ import { docnexusConfigured, docnexusConnected } from '../../workflow/scripts/docnexus-layout.mjs';
10
+ export { docnexusConnected } from '../../workflow/scripts/docnexus-layout.mjs';
11
+
12
+ // 破坏性升级:删除旧知识、运行环境与备份,不迁移、不回退。
13
+ // format 7 的知识输出不含 project.json / npm / skills 等内部文件,重复安装时保留它。
14
+ export function legacyDocNexusPaths(root) {
15
+ const workspace = docnexusWorkspace(root);
16
+ const oldOutput = ['project.json', 'package.json', 'node_modules', 'skills', 'schemas', 'drafts', 'store', '.runtime']
17
+ .some((name) => lstat(path.join(workspace, name)));
18
+ const marker = readJson(path.join(docnexusInternal(root), 'project.json'));
19
+ const legacyRoot = path.join(root, 'docnexus');
20
+ const rootPackage = readJson(path.join(legacyRoot, 'package.json'));
21
+ const oldRoot = fs.existsSync(path.join(legacyRoot, 'project.json'))
22
+ || Object.keys(rootPackage?.dependencies ?? {}).some((name) => /(^|\/)docnexus$/.test(name))
23
+ || (marker?.format_version === 7 && marker.output_dir === '.');
24
+ return [
25
+ ...(oldOutput ? [workspace] : []),
26
+ path.join(root, HOME, '.docnexus'),
27
+ path.join(root, HOME, 'tmp', 'duplicate-docnexus'),
28
+ ...(oldRoot ? [legacyRoot] : []),
29
+ ...INSTALL_BASES.flatMap((base) => ['docnexus', 'docnexus-extract', 'docnexus-ingest', 'docnexus-recall', 'docnexus-library', 'docnexus-maintain']
30
+ .map((name) => path.join(root, HOME, base, name))),
31
+ ].filter((dir) => lstat(dir));
32
+ }
33
+
34
+ export function cleanupLegacyDocNexus(root) {
35
+ root = path.resolve(root);
36
+ const obsolete = legacyDocNexusPaths(root);
37
+ // 目录本身的链接可移除;不能穿过父目录链接删除其他位置的文件。
38
+ for (const dir of obsolete) {
39
+ for (let parent = path.dirname(dir); parent !== path.resolve(root); parent = path.dirname(parent)) {
40
+ if (lstat(parent)?.isSymbolicLink()) fail(`旧 DocNexus 路径的父目录是软链接,停止清理:${parent}`);
41
+ }
42
+ }
43
+ const marker = readJson(path.join(docnexusInternal(root), 'project.json'));
44
+ const oldSkills = obsolete.map((dir) => path.join(dir, 'skills'));
45
+ for (const base of INSTALL_BASES) {
46
+ const dir = path.join(root, base);
47
+ for (const name of fs.existsSync(dir) ? fs.readdirSync(dir) : []) {
48
+ const link = path.join(dir, name);
49
+ if (!lstat(link)?.isSymbolicLink()) continue;
50
+ const target = path.resolve(dir, fs.readlinkSync(link).replace(/^\\\\\?\\/, ''));
51
+ if (oldSkills.some((src) => target.startsWith(src + path.sep))) removeLink(link);
52
+ }
53
+ }
54
+ for (const dir of obsolete) {
55
+ if (!lstat(dir)) continue;
56
+ if (lstat(dir).isSymbolicLink()) removeLink(dir); else fs.rmSync(dir, { recursive: true, force: true });
57
+ warn(`破坏性更新:已删除旧版 ${toPosix(path.relative(root, dir))}/(不迁移、不备份)`);
58
+ }
59
+ if (marker && !docnexusConfigured(root)) {
60
+ if (lstat(docnexusInternal(root))?.isSymbolicLink()) removeLink(docnexusInternal(root));
61
+ else {
62
+ for (const name of ['project.json', 'skills', 'schemas', 'drafts', 'store', '.runtime', '.docnexus-skills.json']) {
63
+ fs.rmSync(path.join(docnexusInternal(root), name), { recursive: true, force: true });
64
+ }
65
+ }
66
+ warn('破坏性更新:已清除旧版 .docnexus/ 的项目状态,运行依赖保留供重新初始化');
67
+ }
68
+ for (const file of ['CLAUDE.md', 'AGENTS.md', 'QWEN.md']) {
69
+ dropBlock(path.join(root, HOME, file), CTX_START, CTX_END);
70
+ dropBlock(path.join(root, HOME, file), '<!-- docnexus:start -->', '<!-- docnexus:end -->');
71
+ if (obsolete.length || (marker && !docnexusConfigured(root))) dropBlock(path.join(root, file), '<!-- docnexus:start -->', '<!-- docnexus:end -->');
72
+ }
73
+ return obsolete;
74
+ }
8
75
 
9
76
  export function run(cmd, args, cwd, quiet = false) {
10
77
  const r = spawnSync(cmd, args, { cwd, stdio: quiet ? ['ignore', 'ignore', 'inherit'] : 'inherit', shell: IS_WIN });
@@ -16,11 +83,10 @@ export function checkNodeForDocNexus() {
16
83
  if (spawnSync(IS_WIN ? 'npm.cmd' : 'npm', ['-v'], { stdio: 'ignore', shell: IS_WIN }).status !== 0) fail('--with-docnexus 需要 npm,但未找到。请安装后重试,或去掉 --with-docnexus。');
17
84
  ok(`Node v${process.versions.node}`);
18
85
  }
19
- // DocNexus 的 skills(workflow-run/docnexus/skills/*)软链接到本项目的 agent skills 目录。DocNexus 以 workflow-run/ 为项目根、
20
- // init --agent none 运行,它自己不链接 skills、不写 CLAUDE.md / AGENTS.md(那些会落在 workflow-run/ 里),都由这里接到本项目。
86
+ // DocNexus 的 skills(.docnexus/skills/*)链接到所选 Agent 环境;init --agent none,由本工具统一接入。
21
87
  // 已有的同名软链接换成新的;同名目录内容和 DocNexus 的一样(建不了链接时的拷贝)也换掉,否则跳过
22
88
  export function linkDocNexusSkills(root, dirs) {
23
- const srcRoot = path.join(docnexusRoot(root), 'docnexus', 'skills');
89
+ const srcRoot = path.join(docnexusInternal(root), 'skills');
24
90
  const names = fs.existsSync(srcRoot) ? fs.readdirSync(srcRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name) : [];
25
91
  const sameSkill = (dir, name) => { const a = path.join(dir, 'SKILL.md'); const b = path.join(srcRoot, name, 'SKILL.md'); return fs.existsSync(a) && fs.existsSync(b) && fs.readFileSync(a).equals(fs.readFileSync(b)); };
26
92
  for (const rel of dirs) {
@@ -42,14 +108,14 @@ export function linkDocNexusSkills(root, dirs) {
42
108
  }
43
109
  }
44
110
 
45
- // 本项目 agent 的说明段落:概念索引在哪、docnexus 系列 skill 里的「项目根」指 workflow-run/。
111
+ // 本项目 agent 的说明段落:在业务项目根运行,知识路径使用 CLI 返回值。
46
112
  // 用自己的标记,不碰 DocNexus 自己的段落
47
113
  const CTX_START = '<!-- workflow-run:docnexus:start -->';
48
114
  const CTX_END = '<!-- workflow-run:docnexus:end -->';
49
115
  // AGENTS.md 由 codex / doubao / workbuddy 共用:命令指向装了 workflow 的那个目录(--copy 只装 workbuddy 时没有 .agents/skills)
50
116
  function contextBlock(root, file) {
51
117
  const has = (dir) => fs.existsSync(path.join(root, dir, 'workflow', 'scripts', 'docnexus.mjs'));
52
- const dir = file === 'CLAUDE.md' ? '.claude/skills' : ['.agents/skills', '.codebuddy/skills'].find(has) ?? '.agents/skills';
118
+ const dir = file === 'CLAUDE.md' ? '.claude/skills' : file === 'QWEN.md' ? '.qwen/skills' : ['.agents/skills', '.codebuddy/skills'].find(has) ?? '.agents/skills';
53
119
  const run = `${dir}/workflow/scripts/docnexus.mjs`;
54
120
  const index = file === 'CLAUDE.md' ? `@${HOME}/docnexus/CONCEPTS.md` : `\`${HOME}/docnexus/CONCEPTS.md\`(每个任务开始时读)`;
55
121
  return [
@@ -58,7 +124,9 @@ function contextBlock(root, file) {
58
124
  '',
59
125
  `本项目的 DocNexus 工作区在 \`${HOME}/docnexus/\`,概念索引:${index}`,
60
126
  '',
61
- `- docnexus 系列 skill 里说的「项目根」指 \`${HOME}\`。其中的命令在本项目根这样运行,不用先 cd,任何终端写法都一样:\`./docnexus/node_modules/.bin/docnexus <参数>\` → \`node ${run} <参数>\`;\`node docnexus/skills/<…>.mjs <参数>\` → \`node ${run} --node docnexus/skills/<…>.mjs <参数>\`。参数里的 \`docnexus/library/<path>\`、草稿等路径照旧相对 \`${HOME}\`。`,
127
+ `- 运行环境、skills、草稿与检索索引在业务项目根的 \`.docnexus/\`;\`init --output-dir ${HOME}\` 将知识输出到 \`${HOME}/docnexus/\`,配置保存在 \`.docnexus/project.json\`。`,
128
+ `- docnexus 系列 skill 的「项目根」就是业务项目根。命令用 \`./.docnexus/node_modules/.bin/docnexus <参数>\` 或 \`node ${run} <参数>\`;辅助脚本用 \`node ${run} --node .docnexus/skills/<…>.mjs <参数>\`。直接使用 status、draft、document 返回的路径,不再添加或剥离 workflow-run/ 前缀。`,
129
+ '- 找不到运行环境或配置时,用 workflow-run 安装 / 升级修复;不要在 workflow-run/ 里安装、初始化第二份,也不要调用根目录旧 docnexus/ 或全局 CLI。',
62
130
  '- 规划或改代码前,看任务是否涉及概念索引里的概念,或已记录的决定、组件、约定;涉及就用 `docnexus-recall` skill 查,并以查到的内容为依据。',
63
131
  '- 任务产生值得保留的决定或知识时,提出用 `docnexus` skill 沉淀;没有用户同意不写入。',
64
132
  CTX_END,
@@ -86,14 +154,14 @@ export function writeDocNexusContext(root, files) {
86
154
  if (next !== cur) { fs.writeFileSync(f, next); ok(`${file} 已写入 DocNexus 段落(工作区在 ${HOME}/docnexus/)`); }
87
155
  }
88
156
  }
89
- export const removeDocNexusContext = (root) => ['CLAUDE.md', 'AGENTS.md'].filter((file) => dropBlock(path.join(root, file), CTX_START, CTX_END));
157
+ export const removeDocNexusContext = (root) => ['CLAUDE.md', 'AGENTS.md', 'QWEN.md'].filter((file) => dropBlock(path.join(root, file), CTX_START, CTX_END));
90
158
 
91
159
  // 选择的环境 → 本项目里要链接 DocNexus skills 的目录与要写段落的文件
92
160
  function agentTargets(sel) {
93
161
  const dirs = new Set(); const files = new Set();
94
162
  if (sel.includes('claude')) { dirs.add(path.join('.claude', 'skills')); files.add('CLAUDE.md'); }
95
163
  if (sel.includes('codex') || sel.includes('doubao')) { dirs.add(CANON_DIR); files.add('AGENTS.md'); }
96
- if (sel.includes('qwen')) dirs.add(path.join('.qwen', 'skills'));
164
+ if (sel.includes('qwen')) { dirs.add(path.join('.qwen', 'skills')); files.add('QWEN.md'); }
97
165
  // WorkBuddy 读项目的 AGENTS.md(与 CODEBUDDY.md 并存)
98
166
  if (sel.includes('workbuddy')) { dirs.add(path.join('.codebuddy', 'skills')); files.add('AGENTS.md'); }
99
167
  return { dirs: [...dirs], files: [...files] };
@@ -101,7 +169,7 @@ function agentTargets(sel) {
101
169
 
102
170
  // 升级 / 重装都刷新本项目的链接与段落(DocNexus 新增或改名 skill 后跟上)
103
171
  export function refreshDocNexusLinks(root, sel) {
104
- if (!fs.existsSync(path.join(docnexusRoot(root), 'docnexus', 'project.json'))) return false;
172
+ if (!docnexusConfigured(root)) return false;
105
173
  const t = agentTargets(sel);
106
174
  linkDocNexusSkills(root, t.dirs);
107
175
  writeDocNexusContext(root, t.files);
@@ -109,45 +177,24 @@ export function refreshDocNexusLinks(root, sel) {
109
177
  }
110
178
 
111
179
  export function setupDocNexus(root, sel) {
112
- const dn = docnexusRoot(root);
113
- const npmArgs = ['install', '--prefix', './docnexus', '--save-exact', DOCNEXUS_PKG];
114
- step(`接入 DocNexus(位置 ${HOME}/docnexus/;npm ${npmArgs.join(' ')};docnexus init --agent none)`);
115
- fs.mkdirSync(dn, { recursive: true });
116
- if (!run(IS_WIN ? 'npm.cmd' : 'npm', npmArgs, dn)) { warn('npm 安装 DocNexus 失败,已跳过 DocNexus 接入(skills 已正常安装)'); return 'failed'; }
180
+ const npmArgs = ['install', '--prefix', './.docnexus', '--save-exact', DOCNEXUS_PKG];
181
+ step(`接入 DocNexus(运行环境 .docnexus/;知识输出 ${HOME}/docnexus/)`);
182
+ if (!run(IS_WIN ? 'npm.cmd' : 'npm', npmArgs, root)) { warn('npm 安装 DocNexus 失败,已跳过 DocNexus 接入(skills 已正常安装)'); return 'failed'; }
117
183
  const bin = docnexusBin(root);
118
- if (!bin) { warn(`安装后未找到 ${HOME}/docnexus/node_modules/.bin/docnexus,已跳过 DocNexus 接入`); return 'failed'; }
119
- const rel = `./${path.relative(dn, bin).split(path.sep).join('/')}`;
120
- if (!run(bin, ['init', '--agent', 'none'], dn, true)) {
121
- warn(`docnexus init 失败,已跳过 DocNexus 接入(可在 ${HOME}/ 下手动执行 ${rel} init --agent none)`);
184
+ if (!bin) { warn('安装后未找到 .docnexus/node_modules/.bin/docnexus,已跳过 DocNexus 接入'); return 'failed'; }
185
+ if (!run(bin, ['init', '--output-dir', HOME, '--agent', 'none'], root)) {
186
+ warn(`DocNexus 初始化失败:需要支持 --output-dir 的 format 7 版本。修复后在项目根运行 ./.docnexus/node_modules/.bin/docnexus init --output-dir ${HOME} --agent none`);
122
187
  return 'failed';
123
188
  }
124
- ok(`docnexus init 完成:${HOME}/docnexus/ 工作区 + skills(${HOME}/docnexus/skills)`);
189
+ if (!docnexusConfigured(root)) { warn('DocNexus 未生成 output_dir=workflow-run 的 format 7 配置,接入失败'); return 'failed'; }
125
190
  const t = agentTargets(sel);
126
191
  linkDocNexusSkills(root, t.dirs);
127
192
  writeDocNexusContext(root, t.files);
128
193
  if (sel.includes('doubao')) ok('doubao 与 codex 共用 .agents/skills,按 codex 接入');
129
- if (sel.includes('qwen')) warn('qwen:不会写 QWEN.md;需要自动召回时可在 QWEN.md 引用 AGENTS.md 中的 DocNexus 段落,或在 .qwen/settings.json 的 context.fileName 加入 AGENTS.md');
194
+ ok(`DocNexus 已初始化:.docnexus/ 运行环境,${HOME}/docnexus/ 知识输出`);
130
195
  return 'ok';
131
196
  }
132
197
 
133
- // 已装 DocNexus 是否支持调用方显式分类(draft seal --category,0.14.0 之后)。旧版会静默忽略该参数,
134
- // skill 的写入仍靠 metadata.category 落到同一分类,只是不会记成调用方指定;这里只用于提示升级
135
- function docnexusHelp(root) {
136
- const bin = docnexusBin(root);
137
- if (!bin) return null;
138
- const r = spawnSync(bin, ['--help'], { cwd: docnexusRoot(root), encoding: 'utf8', shell: IS_WIN });
139
- return `${r.stdout}${r.stderr}`;
140
- }
141
- export function docnexusSupportsCategory(root) {
142
- const h = docnexusHelp(root);
143
- return h === null ? null : /--category-reason/.test(h);
144
- }
145
- // recall --category(0.16.0 起):knowledge.mjs --query 按分类检索;旧版退回不带分类的 recall
146
- export function docnexusSupportsRecallCategory(root) {
147
- const h = docnexusHelp(root);
148
- return h === null ? null : /recall[^\n]*--category/.test(h);
149
- }
150
-
151
198
  export function templateRecords(root) { return findManifests(root)[0]?.m.templates ?? {}; }
152
199
  // 已安装的 skill(有安装清单时以清单为准),模板导入与状态只针对它们
153
200
  export function installedSkills(root) { const m = findManifests(root)[0]?.m; return m ? Object.keys(m.skills ?? {}) : undefined; }
@@ -165,9 +212,6 @@ export function reportTemplateStatus(root, say = warn) {
165
212
  return list.length > 0;
166
213
  }
167
214
 
168
- export function docnexusConnected(root) {
169
- return fs.existsSync(path.join(docnexusRoot(root), 'docnexus', 'project.json')) && docnexusBin(root) !== null;
170
- }
171
215
 
172
216
  // 团队改过的模板(customized / untracked)与当前默认模板的差异:只读,不写 DocNexus。
173
217
  // 只有导入时的哈希、没有导入时的原文,做不了三方合并,所以差异里同时包含团队自己的改动和默认模板的新改动。返回打印了几份
@@ -11,7 +11,7 @@
11
11
  import fs from 'node:fs';
12
12
  import path from 'node:path';
13
13
  import { spawnSync } from 'node:child_process';
14
- import { HOME, step, ok, warn, toPosix, findManifests, docnexusBin, docnexusRoot } from './core.mjs';
14
+ import { HOME, step, ok, warn, toPosix, findManifests, docnexusBin } from './core.mjs';
15
15
  import { updateGitignore, excludeHomeTmp, reportMigration } from './install.mjs';
16
16
  import { refreshDocNexusLinks } from './docnexus.mjs';
17
17
  import { layout, setConfig, artifactsConfig, suggestProject, migrateLayout } from '../../workflow/scripts/requirement.mjs';
@@ -102,7 +102,7 @@ export function docsRepo(root, { url, project }) {
102
102
  const c = setConfig(root, { project: name });
103
103
  ok(`本项目在文档仓库里的项目名:${c.artifacts.project}(写在 ${c.file},随代码提交,团队共用)`);
104
104
 
105
- if (fs.existsSync(path.join(docnexusRoot(root), 'docnexus', 'project.json'))) {
105
+ if (fs.existsSync(path.join(root, HOME, 'docnexus'))) {
106
106
  const envs = findManifests(root)[0]?.m.envs ?? [];
107
107
  if (docnexusBin(root)) refreshDocNexusLinks(root, envs);
108
108
  else warn(`文档仓库里有 DocNexus,本机还没装它的依赖:运行 npx @rowansenne/workflow-run upgrade --with-docnexus`);
@@ -3,8 +3,8 @@
3
3
  import fs from 'node:fs';
4
4
  import path from 'node:path';
5
5
  import { spawnSync } from 'node:child_process';
6
- import { PKG, CANON_DIR, INSTALL_BASES, LINK_ENVS, HOME, DOCNEXUS_PKG, docnexusUpgradeCmd, SKILLS, step, ok, warn, fail, toPosix, fileHash, readManifest, lstat, findManifests, cmpVersion } from './core.mjs';
7
- import { reportTemplateStatus, docnexusSupportsCategory, docnexusSupportsRecallCategory } from './docnexus.mjs';
6
+ import { PKG, CANON_DIR, INSTALL_BASES, LINK_ENVS, HOME, SKILLS, step, ok, warn, fail, toPosix, fileHash, readManifest, lstat, findManifests, cmpVersion } from './core.mjs';
7
+ import { reportTemplateStatus, legacyDocNexusPaths } from './docnexus.mjs';
8
8
  import { codegraphVersion } from './codegraph.mjs';
9
9
  import { legacyRequirements } from '../../workflow/scripts/requirement.mjs';
10
10
 
@@ -33,6 +33,8 @@ export function doctor(root, offline) {
33
33
  const [maj] = process.versions.node.split('.').map(Number);
34
34
  if (maj < 22) bad(`Node v${process.versions.node} 低于 22(workflow-run 只在 Node 22 / 24 上测试;18、20 已停止维护)`); else ok(`Node v${process.versions.node}`);
35
35
 
36
+ for (const dir of legacyDocNexusPaths(root)) bad(`发现旧版 DocNexus:${toPosix(path.relative(root, dir))}/;运行 npx @rowansenne/workflow-run upgrade --with-docnexus 强制清理并重新接入`);
37
+
36
38
  const found = findManifests(root);
37
39
  if (!found.length) { bad('未找到安装清单:本项目还没用 workflow-run 安装过'); return 1; }
38
40
  const m = found[0].m;
@@ -90,11 +92,11 @@ export function doctor(root, offline) {
90
92
  if (st.profile.ok) ok('项目画像已填写'); else meh(`项目画像未完成(空字段:${st.profile.empty.join('、')}):运行 /workflow profile`);
91
93
  if (st.wiki.ok) ok('wiki 已配置'); else console.log(' -- wiki 未配置(可选;也可能通过 wiki MCP 提供)');
92
94
  if (st.docnexus.ok) {
93
- // DocNexus 以 workflow-run/ 为项目根:本项目要有 docnexus skill 的链接和 CLAUDE.md / AGENTS.md 段落,agent 才知道去哪里找
95
+ // 运行环境在 .docnexus/,所有 Agent 都从业务项目根使用配置的知识输出。
94
96
  const linked = INSTALL_BASES.some((b) => lstat(path.join(root, b, 'docnexus-recall'))?.isSymbolicLink());
95
- const ctx = ['CLAUDE.md', 'AGENTS.md'].some((f) => { try { return fs.readFileSync(path.join(root, f), 'utf8').includes('workflow-run:docnexus:start'); } catch { return false; } });
96
- if (linked && ctx) ok(`DocNexus 在 ${HOME}/docnexus/,本项目已链接它的 skills 并写入段落`);
97
- else meh(`DocNexus 在 ${HOME}/docnexus/,但本项目${linked ? '' : '缺少 docnexus skill 链接'}${!linked && !ctx ? '、' : ''}${ctx ? '' : '缺少 CLAUDE.md / AGENTS.md 里的 DocNexus 段落'}:运行 npx @rowansenne/workflow-run upgrade`);
97
+ const ctx = ['CLAUDE.md', 'AGENTS.md', 'QWEN.md'].some((f) => { try { return fs.readFileSync(path.join(root, f), 'utf8').includes('workflow-run:docnexus:start'); } catch { return false; } });
98
+ if (linked && ctx) ok(`DocNexus 运行环境 .docnexus/,知识输出 ${HOME}/docnexus/,已接入 Agent`);
99
+ else meh(`DocNexus 已配置 output_dir=${HOME},但本项目${linked ? '' : '缺少 docnexus skill 链接'}${!linked && !ctx ? '、' : ''}${ctx ? '' : '缺少 Agent 上下文段落'}:运行 npx @rowansenne/workflow-run upgrade`);
98
100
  }
99
101
  // 产出目录:都在 workflow-run/
100
102
  const mode = st.artifacts?.mode === 'repo' ? `独立文档仓库(${HOME}/ 自己是 git 仓库)` : '随项目提交';
@@ -104,11 +106,8 @@ export function doctor(root, offline) {
104
106
  if (legacy.length) meh(`${legacy.length} 个需求还是旧结构(文件直接放在 docs/requirements/<需求>/ 下):运行 npx @rowansenne/workflow-run upgrade 迁到 <需求>/<项目名>/`);
105
107
  if (st.docnexus.ok) {
106
108
  if (!reportTemplateStatus(root, meh)) ok('DocNexus 已接入,文档模板齐全且与默认模板同步(或为团队定制版)');
107
- if (docnexusSupportsCategory(root) === false) meh(`已装的 DocNexus 不支持 draft seal --category:各 skill 的分类只能经 metadata.category 写入,不记为调用方指定。升级:${docnexusUpgradeCmd()}`);
108
- else ok('DocNexus 支持调用方显式分类(draft seal --category)');
109
- if (docnexusSupportsRecallCategory(root) === false) meh(`已装的 DocNexus 不支持 recall --category:knowledge.mjs --query 只列文档,skill 改用不带分类的 recall。升级:${docnexusUpgradeCmd()}`);
110
- else ok('DocNexus 支持按分类检索(recall --category)');
111
- } else console.log(' -- DocNexus 未接入(可选;未接入时只能用 skills 自带的默认模板,不能修改)');
109
+ } else if (fs.existsSync(path.join(root, '.docnexus', 'project.json'))) bad('DocNexus 配置或运行环境不完整:需要 format 7、output_dir=workflow-run 和 .docnexus/ 下的 CLI,运行 upgrade --with-docnexus 修复');
110
+ else console.log(' -- DocNexus 未接入(可选;未接入时只能用 skills 自带的默认模板,不能修改)');
112
111
  // workflow-run/templates/ 已不再生效:模板定制只放 DocNexus
113
112
  for (const f of st.docnexus.stale_project_templates ?? []) meh(st.docnexus.ok ? `${f} 已不再生效:确认已导入 DocNexus 知识库后删除` : `${f} 已不再生效(模板定制只放 DocNexus):重跑 npx @rowansenne/workflow-run 并加 --with-docnexus 接入后导入知识库,再删除`);
114
113
  for (const t of st.templates_need_docnexus ?? []) console.log(` -- ${t.skill} 的模板 ${t.id} 不随 skills 提供,要先接入 DocNexus 才能创建(重跑 npx @rowansenne/workflow-run 并加 --with-docnexus)`);
@@ -209,7 +209,7 @@ export function linkSkills(root, envName, rel, skills = SKILLS) {
209
209
 
210
210
  // ---------------------------------------------------------------- 4. .gitignore 保护
211
211
  // workflow-run/ 是独立文档仓库时整个忽略(它自己提交);否则只忽略其中的临时文件。
212
- // DocNexus 的依赖与检索库由它自带的 docnexus/.gitignore 忽略,随工作区走,两种模式都不用这里加规则
212
+ // DocNexus 的运行依赖、检索库和草稿由项目根的 .docnexus/.gitignore 忽略。
213
213
  export function updateGitignore(root, withCodeGraph = false) {
214
214
  const gi = path.join(root, '.gitignore');
215
215
  const rules = ['**/database-query/config.json', '**/wiki-config.json', '**/workflow/profile.local.json', homeIsRepo(root) ? `/${HOME}/` : `/${TMP_DIR}/`];
@@ -2,7 +2,7 @@
2
2
 
3
3
  import fs from 'node:fs';
4
4
  import path from 'node:path';
5
- import { IS_WIN, CANON_DIR, INSTALL_BASES, MANIFEST, step, ok, warn, toPosix, fileHash, readManifest, removeEmptyDirs, lstat, timestamp, removeLink, docnexusRoot } from './core.mjs';
5
+ import { IS_WIN, CANON_DIR, INSTALL_BASES, MANIFEST, step, ok, warn, toPosix, fileHash, readManifest, removeEmptyDirs, lstat, timestamp, removeLink, docnexusInternal } from './core.mjs';
6
6
  import { removeDocNexusContext } from './docnexus.mjs';
7
7
 
8
8
  // 软链接是否指向 absTarget(按解析后的绝对路径比较,兼容 Windows 上 readlink 返回的 \\?\ 前缀)
@@ -37,10 +37,9 @@ export function uninstall(root) {
37
37
  if (n) ok(`已移除 ${toPosix(rel)} 下 ${n} 个指向 ${toPosix(CANON_DIR)} 的软链接`);
38
38
  }
39
39
 
40
- // 1b. 本工具接进来的 DocNexus:指向 workflow-run/docnexus/skills 的链接、CLAUDE.md / AGENTS.md 里的段落。
41
- // workflow-run/ 里的东西(产物、DocNexus 工作区、项目模板)是项目数据,不删
40
+ // 1b. 移除指向 .docnexus/skills 的入口及上下文;运行环境和知识输出保留。
42
41
  {
43
- const src = path.join(docnexusRoot(root), 'docnexus', 'skills');
42
+ const src = path.join(docnexusInternal(root), 'skills');
44
43
  let n = 0;
45
44
  for (const rel of bases) {
46
45
  const dir = path.join(root, rel);
@@ -50,7 +49,7 @@ export function uninstall(root) {
50
49
  }
51
50
  }
52
51
  const files = removeDocNexusContext(root);
53
- if (n || files.length) ok(`已移除指向 ${toPosix(path.relative(root, src))} 的 ${n} 个 DocNexus skill 链接${files.length ? `,以及 ${files.join('、')} 里的 DocNexus 段落` : ''}(workflow-run/docnexus/ 本身不动)`);
52
+ if (n || files.length) ok(`已移除指向 ${toPosix(path.relative(root, src))} 的 ${n} 个 DocNexus skill 链接${files.length ? `,以及 ${files.join('、')} 里的 DocNexus 段落` : ''}(.docnexus/ 运行环境与 workflow-run/docnexus/ 知识输出保留)`);
54
53
  }
55
54
 
56
55
  // 2. 各安装目录:按清单删除模板文件;用户配置与手改过的模板文件先备份
@@ -90,6 +89,6 @@ export function uninstall(root) {
90
89
  for (const f of backedUp) console.log(` ${f}`);
91
90
  }
92
91
  for (const d of leftovers) warn(`${d} 中有不在清单里的文件,已保留该目录`);
93
- console.log('.gitignore 中追加的规则未移除,如不再需要可手动删除。workflow-run/(需求产物、DocNexus 工作区、项目模板)是项目数据,没有删除。');
92
+ console.log('.gitignore 中追加的规则未移除,如不再需要可手动删除。.docnexus/ 运行环境及 workflow-run/(需求产物、DocNexus 知识、项目模板)没有删除。');
94
93
  return 0;
95
94
  }
@@ -16,7 +16,8 @@ import path from 'node:path';
16
16
  import { spawnSync } from 'node:child_process';
17
17
  import { fileURLToPath } from 'node:url';
18
18
  import { loadSkills, deriveMeta, TEMPLATE_CATEGORY, TEMPLATE_CATEGORY_REASON } from './skills.mjs';
19
- import { docnexusBin, docnexusRoot } from './lib/core.mjs';
19
+ import { docnexusBin, docnexusRoot, docnexusWorkspace } from './lib/core.mjs';
20
+ import { docnexusConnected } from '../workflow/scripts/docnexus-layout.mjs';
20
21
 
21
22
  // 模板清单来自各 skill 的 skill.json(templates 字段)。没有 src 的(技术方案、上线方案)不随 skills 提供默认版本,
22
23
  // 由用户第一次使用时按样例创建,不在导入与版本跟踪之列
@@ -25,8 +26,8 @@ export { TEMPLATE_CATEGORY };
25
26
 
26
27
  const IS_WIN = process.platform === 'win32';
27
28
  const sha256 = (s) => crypto.createHash('sha256').update(s).digest('hex');
28
- // DocNexus 以 workflow-run/ 为项目根:命令在那里运行,草稿与知识库路径也相对那里
29
- const binPath = (root) => docnexusBin(root) ?? path.join(docnexusRoot(root), 'docnexus', 'node_modules', '.bin', IS_WIN ? 'docnexus.cmd' : 'docnexus');
29
+ // CLI 在业务项目根运行,草稿与知识路径按返回值使用。
30
+ const binPath = (root) => docnexusBin(root);
30
31
 
31
32
  function docnexus(root, args) {
32
33
  const r = spawnSync(binPath(root), args, { cwd: docnexusRoot(root), encoding: 'utf8', shell: IS_WIN, maxBuffer: 64 * 1024 * 1024 });
@@ -36,16 +37,16 @@ function docnexus(root, args) {
36
37
  }
37
38
 
38
39
  function requireDocNexus(root) {
39
- if (!fs.existsSync(path.join(docnexusRoot(root), 'docnexus', 'project.json')) || !docnexusBin(root)) {
40
- throw new Error('未检测到 DocNexus(需要 workflow-run/docnexus/project.json 与 workflow-run/docnexus/node_modules/.bin/docnexus)。先加 --with-docnexus 接入。');
40
+ if (!docnexusConnected(root)) {
41
+ throw new Error('未检测到新版 DocNexus(需要 .docnexus/project.json 的 output_dir=workflow-run 与 .docnexus/node_modules/.bin/docnexus)。先加 --with-docnexus 接入。');
41
42
  }
42
43
  }
43
44
 
44
- const libFile = (root, t) => path.join(docnexusRoot(root), 'docnexus', 'library', t.file);
45
+ const libFile = (root, t) => path.join(docnexusWorkspace(root), 'library', t.file);
45
46
  const defaultContent = (tplDir, t) => fs.readFileSync(path.join(tplDir, t.src), 'utf8');
46
47
 
47
48
  // 写草稿并提交;baseVersion 为 'new'(新建)或已有记录的 record_version(更新,带 --replace)。成功返回 null,失败返回错误信息
48
- // 分类以调用方显式分类封存(--category doc-template),不走模型分类;不支持该参数的旧版 DocNexus 会忽略它,仍按 metadata.category 入库
49
+ // 分类以调用方显式分类封存(--category doc-template),不走模型分类。
49
50
  function writeDraft(root, t, content, metadata, baseVersion) {
50
51
  const draft = docnexus(root, ['draft', 'new', '--slug', `template-${t.skill}`]);
51
52
  if (!draft.ok || !draft.json?.artifacts) return draft.err || 'draft new 失败';