@comate/zulu 1.3.3-internal.3 → 1.3.3-internal.4

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.
@@ -0,0 +1,220 @@
1
+ # 交互指南
2
+
3
+ 本文档描述 auto-commit skill 中 UI 交互和数据提交的具体操作方式。
4
+
5
+ ---
6
+
7
+ ## 全局规则
8
+
9
+ **⚠️ 全局静默要求:整个流程中 Agent 仅通过 `__interactive:` UI 组件与用户交互。除步骤 9 的最终结果文字外,Agent 禁止向用户输出任何文字内容,包括但不限于:**
10
+ - **过渡性话语**:如 "Now I have the data"、"Let me perform matching"、"让我分析一下"
11
+ - **内部分析过程**:如 "Card 26: xxx - Score: 95"、"Matching analysis:"、匹配评分细节
12
+ - **状态说明**:如 "has_good_match: true"、"Cards sorted by score"
13
+ - **JSON 数据或代码片段**
14
+
15
+ **Agent 应在内部完成所有思考后,直接调用对应的 `run_command`,不在工具调用之间输出任何文字。**
16
+
17
+ **⚠️ 重要:所有 `run_command` 调用(`__interactive:` 除外)必须加 `__silent:` 前缀。唯一例外是步骤 9 的 git commit 命令,需要展示提交结果给用户。**
18
+
19
+ **⚠️ 禁止额外命令:Agent 不得在流程步骤之外自行执行任何 shell 命令(如 `git status`、`ls` 等)。iCafe 数据由步骤 1 的 Python 脚本获取,git diff 由步骤 2 中 Agent 按规定命令获取。**
20
+
21
+ **⚠️ 工作目录规则:Skill 执行时的工作目录可能不是用户项目目录。当命令需要在用户项目目录下执行时(如 git 操作、Python 脚本),必须在命令中加 `cd <用户项目目录> &&` 切换目录。但 `__silent:` 或 `__interactive:` 前缀必须始终在命令最开头。**
22
+
23
+ **⚠️ 多 Workspace 规则:用户可能打开了多个代码库(multi-root workspace)。环境上下文中的 `Workspace Path` 会列出所有 workspace 路径。在步骤 2 和步骤 8 中,Agent 必须对每个 workspace 分别执行 git 命令,不可只处理第一个 workspace 后忽略其余的。**
24
+
25
+ 正确格式:
26
+ ```
27
+ __silent: cd /path/to/project && git add .
28
+ __interactive:icafe-cards {...}
29
+ ```
30
+
31
+ 错误格式(禁止):
32
+ ```
33
+ cd /path/to/project && __silent: git add .
34
+ cd /path/to/project && __interactive:icafe-cards {...}
35
+ ```
36
+
37
+ ---
38
+
39
+ ## 步骤 1:获取 iCafe 数据
40
+
41
+ 使用 SKILL.md 步骤 1 中定义的命令模板执行。
42
+
43
+ ---
44
+
45
+ ## 步骤 2:获取 git diff
46
+
47
+ 使用 SKILL.md 步骤 2 中定义的命令模板执行。
48
+
49
+ ---
50
+
51
+ ## 步骤 4:展示卡片选择
52
+
53
+ ### 4a. 构造并通过脚本验证 payload
54
+
55
+ **⚠️ 不需要手动转换 snake_case/camelCase,不需要拼 JSON,不需要传文件路径。** 脚本自动读取步骤 1 留下的缓存文件,Agent 只需传入简单的 CLI 参数。
56
+
57
+ ```bash
58
+ __silent: python3 <skill_directory>/scripts/build_icafe_cards_payload.py --view-mode list --default-title "优化 xxx 相关功能"
59
+ ```
60
+
61
+ 参数说明:
62
+ - `--view-mode`(必填):`list` 或 `create`,由步骤 3 确定
63
+ - `--default-title`(必填):Agent 根据 diff 语义生成的推荐标题
64
+ - `--cards`(可选):排序后的 cards JSON 数组,不传则使用 result 中的原始顺序
65
+ - `--result-file`(可选):默认读取 `/tmp/__comate_icafe_result.json`(步骤 1 自动写入)
66
+
67
+ **脚本自动从缓存文件读取所有数据并完成 snake_case → camelCase 转换,Agent 不需要手动处理任何字段名。**
68
+
69
+ ### 4b. 解析脚本输出并调用 UI
70
+
71
+ **如果脚本返回 `{"valid": true, "payload": {...}}`:**
72
+
73
+ **⚠️ 必须提取 `payload` 字段的值(内层 JSON),使用 `run_command` 工具调用 UI,禁止将 payload 作为文本输出给用户。**
74
+
75
+ 例如脚本返回:
76
+ ```json
77
+ {"valid": true, "payload": {"cards": [...], "spacePrefix": "dkx", ...}}
78
+ ```
79
+ 则调用:
80
+ ```
81
+ run_command(command="__interactive:icafe-cards {\"cards\": [...], \"spacePrefix\": \"dkx\", ...}")
82
+ ```
83
+ **禁止**(作为文本输出或传入错误数据):
84
+ ```
85
+ __interactive:icafe-cards {"valid": true, "payload": {"cards": [...], ...}}
86
+ ```
87
+
88
+ **如果脚本返回 `{"valid": false, "errors": [...]}`:**
89
+
90
+ 根据 `errors` 中的具体描述修正数据,重新调用脚本(最多重试 1 次)。如果重试后仍返回 `valid: false`,或脚本返回 `{"error": "..."}`,则**降级为文本交互**(见下方降级格式)。
91
+
92
+ ---
93
+
94
+ ## 步骤 5:处理用户操作
95
+
96
+ UI 组件返回 JSON,可能的 action 值:
97
+
98
+ | action | 含义 | 处理方式 |
99
+ |--------|------|----------|
100
+ | `select` | 选择已有卡片 | 保存 `card` 中的卡片信息,继续步骤 7 |
101
+ | `recognize` | 链接或卡片 ID 识别 | 调用 `recognize_card_cli.py` 获取卡片详情,继续步骤 7 |
102
+ | `create` | 新建卡片 | 调用 `create_card_cli.py` 创建卡片,继续步骤 7 |
103
+ | `skip` | 跳过 | 输出"已跳过卡片绑定和代码提交。",**结束 skill** |
104
+ | `do_not_show_again` | 不再提示 | 输出"已关闭推荐卡片提示。",**结束 skill** |
105
+
106
+ **recognize 处理:**
107
+ ```
108
+ __silent: cd <用户项目目录> && python3 <skill_directory>/scripts/recognize_card_cli.py --link "<用户返回的link>"
109
+ ```
110
+
111
+ **create 处理:**
112
+
113
+ 使用 SKILL.md 步骤 5 中定义的「创建卡片命令模板」,**加 `__silent:` 前缀**执行。
114
+
115
+ ---
116
+
117
+ ## 步骤 8:展示提交确认
118
+
119
+ ### 8a. 构造并通过脚本验证 payload
120
+
121
+ **推荐:使用缓存模式**(脚本自动从缓存读取 diff、执行 git branch 获取分支信息):
122
+
123
+ ```bash
124
+ __silent: python3 <skill_directory>/scripts/build_git_commit_payload.py --cache --commit-message "<commitMessage>" --bound-card '<boundCard JSON 或 null>'
125
+ ```
126
+
127
+ 参数说明:
128
+ - `--commit-message`(必填):iCode 仓库格式 `"<spacePrefix>-<sequence> <描述>"`;非 iCode 仓库不带卡片 ID
129
+ - `--bound-card`(必填):步骤 5 选定的卡片对象 JSON,无卡片时传字符串 `'null'`
130
+
131
+ **⚠️ 所有命令必须带 `__silent:` 前缀(`__interactive:` 除外)。**
132
+
133
+ ### 8b. 解析脚本输出并调用 UI
134
+
135
+ **如果脚本返回 `{"valid": true, "payload": {...}}`:**
136
+
137
+ **⚠️ 必须提取 `payload` 字段的值(内层 JSON),使用 `run_command` 工具调用 UI,禁止将 payload 作为文本输出给用户。**
138
+
139
+ 例如脚本返回:
140
+ ```json
141
+ {"valid": true, "payload": {"commitMessage": "dkx-200 fix", "boundCard": {...}, "workspaces": [...]}}
142
+ ```
143
+ 则调用:
144
+ ```
145
+ run_command(command="__interactive:git-commit {\"commitMessage\": \"dkx-200 fix\", \"boundCard\": {...}, \"workspaces\": [...]}")
146
+ ```
147
+ **禁止**(作为文本输出或传入错误数据):
148
+ ```
149
+ __interactive:git-commit {"valid": true, "payload": {"commitMessage": "dkx-200 fix", ...}}
150
+ ```
151
+
152
+ **如果脚本返回 `{"valid": false, "errors": [...]}`:**
153
+
154
+ 根据 `errors` 中的具体描述修正数据,重新调用脚本(最多重试 1 次)。如果重试后仍返回 `valid: false`,或脚本返回 `{"error": "..."}`,则**降级为文本交互**(见下方降级格式)。
155
+
156
+ 用户操作后,tool output 返回 JSON:
157
+ - 确认提交:`{"action": "submit", "repos": [{"workspace": "...", "commitMessage": "...", "branch": "..."}]}`
158
+ - 取消提交:`{"action": "cancel"}`
159
+
160
+ ---
161
+
162
+ ## 步骤 9:执行提交
163
+
164
+ 从 tool output 解析用户操作:
165
+
166
+ **如果是 action === "submit":**
167
+
168
+ UI 返回的结果格式:
169
+ ```json
170
+ {"action": "submit", "repos": [{"workspace": "/path/to/repo", "commitMessage": "dkx-200 修复...", "branch": "feature/xxx"}]}
171
+ ```
172
+
173
+ Agent 收到后,对 `repos` 数组中的每个仓库自主决定执行方式:
174
+ - `cd` 到对应 workspace 目录
175
+ - 执行 `git add .`、`git commit -m "<commitMessage>"`(git commit 命令不使用 `__silent:`,展示提交结果给用户)
176
+ - 如果用户选择了特定分支且不是当前分支,先 `git checkout` 切换
177
+ - **推送方式根据仓库类型区分:**
178
+ - 统一使用 `git push origin HEAD:refs/for/<branch>` 推送到 Gerrit 代码评审
179
+ - 多仓库时逐个执行,每个仓库独立处理
180
+
181
+ **如果是 action === "cancel":**
182
+ - 输出"已取消提交"
183
+
184
+ ---
185
+
186
+ ## 降级文本交互格式
187
+
188
+ 当 builder 脚本验证失败且重试仍失败,或脚本返回系统错误时,Agent 降级为纯文本交互。
189
+
190
+ ### 步骤 4 降级(卡片选择)
191
+
192
+ ```
193
+ 请选择要绑定的 iCafe 卡片(回复编号):
194
+
195
+ 1. dkx-200 修复登录问题 [Bug] [开发中]
196
+ 2. dkx-198 新增导出功能 [Story] [待开发]
197
+ 3. dkx-195 优化查询性能 [Task] [进行中]
198
+
199
+ 回复 N 选择对应卡片,或回复卡片链接/ID,或回复 "新建" 创建卡片,回复 "跳过" 跳过。
200
+ ```
201
+
202
+ 用户回复后,Agent 按步骤 5 的规则处理对应操作。
203
+
204
+ ### 步骤 8 降级(提交确认)
205
+
206
+ ```
207
+ 📝 提交确认
208
+
209
+ Commit Message: dkx-200 修复登录问题
210
+ 绑定卡片: dkx-200 修复登录问题 (Bug)
211
+
212
+ 变更文件:
213
+ - frontend-app: 2 files, +100, -78
214
+ - src/App.tsx (+76, -66)
215
+ - src/utils.ts (+24, -12)
216
+
217
+ 回复 "确认" 提交代码,回复 "取消" 放弃提交。
218
+ ```
219
+
220
+ 用户回复后,Agent 按步骤 9 的规则处理对应操作。
@@ -0,0 +1,195 @@
1
+ """命令行工具:构建并验证 git-commit UI payload
2
+
3
+ 用法:
4
+ # 缓存模式(推荐):脚本自动从缓存读取 diff、执行 git branch 获取分支信息
5
+ python3 build_git_commit_payload.py --cache --commit-message "msg" --bound-card '{"sequence":"39",...}'
6
+ python3 build_git_commit_payload.py --cache --commit-message "msg" --bound-card 'null'
7
+
8
+ # JSON 模式(降级):Agent 手动构造完整 workspaces 数组
9
+ python3 build_git_commit_payload.py --json '<payload JSON>'
10
+
11
+ 缓存模式输入:
12
+ --commit-message: commit message(必填)
13
+ --bound-card: 绑定卡片 JSON 对象,或字符串 'null' 表示无卡片(必填)
14
+
15
+ JSON 模式输入:
16
+ --json: 完整 payload JSON,包含 commitMessage、boundCard、workspaces。
17
+ diffSummary 内部保持 snake_case(与 git_diff_cli.py 输出一致)。
18
+
19
+ 输出:
20
+ 验证通过: {"valid": true, "payload": {...}}
21
+ 验证失败: {"valid": false, "errors": ["...", ...]}
22
+ 系统错误: {"error": "..."}
23
+ """
24
+
25
+ import argparse
26
+ import json
27
+ import os
28
+ import subprocess
29
+ import sys
30
+
31
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
32
+ from cache_manager import get_all_git_diffs, CACHE_PATH
33
+ from payload_validators import convert_git_commit_payload
34
+
35
+
36
+ def _get_current_branch(workspace):
37
+ """通过 git branch --show-current 获取当前分支名。"""
38
+ try:
39
+ result = subprocess.run(
40
+ ["git", "branch", "--show-current"],
41
+ capture_output=True, text=True, encoding="utf-8", errors="replace",
42
+ timeout=10, cwd=workspace
43
+ )
44
+ if result.returncode == 0 and result.stdout.strip():
45
+ return result.stdout.strip()
46
+ except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
47
+ pass
48
+ return ""
49
+
50
+
51
+ def _get_remote_branches(workspace, current_branch):
52
+ """通过 git branch -r 获取远程分支列表,最多 10 个。"""
53
+ try:
54
+ result = subprocess.run(
55
+ ["git", "branch", "-r", "--sort=-committerdate"],
56
+ capture_output=True, text=True, encoding="utf-8", errors="replace",
57
+ timeout=10, cwd=workspace
58
+ )
59
+ if result.returncode == 0:
60
+ branches = []
61
+ for line in result.stdout.splitlines():
62
+ line = line.strip()
63
+ if not line or "->" in line:
64
+ continue
65
+ # 去掉 "origin/" 前缀
66
+ name = line.split("/", 1)[-1] if "/" in line else line
67
+ is_current = (name == current_branch)
68
+ branches.append({"name": name, "isCurrent": is_current})
69
+ if len(branches) >= 10:
70
+ break
71
+
72
+ # 确保当前分支在列表中
73
+ if current_branch and not any(b["isCurrent"] for b in branches):
74
+ branches.insert(0, {"name": current_branch, "isCurrent": True})
75
+
76
+ return branches
77
+ except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
78
+ pass
79
+ return []
80
+
81
+
82
+ def _build_workspace_entry(workspace_path, diff_data):
83
+ """为单个 workspace 构建 payload 条目。"""
84
+ has_changes = diff_data.get("has_changes", False)
85
+ repo_name = os.path.basename(workspace_path)
86
+
87
+ if has_changes:
88
+ current_branch = _get_current_branch(workspace_path)
89
+ remote_branches = _get_remote_branches(workspace_path, current_branch)
90
+ diff_summary = {
91
+ "changed_files": diff_data.get("changed_files", []),
92
+ "stat_summary": diff_data.get("stat_summary", ""),
93
+ }
94
+ else:
95
+ current_branch = ""
96
+ remote_branches = []
97
+ diff_summary = None
98
+
99
+ return {
100
+ "workspace": workspace_path,
101
+ "repoName": repo_name,
102
+ "currentBranch": current_branch,
103
+ "remoteBranches": remote_branches,
104
+ "hasChanges": has_changes,
105
+ "diffSummary": diff_summary,
106
+ }
107
+
108
+
109
+ def _handle_cache_mode(args):
110
+ """从缓存读取 diff 数据,自动构建 workspaces 并验证。"""
111
+ git_diffs = get_all_git_diffs()
112
+
113
+ if not git_diffs:
114
+ print(json.dumps({"error": f"缓存中无 git_diffs 数据(缓存路径: {CACHE_PATH})。"
115
+ "请确保步骤 2 已执行 git_diff_cli.py"}, ensure_ascii=False))
116
+ sys.exit(1)
117
+
118
+ # 解析 bound_card
119
+ bound_card = None
120
+ if args.bound_card and args.bound_card.lower() != "null":
121
+ try:
122
+ bound_card = json.loads(args.bound_card)
123
+ except json.JSONDecodeError as e:
124
+ print(json.dumps({"error": f"--bound-card JSON 解析失败: {e}"}, ensure_ascii=False))
125
+ sys.exit(1)
126
+
127
+ # 构建 workspaces 数组
128
+ workspaces = []
129
+ for workspace_path, diff_data in git_diffs.items():
130
+ ws_entry = _build_workspace_entry(workspace_path, diff_data)
131
+ workspaces.append(ws_entry)
132
+
133
+ # 构建完整 payload
134
+ raw = {
135
+ "commitMessage": args.commit_message,
136
+ "boundCard": bound_card,
137
+ "workspaces": workspaces,
138
+ }
139
+
140
+ converted, errors = convert_git_commit_payload(raw)
141
+
142
+ if errors:
143
+ print(json.dumps({"valid": False, "errors": errors}, ensure_ascii=False))
144
+ else:
145
+ print(json.dumps({"valid": True, "payload": converted}, ensure_ascii=False))
146
+
147
+
148
+ def _handle_json_mode(args):
149
+ """原有 --json 模式,直接解析传入的 JSON 进行验证。"""
150
+ try:
151
+ data = json.loads(args.json)
152
+ except json.JSONDecodeError as e:
153
+ print(json.dumps({"error": f"JSON 解析失败: {e}"}, ensure_ascii=False))
154
+ sys.exit(1)
155
+
156
+ converted, errors = convert_git_commit_payload(data)
157
+
158
+ if errors:
159
+ print(json.dumps({"valid": False, "errors": errors}, ensure_ascii=False))
160
+ else:
161
+ print(json.dumps({"valid": True, "payload": converted}, ensure_ascii=False))
162
+
163
+
164
+ def main():
165
+ """解析命令行参数,构建并验证 git-commit UI payload。"""
166
+ parser = argparse.ArgumentParser(description="构建并验证 git-commit UI payload")
167
+
168
+ # 缓存模式参数
169
+ parser.add_argument("--cache", action="store_true",
170
+ help="从统一缓存读取 git_diff 数据,自动构建 workspaces")
171
+ parser.add_argument("--commit-message", default=None,
172
+ help="commit message(--cache 模式必填)")
173
+ parser.add_argument("--bound-card", default=None,
174
+ help="绑定卡片 JSON(--cache 模式,传 'null' 表示无卡片)")
175
+
176
+ # JSON 模式参数(向后兼容)
177
+ parser.add_argument("--json", default=None,
178
+ help="payload 数据 JSON(向后兼容,与 --cache 二选一)")
179
+
180
+ args = parser.parse_args()
181
+
182
+ if args.cache:
183
+ if not args.commit_message:
184
+ print(json.dumps({"error": "--cache 模式下 --commit-message 为必填参数"}, ensure_ascii=False))
185
+ sys.exit(1)
186
+ _handle_cache_mode(args)
187
+ elif args.json:
188
+ _handle_json_mode(args)
189
+ else:
190
+ print(json.dumps({"error": "必须提供 --cache 或 --json 参数"}, ensure_ascii=False))
191
+ sys.exit(1)
192
+
193
+
194
+ if __name__ == "__main__":
195
+ main()
@@ -0,0 +1,80 @@
1
+ """命令行工具:构建并验证 icafe-cards UI payload
2
+
3
+ 用法:
4
+ python3 build_icafe_cards_payload.py --view-mode list --default-title "优化 xxx 相关功能"
5
+
6
+ 输入:
7
+ --result-file: match_card_cli.py 的输出 JSON 文件路径(可选,默认读取 /tmp/__comate_icafe_result.json)
8
+ --view-mode: "list" 或 "create"(必填,模型决策)
9
+ --default-title: 推荐卡片标题(必填,模型根据 diff 生成)
10
+ --cards: 排序后的卡片列表 JSON(可选,不传则使用 result 中的原始 cards)
11
+
12
+ 脚本从 result 文件读取所有数据,完成 snake_case → camelCase 转换,
13
+ 覆盖模型决策字段(viewMode、defaults.title、cards),验证后输出 payload。
14
+ Agent 不需要手动转换任何字段名,不需要拼 JSON。
15
+
16
+ 输出:
17
+ 验证通过: {"valid": true, "payload": {...}}
18
+ 验证失败: {"valid": false, "errors": ["...", ...]}
19
+ 系统错误: {"error": "..."}
20
+ """
21
+
22
+ import argparse
23
+ import json
24
+ import os
25
+ import sys
26
+
27
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
28
+ from cache_manager import get_icafe_result, CACHE_PATH
29
+ from payload_validators import convert_icafe_cards_payload
30
+
31
+
32
+ def main():
33
+ """解析命令行参数,构建并验证 icafe-cards UI payload。"""
34
+ parser = argparse.ArgumentParser(description="构建并验证 icafe-cards UI payload")
35
+ parser.add_argument("--result-file", default=None,
36
+ help="已废弃,忽略此参数。脚本自动从统一缓存读取")
37
+ parser.add_argument("--view-mode", required=True, choices=["list", "create"], help="视图模式")
38
+ parser.add_argument("--default-title", required=True, help="默认卡片标题")
39
+ parser.add_argument("--cards", default=None, help="排序后的卡片列表 JSON(可选)")
40
+ args = parser.parse_args()
41
+
42
+ # 从统一缓存读取 result
43
+ result = get_icafe_result()
44
+ if result is None:
45
+ print(json.dumps({"error": f"缓存中无 icafe_result 数据(缓存路径: {CACHE_PATH})"}, ensure_ascii=False))
46
+ sys.exit(1)
47
+
48
+ if not isinstance(result, dict):
49
+ print(json.dumps({"error": "result 文件内容必须是 JSON 对象"}, ensure_ascii=False))
50
+ sys.exit(1)
51
+
52
+ # 覆盖模型决策字段
53
+ result["viewMode"] = args.view_mode
54
+
55
+ # 覆盖 defaults.title
56
+ if "defaults" not in result or not isinstance(result["defaults"], dict):
57
+ result["defaults"] = {}
58
+ result["defaults"]["title"] = args.default_title
59
+
60
+ # 覆盖 cards(如果传了 --cards)
61
+ if args.cards is not None:
62
+ try:
63
+ cards = json.loads(args.cards)
64
+ if isinstance(cards, list):
65
+ result["cards"] = cards
66
+ except json.JSONDecodeError:
67
+ print(json.dumps({"error": "--cards 参数 JSON 解析失败"}, ensure_ascii=False))
68
+ sys.exit(1)
69
+
70
+ # 转换 + 验证
71
+ converted, errors = convert_icafe_cards_payload(result)
72
+
73
+ if errors:
74
+ print(json.dumps({"valid": False, "errors": errors}, ensure_ascii=False))
75
+ else:
76
+ print(json.dumps({"valid": True, "payload": converted}, ensure_ascii=False))
77
+
78
+
79
+ if __name__ == "__main__":
80
+ main()
@@ -0,0 +1,69 @@
1
+ """统一的缓存文件管理模块
2
+
3
+ 所有脚本共享一个缓存文件 /tmp/__comate_auto_commit_cache.json,
4
+ 结构为 {"icafe_result": {...}, "git_diffs": {"/path": {...}}}。
5
+ """
6
+
7
+ import json
8
+ import os
9
+
10
+ # 统一缓存文件路径(唯一声明位置)
11
+ CACHE_PATH = "/tmp/__comate_auto_commit_cache.json"
12
+
13
+
14
+ def read_cache():
15
+ """读取完整缓存 dict。文件不存在或解析失败返回空 dict。"""
16
+ try:
17
+ with open(CACHE_PATH, "r", encoding="utf-8") as f:
18
+ return json.load(f)
19
+ except (OSError, json.JSONDecodeError):
20
+ return {}
21
+
22
+
23
+ def write_cache(data):
24
+ """将完整 dict 写入缓存文件。失败静默忽略。"""
25
+ try:
26
+ with open(CACHE_PATH, "w", encoding="utf-8") as f:
27
+ json.dump(data, f, ensure_ascii=False)
28
+ except OSError:
29
+ pass
30
+
31
+
32
+ def update_icafe_result(result):
33
+ """更新缓存中的 icafe_result 字段。"""
34
+ cache = read_cache()
35
+ cache["icafe_result"] = result
36
+ write_cache(cache)
37
+
38
+
39
+ def update_git_diff(workspace, diff_data):
40
+ """更新缓存中指定 workspace 的 git_diff 数据。"""
41
+ cache = read_cache()
42
+ if "git_diffs" not in cache:
43
+ cache["git_diffs"] = {}
44
+ cache["git_diffs"][workspace] = diff_data
45
+ write_cache(cache)
46
+
47
+
48
+ def get_icafe_result():
49
+ """读取缓存中的 icafe_result。不存在返回 None。"""
50
+ return read_cache().get("icafe_result")
51
+
52
+
53
+ def get_git_diff(workspace):
54
+ """读取缓存中指定 workspace 的 git_diff。不存在返回 None。"""
55
+ return read_cache().get("git_diffs", {}).get(workspace)
56
+
57
+
58
+ def get_all_git_diffs():
59
+ """读取缓存中所有 workspace 的 git_diff dict。不存在返回空 dict。"""
60
+ return read_cache().get("git_diffs", {})
61
+
62
+
63
+ def cleanup():
64
+ """删除缓存文件。用于 skill 流程结束时清理。"""
65
+ try:
66
+ if os.path.exists(CACHE_PATH):
67
+ os.remove(CACHE_PATH)
68
+ except OSError:
69
+ pass