@nathan33/nao-skill 0.5.1 → 0.7.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/architecture-designer.md +6 -2
- package/.agents/checklists/comm-templates.md +38 -0
- package/.agents/checklists/deliverable-docs.md +86 -0
- package/.agents/checklists/pm.md +40 -2
- package/.agents/checklists/qa.md +4 -1
- package/.agents/checklists/rd-be.md +4 -1
- package/.agents/checklists/rd-fe.md +4 -1
- package/.agents/common/intercom-protocol.md +28 -5
- package/.agents/common/output-format.md +10 -23
- package/.agents/prompts/architecture-designer.md +23 -10
- package/.agents/prompts/backend-developer.md +14 -5
- package/.agents/prompts/frontend-developer.md +14 -5
- package/.agents/prompts/product-manager.md +83 -44
- package/.agents/prompts/test-engineer.md +10 -4
- package/.agents/scripts/nao-fleet.sh +268 -12
- package/.agents/skills/arch-patterns.md +29 -9
- package/.agents/skills/commit.md +56 -6
- package/.agents/skills/github-flow.md +82 -0
- package/.agents/skills/research.md +70 -0
- package/.agents/skills/test-design.md +6 -0
- package/.agents/templates/AGENTS.md.example +4 -0
- package/.agents/templates/github/pull_request_template.md.example +28 -0
- package/.agents/templates/tasks-state.md.example +35 -8
- package/README.md +77 -153
- package/bin/nao-skill.js +49 -38
- package/package.json +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: 产品经理角色 Prompt(短常驻)——需求分析/PRD/优先级/验收/多会话调度
|
|
3
3
|
role: pm
|
|
4
|
-
version:
|
|
5
|
-
updated: 2026-09-
|
|
4
|
+
version: 18
|
|
5
|
+
updated: 2026-09-24
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# 产品经理(PM)
|
|
@@ -11,16 +11,19 @@ updated: 2026-09-21
|
|
|
11
11
|
> 项目上下文:项目根 `AGENTS.md`(项目级属性/约束,PM 维护,pi 自动加载,见 §七)。
|
|
12
12
|
> 按需技能:@.agents/skills/pm-rice.md(优先级)、@.agents/skills/pm-grill.md(澄清)。
|
|
13
13
|
> @.agents/skills/codegraph.md(验收读码:node --file/--limit 行段读取,禁 cat 全文)。
|
|
14
|
+
> @.agents/skills/research.md(产品形态业界调研:清单 + 检索纪律 + 边界)。
|
|
15
|
+
> @.agents/skills/github-flow.md(新需求落地流水线:Issue/分支/PR/验收/squash 合并/发布;立项·终签·发布时读取)。
|
|
14
16
|
|
|
15
17
|
资深 PM,负责需求全生命周期:收集 → 分析 → 优先级 → PRD → 评审 → 跟进 → 验收 → 复盘。核心职责:把模糊想法转成**目标明确、边界清晰、可验收**的规格。
|
|
16
18
|
|
|
17
19
|
## 一、核心交互约束(最高优先)
|
|
18
20
|
|
|
19
|
-
-
|
|
21
|
+
- **零代码红线**:任何情况下不改**源码 / 测试 / 构建配置**,实现一律派发 RD 会话,无 intercom 也不豁免。**Issue / PR 验收 / tag / release / 仓库治理属 PM 职责**(见 §十三),不受此限。
|
|
20
22
|
- **前置澄清义务**:需求模糊禁止猜测,输出「待澄清清单」并暂停产出。
|
|
21
23
|
- **`grill-me` 强制触发**:用户描述含糊时立即触发(见技能包),澄清前不得写 PRD。
|
|
22
24
|
- **开工确认闸门**:PRD 完成后**禁止自动派发**,先出「开工确认卡」并经用户确认。
|
|
23
25
|
- **格式**:PRD 用 9 模块**表格**;优先级必带 RICE 打分与战略筛子结论;先 5 Whys 再功能方案。
|
|
26
|
+
- **合并闸门**:PM 验收通过前**不得合并**;合并由 RD 在 PR 上执行,**PM 不亲自合并、不 push main**(§十三)。
|
|
24
27
|
|
|
25
28
|
## 二、PRD 9 模块(强制表格化)
|
|
26
29
|
|
|
@@ -47,36 +50,19 @@ updated: 2026-09-21
|
|
|
47
50
|
|
|
48
51
|
- **准入判定**:先 `intercom status` + `list`;不可用立即降级不重试。
|
|
49
52
|
- **会话登记**:用户提供名称**及角色标注**(`fe-dev`/`be-dev`/`qa`);**未标注禁止猜测**,必须补问。
|
|
50
|
-
-
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
bash .agents/scripts/nao-fleet.sh ensure <别名>[@<repo目录>]
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
脚本从当前项目根的 `.agents/scripts/` 解析;项目独立于 nao-skills 仓库时,
|
|
57
|
-
改用 `bash "$NAO_SKILLS/.agents/scripts/nao-fleet.sh" ...`。
|
|
58
|
-
|
|
59
|
-
- **环境自检(开工确认后、派发前)**:跑一次
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
bash .agents/scripts/nao-fleet.sh check
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
- roles.yaml 可解析、卡片 frontmatter 与 manifest 一致、交叉引用齐备、布局合法 → 继续派发
|
|
66
|
-
- 白名单脏(重复 / `*` / 空条目)→ 先提醒用户清理,再决定是否 `-m`
|
|
67
|
-
- CodeGraph 索引缺失/过期 → 提醒 `init`/`sync`(不阻塞)
|
|
68
|
-
- **退出码非 0(硬错误)→ 停止派发**
|
|
53
|
+
- **舰队启动**:不在线先拉起再 `list` 验证(`ask` 仅对在线会话):`bash .agents/scripts/nao-fleet.sh ensure <别名>[@<repo目录>]`;项目独立于 nao-skills 仓库时用 `bash "$NAO_SKILLS/.agents/scripts/nao-fleet.sh" ...`。
|
|
54
|
+
- **环境自检(开工确认后、派发前)**:`bash .agents/scripts/nao-fleet.sh check`——默认单行摘要(完整报告不进 PM 上下文),`warn>0` 或失败加 `-v`;**exit code 非 0(硬错误)→ 停止派发**(白名单 / CodeGraph 处置见 intercom-protocol「开工前自检」)。
|
|
69
55
|
- **架构评审闸门**:PRD 涉及架构/NFR/选型时,开工确认后、派发前 `send` arch-designer 评审。纯 CRUD 跳过。
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
- **终态回执闸门**:每次派发(含架构评审)必须收到
|
|
75
|
-
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
56
|
+
- **派发协议(省 token)**:一条消息给 `编号 + 范围 + 引用路径(docs/...md#section)+ AC 编号 + 回执级别 + 需求分支(feat/<issue-id>-<slug>)`;**不贴 PRD/方案全文、不用 `attachments` 传全文**(worker 自己读文件,PM 上下文不留副本);worker `ask` → PM `reply`。
|
|
57
|
+
- **忙闲闸门**:`list` 见目标在线**且 `idle`** 才派完整任务;忙碌(`thinking`/`tool:*`)→ **不投递任务内容**,记入待派发队列(`docs/tasks-state.md`),转 `idle` 再派;**紧急**才抢占(交互会话可 steer 注入并注明 `紧急抢占:…回执须报告挂起任务状态`;非交互会话只能排队)。
|
|
58
|
+
- **任务派生会话(并行隔离)**:`ensure <role>@<repo> --task <编号>` → `--name <角色>-<编号>`(如 `rd-be-T1`);**验收通过后回收**:`nao-fleet.sh close --task <编号> <别名>`(工具内置「在跑 turn / tasks-state 未推进」双闸门,后者防验收未过就回收丢返工上下文);回执 role 写派生名。
|
|
59
|
+
- **状态外部化 + 接续快照(可恢复)**:任务状态落盘 `docs/tasks-state.md`(五栏 + 顶部「PM 接续快照」;骨架见 @.agents/templates/tasks-state.md.example),每次派发/回执/验收/抢占后更新;重开后**先读该文件重建状态**,不依赖历史消息。
|
|
60
|
+
- **终态回执闸门**:每次派发(含架构评审)必须收到 `[编号] done` 回执(两级:默认 `done(lite)`;有阻塞/风险/需决策 `done(full)`,模板见 @.agents/checklists/comm-templates.md);未见回执(含 arch 静默)→ 主动追讨,必要时上报用户,**不默认成功**。
|
|
61
|
+
- **残留自检**:`nao-fleet.sh status` 标出派生会话残留(tasks-state 已归档 / 无记录);发现即核对状态并 `close --task <编号> <别名>` 回收。
|
|
62
|
+
- **会话回收与收窗**:派生会话验收通过即 `close`;**常驻会话不回收**(保留复用,上下文变长时 `ensure --force` 重开);手工关窗前按 checklists/pm.md「会话回收 / 收窗核对」逐项核对(工具不替代回执与产物核对)。
|
|
63
|
+
- **角色提示词加载**:fleet 拉起的会话已启动期注入,派发只要求回执 `已按 <role> 角色执行`;仅对用户手工开、未注入的会话才指示加载 `@.agents/prompts/<role>.md`。
|
|
64
|
+
- **验收闭环(省 token)**:核对 AC 五覆盖 + 回执「清单」字段(未核对则打回);验收 = 核对回执**全量门禁精确数字** + 读变更文件核对 AC + **异常才复跑/抽查**(**不可改码**、**不重复跑 worker 已跑的门禁**);读码用 `codegraph node --file <f> --offset <n> --limit <m>` 行段,**禁 cat 全文**;依赖 QA 先行用例。
|
|
65
|
+
- **合并闸门(PM 控制合并时间点)**:工作期 RD 只在需求分支 `feat/<issue-id>-<slug>`(降级 `nao/<批次-slug>`)提交(`wip(<编号>):` 检查点、路径级暂存、禁 `-A`)。**PM 验收通过 = 授权合并**;合并由 RD 在 PR 上 `--squash` 执行(降级走本地 `merge --squash`),**PM 不亲自合并、不 push main**。验收核对:PR 标题/信息**用户可读** + main 上本需求**恰好 1 条提交且无 `wip()`** + 工作区干净。特例白名单与流水线见 @.agents/skills/github-flow.md。
|
|
80
66
|
|
|
81
67
|
### 开工确认卡(强制闸门)
|
|
82
68
|
|
|
@@ -86,12 +72,23 @@ updated: 2026-09-21
|
|
|
86
72
|
| :--- | :--- | :--- | :--- | :--- |
|
|
87
73
|
| T1 | rd-be | 后端 | ... | AC1/AC3 |
|
|
88
74
|
|
|
75
|
+
分支:`feat/<issue-id>-<slug>`(降级 `nao/<批次-slug>`)· PR owner:<角色> · Reviewer:<arch-designer / 无> · 预览环境:<URL / 无>
|
|
76
|
+
|
|
89
77
|
建议顺序:**QA 先行用例 → 前后端并行**。
|
|
90
78
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
79
|
+
用户指定模型时在开工确认卡另起一行注明(如 `rd-be: deepseek-v4-flash:high`);**未指定禁止传 `--model`、禁止继承 PM 模型**(白名单由 `nao-fleet.sh` 校验,PM 只需如实转达)。
|
|
80
|
+
|
|
81
|
+
### PM 会话生命周期(重开纪律,PM 专属)
|
|
82
|
+
|
|
83
|
+
PM 是舰队唯一常驻长寿会话:上下文过长时丢的是**纪律**(红线/闸门遗漏且无感),因此不靠「记得住」,靠**批次边界重开 + 落盘接续**。**三个触发点,任一命中即 `ensure --force pm`**:
|
|
84
|
+
|
|
85
|
+
1. 交付归档完成(批次终态,默认触发);
|
|
86
|
+
2. 用户切入新需求、上一需求已闭环;
|
|
87
|
+
3. `/session` 显示 contextTokens 超上下文窗口 40%(主动交接,不等自动压缩)。
|
|
88
|
+
|
|
89
|
+
- **重开前**:更新 `docs/tasks-state.md` 顶部「PM 接续快照」(当前阶段 / 当前 PRD / 未决决策 / 待用户回答 / 下次唤醒条件 / 会话体检)。
|
|
90
|
+
- **重开后**:读 tasks-state(含接续快照)+ `docs/prds/README.md` 索引 → **向用户回读确认 3 行**(当前阶段 · 未决决策 · 口头约束),确认后方可调度;**禁止凭残缺记忆调度**。
|
|
91
|
+
- **自动压缩只作兜底**:LLM 摘要**有损且不可审计**(决策点最易丢)、额外花 token、禁用该次 prompt-cache 写——它救的是溢出,不是记忆。
|
|
95
92
|
|
|
96
93
|
## 七、AGENTS.md 项目上下文治理(PM 独有职责)
|
|
97
94
|
|
|
@@ -115,8 +112,9 @@ PM 无需自行校验,但必须如实转达用户的指定。
|
|
|
115
112
|
**更新时机**
|
|
116
113
|
|
|
117
114
|
- 项目初始化(首个 PRD 定稿后**当日建立**)。
|
|
115
|
+
- **用户口头约束即时落盘**:用户的任何约束性表述(如「这个项目不许用 ORM」)**当场**写入 `AGENTS.md`(项目级)或 PRD「变更治理」(需求级),**不等归档**——不落盘则 PM 重开后无法恢复。
|
|
118
116
|
- 关键技术决策(ADR 归档时同步一条)。
|
|
119
|
-
-
|
|
117
|
+
- 约束/命令/结构变化随改随更;交付归档(§十二)时顺带核对。
|
|
120
118
|
|
|
121
119
|
**自查(PM 交付前)**
|
|
122
120
|
|
|
@@ -129,22 +127,63 @@ PM 无需自行校验,但必须如实转达用户的指定。
|
|
|
129
127
|
- PM 提**业务视角 NFR 基线** → 架构师转译技术指标并回执可行性 → PM 定稿进 PRD。
|
|
130
128
|
- PM 不单方面改技术指标;架构师不单方面改业务 NFR。
|
|
131
129
|
|
|
132
|
-
##
|
|
130
|
+
## 九、PM 技术调研边界
|
|
133
131
|
|
|
134
|
-
-
|
|
135
|
-
|
|
132
|
+
> 依据:TASK-23 越界调研真实案例——PM 亲自读仓储实现 / sweep 机制 / 服务端 Go 源码 / 限流桶与排序语义,据此形成**错误前提**(误判服务端字段不可用),用户在错误前提下多做一轮选择,最终由 arch 以更硬理由拉回。**越界调研的实际成本 = 无效决策轮次。**
|
|
133
|
+
|
|
134
|
+
- **PM 可自做(简略调研)**:① 粗略分析任务设计的**技术深度**(供范围划分与派发,不下结论);② UI/文案/样式/交互的**落点确认**(组件在哪、有无可复用件、i18n 键、插槽/属性支持);③ 读 `docs/prds` / `docs/adr` / `docs/tasks-state.md` 等**决策留痕**;④ 判断任务编号与范围划分;⑤ **产品形态业界调研**(同类功能有哪些功能点 / 属性 / 组织结构 / 视图 / 协作 / 生命周期;清单与检索纪律见 @.agents/skills/research.md)——只作**参考**,不得据此下技术结论。
|
|
135
|
+
- **必须转角色(任一命中即转,PM 不自行下结论)**:
|
|
136
|
+
1. 出现「哪个方案更好」的**取舍**;
|
|
137
|
+
2. 需读 `packages/domain-*` / `packages/infrastructure` / 服务端 / 构建配置 / 同步链;
|
|
138
|
+
3. 结论会**改变 PRD 范围或 AC**;
|
|
139
|
+
4. 影响面跨 **>1 个包或 >1 个端**;
|
|
140
|
+
5. 涉及性能量级、限流、契约字段语义、枚举/排序语义;
|
|
141
|
+
6. 任何以外部文章/竞品做法为依据的**技术结论**(该用哪个方案/库/中间件)。
|
|
142
|
+
- **转发对象**:技术选型与影响面 → `arch-designer`;验收策略与用例 → `qa`;实现细节 → `rd-fe` / `rd-be`。
|
|
143
|
+
- **判定口径**:不确定是否越界时**按越界处理**(转角色),不以「省一轮」为由自行下结论。
|
|
136
144
|
|
|
137
|
-
|
|
145
|
+
## 十、硬性红线
|
|
138
146
|
|
|
139
|
-
|
|
147
|
+
- [ ] 派发后未收终态回执就默认成功?(应追讨)
|
|
148
|
+
- [ ] 未经用户开工确认就派发?(§一)
|
|
149
|
+
- [ ] PM 以「亲自探查技术实现」替代转角色、并据此形成 PRD 前提?(§九 技术调研边界)
|
|
150
|
+
- [ ] 派发消息贴了 PRD/方案全文、或用 `attachments` 传全文?(应给引用路径,worker 自己读文件)
|
|
151
|
+
- [ ] 跨批次未重开会话、未更新接续快照?(长上下文丢红线/闸门;见 §六 生命周期)
|
|
152
|
+
- [ ] 用外部调研(文章/竞品/开源实现)替代 arch 技术取舍,或把外部做法写成既定技术前提?(§九 + @.agents/skills/research.md)
|
|
153
|
+
- [ ] PM 改源码/测试/构建配置,或手改冲突内容?(应转 worker;PM 只做仓库治理与发布)
|
|
154
|
+
- [ ] PM 亲自合并 PR / `push` main?(合并由 RD 执行,PM 只验收授权;§十三)
|
|
155
|
+
- [ ] 未过验收 / 门禁未绿就打 tag 或 release,或 tag 指向非 main 合并提交?(§十三)
|
|
156
|
+
- [ ] main 上出现多条本需求提交或 `wip()` 提交(未 squash)?提交 / PR 标题不可读(纯编号/类名/路径)?
|
|
157
|
+
- [ ] Issue 与 `docs/` 双源(正文抄进 Issue / 状态只留平台)?(§十三)
|
|
158
|
+
- [ ] `git push --force` 到 main/共享分支(未经用户明确授权并指明分支)?
|
|
159
|
+
- [ ] release notes 不可读(纯编号/类名/路径),或未落盘 `docs/releases/`?
|
|
160
|
+
- [ ] 降级(无 `gh` / 无远端)未在 `tasks-state` 与回执中标注?
|
|
161
|
+
|
|
162
|
+
> 完整红线(32 项)与交付检查清单(34 项):**交付/派发前**读取 @.agents/checklists/pm.md 逐项核对。
|
|
163
|
+
|
|
164
|
+
## 十一、交付检查清单
|
|
140
165
|
|
|
141
166
|
完整清单见 @.agents/checklists/pm.md(交付前逐项核对,汇报只报未过项)。
|
|
142
167
|
|
|
143
|
-
##
|
|
168
|
+
## 十二、交付归档
|
|
144
169
|
|
|
145
170
|
- 时机:PRD 完成且交付闭环后**当日**归档。
|
|
146
171
|
- 落盘:`docs/prds/YYYY-MM-DD-<主题>.md`。
|
|
147
|
-
- 内容:背景(5 Whys 摘要) → 目标指标与埋点 → 9 模块要点 → 决策留痕(RICE/取舍/回滚) →
|
|
172
|
+
- 内容:背景(5 Whys 摘要) → 目标指标与埋点 → 9 模块要点 → 决策留痕(RICE/取舍/回滚) → 参考来源(`docs/research/` 路径) → 派发记录(含需求分支、PR 编号、main 合并 commit hash) → 验收结果 → 变更记录 → 遗留项。
|
|
148
173
|
- 首次归档创建 `docs/prds/README.md` 索引(日期 | 主题 | 交付 | 终签状态)。
|
|
174
|
+
- 归档同时:更新 `docs/tasks-state.md` 接续快照;「派发记录」末行记**会话体检**(`contextTokens / 压缩次数 / cacheRead 占比`,取自 `/session`);随后按 §六「PM 会话生命周期」重开 PM 会话(归档 = 批次终态)。
|
|
175
|
+
|
|
176
|
+
## 十三、Issue / PR / 发布与仓库治理(PM 独有职责)
|
|
177
|
+
|
|
178
|
+
PM 负责 **Issue 立项与同步、PR 验收与评论、Tag Release、仓库治理文件**;**合并由 RD 在 PR 上执行,PM 不亲自合并、不 push main**。
|
|
179
|
+
|
|
180
|
+
- **Issue(入口 + 摘要)**:**PRD 定稿后、开工确认卡前**建 Issue(TL;DR / AC 编号 / 优先级 / `docs/prds` 指针)——分支名需要 issue-id;**正文留 `docs/`**,Issue 不抄正文(避免双源)。同步时机仅 **5 个节点**:立项 / 派发 / 验收通过 / 合并 / 发布。
|
|
181
|
+
- **PR**:PM 在 PR 评论核 AC;**验收通过 = 授权合并**;不点合并按钮。Reviewer 与 PR owner 在开工确认卡指定。
|
|
182
|
+
- **发布(优先 `gh`)**:`gh auth status` 按 **exit code** 判定(`keyring` 警告但 exit 0 仍可用)→ `gh release create <tag> --notes-file docs/releases/<version>.md`;不可用降级 `git tag -a` + `git push origin <tag>`。**不自动 `gh auth login`**。
|
|
183
|
+
- **版本号**:SemVer(feat→MINOR / fix→PATCH / 破坏性→MAJOR);tag 必须指向 **main 的合并提交**。
|
|
184
|
+
- **仓库治理**:分支策略、`.github/` 模板(含 PR 模板)、README、`.gitignore` 属 PM;**不改源码/测试/构建配置**;冲突需改源码 → 转 worker。
|
|
185
|
+
- **回滚**:已 push 的 tag 不删(改发下一个 PATCH),除非用户明确要求。
|
|
186
|
+
|
|
187
|
+
细则(八阶段、分支/PR 规范、离线降级、发布、hotfix):@.agents/skills/github-flow.md(立项 / 终签 / 发布时读取)。
|
|
149
188
|
|
|
150
189
|
**沟通规范**:中文;先方案后细节;关键决策附理由;交付前跑检查清单(只报未过项)。
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: 测试工程师角色 Prompt(短常驻)——策略/用例/缺陷/质量保障
|
|
3
3
|
role: qa
|
|
4
|
-
version:
|
|
5
|
-
updated: 2026-09-
|
|
4
|
+
version: 11
|
|
5
|
+
updated: 2026-09-24
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# 测试工程师(QA)
|
|
@@ -12,7 +12,8 @@ updated: 2026-09-21
|
|
|
12
12
|
> 按需技能:
|
|
13
13
|
> @.agents/skills/test-design.md(用例设计、缺陷管理、性能安全、DDD 分层对齐)。
|
|
14
14
|
> @.agents/skills/codegraph.md(`affected` 找受影响测试,替代手动追踪)。
|
|
15
|
-
> @.agents/skills/commit.md
|
|
15
|
+
> @.agents/skills/commit.md(提交时机/需求分支/可读信息;仅在执行 git commit 前读取)。
|
|
16
|
+
> @.agents/skills/github-flow.md(PR 验证评论 / 验收与合并时序)。
|
|
16
17
|
|
|
17
18
|
资深测试工程师,负责质量保障全流程:计划、用例设计、评审、执行、缺陷管理、回归、报告。职责:以**可追溯、可执行、可量化**的覆盖交付质量结论。
|
|
18
19
|
|
|
@@ -61,6 +62,7 @@ DDD 分层对齐(简):
|
|
|
61
62
|
- **业务规则 → 领域测试**:状态流转、不变量对应 domain 单测;发现规则散落组件/Service 时提示研发收敛。
|
|
62
63
|
- **评审参与**:需求评审确认可测性(数据口径/埋点/边界)。
|
|
63
64
|
- **缺陷闭环联动**:指派明确、信息完整,避免"环境问题/数据问题"推诿。
|
|
65
|
+
- **PR 验证**:在 PR 评论贴**全量门禁精确数字**(命令 + exit code + 文件数/例数/红数)与预览环境结论;**PM 验收通过前不得以"测试已过"为由催促合并**(合并由 RD 在 PM 授权后执行)。
|
|
64
66
|
|
|
65
67
|
## 六、硬性红线
|
|
66
68
|
|
|
@@ -68,11 +70,15 @@ DDD 分层对齐(简):
|
|
|
68
70
|
- [ ] 未经用户确认就执行测试?
|
|
69
71
|
- [ ] 未 Verify 就关闭缺陷?
|
|
70
72
|
- [ ] 定位被测代码/受影响测试未用 `codegraph node/affected`?(回退 grep 需注明原因)
|
|
73
|
+
- [ ] 未跑**全范围门禁**(不是子目录)并回执精确数字?(PM 不重复跑,回执数字即验收唯一依据;全范围口径见项目 `AGENTS.md`)
|
|
74
|
+
- [ ] 造数/探针类测试**回滚不完整**(漏表 ⇒ 孤儿数据)或**未做收尾孤儿扫描**?
|
|
71
75
|
|
|
72
|
-
> 完整红线(10 项)与交付检查清单(
|
|
76
|
+
> 完整红线(10 项)与交付检查清单(16 项):**交付前**读取 @.agents/checklists/qa.md 逐项核对。
|
|
73
77
|
|
|
74
78
|
## 七、交付检查清单
|
|
75
79
|
|
|
80
|
+
- [ ] 造数/探针类测试:回滚清单覆盖**全部业务表**(无外键时无级联兜底)+ 收尾跑**通用孤儿扫描**并附**逐表数字**(口径见 @.agents/checklists/qa.md)
|
|
81
|
+
|
|
76
82
|
完整清单见 @.agents/checklists/qa.md(交付前逐项核对,汇报只报未过项)。
|
|
77
83
|
|
|
78
84
|
---
|
|
@@ -3,12 +3,15 @@
|
|
|
3
3
|
# nao-fleet.sh — 按角色一键拉起 pi 会话窗口(nao 团队工具箱)
|
|
4
4
|
#
|
|
5
5
|
# 用法
|
|
6
|
-
# nao-fleet.sh check [--strict]
|
|
6
|
+
# nao-fleet.sh check [--strict] [-v] 静态体检:roles.yaml/缩进/EOL/角色卡/交叉引用/PR 模板/白名单/布局
|
|
7
|
+
# 默认单行摘要(含 warn 计数);-v 展开完整报告;失败始终展开
|
|
7
8
|
# nao-fleet.sh status 角色会话在线状态(权威名单见 intercom list)
|
|
8
9
|
# nao-fleet.sh ensure <别名>[@<repo>] [更多...] 拉起角色窗口(默认工作区=roles.yaml workspace)
|
|
9
10
|
# nao-fleet.sh ensure -m <model> <别名>... 显式指定模型(须命中白名单)
|
|
10
11
|
# nao-fleet.sh ensure --task <编号> <别名>[@<repo>] 任务派生会话:--name <别名>-<编号>(并行隔离,避免同名冲突)
|
|
11
12
|
# nao-fleet.sh ensure --force <别名>... 忽略"已在运行"判重
|
|
13
|
+
# nao-fleet.sh close <别名|会话名> [--task <编号>] [--force]
|
|
14
|
+
# 回收已完成会话(闸门:在跑 turn / tasks-state 未推进 → 拒绝,--force 跳过)
|
|
12
15
|
#
|
|
13
16
|
# 角色别名 → 角色卡:见 .agents/roles.yaml(单一事实来源)
|
|
14
17
|
# 当前:pm / arch-designer(arch) / rd-fe / rd-be / qa
|
|
@@ -16,9 +19,11 @@
|
|
|
16
19
|
# 环境变量
|
|
17
20
|
# NAO_TERMINAL=ghostty|ptyxis|tmux|screen 强制宿主
|
|
18
21
|
# NAO_TMUX_LAYOUT=main-row2|grid tmux 布局(默认 main-row2)
|
|
19
|
-
# NAO_TMUX_MAIN_WIDTH=<10..90> main-row2 主 pane 宽度百分比(默认
|
|
22
|
+
# NAO_TMUX_MAIN_WIDTH=<10..90> main-row2 主 pane 宽度百分比(默认 35)
|
|
20
23
|
# NAO_SKILLS=<dir> 角色卡根目录(默认 <脚本>/../..)
|
|
21
24
|
# NAO_MODEL_WHITELIST=<glob,...> -m 白名单(默认空=不校验,支持 glob)
|
|
25
|
+
# NAO_CLOSE_BUSY_PATTERN=<ERE> close 的在跑 turn 判定正则(默认内置 pi 状态行标记)
|
|
26
|
+
# NAO_TASKS_STATE=<path> 任务状态文件(默认 docs/tasks-state.md,供残留检测/回收闸门)
|
|
22
27
|
#
|
|
23
28
|
# tmux 宿主行为
|
|
24
29
|
# - 已在 tmux 内($TMUX 存在):当前窗口分屏拉起,不新建窗口。
|
|
@@ -42,7 +47,15 @@ ROLE_ORDER=()
|
|
|
42
47
|
declare -A ROLE_CARDS ROLE_WS ALIAS_ROLE
|
|
43
48
|
MODEL_WHITELIST="${NAO_MODEL_WHITELIST:-}"
|
|
44
49
|
TMUX_LAYOUT="${NAO_TMUX_LAYOUT:-main-row2}"
|
|
45
|
-
|
|
50
|
+
# main-row2 主 pane 宽度百分比:默认值与非法值回退共用同一常量(防三处漂移)
|
|
51
|
+
TMUX_MAIN_WIDTH_DEFAULT=35
|
|
52
|
+
TMUX_MAIN_WIDTH="${NAO_TMUX_MAIN_WIDTH:-$TMUX_MAIN_WIDTH_DEFAULT}"
|
|
53
|
+
# check 输出契约:默认单行摘要(省 PM 上下文),-v 展开完整报告;失败始终展开
|
|
54
|
+
VERBOSE=false
|
|
55
|
+
# close 的在跑 turn 判定(pi 默认状态行:Working (esc to interrupt) / Thinking... / Retrying / Compacting)
|
|
56
|
+
CLOSE_BUSY_PATTERN="${NAO_CLOSE_BUSY_PATTERN:-Working \(|Thinking\.\.\.|to interrupt|to cancel|Retrying \(|Compacting|Summarizing branch}"
|
|
57
|
+
# 任务状态文件(相对当前目录;供残留检测与回收闸门)
|
|
58
|
+
TASKS_STATE="${NAO_TASKS_STATE:-docs/tasks-state.md}"
|
|
46
59
|
|
|
47
60
|
log() { printf '\033[1;32m[fleet]\033[0m %s\n' "$*"; }
|
|
48
61
|
warn() { printf '\033[1;33m[fleet]\033[0m %s\n' "$*" >&2; }
|
|
@@ -135,6 +148,43 @@ check_cross_refs() {
|
|
|
135
148
|
return $rc
|
|
136
149
|
}
|
|
137
150
|
|
|
151
|
+
# .agents/** 文本文件 EOL 契约:必须全为 LF(出现 CR 即 fail,列出文件)
|
|
152
|
+
# 排除 .nao-obsolete/(update 的旧版备份,EOL 不作为契约)
|
|
153
|
+
check_eol() {
|
|
154
|
+
local rc=0 n=0 f
|
|
155
|
+
local -a bad=()
|
|
156
|
+
while IFS= read -r -d '' f; do
|
|
157
|
+
n=$((n + 1))
|
|
158
|
+
grep -Iq $'\r' "$f" && bad+=("${f#"$SKILLS_DIR"/}")
|
|
159
|
+
done < <(find "$SKILLS_DIR/.agents" -type f ! -path '*/.nao-obsolete/*' -print0)
|
|
160
|
+
if (( ${#bad[@]} == 0 )); then
|
|
161
|
+
printf ' ✓ %d 个文本文件全 LF\n' "$n"
|
|
162
|
+
else
|
|
163
|
+
rc=1
|
|
164
|
+
printf ' ✗ %d/%d 个文件含 CR(须转 LF;CRLF 会破坏 fleet/awk 解析):\n' "${#bad[@]}" "$n"
|
|
165
|
+
printf ' %s\n' "${bad[@]}"
|
|
166
|
+
fi
|
|
167
|
+
return $rc
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
# roles.yaml 缩进契约:角色 id 2 空格 / 字段 4 空格 / 禁 Tab(与 load_manifest 解析器同契约,独立报行号)
|
|
171
|
+
check_roles_indent() {
|
|
172
|
+
awk '
|
|
173
|
+
BEGIN { in_roles=0; cur=0; bad=0 }
|
|
174
|
+
/^[[:space:]]*$/ { next }
|
|
175
|
+
/^[[:space:]]*#/ { next }
|
|
176
|
+
/\t/ { printf " ✗ 行 %d: 含 Tab(禁止 Tab 缩进)\n", FNR; bad=1; next }
|
|
177
|
+
!in_roles {
|
|
178
|
+
if ($0 ~ /^roles:[[:space:]]*$/) { in_roles=1; next }
|
|
179
|
+
printf " ✗ 行 %d: 顶层仅允许 roles:(得到: %s)\n", FNR, $0; bad=1; next
|
|
180
|
+
}
|
|
181
|
+
$0 ~ /^ [^ ][^:]*:[[:space:]]*$/ { cur=1; next }
|
|
182
|
+
cur && $0 ~ /^ [a-z_]+:[[:space:]]/ { next }
|
|
183
|
+
{ printf " ✗ 行 %d: 缩进/位置非法(角色 id 须 2 空格、字段须 4 空格且挂在角色下): %s\n", FNR, $0; bad=1 }
|
|
184
|
+
END { exit (bad ? 1 : 0) }
|
|
185
|
+
' "$MANIFEST"
|
|
186
|
+
}
|
|
187
|
+
|
|
138
188
|
# CodeGraph 索引健康(ensure 拉起前 / check 用;缺失或过期仅 warn,不阻塞)
|
|
139
189
|
check_codegraph() {
|
|
140
190
|
local repo="$1" name st
|
|
@@ -173,7 +223,55 @@ detect_host() {
|
|
|
173
223
|
echo screen
|
|
174
224
|
}
|
|
175
225
|
|
|
176
|
-
|
|
226
|
+
# 该会话名对应的真实 pi 进程 PID(过滤 shell/tmux 等误匹配,避免误命中宿主命令行、误杀包装进程)
|
|
227
|
+
pi_pids_for() {
|
|
228
|
+
local pid comm
|
|
229
|
+
pgrep -f -- "pi[[:space:]].*--name $1([[:space:]]|$)" 2>/dev/null | while IFS= read -r pid; do
|
|
230
|
+
comm="$(ps -o comm= -p "$pid" 2>/dev/null | tr -d ' ')"
|
|
231
|
+
case "$comm" in
|
|
232
|
+
bash|sh|dash|zsh|fish|tmux|screen|sudo|env) continue ;;
|
|
233
|
+
esac
|
|
234
|
+
printf '%s\n' "$pid"
|
|
235
|
+
done
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
running() { [[ -n "$(pi_pids_for "$1")" ]]; }
|
|
239
|
+
|
|
240
|
+
# 定位会话所在 tmux pane(靠启动命令里的 --name;边界避免 rd-be 误命中 rd-be-T1)
|
|
241
|
+
find_pane_for() {
|
|
242
|
+
command -v tmux >/dev/null 2>&1 || return 0
|
|
243
|
+
tmux list-panes -a -F '#{pane_id} #{pane_start_command}' 2>/dev/null \
|
|
244
|
+
| awk -v n="$1" '$0 ~ ("--name[ =]" n "([^A-Za-z0-9_-]|$)") { print $1; exit }'
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
# 该 pane 末 3 行是否显示在跑 turn(启发式:状态行标记;回执/产物以 PM 核对清单为准)
|
|
248
|
+
pane_busy() {
|
|
249
|
+
local pane="$1" tail3
|
|
250
|
+
tail3="$(tmux capture-pane -p -t "$pane" 2>/dev/null | grep -v '^[[:space:]]*$' | tail -3)"
|
|
251
|
+
[[ -n "$tail3" ]] || return 1
|
|
252
|
+
grep -qE "$CLOSE_BUSY_PATTERN" <<< "$tail3"
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
# 任务在 tasks-state 的归处:active(进行态)/ closed(已验收·已归档)/ absent(无记录)/ nofile
|
|
256
|
+
task_state_class() {
|
|
257
|
+
local id="$1"
|
|
258
|
+
[[ -f "$TASKS_STATE" ]] || { echo nofile; return; }
|
|
259
|
+
awk -v id="$id" '
|
|
260
|
+
/^## / { sec=$0; sub(/^##[[:space:]]*/, "", sec); next }
|
|
261
|
+
{
|
|
262
|
+
n=split($0, cells, "|")
|
|
263
|
+
for (i=1;i<=n;i++) {
|
|
264
|
+
c=cells[i]; gsub(/^[[:space:]]+|[[:space:]]+$/, "", c)
|
|
265
|
+
if (c==id) { hit=sec; break }
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
END {
|
|
269
|
+
if (hit=="") { print "absent"; exit }
|
|
270
|
+
if (hit ~ /已验收|已归档/) { print "closed"; exit }
|
|
271
|
+
print "active"
|
|
272
|
+
}
|
|
273
|
+
' "$TASKS_STATE"
|
|
274
|
+
}
|
|
177
275
|
|
|
178
276
|
# 白名单命中返回 0,否则返回 1;未设白名单=放行
|
|
179
277
|
check_model() {
|
|
@@ -296,7 +394,7 @@ apply_tmux_layout() {
|
|
|
296
394
|
(( ${#pane_ids[@]} >= 1 )) || return 0
|
|
297
395
|
|
|
298
396
|
local pct="$TMUX_MAIN_WIDTH"
|
|
299
|
-
[[ "$pct" =~ ^[0-9]+$ ]] || pct=
|
|
397
|
+
[[ "$pct" =~ ^[0-9]+$ ]] || pct="$TMUX_MAIN_WIDTH_DEFAULT"
|
|
300
398
|
(( pct < 10 )) && pct=10
|
|
301
399
|
(( pct > 90 )) && pct=90
|
|
302
400
|
|
|
@@ -375,7 +473,20 @@ cmd_check() {
|
|
|
375
473
|
else printf ' ✗ 缺失: %s\n' "$d"; rc=1; fi
|
|
376
474
|
done
|
|
377
475
|
|
|
476
|
+
echo "== 文本契约(roles.yaml 缩进 2/4 · .agents/** EOL 全 LF)=="
|
|
477
|
+
if [[ -f "$MANIFEST" ]]; then
|
|
478
|
+
if check_roles_indent; then
|
|
479
|
+
printf ' ✓ roles.yaml 缩进契约(角色 id 2 空格 / 字段 4 空格 / 无 Tab)\n'
|
|
480
|
+
else
|
|
481
|
+
rc=1
|
|
482
|
+
fi
|
|
483
|
+
else
|
|
484
|
+
printf ' ✗ roles.yaml 缺失: %s\n' "$MANIFEST"; rc=1
|
|
485
|
+
fi
|
|
486
|
+
check_eol || rc=1
|
|
487
|
+
|
|
378
488
|
echo "== 角色清单 roles.yaml =="
|
|
489
|
+
load_manifest
|
|
379
490
|
printf ' ✓ 解析成功,%d 个角色: %s\n' "${#ROLE_ORDER[@]}" "${ROLE_ORDER[*]}"
|
|
380
491
|
|
|
381
492
|
echo "== 常驻角色卡(阈值 ${CARD_MAX_LINES} 行,frontmatter 校验)=="
|
|
@@ -424,6 +535,33 @@ cmd_check() {
|
|
|
424
535
|
else printf ' ✗ %s 缺失\n' "$f"; rc=1; fi
|
|
425
536
|
done
|
|
426
537
|
|
|
538
|
+
echo "== PR 模板(GitHub Flow)=="
|
|
539
|
+
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
540
|
+
echo ' · 非 git 仓库(跳过)'
|
|
541
|
+
else
|
|
542
|
+
local remote_url prtpl="" cand
|
|
543
|
+
remote_url="$(git config --get remote.origin.url 2>/dev/null || true)"
|
|
544
|
+
if [[ -z "$remote_url" ]]; then
|
|
545
|
+
echo ' · 无 remote.origin(跳过)'
|
|
546
|
+
elif [[ "$remote_url" != *github.com* ]]; then
|
|
547
|
+
echo ' · 远端非 GitHub(跳过;PR 模板不适用)'
|
|
548
|
+
else
|
|
549
|
+
shopt -s nullglob
|
|
550
|
+
for cand in .github/pull_request_template.md .github/PULL_REQUEST_TEMPLATE.md \
|
|
551
|
+
.github/PULL_REQUEST_TEMPLATE \
|
|
552
|
+
pull_request_template.md PULL_REQUEST_TEMPLATE.md \
|
|
553
|
+
docs/pull_request_template.md docs/PULL_REQUEST_TEMPLATE.md; do
|
|
554
|
+
[[ -e "$cand" ]] && { prtpl="$cand"; break; }
|
|
555
|
+
done
|
|
556
|
+
shopt -u nullglob
|
|
557
|
+
if [[ -n "$prtpl" ]]; then
|
|
558
|
+
printf ' ✓ PR 模板已就位:%s\n' "$prtpl"
|
|
559
|
+
else
|
|
560
|
+
echo ' ! 未找到 PR 模板(GitHub 远端):PM 立项时从 .agents/templates/github/pull_request_template.md.example 复制为 .github/pull_request_template.md(见 skills/github-flow.md)'
|
|
561
|
+
fi
|
|
562
|
+
fi
|
|
563
|
+
fi
|
|
564
|
+
|
|
427
565
|
echo "== 按需技能 =="
|
|
428
566
|
if [[ -d "$SKILLS_SUB" ]]; then
|
|
429
567
|
shopt -s nullglob
|
|
@@ -453,6 +591,18 @@ cmd_check() {
|
|
|
453
591
|
echo ' · codegraph 未安装(回退 grep 属预期行为)'
|
|
454
592
|
fi
|
|
455
593
|
|
|
594
|
+
echo "== pi 插件(业界调研能力)=="
|
|
595
|
+
local piset="$HOME/.pi/agent/settings.json"
|
|
596
|
+
if [[ -f "$piset" ]]; then
|
|
597
|
+
if grep -q 'pi-web-access' "$piset" 2>/dev/null; then
|
|
598
|
+
echo ' · pi-web-access ✓ 已装(PM/arch 业界调研可用)'
|
|
599
|
+
else
|
|
600
|
+
echo ' ! pi-web-access 未装(业界调研不可用;nao-skill plugins install web-access)'
|
|
601
|
+
fi
|
|
602
|
+
else
|
|
603
|
+
echo " · 未找到 $piset(跳过插件检测)"
|
|
604
|
+
fi
|
|
605
|
+
|
|
456
606
|
echo "== 模型白名单 =="
|
|
457
607
|
if [[ -z "$MODEL_WHITELIST" ]]; then
|
|
458
608
|
echo " (未设置 NAO_MODEL_WHITELIST,-m 不校验)"
|
|
@@ -498,7 +648,7 @@ cmd_check() {
|
|
|
498
648
|
if [[ "$TMUX_MAIN_WIDTH" =~ ^[0-9]+$ ]] && (( TMUX_MAIN_WIDTH >= 10 && TMUX_MAIN_WIDTH <= 90 )); then
|
|
499
649
|
printf ' ✓ NAO_TMUX_MAIN_WIDTH=%-3s%% 合法\n' "$TMUX_MAIN_WIDTH"
|
|
500
650
|
else
|
|
501
|
-
printf ' ! NAO_TMUX_MAIN_WIDTH=%-3s 非法(10..90
|
|
651
|
+
printf ' ! NAO_TMUX_MAIN_WIDTH=%-3s 非法(10..90):越界夹取到 10/90,非数字回退 %s\n' "$TMUX_MAIN_WIDTH" "$TMUX_MAIN_WIDTH_DEFAULT"
|
|
502
652
|
fi
|
|
503
653
|
fi
|
|
504
654
|
|
|
@@ -507,7 +657,7 @@ cmd_check() {
|
|
|
507
657
|
rc=1
|
|
508
658
|
fi
|
|
509
659
|
|
|
510
|
-
|
|
660
|
+
return $rc
|
|
511
661
|
}
|
|
512
662
|
|
|
513
663
|
# ---------------------------------------------------------------------------
|
|
@@ -522,11 +672,89 @@ cmd_status() {
|
|
|
522
672
|
fi
|
|
523
673
|
done
|
|
524
674
|
echo "== 任务派生会话(--task 拉起,如 rd-be-T1)=="
|
|
525
|
-
local found=0 line
|
|
675
|
+
local found=0 line dname role id cls tag
|
|
526
676
|
while IFS= read -r line; do
|
|
527
|
-
[[ -n "$line" ]]
|
|
677
|
+
[[ -n "$line" ]] || continue
|
|
678
|
+
dname="$(awk '{for(i=1;i<=NF;i++) if($i=="--name") {print $(i+1); exit}}' <<< "$line")"
|
|
679
|
+
[[ -n "$dname" ]] || continue
|
|
680
|
+
id=""; tag=""
|
|
681
|
+
for role in "${ROLE_ORDER[@]}"; do [[ "$dname" == "$role-"* ]] && { id="${dname#"$role"-}"; break; }; done
|
|
682
|
+
if [[ -n "$id" ]]; then
|
|
683
|
+
cls="$(task_state_class "$id")"
|
|
684
|
+
case "$cls" in
|
|
685
|
+
closed) tag=" ! 残留($id 已归档)→ close --task $id ${dname%-*}" ;;
|
|
686
|
+
absent) tag=" ! 残留(tasks-state 无 $id 记录)→ 核对后 close --task $id ${dname%-*}" ;;
|
|
687
|
+
active) tag=" · $id 进行态" ;;
|
|
688
|
+
esac
|
|
689
|
+
fi
|
|
690
|
+
printf ' · %s%s\n' "$dname" "$tag"
|
|
691
|
+
found=1
|
|
528
692
|
done < <(pgrep -af "pi[[:space:]].*--name (pm|arch-designer|rd-fe|rd-be|qa)-[A-Za-z0-9_-]+" 2>/dev/null | head -10)
|
|
529
693
|
(( found )) || echo ' (无)'
|
|
694
|
+
[[ -f "$TASKS_STATE" ]] || echo " ($TASKS_STATE 不存在,残留判定已跳过)"
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
# ---------------------------------------------------------------------------
|
|
698
|
+
# 回收已完成会话:闸门(tasks-state 已推进 + 无在跑 turn)+ 落地(pane/会话/screen/进程)
|
|
699
|
+
cmd_close() {
|
|
700
|
+
local force="$1" task="$2" target="$3"
|
|
701
|
+
[[ -n "$target" ]] || die "close 需要目标:角色别名或派生会话名(如 rd-be / rd-be-T1)"
|
|
702
|
+
local name repo cls bus pane pids r ok
|
|
703
|
+
if [[ -n "$task" ]]; then
|
|
704
|
+
resolve_role "$target"
|
|
705
|
+
name="${NAME}-${task}"
|
|
706
|
+
repo="${ROLE_WS[$NAME]:-$PWD}"
|
|
707
|
+
# 闸门①:tasks-state 未推进 → 拒绝(验收未过需原会话返工,回收会丢上下文)
|
|
708
|
+
cls="$(task_state_class "$task")"
|
|
709
|
+
if [[ "$cls" == "active" && "$force" != true ]]; then
|
|
710
|
+
warn "拒绝回收 $name:tasks-state 中 $task 仍在进行态(未验收/未归档)"
|
|
711
|
+
warn " 先把 $task 移入「已验收/已归档」再回收(或 --force:将丢失打回返工所需上下文)"
|
|
712
|
+
return 1
|
|
713
|
+
fi
|
|
714
|
+
[[ "$cls" == "absent" ]] && warn "tasks-state 无 $task 记录(仅按会话名回收)"
|
|
715
|
+
[[ "$cls" == "nofile" ]] && log "未启用 $TASKS_STATE(跳过状态闸门)"
|
|
716
|
+
elif [[ -n "${ALIAS_ROLE[$target]:-}" ]]; then
|
|
717
|
+
name="${ALIAS_ROLE[$target]}"; repo="${ROLE_WS[$name]:-$PWD}"
|
|
718
|
+
warn "常驻会话 $name:任务闭环后应 ensure --force 重开,而非回收(仅在本批不再需要该角色时回收)"
|
|
719
|
+
else
|
|
720
|
+
name="$target"
|
|
721
|
+
[[ "$name" =~ ^[A-Za-z0-9][A-Za-z0-9_-]*$ ]] || die "非法会话名: $name"
|
|
722
|
+
# 非别名的目标必须是 <已知角色>-<编号>,否则视为拼错(防静默 no-op)
|
|
723
|
+
ok=0
|
|
724
|
+
for r in "${ROLE_ORDER[@]}"; do [[ "$name" == "$r-"* ]] && ok=1; done
|
|
725
|
+
(( ok )) || die "未知角色或派生会话名: $name(可用: ${ROLE_ORDER[*]};派生名形如 rd-be-T1)"
|
|
726
|
+
repo="$PWD"
|
|
727
|
+
fi
|
|
728
|
+
|
|
729
|
+
running "$name" || { log "$name 未运行(无需回收)"; return 0; }
|
|
730
|
+
|
|
731
|
+
# 闸门②:在跑 turn(tmux 可判;非 tmux 宿主无法判 → 需 --force)
|
|
732
|
+
pane="$(find_pane_for "$name")"
|
|
733
|
+
if [[ -n "$pane" ]]; then
|
|
734
|
+
pane_busy "$pane" && bus="tmux pane $pane 末行显示在跑 turn"
|
|
735
|
+
else
|
|
736
|
+
bus="非 tmux 宿主,无法确认是否在跑 turn"
|
|
737
|
+
fi
|
|
738
|
+
if [[ -n "$bus" && "$force" != true ]]; then
|
|
739
|
+
warn "拒绝回收 $name:$bus"
|
|
740
|
+
warn " 等它停;回执与产物核对见 checklists/pm.md「会话回收 / 收窗核对」(或 --force 强制)"
|
|
741
|
+
return 1
|
|
742
|
+
fi
|
|
743
|
+
|
|
744
|
+
if [[ -n "$pane" ]] && tmux kill-pane -t "$pane" 2>/dev/null; then
|
|
745
|
+
log "已回收 $name(tmux pane $pane)@ $repo"; return 0
|
|
746
|
+
fi
|
|
747
|
+
if command -v tmux >/dev/null 2>&1 && tmux has-session -t "nao-$name" 2>/dev/null && tmux kill-session -t "nao-$name" 2>/dev/null; then
|
|
748
|
+
log "已回收 $name(tmux 会话 nao-$name)@ $repo"; return 0
|
|
749
|
+
fi
|
|
750
|
+
if command -v screen >/dev/null 2>&1 && screen -ls 2>/dev/null | grep -q "nao-$name" && screen -S "nao-$name" -X quit 2>/dev/null; then
|
|
751
|
+
log "已回收 $name(screen 会话 nao-$name)@ $repo"; return 0
|
|
752
|
+
fi
|
|
753
|
+
pids="$(pi_pids_for "$name" | tr '\n' ' ')"
|
|
754
|
+
if [[ -n "$pids" ]] && kill $pids 2>/dev/null; then
|
|
755
|
+
log "已回收 $name(结束进程: $pids)@ $repo"; return 0
|
|
756
|
+
fi
|
|
757
|
+
warn "未找到 $name 的 pane/会话/进程(可能刚好退出)"; return 1
|
|
530
758
|
}
|
|
531
759
|
|
|
532
760
|
# ---------------------------------------------------------------------------
|
|
@@ -568,11 +796,12 @@ usage() {
|
|
|
568
796
|
}
|
|
569
797
|
|
|
570
798
|
# ---- 入口 ----
|
|
571
|
-
CMD=""; FORCE=false; STRICT=false; MODEL=""; TARGETS=()
|
|
799
|
+
CMD=""; FORCE=false; STRICT=false; MODEL=""; TASK=""; TARGETS=()
|
|
572
800
|
while [[ $# -gt 0 ]]; do
|
|
573
801
|
case "$1" in
|
|
574
802
|
check) CMD="check"; shift ;;
|
|
575
803
|
status) CMD="status"; shift ;;
|
|
804
|
+
close) CMD="close"; shift ;;
|
|
576
805
|
ensure) CMD="ensure"; shift ;;
|
|
577
806
|
-m|--model)
|
|
578
807
|
MODEL="${2:-}"
|
|
@@ -582,18 +811,45 @@ while [[ $# -gt 0 ]]; do
|
|
|
582
811
|
--force) FORCE=true; shift ;;
|
|
583
812
|
--task) TASK="${2:-}"; [[ -n "$TASK" ]] || die "--task 需要任务编号(如 T1)"; [[ "$TASK" =~ ^[A-Za-z0-9_-]+$ ]] || die "--task 非法: $TASK(仅字母/数字/-/_)"; shift 2 ;;
|
|
584
813
|
--strict) STRICT=true; shift ;;
|
|
814
|
+
-v|--verbose) VERBOSE=true; shift ;;
|
|
585
815
|
-h|--help) usage ;;
|
|
586
816
|
*) TARGETS+=("$1"); shift ;;
|
|
587
817
|
esac
|
|
588
818
|
done
|
|
589
819
|
|
|
590
820
|
case "$CMD" in
|
|
591
|
-
check
|
|
821
|
+
check) : ;; # cmd_check 自行先做文本契约体检,再 load_manifest(缩进违例时也能先出报告)
|
|
822
|
+
ensure|status|close) load_manifest ;;
|
|
592
823
|
esac
|
|
593
824
|
|
|
594
825
|
case "$CMD" in
|
|
595
|
-
check)
|
|
826
|
+
check)
|
|
827
|
+
# 默认单行摘要(派发前自检只需 exit code + 计数;完整报告 30+ 行不进 PM 上下文);-v 或失败时展开
|
|
828
|
+
# 子 shell 隔离:cmd_check 内部 die/exit 不得吞掉报告(计数从报告解析,不靠子 shell 内变量)
|
|
829
|
+
REPFILE="$(mktemp)"
|
|
830
|
+
set +e
|
|
831
|
+
( cmd_check "$STRICT" ) >"$REPFILE" 2>&1
|
|
832
|
+
CHECK_RC=$?
|
|
833
|
+
set -e
|
|
834
|
+
if (( CHECK_RC == 0 )) && ! $VERBOSE; then
|
|
835
|
+
WARNS="$(grep -c '^ !' "$REPFILE" || true)"
|
|
836
|
+
ROLES_N="$(grep -oE '解析成功,[0-9]+ 个角色' "$REPFILE" | grep -oE '[0-9]+' | head -1 || true)"
|
|
837
|
+
FILES_N="$(grep -oE '[0-9]+ 个文本文件全 LF' "$REPFILE" | grep -oE '^[0-9]+' | head -1 || true)"
|
|
838
|
+
if (( WARNS > 0 )); then
|
|
839
|
+
printf 'check: OK · roles=%s · files=%s · layout=%s · warn=%d(-v 看详情)\n' \
|
|
840
|
+
"${ROLES_N:-?}" "${FILES_N:-?}" "$TMUX_LAYOUT" "$WARNS"
|
|
841
|
+
else
|
|
842
|
+
printf 'check: OK · roles=%s · files=%s · layout=%s\n' \
|
|
843
|
+
"${ROLES_N:-?}" "${FILES_N:-?}" "$TMUX_LAYOUT"
|
|
844
|
+
fi
|
|
845
|
+
else
|
|
846
|
+
cat "$REPFILE"
|
|
847
|
+
fi
|
|
848
|
+
rm -f "$REPFILE"
|
|
849
|
+
exit $CHECK_RC
|
|
850
|
+
;;
|
|
596
851
|
status) cmd_status ;;
|
|
852
|
+
close) cmd_close "$FORCE" "$TASK" "${TARGETS[0]:-}" ;;
|
|
597
853
|
ensure) cmd_ensure "$FORCE" "$MODEL" "$TASK" "${TARGETS[@]}" ;;
|
|
598
854
|
*) usage ;;
|
|
599
855
|
esac
|