@nathan33/nao-skill 0.10.0 → 0.12.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/.agents/checklists/pm.md +1 -1
- package/.agents/common/release-notes.md +6 -6
- package/.agents/scripts/nao-fleet.sh +2 -2
- package/.agents/skills/github-flow.md +1 -1
- package/.agents/skills/nao-fleet/SKILL.md +41 -0
- package/.agents/templates/AGENTS.md.example +5 -2
- package/.agents/templates/release-notes.md.example +2 -6
- package/README.md +57 -30
- package/bin/nao-skill.js +294 -10
- package/bin/shim/nao-fleet.sh +119 -0
- package/package.json +8 -1
package/.agents/checklists/pm.md
CHANGED
|
@@ -56,7 +56,7 @@ description: PM 交付核对清单(按需,交付/派发前读取)
|
|
|
56
56
|
- [ ] Issue 与 `docs/` 双源(正文抄进 Issue,或状态只留平台)?
|
|
57
57
|
- [ ] 未过验收 / 门禁未绿就打 tag 或 release,或 tag 指向非 main 合并提交?(见 `github-flow.md`)
|
|
58
58
|
- [ ] `git push --force` 到 main/共享分支(未经用户明确授权并指明分支)?
|
|
59
|
-
- [ ] release notes 不可读(纯编号/类名/路径),或未落盘 `docs/releases/`?(规范:@.agents/common/release-notes.md ——
|
|
59
|
+
- [ ] release notes 不可读(纯编号/类名/路径),或未落盘 `docs/releases/`?(规范:@.agents/common/release-notes.md —— 面向用户「本次更新」置顶、内部信息下沉、正文不用表格)
|
|
60
60
|
- [ ] 降级(无 `gh` / 无远端)未在 `tasks-state` 与回执中标注?
|
|
61
61
|
|
|
62
62
|
## 交付检查清单(完整 36 项)
|
|
@@ -11,7 +11,7 @@ description: 发版说明写作规范(面向用户、说人话;写 / 验收
|
|
|
11
11
|
|
|
12
12
|
## 一、硬约束(必须遵守)
|
|
13
13
|
|
|
14
|
-
1.
|
|
14
|
+
1. **必须有面向用户的「本次更新」段,且置于正文最前。**
|
|
15
15
|
- GitHub Release 正文 与 仓库 `docs/releases/vX.Y.Z.md` 的**顶部口径一致**(同一份文字、同一版本)。
|
|
16
16
|
- 读者 = **最终用户**,不是开发者;看完前几行就知道「这版对我有什么变化」。
|
|
17
17
|
2. **正文 ⛔ 不得出现内部术语 / 编号。** 一律不写:任务或缺陷编号(`DEF-xx` / `Txxx` / `#123`)、提交哈希、文件名与函数名、内部属性名(如 `aria-disabled` / `dirty`)、测试例数与通过率、守卫名、内部模块名。
|
|
@@ -28,9 +28,9 @@ description: 发版说明写作规范(面向用户、说人话;写 / 验收
|
|
|
28
28
|
3. **🐛 修好了** —— 修掉的问题(用户视角现象 → 现在)。
|
|
29
29
|
4. **⚠️ 请留意** —— 行为变更 / 需要用户配合的事。
|
|
30
30
|
5. **📦 更小更快** —— 体积 / 性能 / 启动等可感知收益。
|
|
31
|
-
6.
|
|
32
|
-
7. **技术细节(可略过)** —— 编号 / 哈希 / 依赖版本 / 门禁数字 / 文档链接。
|
|
31
|
+
6. **技术细节(可略过)** —— 编号 / 哈希 / 依赖版本 / 门禁数字 / 文档链接。
|
|
33
32
|
|
|
33
|
+
- **已知问题出口**:会影响用户 ⇒ 写入「⚠️ 请留意」;否则下沉「技术细节(可略过)」。
|
|
34
34
|
- **纯内部仓**(无面向用户交付)可省略第 2–5 段的「面向用户」写法,但**结构保留**(仍按「概括 → 变化 → 技术细节」分层)。
|
|
35
35
|
- **排版**:本文件与其他文档同受 @.agents/checklists/deliverable-docs.md 「默认不使用表格」约束 —— release notes 正文尤其不要用表格。
|
|
36
36
|
|
|
@@ -43,10 +43,10 @@ description: 发版说明写作规范(面向用户、说人话;写 / 验收
|
|
|
43
43
|
|
|
44
44
|
## 四、发布前核对
|
|
45
45
|
|
|
46
|
-
- [ ]
|
|
46
|
+
- [ ] 正文最前有面向用户的**「本次更新」**段(用户看得懂)。
|
|
47
47
|
- [ ] 正文**无**编号 / 哈希 / 文件名 / 函数名 / 内部属性名 / 测试例数 / 守卫名 / 内部模块名。
|
|
48
48
|
- [ ] 行为变更写了**用户视角的后果**(不是内部机制)。
|
|
49
|
-
- [ ] 结构完整(一句话 → 变化 →
|
|
49
|
+
- [ ] 结构完整(一句话 → 变化 → 技术细节);内部信息集中在「技术细节」段。
|
|
50
50
|
- [ ] 正文**未使用表格**(改用有序 / 无序列表;见 @.agents/checklists/deliverable-docs.md)。
|
|
51
51
|
- [ ] `docs/releases/<version>.md` 与 GitHub Release 正文同源。
|
|
52
52
|
|
|
@@ -54,5 +54,5 @@ description: 发版说明写作规范(面向用户、说人话;写 / 验收
|
|
|
54
54
|
|
|
55
55
|
- [ ] 只写内部机制、用户看不懂?
|
|
56
56
|
- [ ] 正文里直接出现编号 / 哈希 / 类名 / 属性名 / 测试例数?
|
|
57
|
-
- [ ]
|
|
57
|
+
- [ ] 没有面向用户的「本次更新」段,或它不在最前?
|
|
58
58
|
- [ ] 盘上 release notes 与 Release 正文两处不一致?
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
# NAO_TMUX_LAYOUT=main-row2|main-col|grid tmux 布局(默认 main-row2)
|
|
22
22
|
# NAO_TMUX_MAIN_WIDTH=<10..90> 主 pane 宽度百分比(默认 35;main-row2/main-col 共用)
|
|
23
23
|
# NAO_TMUX_MIN_PANE_WIDTH=<10..80> 最小非主 pane 列宽守卫(默认 30;低于则回退)
|
|
24
|
-
# NAO_SKILLS=<dir>
|
|
24
|
+
# NAO_SKILLS=<dir> nao 机制包根目录(默认 <脚本>/../..;项目内 shim 会显式指向包根)
|
|
25
25
|
# NAO_MODEL_WHITELIST=<glob,...> -m 白名单(默认空=不校验,支持 glob)
|
|
26
26
|
# NAO_CLOSE_BUSY_PATTERN=<ERE> close 的在跑 turn 判定正则(默认内置 pi 状态行标记)
|
|
27
27
|
# NAO_TASKS_STATE=<path> 任务状态文件(默认 docs/tasks-state.md,供残留检测/回收闸门)
|
|
@@ -824,7 +824,7 @@ build_system_prompt() {
|
|
|
824
824
|
f="$(mktemp "/tmp/nao-fleet-$(basename "$card" .md)-XXXXXX.md")"
|
|
825
825
|
{
|
|
826
826
|
cat "$card"
|
|
827
|
-
printf '\n## 环境锚点\n- NAO_SKILLS=%s
|
|
827
|
+
printf '\n## 环境锚点\n- NAO_SKILLS=%s(nao 机制**包根**;@.agents/... 引用一律以 `$NAO_SKILLS/.agents/` 为根解析。注意:项目内 `.agents/` 只有 shim,不存机制副本)\n' "$SKILLS_DIR"
|
|
828
828
|
} > "$f"
|
|
829
829
|
echo "$f"
|
|
830
830
|
}
|
|
@@ -54,7 +54,7 @@ PM 在**立项 / 终签 / 发布**时读取;RD 在**起分支 / 开 PR / 合
|
|
|
54
54
|
- 门禁:AC 全过 + 验收闭环 + main 门禁全绿 + 工作区干净 + PR 已合并。
|
|
55
55
|
- **优先 `gh`**:`gh release create <tag> --title "<用户可读标题>" --notes-file docs/releases/<version>.md`。
|
|
56
56
|
- 探测:`gh auth status` 按 **exit code** 判定(`keyring` 警告但 exit 0 **仍可用**,可 `gh api user` 复核);**不自动 `gh auth login`**(涉用户凭证)。
|
|
57
|
-
- release notes 落盘 `docs/releases/<version>.md`(**写作规范(强约束):@.agents/common/release-notes.md** ——
|
|
57
|
+
- release notes 落盘 `docs/releases/<version>.md`(**写作规范(强约束):@.agents/common/release-notes.md** —— 面向用户「本次更新」置顶、内部编号 / 文件名 / 属性名一律下沉「技术细节」、正文不用表格;骨架见 @.agents/templates/release-notes.md.example)。
|
|
58
58
|
- 回滚:未 push → `git tag -d`;已 push → **不删远端 tag**,改发下一个 PATCH 修正(用户明确要求才删)。
|
|
59
59
|
|
|
60
60
|
## 离线 / 无远端降级
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nao-fleet
|
|
3
|
+
description: nao 多角色 AI 开发舰队的单一入口。当用户要在当前项目启用/运行 nao 工作流时加载——按角色拉起 6 个常驻会话(pm / arch-designer / rd-fe / rd-be / qa / rd-infra)、跑机制体检 check、迁移旧版安装。本 skill 只做入口与说明书:加载后按指引调用项目内 `.agents/scripts/nao-fleet.sh`(shim);角色卡仍由 fleet 以 `--append-system-prompt` 常驻注入,不由 skill 承载。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# nao 舰队入口
|
|
7
|
+
|
|
8
|
+
nao 是一套「多角色 AI 开发舰队」:6 个分工明确的常驻会话(PM 拆需求/派活/验收,前端、后端、测试、架构、工程基座各自干活),用户只回答问题、点确认。
|
|
9
|
+
|
|
10
|
+
## 何时加载
|
|
11
|
+
|
|
12
|
+
- 用户要在当前项目**启用 nao 工作流**(立项、拆任务、派活、验收、发版)。
|
|
13
|
+
- 用户要**拉起舰队**或查看会话状态。
|
|
14
|
+
- 用户要**迁移旧版 nao 安装**(项目内平铺 `.agents/` → 包内单一来源 + 项目 shim)。
|
|
15
|
+
|
|
16
|
+
## 机制在哪(单一事实来源)
|
|
17
|
+
|
|
18
|
+
机制全部在 **nao 机制包内**,项目内只有 shim。包根由环境锚点 `$NAO_SKILLS` 给出(fleet 注入到会话系统提示):
|
|
19
|
+
|
|
20
|
+
- 角色清单:`$NAO_SKILLS/.agents/roles.yaml`(6 角色,id 即 intercom 会话名)
|
|
21
|
+
- 角色卡:`$NAO_SKILLS/.agents/prompts/<card>.md`
|
|
22
|
+
- 按需技能:`$NAO_SKILLS/.agents/skills/*.md`
|
|
23
|
+
- 交付清单:`$NAO_SKILLS/.agents/checklists/<role>.md`
|
|
24
|
+
- 协作协议 / 输出规范:`$NAO_SKILLS/.agents/common/`
|
|
25
|
+
|
|
26
|
+
> 若 `$NAO_SKILLS` 未设置(普通会话),按本 SKILL.md 所在包解析(本文件位于 `<包根>/.agents/skills/nao-fleet/SKILL.md`,包根 = 本文件上两级)。
|
|
27
|
+
|
|
28
|
+
## 怎么用
|
|
29
|
+
|
|
30
|
+
1. **体检**:`bash .agents/scripts/nao-fleet.sh check`(exit=0 即可开工;非 0 看报告)。
|
|
31
|
+
2. **拉起舰队**:`bash .agents/scripts/nao-fleet.sh ensure pm arch-designer rd-fe rd-be qa rd-infra`
|
|
32
|
+
- 任务派生(并行隔离):`ensure --task <编号> <别名>`
|
|
33
|
+
- 状态 / 回收:`status` / `close --task <编号> <别名>`
|
|
34
|
+
3. **迁移旧版安装**:`npx @nathan33/nao-skill migrate`(旧文件备份到 `.agents/.nao-obsolete/`,shim 就位)。
|
|
35
|
+
|
|
36
|
+
## 硬约束(勿违反)
|
|
37
|
+
|
|
38
|
+
- **BR1**:角色仍是 6 个独立会话;本 skill 不合并角色。
|
|
39
|
+
- **BR2**:角色卡必须继续由 fleet `--append-system-prompt` 常驻注入;**不得**改用 skill / prompt 按需加载。
|
|
40
|
+
- 项目根 `AGENTS.md` 是项目级单一事实来源(PM 维护,pi 自动注入),本 skill 不复制其内容。
|
|
41
|
+
- 机制自身零网络;shim 只用 node resolve 定位包,不调用 `pi`/`npm` 可执行文件。
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
- <命令纪律,如:禁 cat 全文读代码,用 CodeGraph 或 sed 行段>
|
|
25
25
|
- <依赖限制 / 禁止事项>
|
|
26
26
|
- **Git 流程(GitHub Flow)**:一个需求一条分支 `feat/<issue-id>-<slug>`;工作期 `wip()` 小步提交 + 路径级暂存(禁 `git add -A`);**PM 验收通过后由 RD 在 PR 上 squash 合并**(main = 1 条/需求);**禁直推 main**。
|
|
27
|
-
- **发布与仓库治理**:PM 负责 Issue / PR 验收评论 / tag / release / `.github/` 模板(优先 `gh`,不可用降级 `git`);**发版说明写法遵循 `@.agents/common/release-notes.md
|
|
27
|
+
- **发布与仓库治理**:PM 负责 Issue / PR 验收评论 / tag / release / `.github/` 模板(优先 `gh`,不可用降级 `git`);**发版说明写法遵循 `@.agents/common/release-notes.md`**(面向用户「本次更新」置顶、内部信息下沉、正文不用表格)。
|
|
28
28
|
|
|
29
29
|
## 与 nao 机制衔接
|
|
30
30
|
|
|
@@ -36,6 +36,9 @@
|
|
|
36
36
|
|
|
37
37
|
## 指针(勿复制内容)
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
> 项目内 `.agents/` **只有 shim**(`.agents/scripts/nao-fleet.sh`);机制单一事实来源在 **nao 机制包**内,包根由 `NAO_SKILLS` 给出(fleet 注入到会话系统提示)。所有机制引用一律以 `$NAO_SKILLS/.agents/` 为根解析。
|
|
40
|
+
|
|
41
|
+
- 团队级协议/角色清单:`$NAO_SKILLS/.agents/common/` + `$NAO_SKILLS/.agents/roles.yaml`
|
|
40
42
|
- 角色卡:`$NAO_SKILLS/.agents/prompts/`
|
|
41
43
|
- 按需技能:`$NAO_SKILLS/.agents/skills/`
|
|
44
|
+
- 交付清单:`$NAO_SKILLS/.agents/checklists/`
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
release notes 骨架 —— 复制为 docs/releases/<version>.md 后逐段填空。
|
|
3
3
|
权威规范:@.agents/common/release-notes.md
|
|
4
|
-
|
|
4
|
+
用法:把面向用户的「本次更新」放在最前;内部信息一律下沉「技术细节(可略过)」。
|
|
5
5
|
⛔ 自查:正文无编号 / 哈希 / 文件名 / 函数名 / 内部属性名 / 测试例数 / 守卫名 / 内部模块名;正文不要用表格。
|
|
6
6
|
-->
|
|
7
7
|
|
|
8
|
-
#
|
|
8
|
+
# 本次更新
|
|
9
9
|
|
|
10
10
|
**<一句话概括这版最大的变化;用用户能听懂的话。>**
|
|
11
11
|
|
|
@@ -28,10 +28,6 @@
|
|
|
28
28
|
|
|
29
29
|
- <体积 / 性能 / 启动等可感知收益>。<!-- 没有就整节删除 -->
|
|
30
30
|
|
|
31
|
-
## 已知但本期未处理
|
|
32
|
-
|
|
33
|
-
- <已知问题 + 计划>。<!-- 没有就写「无」 -->
|
|
34
|
-
|
|
35
31
|
## 技术细节(可略过)
|
|
36
32
|
|
|
37
33
|
- 版本号:<包名旧版本 → 新版本>(如涉及多个包,逐条列出)
|
package/README.md
CHANGED
|
@@ -18,16 +18,29 @@ nao-skills 是一套 **角色卡 + 协作规矩 + 小工具**。装上以后,
|
|
|
18
18
|
|
|
19
19
|
## 安装(1 分钟)
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
npm install -g @nathan33/nao-skill
|
|
23
|
-
cd 你的项目
|
|
24
|
-
nao-skill install # 装 .agents/ 并生成 AGENTS.md(已有则提示按规则追加,不覆盖原文)
|
|
25
|
-
nao-skill plugins install-all # 装协作插件(intercom 等),多会话派活必需
|
|
26
|
-
```
|
|
21
|
+
> 前置依赖:[pi](https://pi.dev)。没装 pi 也能看到这套文件,但跑不起来舰队。
|
|
27
22
|
|
|
28
|
-
|
|
23
|
+
1. **把机制装进项目**(项目级,版本 pin 在仓库里,团队 clone 即得同款):
|
|
29
24
|
|
|
30
|
-
|
|
25
|
+
```bash
|
|
26
|
+
cd 你的项目
|
|
27
|
+
pi install --local npm:@nathan33/nao-skill@0.12.0 # 声明 pin + 把机制包物化到项目内 .pi/npm
|
|
28
|
+
npx @nathan33/nao-skill init # 写入转发 shim + 生成 AGENTS.md
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
2. **装协作插件**(多会话派活必需):
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npx @nathan33/nao-skill plugins install-all
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
升级:改 `.pi/settings.json` 里的 pin 版本,再 `pi update --extensions`(机制以包为准,项目里只有 shim)。
|
|
38
|
+
|
|
39
|
+
**老项目迁移**(曾在项目里铺开整套 `.agents/` 的):
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npx @nathan33/nao-skill migrate # 旧文件备份到 .agents/.nao-obsolete/,shim 就位,旧命令照跑
|
|
43
|
+
```
|
|
31
44
|
|
|
32
45
|
## 用起来(3 步)
|
|
33
46
|
|
|
@@ -66,11 +79,11 @@ PM:在 PR 核 AC 并评论 → 验收通过(授权合并)→ rd-be squash
|
|
|
66
79
|
装上 [`pi-agent-qqbot`](https://github.com/Nathan3303) 并配好 `~/.pi/agent/pi-agent-qqbot.json`(`appId` / `clientSecret` / `ownerOpenId` / `sandbox`)后,PM 可把**关键节点**主动推到你 QQ——批次进度汇总、验收结论、发版或合并完成、异常阻塞:
|
|
67
80
|
|
|
68
81
|
```bash
|
|
69
|
-
|
|
70
|
-
|
|
82
|
+
$NAO_SKILLS/.agents/scripts/qq-notify "[T301] 验收通过 · PR #4 已合并 · v0.8.0"
|
|
83
|
+
$NAO_SKILLS/.agents/scripts/qq-notify --dry-run "连通性自检" # 只取 token、不发送
|
|
71
84
|
```
|
|
72
85
|
|
|
73
|
-
**为什么默认不主动打扰**:这是**可选能力**——没配 `pi-agent-qqbot` 就自动视为未启用;四类节点之外不发,一个节点最多一条(禁刷屏);推送失败只记回执行风险项,**不阻断交付**。默认走 sandbox(测试环境),正式发布通知才考虑 prod。能力边界、退出码与纪律见
|
|
86
|
+
**为什么默认不主动打扰**:这是**可选能力**——没配 `pi-agent-qqbot` 就自动视为未启用;四类节点之外不发,一个节点最多一条(禁刷屏);推送失败只记回执行风险项,**不阻断交付**。默认走 sandbox(测试环境),正式发布通知才考虑 prod。能力边界、退出码与纪律见 `$NAO_SKILLS/.agents/skills/qq-notify.md`。
|
|
74
87
|
|
|
75
88
|
## 为什么它省 token(这是本项目的核心追求)
|
|
76
89
|
|
|
@@ -85,7 +98,7 @@ PM:在 PR 核 AC 并评论 → 验收通过(授权合并)→ rd-be squash
|
|
|
85
98
|
|
|
86
99
|
## 常见问题
|
|
87
100
|
|
|
88
|
-
**会改我的代码库吗?**
|
|
101
|
+
**会改我的代码库吗?** 只会新增项目根 `AGENTS.md`、`.pi/settings.json`,以及一个转发入口 `.agents/scripts/nao-fleet.sh`;角色卡、清单、技能这些机制文件都在依赖包内,**不再铺进你的仓库**,也不碰你原有文件。
|
|
89
102
|
|
|
90
103
|
**只能用 pi 吗?** 角色卡和技能本质就是 Markdown,Claude Code 等也能读;但"派活 / 回执"这种多会话协作依赖 pi + pi-intercom 插件。
|
|
91
104
|
|
|
@@ -103,37 +116,51 @@ PM:在 PR 核 AC 并评论 → 验收通过(授权合并)→ rd-be squash
|
|
|
103
116
|
你的项目/
|
|
104
117
|
├── AGENTS.md # 项目级约定(PM 维护,AI 每次都会读)
|
|
105
118
|
├── .github/ # PR 模板(PM 建,GitHub Flow 用)
|
|
119
|
+
├── .pi/
|
|
120
|
+
│ ├── settings.json # 记录机制包的 pin 版本(进仓库)
|
|
121
|
+
│ └── npm/ # pi 把机制包物化在这里(pi 自建 gitignore,不提交)
|
|
106
122
|
└── .agents/
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
├── checklists/ # 各角色红线 / 交付清单 / 回执模板(按需读)
|
|
110
|
-
├── skills/ # 按需技能(DDD 细节、代码定位、提交规范…)
|
|
111
|
-
├── templates/ # 项目接入用的骨架文件
|
|
112
|
-
└── scripts/ # nao-fleet.sh 等小工具
|
|
123
|
+
└── scripts/
|
|
124
|
+
└── nao-fleet.sh # 转发入口(shim):自动定位包内机制并转发
|
|
113
125
|
```
|
|
114
126
|
|
|
115
|
-
|
|
127
|
+
机制本体(6 张角色卡、协作规矩、清单、按需技能、小工具)都在 npm 包 `@nathan33/nao-skill` 的 `.agents/` 里——**包里是什么就是什么**,项目里不留副本。
|
|
128
|
+
|
|
129
|
+
## 发布形态与完整性判据(0.12 起有变化)
|
|
130
|
+
|
|
131
|
+
**从 0.12 起,机制不再铺进项目**:项目里只留一个转发入口,机制的**单一事实来源**是 npm 包 `@nathan33/nao-skill` 内的 `.agents/`。
|
|
132
|
+
|
|
133
|
+
- **旧的完整性判据已失效**:以前下游靠「项目内 `.agents/**` 与上游逐字节 sha 一致」证明机制没漏没改;现在项目内不再有机制副本,这条判据不再适用。
|
|
134
|
+
- **新的完整性判据:以 pin 的包版本为准**。`.pi/settings.json` 记录版本,`pi install/update` 从包物化;包里是什么就是什么,不存在「项目内被就地改动」的问题。转发入口是生成的固定文本,可用 `npx @nathan33/nao-skill init` 幂等重写核对。
|
|
135
|
+
- **版本权威口径**:`.pi/settings.json` 里的 pin 是**唯一权威**(`pi install/update` 据此物化到 `.pi/npm`);`.agents/.nao-version` 只是本机安装标记(供升级检测与迁移提示),**不作为版本依据**;两者不一致时**以 pin 为准**。
|
|
136
|
+
- **fmt 边界**:包内 `.agents/**` 仍以上游原始存储形态发布(部分 YAML / Markdown / MTS 未按 oxfmt / prettier 归一)。迁移期仍持有旧版全套 `.agents/**` 的仓库,继续把 `.agents/**` 排除出 fmt 校验即可,⛔ **不要为过 fmt 就地格式化**;新形态项目内只有转发入口,无需再处理。
|
|
137
|
+
|
|
138
|
+
### 物化与离线(CI / 无 pi 环境必读)
|
|
116
139
|
|
|
117
|
-
|
|
140
|
+
- `.pi/npm/` 由 pi 管理并 **gitignore**。因此 **fresh clone 上没有 `.pi/npm/package.json` 与 lockfile**,`npm ci --prefix .pi/npm` 当前**不可用**。
|
|
141
|
+
- 物化只有一条路:**显式执行** `pi install -l --approve npm:@nathan33/nao-skill@<pin>`(需要 pi 与网络,是有意为之的一步)。
|
|
142
|
+
- **联动条款**:若某个仓库想把机制体检 `check` 放进 CI(即改变「下游 CI 零改动」的约定),**必须同时**用 `git add -f` 把 `.pi/npm/package.json`、`.pi/npm/package-lock.json` 纳入版本控制,`npm ci` 才有输入。
|
|
143
|
+
- **pi 启动的隐式安装**:当 `.pi/npm/node_modules` 缺失时,pi 启动会隐式执行 `npm install --prefix .pi/npm --legacy-peer-deps`;离线且缓存为空会得到可读的 npm 报错,并让 pi 以非 0 退出(不会静默假装成功)。
|
|
144
|
+
- **运行纪律**:**先显式物化,再 `PI_OFFLINE=1` 运行**。nao 机制自身(转发入口 / fleet / CLI / check)**零网络调用**;入口找不到机制包时不静默成功,而是 `exit 2` 并打印一行 `DEGRADED:` + 恢复命令。
|
|
118
145
|
|
|
119
|
-
|
|
146
|
+
## 下游仓库迁移(nao-todo / nao-todo-server / nue-ui)
|
|
120
147
|
|
|
121
|
-
-
|
|
122
|
-
-
|
|
123
|
-
-
|
|
148
|
+
- **迁移期零改动**:旧的全套 `.agents/scripts/nao-fleet.sh` 照常可用,这些仓库的 CI 目前不调用 `check`,不受影响。
|
|
149
|
+
- 想切到新形态时,在各仓执行 `npx @nathan33/nao-skill migrate`:只删**已知 nao 资产**(共享的 `.agents/skills/` 里项目自有的技能会保留),并把 `skills-lock.json` 里重复的 `frontend-design` 来源收敛为「机制包唯一来源」。
|
|
150
|
+
- **`nao-todo-minimal` 特例**:无脚本、无 nao-fleet 引用 ⇒ **不建转发入口**,按纯文档/指针迁移;它的 `.agents/commands/`、`.agents/prompts/` 不在 pi 的 skill 扫描面(pi 只扫 `.agents/skills/**`)。
|
|
124
151
|
|
|
125
152
|
## 想深入
|
|
126
153
|
|
|
127
|
-
README
|
|
154
|
+
README 只讲怎么用。真正的机制都在依赖包 `@nathan33/nao-skill` 的 `.agents/` 里(会话里以 `$NAO_SKILLS/.agents/` 指向),想改就从这几处入手:
|
|
128
155
|
|
|
129
156
|
| 想看什么 | 去哪看 |
|
|
130
157
|
| --- | --- |
|
|
131
158
|
| 整体架构、分层与设计取舍 | [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) |
|
|
132
|
-
| 派活 / 回执 / 忙闲与回收规矩 |
|
|
133
|
-
| 输出格式与回执红线 |
|
|
134
|
-
| 角色怎么定义 |
|
|
135
|
-
| 各角色的红线与交付清单 |
|
|
136
|
-
| 工具命令 | `bash .agents/scripts/nao-fleet.sh --help
|
|
159
|
+
| 派活 / 回执 / 忙闲与回收规矩 | `$NAO_SKILLS/.agents/common/intercom-protocol.md` |
|
|
160
|
+
| 输出格式与回执红线 | `$NAO_SKILLS/.agents/common/output-format.md`、`$NAO_SKILLS/.agents/checklists/comm-templates.md` |
|
|
161
|
+
| 角色怎么定义 | `$NAO_SKILLS/.agents/prompts/*.md` |
|
|
162
|
+
| 各角色的红线与交付清单 | `$NAO_SKILLS/.agents/checklists/*.md` |
|
|
163
|
+
| 工具命令 | `bash .agents/scripts/nao-fleet.sh --help`(转发入口) |
|
|
137
164
|
| 项目自己的约定 | 项目根 `AGENTS.md` |
|
|
138
165
|
|
|
139
166
|
## License
|
package/bin/nao-skill.js
CHANGED
|
@@ -17,8 +17,9 @@
|
|
|
17
17
|
* bash .agents/scripts/nao-fleet.sh check / ensure arch rd-fe rd-be qa rd-infra
|
|
18
18
|
* 角色卡经 fleet 拉起时 --append-system-prompt 注入;checklists 按需读取
|
|
19
19
|
*/
|
|
20
|
-
import { execFileSync } from 'node:child_process';
|
|
20
|
+
import { execFileSync, spawnSync } from 'node:child_process';
|
|
21
21
|
import {
|
|
22
|
+
chmodSync,
|
|
22
23
|
cpSync,
|
|
23
24
|
existsSync,
|
|
24
25
|
mkdirSync,
|
|
@@ -28,17 +29,32 @@ import {
|
|
|
28
29
|
rmSync,
|
|
29
30
|
statSync,
|
|
30
31
|
} from 'node:fs';
|
|
31
|
-
import { join, resolve, basename } from 'node:path';
|
|
32
|
+
import { join, resolve, basename, relative } from 'node:path';
|
|
32
33
|
import { fileURLToPath } from 'node:url';
|
|
33
34
|
|
|
34
35
|
const PKG = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
36
|
+
const PKG_ROOT = fileURLToPath(new URL('..', import.meta.url));
|
|
35
37
|
const SRC = fileURLToPath(new URL('../.agents', import.meta.url));
|
|
36
38
|
const TEMPLATE_AGENTS = fileURLToPath(new URL('../.agents/templates/AGENTS.md.example', import.meta.url));
|
|
39
|
+
const SHIM_SRC = fileURLToPath(new URL('./shim/nao-fleet.sh', import.meta.url));
|
|
37
40
|
|
|
38
41
|
// 已知废弃路径(相对项目根):旧版遗留,update 时备份后移除
|
|
39
42
|
const OBSOLETE = ['.agents/skills/checklists'];
|
|
40
43
|
// 安装版本标记(install/update 写入,供下次检测升级)
|
|
41
44
|
const VERSION_FILE = '.agents/.nao-version';
|
|
45
|
+
// 迁移一次性提示标记(BR4:每版本最多提示一次)
|
|
46
|
+
const MIGRATED_FILE = '.agents/.nao-migrated';
|
|
47
|
+
|
|
48
|
+
// 旧版全套安装的已知 nao 资产(迁移时**精准**备份+移除;共享目录不得整目录删)
|
|
49
|
+
const LEGACY_TOP_DIRS = ['prompts', 'common', 'checklists', 'templates'];
|
|
50
|
+
const LEGACY_TOP_FILES = ['roles.yaml'];
|
|
51
|
+
const LEGACY_SCRIPT_FILES = ['nao-fleet.sh', 'intercom-probe.mts', 'qq-notify', 'ui-tokens-check.sh'];
|
|
52
|
+
const LEGACY_SKILL_ENTRIES = [
|
|
53
|
+
'nao-fleet', 'frontend-design',
|
|
54
|
+
'arch-patterns.md', 'backend-ddd-details.md', 'codegraph.md', 'commit.md',
|
|
55
|
+
'frontend-ddd-details.md', 'github-flow.md', 'pm-grill.md', 'pm-operations.md',
|
|
56
|
+
'pm-rice.md', 'pm-routing.md', 'qq-notify.md', 'research.md', 'test-design.md',
|
|
57
|
+
];
|
|
42
58
|
|
|
43
59
|
const log = (...a) => console.log('[nao-skill]', ...a);
|
|
44
60
|
const warn = (...a) => console.error('[nao-skill]', ...a);
|
|
@@ -218,23 +234,271 @@ function detectObsolete(target) {
|
|
|
218
234
|
return OBSOLETE.filter((p) => existsSync(join(target, p)));
|
|
219
235
|
}
|
|
220
236
|
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
// shim(项目内转发层):定位包内机制,不复制机制副本
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
function shimContent() {
|
|
241
|
+
return readFileSync(SHIM_SRC, 'utf8');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function installShim(target, verbose) {
|
|
245
|
+
const dir = join(target, '.agents', 'scripts');
|
|
246
|
+
mkdirSync(dir, { recursive: true });
|
|
247
|
+
const dst = join(dir, 'nao-fleet.sh');
|
|
248
|
+
const content = shimContent();
|
|
249
|
+
const changed = !existsSync(dst) || readFileSync(dst, 'utf8') !== content;
|
|
250
|
+
if (changed) writeFileSync(dst, content);
|
|
251
|
+
chmodSync(dst, 0o755);
|
|
252
|
+
if (verbose) log(` shim ${changed ? 'write' : 'same '} .agents/scripts/nao-fleet.sh`);
|
|
253
|
+
return changed;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function writeVersion(target) {
|
|
257
|
+
mkdirSync(join(target, '.agents'), { recursive: true });
|
|
258
|
+
writeFileSync(join(target, VERSION_FILE), PKG.version);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function hasNaoPackageDeclared(target) {
|
|
262
|
+
try {
|
|
263
|
+
const s = JSON.parse(readFileSync(join(target, '.pi', 'settings.json'), 'utf8'));
|
|
264
|
+
return (s.packages || []).some((p) => String(p).startsWith(`npm:${PKG.name}`));
|
|
265
|
+
} catch {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// 旧版全套安装探测:返回命中的已知 nao 资产(相对项目根)
|
|
271
|
+
// 注意:shim-only 项目里 .agents/scripts/nao-fleet.sh 就是 shim,不算旧版资产
|
|
272
|
+
function isShimFile(p) {
|
|
273
|
+
try { return readFileSync(p, 'utf8').includes('NAO_SHIM_ENTERED'); } catch { return false; }
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function detectLegacyAssets(target) {
|
|
277
|
+
const hits = [];
|
|
278
|
+
for (const d of LEGACY_TOP_DIRS) if (existsSync(join(target, '.agents', d))) hits.push(`.agents/${d}/`);
|
|
279
|
+
for (const f of LEGACY_TOP_FILES) if (existsSync(join(target, '.agents', f))) hits.push(`.agents/${f}`);
|
|
280
|
+
for (const f of LEGACY_SCRIPT_FILES) {
|
|
281
|
+
const p = join(target, '.agents', 'scripts', f);
|
|
282
|
+
if (!existsSync(p)) continue;
|
|
283
|
+
if (f === 'nao-fleet.sh' && isShimFile(p)) continue;
|
|
284
|
+
hits.push(`.agents/scripts/${f}`);
|
|
285
|
+
}
|
|
286
|
+
for (const e of LEGACY_SKILL_ENTRIES) if (existsSync(join(target, '.agents', 'skills', e))) hits.push(`.agents/skills/${e}`);
|
|
287
|
+
return hits;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ---------------------------------------------------------------------------
|
|
291
|
+
// init:干净项目 —— 写 shim + AGENTS.md(不复制机制;机制在 .pi/npm 包内)
|
|
292
|
+
// ---------------------------------------------------------------------------
|
|
293
|
+
function init(target, force, verbose) {
|
|
294
|
+
const legacy = detectLegacyAssets(target);
|
|
295
|
+
if (legacy.length) {
|
|
296
|
+
if (!force) {
|
|
297
|
+
warn('检测到旧版全套 .agents/ 安装(机制副本):');
|
|
298
|
+
for (const p of legacy) warn(` - ${p}`);
|
|
299
|
+
warn('请先运行 `nao-skill migrate`(备份到 .agents/.nao-obsolete/ + 就位 shim),勿直接 init。');
|
|
300
|
+
process.exit(1);
|
|
301
|
+
}
|
|
302
|
+
// F2:--force 不得绕过迁移守卫(否则 shim 覆盖 + 旧副本残留 = 混装,违 BR5/AC1 双注册)
|
|
303
|
+
log('init --force 检测到旧版 .agents/:改走 migrate(备份 + 精准移除 + shim),避免混装。');
|
|
304
|
+
return migrate(target, true, verbose);
|
|
305
|
+
}
|
|
306
|
+
installShim(target, verbose);
|
|
307
|
+
writeVersion(target);
|
|
308
|
+
if (!hasNaoPackageDeclared(target)) {
|
|
309
|
+
warn(`未在 .pi/settings.json 发现 npm:${PKG.name} 声明;请先执行 pi install --local npm:${PKG.name}@<pin>。`);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const agentsMd = join(target, 'AGENTS.md');
|
|
313
|
+
if (!existsSync(agentsMd)) {
|
|
314
|
+
let tpl = readFileSync(TEMPLATE_AGENTS, 'utf8');
|
|
315
|
+
tpl = tpl.replaceAll('<项目名>', basename(resolve(target)));
|
|
316
|
+
writeFileSync(agentsMd, tpl);
|
|
317
|
+
log('已生成 AGENTS.md(项目级上下文,PM 维护;保持精简 <120 行)。');
|
|
318
|
+
} else {
|
|
319
|
+
warn('AGENTS.md 已存在:按 PM 卡 §七 合并规则手动追加「nao 舰队接入」区块,勿覆盖原文。');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
log('');
|
|
323
|
+
log('✔ 初始化完成(项目内仅 shim,机制在 .pi/npm 包内)。下一步:');
|
|
324
|
+
log(' bash .agents/scripts/nao-fleet.sh check # 体检');
|
|
325
|
+
log(' bash .agents/scripts/nao-fleet.sh ensure pm arch-designer rd-fe rd-be qa rd-infra');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ---------------------------------------------------------------------------
|
|
329
|
+
// migrate:旧版全套安装 → 精准备份 .agents/.nao-obsolete/ + 移除 + shim 就位
|
|
330
|
+
// ---------------------------------------------------------------------------
|
|
331
|
+
function backupAndRemove(target, rel, stamp, verbose) {
|
|
332
|
+
const src = join(target, rel);
|
|
333
|
+
if (!existsSync(src)) return false;
|
|
334
|
+
const bak = join(target, '.agents', '.nao-obsolete', stamp, rel.replace(/^\.agents\//, ''));
|
|
335
|
+
mkdirSync(join(bak, '..'), { recursive: true });
|
|
336
|
+
const isDir = statIsDir(src);
|
|
337
|
+
cpSync(src, bak, { recursive: true });
|
|
338
|
+
rmSync(src, { recursive: isDir, force: true });
|
|
339
|
+
if (verbose) log(` backup+remove ${rel} → .agents/.nao-obsolete/${stamp}/`);
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// 共享目录精准删除:只删已知 nao 资产(保留项目其它 skill / 脚本)
|
|
344
|
+
function migrateLegacy(target, stamp, verbose) {
|
|
345
|
+
const removed = [];
|
|
346
|
+
const rem = (rel) => { if (backupAndRemove(target, rel, stamp, verbose)) removed.push(rel); };
|
|
347
|
+
for (const d of LEGACY_TOP_DIRS) rem(`.agents/${d}`);
|
|
348
|
+
for (const f of LEGACY_TOP_FILES) rem(`.agents/${f}`);
|
|
349
|
+
for (const f of LEGACY_SCRIPT_FILES) {
|
|
350
|
+
// shim-only 项目里 nao-fleet.sh 是 shim,不是旧版资产 —— 不备份不移除
|
|
351
|
+
if (f === 'nao-fleet.sh' && isShimFile(join(target, '.agents', 'scripts', f))) continue;
|
|
352
|
+
rem(`.agents/scripts/${f}`);
|
|
353
|
+
}
|
|
354
|
+
for (const e of LEGACY_SKILL_ENTRIES) rem(`.agents/skills/${e}`);
|
|
355
|
+
const obsolete = join(target, '.agents', 'skills', 'checklists');
|
|
356
|
+
if (existsSync(obsolete)) rem('.agents/skills/checklists');
|
|
357
|
+
return removed;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// 文本级移除 JSON 对象的某个属性:仅删该属性所占字节,其余字节(含缩进/空白)不变
|
|
361
|
+
function findJsonValueEnd(s, i) {
|
|
362
|
+
const ch = s[i];
|
|
363
|
+
if (ch === '{' || ch === '[') {
|
|
364
|
+
const close = ch === '{' ? '}' : ']';
|
|
365
|
+
let depth = 0, inStr = false, esc = false;
|
|
366
|
+
for (let k = i; k < s.length; k++) {
|
|
367
|
+
const c = s[k];
|
|
368
|
+
if (inStr) {
|
|
369
|
+
if (esc) esc = false;
|
|
370
|
+
else if (c === '\\') esc = true;
|
|
371
|
+
else if (c === '"') inStr = false;
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
if (c === '"') { inStr = true; continue; }
|
|
375
|
+
if (c === ch) depth++;
|
|
376
|
+
else if (c === close) { depth--; if (depth === 0) return k + 1; }
|
|
377
|
+
}
|
|
378
|
+
return -1;
|
|
379
|
+
}
|
|
380
|
+
if (ch === '"') {
|
|
381
|
+
let esc = false;
|
|
382
|
+
for (let k = i + 1; k < s.length; k++) {
|
|
383
|
+
const c = s[k];
|
|
384
|
+
if (esc) esc = false;
|
|
385
|
+
else if (c === '\\') esc = true;
|
|
386
|
+
else if (c === '"') return k + 1;
|
|
387
|
+
}
|
|
388
|
+
return -1;
|
|
389
|
+
}
|
|
390
|
+
const m = /^[^,\s}\]\n]+/.exec(s.slice(i));
|
|
391
|
+
return m ? i + m[0].length : -1;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function removeJsonProperty(raw, key) {
|
|
395
|
+
const re = new RegExp(`"${key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"\\s*:`);
|
|
396
|
+
const m = re.exec(raw);
|
|
397
|
+
if (!m) return null;
|
|
398
|
+
let vs = m.index + m[0].length;
|
|
399
|
+
while (vs < raw.length && /\s/.test(raw[vs])) vs++;
|
|
400
|
+
const ve = findJsonValueEnd(raw, vs);
|
|
401
|
+
if (ve < 0) return null;
|
|
402
|
+
// 本属性所在行的起始位置(仅当该行确以 "key" 开头时才做行级删除,防异形格式误伤)
|
|
403
|
+
let lineStart = m.index;
|
|
404
|
+
while (lineStart > 0 && raw[lineStart - 1] !== '\n') lineStart--;
|
|
405
|
+
let nl = raw.indexOf('\n', lineStart);
|
|
406
|
+
if (nl === -1) nl = raw.length;
|
|
407
|
+
if (!raw.slice(lineStart, nl).trim().startsWith(`"${key}"`)) return null;
|
|
408
|
+
// 本属性值闭合所在行之后(含换行)
|
|
409
|
+
let lineEnd = ve;
|
|
410
|
+
while (lineEnd < raw.length && raw[lineEnd] !== '\n') lineEnd++;
|
|
411
|
+
if (lineEnd < raw.length) lineEnd++;
|
|
412
|
+
// 是否还有后续属性(值后跟逗号)
|
|
413
|
+
let after = ve;
|
|
414
|
+
while (after < raw.length && (raw[after] === ' ' || raw[after] === '\t')) after++;
|
|
415
|
+
if (raw[after] === ',') {
|
|
416
|
+
return raw.slice(0, lineStart) + raw.slice(lineEnd);
|
|
417
|
+
}
|
|
418
|
+
// 末位属性:删本行,并去掉前一属性末尾的逗号
|
|
419
|
+
let out = raw.slice(0, lineStart) + raw.slice(lineEnd);
|
|
420
|
+
let p = lineStart;
|
|
421
|
+
while (p > 0 && /[ \t\r\n]/.test(out[p - 1])) p--;
|
|
422
|
+
if (out[p - 1] === ',') out = out.slice(0, p - 1) + out.slice(p);
|
|
423
|
+
return out;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// §12-C:nao 包为唯一来源 —— 移除 skills-lock.json 里重复的 frontend-design 条目(原文件先备份)
|
|
427
|
+
// F1:文本级删除单条,保留原缩进与其余字节(4 空格 / 2 空格均不重排)
|
|
428
|
+
function stripLockDuplicate(target, stamp, verbose) {
|
|
429
|
+
const lock = join(target, 'skills-lock.json');
|
|
430
|
+
if (!existsSync(lock)) return false;
|
|
431
|
+
const raw = readFileSync(lock, 'utf8');
|
|
432
|
+
let data;
|
|
433
|
+
try { data = JSON.parse(raw); } catch { return false; }
|
|
434
|
+
if (!data.skills || !data.skills['frontend-design']) return false;
|
|
435
|
+
const next = removeJsonProperty(raw, 'frontend-design');
|
|
436
|
+
if (next == null) return false;
|
|
437
|
+
try {
|
|
438
|
+
const parsed = JSON.parse(next);
|
|
439
|
+
if (!parsed.skills || 'frontend-design' in parsed.skills) return false;
|
|
440
|
+
} catch { return false; }
|
|
441
|
+
const bak = join(target, '.agents', '.nao-obsolete', stamp, 'skills-lock.json');
|
|
442
|
+
mkdirSync(join(bak, '..'), { recursive: true });
|
|
443
|
+
cpSync(lock, bak);
|
|
444
|
+
writeFileSync(lock, next);
|
|
445
|
+
if (verbose) log(' strip skills-lock.json → 文本级移除 frontend-design(其余字节不变)');
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function migrate(target, force, verbose) {
|
|
450
|
+
const legacy = detectLegacyAssets(target);
|
|
451
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
452
|
+
if (!legacy.length) {
|
|
453
|
+
log('未发现旧版全套 .agents/ 机制副本;按 init 处理。');
|
|
454
|
+
return init(target, force, verbose);
|
|
455
|
+
}
|
|
456
|
+
log(`迁移:发现 ${legacy.length} 处旧版 nao 资产,备份到 .agents/.nao-obsolete/${stamp}/ 后移除。`);
|
|
457
|
+
const removed = migrateLegacy(target, stamp, verbose);
|
|
458
|
+
stripLockDuplicate(target, stamp, verbose);
|
|
459
|
+
installShim(target, verbose);
|
|
460
|
+
writeVersion(target);
|
|
461
|
+
mkdirSync(join(target, '.agents'), { recursive: true });
|
|
462
|
+
writeFileSync(join(target, MIGRATED_FILE), PKG.version);
|
|
463
|
+
log(`✔ 迁移完成:移除 ${removed.length} 项已知 nao 资产(备份在 .agents/.nao-obsolete/${stamp}/);shim 已就位。`);
|
|
464
|
+
log(' 旧命令照跑:bash .agents/scripts/nao-fleet.sh check / ensure ...');
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// ---------------------------------------------------------------------------
|
|
468
|
+
// exec:CI / 脚本用 —— 定位本包真实 nao-fleet.sh 并转发(不依赖 pi/npm)
|
|
469
|
+
// ---------------------------------------------------------------------------
|
|
470
|
+
function execFleet(args) {
|
|
471
|
+
const target = join(PKG_ROOT, '.agents', 'scripts', 'nao-fleet.sh');
|
|
472
|
+
if (!existsSync(target)) {
|
|
473
|
+
warn(`包内机制脚本缺失:${target}`);
|
|
474
|
+
process.exit(2);
|
|
475
|
+
}
|
|
476
|
+
const r = spawnSync('bash', [target, ...args], {
|
|
477
|
+
stdio: 'inherit',
|
|
478
|
+
env: { ...process.env, NAO_SKILLS: PKG_ROOT, NAO_SHIM_ENTERED: '1' },
|
|
479
|
+
});
|
|
480
|
+
process.exit(r.status ?? 1);
|
|
481
|
+
}
|
|
482
|
+
|
|
221
483
|
function help() {
|
|
222
|
-
console.log(`nao-skill v${PKG.version} — nao 多角色 AI
|
|
484
|
+
console.log(`nao-skill v${PKG.version} — nao 多角色 AI 开发舰队(pi 原生分发)
|
|
223
485
|
|
|
224
486
|
用法:
|
|
225
|
-
nao-skill
|
|
226
|
-
nao-skill
|
|
227
|
-
nao-skill
|
|
487
|
+
nao-skill init [dir] [-v] 干净项目:写 shim + AGENTS.md(机制留在 .pi/npm 包内)
|
|
488
|
+
nao-skill migrate [dir] [-v] 迁移旧版全套 .agents/:备份 .nao-obsolete/ + 移除 + shim 就位
|
|
489
|
+
nao-skill exec <参数...> 转发给包内 nao-fleet.sh(CI/脚本用;NAO_SKILLS=包根)
|
|
228
490
|
nao-skill plugins list 列出已知 pi 插件与安装状态
|
|
229
491
|
nao-skill plugins install <名...> 安装指定插件(intercom/ask-me/subagents/web-access/codegraph)
|
|
230
492
|
nao-skill plugins install-all 安装全部舰队插件
|
|
231
493
|
nao-skill --version 显示版本
|
|
232
494
|
nao-skill --help 显示本帮助
|
|
233
495
|
|
|
234
|
-
|
|
235
|
-
|
|
496
|
+
兼容(旧流程,建议迁移到 init/migrate):
|
|
497
|
+
nao-skill install [dir] [--force] 复制全套 .agents/ 到项目(--plugins 顺带装插件)
|
|
498
|
+
nao-skill update [dir] 源优先覆盖升级旧版全套安装
|
|
236
499
|
|
|
237
|
-
|
|
500
|
+
安装后: bash .agents/scripts/nao-fleet.sh check / ensure pm arch-designer rd-fe rd-be qa rd-infra
|
|
501
|
+
说明: 新形态项目内只有 shim;机制单一事实来源在 npm 包内(pi install --local 物化到 .pi/npm)。
|
|
238
502
|
`);
|
|
239
503
|
}
|
|
240
504
|
|
|
@@ -249,6 +513,26 @@ if (cmd === '--version' || cmd === '-v' || cmd === 'version') {
|
|
|
249
513
|
console.log(PKG.version);
|
|
250
514
|
} else if (cmd === '--help' || cmd === '-h' || cmd === 'help' || !cmd) {
|
|
251
515
|
help();
|
|
516
|
+
} else if (cmd === 'init' || cmd === 'migrate') {
|
|
517
|
+
let target = process.cwd();
|
|
518
|
+
let force = false;
|
|
519
|
+
let verbose = false;
|
|
520
|
+
const rest = args.slice(1);
|
|
521
|
+
for (let i = 0; i < rest.length; i++) {
|
|
522
|
+
if (rest[i] === '--force' || rest[i] === '-f') force = true;
|
|
523
|
+
else if (rest[i] === '--verbose' || rest[i] === '-v') verbose = true;
|
|
524
|
+
else if (rest[i] === '--target') target = resolve(rest[++i]);
|
|
525
|
+
else if (!rest[i].startsWith('-')) target = resolve(rest[i]);
|
|
526
|
+
else { warn(`未知参数: ${rest[i]}`); process.exit(2); }
|
|
527
|
+
}
|
|
528
|
+
if (!statIsDir(target)) {
|
|
529
|
+
warn(`目标目录无效: ${target}`);
|
|
530
|
+
process.exit(2);
|
|
531
|
+
}
|
|
532
|
+
if (cmd === 'init') init(target, force, verbose);
|
|
533
|
+
else migrate(target, force, verbose);
|
|
534
|
+
} else if (cmd === 'exec') {
|
|
535
|
+
execFleet(args.slice(1));
|
|
252
536
|
} else if (cmd === 'install' || cmd === 'i') {
|
|
253
537
|
let target = process.cwd();
|
|
254
538
|
let force = false;
|
|
@@ -298,6 +582,6 @@ if (cmd === '--version' || cmd === '-v' || cmd === 'version') {
|
|
|
298
582
|
}
|
|
299
583
|
update(target, verbose);
|
|
300
584
|
} else {
|
|
301
|
-
warn(`未知命令: ${cmd}(可用: install, update, plugins)`);
|
|
585
|
+
warn(`未知命令: ${cmd}(可用: init, migrate, exec, install, update, plugins)`);
|
|
302
586
|
process.exit(2);
|
|
303
587
|
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# nao-fleet.sh — 项目内兼容 shim(nao 机制包才是单一事实来源)
|
|
4
|
+
#
|
|
5
|
+
# 作用:定位 nao 机制包根,并把全部参数原样转发给包内
|
|
6
|
+
# `<包根>/.agents/scripts/nao-fleet.sh`。
|
|
7
|
+
#
|
|
8
|
+
# 解析序(D6):
|
|
9
|
+
# 1) NAO_SKILLS(显式)—— 直接采用(须含 .agents/scripts/nao-fleet.sh)
|
|
10
|
+
# 2) 项目 .pi/npm(pin 主)—— 遍历 .pi/settings.json 的 npm 声明,node resolve
|
|
11
|
+
# 出「含 nao 机制」的包,版本须与声明一致
|
|
12
|
+
# 3) ~/.pi/agent/npm —— 仅当版本与 pin 完全一致时采用
|
|
13
|
+
# 4) 显式失败(exit 2 + 单行 DEGRADED:)
|
|
14
|
+
# 约定:按机制存在性识别包,**不硬编码包名**。
|
|
15
|
+
#
|
|
16
|
+
# 防重入(D7,双防护):
|
|
17
|
+
# ① 环境标记 NAO_SHIM_ENTERED=1 已存在 ⇒ 立即失败(防 shim→shim 递归)
|
|
18
|
+
# ② 解析出的包根 == shim 自身所属项目根 ⇒ 立即失败(防自指)
|
|
19
|
+
#
|
|
20
|
+
# 纪律:nao 机制自身零网络;本 shim 只用 node resolve,不调用 pi / npm 可执行文件。
|
|
21
|
+
# 生成:由 `nao-skill init` / `nao-skill migrate` 写入,勿手工编辑。
|
|
22
|
+
# =============================================================================
|
|
23
|
+
set -euo pipefail
|
|
24
|
+
|
|
25
|
+
SHIM_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
26
|
+
AGENTS_DIR="$(cd "$SHIM_DIR/.." && pwd)"
|
|
27
|
+
PROJ_ROOT="$(cd "$AGENTS_DIR/.." && pwd)"
|
|
28
|
+
SETTINGS="$PROJ_ROOT/.pi/settings.json"
|
|
29
|
+
GLOBAL_MOD="${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}/npm/node_modules"
|
|
30
|
+
|
|
31
|
+
DEGRADED_HINT='恢复:在项目根执行 `pi install -l --approve npm:@nathan33/nao-skill@<pin>`(或运行 pi 让其自动物化),再重试'
|
|
32
|
+
|
|
33
|
+
degrade() {
|
|
34
|
+
printf 'DEGRADED: %s;%s\n' "$1" "$DEGRADED_HINT" >&2
|
|
35
|
+
exit 2
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# D7①:标记命中即失败(防递归;也防 stale 导出把 shim 指回自身)
|
|
39
|
+
[[ "${NAO_SHIM_ENTERED:-}" == "1" ]] && degrade "检测到 shim 递归调用(NAO_SHIM_ENTERED=1)"
|
|
40
|
+
|
|
41
|
+
command -v node >/dev/null 2>&1 || degrade "未找到 node(用于解析机制包根)"
|
|
42
|
+
|
|
43
|
+
# 在给定 node_modules 里找「含 nao 机制」的包(不硬编码包名)
|
|
44
|
+
# 输出:<包根>\t<已装版本>\t<settings pin 版本>(未命中则空)
|
|
45
|
+
resolve_nao_pkg() { # $1 = node_modules 目录
|
|
46
|
+
node -e '
|
|
47
|
+
(() => {
|
|
48
|
+
const fs = require("fs"), path = require("path");
|
|
49
|
+
const mod = process.argv[1], settings = process.argv[2];
|
|
50
|
+
const names = [];
|
|
51
|
+
try {
|
|
52
|
+
const s = JSON.parse(fs.readFileSync(settings, "utf8"));
|
|
53
|
+
for (const raw of (s.packages || [])) {
|
|
54
|
+
const m = /^npm:(.+?)(?:@([^@/]+))?$/.exec(String(raw));
|
|
55
|
+
if (!m) continue;
|
|
56
|
+
let name = m[1], ver = m[2] || "";
|
|
57
|
+
if (name.startsWith("@") && name.includes("/@")) {
|
|
58
|
+
const i = name.indexOf("/@");
|
|
59
|
+
ver = name.slice(i + 2);
|
|
60
|
+
name = name.slice(0, i);
|
|
61
|
+
}
|
|
62
|
+
names.push([name, ver]);
|
|
63
|
+
}
|
|
64
|
+
} catch {}
|
|
65
|
+
for (const [name, pin] of names) {
|
|
66
|
+
let pj;
|
|
67
|
+
try { pj = require.resolve(name + "/package.json", { paths: [mod] }); } catch { continue; }
|
|
68
|
+
const root = path.dirname(pj);
|
|
69
|
+
let installed = "";
|
|
70
|
+
try { installed = JSON.parse(fs.readFileSync(pj, "utf8")).version || ""; } catch {}
|
|
71
|
+
if (!fs.existsSync(path.join(root, ".agents", "scripts", "nao-fleet.sh"))) continue;
|
|
72
|
+
process.stdout.write(root + "\t" + installed + "\t" + pin);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
})();
|
|
76
|
+
' "$1" "$SETTINGS" 2>/dev/null || true
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
PKG_ROOT=""
|
|
80
|
+
PIN_VER=""
|
|
81
|
+
|
|
82
|
+
# 1) 显式 NAO_SKILLS
|
|
83
|
+
if [[ -n "${NAO_SKILLS:-}" ]]; then
|
|
84
|
+
[[ -d "$NAO_SKILLS" ]] || degrade "NAO_SKILLS 不是目录:$NAO_SKILLS"
|
|
85
|
+
PKG_ROOT="$(cd "$NAO_SKILLS" && pwd)"
|
|
86
|
+
[[ -f "$PKG_ROOT/.agents/scripts/nao-fleet.sh" ]] || degrade "NAO_SKILLS 指向的不是 nao 机制包:$PKG_ROOT"
|
|
87
|
+
else
|
|
88
|
+
# 2) 项目 .pi/npm(pin 主)
|
|
89
|
+
FOUND="$(resolve_nao_pkg "$PROJ_ROOT/.pi/npm/node_modules")"
|
|
90
|
+
# 3) ~/.pi/agent/npm(须版本与 pin 一致才采用)
|
|
91
|
+
if [[ -z "$FOUND" ]]; then
|
|
92
|
+
CAND="$(resolve_nao_pkg "$GLOBAL_MOD")"
|
|
93
|
+
if [[ -n "$CAND" ]]; then
|
|
94
|
+
c_inst="${CAND#*$'\t'}"; c_inst="${c_inst%%$'\t'*}"
|
|
95
|
+
c_pin="${CAND##*$'\t'}"
|
|
96
|
+
[[ -n "$c_pin" && "$c_inst" == "$c_pin" ]] && FOUND="$CAND"
|
|
97
|
+
fi
|
|
98
|
+
fi
|
|
99
|
+
[[ -n "$FOUND" ]] || degrade "机制包未物化(.pi/npm 与 ~/.pi/agent/npm 均未找到含 .agents/scripts/nao-fleet.sh 的包)"
|
|
100
|
+
|
|
101
|
+
PKG_ROOT="${FOUND%%$'\t'*}"
|
|
102
|
+
REST="${FOUND#*$'\t'}"
|
|
103
|
+
INSTALLED="${REST%%$'\t'*}"
|
|
104
|
+
PIN_VER="${REST#*$'\t'}"
|
|
105
|
+
if [[ -n "$PIN_VER" && -n "$INSTALLED" && "$INSTALLED" != "$PIN_VER" ]]; then
|
|
106
|
+
degrade "机制包版本不符(pin=$PIN_VER,实得=$INSTALLED)"
|
|
107
|
+
fi
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
# D7②:包根 != shim 自身所属项目根
|
|
111
|
+
[[ "$PKG_ROOT" != "$PROJ_ROOT" ]] || degrade "解析出的包根与本项目根相同($PKG_ROOT;shim 自指)"
|
|
112
|
+
|
|
113
|
+
TARGET="$PKG_ROOT/.agents/scripts/nao-fleet.sh"
|
|
114
|
+
[[ -f "$TARGET" ]] || degrade "机制包缺少 $TARGET"
|
|
115
|
+
|
|
116
|
+
# 进入包内真实脚本(置标记;NAO_SKILLS=机制包根)
|
|
117
|
+
export NAO_SHIM_ENTERED=1
|
|
118
|
+
export NAO_SKILLS="$PKG_ROOT"
|
|
119
|
+
exec bash "$TARGET" "$@"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nathan33/nao-skill",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "nao 多角色 AI 开发舰队:角色卡 / 技能 / 协作协议 / 工具链,一键接入项目(pi / Claude / 任意 Agent)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,10 +18,17 @@
|
|
|
18
18
|
"skills",
|
|
19
19
|
"multi-agent",
|
|
20
20
|
"pi",
|
|
21
|
+
"pi-package",
|
|
21
22
|
"claude",
|
|
22
23
|
"design-system",
|
|
23
24
|
"codegraph"
|
|
24
25
|
],
|
|
26
|
+
"pi": {
|
|
27
|
+
"skills": [
|
|
28
|
+
"./.agents/skills/nao-fleet/SKILL.md",
|
|
29
|
+
"./.agents/skills/frontend-design/SKILL.md"
|
|
30
|
+
]
|
|
31
|
+
},
|
|
25
32
|
"author": "Nathan Lee",
|
|
26
33
|
"license": "MIT",
|
|
27
34
|
"publishConfig": {
|