@nathan33/nao-skill 0.5.1 → 0.6.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/scripts/nao-fleet.sh +1 -1
- package/README.md +10 -1
- package/bin/nao-skill.js +49 -38
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -84,6 +84,12 @@ PM 是任务状态权威,以状态机驱动执行:
|
|
|
84
84
|
|
|
85
85
|
- **忙闲闸门**:`list` 的 live status(`idle`/`thinking`/`tool:*`)为判定依据;忙时**不 send 任务内容**(非交互拒收 / 交互 steer 打断),直接排队;紧急才抢占(交互可 steer 注入 + 注明挂起,非交互只能排队)。
|
|
86
86
|
- **状态外部化**:`docs/tasks-state.md` 五栏(待派发/进行中/待验收/挂起/已归档)随每次派发·回执·验收更新;PM 会话重开先读文件重建状态,不依赖历史消息。
|
|
87
|
+
- **回执 = 已核对清单**:回执须声明已读 `checklists/<role>.md`(未过项必列),未核对不回执。
|
|
88
|
+
- **离线检测与恢复**:`list` 发现已派发目标离线 → 标记挂起 → `ensure --force` 重拉 → 按挂起快照重派。
|
|
89
|
+
- **队列唤醒**:回执/汇报/用户输入时顺带检查待派发队列(不单独轮询),避免排队任务悬置。
|
|
90
|
+
- **降级回流**:intercom 不可用的降级交付经用户转交后,PM 补登记 tasks-state + 归档(标注「降级回流」)。
|
|
91
|
+
- **常驻 vs 派生**:串行任务用常驻角色(忙闲排队);并行/隔离任务用派生会话 `ensure --task <编号>`(`<角色>-<编号>`,如 `rd-be-T1`,任务完成即结束)。
|
|
92
|
+
- **单 PM 纪律**:同一项目同一时刻仅一个 PM 调度会话。
|
|
87
93
|
|
|
88
94
|
- **终态回执硬闸门**:所有角色(含 arch 评审)必须以回执模板结束任务;`send` 失败降级 `ask` → 再失败才降级交付。
|
|
89
95
|
- **卡片版本同步**:改卡 bump `version` 并广播 `card <role> vN`(不贴全文);worker 接任务前核对版本,stale 则重读。
|
|
@@ -93,6 +99,8 @@ PM 是任务状态权威,以状态机驱动执行:
|
|
|
93
99
|
|
|
94
100
|
`@.agents/skills/codegraph.md`(按需)——`query`/`context`/`node`/`callers`/`callees`/`impact`/`affected` 速查 + token 纪律 + 降级 grep 规则。索引项目级维护:`status` → `init`/`sync`;`nao-fleet.sh` 的 `check`/`ensure` 自动探测索引健康并提醒。
|
|
95
101
|
|
|
102
|
+
**全角色行为指令**(非可选引用):rd-be 关键约定「先 codegraph 后 grep」、rd-fe 硬性红线「未先试 codegraph 即违规」、qa `node/affected` 定位被测代码、arch `impact/callers` 影响面评审、PM 验收 `node --file/--limit` 行段读取——**禁 cat 全文**是本机制通用读码纪律。
|
|
103
|
+
|
|
96
104
|
## UI/UX 落地(三层,不绑定组件库)
|
|
97
105
|
|
|
98
106
|
| 层 | 载体 | 职责 |
|
|
@@ -107,7 +115,7 @@ PM 是任务状态权威,以状态机驱动执行:
|
|
|
107
115
|
|
|
108
116
|
| Prompt | Role | 核心职责 |
|
|
109
117
|
| --- | --- | --- |
|
|
110
|
-
| `product-manager.md` | 产品经理(调度者) | 需求全生命周期、9 模块 PRD、RICE、多会话调度、终态回执闸门、**§七 AGENTS.md 项目上下文治理**、§十一 docs/prds
|
|
118
|
+
| `product-manager.md` | 产品经理(调度者) | 需求全生命周期、9 模块 PRD、RICE、多会话调度、终态回执闸门、**§七 AGENTS.md 项目上下文治理**、§十一 docs/prds 归档、验收读码(codegraph 行段) |
|
|
111
119
|
| `architecture-designer.md` | 系统架构师(评审/咨询) | 技术选型四步法、评审签字 + ADR、终态回执 PM、降级规则 |
|
|
112
120
|
| `frontend-developer.md` | 前端研发 | 前端 DDD 五层、UI/UX 三层落地(先读项目既有风格→定方向→tokens/组件库,引用 frontend-design) |
|
|
113
121
|
| `backend-developer.md` | 后端研发 | Go DDD 四层、依赖倒置、事务/事件/错误约定 |
|
|
@@ -157,6 +165,7 @@ nao-skill update # 升级已有安装(源优先 +
|
|
|
157
165
|
- **update(升级)**:机制文件源优先覆盖(项目定制应放 `AGENTS.md`)、备份清理已知废弃路径(如旧版 `skills/checklists/`,防 skill 冲突)到 `.agents/.nao-obsolete/`、保留项目自定义文件。
|
|
158
166
|
- **旧版检测**:install 发现版本落后或废弃路径 → 提示运行 `update`。
|
|
159
167
|
- **安装内容**:`.agents/` 全套(角色卡 / 技能 / 交付清单 / 协议 / roles.yaml / 工具链 / 模板)+ 生成 `AGENTS.md`(已存在则提示按 §七 合并,不覆盖)。
|
|
168
|
+
- **pi 插件管理**:`nao-skill plugins list` / `install <名...>` / `install-all`——舰队生态插件(intercom / ask-me / subagents / web-access / codegraph),透传 `pi install npm:<pkg>`,幂等跳过已装。
|
|
160
169
|
- **发布**:`npm publish --access public`(scope 包需 `--access public`)。
|
|
161
170
|
|
|
162
171
|
## 目录结构
|
package/bin/nao-skill.js
CHANGED
|
@@ -43,26 +43,39 @@ const VERSION_FILE = '.agents/.nao-version';
|
|
|
43
43
|
const log = (...a) => console.log('[nao-skill]', ...a);
|
|
44
44
|
const warn = (...a) => console.error('[nao-skill]', ...a);
|
|
45
45
|
|
|
46
|
-
function
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
46
|
+
function syncTree(src, dst, { mode, force, verbose }) {
|
|
47
|
+
// 递归文件级同步:mode='install'(保守)| 'update'(源优先)
|
|
48
|
+
// 源文件 → 目标无:copy;同内容:same(跳过);不同内容:install 默认 keep(--force 覆盖)/ update 总是 overwrite
|
|
49
|
+
// 目标独有:不动(项目自定义保留)
|
|
50
|
+
const stats = { copied: 0, overwritten: 0, same: 0, kept: 0 };
|
|
51
|
+
const base = dst;
|
|
52
|
+
const rel = (p) => p.replace(base + '/', '');
|
|
53
|
+
const walk = (s, d) => {
|
|
54
|
+
if (!existsSync(d)) mkdirSync(d, { recursive: true });
|
|
55
|
+
for (const e of readdirSync(s, { withFileTypes: true })) {
|
|
56
|
+
const sp = join(s, e.name);
|
|
57
|
+
const dp = join(d, e.name);
|
|
58
|
+
if (e.isDirectory()) { walk(sp, dp); continue; }
|
|
59
|
+
if (!existsSync(dp)) {
|
|
60
|
+
cpSync(sp, dp);
|
|
61
|
+
stats.copied++;
|
|
62
|
+
if (verbose) log(` copy ${rel(dp)}`);
|
|
63
|
+
} else if (readFileSync(sp).equals(readFileSync(dp))) {
|
|
64
|
+
stats.same++;
|
|
65
|
+
if (verbose) log(` same ${rel(dp)}`);
|
|
66
|
+
} else if (mode === 'update' || force) {
|
|
67
|
+
rmSync(dp, { force: true });
|
|
68
|
+
cpSync(sp, dp);
|
|
69
|
+
stats.overwritten++;
|
|
70
|
+
if (verbose) log(` overwrite ${rel(dp)}`);
|
|
57
71
|
} else {
|
|
58
|
-
|
|
72
|
+
stats.kept++;
|
|
73
|
+
if (verbose) log(` keep ${rel(dp)}(内容不同,--force 覆盖)`);
|
|
59
74
|
}
|
|
60
|
-
} else {
|
|
61
|
-
cpSync(s, d, { recursive: true });
|
|
62
|
-
copied++;
|
|
63
75
|
}
|
|
64
|
-
}
|
|
65
|
-
|
|
76
|
+
};
|
|
77
|
+
walk(src, dst);
|
|
78
|
+
return stats;
|
|
66
79
|
}
|
|
67
80
|
|
|
68
81
|
// 已知 pi 插件(nao 舰队生态),name → npm 包
|
|
@@ -126,7 +139,7 @@ function pluginsInstall(names, all = false) {
|
|
|
126
139
|
}
|
|
127
140
|
}
|
|
128
141
|
|
|
129
|
-
function install(target, force) {
|
|
142
|
+
function install(target, force, verbose) {
|
|
130
143
|
if (!existsSync(SRC)) {
|
|
131
144
|
warn(`包内 .agents 缺失:${SRC}(发布物损坏?)`);
|
|
132
145
|
process.exit(1);
|
|
@@ -137,10 +150,10 @@ function install(target, force) {
|
|
|
137
150
|
cpSync(SRC, dst, { recursive: true });
|
|
138
151
|
log(`已完整安装 .agents/ → ${dst}`);
|
|
139
152
|
} else {
|
|
140
|
-
const r =
|
|
141
|
-
log(`合并完成:复制 ${r.copied}
|
|
142
|
-
if (r.
|
|
143
|
-
warn('
|
|
153
|
+
const r = syncTree(SRC, dst, { mode: 'install', force, verbose });
|
|
154
|
+
log(`合并完成:复制 ${r.copied},覆盖 ${r.overwritten},相同跳过 ${r.same},保留项目版 ${r.kept}`);
|
|
155
|
+
if (r.kept > 0 && !force) {
|
|
156
|
+
warn('内容不同的文件保留项目既有(避免覆盖自定义);--force 覆盖,或 update 模式源优先。');
|
|
144
157
|
}
|
|
145
158
|
}
|
|
146
159
|
|
|
@@ -172,21 +185,15 @@ function install(target, force) {
|
|
|
172
185
|
log(' 角色卡:.agents/prompts/ · 技能:.agents/skills/ · 交付清单:.agents/checklists/');
|
|
173
186
|
}
|
|
174
187
|
|
|
175
|
-
function update(target) {
|
|
188
|
+
function update(target, verbose) {
|
|
176
189
|
// 源优先升级:机制文件以包为权威(项目定制应放 AGENTS.md,不在机制文件里改)
|
|
177
190
|
const dst = join(target, '.agents');
|
|
178
191
|
if (!existsSync(dst)) {
|
|
179
192
|
log('目标无 .agents/,直接完整安装。');
|
|
180
|
-
return install(target, false);
|
|
181
|
-
}
|
|
182
|
-
let overwritten = 0;
|
|
183
|
-
for (const e of readdirSync(SRC)) {
|
|
184
|
-
const s = join(SRC, e);
|
|
185
|
-
const d = join(dst, e);
|
|
186
|
-
rmSync(d, { recursive: true, force: true });
|
|
187
|
-
cpSync(s, d, { recursive: true });
|
|
188
|
-
overwritten++;
|
|
193
|
+
return install(target, false, verbose);
|
|
189
194
|
}
|
|
195
|
+
const r = syncTree(SRC, dst, { mode: 'update', force: true, verbose });
|
|
196
|
+
log(`升级完成:复制 ${r.copied},覆盖 ${r.overwritten},相同跳过 ${r.same}(项目自定义保留)`);
|
|
190
197
|
// 清理已知废弃路径(先备份到 .agents/.nao-obsolete/)
|
|
191
198
|
for (const p of OBSOLETE) {
|
|
192
199
|
const full = join(target, p);
|
|
@@ -215,9 +222,9 @@ function help() {
|
|
|
215
222
|
console.log(`nao-skill v${PKG.version} — nao 多角色 AI 开发舰队安装器
|
|
216
223
|
|
|
217
224
|
用法:
|
|
218
|
-
nao-skill install [dir] [--force] 安装/合并 .agents/
|
|
219
|
-
nao-skill install --plugins
|
|
220
|
-
nao-skill update [dir] 升级已有安装:源优先覆盖 + 清理废弃路径 + 保留项目自定义
|
|
225
|
+
nao-skill install [dir] [--force] [-v] 安装/合并 .agents/ 到目标项目(默认当前目录;-v 显示每文件过程)
|
|
226
|
+
nao-skill install --plugins [-v] 项目接入时顺带安装 pi 舰队插件
|
|
227
|
+
nao-skill update [dir] [-v] 升级已有安装:源优先覆盖 + 清理废弃路径 + 保留项目自定义
|
|
221
228
|
nao-skill plugins list 列出已知 pi 插件与安装状态
|
|
222
229
|
nao-skill plugins install <名...> 安装指定插件(intercom/ask-me/subagents/web-access/codegraph)
|
|
223
230
|
nao-skill plugins install-all 安装全部舰队插件
|
|
@@ -246,10 +253,12 @@ if (cmd === '--version' || cmd === '-v' || cmd === 'version') {
|
|
|
246
253
|
let target = process.cwd();
|
|
247
254
|
let force = false;
|
|
248
255
|
let withPlugins = false;
|
|
256
|
+
let verbose = false;
|
|
249
257
|
const rest = args.slice(1);
|
|
250
258
|
for (let i = 0; i < rest.length; i++) {
|
|
251
259
|
if (rest[i] === '--force' || rest[i] === '-f') force = true;
|
|
252
260
|
else if (rest[i] === '--plugins') withPlugins = true;
|
|
261
|
+
else if (rest[i] === '--verbose' || rest[i] === '-v') verbose = true;
|
|
253
262
|
else if (rest[i] === '--target') target = resolve(rest[++i]);
|
|
254
263
|
else if (!rest[i].startsWith('-')) target = resolve(rest[i]);
|
|
255
264
|
else { warn(`未知参数: ${rest[i]}`); process.exit(2); }
|
|
@@ -258,7 +267,7 @@ if (cmd === '--version' || cmd === '-v' || cmd === 'version') {
|
|
|
258
267
|
warn(`目标目录无效: ${target}`);
|
|
259
268
|
process.exit(2);
|
|
260
269
|
}
|
|
261
|
-
install(target, force);
|
|
270
|
+
install(target, force, verbose);
|
|
262
271
|
if (withPlugins) pluginsInstall([], true);
|
|
263
272
|
} else if (cmd === 'plugins' || cmd === 'plugin') {
|
|
264
273
|
const sub = args[1];
|
|
@@ -275,9 +284,11 @@ if (cmd === '--version' || cmd === '-v' || cmd === 'version') {
|
|
|
275
284
|
}
|
|
276
285
|
} else if (cmd === 'update' || cmd === 'u') {
|
|
277
286
|
let target = process.cwd();
|
|
287
|
+
let verbose = false;
|
|
278
288
|
const rest = args.slice(1);
|
|
279
289
|
for (let i = 0; i < rest.length; i++) {
|
|
280
|
-
if (rest[i] === '--
|
|
290
|
+
if (rest[i] === '--verbose' || rest[i] === '-v') verbose = true;
|
|
291
|
+
else if (rest[i] === '--target') target = resolve(rest[++i]);
|
|
281
292
|
else if (!rest[i].startsWith('-')) target = resolve(rest[i]);
|
|
282
293
|
else { warn(`未知参数: ${rest[i]}`); process.exit(2); }
|
|
283
294
|
}
|
|
@@ -285,7 +296,7 @@ if (cmd === '--version' || cmd === '-v' || cmd === 'version') {
|
|
|
285
296
|
warn(`目标目录无效: ${target}`);
|
|
286
297
|
process.exit(2);
|
|
287
298
|
}
|
|
288
|
-
update(target);
|
|
299
|
+
update(target, verbose);
|
|
289
300
|
} else {
|
|
290
301
|
warn(`未知命令: ${cmd}(可用: install, update, plugins)`);
|
|
291
302
|
process.exit(2);
|