返回 Skills 目录
larksuite/meegle-cli已通过检查

SKILL DETAIL

meegle

larksuite/meegle-cli/meegle

本技能通过 Meegle CLI 操作飞书项目数据,覆盖工作项、工作流、视图、待办、排期、评论、交付物、资源库、WBS 计划表等多个领域。支持使用 MQL 查询工作项,创建、更新、批量获取工作项,完成节点流转或状态流转,查看视图下的工作项列表,查询个人待办/已办/逾期/本周待办,获取人员排期与工作量明细,以及添加评论、管理交付物、创建资源实例、编辑和发布 WBS 计划表等。 使用前需完成授权流程,并参考 CLI 使用指南了解命令结构。各命令的详细参数和示例见对应参考文档。输出语言跟随用户输入语言,默认中文。

安装量 · 127查看来源

Installation

npx skills add https://github.com/larksuite/meegle-cli --skill meegle

技能文件

SKILL.md

最近同步 · 2026年9月20日

references/ai-handoff.md›
# AI 助手续处理细则

本文件用于在 Meegle CLI 已完成可执行部分,或明确无法执行用户请求后,判断是否提供 AI 助手兜底链接。链接是补充入口,不替代 CLI 结果。

## 适用范围

| 场景 | 示例 | CLI 先做什么 | 续处理目标 |
| --- | --- | --- | --- |
| 分析、追溯、总结、诊断、研判 | “这些需求为什么卡住了” | 查询状态分布和操作记录 | 分析共性原因、风险与建议 |
| 排期或工时分析 | “这周我们组是不是超载了” | 能查询则先返回排期明细 | 分析负载与冲突 |
| 历史变更或延期追溯 | “这个需求为什么拖这么久” | 查询操作记录 | 总结关键变更与延期原因 |
| 周报或待办总结 | “帮我写个周报” | 查询待办和必要的操作记录 | 生成结构化周报 |
| CLI 不支持的工作项操作 | “把这几个需求冻结” | 明确 CLI 当前不能执行 | 让 AI 助手继续完成操作 |
| 度量图表生成或修改 | “这张图加个维度” | 能查询则先返回现状 | 让 AI 助手继续修改图表 |

以下情况不提供链接:纯数据查询、导出或列举且没有分析意图;单实例详情查看;用户明确只要数据;用户已拒绝;本会话已主动引导过一次。

## 处理流程

1. **先完成 CLI 范围内的工作**:有可执行的查询或操作时,先返回真实结果。请求完全不受 CLI 支持时,说明能力边界后继续判断,不要为了满足流程而伪造查询。
2. **检查可用性**:调用 `ai-handoff availability`。`available=false` 时停止;如果原请求只能续处理,可简短说明入口当前不可用。成功结果按 profile 缓存约一小时,同一会话不重复预检;最终创建仍由服务端重新校验。
3. **遵守模式**:
   - `auto`:符合适用范围时继续生成链接;
   - `ask`:先问用户是否要在 AI 助手中继续。得到明确同意前不得调用 `ai-handoff create-link`;同意后可直接创建,无需重复预检;
   - `off`:停止,不提示用户修改偏好。
4. **构造 query**:只描述 AI 助手要继续完成的增量任务,遵循下方规则。
5. **构造关联上下文**:只传恢复业务对象所需的 ID 指针,遵循下方类型和降级策略。
6. **创建链接**:调用 `ai-handoff create-link`。只有返回 `available=true` 且包含 `url` 时才附链接;返回 `available=false` 时按停止信号处理。不要硬编码域名,CLI 会按当前登录环境归一化 host。
7. **附在回复末尾**:说明链接能继续完成什么,例如:`如需继续分析卡点原因和处理建议,可在 AI 助手中继续:<url>`。不得暗示 CLI 查询结果集已完整传入。

用户拒绝后,本会话不再提示。用户明确要求持久关闭或重新开启时,分别调用 `preference handoff off` 或 `preference handoff auto`;只有用户明确要求“先询问”模式时才调用 `preference handoff ask`。

## query 构造规则

query 写成用户可直接理解的自然语言,不暴露 CLI、MQL、命令或内部编排过程。自然包含:

- 续处理目标;
- 对象范围和筛选语义,如空间、工作项类型、状态、负责人、时间范围;
- 已观察到的现象或当前状态摘要;
- 希望 AI 助手继续完成的分析、建议或操作。

关联上下文只提供 ID 指针,不包含 CLI 完整结果。上下文粒度不足时,query 必须补充“范围指纹”:对象类型、关键状态、负责人、时间范围与共性特征。不要写入完整对象清单,也不要把分页结果数当成真实总数。

示例:

```text
分析 Meego 空间下状态为处理中且本周有更新的需求,重点判断哪些进度滞后、卡在哪个环节、有哪些风险,并给出优先关注对象和处理建议。
```

## 关联上下文策略

按以下优先级选择 payload。当前续处理策略不使用 type=2(WorkItemType)。

| 优先级 | type | 名称 | payload | 适用场景 |
| ---: | ---: | --- | --- | --- |
| 1 | 4 | View | `view.project_key`、`view.view_id`、可选 `view.work_item_type_key` | 用户提供视图链接,范围还原最稳定 |
| 2 | 3 | WorkItem | `work_item.project_key`、`work_item.work_item_type_key`、`work_item.work_item_id` | 少量明确工作项,默认不超过 3 条 |
| 3 | 1 | Project | `project.project_key` | 批量结果、跨类型或无法传更细粒度对象 |
| — | 5 | MeasureChart | `measure_chart.project_key`、`measure_chart.chart_id` | 度量图表场景 |

- 用户提供视图或工作项 URL 时,先调用 `url decode`,从结果提取 ID;不要手工拆 URL。即使后续查询因权限失败,仍可用已解析的 ID 表达用户意图。
- type=3 只用于用户直接给出 URL 或明确锚点的少量对象。超过 3 条时退化为 type=1,并在 query 中补足范围指纹。接口返回的 `limits.max_related_context_items` 只是协议上限,不是建议逐条传满。
- `--related-context` 接受严格 JSON 对象或数组。出现解析错误时,只修正一次 JSON 引号、字段名或 type/payload 对应关系;这类本地参数修正不计为链接创建重试。

工作项示例:

```bash
meegle ai-handoff create-link --query '分析这个工作项的延期原因并给出下一步建议' --related-context '{"type":3,"work_item":{"project_key":"space_key","work_item_type_key":"story","work_item_id":"123456"}}' --format json
```

## 失败与重试

- `available=false`、`mode=off`、`HANDOFF_REJECTED` 或 `LOCAL_DISABLED` 都是明确停止信号,不重试、不建议用户绕过策略。
- `ai-handoff create-link` 已内置传输层重试。任意创建失败后都不要从 Skill 再次调用,避免重复生成链接;向用户简要说明链接暂不可用即可。
- 每轮最多生成一条链接,同一会话最多主动引导一次。用户随后明确要求重新生成时,才把它视为新的显式请求。
references/api-examples.md›
# 命令调用示例

> 参数占位符(`{{xxx}}`)表示按需填入;未使用的可选参数可省略。所有命令统一加 `--format json` 以获得结构化输出。

---

## 空间域

### project search

按名称或 key 搜索:

```bash
meegle project search --project-key 空间名或key --page-num 1 --format json
```

省略 project_key 可列出当前用户可访问的空间(按最近访问排序)。

---

## 工作项域

### workitem meta-types

```bash
meegle workitem meta-types --project-key 空间key --format json
```

### workitem meta-fields

```bash
meegle workitem meta-fields --page-num 1 --project-key 空间key --work-item-type story --field-types '{{field_types}}' --field-keys '{{field_keys}}' --field-query '{{field_query}}' --format json
```

### workitem meta-roles

```bash
meegle workitem meta-roles --page-num 1 --project-key 空间key --work-item-type story --role-keys '{{role_keys}}' --role-query '{{role_query}}' --format json
```

> **MQL 查询前必须先执行**:字段/枚举/树状字段用 `workitem meta-fields` 查 key 与 options;涉及角色并行调用 `workitem meta-roles` 取 `role_id` / `role_name`。字段与角色配置由「空间 + 工作项类型」双维度决定。业务线等树状字段直接传中文时必须传叶子节点名,禁拼完整路径。

### workitem query

首查:

```bash
meegle workitem query --project-key 空间key --mql 'SELECT `work_item_id`, `name`, `work_item_status` FROM `空间key`.`story` WHERE `archiving_date` IS NULL' --format json
```

无分组翻页时 group_id 传 `"1"`:

```bash
meegle workitem query --project-key 空间key --session-id 首查返回的session_id --mql '' --group-pagination-list '[{"group_id":"1","page_num":2}]' --format json
```

有分组翻页时,group_id 从首查 `list[].group_infos[].group_id` 取:

```bash
meegle workitem query --project-key 空间key --session-id 首查返回的session_id --mql '' --group-pagination-list '[{"group_id":"分组ID","page_num":3}]' --format json
```

### workitem get

基础查询:

```bash
meegle workitem get --work-item-id 工作项ID --fields '{{fields}}' --project-key 空间key --format json
```

只取拉群方式:

```bash
meegle workitem get --work-item-id 工作项ID --fields '["group_type"]' --project-key 空间key --format json
```

全量字段分页时 fields 传 `["_all"]`。Meegle CLI 的 page_size / page_token 必须通过 `--params` 传,避免被序列化为字符串:

```bash
meegle workitem get --work-item-id 工作项ID --fields '["_all"]' --project-key 空间key --params '{"page_size":100}' --format json
```

```bash
meegle workitem get --work-item-id 工作项ID --fields '["_all"]' --project-key 空间key --params '{"page_size":100,"page_token":"<next_page_token>"}' --format json
```

### workitem create

基础创建(仅标量):

```bash
meegle workitem create --work-item-type story --fields '[{"field_key":"template","field_value":"模板ID"},{"field_key":"name","field_value":"需求标题"}]' --project-key 空间key --format json
```

创建并指定报告人/经办人(role_owners 是 stringified JSON):

```bash
meegle workitem create --work-item-type issue --fields '[{"field_key":"name","field_value":"示例缺陷"},{"field_key":"template","field_value":"模板ID"},{"field_key":"role_owners","field_value":"[{\"role\":\"reporter\",\"owners\":[\"userkey1\"]},{\"role\":\"operator\",\"owners\":[\"userkey2\"]}]"}]' --project-key 空间key --format json
```

> **`role_owners` 中 `role` 字段填 `role_id`**(`workitem meta-roles` 返回的英文 key,如 `operator` / `reporter` / `role_xxxxxx`),不是 `role_name`。系统默认 `role_id`:报告人 `reporter`、经办人 `operator`。自定义角色的 `role_id` 形式多样,**必须先调 `workitem meta-roles` 确认**。创建时可在 `fields` 传 `role_owners`;更新已有工作项角色**必须**用 `workitem update` 的 `role_operate`。

### workitem update

更新普通字段:

```bash
meegle workitem update --work-item-id 工作项ID --project-key 空间key --fields '[{"field_key":"priority","field_value":"option_id"}]' --format json
```

更新 multi-user(复合值 stringified):

```bash
meegle workitem update --work-item-id 工作项ID --project-key 空间key --fields '[{"field_key":"current_status_operator","field_value":"[\"userkey1\",\"userkey2\"]"}]' --format json
```

普通复合字段分别使用 `add` / `update` / `delete` action:

```bash
meegle workitem update --work-item-id 工作项ID --project-key 空间key --fields '[{"field_key":"复合字段key","field_value":"{\"action\":\"add\",\"fields\":[[{\"field_key\":\"子字段key\",\"field_value\":\"示例值\"}]]}"}]' --format json
```

```bash
meegle workitem update --work-item-id 工作项ID --project-key 空间key --fields '[{"field_key":"复合字段key","field_value":"{\"action\":\"update\",\"group_uuid\":\"读回的组标识\",\"fields\":[[{\"field_key\":\"子字段key\",\"field_value\":\"新值\"}]]}"}]' --format json
```

```bash
meegle workitem update --work-item-id 工作项ID --project-key 空间key --fields '[{"field_key":"复合字段key","field_value":"{\"action\":\"delete\",\"group_uuid\":\"读回的组标识\"}"}]' --format json
```

多人复合字段必须整体覆盖且不可用于新增人员:

```bash
meegle workitem update --work-item-id 工作项ID --project-key 空间key --fields '[{"field_key":"多人复合字段key","field_value":"{\"userkey1\":[{\"field_key\":\"子字段key\",\"field_value\":\"示例值\"}],\"userkey2\":[]}"}]' --format json
```

更新拉群方式(group_type 逻辑字段):

```bash
meegle workitem update --work-item-id 工作项ID --project-key 空间key --fields '[{"field_key":"group_type","field_value":"{\"type\":\"auto\"}"}]' --format json
```

```bash
meegle workitem update --work-item-id 工作项ID --project-key 空间key --fields '[{"field_key":"group_type","field_value":"{\"type\":\"bind\",\"group_id\":\"oc_xxx\"}"}]' --format json
```

```bash
meegle workitem update --work-item-id 工作项ID --project-key 空间key --fields '[{"field_key":"group_type","field_value":"{\"type\":\"disabled\"}"}]' --format json
```

`auto` / `disabled` 不得携带 group_id。

> **多人复合字段**:目标 userkey 不在读回 map 中时停止自动更新,向用户说明需先通过页面配置人员范围;接口空成功后必须回读确认。

---

## 人员域

### user search

默认仅在职:

```bash
meegle user search --user-keys '["张三","李四"]' --project-key {{project_key}} --need-all-status false --format json
```

包含离职/停用:

```bash
meegle user search --user-keys '["张三"]' --project-key {{project_key}} --need-all-status true --format json
```

### user me

```bash
meegle user me --format json
```

---

## 工作台域

### mywork todo

```bash
meegle mywork todo --action todo --page-num 1 --asset-key {{asset_key}} --format json
```

`--action`:`todo` / `done` / `overdue` / `this_week`。

---

## 工时域

### workhour list-schedule

```bash
meegle workhour list-schedule --start-time 2025-03-01 --end-time 2025-03-31 --project-key 空间key --user-keys '["张三","李四"]' --work-item-type-keys '{{work_item_type_keys}}' --format json
```

### workhour list-records

```bash
meegle workhour list-records --project-key 空间key --work-item-type story --work-item-id 工作项ID --page-num 1 --format json
```

---

## 视图域

### view get

```bash
meegle view get --view-id 视图ID --project-key 空间key --fields '{{fields}}' --page-num {{page_num}} --format json
```

---

## 工作流域

### workflow get-node

```bash
meegle workflow get-node --work-item-id 工作项ID --field-key-list '{{field_key_list}}' --need-sub-task {{need_sub_task}} --page-num {{page_num}} --project-key 空间key --node-id-list '["节点ID或_all"]' --format json
```

### workflow transition(节点流)

```bash
meegle workflow transition --work-item-id 工作项ID --project-key 空间key --node-id 节点ID --action confirm --rollback-reason '{{rollback_reason}}' --format json
```

### workflow transition-state(状态流)

```bash
meegle workflow transition-state --work-item-id 工作项ID --project-key 空间key --transition-id 流转ID --format json
```

### workflow list-state-transitions

```bash
meegle workflow list-state-transitions --work-item-id 工作项ID --work-item-type story --user-key userkey --project-key 空间key --format json
```

### workflow list-state-required

```bash
meegle workflow list-state-required --work-item-id 工作项ID --state-key 目标状态key --project-key 空间key --mode unfinished --format json
```

---

## 评论域

### comment add

```bash
meegle comment add --work-item-id 工作项ID --content '评论内容' --project-key {{project_key}} --format json
```

### comment list

```bash
meegle comment list --work-item-id 工作项ID --project-key 空间key --page-num {{page_num}} --start-time {{start_time}} --end-time {{end_time}} --format json
```

---

## 关系域

### relation meta-definitions

```bash
meegle relation meta-definitions --project-key 空间key --work-item-type {{work_item_type}} --relation-work-item-type {{relation_work_item_type}} --format json
```

### relation list

```bash
meegle relation list --project-key 空间key --work-item-id 工作项ID --page-size {{page_size}} --relation-field-key {{relation_field_key}} --node-id {{node_id}} --relation-id {{relation_id}} --page-num {{page_num}} --format json
```

---

## 子任务域

### subtask update

```bash
meegle subtask update --node-id 节点ID --project-key {{project_key}} --task-id {{task_id}} --assignee '{{assignee}}' --work-item-id 工作项ID --role-assignee '{{role_assignee}}' --fields '{{fields}}' --schedule '{{schedule}}' --action create --deliverable '{{deliverable}}' --format json
```
references/attachment.md›
# 附件域

## 核心边界

工作项附件有三个独立阶段:**上传文件到对象存储 → 把文件对象写入目标字段 → 回读目标字段验收**。上传成功不等于字段写入成功;只有三步都完成,才可以向用户宣称附件已写入。

附件上传/下载底层分两步:先调 `attachment prepare-upload` / `attachment prepare-download` 申请带签名的对象存储 URL,再与对象存储做一次或多次 HTTP 直连。

`attachment prepare-upload` 的返回结果可能包含 `target_unit_code`;自行对对象存储发起上传时,若该值非空,必须在每次 POST(包括所有上传分片)添加 `x-target-unit`,值原样使用该 `target_unit_code`;缺失或为空时不添加该请求头。`attachment prepare-download` 的返回结果同样可能包含 `target_unit_code`;自行下载时,若该值非空,必须在每次 GET(包括所有下载分片)添加同样的 `x-target-unit`,值原样使用该 `target_unit_code`;缺失或为空时不添加该请求头。

Meegle CLI 内置 `attachment +upload` / `attachment +download` 一键封装,把两步合成一条命令;脚本里需要逐步控制时也可单独调上面的 prepare 命令。
## 写入前门禁

### 1. 校验本地文件

- 对用户点名的每个路径逐一确认:文件存在、是普通文件、可读,且 basename 与用户要求一致。
- 本地文件不存在时立即停止并请用户补充或确认。**禁止自动替换成同名的其他文件,禁止擅自替换文件扩展名**(例如请求 `ad.png` 时改传 `ad.mp4`)。

### 2. 目标字段消歧

每次都必须针对**当前空间 + 当前工作项类型**调用 `workitem meta-fields`,使用 `field_types=["file","multi-file"]`、`page_num=1` 实时查询附件字段;若返回 `pagination.has_more=true`,继续递增页码,直到收集完所有页后再消歧。然后按以下顺序确定目标:

1. 用户明确给出字段 key:只接受精确 key 匹配。
2. 用户明确给出字段名:只接受精确字段名匹配;模糊结果只能用于展示候选。字段名精确匹配后仍有多个候选时,列出每个候选的 key 和类型,让用户按 key 选择。
3. 用户只说“附件”:若仅有一个候选可直接使用;若有多个候选,列出字段名、key、类型并让用户选择。

禁止选择第一个候选,禁止复用历史会话中的字段,禁止把 `attachment` 或 `multi_attachment` 当作固定 key。执行前记录 `target_field_key`、字段名和 `field_type`,后续上传、写入与验收必须始终使用同一个 key。

元数据中存在某字段,只能证明 API 可以识别它,**不能证明该字段已放入用户当前详情页布局或在 UI 可见**。没有布局查询能力时,应明确把结论限定为“API 目标字段写入并回读成功”。

### 3. 区分字段协议

`file` 与 `multi-file` 不是可无条件互换的协议:

- `multi-file` 使用 stringified 文件对象数组,支持按下文协议维护多个附件。
- `file` 可能是旧版或单附件字段;只有当前空间的实际写入与回读已经验证其行为时,才可按其已验证能力操作。不得仅凭字段名或历史经验宣称它支持多附件。
- `workitem get` 回读的文件对象可能比上传结果多出 `uid`、`url` 或其他服务端字段。后续合并时必须原样保留这些未知字段,不能把旧对象降级为只有 `name/type/size/fileToken` 的简化对象。

## 端到端写入协议

上传成功后,把返回值映射为字段对象:

```json
{"name":"a.pdf","type":"application/pdf","size":"12345","fileToken":"<token>"}
```

其中 `type` 来自上传结果的 `mime_type`,`fileToken` 来自 `file_token`,`size` 写成十进制字符串;整个数组还要再次 JSON.stringify 后作为 `field_value`。

### 单附件

1. 通过已消歧的 `target_field_key` 上传文件,获得真实 `file_token`。
2. 创建工作项、目标字段当前为空,或用户明确要求替换时,可调 `workitem create` / `workitem update` 直接写入单元素数组。
3. 向已有 `multi-file` 字段**新增一个附件**时也必须先调 `workitem get`:记录基线数量,原样保留当前完整数组,追加新对象后再调 `workitem update`。不得因为这次只新增一个文件就覆盖旧数组。`file` 字段仍按上面的兼容性规则处理。
4. 立即调 `workitem get` 回读**同一个目标字段**,按文件名和数量做写后验收。追加语义下预期数量为“基线数量 + 1”;不能只检查上传返回值,也不能回读另一个附件字段。

### 同一 `multi-file` 字段写入多个附件

可靠路径是**单附件落库后逐个追加**,不要把“创建时一次传入多个简化对象”作为默认协议:

1. 创建场景先上传并随 `workitem create` 写入第一个附件,拿到工作项 ID;剩余附件改用该 ID 逐个处理。已有工作项直接从下一步开始。
2. 每追加一个文件前,先调 `workitem get` 读取 `target_field_key` 的当前完整数组。
3. 上传一个新文件;将当前数组中的旧对象**原样保留**(包括 `uid`、`url` 及未知字段),只在数组末尾加入这一个新文件对象。
4. stringify 整个新数组并调 `workitem update` 覆盖目标字段;随后再次回读确认这一轮新增文件已出现。
5. 对剩余文件重复 2–4。最终比较用户请求的文件名多重集和数量:创建语义下等于请求总数,追加语义下等于“基线数量 + 成功新增数”;token 可能被服务端重签,不能把 token 完全相等作为唯一验收条件。

如果一次写入多个简化对象触发 `unique` 校验,直接切换到上述逐个追加协议;不得删除已成功落库的附件后盲目重试。更新是覆盖语义,不先读取并保留旧数组会导致已有附件丢失。

## 写后验收清单

- 回读的是已消歧并实际写入的 `target_field_key`。
- 单附件:目标文件名存在,目标字段数量符合预期。
- 多附件:请求的每个文件名都存在,重复文件按用户意图计数,总数符合预期。
- API 验收通过但无法验证页面布局时,如实说明 UI 可见性未验证,不把它包装成 UI 成功。

## attachment prepare-upload
申请上传签名。`work_item_id` 与 `work_item_type` **二选一必填**:已有工作项传 `work_item_id`;"创建工作项时同步上传附件" 场景传 `work_item_type`,两者同传时 `work_item_id` 优先。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --project-key | string | 是 | 空间 key |
| --resource-type | number | 是 | 附件场景:13=评论附件 / 14=评论图片 / 15=工作项附件字段 / 16=富文本字段图片 |
| --file-name | string | 是 | 附件名称 |
| --mime-type | string | 是 | MIME 类型 |
| --size | number | 是 | 文件总大小(字节);后端据此判断走单次上传还是分片 |
| --work-item-id | string | 二选一 | 已有工作项 ID |
| --work-item-type | string | 二选一 | 工作项类型(仅 "创建工作项同步上传附件" 场景) |
| --field-key | string | 条件 | `resource_type=15/16` 必填,13/14 不填 |

## attachment prepare-download
申请下载签名。`file_url` 是其它命令(如 `workitem get` 的附件字段值、`comment list` 评论里的附件链接、富文本中的附件引用)回传的不透明引用,**不要**手工拼接。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --project-key | string | 是 | 空间 key |
| --work-item-id | string | 是 | 工作项 ID |
| --file-url | string | 是 | 附件 URL(来自附件字段、评论或富文本) |

## attachment +upload
端到端上传:CLI 在本地把 `attachment prepare-upload` 与对象存储的签名 HTTP POST 串起来,返回 `file_token` 与文件元数据,可直接喂给 `workitem create` / `workitem update` / `comment add` 的附件字段。**Meegle CLI 专用**。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `<source-path>`(位置参数) | string | 是 | 本地文件路径 |
| --resource-type | string | 是 | 13/14/15/16,含义同 `attachment prepare-upload` |
| --project-key | string | 是 | 空间 key |
| --work-item-id | string | 二选一 | 已有工作项 ID |
| --work-item-type | string | 二选一 | 创建场景的工作项类型 |
| --field-key | string | 条件 | resource_type=15/16 时必填 |
| --filename | string | 否 | 覆盖发送给后端的文件名(默认取本地 basename) |
| --content-type | string | 否 | 覆盖 MIME 类型(默认按扩展名探测,未识别走 `application/octet-stream`) |

## attachment +download
端到端下载:CLI 在本地把 `attachment prepare-download` 与对象存储的签名 HTTP GET 串起来,并用 `.partial` 临时文件 + 原子改名落盘,失败时不会留下半残文件。**Meegle CLI 专用**。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `<file-url>`(位置参数) | string | 是 | 附件 URL(来自附件字段、评论或富文本) |
| --project-key | string | 是 | 空间 key |
| --work-item-id | string | 是 | 工作项 ID |
| --output | string | 是 | 本地落地路径 |
| overwrite | bool | 否 | 目标已存在时是否覆盖(默认 false) |
references/auth-guard.md›
# Auth Guard(所有业务命令前必须执行)

## 触发条件

- **主动登录**:用户说"登录 Meegle"、"连接飞书项目"、"login meegle"等。
- **被动拦截**:用户请求任何 Meegle 业务操作(查询待办、查工作项、创建任务等),优先执行 Auth Guard。
- **URL 触发**:用户发送了飞书项目/Meegle URL。处理流程:
  1. 先调 `url decode` 拿到结构化字段(`url_kind`、`host`、`simple_name`、`work_item_id` 等)。**禁止**自己从 URL 截取路径段作参数。字段含义与 kind 分支见 [url-kinds.md](url-kinds.md)。
  2. 保存 `$url_host` = response.host、`$target_host = $url_host`、`$url_kind`、`$simple_name`、`$work_item_id`。`$url_host` 只表示原链接所属站点,`$target_host` 表示本轮业务必须连接的站点,两者都不得被后续登录状态覆盖。
  3. 执行 Auth Guard(下面的 STEP 1 起)。
  4. 登录成功后按 `$url_kind` 分支:
     - `workitem_detail` → `project search` 得权威 `$project_key`,再 `workitem get` 查询详情
     - `workitem_homepage` / `view_*` / `unknown` 等非详情页 → 按 url-kinds.md 的指引拒绝或追问
     - 其他 kind → 参考 url-kinds.md 对应处理方式

按以下 STEP 顺序执行。每个 STEP 结尾的 GOTO 指明下一步,严格遵循跳转。

进入 STEP 1 前初始化变量:

- URL 触发 → 保留上面已解析的 `$url_host` 与 `$target_host`。
- 非 URL 触发 → SAVE `$url_host = null`;调用流程明确要求访问特定站点时,必须为本次请求传入 `$target_host`(例如链接生成流程中的显式域名),否则 SAVE `$target_host = null`。禁止继承上一请求的值。
- 将 `$profile_args` 保存为参数数组:用户显式选择 profile → `profile_args=(--profile "$profile")`;未显式选择 → `profile_args=()`。下面所有 Auth Guard 命令和 STEP DONE 的业务命令都必须复用同一个数组,禁止中途切换。

---

### STEP 1 — 检查登录状态

```bash
meegle "${profile_args[@]}" auth status --format json
```

返回值示例:
- 已登录:`{ "authenticated": true, "host": "meegle.com", "source": "token_store", "expires_in_minutes": 42 }`
- 未登录且有 host:`{ "authenticated": false, "host": "meegle.com", "source": null, "expires_in_minutes": null }`
- 未登录且无 host:`{ "authenticated": false, "host": null, "source": null, "expires_in_minutes": null }`

解析返回值,保存变量:
- `$authenticated` = response.authenticated
- `$auth_host` = response.host

**目标 host 一致性检查**:`$target_host` 非空时执行。比较前将 `$target_host` 与 `$auth_host` 的域名转为小写并去掉末尾的 `.`,端口必须保持一致。

- IF `$target_host != null` AND `$auth_host != null` AND 两者不一致 → SEND "当前登录站点为 `$auth_host`,但目标属于 `$target_host`。请切换或指定目标站点对应的 profile 后重试;本次未执行后续查询。";STOP
- IF `$auth_host != null` → SAVE `$host = $auth_host`
- IF `$auth_host == null` AND `$target_host != null` → SAVE `$host = $target_host`
- 其他情况 → SAVE `$host = null`

**跳转:**
- IF `$authenticated == true` → GOTO STEP DONE
- IF `$host != null` → GOTO STEP 2
- IF `$host == null` → GOTO STEP HOST

---

### STEP HOST — 选择站点

ASK user(等待用户回复):

> 你要连接哪个站点?
> 1) 飞书项目 (project.feishu.cn)
> 2) Meegle (meegle.com)
> 3) 自定义域名(请直接输入域名)

SAVE `$host` from user reply → GOTO STEP 2

---

### STEP 2 — OAuth 登录

```bash
meegle "${profile_args[@]}" auth login --host "$host"
```

命令会自动打开浏览器完成 OAuth 授权。等待命令执行完毕。

**跳转:**
- IF 命令成功(exit code 0) → GOTO STEP OK
- IF 命令失败 → SEND "OAuth 登录失败,请检查错误信息或在终端中手动重新执行上方登录命令",STOP

---

### STEP OK — 通知登录成功

SEND to user: "登录成功!"

> ⚠️ 此消息**必须单独发送**,不要与后续业务查询结果合并到同一条回复中。用户需要第一时间看到授权状态变化。

→ GOTO STEP DONE

---

### STEP DONE — 执行业务命令

Auth 已通过,执行用户请求的操作。每条业务命令继续使用进入 STEP 1 前保存的同一个 `$profile_args`。

## 错误处理

- 如果 bash 返回 `command not found` 或 npx 不可用,提示用户安装 Node.js 18+。
- 如果 OAuth 登录失败,提示用户在终端中手动重新执行上方登录命令。
references/cli-guide.md›
# CLI 使用指南

## 前置条件

运行环境需要 Node.js 18+。所有命令通过 `meegle` 执行。

## 命令结构

```bash
meegle <resource> <method> [flags] --format json
```

命令采用 `resource method` 两级结构。所有输出推荐使用 `--format json` 获取结构化数据。

## 全局 Flag

| Flag | 说明 |
|------|------|
| `--format json\|table\|ndjson` | 输出格式,默认 json |
| `--select <props>` | 选取输出属性,逗号分隔(支持 dot path,如 `name,owner.name`) |
| `--profile <name>` | 临时切换 profile |
| `--verbose` | 显示详细日志 |
| `--refresh` | 从服务端刷新本地命令缓存(旁路 24h cache) |

## 参数传递

几种方式,优先级从高到低:

1. **Flag 模式**(推荐):`--project-key PROJ --work-item-type story`
2. **--fields 模式**(写工作项字段,可重复):`--fields '{"field_key":"name","field_value":"任务标题"}' --fields '{"field_key":"priority","field_value":"1"}'`;`field_value` 支持任意 JSON 值(数组/对象原样传)
3. **--params 模式**(完整 JSON 兜底):`--params '{"fields":[{"field_key":"name","field_value":"任务标题"}]}'`
4. **--set 模式**(仅顶层参数快捷写法,不支持 fields[]):`--set page_num=1` 等价于 `--page-num 1`,支持 dot-path 嵌套;不要用它写工作项字段

Flag 覆盖 `--params`;`--set` 只影响顶层参数,**不会**写到 `fields[]`。

## 命令发现

CLI 的命令和参数会随版本更新。遇到不确定的命令或参数时,使用 `inspect` 获取最新信息:

```bash
meegle inspect                    # 列出所有可用命令
meegle inspect workitem.create    # 查看具体命令的参数 schema
```

> 命令清单本地缓存 24 小时。如果 `inspect` 输出的参数与服务端实际不符,或服务端有新命令但 CLI 报 `unknown command`,加上 `--refresh` 强制从服务端重新拉取最新清单:
> ```bash
> meegle --refresh inspect workitem.create
> ```

## 输出处理

- 始终使用 `--format json` 获取结构化输出,方便解析
- 使用 `--select` 精简返回字段,如 `--select id,name,current_nodes.name`
- 命令返回错误时,JSON 中包含 `error` 和 `message` 字段
references/error-handling.md›
# 错误处理规则

> 症状 → 修复动作映射。语法/协议细节引用 [mql-syntax.md](mql-syntax.md) 与 SKILL.md「字段值格式」。

**通用原则**:从报错提取关键字 → 匹配下表修复 → 重试。**同一错误最多自动重试 2 次(共 3 次尝试)**。仍失败则停止自愈,向用户结构化说明:① 原始请求与目标语义;② 每次修复的关键改动与服务端返回;③ 推测根因(字段/角色/枚举不存在、无权限等)并请求澄清。**禁止无限重试或反复微调同一参数**。

---

## 1. MQL 自愈规则(高频,优先匹配)

> `workitem meta-fields` / `workitem meta-roles` 必须同时带 `--project-key` 和 `--work-item-type`;`workitem meta-types` 只需 `--project-key`。

### 1.1 元数据类

| 报错症状 | 修复动作 |
|---------|---------|
| 字段 key 在该空间+工作项类型下不存在 | 立即调 `workitem meta-fields`,同时传空间、类型与报错中的字段名,替换重试 |
| 字段中文名歧义(多字段同名) | 改用字段 key。通过 `workitem meta-fields` 的 `field_query` 拿所有匹配 key,选语义正确的替换;无法判断询问用户 |
| 把角色当字段写(如 `经办人`) | 见 [mql-syntax.md §12 角色](mql-syntax.md):`workitem meta-roles` 取 `role_name` / `role_id`,用 `` `__<role_name>` `` 或 fallback 复合列名 |
| 枚举值/状态值在该空间+工作项类型下不存在 | `workitem meta-fields` 精确获取对应字段的 options,用真实 label 替换。禁硬编码 |
| 树状字段值写成完整路径或非叶子 | `workitem meta-fields` 获取 options,确认叶子 `option_id` / label;父级查询改用 `any_match` |
| 查询不支持的字段类型(`attachment` / `file` / `spec_doc` / `specDocs`) | 从 SELECT/WHERE 移除,改用 `workitem get`。**`multi-file` 例外**:允许 `SELECT` / `IS NULL` / `IS NOT NULL`;深度筛选不支持 |

### 1.2 语法类

| 报错症状 | 修复动作 |
|---------|---------|
| 中文字符编码损坏 | 整个 MQL 用单引号包裹、中文名反引号、无多余反斜杠。重构重试 |
| 用了硬规则禁用语法(`SELECT *`、`count()`/`GROUP BY`、`REGEXP`、`CONTAINS()`) | 对照 [mql-syntax.md §1.1](mql-syntax.md)。`LIMIT ... OFFSET n` / `LIMIT n,count` 均支持,推荐配 `ORDER BY` |
| 字段名未加反引号导致 `syntax error near ':...'` | 字段名用反引号;含 `<target:xxx>` 时**整体**放同一对反引号(`` `name<target:all>` ``) |
| 顶层 SELECT/WHERE 用了 `` `name<target:all>` `` 报 `attribute[...] not found` | `<target:xxx>` **仅**允许在关系判断 lambda 内(`` x.`name<target:all>` `` 形式);顶层引用去掉修饰符 |
| 用 `\'` 转义单引号 | 改用 `''`(两个单引号) |
| `Internal % and _ characters must be escaped` | LIKE 内部字面量 `%` / `_` 必须写作 `\%` / `\_`;即便 `_` 出现在关键词中间(如 `%test_case%`)也会被服务端强制拒回,必须转义为 `%test\_case%`。见 [mql-syntax.md §1.6](mql-syntax.md) |
| LIKE 缺通配符 | 完整包含形态 `LIKE '%关键词%'` |

### 1.3 语义类

| 报错症状 | 修复动作 |
|---------|---------|
| FROM 缺空间或类型 | 修正为 `` FROM `project_key`.`work_item_type_key` `` |
| SELECT 中放了函数(`current_login_user()`、`array_contains()`) | 从 SELECT 移除,函数只能在 WHERE 中 |
| `parent_work_item() not supported in stage Where` | `parent_work_item()` 仅支持 SELECT;WHERE 中按父工作项过滤改用 `` any_relation_match(relation_field_chain('__父工作项'), x -> x.`work_item_id<target:all>` = '<父ID>') `` |
| 操作符与字段类型不兼容 | 对照 [mql-syntax.md §4 兼容性表](mql-syntax.md) 修正。常见:`number` / `bigint` 不支持 `BETWEEN` / `LIKE` / `array_contains`,改为 `>=` + `<=` / 精确等值 |
| 多值右值用了 JSON 数组字符串(`IN '["a","b"]'`) | `IN` 直接 syntax error,改元组 `IN ('a','b')`。`=` 服务端当前兼容 JSON 数组字符串但非推荐写法,统一改元组 `= ('a','b')`。**例外**:`array_intersect` / `risk_label() = ...` 保留 JSON 数组字符串 |
| `tree-multi-select` / `workitem_related_multi_select` 右值拒回 `metadata error` 或 `attrValueLabel not found`(如裸 option_id / 裸 work_item_id) | 改为 label 或 `<id:option_id>` / `<id:work_item_id>` 包裹形式。见 [mql-syntax.md §5.1](mql-syntax.md) |
| signal 字段 `IS NULL` / `IS NOT NULL` 报 `operator not supported` | signal 不支持空判断,改用 `!= '真实 label'` 或去掉该条件 |
| signal 值位传了 `option_id` / `<id:>` / `'true'` / `'false'` / `'null'` | 改用 `option_name` label(如 `'已通过'`) |
| Lambda 报 `lambda predicate operator not supported: <OP>` / `unsupported lambda predicate` | Lambda 内仅支持 `x = 'v'`、`x IN (...)`、同变量 OR。其余(`!=` / `NOT IN` / 比较符 / `LIKE` / `BETWEEN` / `IS NULL` / `RELATIVE_DATETIME_*` / `AND` 复合 / 嵌套 Match)全部拒回。见 [mql-syntax.md §14](mql-syntax.md)。多选/数组字段优先用顶层 `IN` / `NOT IN` / `array_contains` / `none_match`;仅值列表含 `team()` / `current_login_user()` 时用 `any_match` |
| `get_node_attribute('__BELONGING','状态')` 触发 nil pointer / panic | 见 [mql-syntax.md §15.6](mql-syntax.md) 权威规则:状态类改用 `=` 顶层比较(禁被 `array_contains` 包裹);`__BELONGING` 属性禁 AND 组合 |

### 1.4 参数类

| 报错症状 | 修复动作 |
|---------|---------|
| `RELATIVE_DATETIME_*` 报 `unexpected operator for future` / `invalid argument` | 见 [mql-syntax.md §6.1 兼容矩阵](mql-syntax.md):`_EQ` 只接受 `today`/`tomorrow`/`yesterday` 且**无 offset**;`_GT/_GE/_LT/_LE` 只接受 `today`(可带 `±Nd`);`future`/`past`+`Nd` 只允许配 `_BETWEEN` |
| ORDER BY 字段不支持排序 | 换可排序字段(如 `updated_at` / `start_time` / `work_item_id`)或移除 |
| MQL 与 session_id 都空 | 首查必须传 MQL;翻页必须传 session_id |
| 翻页参数缺失 | 传 `[{"group_id":"1","page_num":N}]`(无分组时 `group_id` 固定 `"1"`) |
| 未传 project_key | `workitem query` 的 `--project-key` 必填 |
| 空间不存在 | 用 `project search` 确认 |
| 空间名匹配多个 | 从报错提取候选,让用户选择或用精确 project_key 重试 |
| 当前用户对该空间无权限 | 告知用户需申请访问,**禁止重试** |

---

## 2. 通用字段值自愈

| 报错症状 | 修复动作 |
|---------|---------|
| `need STRING type, but got: LIST/MAP` | 原生 JSON 改为 JSON.stringify 字符串(见 SKILL.md「字段值格式」) |
| 字段值类型错配(数字↔字符串、单值↔数组) | 仅改格式,值不变 |
| 级联选项传非叶子节点 | 展示 `children` 树,让用户选叶子 |
| 枚举值不在可选项 | 从 options 匹配;唯一命中则修正重试,否则询问用户 |

---

## 3. 非 MQL 错误速查

| 报错症状 | 修复动作 |
|---------|---------|
| 找不到空间 / 中文名多命中 | `project search` 验证,取 project_key 精确调用 |
| 找不到工作项类型 | `workitem meta-types` 确认合法 `type_key` |
| 模板不存在/禁用 | `workitem meta-fields` 获取可用模板 |
| 角色查询无结果 / 经办人报告人查不到 | `workitem meta-roles` 确认 `role_name` / `role_id`;系统默认 `role_name`=`经办人`/`报告人`,`role_id`=`operator`/`reporter` |
| 人名→userkey 转换失败 | `user search` 批量查询 |
| 人员字段写入失败 | user 传单个 userkey;multi-user 必须 stringified 数组(如 `"[\"k1\",\"k2\"]"`) |
| 找不到节点 | `workflow get-node` 查全量节点详情列表匹配真实 `node_id`(`workitem get` 只返进行中节点) |
| 节点流转失败 | 节点流用 `workflow transition`;状态流用 `workflow transition-state`(先 `workflow list-state-transitions` 取 `transition_id`,`workflow list-state-required` 查必填) |
| 重复流转已完成节点 | 流转前 `workflow get-node` 检查状态 |
| 节点未激活 | 先确认工作流进度 |
| 创建缺模板 | `workitem meta-fields` 获取模板字段 |
| 创建必填字段未提供 | `workitem meta-create-fields` 取 `is_required=1` 的所有字段(注意 `workitem meta-fields` 不返回 required 标记) |
| 角色更新失败 | 改用 `workitem update` 的 `role_operate`(不走 fields) |
| `group_type=bind` 缺 `group_id` | 补非空 `group_id`;解绑用 `{"type":"disabled"}` |
| `group_type=auto/disabled` 却传了 `group_id` | 二选一:保留 `bind + group_id`,或去掉 `group_id` |
| `page_size` 被序列化为字符串 | Meegle CLI 改用 `--params '{"page_size":N,"page_token":"..."}'` |
| `mywork todo` 需选择工作区 | 从报错列表把 `asset_key`(Asset_xxx)传入重试 |
| 操作记录时间区间非法 | `start_time < end_time`,格式毫秒时间戳或 `YYYY-MM-DD` |
| 关联查询缺 `relation_id` | 先用 `relation meta-definitions` 获取 |
| 接口限流 | 等待 1-2 秒重试,或减少并发 |
| 视图配置失效 / session 缓存过期 | 前者 `view search` 取有效 ID;后者不传 `session_id` 重查 |
| 工作项已冻结/终止/归档 | 告知用户不可编辑,**禁止重试** |
| 状态流转目标不可达 | 先 `workflow list-state-transitions` 获取合法路径供用户选择 |
| 选项可见性不满足 | 重新用 `workitem meta-fields` 取上下文可见选项 |
| `work_item_type_key` 不存在 | `workitem meta-types` 取合法列表 |
| WBS 草稿不存在 | 先用 `wbs create-draft`,或确认该空间已启用 WBS |
| 非 IPD 项目调用 WBS/资源库/交付物 | 告知用户该空间不支持 |
| `wbs edit-draft` 父行不支持新增子行 | `wbs list-draft-rows` 确认父行类型和拆解模式 |
| 工时未启用 | 需在项目设置开启(`actual_work_time_switch=true` 或安装"工时登记"插件),**禁止重试** |
| 工时记录返回空但确认有数据 | 确认 `work_item_type` 是否正确 |

---

## 4. 熔断条件(立即终止)

- 空间未找到(`project search` 连续 3 次失败)
- Permission Denied(无空间访问权限)
- 服务端返回明确的"已归档/已冻结/无权限"业务错误
references/field-value-extras.md›
# 字段值进阶:关联工作项名称 → ID 转换

当用户为 `workitem_related_select` / `workitem_related_multi_select` 字段提供的是**工作项名称而非 ID** 时,按以下流程转换后再写入。

1. **获取关联字段的目标约束**:从 `workitem meta-fields` 返回的该字段配置中,提取其绑定的**目标空间**(`project_key`)和**目标工作项类型**(`work_item_type_key`)。若配置未限定(可关联任意类型),默认在当前空间内搜索。
2. **按名称搜索目标工作项**:调用 `workitem query`,在目标空间和类型范围内按名称匹配。示例 MQL:
   ```sql
   SELECT `工作项ID`, `名称` FROM `目标空间`.`目标类型` WHERE `名称` = '用户给的名称'
   ```
   精确匹配无结果时改用 `like '%关键词%'` 模糊搜索。
3. **消歧处理**:
   - 唯一结果 → 直接取工作项 ID
   - 多个结果 → 列出所有匹配项(ID + 名称 + 状态)让用户确认
   - 零结果 → 提示用户"未找到名为 XXX 的工作项,请确认名称或直接提供 ID"
4. **写入格式**:
   - `workitem_related_select` → 传入单个 ID 字符串
   - `workitem_related_multi_select` → 传入 stringified ID 数组
   - 不同空间可能要求字符串或数字格式,遇类型校验失败立刻切换格式重试
5. **循环引用保护**:写入前必须排查当前工作项自身 ID,**禁止将自身 ID 写入关联字段**,否则会触发 `exists loop`(循环引用)报错。
references/misc.md›
# 其它低频命令

低频/单命令小域的参数表汇总。涵盖团队、图表、子任务、关系、评论查询、工时记录、交付物、资源库、WBS 辅助命令。

---

## 团队

### team list
查看空间下的团队列表。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --project-key | string | 否 | 空间 key |

### team list-members
查看团队成员列表。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --project-key | string | 是 | 空间 key |
| --team-id | string | 是 | 团队 ID |

---

## 度量图表

### chart get
查看图表详情。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --chart-id | string | 是 | 图表 ID |

### chart list
查看视图下的图表列表。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --project-key | string | 是 | 空间 key |
| --view-id | string | 是 | 视图 ID |

---

## 子任务

### subtask update
创建/修改/完成/回滚子任务。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --node-id | string | 是 | 节点 ID |
| --work-item-id | string | 是 | 工作项 ID |
| --action | string | 是 | create/update/confirm/rollback |

---

## 关系

### relation list
查看关联的工作项列表。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --project-key | string | 是 | 空间 key |
| --work-item-id | string | 是 | 工作项 ID |
| --relation-field-key | string | 否 | 关联关系字段 key,从 `relation meta-definitions` 获取 |
| --relation-id | string | 否 | 关联关系 ID,从 `relation meta-definitions` 获取 |
| --node-id | string | 否 | 节点 ID,查询某节点下的关联时传入 |
| --page-num | number | 否 | 分页页码,从 1 开始 |
| --page-size | number | 否 | 每页数量,最大 50 |

### relation meta-definitions
查看空间下的关联关系定义。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --project-key | string | 是 | 空间 key |

---

## 评论查询

### comment list
查看评论列表。添加评论用 `comment add`(见 SKILL.md 主文件)。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --project-key | string | 是 | 空间 key |
| --work-item-id | string | 是 | 工作项 ID |

---

## 工时记录

### workhour list-records
查看工作项的工时登记记录。团队排期用 `workhour list-schedule`(见 SKILL.md 主文件)。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --project-key | string | 是 | 空间 key |
| --work-item-type | string | 是 | 工作项类型 |
| --work-item-id | string | 是 | 工作项 ID |

---

## 交付物

### deliverable list
查看交付物详情及其所属根工作项 / 来源工作项。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --project-key | string | 是 | 空间 key |
| --work-item-ids | string[] | 否 | 工作项 ID 列表;URL 自动解析;提供名称需先调 `workitem get` 拿 ID |

---

## 资源库

### resource create
在已启用资源库的工作项类型下创建资源模板(资源实例)。先调 `resource meta-fields` 取字段 / 角色配置。

**创建资源实例 vs 从资源实例创建普通工作项**:
- 创建资源实例:使用 `resource create`;`work_item_type_key` 表示资源库启用的工作项类型,`template_id` 表示流程模板,`fields` / `roles` 描述新资源实例自身。
- 从资源实例创建普通工作项:这是“基于已有资源实例派生/创建业务工作项”的语义,参数通常需要源资源实例标识。当前命令参数必须以 `inspect` / schema 为准;若没有显式源资源实例参数,不要把源资源实例 ID 塞进 `work_item_type_key`、`template_id` 或普通字段里猜测调用。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --project-key | string | 是 | 空间 key |
| --work-item-type-key | string | 是 | 工作项类型 key 或名称;失败时先调 `workitem meta-types` |
| --fields | object[] | 否 | 资源字段列表,每项含字段 key 与字段值 |
| --roles | object[] | 否 | 角色人员;为空则不指定 |
| --template-id | string | 否 | 工作流模板 ID 或名称;未传则取该工作项类型的第一个流程模板 |

### resource meta-fields
查看资源库的字段 / 角色配置。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --project-key | string | 是 | 空间 key |
| --work-item-type-key | string | 是 | 工作项类型 key 或名称 |

---

## WBS 辅助命令

> 计划表(WBS)的核心查询 / 编辑 / 发布命令见 [wbs.md](wbs.md)。本节仅收 4 个辅助命令:草稿生命周期管理(create-draft / reset-draft)、异步操作进度查询(get-draft-progress)、流程资源库元素查询(list-element-templates)。

### wbs create-draft
为指定工作项实例创建新的计划表草稿。当需要编辑计划表但当前不存在草稿时,先调本工具创建草稿,再配合 `wbs edit-draft` 进行编辑。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --work-item-id | string | 是 | 工作项 ID,单值;URL 自动解析 |
| --project-key | string | 是 | 空间 key |

### wbs reset-draft
将草稿重置为线上实例状态,**放弃所有未发布的修改**。不传 `uuids` 时全量重置。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --work-item-id | string | 是 | 工作项 ID |
| --project-key | string | 是 | 空间 key |
| --uuids | string[] | 否 | 要重置的行 uuid 列表;为空则全量重置 |

### wbs get-draft-progress
查询计划表草稿异步操作(create / edit / publish / reset)的执行进度。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --project-key | string | 是 | 空间 key |
| --work-item-id | string | 是 | 工作项 ID 或名称 |
| --op-type | string | 是 | 操作类型:`create` / `edit` / `publish` / `reset` |
| --operation-id | string | 是 | 操作 ID(由 create-draft / edit-draft / publish-draft / reset-draft 返回) |

### wbs list-element-templates
列出流程资源库中的资源节点(node)或资源任务(task)模板。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --element-type | string | 是 | 资源库类型:`node` 或 `task` |
| --project-key | string | 是 | 空间 key |
| --work-item-type | string | 是 | 工作项类型 key 或名称 |
| --page-size | number | 否 | 页大小 |
| --page-no | number | 否 | 页码 |
references/mql-syntax.md›
# MQL 语法规范

> `workitem query` 的 `--mql` 参数必须是完整 SQL 语句,包含 `SELECT` + `FROM`。禁止 JSON 对象、条件片段、缺 SELECT 的写法。

---

## 1. 硬规则

### 1.1 禁用语法

| 禁止 | 替代 | 原因 |
|------|------|------|
| `SELECT *` | 显式列出字段 | 不支持通配符 |
| `count(*)` / `SUM()` / `GROUP BY` | 从返回 `count` 字段读总数 | 不支持聚合 |
| `REGEXP` / `regexp_like()` | `LIKE '%...%'` | 不支持正则 |
| `currentUser()` / `current_user()` | `current_login_user()` | 函数名错误 |
| `CONTAINS(field, val)` | `array_contains()` 或 `LIKE` | 无此运算符 |
| 日期无引号 `2026-06-01` | `'2026-06-01'` | 会被解析为减法 |

### 1.2 不推荐但服务端受理

以下语法**服务端行为不稳定**(部分字段类型直接报 `operator not supported`),一律禁止生成:

| 语法 | 服务端行为 | 强制改写为 |
|------|-----------|-----------|
| `NOT BETWEEN a AND b` | 语法受理 | `< a OR > b` |

### 1.3 不可查询的字段类型

以下字段类型出现在 `SELECT` / `WHERE` 中会报 `unsupported field type`,必须移除并改用 `workitem get`:

- `attachment` / `file`(附件)
- `spec_doc` / `specDocs` / `spec_documents`(文档)

**例外**:`multi-file`(多文件,如 `multi_attachment`)**仅支持** `SELECT` / `IS NULL` / `IS NOT NULL`,返回 `key_label_value_list`。不支持 `LIKE` / `array_contains` 等深度筛选。

### 1.4 常见字段名易错映射

MQL 支持字段 key 和中文名/label,**必须优先 key**。中文名多存在同名歧义。

| ❌ 写法 | ✅ 替代 |
|--------|--------|
| `state_key` / `status` | `work_item_status` |
| `archiving_status` / `is_archived` | `archiving_date`(未归档用 `IS NULL`) |
| 中文名如「名称」「当前负责人」「创建时间」 | 先 `workitem meta-fields` 查 key |

除上表外,中文名一律先用 `workitem meta-fields` 查询,并同时传 `--project-key` 与 `--work-item-type`。

### 1.5 枚举值/状态值禁止硬编码

状态、select、tree-select 等枚举 label 由「空间 + 工作项类型」自定义。**禁止硬编码** `关闭`、`已完成`、`进行中`、`OPEN`、`CLOSED` 等。违反报 `attrValueLabel not found`。

**流程**:`workitem meta-fields` 取 options → 用真实 label 或 `<id:option_id>` 写入。

### 1.6 LIKE 转义

- LIKE 通配符**仅 `%`(任意长度)**。`_` **不是**通配符:未转义会被服务端强制拒回 `Internal % and _ characters must be escaped`(含 `%foo_bar%` / `%test_case%` 等所有含裸 `_` 的模式)
- 字面量 `_` 或"任意单字符"语义**必须**写 `\_`;同理 `%` 若作字面量必须 `\%`
- 模式必须完整包含形态 `'%...%'`

```sql
✅ WHERE `name` LIKE '%性能问题%'
✅ WHERE `name` LIKE '%test\_case%'       -- 字面量下划线必须转义
✅ WHERE `name` LIKE '%100\%完成%'
❌ WHERE `name` LIKE '100%完成'           -- 缺前 %
❌ WHERE `name` LIKE '%test_case%'        -- 裸 _ 未转义,服务端拒回
❌ WHERE `name` LIKE '%100%完成%'         -- 内部 % 未转义
```

### 1.7 字符串编码

- `--mql` 参数外层用单引号包裹
- MQL 内部字符串值用单引号;嵌套单引号用 `''`(两个单引号)
- **禁止**用 `\` 转义单引号(服务端语法接受但**不做转义**:`'张\'三'` 会被当作字面量 `张\`,导致空结果)
- 中文字段名/值放在反引号或单引号内

### 1.8 多值右值语法

多值右值**统一用元组** `(v1, v2)`,适用于 `IN` / `NOT IN` / `array_contains` / `=` / lambda 内的 `IN`。**不推荐** JSON 数组字符串 `'["a","b"]'`:`IN` 直接 syntax error;`=` 服务端当前兼容但非推荐写法,统一用元组。

**唯一例外**:控件函数场景要求 JSON 数组字符串——`array_intersect(<控件函数>, '["a","b"]')`(有交集语义)与 `risk_label() = '["a","b"]'`(集合完全相等语义)。

---

## 2. 基础语法

```sql
SELECT fieldList                          -- 字段列表,禁 *
FROM `project_key`.`work_item_type`       -- 必须双段完整
WHERE conditionExpression                 -- 可选
[ORDER BY field [ASC|DESC]]
[LIMIT [offset,] row_count]   -- 也支持 LIMIT row_count OFFSET n
```

**标识符**:

- 字段/表名推荐反引号:`` `work_item_id` `` / `` `project_key`.`story` ``
- `<target:xxx>` 修饰符**必须整体放在同一对反引号内**:`` `name<target:all>` ``。写成 `` `name`<target:all> `` 或 `name<target:all>` 报 `syntax error near ':...'`
- `<target:xxx>` **仅允许在关系判断 lambda 内部**(`` x.`name<target:all>` `` 形式)。顶层 SELECT/WHERE 直接引用主表字段禁止使用,否则报 `attribute[name<target:all>] not found`
- 字符串值用单引号;枚举值优先用 label(必须先经 workitem meta-fields 确认存在),找不到时用 `<id:option_id>`

---

## 3. 数据类型

| MQL 类型 | 对应字段类型 |
|---------|-------------|
| bool | bool |
| bigint | number 中的 `work_item_id`、`auto_number` |
| double | 其它 number |
| varchar | text、multi-pure-text、multi-text、select、tree-select、radio、user、link、signal、workitem_related_select |
| date | date(格式 `YYYY-MM-DD` 或 `YYYY-MM-DD+TZD`) |
| datetime | schedule、precise_date(格式 `YYYY-MM-DDThh:mm:ss[TZD]`) |
| array(varchar) | multi-select、tree-multi-select、multi-user、link_cloud_doc、workitem_related_multi_select |
| array(struct) | compound_field |
| lambda | `x -> x IN ('a','b')` 等 |

---

## 4. 运算符与字段类型兼容性

**唯一权威表**。违反本表报 `field not supported OPERATOR` / `operator not supported`。

| 字段类型 | 支持 | 不支持 |
|---------|------|--------|
| text / multi-pure-text | `=` `!=` `IN` `NOT IN` `LIKE` `NOT LIKE` `IS NULL` `IS NOT NULL` | 比较符、`BETWEEN`、`array_contains` |
| multi-text | `LIKE` `NOT LIKE` `IS NULL` `IS NOT NULL` | 其它 |
| select / radio / workitem_related_select | `=` `!=` `IN` `NOT IN` `IS NULL` `IS NOT NULL` | `LIKE`、比较符、`BETWEEN`、`array_contains`、`any_match` |
| tree-select(单选) | `=` `!=` `IN` `NOT IN` `IS NULL` `IS NOT NULL` | `LIKE`、比较符、`BETWEEN`、`array_contains`、`any_match`(不支持父级级联,仅按叶子精确匹配) |
| user | `=` `!=` `IN` `NOT IN` `array_contains` `IS NULL` `IS NOT NULL` | `LIKE`、比较符、`BETWEEN` |
| link | `=` `!=` `LIKE` `NOT LIKE` `IS NULL` `IS NOT NULL` | `IN`、比较符、`BETWEEN`、`array_contains` |
| signal | `=` `!=` `IN` `NOT IN`(值位见下) | `IS NULL` / `IS NOT NULL` 报 `operator not supported`;其它 |
| number(含 `work_item_id`) | `=` `!=` `IN` `NOT IN` `>` `>=` `<` `<=` `IS NULL` `IS NOT NULL` | `LIKE`、`BETWEEN`、`array_contains` |
| array(varchar) | `=` `!=` `IN` `NOT IN` `array_contains` `NOT array_contains` `any_match` `none_match` `IS NULL` `IS NOT NULL` | `LIKE`、比较符、`BETWEEN` |
| date | `=` `!=` `>` `>=` `<` `<=` `BETWEEN` `RELATIVE_DATETIME_*` `IS NULL` `IS NOT NULL` | `LIKE`、`IN`、`array_contains` |
| datetime(`schedule` / `precise_date`) | `>` `>=` `<` `<=` `BETWEEN` `RELATIVE_DATETIME_*`(需先拆为子字段) `IS NULL` `IS NOT NULL` | `=` `!=`、`LIKE`、`IN`、`array_contains`;必须拆为 `` `__字段key_开始时间` `` / `` `__字段key_结束时间` `` 访问(见 §13.1) |
| bool | `=` `!=` `IS NULL` `IS NOT NULL` | `IN`、`LIKE`、`BETWEEN`、`array_contains`、比较符 |
| array(struct)(`compound_field`) | 不可直接在 WHERE 中比较 | 需通过子字段或 `workitem get` 读取 |
| `multi-file` | `SELECT` / `IS NULL` / `IS NOT NULL` | `LIKE`、比较符、`BETWEEN`、`array_contains`、`IN` |

**signal 值位(MQL 查询)**:仅接受 `option_name` label(如 `'已通过'` / `'未通过'` / `'处理中'` / `'暂无信息'`)。禁 `option_id`(`'passed'`)、`<id:option_id>`、`'true'/'false'/'null'`。写入接口(create/update/transition)的值位规则不同,见 [SKILL.md「字段值格式」](../SKILL.md)。

**数组字段语义**:`=` / `!=` 按整组精确匹配(数组完全等于右值集合);`IN` 按元素级 OR(存在任一即匹配);`array_contains` 多参数为 AND(同时包含所有元素);`any_match` 按逐元素匹配。

**树状字段父级匹配**:`tree-multi-select`(数组型)父级匹配(含所有下级)用 `any_match(field, x -> x = '<父级 label>')` 或 `` `field` IN ('<父级 label>') ``;`array_contains` 仅做精确 label 匹配、**不含子级级联**。`tree-select`(单选)不支持父级级联,仅按叶子 label / option_id 精确匹配。

**tree-multi-select / workitem_related_multi_select 右值形式**:仅接受 label 或 `<id:option_id>` / `<id:work_item_id>` 包裹形式。裸 `option_id` / `work_item_id` 拒回 `metadata error`。详见 §5.1。

> **`workitem_related_select`** 值位:`= '<id:work_item_id>'` 或真实 label 命中;裸 `work_item_id` 拒回 `attrValueLabel not found`;`IS NULL` / `IS NOT NULL` 可用。

---

## 5. 数组与集合函数

### 5.1 函数清单

| 函数 | 说明 |
|------|------|
| `array_contains(field, e1 [,e2,...])` | 数组包含元素;多参数为 AND(同时包含所有元素)。对 `tree-multi-select` / `workitem_related_multi_select` 字段,右值仅接受 label 或 `<id:option_id>` / `<id:work_item_id>` 包裹形式;裸 `option_id` / `work_item_id` 拒回 `attrValueLabel not found` 或 `metadata error` |
| `any_match(field, x -> pred)` | 任一元素满足;**仅当 lambda 值列表含函数(`team()` / `current_login_user()`)或多个 `<id:userkey>` 时使用** |
| `none_match(field, x -> pred)` | 全部元素都不满足 |
| `array_intersect(field, '[...]')` | 有交集;**仅用于控件函数返回值**(`risk_label()`、`all_nodes_name()`、`in_progress_nodes_name()`);对普通多选字段会报 `array_intersect: invalid right_array`。**第二参数必须是 JSON 数组字符串** |

### 5.2 多选/数组字段决策顺序

1. 单元素 / 多元素 OR → `IN + 元组`
2. 多元素 AND(同时包含)→ `array_contains(field, 'a', 'b')`
3. 集合完全相等 → `` `field` = ('v1','v2') ``(元组,禁 JSON 数组字符串)
4. 全不属于 → `NOT IN` 或 `none_match`;`NOT array_contains` 语义为"不同时包含"(至少缺一个),非"全不属于"
5. **树状/级联字段父级匹配**(含所有下级):`tree-multi-select`(数组型)用 `any_match(field, x -> x = '<父级 label>')` 或 `` `field` IN ('<父级 label>') ``;`array_contains` 仅精确匹配、不含子级级联。`tree-select`(单选)不支持父级级联,仅按叶子 label / option_id 精确匹配
6. 值列表含 `team()` / `current_login_user()` 等返回集合的函数,或需在集合内做多个 `<id:userkey>` 判断 → `any_match(x -> x IN (...))`

```sql
-- 单人命中(多人字段)
array_contains(`current_status_operator`, '<id:userkey>')
-- 多人 OR
`current_status_operator` IN ('<id:k1>', '<id:k2>')
-- 多标签 AND
array_contains(`tag`, '标签A', '标签B')
-- 空判断
`tag` IS NULL
-- 值列表含团队函数
any_match(`watchers`, x -> x IN (team(true, '真实团队名')))
```

---

## 6. 时间函数 `RELATIVE_DATETIME_*`

签名:`RELATIVE_DATETIME_{EQ|GT|GE|LT|LE|BETWEEN}(col_name, 'date_para', ['days'])`

**date_para**:`today` / `tomorrow` / `yesterday` / `current_week` / `next_week` / `last_week` / `current_month` / `next_month` / `last_month` / `future` / `past`。

### 6.1 函数 × date_para × days 兼容矩阵

不在允许列的组合服务端拒回 `unexpected operator for <para>` / `invalid argument`。

| 函数 | **推荐使用** date_para | days(`'Nd'` / `'-Nd'`) | 备注 |
|------|-----------------------|--------------------------|------|
| `_EQ` | `today` / `tomorrow` / `yesterday` | ❌ 不接受 | `_EQ + future/past` **无 offset** 时报 `invalid argument: relative datetime <para> expr must have 2 params`;带 offset 时服务端受理但语义模糊,**禁止生成**(`future`/`past` 语义应改用 `_BETWEEN`) |
| `_GT` / `_GE` / `_LT` / `_LE` | 仅 `today` | ✅ 接受,可正可负 | — |
| `_BETWEEN` | `current_week` / `next_week` / `last_week` / `current_month` / `next_month` / `last_month` / `future` / `past` | 仅 `future` / `past` 接受且**必须传** `'Nd'`;其它 date_para 不接受 days | ⚠️ 服务端**拒回** `today` / `tomorrow` / `yesterday`(`unexpected operator for today`),改用 `_EQ` |

### 6.2 示例

```sql
-- 今天创建
RELATIVE_DATETIME_EQ(`start_time`, 'today')
-- 上周创建
RELATIVE_DATETIME_BETWEEN(`start_time`, 'last_week')
-- 未来 3 天到期
RELATIVE_DATETIME_BETWEEN(`field_xxxxxx`, 'future', '3d')
-- 今天前后 N 天
RELATIVE_DATETIME_GT(`start_time`, 'today', '-3d')
RELATIVE_DATETIME_LT(`start_time`, 'today', '3d')
```

字段 key 必须先经 `workitem meta-fields` 确认,且同时传 `--project-key` 与 `--work-item-type`;禁复用示例 key。

---

## 7. 人员与角色函数

| 函数 | 返回 |
|------|------|
| `current_login_user()` | 当前登录用户 userkey |
| `team(include_manager, '团队名')` | 团队成员 userkey 数组;首参 `true` 含管理者 |
| `all_participate_persons()` | 全部参与人 userkey 数组 |
| `participate_persons()` | 当前参与人 userkey 数组 |
| `participate_roles()` | 参与角色的 `role_name` label 数组 |

**关键约束**:

- 团队名必须先用 `team list` 查询,并传 `--project-key`;否则报 `attribute_value not found`
- `participate_roles()` 值位**必须**用 `role_name` label(`'后端开发'`),禁传 `role_id`(`'fe_rd'`)
- 人员字段值位**必须**用 `<id:userkey>`(详见 §11)

```sql
-- 当前负责人是我
array_contains(`current_status_operator`, current_login_user())
-- 指派给某团队(含管理者)
any_match(`current_status_operator`, x -> x IN (team(true, '真实团队名')))
-- 有指定角色参与
array_contains(participate_roles(), '后端开发', '前端开发')
```

---

## 8. 节点函数

| 函数 | 用途 |
|------|------|
| `all_nodes_name()` | 全部节点名数组 |
| `in_progress_nodes_name()` | 进行中节点名数组 |
| `risk_label()` | 节点延期状态标识数组,**不支持 `any_match`** |
| `get_node_attribute(node, attribute)` | 指定节点属性;`node` 可为节点名/`__ALL`/`__BELONGING`;**「所属节点」必须用 `__BELONGING`** |

### 8.1 `get_node_attribute` 属性访问

**可用属性**:排期、估分、节点时间、节点完成结论、节点完成意见、负责人、当前负责人、状态。**指定节点负责人**:`owner` 与 `负责人` 语义等价。

**开始/结束时间访问形式**(作为第二参数):

- 节点排期:`'__排期_开始时间'` / `'__排期_结束时间'`
- 节点时间:`'__节点时间_开始时间'` / `'__节点时间_结束时间'`

### 8.2 `__ALL` 简写规则

- 对整体属性直接用区间/比较;**禁止**拆 `__排期_开始时间` / `__排期_结束时间`,**禁止**外套 `any_match`
- `排期` 支持 `BETWEEN`;`估分` 不支持 `BETWEEN`,需拆 `>= a AND <= b`

```sql
-- 指定节点负责人(value 必须 <id:userkey>)
WHERE array_contains(get_node_attribute('需求详评','owner'), '<id:userkey>')
-- 开始节点排期在过去 30 天内
WHERE RELATIVE_DATETIME_BETWEEN(get_node_attribute('开始','__排期_开始时间'), 'past','30d')
-- 所属节点当前负责人
WHERE array_contains(get_node_attribute('__BELONGING','当前负责人'), '<id:userkey>')
-- 全部节点排期在区间内
WHERE get_node_attribute('__ALL','排期') between '2026-01-01' and '2026-03-31'
-- 全部节点估分 ≥ 30
WHERE get_node_attribute('__ALL','估分') >= 30
```

节点名以 `workflow get-node` 返回为准:`--node-id-list` 传 `["_all"]`,并同时传 `--project-key` 与 `--work-item-id`。

### 8.3 控件函数多值语义

**`all_nodes_name()` / `in_progress_nodes_name()`**(不能用 `IN`,只能走 `any_match`):

| 语义 | 写法 |
|------|------|
| 存在选项属于(OR) | `any_match(<控件>, x -> x IN ('a','b'))` |
| 包含(AND) | `array_contains(<控件>, 'a','b')` |
| 集合完全相等 | `<控件> = '["a","b"]'`(JSON 数组,例外) |
| 全部不属于 | `none_match(<控件>, x -> x IN ('a','b'))` |
| 不同时包含(至少缺一个) | `NOT array_contains(<控件>, 'a','b')` |

**`risk_label()`**(**不支持 `any_match`**):

| 语义 | 写法 |
|------|------|
| 有交集(默认「含延期节点」) | `array_intersect(risk_label(), '["延期/前端","延期/后端"]')` |
| 全部包含(AND) | 多个 `array_contains(risk_label(), 'x')` AND |
| 父级「延期」/「排期信息不全」 | `array_contains(risk_label(),'延期')` |

---

## 9. 关系与关联函数

### 9.1 关系判断函数

对关系对端做条件判断:

- `any_relation_match(rel, x -> expr)` — 存在一个对端满足
- `all_relation_match(rel, x -> expr)` — 每一个对端都满足
- `none_relation_match(rel, x -> expr)` — 每一个对端都不满足
- `not all_relation_match(rel, x -> expr)` — 至少一个对端不满足

`relation_field_chain` 等取关系后**必须**外层套关系判断函数。

### 9.2 关系参数三种形式

1. 关联字段:`` `字段key` ``
2. `relation('关系名')`
3. `relation_field_chain('rel1', 'rel2' [, 'rel3'])` — ≤ 3 跳

**子任务父工作项**:关系名固定 `'__父工作项'`(双下划线前缀)。报错 `relationNode not found, label:父工作项` 时改为 `'__父工作项'`。

### 9.3 跨端字段引用

对端字段引用形式:`` x.`字段名<target:project_key::type_key>` `` 或 `` x.`字段名<target:all>` ``。

**通用字段必须用 `<target:all>`**:标题、创建人、创建时间、业务线、优先级、当前负责人、所属工作项、所属空间、工作项 ID、工作项类型、状态。

```sql
-- 对端优先级为 P0
WHERE any_relation_match(`多选关联字段`, x -> x.`priority<target:all>` = 'P0')
-- 子任务父工作项名称
WHERE any_relation_match(relation_field_chain('__父工作项'), x -> x.`name<target:all>` like '%登录%')
-- 多级关系 + 节点属性
WHERE all_relation_match(relation_field_chain('__父工作项','需求关联软件'), x -> array_contains(get_node_attribute('开始','负责人'), '<id:userkey>'))
```

### 9.4 其它关系函数

- `parent_work_item(relation('关系名'))` — 父工作项 ID。**仅支持 SELECT 阶段**,WHERE 中使用报 `parent_work_item() not supported in stage Where`。
  - SELECT:`SELECT parent_work_item(relation('关系名')), work_item_id FROM ...`
  - 按父工作项过滤改用:`WHERE any_relation_match(relation_field_chain('__父工作项'), x -> x.\`work_item_id<target:all>\` = '12345')`
- `association()` — 跨空间关联实例 ID:`WHERE association() = '实例ID'`
- `linked_work_item()` — 子任务来源控件(父工作项 ID)。判空推荐 `IS NOT NULL`;等值右值必须是真实父工作项 ID,否则 `attribute_value not found`

---

## 10. 状态函数 `status_time`

对状态流工作项(如 `issue`、缺陷)和节点流工作项(如 `story`)均有效。传入的状态名不存在时报 `metadata error`,需以 `workflow list-state-transitions` 或 `workitem meta-fields` 返回的真实状态名为准;后者的 `--field-keys` 传 `["work_item_status"]`。

| 用法 | 允许位置 | 参数形式 |
|------|---------|---------|
| `status_time('状态名')` | WHERE / SELECT | **纯状态名**,不带 `__` 前缀 或 `_开始时间`/`_结束时间` 后缀 |
| `status_time('__状态名_开始时间')` / `_结束时间` | **仅 SELECT 或时间差表达式** | WHERE 中使用会报错 |

状态名以 `workflow list-state-transitions` 或 `workitem meta-fields` 返回的真实 option 为准;后者的 `--field-keys` 传 `["work_item_status"]`。

```sql
-- ✅ WHERE 用纯状态名过滤
WHERE status_time('<状态名>') between '2025-01-01' and '2025-12-31'
-- ✅ SELECT 计算状态累计时长
SELECT `work_item_id`, status_time('__<状态名>_结束时间') - status_time('__<状态名>_开始时间')
FROM `project_key`.`issue`
-- ❌ WHERE 里做算术过滤
WHERE status_time('__<状态名>_结束时间') - status_time('__<状态名>_开始时间') > 86400
```

---

## 11. 名称消歧 `<id:xxxx>`

### 11.1 人员字段值位规则

人员字段、user、multi-user、自定义人员控件、角色列的值位**必须**优先用 `<id:userkey>`:

- 裸 userkey(`= 'example_userkey'`)在部分场景被当姓名解析,报 `user label '...' does not exist`
- 裸中文姓名(`= '张三'`)常报 `user label '...' is not unique`(即便 `user search` 唯一,服务端仍全局校验)

流程:`user search` 拿 userkey → 值位写 `<id:userkey>`。

```sql
-- 单人字段
WHERE `owner` = '<id:userkey>'
-- 多人字段:= 或 array_contains 均可
WHERE array_contains(`current_status_operator`, '<id:userkey>')
-- 多人 OR:首选 IN;含函数返回集合时才用 any_match
WHERE `current_status_operator` IN ('<id:k1>', '<id:k2>')
WHERE `current_status_operator` = current_login_user()
```

### 11.2 团队/枚举消歧

同名重复时用 `<id:xxxx>`:

```sql
WHERE any_match(`current_status_operator`, x -> x IN (team(true, '开放平台团队<id:3455>')))
WHERE `priority` = '<id:option_2>'
```

---

## 12. 角色(Role)

**角色不是字段**。MQL 中角色作为**列引用**。

### 12.1 列名两种写法

| 写法 | 使用场景 | 示例 |
|------|---------|------|
| **主写法** `` `__<role_name>` `` | 默认。`role_name` 严格取自 `workitem meta-roles` 返回值,含空格原样保留 | `` `__后端开发` ``、`` `__经办人` `` |
| **fallback** `` `__role_<project_key>_<work_item_type>_<role_id>` `` | 仅当 `role_name` 与其它字段/角色中文名冲突时 | `` `__role_<project_key>_story_<role_id>` `` |

**硬性前置**:涉及角色的 MQL 必须先调 `workitem meta-roles` 获取真实 `role_name` / `role_id`,并同时传 `--project-key` 与 `--work-item-type`。**禁止**按用户自然语言原文(俗称、缩写、错别字)直接拼列名。

**系统默认角色**:`role_name` = 「经办人」/「报告人」;`role_id` = `operator` / `reporter`(后者仅用于 `role_operate` API 参数,禁出现在 MQL 列名或函数里)。

### 12.2 无效形式(Code 3010 `attr label not found`)

- `` `__<role_id>` ``(如 `__fe_rd`、`__operator`)
- `` `<role_id>` ``(无 `__` 前缀)
- 函数包装:`role(fe_rd)`、`get_role_owners(fe_rd)`

```sql
-- ✅ 主写法
WHERE array_contains(`__后端开发`, '<id:userkey>')
WHERE array_contains(`__经办人`, '<id:userkey>')

-- ✅ fallback(仅冲突时)
WHERE array_contains(`__role_<project_key>_story_<role_id>`, '<id:userkey>')

-- ❌ 缺 __ / 用 role_id / 中文原文
WHERE `经办人` = '<id:userkey>'
WHERE array_contains(`__operator`, '<id:userkey>')
```

---

## 13. 特殊字段查询

### 13.1 日期区间字段(date_range)

工作项级日期区间字段(自定义"计划周期"等)**不能**直接以字段 key 查询,必须拆成访问形式(**MQL 派生语法**,非复合字段子字段概念):

- `` `__<字段key>_开始时间` ``
- `` `__<字段key>_结束时间` ``

> 与节点排期不同:节点排期用 `get_node_attribute(...,'__排期_开始时间')` 访问。

```sql
-- ✅ 正确
WHERE `__field_xxxxxx_开始时间` > '2025-01-01'
WHERE RELATIVE_DATETIME_BETWEEN(`__field_xxxxxx_结束时间`, 'past', '30d')
-- ❌ 错误
WHERE RELATIVE_DATETIME_BETWEEN(`field_xxxxxx`, 'past', '30d')
```

### 13.2 树状/级联字段(如业务线)

先用 `workitem meta-fields` 获取 options:`--field-keys` 传 `["business"]`,或 `--field-query` 传 `业务线`;确认**叶子 option_id 与 label**。

| 语义 | 写法 |
|------|------|
| 父级(含所有下级) | `tree-multi-select`(数组型)用 `` any_match(`业务线`, x -> x = '<父级 label>') `` 或 `` `业务线` IN ('<父级 label>') ``;`tree-select`(单选)不支持父级级联,仅按叶子精确匹配 |
| 叶子等值 | `` `业务线` = '<叶子 label>' `` |
| ❌ 完整路径 | `` `业务线` = '<父级>/<叶子>' `` |

创建/更新类接口优先使用叶子节点 `option_id`。

---

## 14. Lambda 表达式限制

Lambda `x -> ...` 内部服务端**只受理下列极简条件**,其余一律拒回 `lambda predicate operator not supported: <OP>` 或 `unsupported lambda predicate`。

### 14.1 支持 / 不支持

| ✅ 支持 | ❌ 不支持 |
|--------|----------|
| `x = 'value'` | `x != 'a'` |
| `x IN ('a','b',...)`(值可含 `team(...)` / `current_login_user()` / `<id:userkey>`) | `x NOT IN (...)` |
| 同变量 OR:`x = 'a' OR x = 'b'`(推荐改 `IN`) | `x > / >= / < / <=` |
| — | `LIKE` / `NOT LIKE` |
| — | `BETWEEN` |
| — | `IS NULL` / `IS NOT NULL` |
| — | `RELATIVE_DATETIME_*(x,...)` |
| — | `AND` 复合条件(同/跨变量) |
| — | 嵌套 `any_match` / `none_match` |

### 14.2 决策规则

- 多选/数组字段优先直接用顶层 `IN` / `NOT IN` / `array_contains` / `NOT array_contains` / `none_match`
- **树状/级联字段父级匹配**(含所有下级):`tree-multi-select`(数组型)用 `any_match(field, x -> x = '<父级 label>')` 或 `` `field` IN ('<父级 label>') ``;`array_contains` 仅精确匹配、不含子级级联。`tree-select`(单选)不支持父级级联,仅按叶子 label / option_id 精确匹配
- 仅当 lambda 值列表需引用 `team(...)` / `current_login_user()` 等**返回集合的函数**,或需在集合内做多个 `<id:userkey>` 判断,才使用 `any_match(x -> x IN (...))`
- `any_match` 第二参数**必须**是 `x -> ...` 形式的 lambda;不接受数组字面量

---

## 15. 完整示例

> 示例中字段 key / 角色 / options 均以 `workitem meta-fields` / `workitem meta-roles` 返回为准;两者都必须同时传 `project_key` 与 `work_item_type`。

### 15.1 数组包含 + 当前用户 + 未归档

```sql
SELECT `work_item_id`, `name`, `work_item_status`, `priority`
FROM `project_key`.`story`
WHERE array_contains(`current_status_operator`, current_login_user())
  AND `archiving_date` IS NULL
```

### 15.2 相对时间

```sql
SELECT `work_item_id`, `name`, `start_time`
FROM `project_key`.`story`
WHERE RELATIVE_DATETIME_BETWEEN(`start_time`, 'past', '30d')
```

### 15.3 逾期未完成(日期区间字段 + 状态过滤)

```sql
SELECT `work_item_id`, `name`, `work_item_status`
FROM `project_key`.`story`
WHERE RELATIVE_DATETIME_LT(`__field_xxxxxx_结束时间`, 'today')
  AND `work_item_status` != '<完成态label>'
```

### 15.4 团队角色(`team list` 前置)

> 团队名 `'真实团队名'` 是占位。**必须先用 `team list` 拉取真实团队名,并传 `--project-key`**,否则报 `attribute_value not found`。

```sql
-- 主写法
SELECT `work_item_id`, `name`, `priority`
FROM `project_key`.`story`
WHERE any_match(`__后端开发`, x -> x IN (team(true, '真实团队名')))

-- fallback(仅角色名冲突时)
WHERE any_match(`__role_<project_key>_story_<role_id>`, x -> x IN (team(true, '真实团队名')))
```

### 15.5 综合(模糊 + 数组 + 排序分页)

```sql
SELECT `work_item_id`, `name`, `work_item_status`, `priority`
FROM `project_key`.`issue`
WHERE `name` LIKE '%性能优化%'
  AND array_contains(`current_status_operator`, current_login_user())
  AND `priority` = 'P0'
ORDER BY `updated_at` DESC
LIMIT 50
```

### 15.6 节点属性 + 延期标识

```sql
-- 所属节点当前负责人(单条件;`__BELONGING` 属性禁与其它 `__BELONGING` 属性 AND 组合,禁被 `array_contains` 包裹后与其它条件组合,见下方警示)
SELECT `work_item_id`, `name`, `work_item_status`
FROM `project_key`.`story`
WHERE array_contains(get_node_attribute('__BELONGING','当前负责人'), '<id:userkey>')

-- 所属节点状态:必须用 `=` 顶层比较(返回空 list 无 error),禁止 array_contains 包裹
SELECT `work_item_id`, `name`
FROM `project_key`.`story`
WHERE get_node_attribute('__BELONGING','状态') = '<进行中状态label>'

-- 开始节点已延期(risk_label() 等值是「集合完全相等」,用 JSON 数组字符串,属例外)
SELECT `work_item_id`, `name`
FROM `project_key`.`story`
WHERE risk_label() = '["延期/开始"]'
```

> ⚠️ **服务端已知限制(权威定义,其它章节引用本节)**:`get_node_attribute('__BELONGING','状态')` 一旦被 `array_contains` 包裹(单条件即触发),或任意 `__BELONGING` 属性之间做 AND 组合(如"当前负责人 + 状态"),会触发 nil pointer / panic。修复策略:状态类**必须**用 `=` 顶层比较;负责人等多值属性**必须**保持单条件;如需组合,改用 `__ALL` 属性或状态字段(`work_item_status`)+ `current_status_operator` 拼接。

### 15.7 关系查询(链式 + 跨空间字段)

```sql
-- 子任务的父工作项名称包含"登录"
SELECT `work_item_id`, `name`
FROM `project_key`.`sub_task`
WHERE any_relation_match(relation_field_chain('__父工作项'), x -> x.`name<target:all>` like '%登录%')

-- 多级关系:子任务→父工作项→关联软件
SELECT `work_item_id`, `name`
FROM `project_key`.`sub_task`
WHERE any_relation_match(relation_field_chain('__父工作项','需求关联软件'), x -> x.`name<target:all>` = '某软件')
```

### 15.8 状态时间 + 节点负责人

```sql
-- issue(状态流):状态窗口
SELECT `work_item_id`, `name`, `work_item_status`
FROM `project_key`.`issue`
WHERE status_time('<状态名>') between '2025-01-01' and '2025-12-31'

-- story(节点流):开始节点负责人
SELECT `work_item_id`, `name`
FROM `project_key`.`story`
WHERE array_contains(get_node_attribute('开始','负责人'), '<id:userkey>')
```

---

## 附:关键词 → 语法 映射

### 控件关键词

| 用户关键词 | 语法 |
|-----------|------|
| 参与人员、全部参与人员 | `all_participate_persons()` |
| 当前参与人 | `participate_persons()` |
| 流程节点、所有节点 | `all_nodes_name()` |
| 进行中节点 | `in_progress_nodes_name()` |
| 节点排期、节点估分、所属节点 | `get_node_attribute(node, attr)`(所属节点用 `__BELONGING`) |
| 节点延期标识 | `risk_label()` |
| 关联工作项字段 | `relation_field_chain('rel1',...)`(≤3 跳) |
| 子任务父工作项 | `relation_field_chain('__父工作项')` |
| 子任务来源 | `linked_work_item()` |
| 状态时间窗口(状态流) | `status_time('状态名') between ...` |
| 状态累计时长(状态流) | `status_time('__<状态名>_结束时间') - status_time('__<状态名>_开始时间')`(仅 SELECT) |

### 关系语境

| 关键词 | 函数 |
|-------|------|
| 每一个 | `all_relation_match` |
| 存在一个/一组 | `any_relation_match` |
| 每一个不满足 | `none_relation_match` |
| 存在一个不满足 | `not all_relation_match` |

### 数组语义

| 语义 | 语法 |
|------|------|
| 存在选项属于(OR) | `IN + 元组`(首选);控件函数用 `any_match` |
| 全部选项均不属于 | `NOT IN`(首选);`none_match` 备选 |
| 同时包含(AND) | `array_contains(field, 'a', 'b')` |
| 集合完全相等 | `` `field` = ('v1','v2') ``(禁 JSON 数组字符串,控件函数除外) |
references/performance.md›
# 性能与并发调用指南

本文件收录减少延迟的工程性规则。核心协议(字段格式、错误自愈)仍在 SKILL.md 主文件中。

## 并行调用

无依赖的命令调用应并行发起,有依赖则必须串行。

**必须串行**(前者输出是后者输入):
- `project search` → `workitem meta-fields` → `workitem query`
- `workitem get` → `workflow transition` / `workitem update`
- `workitem meta-fields` → `workitem create`

**可并行**:
- `workitem meta-fields` 和 `workitem meta-roles`(同类型)
- 多种工作项类型的 `workitem meta-fields`(如 story + issue)
- 各条件的 count 查询、多人排期分批查询

## 大结果处理

- **分批查询**:`workhour list-schedule` 多人时拆成每批 ≤ 20 人并行
- **精简 SELECT**:只选必要字段,避免富文本等大体积字段
- **按需翻页**:先读首页获取总数,按需翻页
references/rich-text-editor-markdown-syntax.md›
# 富文本编辑器 Markdown 格式规范

## 概述

富文本编辑器使用基于 **GFM(GitHub Flavored Markdown)** 的扩展 Markdown 格式。对于标准 Markdown 无法表达的功能,通过 HTML 注释和标签进行扩展。该格式可转换为 DSL、Delta 和 doc_html。

**核心原则:** 标准 GFM + HTML 注释承载元数据 + `<span>`/`<u>` 标签补充样式能力。

## 速查表

| 功能 | 语法 | 说明 |
|------|------|------|
| 加粗 | `**文本**` | |
| 斜体 | `*文本*` | |
| 删除线 | `~~文本~~` | |
| 下划线 | `<u>文本</u>` | HTML 标签,非标准 MD |
| 行内代码 | `` `代码` `` | |
| 字体颜色 | `<span style="color: rgb(R, G, B)">文本</span>` | 必须使用 `rgb()` 格式 |
| 背景颜色 | `<span style="background-color: rgb(R, G, B)">文本</span>` | 必须使用 `rgb()` 格式 |
| 字体大小 | `<span style="font-size: Npx">文本</span>` | 值为 px 单位 |
| 标题 | `#` 到 `######` | h1-h6 |
| 有序列表 | `1. 项目` | 嵌套用 4 空格缩进 |
| 无序列表 | `- 项目` | 嵌套用 4 空格缩进 |
| 任务列表 | `- [ ] 待办` / `- [x] 已完成` | |
| 引用块 | `> 文本` | 内部支持嵌套块级元素 |
| 代码块 | ` ```语言 ... ``` ` | 开头栅栏后跟语言标识 |
| 链接 | `[文本](url)` | |
| 图片 | `![描述](url)` | 写入时无需提供图片 UUID,服务会自动生成 |
| 链接预览 | `[文本](url)<!-- linkPreview -->` | 注释前无空格 |
| 分割线 | `---` | |
| 表情 | `:ShortCode:` | 大小写敏感的规范键名 |
| 居中对齐 | `<!-- center:start -->` ... `<!-- center:end -->` | 区域式 |
| 右对齐 | `<!-- right:start -->` ... `<!-- right:end -->` | 区域式 |
| 两端对齐 | `<!-- justify:start -->` ... `<!-- justify:end -->` | 区域式 |
| @提及 | `@名字<!-- mention:{JSON} -->` | 注释前无空格 |

## 扩展语法详解

### 对齐方式(区域式)

用 start/end 注释对包裹一个或多个段落,标签必须独占一行:

```markdown
<!-- center:start -->
这段文字居中显示。

这段也是居中的。
<!-- center:end -->

<!-- right:start -->
右对齐内容。
<!-- right:end -->
```

支持的值:`center`(居中)、`right`(右对齐)、`justify`(两端对齐)。左对齐为默认值,无需标记。

### @提及(带元数据)

为了在转换过程中保留用户身份信息,使用元数据格式。`@名字` 和注释之间**不能有空格**:

```markdown
@张三<!-- mention:{"id":"lark_user_id_7361251974161006596","cn_name":"张三","en_name":"Zhang San","email":"[email protected]","blockType":"AT_USER_BLOCK"} -->
```

注释中必填的 JSON 字段:

| 字段 | 说明 | 示例 |
|------|------|------|
| `id` | 用户 ID | `"lark_user_id_7361251974161006596"` |
| `cn_name` | 中文名 | `"张三"` |
| `en_name` | 英文名 | `"Zhang San"` |
| `email` | 邮箱地址 | `"[email protected]"` |
| `blockType` | 固定值 | `"AT_USER_BLOCK"` |

可选字段:`blockId`(UUID v4)、`type`(0 = 用户)、`avatar_url`。

同一行多个提及(之间不加空格):

```markdown
@张三<!-- mention:{"id":"id_1","cn_name":"张三","en_name":"Zhang San","email":"[email protected]","blockType":"AT_USER_BLOCK"} -->@李四<!-- mention:{"id":"id_2","cn_name":"李四","en_name":"Li Si","email":"[email protected]","blockType":"AT_USER_BLOCK"} -->
```

如果没有元数据,纯 `@名字` 也可接受,但无法在格式转换中完整还原。

### 图片
写入图片时只需使用标准 Markdown 图片语法,无需提供图片 UUID:

```markdown
![描述](https://example.com/page/***)
```

图片 UUID 由服务自动生成。读取富文本内容时,图片后可能带有服务返回的 UUID 注释,例如:

```markdown
![描述](https://example.com/page/***)<!-- *****-*****-**** -->
```

该 UUID 是读取结果中的图片标识;写入者无需手动生成或追加。

### 链接预览

在链接后紧跟 `<!-- linkPreview -->`(**无空格**):

```markdown
[https://example.com/page](https://example.com/page)<!-- linkPreview -->
```

### 下划线

标准 Markdown 不支持下划线,使用 HTML `<u>` 标签:

```markdown
<u>带下划线的文本</u>
```

可与其他格式嵌套:

```markdown
*<u>斜体加下划线</u>*
**<u>加粗加下划线</u>**
```

### Span 样式

字体颜色、背景颜色、字体大小使用 `<span>` 的 `style` 属性。**颜色必须用 `rgb(R, G, B)` 格式**(不支持 hex 和颜色名):

```markdown
<span style="color: rgb(245, 74, 69)">红色文字</span>
<span style="background-color: rgb(53, 189, 75)">绿色背景</span>
<span style="font-size: 18px">大号文字</span>
```

### 表情短代码

使用 `:CODE:` 格式。键名大小写敏感,解析时会归一化到 lark 规范形式:

```
:OK: :DarkThumbsup: :THANKS: :DarkFightOn: :DarkFingerHeart: :APPLAUSE: :LightFistBump: :JIAYI: :DONE: :SMILE: :Delighted: :BeamingFace: :BLUSH: :LAUGH: :SMIRK: :LOL: :FACEPALM: :LOVE: :ERROR: :CRY: :SOB: :THINKING: :SCOWL: :SMART: :WITTY: :PROUD: :WINK: :NOSEPICK: :HAUGHTY: :SLAP: :SPITBLOOD: :TOASTED: :ColdSweat: :BLACKFACE: :FullMoonFace: :GLANCE: :DULL: :ROSE: :HEART: :PARTY: :INNOCENTSMILE: :SHY: :CHUCKLE: :JOYFUL: :WOW: :OBSESSED: :DROOL: :SMOOCH: :KISS: :EMBARRASSED: :TEARS: :ENOUGH: :YEAH: :TRICK: :MONEY: :TEASE: :SHOWOFF: :COMFORT: :CLAP: :PRAISE: :STRIVE: :XBLUSH: :SILENT: :HUG: :WHIMPER: :CRAZY: :WAIL: :LOOKDOWN: :DIZZY: :FROWN: :WHAT: :WAVE: :BLUBBER: :WRONGED: :HUSKY: :SHHH: :SMUG: :ANGRY: :HAMMER: :SHOCKED: :TERROR: :PUKE: :SICK: :YAWN: :DROWSY: :SLEEP: :SPEECHLESS: :SWEAT: :SKULL: :PETRIFIED: :BETRAYED: :HEADSET: :EatingFood: :Typing: :Lemon: :Get: :LGTM: :OnIt: :OneSecond: :YouAreTheBest: :Shrug: :ThanksFace: :SaluteFace: :GoGoGo: :Partying: :VRHeadset: :MeMeMe: :Sigh: :DarkSalute: :DarkShake: :LightHighFive: :DarkWavingHand: :DarkClick: :DarkThumbsDown: :ClownFace: :SLIGHT: :TONGUE: :LIPS: :SiSiASYouWish: :HappyDragon: :JubilantRabbit: :RoarForYou: :CALF: :BULL: :BEAR: :EYESCLOSED: :BEER: :CAKE: :GIFT: :CUCUMBER: :Drumstick: :Pepper: :CANDIEDHAWS: :BubbleTea: :Coffee: :Pin: :AWESOMEN: :Hundred: :MinusOne: :CrossMark: :CheckMark: :OKR: :No: :Yes: :Alarm: :Loudspeaker: :Trophy: :Fire: :RAINBOWPUKE: :Music: :TV: :Movie: :Pumpkin: :LUCK: :FORTUNE: :REDPACKET: :BeAtTheForefront: :2026: :FIREWORKS: :XmasHat: :Snowman: :XmasTree: :FIRECRACKER: :StickyRiceBalls: :Mooncake: :MoonRabbit: :HEARTBROKEN: :BOMB: :POOP: :18X: :CLEAVER: :GeneralWorkFromHome: :GeneralBusinessTrip: :StatusFlashOfInspiration: :StatusReading: :GeneralInMeetingBusy: :Status_PrivateMessage: :GeneralDoNotDisturb: :Basketball: :Soccer: :StatusEnjoyLife: :GeneralTravellingCar: :StatusBus: :StatusInFlight: :GeneralSun: :GeneralMoonRest: 
```

解析器会归一化大小写(`:smile:` → `:SMILE:`,`:beamingface:` → `:BeamingFace:`),但建议直接使用规范写法。

### 列表 — 4 空格缩进

嵌套列表使用 **4 个空格**(不是 2 个)缩进:

```markdown
1. 第一级有序
    1. 第二级(4 空格)
        1. 第三级(8 空格)
    - 混合:有序中嵌套无序(4 空格)
- 第一级无序
    - 第二级
        - 第三级
    1. 混合:无序中嵌套有序
```

任务列表:

```markdown
- [ ] 未完成任务
- [x] 已完成任务
    - [ ] 嵌套未完成
    - [x] 嵌套已完成
```

### 引用块

支持嵌套和内部块级内容:

```markdown
> 带 **加粗** 和 *斜体* 的引用
> 1. 引用内有序列表
> 2. 第二项
>     1. 引用内嵌套列表
> - 引用内无序列表
```

### 代码块

使用围栏式代码块,开头标注语言:

````markdown
```TypeScript
function add(a: number, b: number): number {
  return a + b;
}
```

```Go
func add(a, b int) int {
    return a + b
}
```
````

### GFM 表格(简单内容)

表格内仅包含行内内容(文本、加粗、链接等)时使用:

```markdown
| 表头1 | 表头2 | 表头3 |
|-------|-------|-------|
| 单元格1 | **加粗** | [链接](url) |
| 单元格3 | 单元格4 | 单元格5 |
```

### HTML 表格(单元格内含富内容)

当表格单元格需要块级元素(标题、列表、代码块、对齐、图片)时,使用 HTML `<table>` 语法。**`<td>` 后和 `</td>` 前必须留空行**,这样内部的 Markdown 才能被正确解析:

```markdown
<table>
<tr>
<td>

# 单元格内标题

**加粗段落**

</td>
<td>

1. 有序列表
2. 在单元格中
    - 嵌套项

</td>
<td>

<!-- center:start -->
单元格内居中
<!-- center:end -->

</td>
</tr>
<tr>
<td>

```TypeScript
// 单元格内代码块
const x = 1;
```

</td>
<td>

> 单元格内引用块

</td>
<td>

<span style="color: rgb(245, 74, 69)">单元格内彩色文字</span>

</td>
</tr>
</table>
```

空单元格:`<td></td>`

## 格式组合

行内样式可以嵌套使用:

```markdown
**~~加粗删除线~~**
*<u>斜体下划线</u>*
[**加粗链接**](https://example.com)
<span style="color: rgb(245, 74, 69)">**红色加粗**</span>
```

## 段落分隔

每个块级元素(段落、标题、列表组、表格、代码块、引用块)之间用空行分隔:

```markdown
# 标题

第一段正文。

第二段正文。

- 列表项 1
- 列表项 2

列表后面的段落。
```

## 常见错误

| 错误写法 | 正确写法 |
|----------|----------|
| `<b>加粗</b>` | `**加粗**` |
| `<i>斜体</i>` | `*斜体*` |
| `<s>删除</s>` 或 `<del>删除</del>` | `~~删除~~` |
| `<span style="color: #ff0000">` | `<span style="color: rgb(255, 0, 0)">` |
| `<span style="color: red">` | `<span style="color: rgb(255, 0, 0)">` |
| `<!-- center -->文本<!-- /center -->` | `<!-- center:start -->\n文本\n<!-- center:end -->` |
| `@名字 <!-- mention:... -->`(有空格) | `@名字<!-- mention:... -->`(无空格) |
| `[链接](url) <!-- linkPreview -->`(有空格) | `[链接](url)<!-- linkPreview -->`(无空格) |
| 2 空格嵌套列表缩进 | 4 空格嵌套列表缩进 |
| `:smile:`(全小写) | `:SMILE:`(使用规范大小写) |
| `![图片](url)<!-- 图片uuid -->`(手动填写 UUID) | `![图片](url)`(写入时由服务自动生成 UUID) |
| `![图片](url)<!-- linkPreview -->` | `<!-- linkPreview -->` 仅用于 `[文本](url)` 链接 |
| `<td>文本</td>`(`<td>` 后无空行) | `<td>\n\n文本\n\n</td>`(需要空行) |

## 完整示例

```markdown
# 项目进展

## 状态

<!-- center:start -->
**Alpha 项目 — 迭代评审**
<!-- center:end -->

本迭代完成了以下工作:

1. 用户认证
    1. 登录流程
    2. 密码重置
2. 仪表盘改版
    - 新布局
    - 性能优化

### 关键指标

| 指标 | 改版前 | 改版后 |
|------|--------|--------|
| 加载耗时 | 3.2s | **1.1s** |
| 错误率 | 2.4% | <span style="color: rgb(53, 189, 75)">0.3%</span> |

### 代码变更

```TypeScript
export function authenticate(token: string): boolean {
  return validateJWT(token);
}
```

> 注意:部署前需要更新 <u>环境配置</u>。

- [x] 代码评审已完成
- [x] 测试通过
- [ ] 部署到预发环境

负责人:@张三<!-- mention:{"id":"lark_user_id_001","cn_name":"张三","en_name":"Zhang San","email":"[email protected]","blockType":"AT_USER_BLOCK"} -->@李四<!-- mention:{"id":"lark_user_id_002","cn_name":"李四","en_name":"Li Si","email":"[email protected]","blockType":"AT_USER_BLOCK"} -->

参考文档:[迭代看板](https://example.com/sprint/42)<!-- linkPreview -->

:DONE: :DarkThumbsup:
```
references/sop-create-workitem.md›
# 创建工作项

> **CRITICAL** — 开始前 MUST 先用 Read 工具读取 `../SKILL.md`,其中包含前置检查、授权流程、命令参数参考、字段值格式、通用规范和错误处理。

本技能用于在飞书项目中创建工作项(需求、任务、缺陷等),全程自动化执行。

---

## 执行流程

### STEP 1 — 提取意图

从用户输入中提取:
- **空间名**:哪个项目空间
- **工作项类型**:需求 / 任务 / 缺陷 / 其他
- **字段值**:标题、优先级、负责人、描述等
- **URL**(如有):先调 `url decode` 解析。`url_kind` 非 `workitem_create` / `workitem_detail` 时按 [url-kinds.md](url-kinds.md) 拒绝或追问;命中则用返回的 `simple_name` 取代空间名探测,`work_item_type` 取代类型探测。**禁止**自己从 URL 截取路径段作参数。


### STEP 2 — 确认空间和类型

1. 用 `project search` 验证空间 → 获取 `project_key`
2. 用 `workitem meta-types` 获取类型列表 → 确认 `work_item_type`

> 唯一匹配则直接用,多个匹配则展示列表让用户选,无匹配则问用户。**禁止猜测。**

### STEP 3 — 收集元数据(并行)

同时发起以下调用:

| 调用 | 目的 |
|------|------|
| `workitem meta-create-fields` | 获取该类型的**必填字段**元信息(前置校验,防止因空间自定义必填项缺失导致创建失败) |
| `workitem meta-fields(field_keys=["template"])` | **获取模板 ID**(创建必填) |
| `workitem meta-fields(field_query="用户提到的字段名")` | 确认字段 key、类型、枚举值(通过 `field_keys` 精确匹配或 `field_query` 模糊搜索,避免全量拉取时因分页导致自定义字段遗漏) |
| `workitem meta-roles(page_num=1)` | 获取角色定义(如用户指定了负责人等角色) |

如用户提到了人名,并行调用 `user search` 转换为 userkey。

> 默认只查询在职用户。只有当用户明确要求包含离职、停用等非在职人员,或在职结果为空且用户要求继续查找时,才给 `user search` 传 `--need-all-status=true`。

**附件字段门禁**:只要用户要求上传附件,就必须额外调用 `workitem meta-fields`,实时查询当前空间和类型下全部 `file` 和 `multi-file` 字段。用户指定字段名或 key 时做精确匹配;用户只说“附件”且出现多个候选时,列出字段名、key、类型并让用户选择。禁止默认取第一个,禁止复用历史会话里的附件字段,禁止把 `attachment` 或 `multi_attachment` 写死。确定目标后,上传、创建和回读都必须使用同一个字段 key。完整协议见 [attachment.md](attachment.md)。

### STEP 4 — 自动匹配模板

根据 STEP 3 获取的模板枚举值:
- **只有一个模板** → 自动选择
- **多个模板** → 根据用户描述中的关键词匹配最接近的模板名,选不出来时展示列表让用户选
- **用户明确指定了模板名** → 精确匹配

### STEP 5 — 自动补全必填项与转换字段值

**1. 自动补全必填项**

严格对比 `workitem meta-create-fields` 返回的必填字段与用户已给定的字段。对于缺失的必填项(尤其是空间自定义的特殊必填项),自动生成合理的默认值:
- 枚举:默认取第一项
- 文本:填"自动生成"
- 布尔:填 true

**2. 转换字段值**

将用户给的自然语言值及自动生成的默认值转换成 API 需要的格式:

| 来源 | 转换 |
|------|------|
| 人名 | 调用 `user search` 批量转换为 userkey |
| 枚举值 | 从字段配置的 options 中按 option_name 匹配得到 option_id |
| 日期 | 转为毫秒时间戳 |
| 其他类型 | 按主文档 [SKILL.md](../SKILL.md)「字段值格式」规范转换 |

**格式速查**(创建场景特有约束;通用类型见主文档 [SKILL.md](../SKILL.md)「字段值格式」章节):

> 🚨 **关键约定**:`field_value` 协议层是 **STRING**。标量直接字符串化;数组/对象**必须先 JSON.stringify**,否则会报 `need STRING type, but got: LIST`。

| 字段类型 | 差异化提示 |
|---------|-----------|
| `role_owners` | **仅创建时可用**;stringified 对象数组 `"[{\"role\":\"<role_id>\",\"owners\":[\"<userkey>\"]}]"` |
| `signal` | option_id 纯字符串 `"<option_id>"`(以 `workitem meta-fields` 的 `options[].option_id` 为准;不接受 `"true"`/`"false"`/`"null"`) |
| `workitem_related_multi_select` | **stringified** ID 数组,**禁止写入自身 ID**(防循环引用,触发 `exists loop`) |
| `multi-file` | 按 [attachment.md](attachment.md) 完成“本地文件校验 → 字段消歧 → 对象存储上传 → 字段绑定 → 写后验收”。单附件可随创建写入;多个附件先随创建写入第一个,获得工作项 ID 后再逐个读取、合并并更新,避免 `unique` 校验与覆盖丢失 |
| `file` | 按 [attachment.md](attachment.md) 的兼容性规则处理;不得假设旧版或单附件字段支持多附件,也不得与 `multi-file` 共用未经验证的多附件写入协议 |

> 其余通用字段类型(text / user / multi-user / date / schedule / precise_date / select 系列等)写入格式详见主文档 [SKILL.md](../SKILL.md)「字段值格式」章节。

**角色设置**(创建时):通过 fields 中的 `role_owners` 字段,值为 stringified 对象数组:

```json
{"field_key":"role_owners","field_value":"[{\"role\":\"<role_id>\",\"owners\":[\"<userkey>\"]}]"}
```

> **角色补充**:创建时除了能在 `fields` 数组内处理极少部分内置的 role_owners 字段外,针对其他自定义角色(如 PO、PM、Tech Lead 等),须在创建后用 `workitem update` 的 `role_operate` 参数追加写入。

### STEP 6 — 创建

```bash
meegle workitem create --work-item-type 类型key --fields '[{"field_key":"template","field_value":"模板ID"},{"field_key":"name","field_value":"标题"}]' --project-key 空间key --format json
```

🚨 **批量创建**:当用户要求批量创建多个工作项时,必须**串行调用**(逐个请求),禁止高并发,以免触发平台限流。每个 field_value 均须符合「字段值格式」的 STRING 约定(标量直接字符串化;数组/对象 JSON.stringify)。

### STEP 7 — 确认结果

创建成功后,如涉及附件,必须先调 `workitem get` 回读已消歧的目标字段,逐一核对请求文件名和数量。上传接口返回 token、创建接口返回成功,均不能替代目标字段的写后验收;回读了其他附件字段也不算成功。

验收通过后,向用户展示:
- 工作项 ID 和名称
- 链接(如返回中包含)
- 已设置的关键字段摘要
- 附件目标字段名/key、实际文件名和数量;无法验证页面布局时仅声明 API 字段写入成功

---

## 特殊字段写入规则

### 级联选项 (Tree-select)

1. **格式极简**:对于业务线(`_business`)等级联单选字段,`field_value` **只传 `option_id` 纯字符串**,不要传 `value/label/children` 的复杂 JSON。
2. **层级强校验**:如果报错 `级联选项字段值不满足层级配置`,说明该字段要求选到末级叶子节点。此时查询该选项的 `children` 树,**将末级叶子节点列表展示给用户选择**,禁止自行向下盲猜。

### 不可写入的字段类型

以下字段类型不支持通过 API 写入,遇到时**直接跳过并告知用户原因**:

| 类型 | 原因 |
|------|------|
| `vote-boolean`(轻量表态) | 计数器,只能由用户在界面操作 |
| `vote-option` / `vote-option-multi`(投票) | 不支持通过接口伪造投票结果 |
| 计算字段 | 系统自动计算,只读 |

### 富文本与关联字段

- **富文本/多行文本**:直接传 Markdown 字符串(`# 标题`、`|列1|列2|`、代码块等)。
- **关联云文档(PRD)**:传 URL 数组,如 `["https://xxx.feishu.cn/docx/xxx"]`。
- **前置依赖/关联工作项**:传目标工作项 ID。**注意**:不同空间可能要求字符串 `"<work_item_id>"` 或数字格式,遇到类型校验失败立刻切换格式重试。用户提供的是工作项**名称而非 ID** 时,按 [field-value-extras.md](field-value-extras.md)「关联工作项名称 → ID 转换」完整流程处理。
- **系统外信号类型 (`signal`)**:传 `option_id` 纯字符串(如 `"<option_id>"`;以 `workitem meta-fields` 的 `options[].option_id` 为准)。**不接受 `"true"` / `"false"` / `"null"`**。当前系统外信号常见 4 个 option:`passed` / `notpassed` / `processing` / `noinformationyet`。

---

## 错误自动恢复(自愈机制)

> 通用自愈规则(格式错误、级联层级、枚举不合法)见 [error-handling.md](error-handling.md)。以下为本 SOP 补充规则:

| 报错特征 | 自愈动作 |
|---------|---------|
| `json: unsupported type` / 网络超时 | 原参数直接重试 |
| 字段 key 不匹配 | 用 `field_query` 模糊搜索取最佳匹配 |
| 人名解析失败 | 尝试用邮箱前缀再搜一次 |
| 明确缺少必填字段 | 核对字段类型限制,关联工作项尝试数字↔字符串切换 |

---

## 熔断机制 (Circuit Breaker)

> 通用熔断规则(空间未找到、权限不足)见主文档 [SKILL.md](../SKILL.md)「错误处理」。以下为本 SOP 补充规则:

1. **工作项类型未找到**:`workitem meta-types` 失败超过 3 次
2. **字段转换大面积失败**:字段值转换失败比例 > 60%,终止流程并列出失败字段明细,**不要强行创建残缺数据**

---

## 常见问题

| 问题 | 处理 |
|------|------|
| 用户未指定空间 | 问用户 |
| 用户未指定类型 | 如空间只有一种类型则直接用,否则问用户 |
| 用户提到的字段不存在 | `workitem meta-fields(field_query="关键词")` 模糊查询,找不到则告知用户 |
| 模板有多个 | 根据关键词匹配,匹配不到则展示列表让用户选 |
| 枚举值匹配不到 | 展示该字段所有枚举值让用户选 |
| 人名匹配到多人 | 展示完整列表让用户指定,**禁止自行选择** |
references/sop-transition-node.md›
# 流转节点

> **CRITICAL** — 开始前 MUST 先用 Read 工具读取 `../SKILL.md`,其中包含前置检查、授权流程、命令参数参考、字段值格式、通用规范和错误处理。

本技能用于在飞书项目中流转节点流工作项的节点(confirm/rollback),全程自动化执行。

> **注意**:此技能仅用于**节点流**工作项(如需求),不适用于状态流工作项(如缺陷)。状态流转参见主文档 [SKILL.md](../SKILL.md) 的 `workflow transition-state`。

---

## 核心设计原则:最小查询 + 按需补充

`workflow transition` 工具**只接受 node_key(节点 ID),不支持传节点名称**。因此必须先通过 `workflow get-node` 获取名称→node_key 映射。但查询应保持精准轻量:

- **用户指定了节点名** → `node_id_list` 直接传中文名称 `["节点名"]`,精准查单个节点
- **用户说"所有节点"** → 传 `["_all"]` 查全量,但**不传 `field_key_list`**(不查表单字段)
- **直接尝试流转**:拿到 node_key 后立即调 `workflow transition`
- **按需补充字段**:仅当流转失败(提示必填字段未填)时,才查询必填字段并补充

> **为什么不预查全量字段?** `workflow get-node` 有分页限制(每页 20 个节点),查全量字段极慢。大多数场景无需补充字段,只在流转失败时按需查目标节点的必填字段即可。

---

## 执行流程

### STEP 1 — 定位工作项

从用户输入中提取 work_item_id 和 project_key:
- 用户给了 **URL** → 先调 `url decode`。只有 `url_kind == workitem_detail` 才能进入本 SOP;其他 kind 按 [url-kinds.md](url-kinds.md) 拒绝或追问
- 用户给了 **ID** → 需同时确定 project_key
- 信息不足时才追问

> **URL 处理**:decode 返回的 `simple_name` 必须再调 `project search` 转为权威 `project_key`(同名空间可能有多个无权限)。**禁止**自己从 URL 截取路径段作参数。`work_item_id` 参数必须是字符串类型。

### STEP 2 — 精准查节点

根据用户意图选择最高效的查询方式:

| 用户意图 | `node_id_list` 传参 | `field_key_list` | 说明 |
|---|---|---|---|
| 指定了节点名(如"完成开发中") | `["开发中"]`(直接传中文名) | 不传 | 精准查单个节点 |
| 指定了多个节点名 | `["开发中", "测试中"]` | 不传 | 精准查指定节点 |
| "当前节点" / 未指定节点 | `["_all"]` | 不传 | 查全量找 status="doing" |
| "所有节点" / "全部流转" | `["_all"]` | 不传 | 查全量但不带字段 |

从返回结果中获取 **name**、**node_key**、**status**(finished/doing/not_started)。

**自动确定操作类型:**
- "完成"、"流转"、"确认"、"推进" → action = `confirm`
- "回滚"、"退回"、"撤回" → action = `rollback`
- 未明确说 → 默认 `confirm`

**目标节点确定:**
- 用户指定了节点名 → 从返回结果中取 node_key
- 用户说"当前节点" → 选 status = "doing" 的节点
- 用户说"所有节点" → 按顺序逐个处理未完成节点
- 用户没指定 → 自动选当前进行中的第一个节点
- 名称匹配不到 → 用 `["_all"]` 重新查全量,列出所有节点供用户选择

**回滚操作**:从用户输入提取原因;用户没给则用"用户发起回滚"作默认原因。

### STEP 3 — 直接尝试流转

```bash
meegle workflow transition --work-item-id 工作项ID --project-key 空间key --node-id 节点node_key --action confirm --rollback-reason '{{rollback_reason}}' --format json
```

**三种结果分支:**

| 结果 | 处理 |
|---|---|
| 流转成功 | 直接跳到 STEP 6 返回结果 |
| 必填字段未填写 | 进入 STEP 4 补充字段 |
| 其他错误(权限/节点不存在等) | 进入错误恢复逻辑 |

### STEP 4 — 按需补充必填字段(仅流转失败时)

只有当 STEP 3 流转失败、提示必填字段未填时才进入本步骤。

**4.1 查询未完成的必填字段**

```bash
meegle workflow list-state-required --work-item-id 工作项ID --state-key 目标节点node_key --project-key 空间key --mode {{mode}} --format json
```

传入 `mode = "unfinished"` 仅查未完成必填项。从返回中识别每个字段的 `form_item_type`(node_field / field)和 `field_type`。

**4.2 评审结论 / 评审意见(`node_finished_conclusion` / `node_finished_opinion`)**

这是节点的「整体完成结论 / 意见」,可经接口读写,但**前提是该节点已启用这两个字段**——只有节点开了「完成结论」配置,`workflow get-node` 的 `form_items` 里才会出现它们;没启用时写入会报 `node field is invalid`。所以**先读 `form_items` 确认字段存在,再写**。

**读取**:用 `workflow get-node` 按 `field_key_list` 读,或用 `workitem get` 按 `fields` 读。字段未启用时返回里不会出现对应项:

```bash
meegle workflow get-node --work-item-id 工作项ID --field-key-list '["node_finished_conclusion","node_finished_opinion"]' --need-sub-task {{need_sub_task}} --page-num {{page_num}} --project-key 空间key --node-id-list '["节点node_key"]' --format json
```

**查询结论选项**:结论是 select 型,用 `workflow meta-node-fields` 查 options,写入用其 `option_id`:

```bash
meegle workflow meta-node-fields --field-keys '["node_finished_conclusion"]' --field-types '{{field_types}}' --project-key 空间key --work-item-type 类型key --query '{{query}}' --format json
```

**写入**:经 `workflow update-node` 的 `fields` 写入——结论写合法 `option_id`,意见写文本。

> 🚨 **必须分两次调用,一次只写一个字段。** `workflow update-node` 的 `fields` 数组里如果同时放结论和意见,**只有第一个字段会落库,其余被静默丢弃**(返回仍是 `success`)。这和「排期 / 负责人不要同时改」是同一类约束。每次写完都要 `workflow get-node` 回读校验,不要凭返回的 success 断言已写入。

```bash
meegle workflow update-node --work-item-id 工作项ID --node-schedule '{{node_schedule}}' --schedules '{{schedules}}' --fields '[{"field_key":"node_finished_conclusion","field_value":"option_id"}]' --project-key 空间key --node-id 节点node_key --node-owners '{{node_owners}}' --format json
```

```bash
meegle workflow update-node --work-item-id 工作项ID --node-schedule '{{node_schedule}}' --schedules '{{schedules}}' --fields '[{"field_key":"node_finished_opinion","field_value":"评审意见文本"}]' --project-key 空间key --node-id 节点node_key --node-owners '{{node_owners}}' --format json
```

**4.3 硬拦截:不可写入的字段类型**

以下字段类型 **API 无法写入**。如果被设为流转必填项,**立即中断当前节点的流转**并告知用户:

| 字段类型 | 说明 | 拦截原因 |
|---|---|---|
| `actual_work_time` | 实际工时 | 需在页面手动登记 |
| `owners_finished_info` | 负责人完成结论与意见 | 仅各负责人可在页面操作 |
| `vote-boolean` / `vote-option` / `vote-option-multi` | 投票类 | 仅支持页面交互 |
| 计算字段 | 系统自动计算 | 只读 |

🚨 遇到硬拦截时输出:
> "节点流转失败。当前节点【节点名称】设置了必须填写【字段名称】(类型:xxx)才能流转。由于该字段类型不支持自动化补充,请您在飞书项目页面手动填写后,再通知我继续流转。"

**4.4 可补充字段的值转换**

**人员字段处理规则(极重要):**
- 用户明确指定了人员 → `user search` 转 userkey
- 搜索到多个同名用户 → 若用户说"分配给我自己"用 `current_login_user()`,否则**必须向用户确认**
- 用户未指定但为必填 → **向用户询问**,不要自动默认为当前用户
- 唯一例外:用户明确说"我来负责"/"分配给我"时才用 `current_login_user()`

**节点专属字段**(使用 `workflow update-node` 的专用参数):

| field_key | 更新方式 |
|---|---|
| `owner` (multi-user) | `node_owners` 参数。用户指定人 → search 转 userkey;用户说"我来" → current_login_user();**未指定 → 询问** |
| `schedule` | `node_schedule` 参数,格式 `{"estimate_start_date": ms, "estimate_end_date": ms, "owners": [userkey], "points": 数字}` |
| `point` (number) | `node_schedule` 中的 `points` 字段 |

> 清空节点负责人时传空数组 `[]`(不是 `["_all"]`,`["_all"]` 仅用于 `update_field` 中删除角色配置)。

> 评审结论 / 意见(`node_finished_conclusion` / `node_finished_opinion`)的读写口径见 §4.2:需节点已启用该字段,且**结论与意见必须分两次 `update-node` 调用**(一次只落第一个字段)。

**通用字段类型转换**(完整格式见主文档 [SKILL.md](../SKILL.md)「字段值格式」):

> 🚨 **关键约定**:表单字段 `field_value` 协议层是 **STRING**。标量直接传字符串;数组/对象**必须 JSON.stringify**,否则报 `need STRING type, but got: LIST`。(上方「节点专属字段」走 `workflow update-node` 的专用参数,不受此约定影响。)

| field_type | 节点流转场景差异化提示 |
|---|---|
| `schedule`(表单字段) | **stringified** `"[开始ms,结束ms]"`;**节点专属排期不走本表**,用 `workflow update-node` 的 `node_schedule` 参数 |
| `signal` | `option_id` 字符串(以 `workitem meta-fields` 的 `options[].option_id` 为准;不接受 `"true"`/`"false"`/`"null"`) |
| `workitem_related_multi_select` | **stringified** ID 数组,**禁止写入自身 ID**(防循环引用,触发 `exists loop` 报错) |
| `tree-select` | 只传 `"option_id"` 纯字符串,不传 value/label/children 复杂 JSON |
| `multi-file` | 按 [attachment.md](attachment.md) 执行完整协议:实时消歧目标字段、完成对象存储上传、写入字段并回读同一字段验收;多个附件逐个追加 |
| `file` | 按 [attachment.md](attachment.md) 的兼容性规则处理,不得假设它与 `multi-file` 具有相同的多附件能力 |

> 其余通用字段类型(text / number / bool / user / multi-user / date / precise_date / select 系列 / multi-text / telephone / email / workitem_related_select 等)写入格式详见主文档 [SKILL.md](../SKILL.md)「字段值格式」章节。

> **用户提供的是工作项名称而非 ID** 时,按 [field-value-extras.md](field-value-extras.md)「关联工作项名称 → ID 转换」完整流程(获取目标约束 → `workitem query` 搜索 → 消歧 → 按类型写入)处理。

**节点字段 vs 工作项字段**:
- `form_item_type = "node_field"` → `workflow update-node`,传 `node_id` + `fields`
- `form_item_type = "field"` → `workitem update`(工作项级别)
- 节点枚举值用 `workflow meta-node-fields` 查询;工作项枚举值用 `workitem meta-fields` 查询(用 `field_keys` 精确或 `field_query` 模糊搜索,禁止逐页遍历)

**4.5 字段补充执行策略**

🚨 **效率要求**:并行完成所有必填字段补充,禁止逐个串行。

1. **分类**:节点负责人(owner)→ `node_owners`;排期/估分 → `node_schedule`;其他字段 → `fields` 或 `workitem update`
2. **节点负责人和排期/估分不可同时更新**,需分两次调用 `workflow update-node`
3. 其他节点字段通过 `workflow update-node` 的 `fields` 参数批量更新
4. 工作项字段通过 `workitem update` 的 `fields` 参数批量更新

**4.6 用户未提供值时**:
- 人员类 → **必须询问**
- 排期/日期类 → **询问用户**
- 枚举类 → 列出选项让用户选
- 文本/数字/布尔 → 可给合理默认值(bool 默认 false,估分默认 1)
- 待确认字段 > 3 个时,一次性列出让用户批量回复

### STEP 5 — 补充后再次流转

字段补充完成后,**再次调用 `workflow transition`** 执行流转。仍然失败则读取错误信息重新处理(最多重试 2 次)。

### STEP 6 — 返回结果

展示表格汇总:

| 节点名称 | 操作 | 结果 | 备注 |
|---|---|---|---|
| 需求评审 | confirm | ✅ 成功 | — |
| 开发中 | confirm | ✅ 成功 | 自动补充了排期、估分、负责人 |
| 测试中 | confirm | ❌ 阻塞 | 必填字段「实际工时」不支持 API 更新 |

如果有阻塞节点,明确列出需要用户手动操作的字段和原因。

---

## 批量流转

当用户说"所有节点"/"全部流转"时:
1. 按节点顺序依次流转:直接调 `workflow transition` → 失败则按需补充 → 下一个
2. 每个节点独立处理,某个节点被阻塞不影响已完成的节点
3. 最终汇总所有节点的流转结果

---

## 错误自动恢复(自愈机制)

> 通用自愈规则(格式错误、级联层级、枚举不合法)见 [error-handling.md](error-handling.md)。以下为本 SOP 补充规则:

| 报错特征 | 自愈动作 |
|---------|---------|
| 节点名匹配不到 | 用 `["_all"]` 查全量节点,模糊匹配;仍失败则列出所有节点供用户选择 |
| 必填字段缺失 | 进入 STEP 4 按需补充 |

---

## 熔断机制 (Circuit Breaker)

> 通用熔断规则(空间未找到、权限不足)见主文档 [SKILL.md](../SKILL.md)「错误处理」。以下为本 SOP 补充规则:

1. **必填字段全部为硬拦截类型**:当前节点所有未完成必填字段都属于不可写类型
2. **连续流转失败**:同一节点重试 2 次仍然失败

---

## 常见问题

| 问题 | 处理 |
|------|------|
| 用户未指定工作项 | 追问工作项 ID 或 URL |
| 用户未指定节点 | 自动选 status="doing" 的当前节点 |
| 用户操作的是状态流工作项 | 提示改用 `workflow transition-state`,本技能仅处理节点流 |
| 流转报"必填字段未填" | 进入 STEP 4 按需补充 |
| 补充字段后仍失败 | 检查是否存在硬拦截字段,明确告知用户 |
references/sop-transition-state.md›
# 流转工作项状态(状态流)

> **CRITICAL** — 开始前 MUST 先用 Read 工具读取 `../SKILL.md`,其中包含前置检查、授权流程、命令参数参考、字段值格式、通用规范和错误处理。

本 Skill 用于**状态流工作项**(如缺陷 / issue)的状态流转,全程自动化编排。

> ⚠️ **仅限状态流**。需求 / story 等节点流工作项须改用 `workflow transition`(action=confirm/rollback),不要混用本 Skill。

---

## 执行流程

### STEP 1 — 定位工作项 + 获取当前用户(并行)

**并行执行**:

1. **定位工作项**:从用户输入中提取 `work_item_id`、`project_key`、`work_item_type`。
   - **URL 解析**:用户给了链接则先调 `url decode`。只有 `url_kind == workitem_detail` 才能进入本 SOP;其他 kind 按 [url-kinds.md](url-kinds.md) 拒绝或追问。decode 返回的 `simple_name` 必须再调 `project search` 转为权威 `project_key`(同名空间可能有多个无权限)。**禁止**自己从 URL 截取路径段作参数。
   - **ID 类型**:传给任何工具的 `work_item_id` 必须是 **字符串(String)**。
   - 信息不足才追问。
2. **获取当前用户**:调用 `user me`,从返回体取 `user_key`(下一步必填)。`current_login_user()` 仅为 MQL 内置函数字面量,`user search` 不会解析该字符串。

### STEP 2 — 查询可流转状态并匹配目标

```bash
meegle workflow list-state-transitions --work-item-id 工作项ID --work-item-type 类型key --user-key 当前用户userkey --project-key 空间key --format json
```

- **匹配目标状态**:精确 / 模糊 / 语义匹配用户意图(如"关闭" → "已关闭","解决" → "已解决")拿到对应的 `transition_id`。
- 🚨 **未明确目标状态且有多个候选时**:**必须展示所有可选项让用户选择**,不得替用户默认选择。

### STEP 3 — Fail-fast 直接尝试流转

**不要前置查询必填项**,直接调用 `workflow transition-state`:

```bash
meegle workflow transition-state --work-item-id 工作项ID --project-key 空间key --transition-id 上一步拿到的ID --format json
```

- **流转成功** → 跳到 STEP 6 返回结果。
- **失败且提示必填字段未填** → 进入 STEP 4 按需补充。
- **失败其他报错** → 参考下方「智能修复」章节。

### STEP 4 — 按需收集并补充必填字段(仅流转失败时触发)

调用 `workflow list-state-required` 获取目标状态所需必填项:

```bash
meegle workflow list-state-required --work-item-id 工作项ID --state-key 目标状态key --project-key 空间key --mode {{mode}} --format json
```

> 若工具支持 `mode` 参数(如 `mode="unfinished"`),优先只查尚未填写的字段,减少噪音。

**4.1 硬拦截:不支持 API 更新的字段类型**

遇到以下字段被设为必填,**立即中断流转**并提示用户在页面手动填写:

- `vote-boolean` / `vote-option` / `vote-option-multi`(投票类)
- 计算字段(系统自动计算,只读)

> **复合明细表可先通过 `workitem update` 补充**,但 `compound_field` 与 `multi_user_compound_field` 的写协议不同;多人复合字段还是整体覆盖,必须先读取并保留全部人员和非目标数据。格式详见主文档 [SKILL.md](../SKILL.md)「字段值格式 → 复合明细表」章节;无法自动判断子字段值或人员范围时仍需询问用户。

> 中断话术示例:「流转失败。当前状态需要填写【字段名】,该字段不支持自动化补充,请在页面手动填写后通知我继续。」

**4.2 枚举选项的前置查询(批量 + 精准)**

缺失项包含枚举类(select/radio/multi-select/tree-select 等)时:

- 将所有目标 `field_key` 数组**一次性**传入 `workitem meta-fields` 的 `field_keys` 精准查询,拿到 `option_name` 与 `option_id`。**绝不逐页遍历全量配置。**
- 将所有必填项及可选值**汇总为一条消息**向用户展示并询问。

```bash
meegle workitem meta-fields --page-num 1 --project-key 空间key --work-item-type 类型key --field-types '{{field_types}}' --field-keys '["key1","key2"]' --field-query '{{field_query}}' --format json
```

**4.3 字段 Mock 与询问边界(安全底线)**

- **业务决策类、人员、日期、枚举类字段** → **禁止 AI 编造或 mock 数据**。必须列出并询问用户。
- **人员字段(user/multi-user)** → 搜出多个同名或用户未指定时必须确认,**不可默认填当前操作者**(除非用户明确说"分配给我"/"我来处理")。
- **打回/关闭原因等纯说明性文本**(如 "Reopen 原因"、"流转说明")在用户未提供且字段语义不关键时,可默认填 `"重新打开处理"` 或 `"发起流转"`。

**4.4 字段值格式**

拿到用户确认值后,按主文档 [SKILL.md](../SKILL.md)「字段值格式」章节转换为 `field_value`。

> 🚨 **关键约定**:`field_value` 协议层是 **STRING**。标量直接传字符串;数组/对象**必须 JSON.stringify**,否则报 `need STRING type, but got: LIST`。

| 字段类型 | 状态流转场景差异化提示 |
|---------|-----------------|
| `select` / `radio` / `tree-select` | **纯字符串 `option_id`**(🚨 不要传 value/label 的 JSON) |
| `multi-select` | **stringified** `"[{\"option_id\":\"xxx\"}]"`;若字段配置允许新增选项且用户明确提出新值,可生成 8 位随机小写加下划线格式的 option_id 填入 |
| `tree-multi-select` | **stringified 字符串一维数组** `"[\"id1\",\"id2\"]"`(🚨 不可对象数组) |
| `signal` | option_id 字符串(以 `workitem meta-fields` 的 `options[].option_id` 为准;不接受 `"true"`/`"false"`/`"null"`) |
| `multi-file` | 按 [attachment.md](attachment.md) 执行完整协议:实时消歧目标字段、完成对象存储上传、写入字段并回读同一字段验收;多个附件逐个追加 |
| `file` | 按 [attachment.md](attachment.md) 的兼容性规则处理,不得假设它与 `multi-file` 具有相同的多附件能力 |

> 其余通用字段类型(text / number / bool / user / multi-user / date / schedule / precise_date / multi-text / workitem_related_select 等)写入格式详见主文档 [SKILL.md](../SKILL.md)「字段值格式」章节。

> **用户提供的是工作项名称而非 ID** 时,按主文档 [SKILL.md](../SKILL.md)「关联工作项名称 → ID 转换」完整流程(获取目标约束 → `workitem query` 搜索 → 消歧 → 按类型写入)处理。

### STEP 5 — 补完字段后再次流转

所有必填字段通过 `workitem update` 写入后,再次调用 `workflow transition-state` 触发流转:

```bash
meegle workitem update --work-item-id 工作项ID --project-key 空间key --role-operate '{{role_operate}}' --fields '[{"field_key":"xxx","field_value":"yyy"}]' --format json
```

```bash
meegle workflow transition-state --work-item-id 工作项ID --project-key 空间key --transition-id transition_id --format json
```

### STEP 6 — 返回结果

向用户展示:

- 状态变更方向(**从 XX → YY**)
- 自动 / 协助填入的必填项摘要
- 工作项 ID 与(如返回包含)链接

---

## 智能修复(自愈机制)

> 通用自愈规则(格式错误、级联层级、枚举不合法)见 [error-handling.md](error-handling.md)。本 Skill 无额外补充规则。

---

## 熔断与终止

> 通用熔断规则(空间未找到、权限不足)见主文档 [SKILL.md](../SKILL.md)「错误处理」。以下为本 Skill 补充规则:

1. **必填字段全部为硬拦截类型**(投票/计算字段),无法通过接口写入。
2. **同一目标状态**:补字段 → 再次流转连续失败 **> 2 次**。

---

## 常见问题

| 问题 | 处理 |
|------|------|
| 用户只说"关闭这个 bug"没给空间 | 如 URL 可解析则用 URL;否则向用户确认 |
| 可流转状态为空 | 说明当前状态无合法下一步,告知用户在页面核对状态流配置 |
| 用户说"改为 XX"但 XX 不在可流转列表 | 展示当前可流转状态列表,让用户重新选择 |
| 工作项实际是节点流 | 告知用户走 `workflow transition`(action=confirm/rollback),本 Skill 仅处理状态流 |
| 同名字段多个 option | 展示全部 option_name 让用户选,**禁止默认取第一项** |
| 同名人员多个 | 展示列表让用户指定,**禁止默认填当前操作者** |
references/sop-update-workitem.md›
# 更新工作项

> **CRITICAL** — 开始前 MUST 先用 Read 工具读取 `../SKILL.md`,其中包含前置检查、授权流程、命令参数参考、字段值格式、通用规范和错误处理。

本技能用于在飞书项目中更新工作项的字段值或角色成员,全程自动化执行。不包括状态流转和节点流转(由其他 Skill 负责)。

---

## 执行流程

### STEP 1 — 定位工作项并提取修改意图

从用户输入中提取:
- **目标工作项** — URL、工作项 ID 或名称
- **修改内容** — 哪些字段要改成什么值

> **URL 处理**:用户给了 URL 必须先调 `url decode`。只有 `url_kind == workitem_detail` 才能进入本 SOP;其他 kind 按 [url-kinds.md](url-kinds.md) 拒绝或追问。拿到 `simple_name` 和 `work_item_id` 后,必须再调 `project search` 把 `simple_name` 转为权威 `project_key`(同名空间可能有多个)。**禁止**自己从 URL 截取路径段作参数。`work_item_id` 参数必须是字符串类型。

🚨 **获取工作项类型(极重要)**:后续所有查询(字段配置、角色配置等)都强依赖 `work_item_type`。如果用户没有明确告知类型,**必须先调 `workitem get` 获取该实例的真实 `work_item_type`(返回体中的 `work_item_type.key`)**,绝不能猜测为 story 或 issue。

### STEP 2 — 并行查询配置

根据要修改的内容,**一轮内并行发起**:

| 调用 | 条件 | 说明 |
|------|------|------|
| `workitem meta-fields(field_query="字段名")` | 涉及字段修改 | 普通字段用 `field_query` 模糊搜索或 `field_keys` 精确匹配,禁止无筛选逐页遍历;附件字段消歧按 [attachment.md](attachment.md) 使用类型过滤并翻页到 `has_more=false` |
| `workitem meta-roles` | 涉及角色修改 | 获取角色 key |
| `user search` | 涉及人员字段 | 批量转换姓名为 userkey |

🚨 **效率要求**:普通配置必须一轮并发查询,下一轮直接执行更新,禁止逐个串行查询。唯一例外是附件字段消歧:首轮按类型过滤查询;若 `has_more=true`,必须继续串行翻页收集完全部候选后才能更新。

**附件字段门禁**:只要用户要求上传附件,就必须实时查询当前空间和真实工作项类型下全部 `file` 和 `multi-file` 字段。用户指定字段名或 key 时做精确匹配;用户只说“附件”且出现多个候选时,列出字段名、key、类型并让用户选择。禁止默认取第一个,禁止复用历史会话里的附件字段,禁止把 `attachment` 或 `multi_attachment` 写死。确定目标后,上传、更新和回读都必须使用同一个字段 key。完整协议见 [attachment.md](attachment.md)。

### STEP 3 — 转换字段值

将用户给的自然语言值转换成 API 格式(完整格式见主文档 [SKILL.md](../SKILL.md)「字段值格式」):

> 🚨 **关键约定**:`field_value` 协议层是 **STRING**。标量直接字符串化;数组/对象**必须 JSON.stringify**,否则报 `need STRING type, but got: LIST`。

| field_type | 更新场景差异化提示 |
|---|---|
| `user` | 姓名 → `user search` → 单个 userkey 字符串;多人同名时用户说"分配给我自己"用 `current_login_user()`,否则**必须确认** |
| `signal` | `option_id` 字符串(以 `workitem meta-fields` 的 `options[].option_id` 为准;不接受 `"true"`/`"false"`/`"null"`) |
| `workitem_related_multi_select` | **stringified** ID 数组,**禁止写入自身 ID**(防循环引用,触发 `exists loop` 报错) |
| `group_type` | 拉群方式为逻辑字段,读返回判别键 `value`,写协议判别键 `type`(`auto`/`bind`/`disabled`),**读写不对称** |
| `multi-file` | 按 [attachment.md](attachment.md) 完成“本地文件校验 → 字段消歧 → 对象存储上传 → 字段绑定 → 写后验收”;追加时必须读取并保留当前完整数组,再逐个追加 |
| `file` | 按 [attachment.md](attachment.md) 的兼容性规则处理;不得假设旧版或单附件字段支持多附件,也不得与 `multi-file` 共用未经验证的多附件写入协议 |

> 其余通用字段类型(text / number / bool / multi-user / date / schedule / precise_date / select 系列 / multi-text 等)写入格式详见主文档 [SKILL.md](../SKILL.md)「字段值格式」章节。

> **用户提供的是工作项名称而非 ID** 时,按 [field-value-extras.md](field-value-extras.md)「关联工作项名称 → ID 转换」完整流程(获取目标约束 → `workitem query` 搜索 → 消歧 → 按类型写入)处理。

### STEP 4 — 执行更新

```bash
meegle workitem update --work-item-id 工作项ID --project-key 空间key --role-operate '{{role_operate}}' --fields '[{"field_key":"priority","field_value":"option_id"}]' --format json
```

**角色操作**通过 `role_operate` 参数:
```json
[{"op": "add", "role_key": "角色key", "user_keys": ["userkey1"]}]
```

**拉群方式更新(`group_type` 逻辑字段)**:服务端把 `group_id` / `chat_group` 合并到 `group_type` 单字段,统一通过它读写——**禁止再单独写 `group_id` / `chat_group`**。写协议 `field_value`:

```json
{"type": "auto" | "bind" | "disabled", "group_id": "oc_xxx"}
```

⚠️ **读写不对称**:读返回的判别键是 `value`(如 `{"value":"auto","label":"自动拉群","group_id":"oc_xxx"}`),写要求的判别键是 `type`。不能直接把读到的结构丢回 update。

| `type` | 含义 | 是否带 `group_id` | 客户端预校验 |
|---|---|---|---|
| `auto` | 自动拉群 | 不允许 | 带了 → 阻断(服务端会回 `group_type conflicts with group_id: type=auto`) |
| `bind` | 绑定现有群 | **必填**,非空 `oc_xxx`(空串/纯空格也算缺失) | 缺失或全空白 → 阻断(服务端会回 `group_id is required when group_type=bind`) |
| `disabled` | 不拉群 | 不允许 | 带了 → 阻断(服务端会回 `group_type conflicts with group_id: type=disabled`) |

写法示例(详见 [api-examples.md](api-examples.md) 工作项域):

```bash
meegle workitem update --work-item-id 工作项ID --project-key 空间key --role-operate '{{role_operate}}' --fields '[{"field_key":"group_type","field_value":"{\"type\":\"bind\",\"group_id\":\"oc_xxx\"}"}]' --format json
```

### STEP 5 — 返回结果

如涉及附件,先调 `workitem get` 回读已消歧的目标字段,逐一核对请求文件名和数量。上传接口或更新接口返回成功,都不能替代目标字段的写后验收。验收通过后再展示修改字段、目标附件字段名/key、实际文件名和数量;无法验证页面布局时仅声明 API 字段写入成功。

---

## 增量追加 (Append SOP)

当用户要求"追加"、"添加"内容时(而非覆盖),必须遵循:

**1. 获取旧值** → 调 `workitem get` 或 `workitem query` 查当前值

**2. 合并新旧值**:

| 字段类型 | 合并方式 |
|---------|---------|
| 文本(`text` / `multi-text`) | 新文本拼接到旧文本后面 |
| 多选枚举(`multi-select`) | 旧选项 + 新选项,`[{"option_id":"xxx"}, ...]` |
| 树状多选(`tree-multi-select`) | 纯字符串一维数组 `["id1", "id2"]`,去重 |
| 关联工作项(`workitem_related_multi_select`) | 旧 ID + 新 ID,去重后写入(只能绑定同空间或白名单空间实例) |
| 多选人员(`multi-user`) | 旧 userkey + 新 userkey,去重 |
| 附件(`multi-file`) | 按 [attachment.md](attachment.md) 逐个追加:每轮读取目标字段,把旧附件完整对象(包括服务端补充的 `uid`、`url` 和未知字段)原样保留,只追加一个新对象,整体 stringify 写回后立即回读验收。`update` 是覆盖语义,不取旧值会丢历史附件 |

**3. 覆盖写入** → 通过 `workitem update` 写入合并后的值

### 角色追加与删除

| 操作 | `role_operate` 参数 |
|------|-------------------|
| **追加角色人员** | `{"op": "add", "role_key": "xxx", "user_keys": ["userkey"]}` — 不需要先获取旧值 |
| **清空角色人员**(保留角色) | `{"op": "remove", "role_key": "xxx", "user_keys": []}` |
| **彻底删除角色** | `{"op": "remove", "role_key": "xxx", "user_keys": ["_all"]}` |

### 动态 MQL 查询追加

当用户以自然语言条件要求追加关联项(如"将名称包含'依赖'的所有需求追加到前置依赖中")时:
1. 用 `workitem query` 检索符合条件的工作项
2. 提取 ID 列表
3. 按【获取旧值 → 合并 → 写入】流程追加

---

## 边界说明

| 场景 | 处理 |
|------|------|
| **节点级字段**(排期、估分、节点负责人) | 不属于本 Skill,需用 `workflow update-node`。如检测到用户要改节点字段,自动调 `workflow update-node` |
| **角色更新** | 必须通过 `role_operate`,不能放在 `fields` 里 |
| **模板切换**(修改 template) | 高风险操作,这是**唯一需要提醒用户确认**的场景 |

---

## 不可写入的字段类型

以下字段不支持 API 写入,遇到时**直接跳过并告知用户**:

| 类型 | 原因 |
|------|------|
| `vote-boolean`(轻量表态) | 计数器,只能页面操作 |
| `vote-option` / `vote-option-multi`(投票) | 不支持接口伪造 |
| 计算字段 | 系统自动计算,只读 |

> **复合明细表已支持通过 `workitem update` 写入**,但两类协议不同:`compound_field` 使用 stringified action 对象并以 `group_uuid` 定位行;`multi_user_compound_field` 使用 stringified userkey map,且是整体覆盖。更新多人复合字段前必须先读当前值并保留全部人员和非目标数据;该协议只更新当前 map 中已有人员,空值或目标人员不在 map 时不能自动新增人员,必须停止并由用户先通过页面配置人员范围。格式详见主文档 [SKILL.md](../SKILL.md)「字段值格式 → 复合明细表」章节。

**富文本与关联字段**:
- **富文本/多行文本** → 直接传 Markdown 字符串
- **关联云文档** → URL 数组 `["https://xxx.feishu.cn/docx/xxx"]`
- **前置依赖/关联工作项** → 工作项 ID,不同空间可能要求字符串或数字格式,遇类型校验失败立刻切换;用户给的是名称而非 ID 时,按 [field-value-extras.md](field-value-extras.md)「关联工作项名称 → ID 转换」完整流程处理
- **signal 类型** → `option_id` 字符串(以 `workitem meta-fields` 的 `options[].option_id` 为准)
- **级联选项 (tree-select)** → 只传 `option_id` 纯字符串;报 `不满足层级配置` 时查 `children` 树找末级叶子节点,**展示给用户选择**
- **循环引用保护** → 关联字段写入前**必须排查当前工作项自身 ID**,禁止将自身 ID 写入关联项,否则触发 `exists loop`(循环引用)报错

---

## 错误自动恢复(自愈机制)

> 通用自愈规则(格式错误、级联层级、枚举不合法)见 [error-handling.md](error-handling.md)。以下为本 SOP 补充规则:

| 报错特征 | 自愈动作 |
|---------|---------|
| 字段名匹配不到 | 用 `field_query` 模糊搜索取最佳匹配 |
| 枚举值匹配不到 | 模糊匹配 option_name(包含关系),失败则列出所有选项 |
| 角色 key 不确定 | 用 `role_query` 模糊搜索 |

---

## 熔断机制 (Circuit Breaker)

> 通用熔断规则(空间未找到、权限不足)见主文档 [SKILL.md](../SKILL.md)「错误处理」。以下为本 SOP 补充规则:

1. **工作项类型未找到**:`workitem meta-types` 失败超过 3 次
2. **字段转换大面积失败**:转换失败比例 > 60%,终止并列出失败字段明细

---

## 常见问题

| 问题 | 处理 |
|------|------|
| 用户未指定工作项 | 追问 ID 或 URL |
| 字段名匹配不到 | `workitem meta-fields(field_query="关键词")` 模糊查询 |
| 枚举值匹配不到 | 展示所有枚举值让用户选 |
| 人名匹配到多人 | 展示列表让用户指定,**禁止自行选择** |
| 用户要"追加"而非覆盖 | 走增量追加 SOP |
| 用户要改节点字段 | 自动切换到 `workflow update-node` |
references/url-kinds.md›
# URL Kinds —— `url decode` 返回值到 SOP 的映射

## 为什么 skill 不自己拆 URL

Meego/飞书项目的路由非常多,且 snake_case 旧路径、`/meego/` 前缀、`_xxx_resource` 资源工作项、预置功能区(user-gantt / chart / multi-project-view 等)这些都会让"看起来像工作项详情页"的 URL 其实不是。**禁止**自己从 URL 截取路径段作参数。

统一走一条命令:

```bash
meegle url decode --url '<URL>' --format json
```

拿到 `url_kind` 后按本文表格选择 SOP 或回绝。纯本地解析,无网络调用。

---

## 返回字段

| 字段 | 说明 |
|---|---|
| `url_kind` | 必返;未识别时为 `unknown` |
| `simple_name` | 空间标识;需要 `project_key` 时先 `project search` 用 `simple_name` 作为输入 |
| `work_item_type` | 工作项类型 api_name(已脱去 `_xxx_resource` 包装) |
| `work_item_id` | 工作项 ID(字符串) |
| `view_id` / `chart_id` / `plugin_key` / `team_id` / `template_id` | 按路径分别返回 |
| `setting_type` | 设置页子参数(如 permission 类型) |
| `edit_str` | `homepage/edit`、`overview/edit` 的编辑态标记 |
| `is_resource` | true 表示路径里带 `_xxx_resource` 包装 |
| `query` | 原始 query 参数,保留 `scope/node` 等二级导航上下文 |
| `redirected_from` | 若经别名归一化或 `/meego/` 前缀剥离,记录原始路径 |
| `pathname` / `host` / `raw` | 诊断用 |

---

## `url_kind` → 允许的 SOP

### 工作项类

| url_kind | 可用字段 | 推荐 SOP / 命令 |
|---|---|---|
| `workitem_detail` | simple_name · work_item_type · work_item_id | `sop-update-workitem` / `sop-transition-node` / `sop-transition-state` 任一,先 `project search` → `workitem get` |
| `workitem_create` | simple_name · work_item_type | `sop-create-workitem`(`workitem create`) |
| `workitem_draft` | simple_name · work_item_type | 同上,提示用户这是草稿视图 |
| `workitem_homepage` / `workitem_homepage_edit` | simple_name · work_item_type | 无具体工作项 ID — **拒绝**直接操作,要求用户提供详情页 URL 或工作项 ID |

### 视图类(有 `view_id` 但无 `work_item_id`)

| url_kind | 语义 | 处理 |
|---|---|---|
| `view_story` / `view_issue` | 需求 / 缺陷视图 | 如果用户想操作"这个视图里的工作项",要求具体工作项 URL;否则可用 `view get` 查视图 |
| `view_multi_project` / `view_project_overview` / `view_user_gantt` | 跨空间/全域/甘特视图 | 同上 |
| `view_chart` | 图表视图 | 交叉到 `chart_*` 流程 |
| `view_workitem` | 通用工作项视图 | 若 `is_resource=true`,`work_item_type` 已脱包装可直接用 |

### 图表类

| url_kind | 可用字段 | 处理 |
|---|---|---|
| `chart_detail` | simple_name · chart_id | 图表详情;可用 `chart get` |
| `chart_create` | simple_name | 图表创建入口(无 ID) |
| `chart_homepage` / `chart_datascope*` / `chart_penetrate*` | simple_name · chart_id? | 抽屉/子页,通常**不**作为操作目标 |

### 空间/设置类(写操作请走 OpenAPI,非本 skill 范围)

| url_kind | 说明 |
|---|---|
| `project_home` · `project_overview` · `project_empty` · `project_ai_assist` | 空间级落地页,`simple_name` 可用于 `project search` |
| `project_overview_edit` | 编辑态,不作为操作目标 |
| `project_404` · `project_401` · `project_500` | 错误页,**拒绝** |
| `setting_*` | 各类设置页;本 skill 不做设置写操作,**拒绝**并告知 |
| `setting_other` | 未枚举的 setting 子页(前端通过非 exact 路由内部渲染),等同于 `setting_*` — **拒绝** |
| `import_jira` · `import_excel` · `data_recycle` | 导入/回收操作在界面内完成,**拒绝** |
| `plugin_page` | 插件页 — 行为由插件定义,CLI 无法操作,**拒绝** |

### 全局/导航类

| url_kind | 处理 |
|---|---|
| `workbench` · `workspaces` · `favorites` · `inbox` | 顶级导航页,**没有具体目标**,请追问 |
| `teams` · `team_detail` | 团队页;`team_detail` 的 `team_id` 可用于 `team list-members` |
| `templates` · `template_detail` · `template_manage` | 模板中心,本 skill 不做模板操作,**拒绝** |
| `project_list` | 全部空间列表,追问具体空间 |

### 系统域 `/b/*`

| url_kind | 处理 |
|---|---|
| `preference` · `mcp_config` · `mcp_auth` · `ai_hub` · `handover` · `onboarding_*` · `trial_*` · `cross_*` · `slack_connect` · `resource_handover` · `no_project_auth` · `login_datacenter` · `unbundled_register_result` · `b_home` | 系统/管理页,本 skill **拒绝**业务操作 |

### 登录/外部入口

| url_kind | 处理 |
|---|---|
| `login_fetch_cookie` · `login_asset` · `switch_asset` · `home_ka` · `tenant_select` · `tenant_create` · `channel_error` | 登录相关 — 改走 `auth-guard` |
| `quick_create_form` · `issue_trans` · `issue_create_open_usecase` · `story_create_open` · `jump_to_outer` · `light_share` · `ai_application_share` | 飞书内嵌入口,本 skill 通常不作为操作起点 |

### 错误兜底

| url_kind | 处理 |
|---|---|
| `lark_page_404` · `project_empty_page` · `route_loading` · `system_upgrade` | 错误页,**拒绝** |
| `unknown` | **拒绝**并要求用户提供详情页 URL 或直接描述任务 |

### 特殊字段校验

- `redirected_from` 非空 → 在回复中提一句"检测到旧版路径,已自动归一化",避免用户误以为 URL 错了
- `is_resource=true` → 告知用户这是资源工作项视图,`work_item_type` 已自动脱去 `_xxx_resource` 包装
- `query.scope` / `query.node` 非空 → 仅作为导航上下文,不要当作业务参数

---

## 典型分支模板(供 SOP 引用)

```
STEP 0 — URL 解析(仅当用户提供了 URL)

  url decode --url "<URL>"
  SAVE $url_kind, $simple_name, $work_item_type, $work_item_id, $view_id, $redirected_from

  SWITCH $url_kind:
    - workitem_detail    → GOTO 本 SOP 的 STEP 1(已具备 simple_name + work_item_id)
    - workitem_homepage  → ASK user:"需要具体工作项 URL,这是类型主页。";STOP
    - view_*             → ASK user:"这是视图 URL,请粘贴具体工作项详情页 URL。";STOP
    - unknown            → ASK user:"无法识别该 URL,请确认后重发或直接描述任务。";STOP
    - 其他非本 SOP 范围   → 告知 kind,建议对应操作;STOP
```
references/url-links.md›
# 生成 Meegle 页面链接

## 使用边界

仅在用户明确要求链接,或链接本身是交付物时使用。普通查询、创建、更新、流转完成后不要自动追加链接。

目标是交付当前用户可打开的 canonical 页面链接:

1. 先固定执行上下文和 `$target_host`,再通过 [Auth Guard](auth-guard.md) 取得有效 host:用户显式选择 `--profile` 时,Auth Guard 内的命令与后续所有查询都传同一个全局 flag;未显式选择时,全程使用当前 profile 且禁止中途切换。随后确认目标对象对该身份可查询;同一轮只有执行上下文与 host 都相同时才能复用成功结果。
2. 只使用本文列出的路径模板,不根据印象发明路由。
3. 生成后必须调用 `url decode` 反向校验;返回的 `url_kind` 与目标类型不同或为 `unknown` 时,不得交付该链接。

反向解析只证明链接结构正确。目标查询成功才是“当前身份可访问”的证据;它不保证其他用户也有权限。

---

## 参数来源

### host

先确定用户要打开的目标 host,并保存为本次请求的 `$target_host` 后再执行 Auth Guard:

1. 用户明确指定的域名;
2. 用户提供的已有 Meegle URL 经 `url decode` 返回的 `host`;
3. 未指定时,SAVE `$target_host = null`,使用 Auth Guard 返回并保存的有效 `$host`(它已按运行环境与所选 profile 解析)。

在查询目标对象前执行 Auth Guard,并复用它的 host 一致性结论:域名忽略大小写并去掉末尾的 `.`,端口必须一致。若不一致,请用户切换到目标环境对应的 profile 或重新登录;不要拿环境 A 的成功查询证明环境 B 的链接可访问,也不要在下游用未经规范化的原字符串重复比较。最终链接只能使用这次成功查询实际连接的 host。

host 为空或不是可信的 Meegle/飞书项目域名时,询问用户,不要默认猜成 `meegle.com` 或 `project.feishu.cn`。host 只保留域名和可选端口,不带协议、路径、query 或 fragment;最终统一使用 `https://`。

### simple_name

`simple_name` 与 `project_key` 不是同一个概念,禁止互换。使用 `project search` 的唯一空间结果作为权威来源:

- 已有 URL 的 `simple_name` 只能作为空间查询输入;只有该 URL 本身指向同一目标对象/视图,或查询结果证明它与目标对象的 `project_key` 属于同一空间时,才能复用;
- 只有空间名称或 project_key:查询后使用唯一命中结果中的 `simple_name`;
- 结果不唯一或没有返回 `simple_name`:询问用户提供准确空间或现有页面链接,不要拿 `project_key` 代替。

### 其他动态字段

ID 与类型 key 优先取自本轮已有命令结果;否则先用对应查询命令确认:

| 目标 | 可访问性检查 |
|---|---|
| 工作项详情 | `workitem get` |
| 需求、缺陷、通用、空间概览、甘特、图表视图 | `view get` |
| 跨空间 / 全景视图 `view_multi_project` | `view list-multi-project-workitems` |
| 图表详情 | `chart get` |
| 空间首页 / 空间概览 | `project search` |

工作项类型、视图 ID、图表 ID 或工作项 ID 缺失时合并为一次询问。查询返回无权限、不存在或不唯一时停止,不生成“可访问”链接。

视图查询成功只能证明该 `view_id` 可读取,不能单独推断页面路由类型。`url_kind` 必须来自指向同一视图的已有 URL 经 `url decode` 的结果,或查询结果中的权威 view scope;用户口述的视图类型只能用于缩小查询范围,不能单独作为路由证据。无法确认时询问用户。特别是“全景视图”必须确认是 `view_multi_project` 还是 `view_project_overview`。

---

## 支持的 canonical 路径

每个 `{...}` 仅表示一个路径段。动态值按 RFC 3986 path segment 编码:只保留字母、数字、`-`、`.`、`_`、`~`,其余字节全部百分号编码;拒绝原始值中包含 `/`、`\\`、`?`、`#`、控制字符,或整个值为 `.` / `..` 的歧义输入。

| url_kind | 必需字段 | pathname |
|---|---|---|
| `workitem_detail` | simple_name, work_item_type, work_item_id | `/{simple_name}/{work_item_type}/detail/{work_item_id}` |
| `view_story` | simple_name, view_id | `/{simple_name}/storyView/{view_id}` |
| `view_issue` | simple_name, view_id | `/{simple_name}/issueView/{view_id}` |
| `view_workitem` | simple_name, work_item_type, view_id | `/{simple_name}/workObjectView/{work_item_type}/{view_id}` |
| `view_multi_project` | simple_name, view_id | `/{simple_name}/multiProjectView/{view_id}` |
| `view_project_overview` | simple_name, view_id | `/{simple_name}/project-overview/{view_id}` |
| `view_user_gantt` | simple_name, view_id | `/{simple_name}/userGantt/{view_id}` |
| `view_chart` | simple_name, view_id | `/{simple_name}/workObjectView/chart/{view_id}` |
| `chart_detail` | simple_name, chart_id | `/{simple_name}/chart/detail/{chart_id}` |
| `project_home` | simple_name | `/{simple_name}` |
| `project_overview` | simple_name | `/{simple_name}/overview` |

资源工作项或资源视图把 `work_item_type` 包装为 `_{work_item_type}_resource`,例如 `story` → `_story_resource`。不要重复包装已经带该形式的值。

`work_item_type=chart` 的工作项详情会与 `chart_detail` 冲突;通用视图中的 `work_item_type=chart` 会与 `view_chart` 冲突。反向校验会暴露这类保留路由冲突,遇到时停止并说明当前 canonical 路由无法无歧义表达目标。

不支持系统页、设置页、登录页、导入页、错误页、插件页,以及任何本文未列出的 `url_kind`。

---

## 生成与校验

每个候选链接都必须校验完整目标身份,不能只比较 `url_kind`:

- 所有类型:decoder 返回的 `host` 按 Auth Guard 的规则规范化后必须等于 `$host`,`simple_name` 必须等于权威空间结果;
- `workitem_detail`:`work_item_type`、`work_item_id` 必须与目标一致;资源工作项还必须返回 `is_resource=true`;
- `view_workitem`:`work_item_type`、`view_id` 必须与目标一致;资源视图还必须返回 `is_resource=true`;
- 其他 `view_*`:`view_id` 必须与目标一致;
- `chart_detail`:`chart_id` 必须与目标一致;
- `project_home` / `project_overview`:除公共字段外没有额外 ID。

任一字段缺失或不一致都停止交付。动态路径段比较解码后的语义值,不比较百分号编码文本本身。

### 工作项详情

已知:host=`project.feishu.cn`、simple_name=`xopenapp`、work_item_type=`story`、work_item_id=`7092625364`。

候选链接:

```text
https://project.feishu.cn/xopenapp/story/detail/7092625364
```

校验:

```bash
meegle url decode --url 'https://project.feishu.cn/xopenapp/story/detail/7092625364' --format json
```

只在输出 `url_kind=workitem_detail` 且关键字段与输入一致时交付。

### 通用工作项视图

```text
https://project.feishu.cn/xopenapp/workObjectView/story/9988
```

必须解码为 `view_workitem`,并返回一致的 `simple_name`、`work_item_type` 和 `view_id`。

### 跨空间 / 全景视图

```text
https://project.feishu.cn/xopenapp/multiProjectView/9988
https://project.feishu.cn/xopenapp/project-overview/9988
```

分别必须解码为 `view_multi_project` 和 `view_project_overview`。不要只凭“全景视图”名称猜一种;根据视图查询结果或已有 URL 确定类型,无法区分时询问用户。

---

## 交付格式与失败处理

成功时优先用固定的“类型 + ID”作 Markdown label,例如:

```markdown
[需求 7092625364](https://project.feishu.cn/xopenapp/story/detail/7092625364)
```

若必须使用查询结果中的名称作为 label,先转义 `\\`、`[`、`]`、`(`、`)` 等 Markdown 元字符,避免不可信名称改变链接结构。

失败时:

- host 缺失:请用户指定环境域名或配置当前 profile;
- `simple_name` 缺失/不唯一:请用户确认空间,禁止用 `project_key` 替代;
- 目标不存在或无权限:说明当前身份无法验证可访问性,不返回伪链接;
- decoder 返回 `unknown` 或另一 `url_kind`:报告路由冲突或模板不支持,不返回候选链接;
- `url decode` 报 unknown command:说明本地 CLI 版本过旧,建议升级;不要绕过校验后手拼交付。

以上均为确定性失败;输入与环境没有变化时不要原样重试。
references/view.md›
# 视图辅助命令

视图搜索与固定视图管理。读取视图数据用 `view get`(见 SKILL.md 主文件)。

## view search
按名称搜索视图。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --project-key | string | 是 | 空间 key |
| --view-scope | string | 是 | 视图范围 |
| --key-word | string | 是 | 关键词 |

## view create-fixed
创建固定视图。上限 200 个工作项。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --project-key | string | 是 | 空间 key |
| --name | string | 是 | 视图名称 |
| --work-item-type | string | 是 | 工作项类型 |
| --work-item-id-list | array | 是 | 工作项 ID 列表 |

## view update-fixed
更新固定视图。add/remove_work_item_ids 二选一。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --project-key | string | 是 | 空间 key |
| --view-id | string | 是 | 视图 ID |
| --work-item-type | string | 是 | 工作项类型 |

## view list-multi-project-workitems
查看全景视图(multiProjectView)下当前用户有权限的工作项列表。全景视图的链接上带有 `multiProjectView` 关键字,可从中提取 `view_id`。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --project-key | string | 是 | 空间 key |
| --view-id | string | 是 | 全景视图 ID |
| --page-num | number | 否 | 分页页码,每页 50 条,从 1 开始 |
references/wbs.md›
# WBS 计划表

计划表(Work Breakdown Structure)有两套数据模型:

- **草稿(draft)** — 当前用户的可编辑副本。`wbs create-draft` 创建、`wbs edit-draft` 原子编辑(单行或批量操作)、`wbs publish-draft` 发布、`wbs reset-draft` 放弃修改。
- **实例(instance)** — 已发布的线上版本。只读,用 `wbs list-instance-rows` 查询。

**常见流程**:`wbs create-draft` → 多次 `wbs edit-draft` → `wbs publish-draft`。

**辅助命令**(创建草稿 / 重置 / 进度 / 模板)请见 [misc.md](misc.md)。

---

## 共用查询能力(draft / instance)

`wbs list-draft-rows` 与 `wbs list-instance-rows` 参数完全一致,仅查询的数据集不同——前者查当前用户的草稿,后者查线上实例。**禁止混用**:例如先用 `wbs list-draft-rows` 取行 uuid,后续递归 / 编辑也必须继续用草稿系列命令。

### condition_query 支持字段

筛选行用 `condition_query`(object)。**仅支持以下字段**:

| 字段 | 含义 | 取值 |
|------|------|------|
| `wbs_name` | 行名称 / 任务名称 / 排期项名称 / 子项名称 | string |
| `wbs_parent_id` | 父级 uuid(用于查子级 / 下级 / 子任务 / 子项) | uuid |
| `wbs_belong_status` | 所属状态 / 阶段(计划 / 开发 / 验证 / 发布等) | string |
| `wbs_states_doing` | 当前状态 / 任务状态 | `not_started` / `doing` / `finished` |
| `wbs_role` | 角色(可多人) | string |
| `wbs_owner_in_charge` | 负责人 / 责任人(可多人) | userkey;查"我负责的"先调 `user search` 取 userkey |
| `wbs_delay_label` | 延期标识 | `delay` / `normal` |
| `wbs_milestone_node_type` | 节点类型 | `milestone` / `normal_node` / `key_path_node` |
| `wbs_deletable` | 允许删除节点 | bool |

**递归查子级 SOP**:用户提到"子级 / 所有子 / 下级"时,先按条件筛出目标行取 `uuid`,再用 `wbs_parent_id` + `In` 查直接子级(多个 uuid 逗号分隔),再以下一层 uuid 继续递归直到无子级。**全流程必须使用同一工具**(草稿就一直草稿,实例就一直实例)。

### row_field_list 返回字段控制

`row_field_list`(string[])按需指定返回字段。为空时默认返回 `base.*` + `meta.uuid`;`["_all"]` 返回全量字段。

| 通配符 | 包含字段 |
|--------|----------|
| `meta.*` | `uuid`、`parent_id`、所属工作项信息等 |
| `base.*` | `name`(行名)、`owners`(负责人)、`start_time` / `end_time`(实际开始 / 完成时间)、`schedule`(排期)、`schedule_dependency`(排期依赖)、`union_deliveries`(交付物)、`process_status`(当前状态) |
| `node_extra.*` | 普通节点扩展:里程碑、所属状态、节点唯一 id `state_key`、前序节点等 |
| `sub_instance_extra.*` | 子实例扩展:拆解模式 `dismantle_mode` |

**查工作项字段 / 节点字段**:先用 `workitem meta-fields` 判断是否为工作项字段、用 `workflow meta-node-fields` 判断是否为节点字段;再从计划表行中取对应 `workitem_id` / `state_key`,基于这些 ID 继续查字段值。

### wbs list-draft-rows

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --work-item-id | string | 是 | 工作项 ID(**字符串**);URL 自动解析;名称需先调 `workitem get` |
| --project-key | string | 是 | 空间 key |
| --condition-query | object | 否 | 筛选条件,仅支持上表字段 |
| --need-structure | boolean | 否 | 是否返回树状层级。默认 `false`;查 / 编辑子级时设为 `true` |
| --page-no | number | 否 | 页号,从 1 开始;返回 `has_more` 时需翻页 |
| --page-size | number | 否 | 页大小,1–50,默认 25。超过 1000 行需分页合并 |
| --row-field-list | string[] | 否 | 见上表 |

### wbs list-instance-rows

参数与 `wbs list-draft-rows` 完全一致,仅查询数据集不同(线上已发布实例)。

---

## wbs edit-draft

对计划表草稿执行**一次原子编辑**。一次调用只能传一个 `operation_type`;该操作内部可通过 `items` / `uuids` 等数组承载单行或批量编辑。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --work-item-id | string | 是 | 工作项 ID |
| --project-key | string | 是 | 空间 key |
| --operation | object | 是 | 操作对象,结构因操作类型而异 |

### operation 结构

`operation` 是一个对象,统一形如:

```json
{
  "operation_type": "<动作 PascalCase>",
  "operation_value": {
    "<动作 snake_case>": { /* 动作特定字段 */ }
  }
}
```

`operation_value` 下的子 key 必须与 `operation_type` 匹配;优先按下表传,避免只做机械 snake_case 猜测。

| operation_type | operation_value 子 key | 用途 |
|----------------|------------------------|------|
| `AddTaskRows` | `add_task_rows` | 批量新增普通任务行 |
| `AddNodeRows` | `add_node_rows` | 批量新增资源节点行 |
| `AddSubInstanceRows` | `add_sub_instance_rows` | 批量新增子工作项行 |
| `AddResourceSubInstanceRows` | `add_resource_sub_instance_row` | 批量新增资源子实例行(注意子 key 里的 `row` 为单数) |
| `DeleteRows` | `delete_rows` | 批量删除 / 移出行 |
| `RestoreRows` | `restore_rows` | 批量恢复行 |
| `UpdateDismantleMode` | `update_dismantle_mode` | 切换拆解模式 |
| `AdjustRowOrder` | `adjust_row_order` | 调整行排序 / 父级 |
| `UpdateNodeSequence` | `update_node_sequence` | 修改资源节点前后序 |
| `UpdateNodePhases` | `update_node_phases` | 批量修改节点所属阶段 |
| `UpdateName` | `update_name` | 修改名称 |
| `UpdateRoleOwners` | `update_role_owners` | 批量修改角色负责人 |
| `UpdateDelivery` | `update_delivery` | 修改交付物 |
| `UpdatePlannedSchedule` | `update_planned_schedule` | 修改单行排期(旧单行形态) |
| `UpdatePlannedSchedules` | `update_planned_schedules` | 批量修改计划排期 |
| `UpdateSchedulePoints` | `update_schedule_points` | 批量修改估分 |
| `UpdateScheduleActualTimes` | `update_schedule_actual_times` | 批量修改实际工时 |

**选择要点**:
- 用户说普通任务 / 子任务 / 任务行时用 `AddTaskRows`;其 `parent_uuid` 必须是当前计划表中允许新增子任务的父行,并非任意 `sub_instance` / `node` / `sub_task` 都可用。若后端返回 `can not add sub task under parentUUID` 或 `wbs task not found`,说明父行不支持该新增位置,需换父行或先确认拆解结构。
- 用户说工作项类型(如"需求"、"项目活动")时用 `AddSubInstanceRows`,并先调 `workitem meta-types` 确认 `work_item_type_key`。
- 涉及资源节点或资源子实例时,先调 `wbs list-element-templates` 查询模板 / `element_key`;资源节点通常用 `AddNodeRows`,资源子实例用 `AddResourceSubInstanceRows`。
- `UpdateRoleOwners` 与 `UpdateDelivery` 是全量覆盖语义。追加负责人或交付物前,先用 `wbs list-draft-rows` 取当前值并合并历史值;不要只传新增值。
- 排期时间写 ISO8601 带时区字符串(如 `2026-04-22T00:00:00+08:00`);不要传毫秒时间戳,也不要在 `specify_schedule` 外额外包一层 `schedule`。

**示例:批量新增普通任务行**

```json
{
  "operation_type": "AddTaskRows",
  "operation_value": {
    "add_task_rows": {
      "parent_uuid": "<上级行 uuid,来自 wbs list-draft-rows>",
      "pre_uuid": "",
      "items": [
        { "name": "新任务名" }
      ]
    }
  }
}
```

**示例:批量新增资源节点行**

```json
{
  "operation_type": "AddNodeRows",
  "operation_value": {
    "add_node_rows": {
      "parent_uuid": "<上级行 uuid>",
      "pre_uuid": "",
      "phase": "started",
      "items": [
        { "element_key": "<来自 wbs list-element-templates 的 element_key>" }
      ]
    }
  }
}
```

**示例:批量新增子工作项行**

```json
{
  "operation_type": "AddSubInstanceRows",
  "operation_value": {
    "add_sub_instance_rows": [
      {
        "parent_uuid": "<上级行 uuid>",
        "work_item_type_key": "<来自 workitem meta-types 的工作项类型 key>",
        "fields": [
          { "field_key": "name", "field_value": "子工作项名称" }
        ]
      }
    ]
  }
}
```

**示例:批量新增资源子实例行**

```json
{
  "operation_type": "AddResourceSubInstanceRows",
  "operation_value": {
    "add_resource_sub_instance_row": [
      {
        "parent_uuid": "<上级行 uuid>",
        "work_item_type_key": "<资源工作项类型 key>",
        "resource_work_item_ids": ["<已有资源实例 ID>"]
      }
    ]
  }
}
```

**示例:批量修改计划排期**

```json
{
  "operation_type": "UpdatePlannedSchedules",
  "operation_value": {
    "update_planned_schedules": {
      "uuids": ["<行 uuid>"],
      "different_schedule": false,
      "specify_schedule": {
        "estimate_start": "2026-04-22T00:00:00+08:00",
        "estimate_finish": "2026-04-25T23:59:59+08:00"
      }
    }
  }
}
```

**示例:批量修改估分 / 实际工时**

```json
{
  "operation_type": "UpdateSchedulePoints",
  "operation_value": {
    "update_schedule_points": {
      "uuids": ["<行 uuid>"],
      "different_schedule": false,
      "schedule_point": {
        "schedule_point_value": 3,
        "schedule_point_value_unit": 8
      }
    }
  }
}
```

```json
{
  "operation_type": "UpdateScheduleActualTimes",
  "operation_value": {
    "update_schedule_actual_times": {
      "uuids": ["<行 uuid>"],
      "different_schedule": false,
      "actual_time": {
        "value": 5,
        "unit": 8
      }
    }
  }
}
```

**示例:批量删除 / 恢复行**

```json
{
  "operation_type": "DeleteRows",
  "operation_value": {
    "delete_rows": {
      "uuids": ["<行 uuid>"],
      "delete_action_type": "delete",
      "reason": "重复行"
    }
  }
}
```

```json
{
  "operation_type": "RestoreRows",
  "operation_value": {
    "restore_rows": {
      "uuids": ["<行 uuid>"]
    }
  }
}
```

**示例:批量修改阶段 / 负责人**

```json
{
  "operation_type": "UpdateNodePhases",
  "operation_value": {
    "update_node_phases": {
      "phase": "started",
      "uuids": ["<行 uuid>"]
    }
  }
}
```

```json
{
  "operation_type": "UpdateRoleOwners",
  "operation_value": {
    "update_role_owners": {
      "uuids": ["<行 uuid>"],
      "owners": ["<userkey>"]
    }
  }
}
```

调用后响应里若有 `change_uuids`,可直接拿去做下一步 `wbs edit-draft`(如改排期)或 `wbs publish-draft` 的部分发布。

**建议工作流**:
1. `wbs list-draft-rows` 取目标行 / 父行的 `uuid` 与当前字段值
2. 调 `wbs edit-draft` 一次执行一种操作
3. 如调用返回了 `operation_id`,先用 `wbs get-draft-progress` 轮询完成再进行下一次编辑——多个 `wbs edit-draft` 并发或不等异步完成就连发可能丢操作
4. 全部改完用 `wbs publish-draft` 发布

---

## wbs publish-draft

将编辑完成的草稿发布到线上。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --project-key | string | 是 | 空间 key |
| --work-item-id | string | 是 | 工作项 ID |
| --uuid-strings-list | string[] | 否 | 要发布的行 uuid 列表。**部分发布**:传 uuid 列表,无需二次确认。**全量发布**:不传此字段(或传 `["_all"]`),必须先用以下**固定话术**二次确认,用户同意后才执行 |

### 全量发布二次确认(固定话术)

> 本人及协同者的全部编辑内容均会被发布,请确认是否全量发布?

部分发布(传入 `uuid_strings_list`)**不需要**二次确认,直接执行。

---

## 异步操作进度

`wbs create-draft` / `wbs edit-draft` / `wbs publish-draft` / `wbs reset-draft` 返回 `operation_id` 后,需用 `wbs get-draft-progress` 轮询进度。参数表见 [misc.md](misc.md#wbs-辅助命令)。
references/workflow.md›
# 工作流辅助命令

工作流流转之前用来查询可流转方向、必填项、节点字段配置的辅助命令。核心流转命令(`workflow transition` / `workflow transition-state` / `workflow get-node` / `workflow update-node`)见 SKILL.md 主文件。

## workflow list-state-transitions
查看工作项可流转的状态列表。状态流流转前必须先调用此命令拿 `transition_id`。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --project-key | string | 是 | 空间 key |
| --work-item-id | string | 是 | 工作项 ID |
| --work-item-type | string | 是 | 工作项类型 |
| --user-key | string | 是 | 用户标识 |

## workflow list-state-required
查看流转所需的必填信息(节点流传 node_key,状态流传 state_key)。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --project-key | string | 是 | 空间 key |
| --work-item-id | string | 是 | 工作项 ID |
| --state-key | string | 是 | 节点流的 node_key 或状态流的 state_key |
| --mode | string | 否 | 默认查所有必填项;传 `unfinished` 仅查未完成必填项 |

## workflow meta-node-fields
查看节点字段配置。`workflow update-node` 修改节点自定义字段前用来确认合法 field_key、字段类型与 options。查询评审结论选项时按 `field_keys=["node_finished_conclusion"]` 精确查询,并从返回配置的 options / 选项列表中取合法值。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --project-key | string | 是 | 空间 key |
| --work-item-type | string | 是 | 工作项类型 |
| --field-keys | array | 否 | 精确匹配节点字段 key 或名称,如 `["node_finished_conclusion"]` |
| --field-types | array | 否 | 按节点字段类型筛选 |
| --query | string | 否 | 按字段 name / key 模糊搜索 |
references/workitem.md›
# 工作项元数据命令

查询工作项类型、字段、角色配置的辅助命令。在 `workitem create` / `workitem update` / `workitem query` 之前用来确认合法 key。

## workitem meta-types
获取指定空间下所有工作项类型列表。用户描述模糊时用此命令确认合法 type_key。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --project-key | string | 是 | 空间 projectKey |

## workitem meta-fields
获取指定空间和工作项类型的可用字段配置(不含禁用字段和角色配置)。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --project-key | string | 是 | 空间 key |
| --work-item-type | string | 是 | 工作项类型 key 或名称 |
| --page-num | number | 是 | 页数,每页 50 条,从 1 开始 |
| --field-keys | array | 否 | 精确匹配字段 key 或名称 |
| --field-query | string | 否 | 模糊查询字段 key 和名称 |
| --field-types | array | 否 | 按字段类型筛选 |

## workitem meta-roles
获取指定工作项类型的角色列表。用于查询/创建/更新工作项前确认合法 role_key。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --project-key | string | 是 | 空间 key |
| --work-item-type | string | 是 | 工作项类型 key 或名称 |
| --page-num | number | 是 | 页数,每页 50 条,从 1 开始 |
| --role-keys | array | 否 | 精确匹配角色 key 或名称 |
| --role-query | string | 否 | 模糊查询角色 key 和名称 |

## workitem meta-create-fields
查看创建工作项时可用的字段及类型。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --project-key | string | 是 | 空间 key |
| --work-item-type | string | 是 | 要创建的工作项类型 key |
SKILL.md›
---
name: meegle
description: |
  飞书项目(Meego/Meegle)操作工具。支持查询和管理工作项、节点流转、视图查询、个人待办、排期统计等功能。 Use when user needs to work with Feishu/Lark Meego project management — including querying work items, creating/updating work items, completing workflow nodes, checking views, listing todos, analyzing schedules/workloads, or searching with MQL. 关键词:飞书项目、meego、meegle、工作项、需求、任务、缺陷、排期、视图、待办、节点。
---

# 飞书项目 (Meego/Meegle) 操作指南

本技能通过 Meegle CLI来操作飞书项目数据。输出语言跟随用户输入语言,默认中文。

> 各命令的调用示例见 [references/api-examples.md](references/api-examples.md)。
> **授权流程**(所有业务命令前必须执行):见 [references/auth-guard.md](references/auth-guard.md)
> **CLI 使用指南**(命令结构、参数传递、命令发现):见 [references/cli-guide.md](references/cli-guide.md)

---

## Project 空间域

### project search
搜索空间信息,将空间名转换为 project_key 或验证空间是否存在;省略 --project-key 时返回当前用户最近访问过的空间列表(按访问时间由近及远)。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --project-key | string | 否 | 空间 projectKey、simpleName 或空间名称;留空查询当前用户可访问的空间 |
| --page-num | number | 否 | 分页页码,每页 50 条,从 1 开始 |

---

## WorkItem 工作项域

> 元数据查询命令(`workitem meta-types` / `workitem meta-fields` / `workitem meta-roles` / `workitem meta-create-fields`)的参数表见 [references/workitem.md](references/workitem.md)。

### workitem create
创建工作项实例。**务必先用 `workitem meta-fields` 获取字段信息,`workitem meta-roles` 获取角色信息。模板 ID 是必填项。**

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --work-item-type | string | 是 | 工作项类型 |
| --project-key | string | 否 | 空间标识 |
| --fields | array | 否 | 字段值列表,每项含 field_key 和 field_value |

### workitem get
按 ID/名称查询工作项概况。不传 fields 时返回固定基础字段加上一组默认系统字段:`group_type`(拉群方式)、`description`、`current_status_operator`、`watchers`(value 为 null 时也会出现);其余字段需先通过 `workitem meta-fields` 拿到 key 再传入 fields。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --work-item-id | string | 否 | 工作项 ID(与 `--name` 二选一) |
| --name | string | 否 | 按名称查询工作项(与 `--work-item-id` 二选一) |
| --project-key | string | 否 | 空间 key |
| --fields | array | 否 | 要查询的 field_key 或 field_name;传 `["_all"]` 时按逻辑字段分页返回全部字段;传 `["group_type"]` 时只取拉群方式 |
| --page-size | number | 否 | 仅 `fields=["_all"]` 时生效;每页字段数量,默认 100,最大 200。**Meegle CLI 序列化约束**:`--page-size N` 会被序列化为字符串触发后端 `need I64 type, but got: STRING`;必须走 `--params '{"page_size":N}'` 以数字传出 |
| --page-token | string | 否 | 仅 `fields=["_all"]` 时生效;翻页 token,首次不传,下一页传上一页响应的 `next_page_token`(token 形如字段 key,例如 `"business"`);同上,须走 `--params '{"page_token":"..."}'` |

> **逻辑字段聚合(重要心智模型)**:服务端把 `group_id` / `chat_group` 这类"拉群"相关的物理字段**合并**到一个逻辑字段 `group_type`。读取/更新统一走 `group_type`,**不要再单独读取 `group_id` 或 `chat_group`**。
>
> ⚠️ **读写协议不对称**:读返回结构里**判别键是 `value`**(不是 `type`),更新时**判别键是 `type`**——禁止照着读到的结构直接回写。
>
> 读返回(`workitem_fields[].value` 字段)的形状:
> - `auto` → `{value: "auto", label: "自动拉群", group_id: "oc_xxx"}`(自动拉群附带 group_id;状态切换时 oc_id 可能会变)
> - `bind` → `{value: "bind", label: "绑定现有群", group_id: "oc_xxx"}`
> - `disabled` → `{value: "disabled", label: "不拉群"}`(无 group_id)
>
> 写协议(`field_value` 里的 JSON):`{"type": "auto" | "bind" | "disabled", "group_id": "oc_xxx"}`

### workitem +batch-get
批量查询工作项(Meegle CLI 客户端 fan-out:并发调用 `workitem get`)。单次 ≤ 200 个 ID,3 并发,返回 `{results, errors, summary}`;ID 量大时用 `--format ndjson` 流式输出。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --work-item-ids | array | 二选一 | 工作项 ID 列表(逗号分隔或多次传入) |
| --ids-file | string | 二选一 | 从文件读取 ID(一行一个,`#` 开头注释) |
| --fields | array | 否 | 要查询的 field_key 列表 |
| --project-key | string | 否 | 空间 key |

### workitem update
修改指定实例的字段值或角色。节点字段更新须用 `workflow update-node`。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --work-item-id | string | 是 | 工作项 ID 或名称 |
| --project-key | string | 否 | 空间 key |
| --fields | array | 否 | 要更新的字段列表,每项含 field_key 和 field_value |
| --role-operate | array | 否 | 角色操作,每项含 op(add/remove)、role_key、user_keys |

**角色更新**:不能通过 fields 更新角色,必须用 `role_operate`。role_key 通过 `workitem meta-roles` 获取,user_keys 通过 `user search` 获取。

**拉群方式更新(`group_type` 逻辑字段)**:要修改/读取拉群方式统一走 `group_type`,不要再单独操作 `group_id` / `chat_group`。写协议 `field_value` 形如:`{"type": "auto" | "bind" | "disabled", "group_id": "oc_xxx"}`(注意写用 `type` 作为判别键,**与读返回的 `value` 不对称**)。校验规则(服务端实际报错文本):`bind` 不带 `group_id` 或带空串/纯空格 → `group_id is required when group_type=bind`;`auto`/`disabled` 同时带 `group_id` → `group_type conflicts with group_id: type=<auto|disabled>`。详细示例见 [references/sop-update-workitem.md](references/sop-update-workitem.md)。

### workitem query
使用 MQL 查询工作项数据。语法详见 [references/mql-syntax.md](references/mql-syntax.md)。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --project-key | string | 是 | 空间标识(支持名称、simpleName、projectKey) |
| --mql | string | 是(翻页时可用 session_id 替代) | MQL 查询语句(完整 SQL) |
| --session-id | string | 否 | 分页会话 ID,传入后不解析 MQL 直接翻页 |
| --group-pagination-list | array | 否 | 分组分页信息,首次查询可不传;翻页时传 `[{ "group_id": "分组ID", "page_num": 页码 }]` |

**分组分页**:
- `--group-pagination-list` 是数组,当前只支持传一组分页数据;元素结构为 `{ "group_id": string, "page_num": number }`
- `group_id` 取首查返回的 `list[].group_infos[].group_id`;无分组查询返回的默认分组 ID 为 `"1"`,翻页时也传 `"1"`
- `page_num` 从 1 开始;MQL 首查不传分页参数时默认返回第一页,单页最多 50 条。当前接口没有 `page_size` / `page_token` 子字段
- 翻页时传首查返回的 `session_id` 和目标分组的分页参数;传 `session_id` 后后端不再解析 MQL,只按已有会话取对应分组页

**要点**:
- 先用 `workitem meta-fields` / `workitem meta-roles` 获取字段与角色配置;查不到直接报错不要继续
- SELECT 后属性不宜过多,**优先使用字段 key**(如 `name`、`priority`、`status`);返回按页返回,需全量时使用翻页参数

### workitem list-op-records
查看工作项操作记录。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --project-key | string | 是 | 空间 key |
| --work-item-id | string | 是 | 工作项 ID |

---

## Attachment 附件域

附件必须完成“对象存储上传 → 目标字段绑定 → 回读同一字段验收”;上传成功不代表附件字段已写入。涉及工作项附件时,必须先实时消歧当前类型下的 `file` 和 `multi-file` 字段,并按 [references/attachment.md](references/attachment.md) 的完整协议执行。Meegle CLI 提供 `attachment +upload` / `attachment +download` 一键封装对象存储步骤。
---

## WorkFlow 工作流域

> 流转辅助命令(`workflow list-state-transitions` / `workflow list-state-required` / `workflow meta-node-fields`)的参数表见 [references/workflow.md](references/workflow.md)。

### workflow transition
仅用于节点流工作项,操作节点完成流转或回滚。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --work-item-id | string | 是 | 工作项 ID |
| --action | string | 否 | confirm(流转) / rollback(回滚) |
| --node-id | string | 否 | 节点 ID |
| --rollback-reason | string | 否 | 回滚原因,action=rollback 时需填写 |
| --project-key | string | 否 | 空间 key |

### workflow transition-state
仅用于状态流工作项,流转工作项状态。先用 `workflow list-state-transitions` 获取可流转状态及 transition_id。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --work-item-id | string | 是 | 工作项 ID |
| --transition-id | string | 否 | 状态流转 ID,从 `workflow list-state-transitions` 获取 |
| --project-key | string | 否 | 空间 key |

### workflow get-node
获取工作项中指定节点或所有节点的完整详情。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --work-item-id | string | 是 | 工作项 ID 或名称 |
| --node-id-list | array | 否 | 节点 ID 列表,传空或 `_all` 获取所有节点 |
| --field-key-list | array | 否 | 节点字段 key,传空或 `_all` 获取所有字段 |
| --need-sub-task | boolean | 否 | 是否需要节点子项(子任务) |
| --page-num | number | 否 | 节点信息一次最多 20 个,按页返回 |
| --project-key | string | 否 | 空间 key |

### workflow update-node
修改节点(排期、负责人、自定义字段等)。排期/差异化排期/负责人不要同时修改,需分多次调用。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --work-item-id | string | 是 | 工作项 ID |
| --node-id | string | 是 | 节点 ID(node_key) |
| --node-owners | array | 否 | 节点负责人 userkey 数组;清空传空数组 `[]` |
| --node-schedule | object | 否 | 节点排期,格式 `{"estimate_start_date":ms,"estimate_end_date":ms,"owners":[userkey],"points":数字}`;清空传 `{}`;不变更则不传 |
| --schedules | array | 否 | 按人差异化排期,每项细化到单个人的排期;清空某人则 `estimate_start_date`/`estimate_end_date` 传 null |
| --fields | array | 否 | 节点自定义字段,每项含 `field_key` 和 `field_value`(STRING 协议,见「字段值格式」) |
| --project-key | string | 否 | 空间 key |

---

## MyWork 工作台域

### mywork todo
按 action 类型查询当前用户的工作项列表。无需 MQL 即可查询待办/已办。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --action | string | 是 | todo(待办)/done(已办)/overdue(逾期)/this_week(本周待办) |
| --page-num | number | 是 | 页码,从 1 开始,每页 50 条 |
| --asset-key | string | 否 | 工作区 key(格式 Asset_xxx),仅在报错需要选择时传 |

需完整结果时,从 page_num=1 连续翻页直到空为止。

---

## WorkHour 工时域

> 工时记录查询(`workhour list-records`)的参数表见 [references/misc.md](references/misc.md)。

### workhour list-schedule
获取指定人员在时间区间内的排期与工作量明细。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --project-key | string | 是 | 空间 key |
| --user-keys | array | 是 | 用户标识(名称/邮箱/userkey),**每次最多 20 个** |
| --start-time | string | 是 | 开始时间,格式 YYYY-MM-DD |
| --end-time | string | 是 | 结束时间,格式 YYYY-MM-DD,**单次跨度最大 3 个月** |
| --work-item-type-keys | array | 否 | 工作项类型列表,查询所有传入 `_all` |

**调用约束**:每次最多 20 人(多人拆批次并行);单次跨度 ≤ 3 个月(超出按月拆分);所有批次完成后再汇总,未完整获取前不得输出结论。

---

## UserGroup 人员域

> 团队相关命令(`team list` / `team list-members`)的参数表见 [references/misc.md](references/misc.md)。

### user search
批量查询用户基础信息。用于将姓名/邮箱转换为 userkey。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --user-keys | array | 是 | userKey、Email 或名字,最多 20 个 |
| --project-key | string | 否 | 空间 key |
| --need-all-status | boolean | 否 | 是否返回所有状态用户;默认 false,仅返回在职用户 |

### user me
查看当前用户信息。无需参数。

> **MQL 中**可直接用 `current_login_user()` 函数,无需提前获取用户信息。如需获取当前用户的 userkey/姓名等详细信息,用 `user me`。

---

## View 视图域

> 视图搜索与固定视图管理(`view search` / `view create-fixed` / `view update-fixed`)的参数表见 [references/view.md](references/view.md)。

### view get
根据视图 ID 获取该视图下的工作项列表。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --view-id | string | 是 | 视图 ID |
| --project-key | string | 否 | 空间 key |
| --page-num | number | 否 | 分页页数起点 |
| --fields | array | 否 | 要查询的字段 |

---

## Comment 评论域

> 评论列表查询(`comment list`)的参数表见 [references/misc.md](references/misc.md)。

### comment add
添加评论。支持富文本 Markdown,语法详见 [references/rich-text-editor-markdown-syntax.md](references/rich-text-editor-markdown-syntax.md)(含 @提及、对齐、链接预览、字号/颜色等扩展语法)。

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| --work-item-id | string | 是 | 工作项 ID |
| --content | string | 是 | 评论内容 |

---

## Deliverable 交付物域

> 单命令小域,参数表见 [references/misc.md](references/misc.md)。

### deliverable list
查看交付物详情及其根工作项 / 来源工作项。可按工作项 ID 列表过滤。

---

## Resource 资源库

> 资源库(资源模板)管理。`resource create` 当前对应 MCP `create_resource_work_item`,用于创建资源实例;查看资源库的字段 / 角色配置用 `resource meta-fields`。详细参数表见 [references/misc.md](references/misc.md)。

### resource create
在已启用资源库的工作项类型下创建资源模板(资源实例)。创建前先调 `resource meta-fields` 取字段 / 角色配置。

**语义边界**:
- 创建资源实例:`work_item_type_key` 是资源库启用的工作项类型;`template_id` 是该类型下的流程模板 ID/名称;`fields` / `roles` 是新资源实例自身的字段和角色。
- 从资源实例创建普通工作项:不要把已有资源实例 ID 填到 `work_item_type_key` 或 `template_id`。必须以当前 `resource create` 的 inspect/schema 为准确认是否有源资源实例参数;若当前 schema 未暴露该参数,先向用户说明无法确认自动化参数,不要猜。

---

## WBS 计划表

> 计划表(WBS)有 **草稿(draft)** 与 **已发布实例(instance)** 两套数据模型。常见编辑流程:`wbs create-draft` → 多次 `wbs edit-draft` → `wbs publish-draft`;放弃改动用 `wbs reset-draft`。详细参数表与 `wbs edit-draft` 的 operation 子类型见 [references/wbs.md](references/wbs.md)。

### wbs list-draft-rows
在计划表草稿中按条件筛选行。常用筛选字段:`wbs_name`、`wbs_parent_id`、`wbs_owner_in_charge`、`wbs_states_doing`。详见 [references/wbs.md](references/wbs.md)。

### wbs list-instance-rows
在已发布的线上计划表实例中按条件筛选行。参数同 `wbs list-draft-rows`。

### wbs edit-draft
对计划表草稿执行一次原子编辑。一次调用只能传一个 `operation_type`,但该操作内部可通过 `items` / `uuids` 等数组承载单行或批量编辑;支持新增 / 删除 / 恢复 / 排序 / 改名 / 改负责人 / 改阶段 / 改排期 / 改估分 / 改实际工时等,结构见 [references/wbs.md](references/wbs.md)。
> ⚠️ **前置**:草稿不存在时先调 `wbs create-draft`,再调 `wbs edit-draft`。判断方法:直接 `wbs list-draft-rows` 报"草稿不存在"类错误即视为缺失草稿。

### wbs publish-draft
将编辑完成的草稿发布到线上。
> ⚠️ 全量发布前必须用**固定话术**二次确认:"本人及协同者的全部编辑内容均会被发布,请确认是否全量发布?";部分发布(传入 `uuid_strings_list`)无需二次确认。

---

## 其它低频域

度量图表、子任务、关系定义查询的命令参数表见 [references/misc.md](references/misc.md):

- **Chart 度量域** — `chart get` / `chart list`
- **SubTask 子任务域** — `subtask update`(create/update/confirm/rollback)
- **Relation 关系域** — `relation list` / `relation meta-definitions`
- **WBS 计划表 · 辅助命令** — `wbs create-draft` / `wbs reset-draft` / `wbs get-draft-progress` / `wbs list-element-templates`(见 [references/wbs.md](references/wbs.md))

---

## 字段值格式(field_value)

> 🚨 **STRING 协议**:`field_value` 协议层固定为字符串。标量(text/number/bool/option_id/userkey/毫秒)直接作字符串;数组、对象**必须先 JSON.stringify** 再传,直接传会报 `need STRING type, but got: LIST` / `MAP`。
> 例:multi-user 正确写法为 `"[\"<userkey>\"]"`,错误写法为 `["<userkey>"]`。

| 字段类型 | 语义 | field_value 传参(已按前述规则序列化) |
|---------|------|------|
| template | 模板 ID(**创建必填**) | `"<template_id>"` — 用 `workitem meta-fields(field_keys=["template"])` 获取 |
| text / multi-pure-text / link / bool / number | 单个字面值 | `"需求标题"` / `"100"` / `"true"` |
| user | 单个 userkey | `"<userkey>"` |
| multi-user | userkey 数组(**stringified**) | `"[\"<userkey1>\",\"<userkey2>\"]"` |
| select / radio / tree-select | 枚举项 option_id | `"<option_id>"` |
| multi-select | option_id 对象数组(**stringified**) | `"[{\"option_id\":\"111\"},{\"option_id\":\"222\"}]"` |
| tree-multi-select | option_id 字符串数组(**stringified**) | `"[\"id1\",\"id2\"]"` |
| multi-text | 富文本 Markdown 字符串(语法详见 [references/rich-text-editor-markdown-syntax.md](references/rich-text-editor-markdown-syntax.md)) | `"**加粗**内容"` |
| date | 毫秒时间戳(天精度) | `"1722182400000"` |
| schedule | `[开始ms, 结束ms]`(**stringified**) | `"[1722182400000,1722355199999]"` |
| precise_date | 对象(**stringified**) | `"{\"start_time\":1722182400000,\"end_time\":1722355199999}"` |
| workitem_related_select | 关联工作项 ID | `"<work_item_id>"` |
| workitem_related_multi_select | ID 数组(**stringified**,数字元素) | `"[<id1>,<id2>]"` |
| role_owners(仅创建时) | 角色-人员对象数组(**stringified**) | `"[{\"role\":\"<role_id>\",\"owners\":[\"<userkey>\"]}]"` |
| signal | option_id 字符串(**写入值位**;MQL 查询值位为 label,见 [mql-syntax.md §4](references/mql-syntax.md)) | `"<option_id>"`(以 `workitem meta-fields` 的 `options[].option_id` 为准;当前系统外信号常见 4 个 option:`passed` / `notpassed` / `processing` / `noinformationyet`) |
| compound_field | 普通复合明细表(**stringified** action 对象) | `"{\"action\":\"add\",\"fields\":[[{\"field_key\":\"sub_key1\",\"field_value\":\"v1\"}]]}"` |
| multi_user_compound_field | 多人复合明细表(**仅更新已有人员**;stringified userkey map,整体覆盖) | `"{\"userkey1\":[{\"field_key\":\"sub_key1\",\"field_value\":\"v1\"}],\"userkey2\":[]}"` |

> 更新角色时不用 fields,用 `workitem update` 的 `role_operate` 参数。

### 普通复合明细表(compound_field)

普通复合明细表通过 `workitem update` 写入时,`field_value` 是 **stringified JSON action 对象**。直接传 JSON object 会在客户端序列化阶段报 `unsupported type: map[string]interface {}, expected type: STRING`。

```json
{"action": "add", "fields": [[{"field_key": "子字段key", "field_value": "子字段值"}, ...]]}
```

- **action** — 操作类型:`"add"`(新增行)、`"update"`(更新行)、`"delete"`(删除行)
- **fields** — **二维数组**:外层每个元素代表一行记录,内层是该行的子字段列表
- 子字段的 `field_value` 遵循各自字段类型的 STRING 协议(text 传纯字符串,multi-user 传 stringified 数组等)
- 子字段 key 从 `workitem meta-fields` 返回的 `compound_field_info` 中获取
- `add` 不传行标识;读取新增结果后,每行会带 `group_uuid`
- `update` / `delete` 必须原样传 `group_uuid` 定位行,键名就是 `group_uuid`,**不是 `record_id`**

更新一行:

```json
{"action": "update", "group_uuid": "读回的组标识", "fields": [[{"field_key": "子字段key", "field_value": "新值"}]]}
```

删除一行:

```json
{"action": "delete", "group_uuid": "读回的组标识"}
```

### 多人复合明细表(multi_user_compound_field)

多人复合明细表**不使用 action / group_uuid 协议**。写入值是 **stringified JSON map**:key 为填写人的 userkey,value 为该人员的子字段数组。

```json
{
  "userkey1": [
    {"field_key": "子字段key", "field_value": "子字段值"}
  ],
  "userkey2": []
}
```

🚨 **整体覆盖,非增量更新**。仅传部分 userkey 会导致未包含的 userkey 整行被删除。更新前必须:

1. 用 `workitem get` 读取当前多人复合字段;返回 map 的 key 是当前人员范围,每个 value 含 `user`,有值时另含 `child_field_list`
2. 用 `workitem meta-fields` 读取 `compound_field_info`,确定子字段 key、类型和枚举 option_id
3. 重建**包含全部现有 userkey** 的 map;非目标人员的子字段值也要保留,只修改目标人员
4. 将完整 map JSON.stringify 后写回;写后再次读取核对所有人员和目标子字段

此协议只用于修改 `workitem get` 当前 map 中**已经存在的人员**。元数据接口只返回子字段配置;`workitem meta-roles` 只返回角色字典,二者均不返回 `editable_personnel_range_type`、字段绑定角色、可选人员或新增人员协议。若当前值为空或目标人员不在 map 中,立即停止并说明当前 Skill / CLI 无法自动新增人员;由用户先通过页面把人员加入范围,再重新读取后更新。不要尝试用 `{"userkey":[]}` 新增人员——接口会返回成功但回读仍为空。

枚举子字段仍传 option_id;读取返回的 `{label, value}` 不能原样回写,应取其中的 option_id 值。修改接口返回空成功不代表已落值,必须回读;若人员或目标值未变化,按未生效报告,不得宣称成功。

### 关联工作项字段(workitem_related_*)

用户提供名称而非 ID 时,需按名称→ID 转换流程(搜目标空间+类型,消歧,写入格式,防循环引用):详见 [references/field-value-extras.md](references/field-value-extras.md)。

---

## 常用场景速查

| 场景 | 命令(注意点) |
|------|-------|
| 空间名 → project_key | `project search` |
| 查类型 / 字段 / 角色 | `workitem meta-types` / `workitem meta-fields` / `workitem meta-roles` |
| 人名 → userkey | `user search`(批量 ≤20) |
| 当前用户 | `user me`;MQL 内可直接 `current_login_user()` |
| 条件查询 / 个人待办 | `workitem query`(MQL) / `mywork todo` |
| 团队排期 | `workhour list-schedule`(≤20 人、≤3 月) |
| 创建 / 修改工作项 | `workitem create` / `workitem update`(字段 fields,角色 role_operate) |
| 节点流转 / 状态流转 | `workflow transition`(confirm/rollback) / `workflow transition-state`(先 `workflow list-state-transitions`) |
| 视图数据 | `view get` |
| 生成 Meegle 页面链接 | 仅在用户明确要求链接或链接本身是交付物时,读取 [references/url-links.md](references/url-links.md) |

## 链接生成

仅当用户明确要求“给链接 / 拼链接 / 打开地址”,或链接本身是请求的交付物时,读取并执行 [references/url-links.md](references/url-links.md)。不要给普通查询、创建或更新结果自动附加链接。


## 通用规范

### 请求处理流程

收到用户输入后依次执行:

1. **参数提取**:从自然语言中提取空间名、工作项类型、时间、人员、筛选条件;含 URL 时先调 `url decode` 解析,按 [references/url-kinds.md](references/url-kinds.md) 的 `url_kind` 分支决定进入哪个 SOP 或拒绝。**禁止**自己从 URL 截取路径段作参数。注意区分空间名与筛选维度(如「XX空间下YY业务线的缺陷」中 XX 才是空间名)。

2. **参数确认**(禁止猜测):用探测命令校验空间(`project search`)、类型(`workitem meta-types`)、人员(`user search`)。**探测结果不唯一时必须展示并询问用户**,禁止自行选择;缺失必填合并为一条消息询问。个人待办(`mywork todo`)可跳过;URL 经 `url decode` 拿到 `simple_name` 后仍需 `project search` 转权威 `project_key`(同名空间可能有多个无权限)。

3. **元数据收集**(无需用户参与):调用 `workitem meta-fields` 获取字段定义(需要特定字段用 `field_keys`,模糊查询用 `field_query`)。**必须同时传 `--project-key` 与 `--work-item-type`**;缺一都会拿到错误 / 空 / 跨类型污染的字段配置。涉及角色时并行调 `workitem meta-roles`。关键字段识别:状态字段 type=`_work_item_status`(含「完成/关闭/终止」的值为完成态)、排期字段 type=`schedule`(MQL 用 `__字段名_开始时间` / `__字段名_结束时间`)、优先级字段 key=`priority`。简单直调场景(仅需 project_key + work_item_id,如 `comment add`)可跳过本步。

4. **执行**:调用目标命令,遵循 [references/performance.md](references/performance.md) 的并行/翻页规则。

5. **判断是否续处理**:完成 CLI 可执行的部分,或确认用户请求超出 CLI 能力后,按 [AI 助手续处理细则](references/ai-handoff.md) 判断是否提供兜底跳转链接。链接只补充 CLI 无法完成的部分,不能替代已能返回的数据或操作结果。

### 并行与大结果

详见 [references/performance.md](references/performance.md):并行调用(必须串行的链路、可并行的组合)、大结果分批与翻页规则。

### 错误处理

**总则**:失败后从返回的 `err_msg` / `inner_err` 中提取错误原因,针对性修正后重试;**最多自动重试 2 次**,连续 3 次同类失败后停止并向用户说明。

**熔断条件**(立即终止,禁止盲目重试):
- 空间未找到(`project search` 连续 3 次失败)
- Permission Denied(当前用户对该空间无访问权限)
- `ai-handoff availability` 返回不可用,或 `ai-handoff create-link` 返回 `HANDOFF_REJECTED` / `LOCAL_DISABLED`;不得为生成链接而重复调用

详细自愈规则与错误速查表(涵盖字段格式、节点流转、人员转换等常见报错)见 [references/error-handling.md](references/error-handling.md)。

---

## AI 助手续处理

仅当用户还需要分析、总结、诊断、建议,或请求了 CLI 不支持的操作时,读取并执行 [references/ai-handoff.md](references/ai-handoff.md)。核心约束:

- 先交付 CLI 能完成的结果;若请求本身完全不受 CLI 支持,明确能力边界后可直接进入续处理判断,不得伪造一次 CLI 输出;
- `available=false` 或 `mode=off` 时不生成链接;`mode=auto` 可直接生成,`mode=ask` 必须先征得用户同意;
- 每轮最多生成一条链接,同一会话最多主动引导一次;链接只携带 query 与 ID 指针,不声称已传递 CLI 完整结果集。

---

## 操作指南(SOP)

具体操作的完整流程、字段转换和自愈机制见对应 SOP:

- [创建工作项](references/sop-create-workitem.md) — 创建需求、任务、缺陷
- [更新工作项](references/sop-update-workitem.md) — 修改字段、更新角色、追加内容
- [流转节点(节点流)](references/sop-transition-node.md) — 完成/回滚节点、批量流转
- [流转状态(状态流)](references/sop-transition-state.md) — 流转缺陷/issue、关闭 bug