feat(huanxi): 拆为个人端 / 管理端两个插件,技能全面对齐寰汐 v2

寰汐 v2 上线双 MCP(个人端 /mcp/ 与管理端 /admin-mcp/),两条端点是**两条独立的
信任边界**——Token 前缀不同、工具集不同、视角不同(管理端看全量、个人端只看我参与的)。
一个插件塞两套会让普通员工的客户端里出现他根本调不动的管理工具,因此拆成两个插件,
按角色各装各的。

## huanxi(个人端,全体员工)

7 个技能:shared / report / leader / task / issue / meeting / org

- 新增 `issue` `meeting`——v2 的议题域与会议域(M6v2 会议×议题解耦后的产物)
- **删除 `weekly`**——v2 没有 weekly_report 表,周报已并入报告体系
- 各技能重写为**流水线定义**而非使用说明:写明工具调用顺序、ID 在步骤间怎么传、
  人工确认节点落在哪。「禁止自动提交」这条 v1 已验证的硬约束保留
- 删掉 `references/` 拆分文件——v2 技能自包含

## huanxi-admin(管理端,仅后台管理员)

4 个技能:admin-shared / admin-report / admin-module / admin-ops

MCP server key 取 `huanxi-admin`(与个人端的 `huanxi` 不同名),否则两插件并存时
会键冲突。

## 缓存目录按信任边界隔离

`~/.claude/huanxi-cache/` 下分 `personal/` `admin/` `dict/`:前两者视角不同,
混用会越权展示或数据错乱;`dict/` 与身份无关可共享。业务数据(任务/日报/会议/议题)
**显式声明不缓存**——v1 没写这条,Agent 会自行决定缓存然后拿到陈旧数据。

## 源码单一真相不在本仓库

技能源码在寰汐仓库 `skills/`,与 MCP docstring 同仓库同 commit——签名一改,
技能与工具在同一次改动里更新,从结构上消除跨仓库漂移(本仓库记忆
`feedback_plugin_dev.md` 记录的 4 类漂移覆盖全部 6 个 v1 技能,正是这个病)。
本仓库退化为**分发壳**,只接收 `python skills/sync_marketplace.py` 的产物,不手工编辑。

寰汐侧有 CI 守卫:技能里出现的每个工具名必须存在于实际注册表、个人端技能不得
指导调用管理端独有工具、不得硬编码状态字面量。

## 本分支不合 main

插件配置的域名此刻跑的还是 v1,合进 main 会通过自动更新推给已安装用户,
他们的技能会去调 v1 上不存在的工具。合并前置条件写在 CLAUDE.md「已发布插件」节。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Vyeia9k43dVaUFNLo8Lny
This commit is contained in:
SkyJourney
2026-08-06 18:56:55 +08:00
co-authored by Claude Opus 5
parent d7aaaf8224
commit 4ce9a92013
24 changed files with 744 additions and 1103 deletions
+54 -66
View File
@@ -1,102 +1,90 @@
---
name: huanxi-task
description: "寰汐任务管理:创建任务(+create)、更新任务状态/进度(+update)、查看任务看板(+board)、设置执行人(+assign)。当用户说"创建任务"、"新建任务"、"更新任务"、"看任务板"、"任务分配"时触发。"
description: "寰汐任务管理:查任务、建任务、改状态进度、分配执行人、认领。当用户说「我有什么任务」「建个任务」「把这个标成完成」「派给谁」时使用。"
---
# 寰汐任务管理
**前置条件:先 Read `../huanxi-shared/SKILL.md`(缓存规则 + TTL 策略)**
> ⚠️ **工具调用规范**:执行任何 `mcp__huanxi__*` 调用前,以工具实际注册的参数名和类型为准,本文档示例仅供工作流引导(见 shared 全局约定第 6 条)。
**前置:先读 `huanxi-shared`(尤其「状态是可配置的两层模型」一节)。**
---
## Shortcuts
## ID 传递链
| 指令 | 说明 |
|------|------|
| [`+create`](references/task-create.md) | 创建新任务(引导式填写) |
| `+update` | 更新任务状态/进度/截止日 |
| [`+board`](references/task-kanban.md) | 查看我的任务看板 |
| `+assign` | 设置/变更任务执行人 |
| `+get` | 查看单个任务详情 |
任务操作几乎都是这条链,**中间结果要展示给用户**,不要一路闷头做到底:
---
## +create:创建任务
详见 [references/task-create.md](references/task-create.md)
**快速概览:**
```
1. 确定所属模块(从 modules.json 缓存解析名字 → ID
2. 收集任务信息(标题、描述、截止日、优先级)
3. mcp__huanxi__task_create(module_id, title, ...)
4. [可选] 设置执行人 → mcp__huanxi__task_set_assignees(task_id, [user_id])
module_query() → module_id
task_query(module_ids=[...]) → task_id[] ← 展示给用户看
↓ ⏸ 用户指明改哪些
task_update(updates=[{id, ...}])
```
---
## +update:更新任务
> ⚠️ 参数细节以 MCP `task_update` 的 docstring 为准,本节仅做工作流引导。
##
```
Step 1: 确认任务 ID
→ 若用户已提供 task_id:直接使用
→ 若用户描述了任务名称:
a. mcp__huanxi__task_list_mine() 获取我的任务列表
b. 按标题关键词模糊匹配,列出候选任务供用户选择
c. 仍未找到(可能属于他人或已归档)→ 告知用户提供精确 task_id
Step 2: 展示当前任务状态,引导用户填写要修改的字段
Step 3: mcp__huanxi__task_update(task_id, {
title? : "新标题",
description? : "新描述",
progress? : 80, ← 0-100 整数
progress_before? : 60, ← 修改 progress 时必传当前值(乐观锁,防并发覆盖)
status? : "not_started" | "in_progress" | "done" | "cancelled" | "on_hold",
end_date? : "YYYY-MM-DD", ← 字段名是 end_date,不是 due_date
priority? : "low" | "medium" | "high" | "critical"
})
Step 4: 告知更新结果
task_query(scope="mine") 我负责执行的(默认)
task_query(scope="all", module_ids=[...]) 某几个模块的全部任务
task_query(q="关键词") 标题模糊搜
task_get(task_ids=[...]) 详情:描述 + 层级路径
```
⚠️ **历史踩坑**
- 字段名 `due_date` 错误,后端为 `end_date`
- 状态值 `todo` 错误,后端为 `not_started`
- priority 缺 `critical`,没有 `urgent`
- 改 progress 不传 `progress_before` 会 409 冲突
过滤维度都收列表,一次查多个模块比循环调用好。
---
## +assign:设置执行人
## 改状态与进度
**先 `dict_get` 取 task 类型的状态选项**,拿到 `status_option_id` 再传:
```
Step 1: 确认任务 ID
Step 2: 解析执行人名字 → user_id(查 users.json 缓存)
→ 详见 huanxi-org references/resolve-ids.md
Step 3: mcp__huanxi__task_set_assignees(task_id, assignee_ids=[user_id, ...])
(此操作幂等:传完整列表,不是追加)
Step 4: 告知设置结果
dict_get(kinds=["status_options"])
→ 筛 entity_type == "task"
→ 按 category 找到目标状态(not_started/in_progress/completed/cancelled
→ 取它的 id
task_update(updates=[{id: 任务id, status_option_id: 状态id}])
```
状态与进度**有联动,只传一个就够**:进度设到 100 会自动转完成;已完成的任务把进度
调低会自动回落进行中。两个都传等于重复表达同一个意思。
**非叶子任务不能直接设进度**——返回里 `progress_readonly` 为 true 的那些,进度是子任务
聚合出来的,硬设会被拒绝。要推进它,去改它的子任务。
---
## +get:查看任务详情
## 建任务
```
Step 1: mcp__huanxi__task_get(task_id)
Step 2: 展示完整任务信息(标题/描述/状态/进度/执行人/截止日/评论数)
task_create(module_id=..., tasks=[{title, description?, priority?, end_date?,
parent_id?, milestone_id?, assignee_ids?}])
```
- 建子任务传 `parent_id`,**最多三级**(任务 / 子任务 / 孙任务)
- 需要是该模块的成员或负责人
- `priority` 的取值以工具说明为准——**不要凭直觉写**,这个字段有 DB 级约束,写错直接报错
---
## 关键约束
## 执行人
- **任务创建后不自动认领**`task_create` 不会自动设置执行人,需要单独调用 `task_set_assignees`
- **执行人是完整列表**`task_set_assignees` 传入的是完整执行人 ID 列表(幂等替换),不是追加
- **progress 是整数**:0-100 的整数,不是小数或百分比字符串
- **模块创建权限**:用户必须是模块成员(任意角色)才能在该模块创建任务,否则返回 403
```
task_set_assignees(task_id=..., user_ids=[...]) 整组覆盖,传空即清空
task_claim(task_ids=[...], claim=true/false) 认领 / 取消认领(只动自己)
```
**`task_set_assignees` 是替换不是追加。** 想加一个人,要先 `task_get` 拿到现有名单,
把新人拼进去再整组传回——直接传一个人会把其余执行人全部踢掉。这是最容易出错的地方,
覆盖前把「改完会变成谁」说给用户听。
被指派的人若不是模块成员,会自动加入该模块;新增执行人会收到飞书通知。
---
## 不在工具里的操作
删除任务、跨模块转移任务**不在 MCP**,请引导用户去网页端——这两个动作作用于整棵子树
且不可逆,需要看清楚影响范围再点。