@lark-apaas/coding-miaoda-sandbox-skills 0.1.36 → 0.1.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -56,6 +56,14 @@ Step 5: 编写代码
56
56
 
57
57
  > reference 文件中的代码示例省略了 Service 类包裹,实际使用时 `client` 应替换为 `this.client` 或从注入的 FeishuService 获取。
58
58
 
59
+ ## Task v2 首次分流
60
+
61
+ Task v2 编码前必读 [专项 reference](references/task-v2.md)。首次编辑创建逻辑前,检查请求入口→分支兼容响应类型→匹配 wrapper/解析器→远端 GUID→既有返回/登记链;新增/切换分支须协调全链路。
62
+
63
+ - 普通创建:`client.task.v2.task.create`、普通响应类型、`data.task.guid`;子任务创建:`client.task.v2.taskSubtask.create`、兼容响应类型、`data.subtask.guid`。禁止无条件跨 wrapper fallback。可跨 helper 多处替换,也可用 SDK/等价 raw HTTP;未受影响的 GUID 缺失处理、返回/登记不改。
64
+ - 父任务设置/改变用 `client.task.v2.task.setAncestorTask` 传 `ancestor_guid`;清除用同一接口省略该字段;`parent_task_guid` 不进通用 PATCH。
65
+ - `completed_at` 非零→另一固定非零值时,先成功写 `"0"` 再写预定值;任一步失败/不确定即停止,不宣告完成。
66
+
59
67
  ## 插件优先原则
60
68
 
61
69
  以下飞书能力存在**平台一方插件**,优先使用插件,不要直接用 node-sdk:
@@ -89,6 +97,7 @@ Step 5: 编写代码
89
97
  | 考勤 | `references/attendance.md` | 打卡记录/考勤规则查询 |
90
98
  | 事件订阅 | `references/events.md` | WebSocket 长连接接收事件 |
91
99
  | OAuth 授权 | `references/oauth.md` | user_access_token 授权 |
100
+ | 任务 v2 | `references/task-v2.md` | 普通/子任务创建、父级变更与完成态恢复(编码前必读) |
92
101
 
93
102
  > 按需读取 `references/` 下的模块文档获取详细 API 用法和代码示例。
94
103
 
@@ -0,0 +1,141 @@
1
+ # 任务 v2 (Task v2)
2
+
3
+ 使用 `@larksuiteoapi/node-sdk` 创建、读取和更新飞书任务。示例省略 Service 类包裹,按主文约束使用注入的客户端。
4
+
5
+ ## 官方文档与权限
6
+
7
+ 文档:[任务 v2 概述](https://open.feishu.cn/document/task-v2/overview.md)、[创建任务](https://open.feishu.cn/document/task-v2/task/create.md)、[创建子任务](https://open.feishu.cn/document/task-v2/task-subtask/create.md)、[获取任务详情](https://open.feishu.cn/document/task-v2/task/get.md)、[更新任务](https://open.feishu.cn/document/task-v2/task/patch.md)、[设置父任务 API Explorer](https://open.feishu.cn/api-explorer?from=op_doc_tab&apiName=set_ancestor_task&project=task&resource=task&version=v2)。
8
+
9
+ 应用权限简称:`R=task:task:read`、`W=task:task:write`、`WO=task:task:writeonly`。
10
+ - 普通/子任务创建:`W` 或 `WO`;子任务另需父任务可编辑;加入清单还需清单可编辑。
11
+ - 更新 `completed_at`:`W` 或 `WO`,且目标任务可编辑。
12
+ - 设置、改变或清除父任务:仅 `W`,且目标任务可编辑。
13
+ - 获取详情和恢复读回:`R` 或 `W`,且目标任务可读。
14
+
15
+ 应用权限不替代对象权限;编码前在接口页复核当前租户需启用的权限,机器人调用还须核对机器人对目标任务、父任务和清单的访问权。
16
+
17
+ ## 创建入口、返回 wrapper 与幂等
18
+
19
+ 普通任务与子任务使用不同 SDK 入口和返回 wrapper。每个分支先提取自己的 wrapper,再归一化为本地 `remoteGuid`;缺少预期 wrapper 时立即失败,不要用 `task?.guid ?? subtask?.guid` 掩盖入口不匹配。
20
+
21
+ ```typescript
22
+ const clientToken = createIntent.clientToken;
23
+ let remoteGuid: string;
24
+
25
+ if (parentGuid) {
26
+ const subtaskResult = await this.client.task.v2.taskSubtask.create({
27
+ path: { task_guid: parentGuid },
28
+ params: { user_id_type: 'open_id' },
29
+ data: {
30
+ summary,
31
+ client_token: clientToken,
32
+ },
33
+ });
34
+ if (subtaskResult.code !== 0) {
35
+ throw new Error(`Feishu API error [${subtaskResult.code}]: ${subtaskResult.msg}`);
36
+ }
37
+
38
+ const subtaskGuid = subtaskResult.data?.subtask?.guid;
39
+ if (!subtaskGuid) {
40
+ throw new Error('Feishu subtask create response is missing data.subtask.guid');
41
+ }
42
+ remoteGuid = subtaskGuid;
43
+ } else {
44
+ const taskResult = await this.client.task.v2.task.create({
45
+ params: { user_id_type: 'open_id' },
46
+ data: {
47
+ summary,
48
+ client_token: clientToken,
49
+ },
50
+ });
51
+ if (taskResult.code !== 0) {
52
+ throw new Error(`Feishu API error [${taskResult.code}]: ${taskResult.msg}`);
53
+ }
54
+
55
+ const taskGuid = taskResult.data?.task?.guid;
56
+ if (!taskGuid) {
57
+ throw new Error('Feishu task create response is missing data.task.guid');
58
+ }
59
+ remoteGuid = taskGuid;
60
+ }
61
+ ```
62
+
63
+ 取得 `remoteGuid` 后再登记本地逻辑对象与远端任务的映射。`client_token` 标识逻辑创建,不是单次网络尝试:
64
+
65
+ 1. 首次调用前生成并持久化 token。
66
+ 2. 超时、响应丢失或远端成功但本地登记失败时,保留创建意图、入口分支和 token。
67
+ 3. 重试时调用原分支并复用该 token,再按该分支的实际 wrapper 解析 GUID;不得换 token 重建。
68
+
69
+ ## 既有任务的父级变更
70
+
71
+ `parent_task_guid` 是返回任务实体字段,不在通用 PATCH 输入合同中。既有任务设置或改变父任务时传 `ancestor_guid`;清除时省略它,使任务成为独立任务。
72
+
73
+ ```typescript
74
+ const setResult = await this.client.task.v2.task.setAncestorTask({
75
+ path: { task_guid: taskGuid },
76
+ data: { ancestor_guid: parentGuid },
77
+ });
78
+ if (setResult.code !== 0) {
79
+ throw new Error(`Feishu API error [${setResult.code}]: ${setResult.msg}`);
80
+ }
81
+
82
+ const clearResult = await this.client.task.v2.task.setAncestorTask({
83
+ path: { task_guid: taskGuid },
84
+ data: {},
85
+ });
86
+ if (clearResult.code !== 0) {
87
+ throw new Error(`Feishu API error [${clearResult.code}]: ${clearResult.msg}`);
88
+ }
89
+ ```
90
+
91
+ 改变父任务使用与设置相同的调用,只替换 `ancestor_guid`。变更后按需调用 `client.task.v2.task.get`,核对返回的 `parent_task_guid`,再更新本地状态或继续扩散。
92
+
93
+ ## `completed_at` 状态转换
94
+
95
+ `completed_at` 是毫秒时间戳字符串:`"0"` 表示未完成,非零值表示完成时间。创建请求可带初始非零值;PATCH 不能把已完成任务直接再次完成。旧值与目标值均非零且不同时,执行两步转换。
96
+
97
+ 先保留固定的 `oldCompletedAt` 和 `targetCompletedAt`,并记录当前阶段。只有清零响应成功后,才允许写入固定目标:
98
+
99
+ ```typescript
100
+ const clearResult = await this.client.task.v2.task.patch({
101
+ path: { task_guid: taskGuid },
102
+ data: {
103
+ task: { completed_at: '0' },
104
+ update_fields: ['completed_at'],
105
+ },
106
+ });
107
+ if (clearResult.code !== 0) {
108
+ throw new Error(`Feishu API error [${clearResult.code}]: ${clearResult.msg}`);
109
+ }
110
+
111
+ const targetResult = await this.client.task.v2.task.patch({
112
+ path: { task_guid: taskGuid },
113
+ data: {
114
+ task: { completed_at: targetCompletedAt },
115
+ update_fields: ['completed_at'],
116
+ },
117
+ });
118
+ if (targetResult.code !== 0) {
119
+ throw new Error(`Feishu API error [${targetResult.code}]: ${targetResult.msg}`);
120
+ }
121
+ ```
122
+
123
+ 若目标写入失败或结果不确定:
124
+
125
+ 1. 立即停止后续对象扩散,记录 `target_write_failed` 阶段、旧值、固定目标值和错误;不要宣告完成。
126
+ 2. 调用 `client.task.v2.task.get({ path: { task_guid: taskGuid } })`,读取 `data.task.completed_at`。
127
+ 3. 读回值等于固定目标时,不再写入,转为完成本地登记;读回值为 `"0"` 时,只重试固定目标,或按恢复策略只写回旧非零值;读回值等于旧值时视为已恢复,不重放两步。
128
+ 4. 读回缺失或出现其它非零值时停止并保留故障状态,不覆盖未知远端变化。恢复写入也失败时再次读回后再决定,禁止盲目重放清零和目标两次写入。
129
+
130
+ 只有成功响应或读回证明远端值等于固定目标,且本地登记完成后,才能把该转换记为完成。
131
+
132
+ ## 扩散前的单对象闭环
133
+
134
+ 批量或层级创建前,先选一个调用身份已有权限的对象,依次验证:
135
+
136
+ - 请求使用预期入口并成功返回;
137
+ - 按该入口的 `data.task.guid` 或 `data.subtask.guid` 取得 GUID;
138
+ - GUID 与稳定 `client_token` 一并完成本地登记;
139
+ - 创建、父级或完成态需要确认时,通过详情读取核对远端事实。
140
+
141
+ 该对象未闭环时停止,不继续创建或更新其它远端对象。
@@ -21,7 +21,7 @@ gate-tools:
21
21
  ```js
22
22
  Task({
23
23
  subagent_type: "E2E",
24
- agent_options: { test_plan_file_path: ".spark/e2e-test/standard/<语义名>.json", retest_all: true },
24
+ agent_options: { test_plan_file_path: ".spark/e2e-test/<语义名>.json", retest_all: true },
25
25
  description: "验收<功能名称>",
26
26
  prompt: "执行<具体交互或业务闭环>,断言<可观察终态>。",
27
27
  })
@@ -29,22 +29,27 @@ Task({
29
29
 
30
30
  ### 验收计划文件
31
31
 
32
- 每次派发都必须带 `agent_options.test_plan_file_path`,不传不执行。先列出 `.spark/e2e-test/standard/` 目录并**读**可能覆盖本需求的那份;同需求有变化就编辑那份,换功能或页面则新建。计划只包含交互、业务、数据和网络的可观察终态;`prompt` 只说明本轮执行范围和 Case 编号。
32
+ 每次派发都必须带 `agent_options.test_plan_file_path`,不传不执行。先列出 `.spark/e2e-test/*.json` 和历史目录 `.spark/e2e-test/standard/*.json`,**读**可能覆盖本需求的那份:已有计划沿用原路径(包括 standard/ 下的,不复制、不搬迁),同需求有变化就编辑那份;没有对应计划的新功能或页面,才新建 `.spark/e2e-test/<语义名>.json`。计划只包含交互、业务、数据和网络的可观察终态;`prompt` 只说明本轮执行范围和 Case 编号。
33
33
 
34
34
  格式(一条用例一行,`id` 必须是**正整数**、优先级小写,每卷 ≤10 条):
35
35
 
36
36
  ```json
37
- {"cases":[{"id":1,"priority":"p0","description":"打开首页,预期工单列表渲染出至少 1 行"}]}
37
+ {"cases":[
38
+ {"id":1,"priority":"p0","description":"打开首页,预期工单列表渲染出至少 1 行"}{% if permissionIdentityEnabled %},
39
+ {"id":2,"priority":"p1","description":"打开工单设置页,预期显示「成员管理」入口","identity":{"mode":"auth","confirmedRoleIds":["<role_id>"]}}{% endif %}
40
+ ]}
38
41
  ```
39
42
 
40
- 本轮跑哪些用例只认结构化字段,写在 `prompt` 里的自然语言范围不生效:刚写好或刚改过的计划首次验收带 `agent_options.retest_all: true`,跑全部用例;修复后复验带 `agent_options.retest_case_ids: [1, 2]`,只列失败或受这次修复影响的用例(编号取计划文件里的 `id`,必须是正整数,不是 `"case-1"` 这类字符串)。
43
+ {% if permissionIdentityEnabled %}需要特定角色或登录状态的用例,在测试计划里给该用例写 identity:普通登录 {"mode":"auth"},未登录 {"mode":"anon"},角色 {"mode":"auth","confirmedRoleIds":["<role_id>"]}。role_id 用 rbac_role_manager QUERY 查,取返回的 role_catalog 里的 role_id(即 BizID),不是角色记录的数字 ID。不同用例可以写不同角色。不要写在 prompt 里,也不要自己 MOCK。
44
+
45
+ {% endif %}本轮跑哪些用例只认结构化字段,写在 `prompt` 里的自然语言范围不生效:刚写好或刚改过的计划首次验收带 `agent_options.retest_all: true`,跑全部用例;修复后复验带 `agent_options.retest_case_ids: [1, 2]`,只列失败或受这次修复影响的用例(编号取计划文件里的 `id`,必须是正整数,不是 `"case-1"` 这类字符串)。
41
46
 
42
47
  ## 验收要求
43
48
 
44
49
  - 检查视觉的用例写清可观察结果,例如「首页标题完整可见、卡片两列排列、主图不是裂图」,不要只写「看起来正常」。
45
50
  - 功能验收必须走到终态:提交后记录出现、跳转后内容正确、删除后记录消失或 AI 输出出现;不要停在弹窗、按钮或表单字段出现。
46
51
  - 写操作和资源加载要核对网络结果;非 2xx、空结果、错误兜底或白屏都应报失败。
47
- - 不测试登录、认证、CAPTCHA,或会造成真实外部副作用的行为。
52
+ - {% if permissionIdentityEnabled %}不测登录流程本身(登录页、注册、OAuth、CAPTCHA),也不测会造成真实外部副作用的行为;需要以某角色或未登录状态验证的用例用 identity 声明。{% else %}不测试登录、认证、CAPTCHA,或会造成真实外部副作用的行为。{% endif %}
48
53
  {% else %}
49
54
 
50
55
  # 检查与验证:工具选择指南
@@ -67,13 +72,11 @@ Task({
67
72
  ├─ Task 工具定义的 subagent_type 选项里没有 E2E(E2E 被关闭/不可用,非"本轮没派过")→ api_request(接口级验收)+ 读代码/读日志兜底,不要尝试派 E2E
68
73
  ├─ 明确要求验证「后端接口」「API 返回值」「请求响应」 → api_request
69
74
  └─ 其他所有情况 → Task(subagent_type="E2E")(打开浏览器,以用户视角操作应用)
70
- ├─ 基础视觉验证(白屏/布局/样式/文案)→ agent_options.mode: "lite"(只截图看视觉)
71
- └─ 用户明确要求验证交互/业务流程/数据(点击/填写/提交/走一遍流程)→ agent_options.mode: "standard"
72
75
  ```
73
76
 
74
77
  - **E2E 不可用时改走 api_request(最高优先,先于下面所有规则判断)**:当 Task 工具定义的 subagent_type 选项里没有 E2E(用户关闭了浏览器验收 / 环境不支持;判断依据是工具定义中是否含 E2E 选项,不是"本轮有没有派过 E2E")时,下面"模糊需求一律派遣 E2E"不再适用——改用 `api_request` 做接口级验收,配合读代码 / 读运行时日志确认核心链路,**不要尝试派 E2E、也不要空等**;此时验收范围收敛到接口与数据层可判定结果,视觉类目标顺延到非核心降级范围
75
78
  - **模糊需求一律派遣 E2E**(仅当 E2E 可用):未明确提到「接口」「API」「后端」时必须派遣 E2E,不要主动选择 `api_request`
76
- - **基础视觉验证走 lite,standard 需用户明确要求功能测试**:用户没有主动要求跑具体功能(只说"看看有没有问题"这类模糊需求也算没主动要求)时,显式传 `agent_options.mode: "lite"`;只有用户明确要验证交互 / 业务流程 / 数据(点击、填写、提交、走一遍流程等)时才显式传 `agent_options.mode: "standard"`
79
+ - **E2E 一律完整执行**:派 `Task(subagent_type="E2E")` 时按标准语义执行,调用时不要额外指定运行方式;视觉检查随页面访问一起完成
77
80
  - **`api_request` 仅限显式请求**:仅当明确提到接口 / API / 后端 / 请求 / 响应、且意图是验证接口逻辑而非页面功能时才使用
78
81
  - **E2E 结束后禁止追加 `api_request`**:浏览器操作的结果即为最终结果,**严禁**再自动补充验证
79
82
 
@@ -83,24 +86,12 @@ Task({
83
86
  - 完整体验依赖真实设备、语音/视频生成等长媒体处理、第三方实时通道或后台长异步终态且无法稳定观察时,标注未验收边界。
84
87
  - 有可观察部分时只选一种现有验证方式;达到时间或次数上限即停,标为部分验收/未验收,不新增验收专用工具,不跨轮等待不可控终态。
85
88
 
86
- ### E2E 的 mode 选择
87
-
88
- 通过 `agent_options.mode` 指定,**必填参数**,且只允许 `lite` 或 `standard`;缺失或非法时 E2E 会拒绝执行:
89
-
90
- | mode | 适用场景 | 行为 | 录屏 |
91
- |------|---------|------|------|
92
- | `lite` | 基础视觉验证(白屏/布局/样式/文案)或快速冒烟 | 仅 `open` / `goto` / `wait` / `snapshot` / `screenshot`,**禁止** `click` / `fill` / `type` / `scroll` / `select` / `press` / `drag` / `hover` | ❌ |
93
- | `standard` | **仅当用户明确要求**验证业务流程、数据流、交互后状态变化 | 完整交互(点击/输入/滚动/提交)+ Network 验证 + 录制 | ✅ |
94
-
95
- **每次派发都必须显式传 mode**。基础视觉验证传 `lite`;`lite` 不能触发 popup / toast / 提交后状态切换等需要操作的现象。**用户明确要求跑功能**(testRequirements 里出现"点击"、"填写"、"提交"等动词,或用户直接要求验证某功能)时,传 `standard`。
96
-
97
89
  ## 工具选择速查表
98
90
 
99
91
  | 用户表达 | 选择 | 原因 |
100
92
  | --- | --- | --- |
101
- | "检查一下页面" / "看看有没有问题" / "哪里出问题了" / "试试能不能用" / "XX 不好使" | E2E(显式 `mode: "lite"`) | 未主动要求跑功能,执行基础视觉验证 |
102
- | "刚改了 CSS,看看样式对不对" | E2E(lite) | 仅视觉巡检 |
103
- | "点提交试试能不能成功" / "帮我把下单流程走一遍" / "走一遍流程" / "验证一下这个功能" | E2E(standard) | 用户明确要求功能 / 交互验证 |
93
+ | "检查一下页面" / "看看有没有问题" / "哪里出问题了" / "试试能不能用" / "XX 不好使" / "刚改了 CSS,看看样式对不对" | E2E | 未明确提到接口,用浏览器验收 |
94
+ | "点提交试试能不能成功" / "帮我把下单流程走一遍" / "走一遍流程" / "验证一下这个功能" | E2E | 用户明确要求功能 / 交互验证 |
104
95
  | "测试一下这个接口的返回值对不对" / "调一下后端 API 看看响应" | `api_request` | 明确指定接口验证 |
105
96
 
106
97
  ## 派遣 E2E 时的 prompt / 用例规范
@@ -111,27 +102,32 @@ Task({
111
102
 
112
103
  ### 验收计划文件:先写文件,再派发(首次与后续同一套)
113
104
 
114
- 每次派发都必须带 `agent_options.test_plan_file_path`,不传不执行。**两种 mode 各一份卷**,目录必须与派发 mode 一致(串卷不执行):standard 卷写 `.spark/e2e-test/standard/<语义名>.json`(含点击 / 输入 / 提交),lite 卷写 `.spark/e2e-test/lite/<语义名>.json`(只放看得见的检查点)。
105
+ 每次派发都必须带 `agent_options.test_plan_file_path`,不传不执行。
115
106
 
116
- 1. **先看已有的**:`glob .spark/e2e-test/<mode>/*.json` → **读**可能覆盖本需求的那份(没读过不要传)
117
- 2. **改还是新建**:同需求用例有变化 → 编辑那份;换了功能 / 页面 → 新建一份
118
- 3. **派发**:standard 卷跑哪些用例只认结构化字段,写在 `prompt` 里的自然语言范围不生效:刚写好或刚改过的卷首次验收带 `agent_options.retest_all: true`,跑全部用例;修复后复验带 `agent_options.retest_case_ids: [1, 2]`,只列失败或受这次修复影响的用例(编号取计划文件里的 `id`,必须是正整数,不是 `"case-1"` 这类字符串)。lite 卷不带这两个字段,`prompt` 只说跑哪些(`跑 Case 1、2` / `测试全部用例`),不点名默认只跑 p0
107
+ 1. **先看已有的**:`glob .spark/e2e-test/*.json` 和历史目录 `.spark/e2e-test/standard/*.json` → **读**可能覆盖本需求的那份(没读过不要传)
108
+ 2. **改还是新建**:已有计划沿用原路径(包括 standard/ 下的,不复制、不搬迁),同需求用例有变化 → 编辑那份;没有对应计划的新功能 / 页面 → 新建 `.spark/e2e-test/<语义名>.json`
109
+ 3. **派发**:跑哪些用例只认结构化字段,写在 `prompt` 里的自然语言范围不生效:刚写好或刚改过的卷首次验收带 `agent_options.retest_all: true`,跑全部用例;修复后复验带 `agent_options.retest_case_ids: [1, 2]`,只列失败或受这次修复影响的用例(编号取计划文件里的 `id`,必须是正整数,不是 `"case-1"` 这类字符串)
119
110
 
120
111
  格式(一条用例一行,优先级小写,每卷 ≤10 条):
121
112
 
122
113
  ```json
123
- {"cases":[{"id":1,"priority":"p0","description":"打开首页,预期工单列表渲染出至少 1 行"}]}
114
+ {"cases":[
115
+ {"id":1,"priority":"p0","description":"打开首页,预期工单列表渲染出至少 1 行"}{% if permissionIdentityEnabled %},
116
+ {"id":2,"priority":"p1","description":"打开工单设置页,预期显示「成员管理」入口","identity":{"mode":"auth","confirmedRoleIds":["<role_id>"]}}{% endif %}
117
+ ]}
124
118
  ```
125
119
 
126
- - **用例正文只写在计划文件里**:本模式下 `prompt` 不写 `## 测试用例`(下面示例里的用例正文改为写进卷),只留应用结构与本次范围——两处都写会让计划外用例一条不跑、报告却全绿
127
- - **复测只选编号、不删用例**:「只传 failed + 未完成的 Case」指 standard 用 `retest_case_ids` 列出这些编号(lite 在 `prompt` 里点名),不是从卷里删掉已通过的用例
120
+ {% if permissionIdentityEnabled %}需要特定角色或登录状态的用例,在测试计划里给该用例写 identity:普通登录 {"mode":"auth"},未登录 {"mode":"anon"},角色 {"mode":"auth","confirmedRoleIds":["<role_id>"]}。role_id 用 rbac_role_manager QUERY 查,取返回的 role_catalog 里的 role_id(即 BizID),不是角色记录的数字 ID。不同用例可以写不同角色。不要写在 prompt 里,也不要自己 MOCK。
121
+
122
+ {% endif %}- **用例正文只写在计划文件里**:本模式下 `prompt` 不写 `## 测试用例`(下面示例里的用例正文改为写进卷),只留应用结构与本次范围——两处都写会让计划外用例一条不跑、报告却全绿
123
+ - **复测只选编号、不删用例**:「只传 failed + 未完成的 Case」指用 `retest_case_ids` 列出这些编号,不是从卷里删掉已通过的用例
128
124
 
129
125
  ### 调用结构
130
126
 
131
127
  ```js
132
128
  task({
133
129
  subagent_type: "E2E",
134
- agent_options: { mode: "standard", test_plan_file_path: ".spark/e2e-test/standard/<语义名>.json", retest_all: true }, // 首次验收;lite 卷改 mode: "lite" + lite 目录,不带 retest_*
130
+ agent_options: { test_plan_file_path: ".spark/e2e-test/<语义名>.json", retest_all: true }, // 首次验收
135
131
  description: "验收创作工具", // 一句话任务名
136
132
  prompt: `
137
133
  对<被测应用一句话描述>进行端到端验收测试。
@@ -174,7 +170,7 @@ task({
174
170
  - **主链路 + AI 都覆盖**,不因"非主链路"遗漏
175
171
  - **一次最多 5 条 Case**:多出来的分下一轮跑,不塞进同一轮
176
172
  - **单条 Case ≤ 8 步**:动作步 + 断言步合计,超了就拆 Case 或删断言
177
- - **不碰登录 / 认证**:应用无登录逻辑,禁止登录 / 注册 / OAuth / CAPTCHA 相关 Case
173
+ {% if permissionIdentityEnabled %}- **不测登录流程本身**(登录页、注册、OAuth、CAPTCHA);需要以某角色或未登录状态验证的用例用 identity 声明{% else %}- **不碰登录 / 认证**:应用无登录逻辑,禁止登录 / 注册 / OAuth / CAPTCHA 相关 Case{% endif %}
178
174
 
179
175
  | ❌ 停在中间状态 | ✅ 跑到最终结果 |
180
176
  |------|------|
@@ -260,7 +256,7 @@ E2E 派遣是**昂贵操作**(每次消耗大量 token + 时间)。E2E agent
260
256
  - 已通过的 Case 最多复测 1 次:担心修复引入回归时可纳入复测,同一 Case 累计通过 2 次后不再纳入
261
257
  - 如果上一轮因超时中断,E2E 会返回"已测通过/已测失败/未测"三组——已测通过的结果可信不需重测,只测"失败"和"未测"的
262
258
 
263
- **白屏 / 整页打不开 → 复测前先重启 devServer**:白屏多为 **devServer 编译挂死 / HMR 卡住**,非代码 bug,直接再派只会重复白屏、白吃一轮配额。顺序:**① 重启 devServer → ② 查编译输出 / runtime-log 确认无报错(不派 E2E)→ ③ 再正常复测**;真要肉眼确认用 `lite` 截图,别用 standard 探活。
259
+ **白屏 / 整页打不开 → 复测前先重启 devServer**:白屏多为 **devServer 编译挂死 / HMR 卡住**,非代码 bug,直接再派只会重复白屏、白吃一轮配额。顺序:**① 重启 devServer → ② 查编译输出 / runtime-log 确认无报错(不派 E2E)→ ③ 再正常复测**。
264
260
 
265
261
  **测试范围**:每次派发 **≤ 5 条 Case**(首轮 spec 验收同此上限),一行一个 Case,必须含主链路流程测试。禁止测试有真实副作用的功能(发送飞书消息、飞书建群)。
266
262
 
@@ -289,9 +285,6 @@ E2E 派遣是**昂贵操作**(每次消耗大量 token + 时间)。E2E agent
289
285
  | E2E 完成后又调 `api_request` 补充验证 | 浏览器操作结果即为最终结果,不追加 |
290
286
  | 用户说"看看有没有 bug"就同时调两个 | 只派遣 E2E |
291
287
  | 用户说"检查接口"时派遣 E2E | 明确提到接口时用 `api_request` |
292
- | 派发 E2E 时不传 `mode` | 每次都显式传 `lite` 或 `standard`;缺失或非法时不会执行 |
293
- | 用户没主动要求跑功能,却传 `standard` | 显式传 `lite`;仅用户明确要求交互 / 功能验证才传 `standard` |
294
- | 仅改 CSS / 文案,仍走 `standard` | 优先用 `lite`,速度快、token 消耗低 |
295
288
  | 达到停止条件(≥3 轮 / 同一错误未改代码连续失败)仍继续派 E2E | 停止派遣,转为根因分析(读代码 / 查日志) |
296
289
  | 复测时全量重测 / 白屏直接再派一轮 | 只传 failed + 未完成的 Case;白屏先重启 devServer 再复测 |
297
290
  {% endif -%}
@@ -116,10 +116,6 @@ Before finalizing any slide, confirm each item:
116
116
  /* 1. 顶部 @import 下载字体资源(示例:Bold Signal) */
117
117
  @import url('https://miaoda.feishu.cn/fonts/css2?family=Archivo+Black&family=Space+Grotesk:wght@400;500;700&display=swap');
118
118
 
119
- @tailwind base;
120
- @tailwind components;
121
- @tailwind utilities;
122
-
123
119
  /* 2. :root 注册 CSS 变量:Display 进 --font-display,Body 进 --font-sans */
124
120
  :root {
125
121
  --font-display: 'Archivo Black', sans-serif; /* 标题 */
@@ -40,7 +40,7 @@ _标题用 `className="font-display"`,正文不加字体类。_
40
40
  **Key Tailwind Classes:**
41
41
  ```tsx
42
42
  // Background
43
- className="bg-[var(--ppt-bg)] bg-gradient-to-br from-[#1a1a1a] via-[#2d2d2d] to-[#1a1a1a]"
43
+ className="bg-[var(--ppt-bg)] bg-linear-to-br from-[#1a1a1a] via-[#2d2d2d] to-[#1a1a1a]"
44
44
 
45
45
  // Accent card
46
46
  className="bg-[var(--ppt-accent)] text-[var(--ppt-accent-foreground)] rounded-2xl p-8"
@@ -116,10 +116,6 @@ Before finalizing any slide, confirm each item:
116
116
  /* 1. 顶部 @import 下载字体资源(示例:Bold Signal) */
117
117
  @import url('https://miaoda.feishu.cn/fonts/css2?family=Archivo+Black&family=Space+Grotesk:wght@400;500;700&display=swap');
118
118
 
119
- @tailwind base;
120
- @tailwind components;
121
- @tailwind utilities;
122
-
123
119
  /* 2. :root 注册 CSS 变量:Display 进 --font-display,Body 进 --font-sans */
124
120
  :root {
125
121
  --font-display: 'Archivo Black', sans-serif; /* 标题 */
@@ -40,7 +40,7 @@ _标题用 `className="font-display"`,正文不加字体类。_
40
40
  **Key Tailwind Classes:**
41
41
  ```tsx
42
42
  // Background
43
- className="bg-[var(--ppt-bg)] bg-gradient-to-br from-[#1a1a1a] via-[#2d2d2d] to-[#1a1a1a]"
43
+ className="bg-[var(--ppt-bg)] bg-linear-to-br from-[#1a1a1a] via-[#2d2d2d] to-[#1a1a1a]"
44
44
 
45
45
  // Accent card
46
46
  className="bg-[var(--ppt-accent)] text-[var(--ppt-accent-foreground)] rounded-2xl p-8"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/coding-miaoda-sandbox-skills",
3
- "version": "0.1.36",
3
+ "version": "0.1.37",
4
4
  "description": "Miaoda 合并沙箱 skills 包(包含原 miaoda-skills 的 miaoda / miaoda-modern / miaoda-design / shared 四条业务线);发布公网 npm,随沙箱运行时经 update-skills 同步到 .agent/skills/",
5
5
  "type": "module",
6
6
  "files": [