@autobest-ui/agent 1.0.5 → 1.0.7

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.
Files changed (28) hide show
  1. package/bin/sync-assets.test.mjs +11 -0
  2. package/bin/traceability-validators.test.mjs +209 -0
  3. package/package.json +2 -2
  4. package/plugins/autobest-delivery/README.md +5 -1
  5. package/plugins/autobest-delivery/mcp-server/src/runner.mjs +48 -0
  6. package/plugins/autobest-delivery/mcp-server/tests/runner.test.mjs +98 -0
  7. package/plugins/autobest-delivery/skills/code-audit/SKILL.md +3 -0
  8. package/plugins/autobest-delivery/skills/code-craft/SKILL.md +3 -3
  9. package/plugins/autobest-delivery/skills/delivery-loop/SKILL.md +4 -4
  10. package/plugins/autobest-delivery/skills/delivery-loop/references/delivery-contract.md +16 -1
  11. package/plugins/autobest-delivery/skills/e2e-gen-spec/SKILL.md +4 -0
  12. package/plugins/autobest-delivery/skills/e2e-ui-checker/SKILL.md +2 -0
  13. package/plugins/autobest-delivery/skills/export-report/SKILL.md +3 -0
  14. package/skills/README.md +5 -0
  15. package/skills/common/code-pr-submit/SKILL.md +110 -139
  16. package/skills/common/code-pr-submit/agents/openai.yaml +2 -2
  17. package/skills/common/make-spec/SKILL.md +37 -0
  18. package/skills/common/make-spec/agents/openai.yaml +4 -0
  19. package/skills/common/make-spec/references/spec-schema.md +82 -0
  20. package/skills/common/make-spec/scripts/validate-spec-traceability.mjs +130 -0
  21. package/skills/common/review-from-docs/SKILL.md +33 -0
  22. package/skills/common/review-from-docs/agents/openai.yaml +4 -0
  23. package/skills/common/review-from-docs/references/review-result-schema.md +42 -0
  24. package/skills/common/review-from-docs/scripts/validate-review-traceability.mjs +91 -0
  25. package/skills/common/ui-prd-scope/SKILL.md +6 -3
  26. package/skills/common/ui-prd-scope/references/requirement-traceability.md +82 -0
  27. package/skills/common/ui-prd-scope/references/scope-schema.md +28 -1
  28. package/skills/common/ui-prd-scope/scripts/validate-scope-bundle.mjs +104 -0
package/skills/README.md CHANGED
@@ -12,9 +12,14 @@ npx --yes --package=@autobest-ui/agent@latest autobest-agent-sync common
12
12
 
13
13
  当前包含:
14
14
 
15
+ - `code-pr-submit`
15
16
  - `figma-ui-capture`
17
+ - `make-spec`
18
+ - `review-from-docs`
16
19
  - `ui-prd-scope`
17
20
 
21
+ 其中 `ui-prd-scope` 使用 `REQ-<页面或Scope简称>-<三位序号>` 分配稳定需求 ID;`review-from-docs` 以该编号记录质询结论;`make-spec` 生成逐项可验收的 `spec.md` 和机器可校验的映射。后续交付角色只引用编号,不重新编号。
22
+
18
23
  ## React
19
24
 
20
25
  React Skills 只应用于单个项目。进入项目根目录后运行:
@@ -1,21 +1,24 @@
1
1
  ---
2
2
  name: code-pr-submit
3
- description: 编排 Azure DevOps 反向 PR:将用户所说的目标分支作为携带变更的 PR 源,将来源分支作为接收合并的 PR 目标;校验参数、显式确认方向、基于 spec.md 与 diff 生成描述,并通过 code-mcp-pr MCP 创建 PR 和发送钉钉通知。用户要求创建 Azure PR、反向 PR,或把目标分支变更合并到来源分支时使用。只负责编排与文本生成,不直接请求 ADO 或钉钉 API。
3
+ description: 从当前 Git 项目自动识别 Azure DevOps 仓库、当前分支及其父分支,生成 PR 标题和描述,并通过 code-mcp-pr 创建从当前分支合并到父分支的 PR。用户要求创建或提交 Azure PR、为当前分支提 PR,或预览待提交 PR 时使用。用户显式参数优先;只在本地项目无法推断必要信息时询问。
4
4
  ---
5
5
 
6
- # Azure DevOps 反向 PR 编排
6
+ # Azure DevOps PR 提交
7
7
 
8
- ## 角色与边界
8
+ ## 目标
9
9
 
10
- 将用户的业务分支语义编排为 Azure DevOps PR。这里的术语与常规 PR 表述相反,首次响应、分支确认和最终创建确认都要明确提醒:
10
+ 在当前项目中创建普通方向的 PR:
11
11
 
12
- - 用户的“目标分支”是携带变更的 PR 源分支,映射为 `businessTargetBranch`。
13
- - 用户的“来源分支”是接收合并的 PR 目标分支,映射为 `businessSourceBranch`。
14
- - 流向始终是 `targetBranch -> sourceBranch`,禁止交换两个 MCP 参数。
12
+ ```text
13
+ from = 当前开发分支(携带变更)
14
+ to = 当前分支的父分支(创建该分支时所基于的分支)
15
+ ```
16
+
17
+ 面向用户始终使用 `from -> to`、源分支和目标分支等常规 Git 术语。MCP 的历史字段命名只在最终参数映射时内部处理。
15
18
 
16
- 只调用 `code-mcp-pr` MCP 服务提供的工具。由 MCP 访问 ADO 和钉钉;不拼接 API、不发起 HTTP 请求、不直接调用 webhook。PAT 和钉钉 token 分别由 MCP 从 `AZURE_DEVOPS_PAT`、`DINGDING_ALERT_TOKEN` 读取,不读取、持有或传递凭据。
19
+ 只通过 `code-mcp-pr` MCP 访问 Azure DevOps 和钉钉。PAT 与钉钉 token 由 MCP 从 `AZURE_DEVOPS_PAT`、`DINGDING_ALERT_TOKEN` 读取;Skill 不读取、持有或传递凭据。
17
20
 
18
- 可用工具:
21
+ 可用 MCP 工具:
19
22
 
20
23
  - `code-mcp-pr.code-mcp-pr`
21
24
  - `code-mcp-pr.get_repo_default_branch`
@@ -23,218 +26,186 @@ description: 编排 Azure DevOps 反向 PR:将用户所说的目标分支作
23
26
  - `code-mcp-pr.get_branch_diff`
24
27
  - `code-mcp-pr.read_file_at_ref`
25
28
 
26
- ## 输入与输出
27
-
28
- 从用户自然语言和明确的会话上下文收集:
29
-
30
- | 字段 | 必填 | 含义 |
31
- | --- | --- | --- |
32
- | `orgName` | 是 | ADO 组织 |
33
- | `projectName` | 是 | ADO 项目 |
34
- | `repoId` | 是 | 仓库 ID/Guid |
35
- | `currentBranch` | 否 | 运行环境明确提供的当前分支;远程 ADO 无法探测 |
36
- | `targetBranch` | 条件必填 | 用户所说的目标分支,携带变更 |
37
- | `sourceBranch` | 条件必填 | 用户所说的来源分支,接收合并 |
38
- | `prTitle` | 是 | PR 标题 |
39
- | `prDescription` | 否 | 用户手动提供的 PR 描述;未提供时自动生成 |
40
- | `reviewers` | 否 | 评审人 ID 列表 |
41
-
42
- 最终输出为创建结果:成功时给出 PR ID、访问链接、再次说明分支流向及钉钉状态;失败时给出可行动的错误说明,并明确未发送钉钉通知。
43
-
44
- ## 确认状态
45
-
46
- 把候选值与已确认值分开管理。用户只是提供分支名,不等于已经确认反向 PR 方向;必须在展示“PR 源/PR 目标”语义后取得明确肯定答复,才将两个分支标记为已确认。
47
-
48
- 任何一方分支发生变化时:
49
-
50
- 1. 撤销该分支及最终创建确认。
51
- 2. 撤销自动生成描述的确认,使用新分支重新读取 diff 和 `spec.md`。
52
- 3. 重新检查两个分支是否相同。
29
+ ## 输入优先级
53
30
 
54
- 用户修改自动生成的描述后,将新文本展示给用户并取得明确确认。确认必须对应当前展示的具体值,不能把含糊回复或对旧候选值的确认沿用到新值。
31
+ 用户显式提供的值始终优先。用户没有提供时,从当前 Git 项目自动发现,不先展示参数清单,不要求用户重复填写能够查到的信息。
55
32
 
56
- ## 工作流
33
+ 内部使用以下清晰字段:
57
34
 
58
- ### 1. 校验基础参数
35
+ | 字段 | 自动来源 |
36
+ | --- | --- |
37
+ | `orgName` | Azure DevOps `origin` URL |
38
+ | `projectName` | Azure DevOps `origin` URL |
39
+ | `repoId` | Azure DevOps `origin` URL 中的仓库名;ADO API 接受仓库名或 GUID |
40
+ | `fromBranch` | 当前 Git 分支 |
41
+ | `toBranch` | 分支创建 reflog、本地 merge-base 或 MCP 启发结果 |
42
+ | `prTitle` | 用户输入;否则使用当前分支最新的非合并 commit 标题,并结合分支名清理 |
43
+ | `prDescription` | 用户输入;否则根据远端 diff、`spec.md`、`spec-traceability.json` 和已有 Checker 证据自动生成 |
44
+ | `reviewers` | 仅使用用户明确提供的评审人 ID;缺失时省略 |
59
45
 
60
- 检查 `orgName`、`projectName`、`repoId`、`prTitle`。一次列出全部缺失项并请用户补齐,不推造默认值。
46
+ ## 自动发现项目
61
47
 
62
- 在首次交互中说明:
48
+ 收到请求后立即在当前工作目录执行只读 Git 命令,不先向用户索要字段:
63
49
 
64
- > ⚠️ 这是反向 PR:你所说的“目标分支”会作为 PR 源(携带变更),“来源分支”会作为 PR 目标(接收合并),方向与常规 PR 表述相反。
65
-
66
- ### 2. 确认 targetBranch
67
-
68
- - 用户提供了 `targetBranch`:将其作为候选值,仍需询问:
69
-
70
- > ⚠️ 反向 PR 方向确认:是否使用分支【${candidate}】作为携带变更的 PR 源分支?
71
-
72
- - 用户未提供 `targetBranch`,但会话上下文明确提供了非空 `currentBranch`:以它作为候选值并询问同一问题。用户确认后才能赋值给 `targetBranch`。
73
- - 用户未提供 `targetBranch`,且 `currentBranch` 为空:直接询问用户提供“目标分支(携带变更的分支)”。远程 ADO API 没有全局当前分支,不能调用 MCP 或根据仓库信息编造当前分支。
50
+ ```bash
51
+ git rev-parse --show-toplevel
52
+ git branch --show-current
53
+ git remote get-url origin
54
+ ```
74
55
 
75
- ### 3. 确认 sourceBranch
56
+ 从 `origin` 解析 ADO 信息,至少支持:
76
57
 
77
- 前提是 `targetBranch` 已确认。
58
+ ```text
59
+ https://dev.azure.com/{org}/{project}/_git/{repo}
60
+ https://{user}@dev.azure.com/{org}/{project}/_git/{repo}
61
+ https://{org}.visualstudio.com/{project}/_git/{repo}
62
+ git@ssh.dev.azure.com:v3/{org}/{project}/{repo}
63
+ ssh://git@ssh.dev.azure.com/v3/{org}/{project}/{repo}
64
+ ```
78
65
 
79
- - 用户提供了 `sourceBranch`:将其作为候选值,明确它是接收合并的 PR 目标并取得确认。
80
- - 用户未提供 `sourceBranch`:调用 `code-mcp-pr.guess_parent_branch`:
66
+ 对 URL decode 后的值去除仓库名末尾 `.git`。如果有多个 remote,优先 `origin`;`origin` 不存在时检查唯一的 ADO remote。不要把 remote 用户名误当成组织名。
81
67
 
82
- ```yaml
83
- orgName: ${orgName}
84
- projectName: ${projectName}
85
- repoId: ${repoId}
86
- branchName: ${targetBranch}
87
- ```
68
+ 以下情况才询问用户对应的最少信息:当前目录不在 Git 仓库;处于 detached HEAD 且用户未给 `fromBranch`;没有可解析的 ADO remote;必要字段存在多个冲突候选。一次只汇总真正无法发现的字段。
88
69
 
89
- 调用成功且 `guessedParentBranch` 非空时,原样带上返回的 `hint` 询问:
70
+ ## 自动发现父分支
90
71
 
91
- > ⚠️ 无法获取该分支真实创建父分支,启发推测父分支为:${guessedParentBranch},${hint}
92
- > 是否使用 ${guessedParentBranch} 作为 PR 目标分支(接收合并)?
72
+ 目标是找出 `fromBranch` 创建时所基于的分支,而不是无条件返回默认分支。按以下优先级执行:
93
73
 
94
- ADO Git 不持久保存分支创建时的父分支;此结果只是启发式猜测。用户确认后才赋值给 `sourceBranch`。用户拒绝时请其手动输入来源分支,再展示其 PR 目标语义并确认。
74
+ 1. 用户明确提供 `toBranch` 时直接使用。
75
+ 2. 检查当前分支最早的 reflog 记录:
95
76
 
96
- 当工具失败、`guessedParentBranch` 为空,或目标分支本身是默认分支而无法推测时,说明工具返回的 `errorMsg`/`hint`,请用户手动提供来源分支。`get_repo_default_branch` 仅在用户明确查询仓库默认分支时使用,不能用其结果静默代替父分支确认。
77
+ ```bash
78
+ git reflog show --format='%H%x09%gs' -- "${fromBranch}"
79
+ ```
97
80
 
98
- 两个分支均确定后,按去除 `refs/heads/` 前缀后的规范分支名比较;若相同,拒绝创建并要求用户修正,不能仅依赖 MCP 校验。
81
+ 从最早记录中的 `branch: Created from <ref>` 提取父分支。`<ref>` 是明确分支名且能被 `git rev-parse --verify` 解析时采用;`HEAD`、commit SHA 或已失效 ref 继续下一步。
82
+ 3. 使用本地 refs 做 merge-base 启发:列出 `refs/heads` 和 `refs/remotes/origin`,排除当前分支、其远端同名分支、`*/HEAD`,计算各候选与 `fromBranch` 的 merge-base。优先选择 merge-base 等于 reflog 创建 commit 的候选;多个候选并列时,优先远端跟踪分支和 `origin` 默认分支。仍并列时根据从 merge-base 到 `fromBranch` 的提交距离选择最近者。
83
+ 4. 本地历史不足时调用 `code-mcp-pr.guess_parent_branch`,传入自动发现的仓库信息和 `fromBranch`。
84
+ 5. 所有方式均失败或仍有多个同等可信候选时,展示候选及推断依据,请用户只选择 `toBranch`。
99
85
 
100
- ### 4. 确认 PR 描述
86
+ 将 `origin/foo`、`refs/remotes/origin/foo` 和 `refs/heads/foo` 规范成业务分支名 `foo`。父分支推断完成后比较规范化的 `fromBranch` 与 `toBranch`;相同时停止并请求修正。
101
87
 
102
- 用户已手动提供 `prDescription` 时,保留其原文并视为用户明确选择;最终创建确认仍需在该描述已确定后进行。
88
+ 父分支推断属于启发式,但不要在正常成功路径输出长篇限制说明。只在置信度较低或需要用户选择时简短说明依据。
103
89
 
104
- 用户未提供时,只有在两个分支均已确认后才执行以下读取:
90
+ ## 收集 PR 内容
105
91
 
106
- 1. 调用 `code-mcp-pr.get_branch_diff`,固定以携带变更的分支为 source、接收合并的分支为 target:
92
+ 分支确定后调用 `code-mcp-pr.get_branch_diff`:
107
93
 
108
94
  ```yaml
109
95
  orgName: ${orgName}
110
96
  projectName: ${projectName}
111
97
  repoId: ${repoId}
112
- sourceBranch: ${targetBranch}
113
- targetBranch: ${sourceBranch}
98
+ sourceBranch: ${fromBranch}
99
+ targetBranch: ${toBranch}
114
100
  ```
115
101
 
116
- 2. 调用 `code-mcp-pr.read_file_at_ref` 读取变更分支根目录的 `spec.md`:
102
+ 随后调用 `code-mcp-pr.read_file_at_ref` 读取当前分支中的 `spec.md`。能够从用户输入、diff 或 spec 定位功能目录时,也读取同目录的 `spec-traceability.json` 和最新 `checker/checker-result.json`:
117
103
 
118
104
  ```yaml
119
105
  orgName: ${orgName}
120
106
  projectName: ${projectName}
121
107
  repoId: ${repoId}
122
- refName: ${targetBranch}
108
+ refName: ${fromBranch}
123
109
  filePath: spec.md
124
110
  ```
125
111
 
126
- 3. `get_branch_diff` 失败时无法可靠生成描述:报告 `errorMsg` 并停止在预览之前。`read_file_at_ref` 返回 `success=true, fileExists=false` 时令 `specMdContent=null` 并继续;其他读取失败则告知用户,并询问是仅基于 diff 继续还是稍后重试,不能把读取错误表述成文件不存在。
127
- 4. 当前 LLM 使用原始 `diffFiles`、`diffShortText` 和 `specMdContent` 生成描述;MCP 只负责读取材料,不承担总结。
112
+ 文件不存在时继续。其他读取错误保留错误事实,但只要 diff 可用就继续生成描述。diff 失败时报告 MCP 的 `errorMsg`,不要基于猜测创建 PR。不得为了寻找产物扫描或猜测无关目录。
128
113
 
129
- 生成规则:
114
+ 用户未给标题时,先读取:
115
+
116
+ ```bash
117
+ git log --no-merges -1 --format=%s -- "${fromBranch}"
118
+ ```
130
119
 
131
- - `specMdContent` 非空时,将需求说明与实际代码变更对齐,逐条写出已实现的变更。
132
- - `specMdContent` 为空时,仅根据 diff 按文件维度说明修改内容。
133
- - 使用简洁、面向评审人的 Markdown;开头是短概述,详情只用 `- ` 无序列表。
134
- - 不粘贴大段原始 diff,不声称材料中无法证明的行为。diff 被截断时只总结可见材料,不推测缺失部分。
120
+ 用最新提交标题作为基础;仅在它为空或明显无意义时,根据分支名、`spec.md` 和 diff 生成简洁标题。不要为标题单独询问用户。
135
121
 
136
- 格式:
122
+ 用户未给描述时,根据 `spec.md` 和 diff 生成面向评审人的 Markdown:
137
123
 
138
124
  ```markdown
139
125
  ## 变更概述
140
- 本次提交完成 xx 能力改造。
126
+ 一句话说明本次改动。
141
127
 
142
128
  ## 变更详情
143
- - `path/to/file`:新增 xx 逻辑,实现 xx。
144
- - `path/to/file`:修复 xx 问题。
129
+ - `path/to/file`:说明可由材料证明的改动。
130
+
131
+ ## 需求追踪
132
+ - `REQ-PD-001`:已实现;Checker 通过 / 失败 / 阻断 / 缺少验收证据
145
133
  ```
146
134
 
147
- 生成后完整展示并询问:
135
+ 存在 `spec-traceability.json` 时按真实 REQ ID 列出本 PR 涉及的 active 需求。只有 Checker 结果明确覆盖且通过时才能写“Checker 通过”;没有对应证据时写“缺少验收证据”,不得从代码、Maker 结果或整体 Checker 状态推断单项通过。不存在追踪产物时省略“需求追踪”,不临时发明编号。只总结可见材料,不粘贴大段 diff,不补造功能。用户提供标题或描述时保留其原意,不擅自替换。
148
136
 
149
- > 📝 自动生成的 PR 描述如下,是否直接使用?如果需要修改,请直接告诉我新的描述。
137
+ ## 创建授权
150
138
 
151
- 只有用户明确确认后才赋值给 `prDescription`。用户给出替换文本时,展示替换后的描述并重新确认;不得静默提交自动生成内容。
139
+ 如果用户本次请求明确包含“创建 PR”“提交 PR”“提 PR”等写操作意图,该请求本身就是创建授权。完成自动发现和内容生成后直接创建,不再逐项确认分支、描述或重复询问最终授权。
152
140
 
153
- ### 5. 最终创建确认
141
+ 如果用户只要求“准备”“生成”“预览”PR,则展示以下简洁预览并停止,不调用创建工具:
154
142
 
155
- 基础参数齐全、两个分支及 PR 描述均确定后,逐字明确方向并等待用户确认:
143
+ ```text
144
+ PR: ${fromBranch} -> ${toBranch}
145
+ 标题:${prTitle}
146
+ 描述:${prDescription}
147
+ ```
156
148
 
157
- > ⚠️【反向 PR 确认】
158
- > PR 源(携带变更):${targetBranch}
159
- > PR 目标(合并进入):${sourceBranch}
160
- > PR 标题:${prTitle}
161
- > 是否确认调用 MCP 工具 code-mcp-pr 创建 Azure DevOps PR?
149
+ 如果用户的表达无法判断是创建还是预览,只询问这一项,不重新询问已自动发现的数据。
162
150
 
163
- 这一步是外部写操作授权。未得到明确肯定答复时停止,不调用创建工具。用户修改任一字段时回到相应确认步骤。
151
+ 创建前检查当前工作区:
164
152
 
165
- ### 6. 组装钉钉消息并创建 PR
153
+ ```bash
154
+ git status --short
155
+ git ls-remote --exit-code origin "refs/heads/${fromBranch}"
156
+ ```
166
157
 
167
- 用户最终确认后,由当前 LLM 从已确认的 `prDescription` 提炼 3 至 5 条核心变更。只组装文本,不调用钉钉 webhook:
158
+ 未提交改动不会包含在 PR 中,发现时明确告知并停止创建。远端不存在 `fromBranch` 时告知需要先推送;Skill 不自行 push。
168
159
 
169
- ```text
170
- dingtalkMarkdownTitle = "✅ ADO PR已创建完成"
171
- ```
160
+ ## 创建与通知
161
+
162
+ 根据最终描述提炼 3 至 5 条钉钉变更要点,保留字面量 `{{PR_WEB_URL}}`:
172
163
 
173
164
  ```markdown
174
165
  **PR 标题:${prTitle}**
175
166
 
176
167
  PR 链接:[{{PR_WEB_URL}}]({{PR_WEB_URL}})
177
168
 
178
- 分支流向:${targetBranch} ➔ ${sourceBranch}
169
+ 分支流向:${fromBranch} -> ${toBranch}
179
170
 
180
171
  变更要点:
181
172
 
182
173
  - 要点 1
183
174
  - 要点 2
184
- - 要点 3
185
175
  ```
186
176
 
187
- 正文必须保留字面量 `{{PR_WEB_URL}}`,由 MCP 在 PR 创建成功后替换为真实 `prWebUrl`。随后只调用一次 `code-mcp-pr.code-mcp-pr`,映射如下:
177
+ 调用一次 `code-mcp-pr.code-mcp-pr`。MCP 的业务字段名与普通 Git 术语不同,严格按以下映射,不向用户展示该内部差异:
188
178
 
189
179
  ```yaml
190
180
  orgName: ${orgName}
191
181
  projectName: ${projectName}
192
182
  repoId: ${repoId}
193
- businessTargetBranch: ${targetBranch}
194
- businessSourceBranch: ${sourceBranch}
183
+ businessTargetBranch: ${fromBranch}
184
+ businessSourceBranch: ${toBranch}
195
185
  prTitle: ${prTitle}
196
186
  prDescription: ${prDescription}
197
187
  reviewers: ${reviewers}
198
- dingtalkMarkdownTitle: "✅ ADO PR已创建完成"
199
- dingtalkMarkdownContent: ${assembledMarkdownWithLiteralPrWebUrlPlaceholder}
188
+ dingtalkMarkdownTitle: "ADO PR 已创建"
189
+ dingtalkMarkdownContent: ${markdownWithLiteralPrWebUrlPlaceholder}
200
190
  ```
201
191
 
202
- `reviewers` 未提供时省略该参数。PR 创建失败时 MCP 不会发送钉钉;钉钉发送失败不改变 PR 创建成功状态。创建工具非幂等:响应不明确或调用中断时,不自动重试,先向用户说明可能已创建并要求核查,避免重复 PR。
203
-
204
- ## MCP 返回处理
192
+ `reviewers` 未提供时省略。创建工具非幂等:调用中断或响应不明确时不自动重试,先查询或请用户核查是否已经创建。
205
193
 
206
- ### 创建成功
194
+ ## 返回结果
207
195
 
208
- 当 `success=true`,读取 `prId`、`prWebUrl`、`dingtalkSendSuccess` 并输出:
196
+ 成功时简洁输出:
209
197
 
210
198
  ```text
211
- ✅ PR 创建成功
212
- PR ID: ${prId}
213
- PR 访问链接:${prWebUrl}
214
- 本次 PR 流向:${targetBranch} → 合并至 ${sourceBranch}
215
- ${dingtalkSendSuccess === true ? "✅ 已推送通知至钉钉群" : "⚠️ PR 创建成功,但钉钉群通知发送失败,请检查环境变量 DINGDING_ALERT_TOKEN"}
199
+ PR 创建成功
200
+ ${fromBranch} -> ${toBranch}
201
+ ${prWebUrl}
202
+ 钉钉通知:已发送 | 发送失败 | 已跳过
216
203
  ```
217
204
 
218
- `dingtalkSendSuccess` 为 `false` 或 `null` 均按通知失败提示处理,不重试 PR 创建。
219
-
220
- ### 创建失败
221
-
222
- 当 `success=false`,根据 `errorMsg` 中的状态码和资源信息转为自然语言,并附上不会发送钉钉通知:
205
+ `dingtalkSendSuccess=true` 表示已发送,`false` 表示失败,`null` 表示未请求通知;不要把 `null` 说成发送失败。
223
206
 
224
- - `401`:`AZURE_DEVOPS_PAT` 权限不足或失效,请检查 PAT 是否拥有代码读取和 PR 创建权限。
225
- - `404` 且指向仓库:请检查 `projectName`、`repoId` 是否正确。
226
- - `404` 且指向分支/ref:分支不存在,请核对分支名。
227
- - `404` 且指向文件:文件读取失败;自动描述阶段的 `spec.md` 正常不存在应由 `fileExists=false` 表示,不归入此错误。
228
- - 其他错误:保留原始 `errorMsg`,不要臆测原因。
229
-
230
- 输出格式:
231
-
232
- ```text
233
- ❌ PR 创建失败
234
- ${friendlyError}
235
- PR 创建失败不会触发钉钉通知。
236
- ```
207
+ 失败时保留 `errorMsg` 的关键信息并说明 PR 未创建、钉钉未触发。钉钉失败不改变 PR 创建成功状态。
237
208
 
238
- ## 完成检查
209
+ ## 完成条件
239
210
 
240
- 调用创建工具前逐项确认:必填基础参数齐全;两个分支分别按反向语义得到确认且不相同;自动生成描述已经预览确认;钉钉正文含字面量 `{{PR_WEB_URL}}`;`businessTargetBranch=targetBranch`、`businessSourceBranch=sourceBranch`;已取得最终外部写操作授权。任一项不满足就停留在对应交互步骤。
211
+ 结束前确认:用户值覆盖自动值;仓库信息来自当前项目;`fromBranch` 是当前或用户指定的开发分支;`toBranch` 是可解释的父分支;远端已有源分支;描述由实际材料支持;MCP 映射为 `businessTargetBranch=fromBranch`、`businessSourceBranch=toBranch`;创建结果与钉钉状态准确呈现。
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: "Code PR Submit"
3
- short_description: "编排并确认 Azure DevOps 反向 PR"
4
- default_prompt: "使用 $code-pr-submit 将目标分支的变更创建反向 PR,合并到来源分支,并在每个关键步骤等待我确认。"
3
+ short_description: "从当前 Git 项目自动创建 Azure DevOps PR"
4
+ default_prompt: "使用 $code-pr-submit 自动读取当前项目和分支,创建合并到父分支的 Azure DevOps PR。"
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: make-spec
3
+ description: 根据带稳定 REQ 编号的页面 scope 和 review-result 生成可追踪的 spec.md 与 spec-traceability.json。用户要求从已质询范围生成或刷新功能规格时使用;不质询未决业务问题、不实现代码或执行验收。
4
+ ---
5
+
6
+ # 生成可追踪功能规格
7
+
8
+ 把已确认的页面需求转换为 Delivery Plugin 可执行的规格。开始前完整读取 [需求追踪契约](../ui-prd-scope/references/requirement-traceability.md) 和 [Spec Schema](references/spec-schema.md)。
9
+
10
+ ## 输入
11
+
12
+ - 页面目录中的 `scope.md` 和范围包根目录的 `scope-manifest.json`。
13
+ - `review/review-result.json`,以及它引用的 Figma 和证据。
14
+ - 可选的既有 `spec.md` 与 `spec-traceability.json`;刷新时保留稳定编号和仍适用的验收边界。
15
+
16
+ Manifest 必须声明 `traceabilitySchemaVersion: 1`。任何 `active` 需求缺少质询记录、仍为 `unresolved`,或 scope、review 的编号和状态不一致时停止,返回需要继续 `$review-from-docs` 或回写 scope 的具体编号。
17
+
18
+ ## 生成
19
+
20
+ 1. 按 manifest 顺序建立需求覆盖台账。`active` 需求进入规格正文;`deferred` 和 `removed` 进入排除清单并保留原因。
21
+ 2. 对每个 active `REQ-ID` 合并原始陈述与已确认决策,写出可观察、可判定的验收条件。不得根据相似需求补造业务规则;执行命令、路由、fixture 和定位器等仓库事实可放在执行上下文,不冒充产品预期。
22
+ 3. 将适用的 Figma 资产映射到对应 `REQ-ID`、设备、变体和语义节点。功能文字、数据内容和业务图片主体由功能验收条件描述,视觉基准只约束结构与样式。
23
+ 4. 按 Schema 写入 `spec.md` 和 `spec-traceability.json`。计算并记录 `scope.md`、`review-result.json`、`spec.md` 的 SHA-256;每个 active 编号在 `spec.md` 中有且仅有一个需求章节,在追踪 JSON 中有且仅有一个需求条目。
24
+ 5. 执行:
25
+
26
+ ```bash
27
+ node .agents/skills/make-spec/scripts/validate-spec-traceability.mjs <页面 scope 目录>
28
+ ```
29
+
30
+ 根据实际安装位置调整脚本路径。校验失败时修正规格产物,不放宽需求覆盖。
31
+
32
+ ## 完成条件
33
+
34
+ - 每个 active 需求有非空规格章节、至少一条验收条件,以及 `runtime`、`visual` 或 `manual` 验证方式。
35
+ - 每个视觉预期都引用存在的本地基准;没有视觉要求的需求明确使用功能验证,不制造截图要求。
36
+ - 追踪文件的输入哈希与当前文件一致,active、deferred、removed 集合与 manifest 一致。
37
+ - 最终答复给出 Spec 路径、active 覆盖数量、排除数量和校验结果;不修改 scope、review、业务代码或交付证据。
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "生成需求规格"
3
+ short_description: "根据 scope 和质询结论生成可追踪的功能 spec"
4
+ default_prompt: "使用 $make-spec 根据当前 scope 和 review-result 生成带需求编号的 spec.md。"
@@ -0,0 +1,82 @@
1
+ # 可追踪 Spec Schema
2
+
3
+ ## spec.md
4
+
5
+ ```markdown
6
+ # <页面族> 功能规格
7
+
8
+ ## 输入与版本
9
+
10
+ - Scope: `scope.md`
11
+ - Review: `review/review-result.json`
12
+ - Azure PR: <URL>
13
+
14
+ ## 需求覆盖
15
+
16
+ | 需求 ID | 规格章节 | 验证方式 | 状态 |
17
+ | --- | --- | --- | --- |
18
+ | `REQ-PD-001` | 商品主图 | `runtime`、`visual` | `specified` |
19
+
20
+ ## REQ-PD-001 商品主图
21
+
22
+ ### 需求与质询结论
23
+
24
+ <保留原始要求及已确认决策。>
25
+
26
+ ### 验收条件
27
+
28
+ - <可观察、可判定的结果。>
29
+
30
+ ### 视觉映射
31
+
32
+ | 设备 | 状态/变体 | 基准 | 语义节点 |
33
+ | --- | --- | --- | --- |
34
+
35
+ ## 排除需求
36
+
37
+ | 需求 ID | 状态 | 原因 |
38
+ | --- | --- | --- |
39
+ ```
40
+
41
+ 每个 active ID 使用一个二级标题;同一需求需要多项检查时保持在同一章节。需求覆盖表不得使用没有对应章节的编号。
42
+
43
+ ## spec-traceability.json
44
+
45
+ ```json
46
+ {
47
+ "schemaVersion": 1,
48
+ "scope": {
49
+ "manifestPath": "../scope-manifest.json",
50
+ "directory": "01-product-detail",
51
+ "scopePath": "scope.md",
52
+ "scopeSha256": "64位小写十六进制"
53
+ },
54
+ "review": {
55
+ "path": "review/review-result.json",
56
+ "sha256": "64位小写十六进制"
57
+ },
58
+ "spec": {
59
+ "path": "spec.md",
60
+ "sha256": "64位小写十六进制"
61
+ },
62
+ "requirements": [
63
+ {
64
+ "id": "REQ-PD-001",
65
+ "status": "specified",
66
+ "heading": "REQ-PD-001 商品主图",
67
+ "acceptanceCriteria": ["商品主图容器保持 4:3 比例"],
68
+ "verification": ["runtime", "visual"],
69
+ "figmaRefs": ["mobile-default.png#12:34"]
70
+ }
71
+ ],
72
+ "excludedRequirements": [
73
+ {
74
+ "id": "REQ-PD-003",
75
+ "status": "deferred",
76
+ "reason": "本迭代不实现"
77
+ }
78
+ ]
79
+ }
80
+ ```
81
+
82
+ `verification` 只使用 `runtime`、`visual`、`manual`。`manual` 仅用于自动化无法可靠观察的预期,并在验收时形成明确人工证据,不能用于规避可自动化检查。
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env node
2
+
3
+ import crypto from 'node:crypto';
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+
7
+ const featureDir = path.resolve(process.argv[2] || '.');
8
+ const errors = [];
9
+ const requirementPattern = /^REQ-[A-Z][A-Z0-9]{1,15}-\d{3}$/;
10
+ const hashPattern = /^[a-f0-9]{64}$/;
11
+ const allowedVerification = new Set(['runtime', 'visual', 'manual']);
12
+ const fail = message => errors.push(message);
13
+
14
+ function readJson(filePath, label) {
15
+ if (!fs.existsSync(filePath)) {
16
+ fail(`缺少 ${label}:${filePath}`);
17
+ return null;
18
+ }
19
+ try {
20
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
21
+ } catch (error) {
22
+ fail(`${label} JSON 无效:${error.message}`);
23
+ return null;
24
+ }
25
+ }
26
+
27
+ function sha256(filePath) {
28
+ return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
29
+ }
30
+
31
+ function resolveInput(relativePath, label) {
32
+ if (typeof relativePath !== 'string' || !relativePath.trim()) {
33
+ fail(`${label} 路径必须是非空字符串`);
34
+ return null;
35
+ }
36
+ const resolved = path.resolve(featureDir, relativePath);
37
+ const relative = path.relative(featureDir, resolved);
38
+ if (relative.startsWith('..') && label !== 'Manifest') {
39
+ fail(`${label} 路径必须位于页面 scope 目录内`);
40
+ }
41
+ if (!fs.existsSync(resolved)) fail(`${label} 不存在:${relativePath}`);
42
+ return resolved;
43
+ }
44
+
45
+ const tracePath = path.join(featureDir, 'spec-traceability.json');
46
+ const trace = readJson(tracePath, 'spec-traceability.json');
47
+
48
+ if (trace) {
49
+ if (trace.schemaVersion !== 1) fail('spec-traceability.schemaVersion 必须为 1');
50
+ const manifestPath = resolveInput(trace.scope?.manifestPath, 'Manifest');
51
+ const scopePath = resolveInput(trace.scope?.scopePath, 'Scope');
52
+ const reviewPath = resolveInput(trace.review?.path, 'Review');
53
+ const specPath = resolveInput(trace.spec?.path, 'Spec');
54
+ const manifest = manifestPath ? readJson(manifestPath, 'scope-manifest.json') : null;
55
+ const review = reviewPath ? readJson(reviewPath, 'review-result.json') : null;
56
+ if (manifest && manifest.traceabilitySchemaVersion !== 1) {
57
+ fail('Manifest 未启用 traceabilitySchemaVersion: 1');
58
+ }
59
+
60
+ for (const [label, filePath, expected] of [
61
+ ['Scope', scopePath, trace.scope?.scopeSha256],
62
+ ['Review', reviewPath, trace.review?.sha256],
63
+ ['Spec', specPath, trace.spec?.sha256]
64
+ ]) {
65
+ if (!hashPattern.test(expected || '')) fail(`${label} SHA-256 格式无效`);
66
+ else if (filePath && fs.existsSync(filePath) && sha256(filePath) !== expected) {
67
+ fail(`${label} SHA-256 与当前文件不一致`);
68
+ }
69
+ }
70
+
71
+ const manifestScope = manifest?.scopes?.find(item => item.directory === trace.scope?.directory);
72
+ if (!manifestScope) fail(`Manifest 未找到 scope:${trace.scope?.directory}`);
73
+ const manifestRequirements = Array.isArray(manifestScope?.requirements)
74
+ ? manifestScope.requirements
75
+ : [];
76
+ const activeIds = manifestRequirements.filter(item => item.status === 'active').map(item => item.id);
77
+ const excludedIds = manifestRequirements.filter(item => ['deferred', 'removed'].includes(item.status)).map(item => item.id);
78
+ const traceRequirements = Array.isArray(trace.requirements) ? trace.requirements : [];
79
+ const traceIds = traceRequirements.map(item => item.id);
80
+ const traceExcluded = Array.isArray(trace.excludedRequirements) ? trace.excludedRequirements : [];
81
+ const traceExcludedIds = traceExcluded.map(item => item.id);
82
+
83
+ for (const id of [...activeIds, ...excludedIds, ...traceIds, ...traceExcludedIds]) {
84
+ if (!requirementPattern.test(id || '')) fail(`需求编号格式无效:${id}`);
85
+ }
86
+ if (new Set(traceIds).size !== traceIds.length) fail('Spec 追踪包含重复 active 需求编号');
87
+ if (new Set(traceExcludedIds).size !== traceExcludedIds.length) fail('Spec 追踪包含重复排除需求编号');
88
+ for (const id of activeIds) if (!traceIds.includes(id)) fail(`Spec 缺少 active 需求:${id}`);
89
+ for (const id of traceIds) if (!activeIds.includes(id)) fail(`Spec 包含非 active 或未知需求:${id}`);
90
+ for (const id of excludedIds) if (!traceExcludedIds.includes(id)) fail(`Spec 排除清单缺少:${id}`);
91
+ for (const id of traceExcludedIds) if (!excludedIds.includes(id)) fail(`Spec 排除清单包含未知或 active 需求:${id}`);
92
+
93
+ for (const item of traceExcluded) {
94
+ const source = manifestRequirements.find(requirement => requirement.id === item.id);
95
+ if (source && item.status !== source.status) fail(`${item.id} 的排除状态与 manifest 不一致`);
96
+ if (!item.reason || typeof item.reason !== 'string') fail(`${item.id} 的排除原因不能为空`);
97
+ }
98
+
99
+ const reviewRequirements = Array.isArray(review?.requirements) ? review.requirements : [];
100
+ const reviewed = new Map(reviewRequirements.map(item => [item.id, item]));
101
+ if (reviewed.size !== reviewRequirements.length) fail('Review 包含重复需求编号');
102
+ for (const item of reviewRequirements) {
103
+ if (!manifestRequirements.some(requirement => requirement.id === item.id)) {
104
+ fail(`Review 包含未知需求:${item.id}`);
105
+ }
106
+ }
107
+ for (const id of activeIds) {
108
+ const item = reviewed.get(id);
109
+ if (item?.reviewStatus !== 'confirmed') fail(`需求尚未确认:${id}`);
110
+ if (item && item.scopeStatus !== 'active') fail(`${id} 的 Review scopeStatus 与 manifest 不一致`);
111
+ }
112
+
113
+ const specText = specPath && fs.existsSync(specPath) ? fs.readFileSync(specPath, 'utf8') : '';
114
+ for (const item of traceRequirements) {
115
+ if (item.status !== 'specified') fail(`${item.id} 的 Spec 状态必须为 specified`);
116
+ if (!Array.isArray(item.acceptanceCriteria) || item.acceptanceCriteria.length === 0 || item.acceptanceCriteria.some(value => typeof value !== 'string' || !value.trim())) {
117
+ fail(`${item.id} 必须包含非空验收条件`);
118
+ }
119
+ if (!Array.isArray(item.verification) || item.verification.length === 0 || item.verification.some(value => !allowedVerification.has(value))) {
120
+ fail(`${item.id} 的 verification 无效`);
121
+ }
122
+ if (!new RegExp(`^## ${item.id}(?:\\s|$)`, 'm').test(specText)) {
123
+ fail(`spec.md 缺少需求章节:${item.id}`);
124
+ }
125
+ }
126
+ }
127
+
128
+ for (const error of errors) console.error(`错误 ${error}`);
129
+ console.log(`Spec 追踪校验:${errors.length} 个错误`);
130
+ process.exit(errors.length === 0 ? 0 : 1);