@rowansenne/workflow-run 0.23.0 → 0.24.1
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 +16 -0
- package/README.md +80 -31
- package/bin/lib/core.mjs +8 -14
- package/bin/lib/docnexus.mjs +86 -42
- package/bin/lib/docsrepo.mjs +2 -2
- package/bin/lib/doctor.mjs +10 -11
- package/bin/lib/install.mjs +16 -2
- package/bin/lib/uninstall.mjs +5 -6
- package/bin/seed-templates.mjs +8 -7
- package/bin/workflow-run.mjs +3 -1
- package/create-release-md-doc/SKILL.md.tpl +2 -2
- package/create-tech-md-doc/SKILL.md.tpl +2 -2
- package/dev/SKILL.md.tpl +9 -6
- package/dev/references/delivery-template.md +1 -1
- package/dev/references/plan-template.md +1 -1
- package/package.json +1 -1
- package/project-review/SKILL.md.tpl +2 -2
- package/project-review/references/report.md.tpl +1 -1
- package/workflow/SKILL.md.tpl +1 -1
- package/workflow/references/docnexus.md +10 -14
- package/workflow/scripts/docnexus-layout.mjs +24 -0
- package/workflow/scripts/docnexus.mjs +7 -8
- package/workflow/scripts/knowledge.mjs +7 -19
- package/workflow/scripts/resolve.mjs +13 -18
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.24.1] - 2026-10-21
|
|
8
|
+
|
|
9
|
+
### 修复
|
|
10
|
+
- WorkBuddy 改用 `--copy` 重装时,将本工具已有的技能软链接转换为真实目录,保留已填配置,避免写入共享来源或影响其他环境;用户指向其他位置的链接保留并报错。
|
|
11
|
+
- 升级以最近一次显式安装的环境和模式为准,自动退回拷贝的清单不覆盖单一来源的完整技能选择。
|
|
12
|
+
- 补充 Windows WorkBuddy 的管理员终端、独立拷贝安装及技能不可见时的排查说明。
|
|
13
|
+
|
|
14
|
+
## [0.24.0] - 2026-10-21
|
|
15
|
+
|
|
16
|
+
### 变更
|
|
17
|
+
- dev 新增规模确认:功能开发和缺陷修复在判定 S / M / L 后都需说明依据、快速通道及落盘安排,等用户明确确认再继续;可以和理解 / 方案确认同轮提出。实现中规模变化需重新确认,单纯的规模确认不影响 S 规模快速通道资格。
|
|
18
|
+
|
|
19
|
+
### 变更(破坏性)
|
|
20
|
+
- DocNexus 按当前 format 7 重新接入:在业务项目根安装到 `.docnexus/`,运行 `init --output-dir workflow-run --agent none`,知识输出在 `workflow-run/docnexus/`。安装、包装脚本、模板导入、知识检索、doctor、卸载入口和所有 Agent 路径说明同步更新;Qwen 自动写入 `QWEN.md`。
|
|
21
|
+
- 强制删除旧版 `workflow-run/docnexus/` 的旧知识和内部运行文件、`workflow-run/.docnexus/`、旧重复备份及能识别的根目录旧安装,不迁移、不备份。新版输出的知识在重装后保留,工作流需求产物不受影响。移除此前的旧版调用与分类检索降级。
|
|
22
|
+
|
|
7
23
|
## [0.23.0] - 2026-10-19
|
|
8
24
|
|
|
9
25
|
### 新增
|
package/README.md
CHANGED
|
@@ -78,6 +78,21 @@ npx @rowansenne/workflow-run docs-repo git@git.example.com:team/team-docs.git
|
|
|
78
78
|
* Windows:安装与升级(`init` / `upgrade`)**必须在管理员终端执行**(建目录软链接需要该权限);在普通终端运行会直接停下、不做任何改动,并提示改用「以管理员身份运行」的终端。`doctor`、`seed-templates`、`uninstall` 不要求管理员。少数文件系统仍建不了软链接时,该环境自动退回独立拷贝。在 Windows 上 clone 含软链接的仓库需要 `git config core.symlinks true`。
|
|
79
79
|
* 不想用软链接:加 `--copy`,每个环境各装一份完整文件。
|
|
80
80
|
|
|
81
|
+
### 在 Windows 的 WorkBuddy 中使用
|
|
82
|
+
|
|
83
|
+
在**业务项目根目录**打开管理员 PowerShell,运行 npm 包提供的安装命令(仅 `npm install` 下载包不会生成项目技能):
|
|
84
|
+
|
|
85
|
+
```powershell
|
|
86
|
+
npx @rowansenne/workflow-run@latest --envs workbuddy --copy --skip-db --yes
|
|
87
|
+
npx @rowansenne/workflow-run@latest doctor --offline
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
这里用 `--copy` 在 `.codebuddy/skills/<skill>/` 生成真实目录和 `SKILL.md`,便于排除目录软链接的兼容问题。已有指向 `.agents/skills/` 的本工具链接会先转成独立拷贝,保留已填配置,再更新技能文件;共享来源和 Qwen 等其他环境的链接不变。后续 `upgrade` 沿用最近一次显式安装的环境和模式。只选择 WorkBuddy 时,全新安装不会生成 `.qwen/skills/`。
|
|
91
|
+
|
|
92
|
+
安装后,将 **同一个业务项目目录**设为 WorkBuddy 任务的工作目录,重新打开项目并新建会话,再尝试 `/workflow status`、`/dev` 或 `/project-review`。`/workflow profile` 等是技能参数,不是额外的独立技能。
|
|
93
|
+
|
|
94
|
+
如果仍然不可见,先确认业务项目下确实有 `.codebuddy/skills/workflow/SKILL.md`,并保留安装命令、`doctor --offline` 输出与 WorkBuddy 版本以便排查。项目级配置目录见 [WorkBuddy 官方说明](https://www.codebuddy.cn/docs/workbuddy/From-Beginner-to-Expert-Guide/Function-Description/Project)。
|
|
95
|
+
|
|
81
96
|
每个 skill 目录结构(四环境一致):
|
|
82
97
|
|
|
83
98
|
|
|
@@ -179,9 +194,9 @@ npx @rowansenne/workflow-run docs-repo git@git.example.com:team/team-docs.git
|
|
|
179
194
|
/dev --bug <现象 | 报错 | 日志> # 缺陷修复
|
|
180
195
|
```
|
|
181
196
|
|
|
182
|
-
* **功能开发**:理解需求(可读 PRD、查 DocNexus 历史决策)→ 调研现有代码 → 需求澄清(借鉴 grill-me:按决策树分轮提问,每题给推荐答案;事实自己查、决定交给用户;S
|
|
183
|
-
* **S
|
|
184
|
-
* **缺陷修复**:收集现象 → 用失败测试复现 → 用证据定位根因 → 最小修复让测试转绿 → 自审 → 交付(必须写明根因)。
|
|
197
|
+
* **功能开发**:理解需求(可读 PRD、查 DocNexus 历史决策)→ 调研现有代码 → 需求澄清(借鉴 grill-me:按决策树分轮提问,每题给推荐答案;事实自己查、决定交给用户;S 规模无未定决定时跳过澄清提问)→ **规模确认(S / M / L 都说明判定依据、对应流程并等用户确认)** → 按规模出方案(S 列步骤开始;M 对话内方案待确认;L 落盘方案文档待确认)→ 小步实现并编译 → 补测试并运行 → 用 project-review 自审 → 交付说明落盘到 `workflow-run/docs/requirements/<需求名>/<项目名>/delivery.md`(带分支元信息;改动、验证结果、配置清单、未执行的 SQL),已接入 DocNexus 时每次都问要不要沉淀(同意后以 `dev-notes` 分类写入,并把库内路径记回交付说明)。审查和上线方案会按分支自动读取这份交付说明。
|
|
198
|
+
* **S 规模快速通道**:先经用户确认 S 规模及快速通道,再按条件执行:不涉及配置、SQL / DDL、对外接口、表结构,也没有需要澄清的业务决定时,编译、测试、自审照常,但只在对话里交付,不落盘交付说明、不问沉淀;实现中碰到上述任何一项或改动超过 3 个文件,就退回完整流程,规模变化时先确认新规模。规模确认本身不影响快速通道资格。缺陷修复同样适用(根因仍须在对话里写清楚)。
|
|
199
|
+
* **缺陷修复**:收集现象 → 用失败测试复现 → 用证据定位根因 → **判定规模并等用户确认** → 最小修复让测试转绿 → 自审 → 交付(必须写明根因)。
|
|
185
200
|
* 守则:不写库、不改凭据、不擅自 commit / push、不装新依赖;测试没跑就写「未验证」。
|
|
186
201
|
* 其他 skill 都是可选增强:wiki、DocNexus、数据库没配置时自动跳过,并在交付说明中注明。
|
|
187
202
|
|
|
@@ -207,38 +222,71 @@ npx @rowansenne/workflow-run docs-repo git@git.example.com:team/team-docs.git
|
|
|
207
222
|
|
|
208
223
|
## DocNexus 文档库(可选)
|
|
209
224
|
|
|
210
|
-
|
|
225
|
+
接入支持 `--output-dir` 的 DocNexus format 7,在**业务项目根**执行安装和初始化:
|
|
211
226
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
227
|
+
```bash
|
|
228
|
+
npm install --prefix ./.docnexus --save-exact @rowansenne/docnexus
|
|
229
|
+
./.docnexus/node_modules/.bin/docnexus init --output-dir workflow-run --agent none
|
|
230
|
+
```
|
|
215
231
|
|
|
216
|
-
|
|
232
|
+
`workflow-run --with-docnexus` 自动执行这两步,并链接所选 Agent 的 skills、写入项目说明、导入文档模板。
|
|
233
|
+
|
|
234
|
+
### 运行环境与知识输出
|
|
235
|
+
|
|
236
|
+
```text
|
|
237
|
+
<业务项目>/
|
|
238
|
+
├── .docnexus/ # npm 运行环境与内部状态
|
|
239
|
+
│ ├── package.json / package-lock.json
|
|
240
|
+
│ ├── project.json # format_version=7,output_dir=workflow-run
|
|
241
|
+
│ ├── node_modules/ # CLI 和依赖
|
|
242
|
+
│ ├── skills/ / schemas/
|
|
243
|
+
│ └── drafts/ / store/ / .runtime/
|
|
244
|
+
└── workflow-run/
|
|
245
|
+
├── docnexus/ # 知识输出,内部不安装 npm
|
|
246
|
+
│ ├── CONCEPTS.md / concepts/
|
|
247
|
+
│ └── library/ / records/ / impacts/
|
|
248
|
+
├── docs/requirements/ # 工作流产物
|
|
249
|
+
└── tmp/
|
|
250
|
+
```
|
|
217
251
|
|
|
218
|
-
|
|
252
|
+
所有 DocNexus 命令均从业务项目根运行,CLI 返回的路径直接用于读取和引用,不添加或删除前缀:
|
|
219
253
|
|
|
220
|
-
|
|
254
|
+
```bash
|
|
255
|
+
node .agents/skills/workflow/scripts/docnexus.mjs status
|
|
256
|
+
node .agents/skills/workflow/scripts/docnexus.mjs view
|
|
257
|
+
node .agents/skills/workflow/scripts/docnexus.mjs usage export > workflow-run/docnexus-usage.json
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
`status` 返回 `workspace_path`、`library_path`、`internal_directory`。知识输出固定为 `workflow-run/docnexus/`;草稿与辅助脚本位于 `.docnexus/`。模板保存在 `workflow-run/docnexus/library/templates/<skill>/`。
|
|
261
|
+
|
|
262
|
+
| 环境 | DocNexus skill 入口 | 项目说明 |
|
|
221
263
|
| --- | --- | --- |
|
|
222
|
-
| claude | `.claude/skills/` | `CLAUDE.md` |
|
|
223
|
-
| codex / doubao | `.agents/skills
|
|
224
|
-
| workbuddy | `.codebuddy/skills/`
|
|
225
|
-
| qwen | `.qwen/skills/`
|
|
264
|
+
| claude | `.claude/skills/` → `.docnexus/skills/` | `CLAUDE.md` |
|
|
265
|
+
| codex / doubao | `.agents/skills/` → `.docnexus/skills/` | `AGENTS.md` |
|
|
266
|
+
| workbuddy | `.codebuddy/skills/` → `.docnexus/skills/` | `AGENTS.md` |
|
|
267
|
+
| qwen | `.qwen/skills/` → `.docnexus/skills/` | `QWEN.md` |
|
|
268
|
+
|
|
269
|
+
升级 / 重装刷新入口与说明,不改手写段落。知识写入仍走 DocNexus 的授权与 capture 流程。普通 `uninstall` 移除 Agent 入口和说明,保留 `.docnexus/` 运行环境与知识输出。
|
|
270
|
+
|
|
271
|
+
`.docnexus/.gitignore` 由 DocNexus 维护,忽略运行依赖、索引、草稿和内置资源;`.docnexus/project.json`、npm 清单和锁文件应随代码提交。知识输出随 `workflow-run/` 提交;独立文档仓库模式下只在文档仓库提交知识,本项目仍保存自己的 `.docnexus/` 配置和运行环境。
|
|
226
272
|
|
|
227
|
-
|
|
273
|
+
### 破坏性更新:旧布局强制清理
|
|
228
274
|
|
|
229
|
-
|
|
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 上的链接使用目录软链接(需管理员终端)。
|
|
275
|
+
本次不兼容旧 DocNexus,不迁移、不备份旧知识。`init` / `upgrade` 在安装前强制删除:
|
|
235
276
|
|
|
236
|
-
|
|
277
|
+
- 旧版 `workflow-run/docnexus/`(含旧 `project.json`、npm / skills / store 等内部文件的布局);
|
|
278
|
+
- 嵌套的 `workflow-run/.docnexus/` 及 `workflow-run/tmp/duplicate-docnexus/` 旧备份;
|
|
279
|
+
- 能识别的根目录旧 `docnexus/` 安装、旧 Agent 链接与不兼容的内部项目状态。
|
|
280
|
+
|
|
281
|
+
新版知识输出不含这些旧内部文件,后续重装 / 升级会保留新版 `library/`、`records/` 等知识。工作流的需求文档、其他临时文件和普通同名业务目录保留。清理失败会停止;npm / init 失败返回非零。
|
|
282
|
+
|
|
283
|
+
已安装项目重新接入:
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
npx @rowansenne/workflow-run upgrade --with-docnexus
|
|
287
|
+
```
|
|
237
288
|
|
|
238
|
-
|
|
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` 装一次。
|
|
289
|
+
新项目用 `npx @rowansenne/workflow-run --with-docnexus`。离线或本地验证可设 `DOCNEXUS_PKG=/path/to/package-or-source` 指定安装来源;默认从 npm 安装,必须使用支持 format 7 和 `--output-dir` 的版本。
|
|
242
290
|
|
|
243
291
|
## CodeGraph 代码图谱与 code-graph skill(可选)
|
|
244
292
|
|
|
@@ -397,8 +445,8 @@ npx @rowansenne/workflow-run uninstall
|
|
|
397
445
|
| `workflow/scripts/resolve.mjs template <skill> <文件名>` | 判定用哪份模板:已接入 DocNexus 只用知识库,缺失时退出码 2 并给出导入来源;未接入时项目文件优先,其次默认模板 |
|
|
398
446
|
| `workflow/scripts/resolve.mjs profile` | 读项目画像,列出空字段 |
|
|
399
447
|
| `workflow/scripts/resolve.mjs env` | 本机系统与终端(安装时记录在 `profile.local.json`),写终端命令按它来 |
|
|
400
|
-
| `workflow/scripts/docnexus.mjs <参数>` / `--node <脚本> <参数>` |
|
|
401
|
-
| `workflow/scripts/knowledge.mjs <skill> [--match 词] [--limit n] [--query 问题]` | 按 `readFrom` 的分类列出 DocNexus 文档(标题、摘要、路径,不含正文);`--query` 再在每个分类内 `recall --category
|
|
448
|
+
| `workflow/scripts/docnexus.mjs <参数>` / `--node <脚本> <参数>` | 在业务项目根运行 `.docnexus/` 中的 CLI 或它的 node 脚本,知识输出到 `workflow-run/docnexus/`;Windows 上直接用 node 跑 CLI 入口,不经过 `.cmd` |
|
|
449
|
+
| `workflow/scripts/knowledge.mjs <skill> [--match 词] [--limit n] [--query 问题]` | 按 `readFrom` 的分类列出 DocNexus 文档(标题、摘要、路径,不含正文);`--query` 再在每个分类内 `recall --category`,直接使用新版按分类检索能力。未接入时退出码 2 |
|
|
402
450
|
| `workflow/scripts/resolve.mjs category <skill>` | 该 skill 写入 DocNexus 时的显式分类(画像 `docnexus_categories` 优先,其次 skill.json),输出 `seal_args` 与 `metadata`;未声明时退出码 1 |
|
|
403
451
|
| `workflow/scripts/resolve.mjs status` | `/workflow status` 的能力探测(不输出任何凭据) |
|
|
404
452
|
| `workflow/scripts/resolve.mjs infer-profile` | 按固定规则从构建文件、Makefile、依赖与配置文件、git 推断项目画像草稿,每个字段附依据;不写文件 |
|
|
@@ -455,7 +503,8 @@ CI(`.github/workflows/test.yml`)在 push 到 main 和每个 PR 上运行:m
|
|
|
455
503
|
| `.agents/skills/database-query/references/schema.md` | 表结构速查,补齐后提交 |
|
|
456
504
|
| `workflow-run/docs/requirements/` | 需求产物;独立文档仓库模式下提交到文档仓库 |
|
|
457
505
|
| `workflow-run/feedback/` | 运行反馈,每人一个文件;独立文档仓库模式下提交到文档仓库 |
|
|
458
|
-
|
|
|
506
|
+
| `.docnexus/project.json`、npm 清单与锁文件 | DocNexus 项目配置与运行依赖版本,随代码提交 |
|
|
507
|
+
| `workflow-run/docnexus/` | DocNexus 知识输出与定制的文档模板。独立文档仓库模式下提交到文档仓库 |
|
|
459
508
|
| `.ignore` | 让代码搜索跳过需求目录(不影响 git) |
|
|
460
509
|
| `.gitignore`、`CLAUDE.md` / `AGENTS.md` | 安装器追加的忽略规则、DocNexus 说明段落 |
|
|
461
510
|
|
|
@@ -688,7 +737,7 @@ npx @rowansenne/workflow-run seed-templates --diff # 团队改过
|
|
|
688
737
|
```
|
|
689
738
|
|
|
690
739
|
* 只导入 DocNexus 里还没有的、有默认版本的模板(技术方案、上线方案不在内),已有的不覆盖;项目已接入但缺模板时,安装结束会列出缺哪几份。
|
|
691
|
-
* 之后模板随知识库更新:直接编辑 `workflow-run/docnexus/library/templates/...`
|
|
740
|
+
* 之后模板随知识库更新:直接编辑 `workflow-run/docnexus/library/templates/...` 后在业务项目根执行 `./.docnexus/node_modules/.bin/docnexus document sync --id <id>` 采纳;或者在生成文档后,skill 发现你做了结构性调整时会提议同步回模板(你确认后才写入)。每次变更都记录在 DocNexus 的 timeline 里,可追溯。
|
|
692
741
|
* 模板里的 `{{...}}` 是给 agent 的填写提示,DocNexus 原样保存;不要写成单独一行的 `{{fact:...}}`(那是 DocNexus 的事实引用语法)。
|
|
693
742
|
* 以前放在 `workflow-run/templates/` 下的项目模板:接入 DocNexus 后,缺模板时 skill 会把它作为 `import_from` 提议导入知识库;导入后删除这些文件。
|
|
694
743
|
|
|
@@ -719,4 +768,4 @@ node scripts/eval-triggers.mjs --only dev # 只跑期望为 dev 的用例
|
|
|
719
768
|
|
|
720
769
|
* **Qwen Code** 项目级为 `.qwen/skills/`,自动发现。
|
|
721
770
|
|
|
722
|
-
* **腾讯 WorkBuddy** 读项目工作目录下的 `.codebuddy/`(与 CodeBuddy Code 一致),项目级技能在 `.codebuddy/skills/`,同名时优先于用户级;它也读 `AGENTS.md`。
|
|
771
|
+
* **腾讯 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
|
-
//
|
|
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
|
|
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
|
// ---------------------------------------------------------------- 输出
|
|
@@ -116,9 +107,12 @@ export function timestamp() {
|
|
|
116
107
|
}
|
|
117
108
|
|
|
118
109
|
export const INSTALL_BASES = [CANON_DIR, ...Object.values(LINK_ENVS)];
|
|
119
|
-
//
|
|
110
|
+
// 以最近一次显式安装为准;自动退回拷贝的清单不覆盖单一来源的模式与 skill 选择。
|
|
120
111
|
export function findManifests(root) {
|
|
121
|
-
|
|
112
|
+
const found = INSTALL_BASES.map((rel) => ({ rel, base: path.join(root, rel), m: readManifest(path.join(root, rel)) })).filter((x) => x.m);
|
|
113
|
+
const primary = found.filter((x) => x.rel === CANON_DIR || x.m.mode === 'copy').reduce((latest, x) =>
|
|
114
|
+
!latest || (Date.parse(x.m.installedAt) || 0) > (Date.parse(latest.m.installedAt) || 0) ? x : latest, null);
|
|
115
|
+
return primary ? [primary, ...found.filter((x) => x !== primary)] : found;
|
|
122
116
|
}
|
|
123
117
|
export function cmpVersion(a, b) {
|
|
124
118
|
const pa = String(a).split(/[.-]/).map((x) => parseInt(x, 10) || 0);
|
package/bin/lib/docnexus.mjs
CHANGED
|
@@ -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,
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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 (!
|
|
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
|
|
113
|
-
|
|
114
|
-
|
|
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(
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
// 只有导入时的哈希、没有导入时的原文,做不了三方合并,所以差异里同时包含团队自己的改动和默认模板的新改动。返回打印了几份
|
package/bin/lib/docsrepo.mjs
CHANGED
|
@@ -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
|
|
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(
|
|
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`);
|
package/bin/lib/doctor.mjs
CHANGED
|
@@ -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,
|
|
7
|
-
import { reportTemplateStatus,
|
|
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
|
-
//
|
|
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
|
|
97
|
-
else meh(`DocNexus
|
|
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
|
-
|
|
108
|
-
|
|
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)`);
|
package/bin/lib/install.mjs
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
-
import { TPL_DIR, PKG, CANON_DIR, INSTALL_BASES, SKILLS, SKILL_LIST, USER_FILES, MANIFEST, ok, warn, readJson, walk, toPosix, sameFile, writeFile, render, sha256, fileHash, readManifest, removeEmptyDirs, lstat, timestamp, removeLink, HOME } from './core.mjs';
|
|
5
|
+
import { TPL_DIR, PKG, CANON_DIR, INSTALL_BASES, SKILLS, SKILL_LIST, USER_FILES, MANIFEST, ok, warn, fail, readJson, walk, toPosix, sameFile, writeFile, render, sha256, fileHash, readManifest, removeEmptyDirs, lstat, timestamp, removeLink, HOME } from './core.mjs';
|
|
6
6
|
import { writeDbConfig } from './db.mjs';
|
|
7
|
+
import { linkPointsTo } from './uninstall.mjs';
|
|
7
8
|
import { layout, ensureIgnore, homeIsRepo, migrateLayout, IGNORE_LINE, TMP_DIR } from '../../workflow/scripts/requirement.mjs';
|
|
8
9
|
import { detectEnv } from '../../workflow/scripts/resolve.mjs';
|
|
9
10
|
import { rotate as rotateFeedback, ensureUnionMerge } from '../../workflow/scripts/feedback.mjs';
|
|
@@ -66,6 +67,19 @@ export async function installSkills(base, db, ctx) {
|
|
|
66
67
|
for (const skill of selected) {
|
|
67
68
|
const srcDir = path.join(TPL_DIR, skill);
|
|
68
69
|
const dstDir = path.join(base, skill);
|
|
70
|
+
// --copy 重装已有链接时先解除共享关系,避免写穿链接、仍留下无法被部分宿主发现的链接目录。
|
|
71
|
+
if (ctx.mode !== 'symlink' && lstat(dstDir)?.isSymbolicLink()) {
|
|
72
|
+
if (!linkPointsTo(dstDir, path.resolve(ctx.root, CANON_DIR, skill))) fail(`${dstDir} 指向其他位置,不能改为独立拷贝;请先手动处理该链接。`);
|
|
73
|
+
const tmp = fs.mkdtempSync(path.join(base, '.workflow-run-copy-'));
|
|
74
|
+
const copy = path.join(tmp, skill);
|
|
75
|
+
try {
|
|
76
|
+
if (fs.existsSync(dstDir)) fs.cpSync(dstDir, copy, { recursive: true, dereference: true });
|
|
77
|
+
else fs.mkdirSync(copy);
|
|
78
|
+
removeLink(dstDir);
|
|
79
|
+
fs.renameSync(copy, dstDir);
|
|
80
|
+
} finally { fs.rmSync(tmp, { recursive: true, force: true }); }
|
|
81
|
+
ok(`${toPosix(path.relative(ctx.root, dstDir))} 已由软链接改为独立拷贝(已有配置保留)`);
|
|
82
|
+
}
|
|
69
83
|
const prevSkill = prev?.skills?.[skill];
|
|
70
84
|
const entry = { files: {}, userFiles: {} };
|
|
71
85
|
const notes = [];
|
|
@@ -209,7 +223,7 @@ export function linkSkills(root, envName, rel, skills = SKILLS) {
|
|
|
209
223
|
|
|
210
224
|
// ---------------------------------------------------------------- 4. .gitignore 保护
|
|
211
225
|
// workflow-run/ 是独立文档仓库时整个忽略(它自己提交);否则只忽略其中的临时文件。
|
|
212
|
-
// DocNexus
|
|
226
|
+
// DocNexus 的运行依赖、检索库和草稿由项目根的 .docnexus/.gitignore 忽略。
|
|
213
227
|
export function updateGitignore(root, withCodeGraph = false) {
|
|
214
228
|
const gi = path.join(root, '.gitignore');
|
|
215
229
|
const rules = ['**/database-query/config.json', '**/wiki-config.json', '**/workflow/profile.local.json', homeIsRepo(root) ? `/${HOME}/` : `/${TMP_DIR}/`];
|
package/bin/lib/uninstall.mjs
CHANGED
|
@@ -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,
|
|
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.
|
|
41
|
-
// workflow-run/ 里的东西(产物、DocNexus 工作区、项目模板)是项目数据,不删
|
|
40
|
+
// 1b. 移除指向 .docnexus/skills 的入口及上下文;运行环境和知识输出保留。
|
|
42
41
|
{
|
|
43
|
-
const src = path.join(
|
|
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 段落` : ''}
|
|
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
|
|
92
|
+
console.log('.gitignore 中追加的规则未移除,如不再需要可手动删除。.docnexus/ 运行环境及 workflow-run/(需求产物、DocNexus 知识、项目模板)没有删除。');
|
|
94
93
|
return 0;
|
|
95
94
|
}
|
package/bin/seed-templates.mjs
CHANGED
|
@@ -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
|
-
//
|
|
29
|
-
const binPath = (root) => docnexusBin(root)
|
|
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 (!
|
|
40
|
-
throw new Error('
|
|
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(
|
|
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
|
|
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 失败';
|
package/bin/workflow-run.mjs
CHANGED
|
@@ -23,7 +23,7 @@ import { findExistingDbConfig, getDb } from './lib/db.mjs';
|
|
|
23
23
|
import { report, installSkills, linkSkills, updateGitignore, setupHome, recordEnv } from './lib/install.mjs';
|
|
24
24
|
import { setupCodeGraph } from './lib/codegraph.mjs';
|
|
25
25
|
import { uninstall } from './lib/uninstall.mjs';
|
|
26
|
-
import { checkNodeForDocNexus, setupDocNexus, refreshDocNexusLinks, templateRecords, saveTemplateRecords, reportTemplateStatus, installedSkills, docnexusConnected, printTemplateDiffs } from './lib/docnexus.mjs';
|
|
26
|
+
import { checkNodeForDocNexus, setupDocNexus, refreshDocNexusLinks, cleanupLegacyDocNexus, templateRecords, saveTemplateRecords, reportTemplateStatus, installedSkills, docnexusConnected, printTemplateDiffs } from './lib/docnexus.mjs';
|
|
27
27
|
import { upgradeOptions, doctor } from './lib/doctor.mjs';
|
|
28
28
|
import { docsRepo } from './lib/docsrepo.mjs';
|
|
29
29
|
import { findLegacy, legacyMessage } from './lib/legacy.mjs';
|
|
@@ -207,6 +207,8 @@ async function main() {
|
|
|
207
207
|
db = await getDb(o);
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
+
cleanupLegacyDocNexus(o.root);
|
|
211
|
+
|
|
210
212
|
// 安装目标:单一来源模式下 .agents/skills 只装一次,其余环境建链接;--copy 时各环境各装一份
|
|
211
213
|
const targets = [];
|
|
212
214
|
const links = [];
|
|
@@ -25,7 +25,7 @@ description: "Generate a Markdown release/deployment plan (上线方案) from th
|
|
|
25
25
|
1. `git rev-parse --is-inside-work-tree` 为 `true`,否则停。
|
|
26
26
|
2. 两个分支都能 `git rev-parse --verify`(或 `origin/<branch>`);不存在 → 列出候选让用户挑,停。
|
|
27
27
|
3. 读 `workflow/profile.json`:发布平台、Region、配置中心、定时任务注解、是否需要数据评审都取自这里;未配置的写「待填写」,**不要沿用其他项目的取值**。
|
|
28
|
-
4. DocNexus 可用时运行 `node <skills>/workflow/scripts/knowledge.mjs create-release-md-doc --match <模块名 / 业务名词> --query "<一句话:本次上线改了什么>"`(模块名取分支名或 diff 涉及的模块),看以往上线方案沉淀的风险、回滚要点与技术方案沉淀;相关的在文档对应章节引用 `docnexus/library/<path>`(见 `<skills>/workflow/references/docnexus.md`「按分类参考知识库」)。未接入就跳过。
|
|
28
|
+
4. DocNexus 可用时运行 `node <skills>/workflow/scripts/knowledge.mjs create-release-md-doc --match <模块名 / 业务名词> --query "<一句话:本次上线改了什么>"`(模块名取分支名或 diff 涉及的模块),看以往上线方案沉淀的风险、回滚要点与技术方案沉淀;相关的在文档对应章节引用 `workflow-run/docnexus/library/<path>`(见 `<skills>/workflow/references/docnexus.md`「按分类参考知识库」)。未接入就跳过。
|
|
29
29
|
|
|
30
30
|
### Step 1 — PRD(仅传入 `--prd` 时)
|
|
31
31
|
|
|
@@ -81,7 +81,7 @@ description: "Generate a Markdown release/deployment plan (上线方案) from th
|
|
|
81
81
|
|
|
82
82
|
- 发布到 wiki:`/md-to-wiki <文档路径> [pageId]`(发布过的文档有发布记录,可省略 pageId)。
|
|
83
83
|
- 模板回写:用户对生成的文档做了结构性调整(新增 / 删除 / 重排章节、改表格列)时,问要不要同步回模板:未接入 DocNexus 时按「修改模板」用 `template.mjs draft / check / save release` 改(用户确认后保存);已接入时按 `<skills>/workflow/references/templates.md`「模板随知识库更新」提议同步回模板。
|
|
84
|
-
- 沉淀到 DocNexus(DocNexus 可用时,判定见 `<skills>/workflow/SKILL.md`「增强能力探测」):列出值得长期记住的决策、配置 / 依赖变化、风险与坑,**询问用户**;同意后按 DocNexus 入口 skill `docnexus` 的 capture 流程写入,分类用 `node <skills>/workflow/scripts/resolve.mjs category create-release-md-doc` 的结果作为调用方显式分类封存(见 `<skills>/workflow/references/docnexus.md`「写入 DocNexus 的分类」),并回报 `docnexus/library/<path>`。只沉淀确认过的事实,不沉淀「待补充」「待人工确认」的推断。未检测到 DocNexus 就跳过,不提示安装。
|
|
84
|
+
- 沉淀到 DocNexus(DocNexus 可用时,判定见 `<skills>/workflow/SKILL.md`「增强能力探测」):列出值得长期记住的决策、配置 / 依赖变化、风险与坑,**询问用户**;同意后按 DocNexus 入口 skill `docnexus` 的 capture 流程写入,分类用 `node <skills>/workflow/scripts/resolve.mjs category create-release-md-doc` 的结果作为调用方显式分类封存(见 `<skills>/workflow/references/docnexus.md`「写入 DocNexus 的分类」),并回报 `workflow-run/docnexus/library/<path>`。只沉淀确认过的事实,不沉淀「待补充」「待人工确认」的推断。未检测到 DocNexus 就跳过,不提示安装。
|
|
85
85
|
|
|
86
86
|
## Red Flags — STOP
|
|
87
87
|
|
|
@@ -24,7 +24,7 @@ description: "Generate a Markdown technical design doc (技术方案) from the d
|
|
|
24
24
|
1. `git rev-parse --is-inside-work-tree` 为 `true`,否则停。
|
|
25
25
|
2. 两个分支都能 `git rev-parse --verify`(或 `origin/<branch>`);不存在 → 列出 `git branch -a -i --list "*<关键词>*"` 的候选让用户挑,停。
|
|
26
26
|
3. 读 `workflow/profile.json`:发布平台、Region、配置中心、定时任务注解、是否需要数据评审都取自这里;未配置的写「待填写」,**不要沿用其他项目的取值**。
|
|
27
|
-
4. DocNexus 可用时运行 `node <skills>/workflow/scripts/knowledge.mjs create-tech-md-doc --match <模块名 / 业务名词> --query "<一句话:本次改动做了什么>"`(模块名取分支名或 diff 涉及的模块),看以往技术方案沉淀的决策与约定、相关开发沉淀;相关的在文档对应章节引用 `docnexus/library/<path>`(见 `<skills>/workflow/references/docnexus.md`「按分类参考知识库」)。未接入就跳过。
|
|
27
|
+
4. DocNexus 可用时运行 `node <skills>/workflow/scripts/knowledge.mjs create-tech-md-doc --match <模块名 / 业务名词> --query "<一句话:本次改动做了什么>"`(模块名取分支名或 diff 涉及的模块),看以往技术方案沉淀的决策与约定、相关开发沉淀;相关的在文档对应章节引用 `workflow-run/docnexus/library/<path>`(见 `<skills>/workflow/references/docnexus.md`「按分类参考知识库」)。未接入就跳过。
|
|
28
28
|
|
|
29
29
|
### Step 1 — PRD(仅传入 `--prd` 时)
|
|
30
30
|
|
|
@@ -80,7 +80,7 @@ description: "Generate a Markdown technical design doc (技术方案) from the d
|
|
|
80
80
|
- 发布到 wiki:`/md-to-wiki <文档路径> [pageId]`(发布过的文档有发布记录,可省略 pageId)。
|
|
81
81
|
- 模板回写:用户对生成的文档做了结构性调整(新增 / 删除 / 重排章节、改表格列)时,问要不要同步回模板:未接入 DocNexus 时按「修改模板」用 `template.mjs draft / check / save tech` 改(用户确认后保存);已接入时按 `<skills>/workflow/references/templates.md`「模板随知识库更新」提议同步回模板。
|
|
82
82
|
- 要上线:`/create-release-md-doc <dev> <main> --tech <文档路径>` 复用本文档的事实。
|
|
83
|
-
- 沉淀到 DocNexus(DocNexus 可用时,判定见 `<skills>/workflow/SKILL.md`「增强能力探测」):列出值得长期记住的决策、约定、配置 / 依赖变化、风险,**询问用户**;同意后按 DocNexus 入口 skill `docnexus` 的 capture 流程写入,分类用 `node <skills>/workflow/scripts/resolve.mjs category create-tech-md-doc` 的结果作为调用方显式分类封存(见 `<skills>/workflow/references/docnexus.md`「写入 DocNexus 的分类」),并回报 `docnexus/library/<path>`。只沉淀确认过的事实,不沉淀「待补充」「待人工确认」的推断。未检测到 DocNexus 就跳过,不提示安装。
|
|
83
|
+
- 沉淀到 DocNexus(DocNexus 可用时,判定见 `<skills>/workflow/SKILL.md`「增强能力探测」):列出值得长期记住的决策、约定、配置 / 依赖变化、风险,**询问用户**;同意后按 DocNexus 入口 skill `docnexus` 的 capture 流程写入,分类用 `node <skills>/workflow/scripts/resolve.mjs category create-tech-md-doc` 的结果作为调用方显式分类封存(见 `<skills>/workflow/references/docnexus.md`「写入 DocNexus 的分类」),并回报 `workflow-run/docnexus/library/<path>`。只沉淀确认过的事实,不沉淀「待补充」「待人工确认」的推断。未检测到 DocNexus 就跳过,不提示安装。
|
|
84
84
|
|
|
85
85
|
## Red Flags — STOP
|
|
86
86
|
|
package/dev/SKILL.md.tpl
CHANGED
|
@@ -42,7 +42,7 @@ description: "Implement a feature or fix a bug in this project end to end: under
|
|
|
42
42
|
### F1 — 理解需求
|
|
43
43
|
|
|
44
44
|
- 需求来源是 PRD pageId/URL → 按 `references/prd-fetch.md` 拉取并提炼;wiki 不可用 → 请用户贴出关键内容。
|
|
45
|
-
- DocNexus 可用 → 先运行 `node <skills>/workflow/scripts/knowledge.mjs dev --match <模块名 / 业务名词> --query "<一句话需求>"`,看以往开发沉淀、技术方案和反复出现的审查问题里有没有相关的(`--query` 在这些分类内按正文 recall
|
|
45
|
+
- DocNexus 可用 → 先运行 `node <skills>/workflow/scripts/knowledge.mjs dev --match <模块名 / 业务名词> --query "<一句话需求>"`,看以往开发沉淀、技术方案和反复出现的审查问题里有没有相关的(`--query` 在这些分类内按正文 recall)。引用 `workflow-run/docnexus/library/<path>`(见 `<skills>/workflow/references/docnexus.md`「按分类参考知识库」)。
|
|
46
46
|
- 先在心里列出初版 **需求要点 + 验收标准**(可验证的行为,而不是实现手段)和还没定的决定,带进 F2 查事实、F3 澄清。
|
|
47
47
|
|
|
48
48
|
### F2 — 现状调研
|
|
@@ -57,7 +57,7 @@ description: "Implement a feature or fix a bug in this project end to end: under
|
|
|
57
57
|
|
|
58
58
|
| 情况 | 做法 |
|
|
59
59
|
|---|---|
|
|
60
|
-
| S 规模且 F2 后没有未定的决定 |
|
|
60
|
+
| S 规模且 F2 后没有未定的决定 | 跳过需求澄清提问,写明本次的理解和假设;仍须按 F4 确认规模 |
|
|
61
61
|
| 只有一两个小决定 | 问一轮即可 |
|
|
62
62
|
| M / L 规模,或用户说「拷问一下 / grill me / 先把需求问清楚」 | 按轮问到前沿为空,输出需求要点、验收标准、不在本次范围、已确认的决定,**等用户确认理解一致**再进 F4 |
|
|
63
63
|
|
|
@@ -67,15 +67,17 @@ description: "Implement a feature or fix a bug in this project end to end: under
|
|
|
67
67
|
|
|
68
68
|
| 规模 | 判定 | 做法 |
|
|
69
69
|
|---|---|---|
|
|
70
|
-
| **S** | ≤3 个文件,无接口签名 / 表结构 / 配置项变更,不涉及权限 / 资金 |
|
|
70
|
+
| **S** | ≤3 个文件,无接口签名 / 表结构 / 配置项变更,不涉及权限 / 资金 | 用户确认规模后,在对话中列改动步骤并开始 |
|
|
71
71
|
| **M** | 新增或修改接口、配置项、少量表字段 | 在对话中给出简要方案(方案模板的精简版),**等用户确认后再写代码** |
|
|
72
72
|
| **L** | 新模块、跨服务、表结构较大变更、数据迁移 | 按方案模板写方案文档落盘(见 Artifact Location),用户确认后再写代码;完成后建议用 `create-tech-md-doc` 生成正式技术方案 |
|
|
73
73
|
|
|
74
74
|
规模拿不准时按大一级处理。涉及权限(鉴权、角色、数据可见范围)或资金(金额计算、扣款、退款、对账)的改动,文件再少也至少按 M:需求理解(F3)和方案都要用户确认。
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
**规模确认(S / M / L 都必做,含快速通道)**:判定后向用户说明推荐规模、依据(预计改动文件 / 模块、接口 / 数据 / 配置变化及风险),以及对应流程(是否走快速通道、方案与交付说明是否落盘),明确询问是否按该规模处理。**等用户明确确认后,再按该规模推进后续流程、绑定需求或写代码**;只告知判定结果或默认选中推荐项不算确认。可以与 F3 的理解确认或简要方案确认在同一轮提出,但必须明确列出规模并收到确认;本轮对话中已明确确认的同一规模不重复询问。用户调整范围或实现中发现规模变化时,说明变化依据,确认新规模后再继续。规模确认属于流程选择,不算 F3 的业务决定,不影响 S 规模快速通道资格。
|
|
77
77
|
|
|
78
|
-
**
|
|
78
|
+
**M / L 规模先定需求名**:用户确认规模、确定要落盘时,按 Artifact Location 运行 `requirement.mjs which`,当前分支没绑定需求就在这一轮问用户这个需求叫什么(可以和规模确认、澄清问题一起问),之后的方案、交付、审查、技术方案、上线方案都归到这个需求下。
|
|
79
|
+
|
|
80
|
+
**S 规模快速通道**:用户已确认 S 规模,且改动**不涉及**配置项、SQL / DDL、对外接口(HTTP 接口签名、MQ 消息体、对外 DTO)、表结构、权限、资金,也没有 F3 中确认过的业务决定 → 走快速通道:F5~F7 照常(编译、测试、自审都不省),F8 只在对话里交付,不落盘交付说明、不问 DocNexus 沉淀(见 F8「快速通道交付」)。规模确认时说明「按快速通道处理,不落盘交付说明」。实现中一旦碰到上面任何一项、改动超过 3 个文件,或用户要求落盘 → 退出快速通道,按完整 F8 交付;重新评估后规模变化的,先确认新规模再继续。小改动也要走完整流程是用户绕开 dev 的主要原因,所以只在上面这些会影响上线和团队的情况下才落盘。
|
|
79
81
|
|
|
80
82
|
方案模板:在项目根运行 `node <skills>/workflow/scripts/resolve.mjs template dev plan-template.md`,读输出的 `path`;退出码 2(已接入 DocNexus 但缺模板)时停下并按 `hint` 处理。L 规模的方案文档落盘后运行 `node <skills>/workflow/scripts/lint-doc.mjs <方案路径> --skill dev` 自检,再请用户确认。
|
|
81
83
|
|
|
@@ -117,7 +119,7 @@ description: "Implement a feature or fix a bug in this project end to end: under
|
|
|
117
119
|
- PR / MR:用户要提 PR / MR 时,运行 `node <skills>/workflow/scripts/pr-desc.mjs`(缺省当前分支、基线 `profile.main_branch`),它把本分支的交付说明、审查报告、技术方案、上线方案汇总成描述(没有交付说明时退回用提交记录和改动文件),把输出给用户;用 `gh pr create` / `glab mr create` 创建要用户明确同意。
|
|
118
120
|
- 文档:运行 `node <skills>/workflow/scripts/artifacts.mjs` 看本分支已有哪些产物(开发方案、交付说明、审查报告、技术方案、上线方案及 wiki 发布记录),按输出的 `next` 建议下一步:M / L 规模缺技术方案时建议 `create-tech-md-doc`,要上线时建议 `create-release-md-doc`(会自动引用本分支的技术方案和交付说明)。
|
|
119
121
|
- **沉淀到 DocNexus**(`resolve.mjs status` 的 `docnexus.ok` 为 true 时**每次都问**):明确问用户「要不要启动 DocNexus 流程,把本次交付沉淀到 DocNexus?」,并从交付说明里列出建议沉淀的内容(已确认的决定、新约定、踩到的坑、缺陷根因、配置 / 表结构变化);没有特别值得记的也照问,并说明这一点。
|
|
120
|
-
- 用户同意 → 以交付说明为来源,按 DocNexus 入口 skill `docnexus` 的 capture 流程写入;正文只写知识,不写分支、路径;有 facts 时按 `<skills>/workflow/references/docnexus.md`「沉淀的来源」的格式写 provenance;分类用 `node <skills>/workflow/scripts/resolve.mjs category dev` 的结果作为调用方显式分类封存(见 `<skills>/workflow/references/docnexus.md`「写入 DocNexus 的分类」)。只沉淀确认过的事实,不沉淀「未验证」「待确认」的内容;知识库已有同主题的沉淀时更新那一篇(带 `--base-version`),不重复新建。写入成功后把交付说明的「DocNexus」元信息行改成 `docnexus/library/<path>`,并回报给用户。
|
|
122
|
+
- 用户同意 → 以交付说明为来源,按 DocNexus 入口 skill `docnexus` 的 capture 流程写入;正文只写知识,不写分支、路径;有 facts 时按 `<skills>/workflow/references/docnexus.md`「沉淀的来源」的格式写 provenance;分类用 `node <skills>/workflow/scripts/resolve.mjs category dev` 的结果作为调用方显式分类封存(见 `<skills>/workflow/references/docnexus.md`「写入 DocNexus 的分类」)。只沉淀确认过的事实,不沉淀「未验证」「待确认」的内容;知识库已有同主题的沉淀时更新那一篇(带 `--base-version`),不重复新建。写入成功后把交付说明的「DocNexus」元信息行改成 `workflow-run/docnexus/library/<path>`,并回报给用户。
|
|
121
123
|
- 用户不同意 → 元信息行保持「未沉淀」,不再追问。
|
|
122
124
|
- 未接入 DocNexus → 元信息行写「未接入」,不提问、不提示安装。
|
|
123
125
|
|
|
@@ -141,6 +143,7 @@ description: "Implement a feature or fix a bug in this project end to end: under
|
|
|
141
143
|
|
|
142
144
|
### B4 — 修复与验证
|
|
143
145
|
|
|
146
|
+
- 修复前按 F4 的标准判定规模,并完成同样的规模确认(S / M / L 都适用,说明是否走快速通道及落盘要求);用户明确确认后再改代码。修复中规模变化时先确认新规模再继续。
|
|
144
147
|
- 做能消除根因的**最小改动**;B2 的测试由红变绿,相关已有测试保持通过(运行方式同 F6)。
|
|
145
148
|
- 如果历史脏数据需要修正,写成 SQL 脚本交给用户,不直接执行。
|
|
146
149
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> 类型:{{功能开发 / 缺陷修复}} | 规模:{{S / M / L}} | 日期:{{YYYY-MM-DD}}
|
|
4
4
|
> 对比分支:`{{当前分支}}` ←→ `{{主分支}}`
|
|
5
5
|
> 参考产物:{{开发方案路径,没有写「无」}}
|
|
6
|
-
> DocNexus:{{沉淀后写 docnexus/library/<path>;未沉淀写「未沉淀」,未接入写「未接入」}}
|
|
6
|
+
> DocNexus:{{沉淀后写 workflow-run/docnexus/library/<path>;未沉淀写「未沉淀」,未接入写「未接入」}}
|
|
7
7
|
|
|
8
8
|
<!-- F8 / B5 交付时按此落盘到 requirement.mjs path delivery 给出的路径(workflow-run/docs/requirements/<需求名>/<项目名>/delivery.md)。小改动每节写一两行即可,不适用的节写「无」;验证结果必须如实 -->
|
|
9
9
|
|
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@ description: "Review source code (any language; Java/Spring, Go, TypeScript, Pyt
|
|
|
12
12
|
- **标准**(默认):检查清单全部维度,**风格类(清单「一致性 / 风格」一节)不查**;「优化建议(LOW)」只写有实际收益的(性能、可维护性上的具体问题),没有就写「无」。
|
|
13
13
|
- **深审**(「仔细审」「全面审」,或资金 / 权限 / 对外接口的大改动合并前):全部维度,包括风格;MEDIUM 也复核。
|
|
14
14
|
2. **建立理解**:先读实体、接口、DTO、调用链,理清数据流再下结论;不要凭文件名猜内容。
|
|
15
|
-
- **可选:查项目约定(DocNexus)**:DocNexus 可用时(`resolve.mjs status` 的 `docnexus.ok`),先运行 `node <skills>/workflow/scripts/knowledge.mjs project-review --match <模块名 / 业务名词> --query "<一句话:改动涉及的模块与业务>"`,看以往审查沉淀的反复问题和开发约定(`--query` 在这些分类内按正文 recall
|
|
15
|
+
- **可选:查项目约定(DocNexus)**:DocNexus 可用时(`resolve.mjs status` 的 `docnexus.ok`),先运行 `node <skills>/workflow/scripts/knowledge.mjs project-review --match <模块名 / 业务名词> --query "<一句话:改动涉及的模块与业务>"`,看以往审查沉淀的反复问题和开发约定(`--query` 在这些分类内按正文 recall),找相关的编码约定、历史决策与已知坑,作为审查依据并在报告中引用 `workflow-run/docnexus/library/<path>`。未检测到或查询失败就跳过,并在「未覆盖与待确认」中注明;审查过程中**不写入** DocNexus(写入见第 8 步)。
|
|
16
16
|
- **同分支已有产物**:运行 `node <skills>/workflow/scripts/artifacts.mjs --branch <dev分支>`(单文件 / 未提交改动用当前分支,省略 `--branch`),看输出的 `latest`:
|
|
17
17
|
- `latest.plan` 或 `latest.tech` 存在 → 读其中的验收标准、改动设计、接口 / 数据 / 配置变更,第 3 步审 diff 时做**需求对照**:方案里有、diff 里找不到的 → 漏实现(属于验收标准的按 HIGH,其余 MEDIUM);diff 里有、方案里没提的接口 / 表结构 / 配置 / 依赖变更 → 列出请作者确认(不一定是问题)。只对照能在 diff 里核对的项,不推测业务语义。
|
|
18
18
|
- `latest.delivery` 存在(dev 的交付说明)→ 其中「已确认的决定」并入需求对照的依据;「配置清单」「SQL 脚本」和 diff 对照,diff 里有而交付说明漏列的配置 key / DDL 记为 MEDIUM(上线时会被漏掉)。
|
|
@@ -32,7 +32,7 @@ description: "Review source code (any language; Java/Spring, Go, TypeScript, Pyt
|
|
|
32
32
|
结果:「成立」在报告「复核」列标「✅」;「可能成立」标「⚠」;「不成立」从问题表里删掉(独立复核否掉的,在「未覆盖与待确认」里记一句「复核排除 N 条」)。复核方式(独立 / 自行)写进报告「审查方式」元信息。
|
|
33
33
|
7. **输出报告**(由 dev 自审调用时跳过第 7~9 步,问题表、影响面、未覆盖项交回 dev 统一交付,见 dev 的 F7):按报告模板输出(`node <skills>/workflow/scripts/resolve.mjs template project-review report.md` 取 `path`;退出码 2 时停下并按 `hint` 处理),落盘规则见 Artifact Location。
|
|
34
34
|
8. **沉淀到 DocNexus(需用户授权)**:`resolve.mjs status` 的 `docnexus.ok` 为 true 时,交付报告后**每次都问**用户「要不要启动 DocNexus 流程,把本次审查沉淀到 DocNexus?」,并从本次发现里列出建议沉淀的内容:同类问题再次出现(知识库里已有类似沉淀,或本次多处出现)、新确立的编码约定、容易踩的坑;没有特别值得记的也照问,并说明这一点。未接入时不提问,报告的「DocNexus」元信息行写「未接入」。
|
|
35
|
-
- 用户同意 → 按 DocNexus 入口 skill `docnexus` 的 capture 流程写入;正文只写知识,不写分支、路径;有 facts 时按 `<skills>/workflow/references/docnexus.md`「沉淀的来源」的格式写 provenance;分类用 `node <skills>/workflow/scripts/resolve.mjs category project-review` 的结果作为调用方显式分类封存(见 `<skills>/workflow/references/docnexus.md`「写入 DocNexus 的分类」)。只沉淀问题模式和约定,不沉淀某次审查的逐条清单,也不沉淀「⚠ 待确认」的发现;知识库已有同类沉淀时更新那一篇(带 `--base-version`),不要重复新建。写入成功后把报告的「DocNexus」元信息行改成 `docnexus/library/<path>`(多篇用「、」分隔),并回报给用户。
|
|
35
|
+
- 用户同意 → 按 DocNexus 入口 skill `docnexus` 的 capture 流程写入;正文只写知识,不写分支、路径;有 facts 时按 `<skills>/workflow/references/docnexus.md`「沉淀的来源」的格式写 provenance;分类用 `node <skills>/workflow/scripts/resolve.mjs category project-review` 的结果作为调用方显式分类封存(见 `<skills>/workflow/references/docnexus.md`「写入 DocNexus 的分类」)。只沉淀问题模式和约定,不沉淀某次审查的逐条清单,也不沉淀「⚠ 待确认」的发现;知识库已有同类沉淀时更新那一篇(带 `--base-version`),不要重复新建。写入成功后把报告的「DocNexus」元信息行改成 `workflow-run/docnexus/library/<path>`(多篇用「、」分隔),并回报给用户。
|
|
36
36
|
- 用户不同意 → 元信息行保持「未沉淀」。
|
|
37
37
|
9. **记录运行反馈**(不问用户):按 `<skills>/workflow/references/feedback.md` 运行 `node <skills>/workflow/scripts/feedback.mjs add --skill project-review --model <模型名> --mode review --depth <快审|标准|深审> --rounds <提问轮数> --corrections <用户纠正审查范围的次数> --findings <C>/<H>/<M>/<L>`,有跳过的步骤、不清楚或做不到的规则、用户的抱怨时再加 `--skipped` / `--unclear` / `--feedback`。只写过程,不写代码和业务名词;报错就提一句,不影响交付。
|
|
38
38
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
> 对比分支:`{{dev分支}}` ←→ `{{基线分支}}`(单文件 / 未提交改动两边都写当前分支)
|
|
5
5
|
> 审查方式:{{快审 / 标准 / 深审}},{{全量精读 / 分批精读(N 批,按风险排序)/ 分 K 个角度并行查找 / 部分摘要化}},{{独立复核 / 自行复核}} | 清单来源:{{DocNexus / 项目文件 / skill 默认}}
|
|
6
6
|
> 参考产物:{{本分支的开发方案 / 交付说明 / 技术方案路径;复审时加「上次审查 v<N>(<日期>)」;没有写「无」}}
|
|
7
|
-
> DocNexus:{{沉淀后写 docnexus/library/<path>;未沉淀写「未沉淀」,未接入写「未接入」}}
|
|
7
|
+
> DocNexus:{{沉淀后写 workflow-run/docnexus/library/<path>;未沉淀写「未沉淀」,未接入写「未接入」}}
|
|
8
8
|
|
|
9
9
|
## 结论摘要 *
|
|
10
10
|
|
package/workflow/SKILL.md.tpl
CHANGED
|
@@ -76,7 +76,7 @@ dev(需求 → 方案 → 实现 → 测试 → 自审)
|
|
|
76
76
|
| 能力 | 判定为可用 | 用途 |
|
|
77
77
|
|---|---|---|
|
|
78
78
|
| wiki | `md-to-wiki/wiki-config.json` 中 `enabled=true` 且 `wiki.base_url` 非空;或环境存在 wiki MCP 工具 | 读 PRD(各 skill 按 `references/prd-fetch.md`,REST 通道统一调 `md-to-wiki/scripts/upload.mjs get`)、发布文档 |
|
|
79
|
-
| DocNexus |
|
|
79
|
+
| DocNexus | `.docnexus/project.json` 为 format 7 且 `output_dir=workflow-run`,CLI 可执行:`.docnexus/node_modules/.bin/docnexus`(`resolve.mjs status` 的 `docnexus.bin`);命令用 `node <skills>/workflow/scripts/docnexus.mjs <参数>` 运行(`docnexus.run`),见 `references/docnexus.md`「DocNexus 的运行位置」 | 查约定与历史决策、沉淀知识、提供文档模板 |
|
|
80
80
|
| 数据库 | `database-query/config.json` 存在且 `connections` 非空 | 查表结构、核对数据 |
|
|
81
81
|
| 代码图谱(CodeGraph) | 项目根有 `.codegraph/` 索引,且 `codegraph` 命令可用(`resolve.mjs status` 的 `codegraph.ok`) | 读代码、查调用链与影响面、找受影响的测试;用法见 `references/codegraph.md` |
|
|
82
82
|
| 项目画像 | `workflow/profile.json` 中 `main_branch` 与 `build` / `test` 非空 | dev 构建与测试 |
|
|
@@ -2,18 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
各 skill 写入与参考 DocNexus 时共用的规则。
|
|
4
4
|
|
|
5
|
-
## DocNexus
|
|
5
|
+
## DocNexus 的运行位置
|
|
6
6
|
|
|
7
|
-
DocNexus
|
|
7
|
+
DocNexus 在业务项目根运行,使用 `.docnexus/node_modules/.bin/docnexus`;运行依赖、skills、草稿和索引都在 `.docnexus/`。初始化参数为 `init --output-dir workflow-run --agent none`,配置写入 `.docnexus/project.json`,知识输出在 `workflow-run/docnexus/`。
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
- 本工作流的脚本已处理位置:`knowledge.mjs` 在那里运行,输出的 `file` 是从项目根读正文的路径;`resolve.mjs template` 给出的模板路径、`expected` 都已带上位置。
|
|
15
|
-
- 引用知识文档仍写 `docnexus/library/<path>`(产物里的「DocNexus:」行也一样)。
|
|
16
|
-
- `workflow-run/` 是独立文档仓库时(见 `references/requirements.md`),知识库由共用这个仓库的各服务一起使用:按分类参考和 recall 都会看到其他服务的沉淀,引用前确认和本次任务相关。写入(capture)完成后运行 `requirement.mjs pending`,`knowledge_files` 是知识库里没提交的文件,`knowledge_commands` 是提交命令(把提交信息换成这次沉淀了什么)。和产物一样,给用户看、用户确认后再提交,推送另外问。
|
|
9
|
+
- 也可使用 `node <skills>/workflow/scripts/docnexus.mjs <参数>`,辅助脚本用 `node <skills>/workflow/scripts/docnexus.mjs --node .docnexus/skills/<…>.mjs <参数>`;所有路径都相对业务项目根。
|
|
10
|
+
- 使用 status 返回的 `workspace_path` / `library_path` / `internal_directory` 和 draft / document 返回的路径。不再做旧版的工作目录切换或前缀转换。知识读取和引用都写 `workflow-run/docnexus/library/<path>`。
|
|
11
|
+
- `resolve.mjs status` 给出 `root`、`internal`、`workspace` 与 `run`;`knowledge.mjs` 的 `path` 与 `file` 都是 CLI 返回的业务根相对路径;`resolve.mjs template` 给出模板实际位置。
|
|
12
|
+
- 不在 `workflow-run/` 内安装或初始化 DocNexus,不使用旧版根目录 `docnexus/` 的 CLI,也不回退到全局 CLI。缺失时通过 workflow-run 安装 / 升级修复。
|
|
13
|
+
- `workflow-run/` 是独立文档仓库时,知识输出可由多个服务共用;各服务仍在自己的业务根配置 `.docnexus/project.json`、运行本机 CLI。写入后 `requirement.mjs pending` 的 `knowledge_files`、`knowledge_commands` 给出知识仓库的待提交内容与命令,按既有授权提交,推送另行授权。
|
|
17
14
|
|
|
18
15
|
## 写入 DocNexus 的分类
|
|
19
16
|
|
|
@@ -33,7 +30,7 @@ node <skills>/workflow/scripts/resolve.mjs category <skill>
|
|
|
33
30
|
DocNexus 按它自己的设计使用:沉淀文档的正文只写知识(约定、决定、坑、根因),不写分支名、产物路径这类工作流信息,免得进入 recall 的检索索引造成干扰,也免得知识文档和工作流的目录结构绑在一起。
|
|
34
31
|
|
|
35
32
|
- 有 facts 时,每条 fact 的 `provenance` 写一条简单的来源,由你按这个格式写:`<产物种类> <产物路径>(<产物日期>)`,例如 `交付说明 workflow-run/docs/requirements/订单导出支持按渠道筛选/delivery.md(2026-10-15)`。没有 facts 就不写。
|
|
36
|
-
- 从知识库追溯回产物,靠的是产物里的「DocNexus:docnexus/library/<path>」元信息行(沉淀成功后写入)反查,`/workflow view` 会按需求列出沉淀到的知识文档,以及每篇来自哪份产物。不往 DocNexus 里多写内容。
|
|
33
|
+
- 从知识库追溯回产物,靠的是产物里的「DocNexus:workflow-run/docnexus/library/<path>」元信息行(沉淀成功后写入)反查,`/workflow view` 会按需求列出沉淀到的知识文档,以及每篇来自哪份产物。不往 DocNexus 里多写内容。
|
|
37
34
|
|
|
38
35
|
## 按分类参考知识库
|
|
39
36
|
|
|
@@ -45,7 +42,6 @@ node <skills>/workflow/scripts/knowledge.mjs <skill> [--match 词1,词2] [--limi
|
|
|
45
42
|
|
|
46
43
|
- 参考哪些分类由该 skill 的 `skill.json`(`docnexus.readFrom`,列出要参考哪些 skill 的沉淀)决定,分类名经 `resolve.mjs category` 解析,项目画像改名后自动跟上。
|
|
47
44
|
- 列表:每个分类下的文档(标题、摘要、`path`、更新时间,按更新时间倒序,不含正文)。`--match` 只按标题 / 摘要 / 标签 / 路径过滤,先用本次的模块名、业务名词,没结果再去掉 `--match` 看最近的几篇。
|
|
48
|
-
- `--query`:在每个分类内 `recall
|
|
49
|
-
|
|
50
|
-
- 从列表和 recall 结果里挑与本次任务相关的读 `path`(普通 Markdown),引用时写 `docnexus/library/<path>`。需要跨分类找(例如别的团队的约定)时,仍可按 `docnexus-recall` 做不带分类的 `recall`。
|
|
45
|
+
- `--query`:在每个分类内 `recall`,结果放在各分类的 `recall` 字段,能找到标题里没写、正文里提到的内容。问题写成一句话,带上模块名和业务名词,例如「订单列表按创建时间筛选 时区 分页」。
|
|
46
|
+
- 从列表和 recall 结果里挑与本次任务相关的读 `path`(普通 Markdown),引用时写 `workflow-run/docnexus/library/<path>`。需要跨分类找(例如别的团队的约定)时,仍可按 `docnexus-recall` 做不带分类的 `recall`。
|
|
51
47
|
- 退出码 2 表示未接入 DocNexus,跳过即可;只读,不写入。
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// DocNexus format 7:项目根运行,内部状态在 .docnexus/,知识输出固定到 workflow-run/docnexus/。
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
export const DOCNEXUS_INTERNAL = '.docnexus';
|
|
6
|
+
export const DOCNEXUS_OUTPUT = 'workflow-run';
|
|
7
|
+
export const docnexusRoot = (root) => path.resolve(root);
|
|
8
|
+
export const docnexusWorkspace = (root) => path.resolve(root, DOCNEXUS_OUTPUT, 'docnexus');
|
|
9
|
+
export const docnexusInternal = (root) => path.resolve(root, DOCNEXUS_INTERNAL);
|
|
10
|
+
|
|
11
|
+
export function docnexusConfigured(root) {
|
|
12
|
+
try {
|
|
13
|
+
const marker = JSON.parse(fs.readFileSync(path.join(docnexusInternal(root), 'project.json'), 'utf8'));
|
|
14
|
+
return marker.format_version === 7 && marker.output_dir === DOCNEXUS_OUTPUT;
|
|
15
|
+
} catch { return false; }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function docnexusBinPath(root) {
|
|
19
|
+
const name = process.platform === 'win32' ? 'docnexus.cmd' : 'docnexus';
|
|
20
|
+
const bin = path.join(docnexusInternal(root), 'node_modules', '.bin', name);
|
|
21
|
+
return fs.existsSync(bin) ? bin : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const docnexusConnected = (root) => docnexusConfigured(root) && docnexusBinPath(root) !== null;
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// `node docnexus/skills/<...>.mjs <参数>` 换成 `node <skills>/workflow/scripts/docnexus.mjs --node docnexus/skills/<...>.mjs <参数>`。
|
|
5
|
-
// 参数里的路径(草稿、docnexus/library/<path>)照旧相对 workflow-run/。输出与退出码原样透传。
|
|
2
|
+
// 在业务项目根运行 DocNexus。内部状态在 .docnexus/,知识输出在 workflow-run/docnexus/。
|
|
3
|
+
// CLI 返回的草稿、文档路径均相对业务项目根;输出与退出码原样透传。
|
|
6
4
|
//
|
|
7
5
|
// 用法(在项目根执行;--root 可指定项目根,须放在最前面):
|
|
8
6
|
// node .agents/skills/workflow/scripts/docnexus.mjs recall "订单幂等" --limit 5
|
|
9
|
-
// node .agents/skills/workflow/scripts/docnexus.mjs --node docnexus/skills/docnexus-library/scripts/query-documents.mjs --category dev-notes
|
|
7
|
+
// node .agents/skills/workflow/scripts/docnexus.mjs --node .docnexus/skills/docnexus-library/scripts/query-documents.mjs --category dev-notes
|
|
10
8
|
// 退出码:DocNexus 的退出码;DocNexus 没装时为 1
|
|
11
9
|
|
|
12
10
|
import fs from 'node:fs';
|
|
@@ -14,13 +12,14 @@ import path from 'node:path';
|
|
|
14
12
|
import { spawnSync } from 'node:child_process';
|
|
15
13
|
import { fileURLToPath } from 'node:url';
|
|
16
14
|
import { docnexusRoot, docnexusBin } from './resolve.mjs';
|
|
15
|
+
import { docnexusInternal } from './docnexus-layout.mjs';
|
|
17
16
|
|
|
18
17
|
const readJson = (f) => { try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch { return null; } };
|
|
19
18
|
|
|
20
19
|
// CLI 的入口 JS:直接用 node 跑,Windows 上不经过 .cmd 和 shell,参数不会被转义坏
|
|
21
|
-
// 包名取
|
|
20
|
+
// 包名取 .docnexus/package.json 的依赖(安装器用 npm install --prefix 装的)
|
|
22
21
|
export function cliEntry(root) {
|
|
23
|
-
const dn =
|
|
22
|
+
const dn = docnexusInternal(root);
|
|
24
23
|
const name = Object.keys(readJson(path.join(dn, 'package.json'))?.dependencies ?? {}).find((n) => /(^|\/)docnexus$/.test(n));
|
|
25
24
|
if (!name) return null;
|
|
26
25
|
const pkgDir = path.join(dn, 'node_modules', ...name.split('/'));
|
|
@@ -33,7 +32,7 @@ export function run(root, args, opts = {}) {
|
|
|
33
32
|
const cwd = docnexusRoot(root);
|
|
34
33
|
const o = { cwd, stdio: 'inherit', ...opts };
|
|
35
34
|
if (args[0] === '--node') {
|
|
36
|
-
if (!args[1]) return { status: 1, error: '--node
|
|
35
|
+
if (!args[1]) return { status: 1, error: '--node 后面要跟脚本路径(相对业务项目根)' };
|
|
37
36
|
return spawnSync(process.execPath, [path.resolve(cwd, args[1]), ...args.slice(2)], o);
|
|
38
37
|
}
|
|
39
38
|
const entry = cliEntry(root);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// 按分类列出与某个 skill 相关的 DocNexus 文档(标题、摘要、路径,不含正文),供 skill 开工前挑选阅读。
|
|
3
3
|
// 相关分类来自 skill.json 的 docnexus.readFrom(列出要参考哪些 skill 的沉淀),分类名经 resolve.mjs category 解析,
|
|
4
4
|
// 所以项目画像改了分类名也能跟上。列表用 document query --category;给了 --query 时再在每个分类内 recall
|
|
5
|
-
//
|
|
5
|
+
// 当前 DocNexus 直接支持按分类 recall。
|
|
6
6
|
// 只读,不写入任何东西。
|
|
7
7
|
//
|
|
8
8
|
// 用法(在项目根执行;--root 可指定项目根):
|
|
@@ -19,8 +19,8 @@ import { skillMeta, resolveCategory, docnexusConnected, docnexusBin, docnexusRoo
|
|
|
19
19
|
|
|
20
20
|
const IS_WIN = process.platform === 'win32';
|
|
21
21
|
|
|
22
|
-
//
|
|
23
|
-
const docnexus = (root, args) => spawnSync(path.resolve(root, docnexusBin(root)
|
|
22
|
+
// 在业务项目根运行;CLI 返回的路径已包含 output-dir 前缀。
|
|
23
|
+
const docnexus = (root, args) => spawnSync(path.resolve(root, docnexusBin(root)), args,
|
|
24
24
|
{ cwd: docnexusRoot(root), encoding: 'utf8', shell: IS_WIN, maxBuffer: 256 * 1024 * 1024 });
|
|
25
25
|
|
|
26
26
|
function runJson(root, args, what) {
|
|
@@ -34,21 +34,12 @@ function query(root, category) {
|
|
|
34
34
|
return r.ok ? { ok: true, documents: r.data.documents ?? [] } : r;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
// 旧版会静默忽略不认识的参数,recall 会退化成全库检索,所以先看帮助里 recall 有没有 --category
|
|
38
|
-
export function recallSupportsCategory(root) {
|
|
39
|
-
const r = docnexus(root, ['--help']);
|
|
40
|
-
return /recall[^\n]*--category/.test(`${r.stdout}${r.stderr}`);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
37
|
export function knowledge(root, skill, { match = [], limit = 20, query: question = '', recallLimit = 5 } = {}) {
|
|
44
38
|
const meta = skillMeta(skill);
|
|
45
39
|
if (!meta) return { ok: false, error: 'unknown', message: `未安装 skill「${skill}」` };
|
|
46
40
|
if (!docnexusConnected(root)) return { ok: false, error: 'no_docnexus', message: '未接入 DocNexus,跳过知识库参考' };
|
|
47
41
|
const terms = match.map((t) => t.toLowerCase()).filter(Boolean);
|
|
48
42
|
const hit = (d) => !terms.length || terms.some((t) => [d.title, d.summary, d.file_path, ...(d.tags ?? [])].some((v) => String(v ?? '').toLowerCase().includes(t)));
|
|
49
|
-
const byCategory = question ? recallSupportsCategory(root) : false;
|
|
50
|
-
// DocNexus 不在本项目时,读文件要加上它的位置
|
|
51
|
-
const dnRel = path.relative(path.resolve(root), docnexusRoot(root)).split(path.sep).join('/');
|
|
52
43
|
const seen = new Set();
|
|
53
44
|
const categories = [];
|
|
54
45
|
for (const from of meta.docnexus?.readFrom ?? []) {
|
|
@@ -60,19 +51,16 @@ export function knowledge(root, skill, { match = [], limit = 20, query: question
|
|
|
60
51
|
const docs = q.documents.filter(hit).sort((a, b) => String(b.updated_at).localeCompare(String(a.updated_at)));
|
|
61
52
|
const entry = {
|
|
62
53
|
category: c.category, from, ok: true, total: docs.length,
|
|
63
|
-
documents: docs.slice(0, limit).map((d) => ({ id: d.id, title: d.title, summary: d.summary, path: d.library_path,
|
|
54
|
+
documents: docs.slice(0, limit).map((d) => ({ id: d.id, title: d.title, summary: d.summary, path: d.library_path, file: d.library_path, updated_at: d.updated_at })),
|
|
64
55
|
};
|
|
65
|
-
if (
|
|
56
|
+
if (question) {
|
|
66
57
|
const r = runJson(root, ['recall', question, '--limit', String(recallLimit), '--category', c.category], 'recall');
|
|
67
58
|
entry.recall = r.ok ? r.data : { ok: false, error: r.error };
|
|
68
59
|
}
|
|
69
60
|
categories.push(entry);
|
|
70
61
|
}
|
|
71
|
-
const out = { ok: true, skill,
|
|
72
|
-
if (question)
|
|
73
|
-
out.recall_by_category = byCategory;
|
|
74
|
-
if (!byCategory) out.recall_hint = '已装的 DocNexus 不支持 recall --category(0.16.0 起支持):改用不带分类的 recall,并按上面列出的分类 / 路径挑结果;可升级 @rowansenne/docnexus';
|
|
75
|
-
}
|
|
62
|
+
const out = { ok: true, skill, categories };
|
|
63
|
+
if (question) out.recall_by_category = true;
|
|
76
64
|
return out;
|
|
77
65
|
}
|
|
78
66
|
|
|
@@ -18,6 +18,8 @@ import { spawnSync } from 'node:child_process';
|
|
|
18
18
|
import { fileURLToPath } from 'node:url';
|
|
19
19
|
import { inferProfile } from './infer-profile.mjs';
|
|
20
20
|
import { layout, HOME, TEMPLATES_DIR } from './requirement.mjs';
|
|
21
|
+
import { docnexusWorkspace, docnexusConnected, docnexusBinPath } from './docnexus-layout.mjs';
|
|
22
|
+
export { docnexusRoot, docnexusWorkspace, docnexusConnected } from './docnexus-layout.mjs';
|
|
21
23
|
|
|
22
24
|
const SKILLS_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
23
25
|
const PROFILE_FIELDS = ['main_branch', 'build', 'test', 'test_single', 'lint', 'config_center', 'deploy.platform', 'deploy.regions', 'job_annotation'];
|
|
@@ -37,19 +39,13 @@ function skillMetas() {
|
|
|
37
39
|
|
|
38
40
|
export const skillMeta = (name) => skillMetas().find((m) => m.name === name);
|
|
39
41
|
|
|
40
|
-
//
|
|
41
|
-
export const docnexusRoot = (root) => path.resolve(root, HOME);
|
|
42
|
-
|
|
43
|
-
// DocNexus CLI 的位置(相对项目根):workflow-run/docnexus/node_modules/.bin;没有返回 null。bin/lib/core.mjs 的 docnexusBin 与此一致
|
|
44
|
-
export const DOCNEXUS_BIN_DIRS = [['docnexus', 'node_modules', '.bin']];
|
|
42
|
+
// CLI 位于项目根的 .docnexus/,对 Agent 返回项目相对路径。
|
|
45
43
|
export function docnexusBin(root) {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
for (const dir of DOCNEXUS_BIN_DIRS) if (fs.existsSync(path.join(dn, ...dir, name))) return path.relative(path.resolve(root), path.join(dn, ...dir, name));
|
|
49
|
-
return null;
|
|
44
|
+
const bin = docnexusBinPath(root);
|
|
45
|
+
return bin ? path.relative(path.resolve(root), bin) : null;
|
|
50
46
|
}
|
|
51
|
-
// 给 agent
|
|
52
|
-
const cmdPath = (p) => { const s = p.split(path.sep).join('/'); return s.startsWith('.') ? s : `./${s}`; };
|
|
47
|
+
// 给 agent 的命令写法(相对项目根):./.docnexus/...
|
|
48
|
+
const cmdPath = (p) => { const s = p.split(path.sep).join('/'); return s.startsWith('./') || s.startsWith('../') ? s : `./${s}`; };
|
|
53
49
|
|
|
54
50
|
// CodeGraph 代码图谱:本项目建过索引(.codegraph/)且 codegraph 命令可用。索引是否建立由用户决定,skill 不自行 codegraph init
|
|
55
51
|
export function codegraphStatus(root) {
|
|
@@ -62,16 +58,12 @@ export function codegraphStatus(root) {
|
|
|
62
58
|
};
|
|
63
59
|
}
|
|
64
60
|
|
|
65
|
-
export function docnexusConnected(root) {
|
|
66
|
-
return fs.existsSync(path.join(docnexusRoot(root), 'docnexus', 'project.json')) && docnexusBin(root) !== null;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
61
|
export function resolveTemplate(root, skill, id) {
|
|
70
62
|
const meta = skillMeta(skill);
|
|
71
63
|
if (!meta) return { ok: false, error: 'unknown', message: `skill「${skill}」没有安装(${rel(root, SKILLS_DIR)} 下找不到 ${skill}/skill.json);重跑安装并在 --skills 里带上它` };
|
|
72
64
|
const t = meta.templates.find((x) => x.id === id);
|
|
73
65
|
if (!t) return { ok: false, error: 'unknown', message: `skill「${skill}」没有登记模板「${id}」(见 ${skill}/skill.json 的 templates)` };
|
|
74
|
-
const lib = path.join(
|
|
66
|
+
const lib = path.join(docnexusWorkspace(root), 'library', 'templates', skill, id);
|
|
75
67
|
const project = path.join(root, TEMPLATES_DIR, skill, id);
|
|
76
68
|
// 没有 src 的模板(技术方案、上线方案)不随 skills 提供,缺失时按「创建模板」由用户给样例生成
|
|
77
69
|
const builtin = t.src ? path.join(SKILLS_DIR, skill, t.src.replace(/\.tpl$/, '')) : null;
|
|
@@ -185,8 +177,11 @@ export function status(root) {
|
|
|
185
177
|
ok: dn,
|
|
186
178
|
// 调用 DocNexus CLI 时用这个路径(在项目根执行)
|
|
187
179
|
bin: dn ? cmdPath(docnexusBin(root)) : null,
|
|
188
|
-
//
|
|
189
|
-
root:
|
|
180
|
+
// CLI、草稿及返回的文档路径均相对业务项目根。
|
|
181
|
+
root: '.',
|
|
182
|
+
internal: '.docnexus',
|
|
183
|
+
workspace: `${HOME}/docnexus`,
|
|
184
|
+
output_dir: HOME,
|
|
190
185
|
// 运行 DocNexus 命令的写法(在项目根执行,任何终端都一样):后面接原命令的参数;node 脚本前加 --node
|
|
191
186
|
run: `node ${(rel(root, path.join(SKILLS_DIR, 'workflow', 'scripts', 'docnexus.mjs')))}`,
|
|
192
187
|
missing_templates: dn ? templates.filter((t) => !t.ok && !t.on_demand).map((t) => t.expected) : [],
|