@comate/zulu 1.1.0 → 1.2.0
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.
- package/README.md +8 -0
- package/comate-engine/assets/skills/auto-commit/SKILL.md +386 -0
- package/comate-engine/assets/skills/auto-commit/references/issue_type_mapping.json +19 -0
- package/comate-engine/assets/skills/auto-commit/references/new_version_instruction.md +196 -0
- package/comate-engine/assets/skills/auto-commit/references/old_version_instruction.md +189 -0
- package/comate-engine/assets/skills/auto-commit/references/query_reference.md +176 -0
- package/comate-engine/assets/skills/auto-commit/scripts/compat.py +86 -0
- package/comate-engine/assets/skills/auto-commit/scripts/create_card_cli.py +67 -0
- package/comate-engine/assets/skills/auto-commit/scripts/git_diff_cli.py +195 -0
- package/comate-engine/assets/skills/auto-commit/scripts/git_utils.py +225 -0
- package/comate-engine/assets/skills/auto-commit/scripts/icafe/__init__.py +66 -0
- package/comate-engine/assets/skills/auto-commit/scripts/icafe/client.py +444 -0
- package/comate-engine/assets/skills/auto-commit/scripts/icafe/farseer.py +53 -0
- package/comate-engine/assets/skills/auto-commit/scripts/icafe/matching.py +778 -0
- package/comate-engine/assets/skills/auto-commit/scripts/logger.py +32 -0
- package/comate-engine/assets/skills/auto-commit/scripts/recognize_card_cli.py +63 -0
- package/comate-engine/assets/skills/automation-browser-comate/SKILL.md +193 -90
- package/comate-engine/assets/skills/figma2code-comate/SKILL.md +2 -2
- package/comate-engine/assets/skills/figma2code-comate/references/codeConnect.md +7 -10
- package/comate-engine/assets/skills/smart-commit/SKILL.md +646 -0
- package/comate-engine/assets/skills/smart-commit/references/issue_type_mapping.json +19 -0
- package/comate-engine/assets/skills/smart-commit/references/query_reference.md +176 -0
- package/comate-engine/assets/skills/smart-commit/scripts/compat.py +86 -0
- package/comate-engine/assets/skills/smart-commit/scripts/create_card_cli.py +67 -0
- package/comate-engine/assets/skills/smart-commit/scripts/git_utils.py +220 -0
- package/comate-engine/assets/skills/smart-commit/scripts/icafe/__init__.py +66 -0
- package/comate-engine/assets/skills/smart-commit/scripts/icafe/client.py +444 -0
- package/comate-engine/assets/skills/smart-commit/scripts/icafe/farseer.py +53 -0
- package/comate-engine/assets/skills/smart-commit/scripts/icafe/matching.py +728 -0
- package/comate-engine/assets/skills/smart-commit/scripts/logger.py +32 -0
- package/comate-engine/assets/skills/smart-commit/scripts/recognize_card_cli.py +63 -0
- package/comate-engine/node_modules/@comate/plugin-engine/dist/index.js +7 -7
- package/comate-engine/node_modules/@comate/plugin-host/dist/index.js +1 -1
- package/comate-engine/node_modules/@comate/plugin-host/dist/main.js +1 -1
- package/comate-engine/node_modules/@comate/plugin-shared-internals/dist/index.js +8 -8
- package/comate-engine/node_modules/@comate/preview-proxy/package.json +2 -2
- package/comate-engine/node_modules/better-sqlite3/build/Release/better_sqlite3.node +0 -0
- package/comate-engine/package.json +2 -2
- package/comate-engine/server.js +61 -44
- package/dist/bundle/index.js +8 -8
- package/package.json +1 -1
- package/comate-engine/node_modules/@comate/plugin-engine/dist/index.d.ts +0 -188
- package/comate-engine/node_modules/@comate/plugin-host/dist/main.d.ts +0 -14
- package/comate-engine/node_modules/@comate/plugin-shared-internals/dist/index.d.ts +0 -4817
- package/comate-engine/node_modules/better-sqlite3/README.md +0 -99
- package/comate-engine/node_modules/bindings/LICENSE.md +0 -22
- package/comate-engine/node_modules/bindings/README.md +0 -98
- package/comate-engine/node_modules/compare-versions/README.md +0 -133
- package/comate-engine/node_modules/compare-versions/lib/esm/compare.d.ts +0 -19
- package/comate-engine/node_modules/compare-versions/lib/esm/compareVersions.d.ts +0 -8
- package/comate-engine/node_modules/compare-versions/lib/esm/index.d.ts +0 -5
- package/comate-engine/node_modules/compare-versions/lib/esm/satisfies.d.ts +0 -14
- package/comate-engine/node_modules/compare-versions/lib/esm/utils.d.ts +0 -7
- package/comate-engine/node_modules/compare-versions/lib/esm/validate.d.ts +0 -28
- package/comate-engine/node_modules/file-uri-to-path/History.md +0 -21
- package/comate-engine/node_modules/file-uri-to-path/README.md +0 -74
- package/comate-engine/node_modules/file-uri-to-path/index.d.ts +0 -2
- package/comate-engine/node_modules/pkce-challenge/README.md +0 -55
- package/comate-engine/node_modules/pkce-challenge/dist/index.browser.d.ts +0 -19
- package/comate-engine/node_modules/pkce-challenge/dist/index.node.d.ts +0 -19
- package/comate-engine/node_modules/sqlite-vec/README.md +0 -1
- package/comate-engine/node_modules/sqlite-vec/index.d.ts +0 -17
- package/comate-engine/node_modules/sqlite-vec-darwin-arm64/README.md +0 -1
- package/comate-engine/node_modules/sqlite-vec-darwin-x64/README.md +0 -1
- package/comate-engine/node_modules/sqlite-vec-linux-arm64/README.md +0 -1
- package/comate-engine/node_modules/sqlite-vec-linux-x64/README.md +0 -1
- package/comate-engine/node_modules/sqlite-vec-windows-x64/README.md +0 -1
- package/comate-engine/node_modules/tree-sitter-bash/README.md +0 -44
- package/comate-engine/node_modules/tree-sitter-bash/bindings/node/binding_test.js +0 -9
- package/comate-engine/node_modules/tree-sitter-bash/bindings/node/index.d.ts +0 -28
- package/comate-engine/node_modules/tree-sitter-bash/bindings/node/index.js +0 -11
- package/comate-engine/node_modules/tree-sitter-bash/prebuilds/darwin-arm64/tree-sitter-bash.node +0 -0
- package/comate-engine/node_modules/tree-sitter-bash/prebuilds/darwin-x64/tree-sitter-bash.node +0 -0
- package/comate-engine/node_modules/tree-sitter-bash/prebuilds/linux-arm64/tree-sitter-bash.node +0 -0
- package/comate-engine/node_modules/tree-sitter-bash/prebuilds/linux-x64/tree-sitter-bash.node +0 -0
- package/comate-engine/node_modules/tree-sitter-bash/prebuilds/win32-arm64/tree-sitter-bash.node +0 -0
- package/comate-engine/node_modules/tree-sitter-bash/prebuilds/win32-x64/tree-sitter-bash.node +0 -0
- package/comate-engine/node_modules/tree-sitter-bash/src/grammar.json +0 -7145
- package/comate-engine/node_modules/tree-sitter-bash/src/node-types.json +0 -2894
- package/comate-engine/node_modules/web-streams-polyfill/README.md +0 -119
- package/comate-engine/node_modules/web-streams-polyfill/types/polyfill.d.ts +0 -28
- package/comate-engine/node_modules/web-streams-polyfill/types/ponyfill.d.ts +0 -809
- package/comate-engine/node_modules/web-tree-sitter/README.md +0 -269
- package/comate-engine/node_modules/web-tree-sitter/debug/tree-sitter.cjs +0 -4558
- package/comate-engine/node_modules/web-tree-sitter/debug/tree-sitter.js +0 -4516
- package/comate-engine/node_modules/web-tree-sitter/debug/tree-sitter.wasm +0 -0
- package/comate-engine/node_modules/web-tree-sitter/web-tree-sitter.d.ts +0 -1030
- package/comate-engine/node_modules/win-ca/README.md +0 -648
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""命令行工具:获取 workspace 的 git diff 摘要
|
|
2
|
+
|
|
3
|
+
用法:
|
|
4
|
+
python3 git_diff_cli.py --workspace /path/to/project
|
|
5
|
+
|
|
6
|
+
输出:
|
|
7
|
+
JSON 格式的 diff 摘要,包含 changed_files、stat_summary、diff_content 等字段。
|
|
8
|
+
失败时输出 {"error": "错误信息"}。
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import subprocess
|
|
15
|
+
import sys
|
|
16
|
+
|
|
17
|
+
# tracked diff 最大行数
|
|
18
|
+
MAX_TRACKED_DIFF_LINES = 300
|
|
19
|
+
# untracked diff 最大行数
|
|
20
|
+
MAX_UNTRACKED_DIFF_LINES = 200
|
|
21
|
+
# 合并后 diff 最大总行数
|
|
22
|
+
MAX_TOTAL_DIFF_LINES = 500
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def run_git(args, cwd, timeout=10):
|
|
26
|
+
"""执行 git 命令,返回 stdout 字符串。失败返回空字符串。"""
|
|
27
|
+
try:
|
|
28
|
+
result = subprocess.run(
|
|
29
|
+
["git"] + args,
|
|
30
|
+
capture_output=True, text=True, timeout=timeout, cwd=cwd
|
|
31
|
+
)
|
|
32
|
+
if result.returncode == 0:
|
|
33
|
+
return result.stdout
|
|
34
|
+
return ""
|
|
35
|
+
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
|
|
36
|
+
return ""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def parse_numstat(cwd):
|
|
40
|
+
"""通过 git diff HEAD --numstat 获取 tracked 文件变更统计。"""
|
|
41
|
+
output = run_git(["diff", "HEAD", "--numstat"], cwd)
|
|
42
|
+
files = []
|
|
43
|
+
for line in output.strip().splitlines():
|
|
44
|
+
if not line:
|
|
45
|
+
continue
|
|
46
|
+
parts = line.split("\t")
|
|
47
|
+
if len(parts) != 3:
|
|
48
|
+
continue
|
|
49
|
+
ins_str, del_str, file_path = parts
|
|
50
|
+
# binary 文件的 numstat 输出是 - - filename
|
|
51
|
+
insertions = int(ins_str) if ins_str != "-" else 0
|
|
52
|
+
deletions = int(del_str) if del_str != "-" else 0
|
|
53
|
+
files.append({
|
|
54
|
+
"file": file_path,
|
|
55
|
+
"insertions": insertions,
|
|
56
|
+
"deletions": deletions,
|
|
57
|
+
})
|
|
58
|
+
return files
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_untracked_files(cwd):
|
|
62
|
+
"""获取 untracked 文件列表。"""
|
|
63
|
+
output = run_git(["ls-files", "--others", "--exclude-standard"], cwd)
|
|
64
|
+
return [f for f in output.strip().splitlines() if f]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def get_tracked_diff(cwd):
|
|
68
|
+
"""获取 tracked 文件的 diff 内容,截断到 MAX_TRACKED_DIFF_LINES 行。"""
|
|
69
|
+
output = run_git(["diff", "HEAD"], cwd, timeout=30)
|
|
70
|
+
lines = output.splitlines()
|
|
71
|
+
truncated = len(lines) > MAX_TRACKED_DIFF_LINES
|
|
72
|
+
if truncated:
|
|
73
|
+
lines = lines[:MAX_TRACKED_DIFF_LINES]
|
|
74
|
+
return "\n".join(lines), truncated
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def get_untracked_diff_and_stats(cwd, untracked_files):
|
|
78
|
+
"""获取 untracked 文件的 diff 和变更统计。
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
(changed_files, diff_content, truncated)
|
|
82
|
+
"""
|
|
83
|
+
changed_files = []
|
|
84
|
+
diff_lines = []
|
|
85
|
+
total_lines = 0
|
|
86
|
+
truncated = False
|
|
87
|
+
|
|
88
|
+
for file_path in untracked_files:
|
|
89
|
+
try:
|
|
90
|
+
result = subprocess.run(
|
|
91
|
+
["git", "diff", "--no-index", "/dev/null", file_path],
|
|
92
|
+
capture_output=True, text=True, timeout=10, cwd=cwd
|
|
93
|
+
)
|
|
94
|
+
# git diff --no-index 在有差异时返回 1,不算失败
|
|
95
|
+
file_diff = result.stdout
|
|
96
|
+
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
# 统计 insertions:+ 开头但非 +++ 的行
|
|
100
|
+
insertions = 0
|
|
101
|
+
for line in file_diff.splitlines():
|
|
102
|
+
if line.startswith("+") and not line.startswith("+++"):
|
|
103
|
+
insertions += 1
|
|
104
|
+
|
|
105
|
+
if insertions > 0:
|
|
106
|
+
changed_files.append({
|
|
107
|
+
"file": file_path,
|
|
108
|
+
"insertions": insertions,
|
|
109
|
+
"deletions": 0,
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
# 拼接 diff 内容,检查总行数上限
|
|
113
|
+
file_lines = file_diff.splitlines()
|
|
114
|
+
remaining = MAX_UNTRACKED_DIFF_LINES - total_lines
|
|
115
|
+
if remaining <= 0:
|
|
116
|
+
truncated = True
|
|
117
|
+
break
|
|
118
|
+
if len(file_lines) > remaining:
|
|
119
|
+
file_lines = file_lines[:remaining]
|
|
120
|
+
truncated = True
|
|
121
|
+
diff_lines.extend(file_lines)
|
|
122
|
+
total_lines += len(file_lines)
|
|
123
|
+
|
|
124
|
+
return changed_files, "\n".join(diff_lines), truncated
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def build_stat_summary(changed_files):
|
|
128
|
+
"""构建统计摘要字符串,如 '3 files, +130, -78'。"""
|
|
129
|
+
total_ins = sum(f["insertions"] for f in changed_files)
|
|
130
|
+
total_del = sum(f["deletions"] for f in changed_files)
|
|
131
|
+
n = len(changed_files)
|
|
132
|
+
return f"{n} file{'s' if n != 1 else ''}, +{total_ins}, -{total_del}"
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def main():
|
|
136
|
+
"""解析命令行参数并输出 workspace 的 git diff 摘要 JSON。"""
|
|
137
|
+
parser = argparse.ArgumentParser(description="获取 workspace 的 git diff 摘要")
|
|
138
|
+
parser.add_argument("--workspace", required=True, help="workspace 路径")
|
|
139
|
+
parser.add_argument("--max-diff-lines", type=int, default=MAX_TOTAL_DIFF_LINES,
|
|
140
|
+
help="diff 内容最大总行数")
|
|
141
|
+
args = parser.parse_args()
|
|
142
|
+
|
|
143
|
+
workspace = os.path.abspath(args.workspace)
|
|
144
|
+
if not os.path.isdir(workspace):
|
|
145
|
+
print(json.dumps({"error": f"workspace 路径不存在: {workspace}"}))
|
|
146
|
+
sys.exit(1)
|
|
147
|
+
|
|
148
|
+
max_total = args.max_diff_lines
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
# 1. tracked 文件变更统计
|
|
152
|
+
tracked_files = parse_numstat(workspace)
|
|
153
|
+
|
|
154
|
+
# 2. untracked 文件列表
|
|
155
|
+
untracked_files = get_untracked_files(workspace)
|
|
156
|
+
|
|
157
|
+
# 3. tracked diff 内容
|
|
158
|
+
tracked_diff, tracked_truncated = get_tracked_diff(workspace)
|
|
159
|
+
|
|
160
|
+
# 4. untracked diff 和统计
|
|
161
|
+
untracked_changed, untracked_diff, untracked_truncated = \
|
|
162
|
+
get_untracked_diff_and_stats(workspace, untracked_files)
|
|
163
|
+
|
|
164
|
+
# 5. 合并 changed_files
|
|
165
|
+
all_changed = tracked_files + untracked_changed
|
|
166
|
+
|
|
167
|
+
# 6. 合并 diff 内容,检查总行数上限
|
|
168
|
+
tracked_lines = tracked_diff.splitlines() if tracked_diff else []
|
|
169
|
+
untracked_lines = untracked_diff.splitlines() if untracked_diff else []
|
|
170
|
+
all_lines = tracked_lines + untracked_lines
|
|
171
|
+
truncated = tracked_truncated or untracked_truncated or len(all_lines) > max_total
|
|
172
|
+
if len(all_lines) > max_total:
|
|
173
|
+
all_lines = all_lines[:max_total]
|
|
174
|
+
diff_content = "\n".join(all_lines)
|
|
175
|
+
|
|
176
|
+
# 7. 构建输出
|
|
177
|
+
has_changes = bool(all_changed) or bool(untracked_files)
|
|
178
|
+
output = {
|
|
179
|
+
"workspace": workspace,
|
|
180
|
+
"changed_files": all_changed,
|
|
181
|
+
"untracked_files": untracked_files,
|
|
182
|
+
"stat_summary": build_stat_summary(all_changed) if all_changed else "",
|
|
183
|
+
"has_changes": has_changes,
|
|
184
|
+
"diff_content": diff_content,
|
|
185
|
+
"truncated": truncated,
|
|
186
|
+
}
|
|
187
|
+
print(json.dumps(output, ensure_ascii=False))
|
|
188
|
+
|
|
189
|
+
except Exception as e:
|
|
190
|
+
print(json.dumps({"error": f"获取 diff 摘要异常: {type(e).__name__}: {e}"}))
|
|
191
|
+
sys.exit(1)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
if __name__ == "__main__":
|
|
195
|
+
main()
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""Git 工具函数"""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import re
|
|
6
|
+
from typing import Dict, List, Any, Optional
|
|
7
|
+
|
|
8
|
+
from icafe.client import (
|
|
9
|
+
DEFAULT_MAX_COMMITS,
|
|
10
|
+
DEFAULT_MAX_DIFF_LINES,
|
|
11
|
+
GIT_LOG_FALSE_POSITIVE_PREFIXES,
|
|
12
|
+
)
|
|
13
|
+
from logger import get_logger
|
|
14
|
+
|
|
15
|
+
logger = get_logger()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _get_username_from_arg_or_env(username: Optional[str] = None) -> Optional[str]:
|
|
19
|
+
"""从命令行参数或环境变量获取用户名
|
|
20
|
+
|
|
21
|
+
优先使用传入的 username 参数(来自命令行 --username),
|
|
22
|
+
回退到环境变量 COMATE_USERNAME。
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
username: 通过命令行参数传入的用户名
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
用户名字符串;未设置或未替换时返回 None
|
|
29
|
+
"""
|
|
30
|
+
# 优先使用命令行参数
|
|
31
|
+
if username and 'COMATE_USERNAME' not in username:
|
|
32
|
+
return username
|
|
33
|
+
# 回退到环境变量
|
|
34
|
+
value = os.environ.get('COMATE_USERNAME', '')
|
|
35
|
+
if value and 'COMATE_USERNAME' not in value:
|
|
36
|
+
return value
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_current_user(username: Optional[str] = None) -> Optional[str]:
|
|
41
|
+
"""获取当前用户 ID
|
|
42
|
+
|
|
43
|
+
优先级:
|
|
44
|
+
1. 通过参数传入的用户名(来自命令行 --username)
|
|
45
|
+
2. 环境变量 COMATE_USERNAME
|
|
46
|
+
3. git config user.email 提取 @ 前的用户名
|
|
47
|
+
4. git config user.name
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
username: 通过命令行参数传入的用户名
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
用户 ID 字符串,如 "dongkexin01";无法获取时返回 None
|
|
54
|
+
"""
|
|
55
|
+
# 1. 优先从参数或环境变量获取用户名
|
|
56
|
+
comate_username = _get_username_from_arg_or_env(username)
|
|
57
|
+
logger.info("skill.md username: %s", comate_username)
|
|
58
|
+
if comate_username:
|
|
59
|
+
logger.info("用户识别: source=arg_or_env, user=%s", comate_username)
|
|
60
|
+
return comate_username
|
|
61
|
+
|
|
62
|
+
# 2. 从 git config user.email 提取
|
|
63
|
+
try:
|
|
64
|
+
result = subprocess.run(
|
|
65
|
+
["git", "config", "user.email"],
|
|
66
|
+
capture_output=True, text=True, timeout=5
|
|
67
|
+
)
|
|
68
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
69
|
+
username = result.stdout.strip().split("@")[0]
|
|
70
|
+
if username:
|
|
71
|
+
logger.info("用户识别: source=email, user=%s", username)
|
|
72
|
+
return username
|
|
73
|
+
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
# 3. 回退到 git config user.name
|
|
77
|
+
try:
|
|
78
|
+
result = subprocess.run(
|
|
79
|
+
["git", "config", "user.name"],
|
|
80
|
+
capture_output=True, text=True, timeout=5
|
|
81
|
+
)
|
|
82
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
83
|
+
username = result.stdout.strip()
|
|
84
|
+
logger.info("用户识别: source=name, user=%s", username)
|
|
85
|
+
return username
|
|
86
|
+
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
logger.warning("无法获取用户 ID,所有来源均失败")
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def get_git_diff_summary(max_diff_lines: int = DEFAULT_MAX_DIFF_LINES) -> Optional[Dict[str, Any]]:
|
|
94
|
+
"""获取当前工作区的 git diff 摘要
|
|
95
|
+
|
|
96
|
+
执行 git diff HEAD 获取所有未提交的变更(包括暂存和未暂存),
|
|
97
|
+
并提取变更文件列表和 diff 内容摘要。
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
max_diff_lines: diff 输出的最大行数,超出部分截断。默认 500。
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
包含 diff 信息的字典:
|
|
104
|
+
{
|
|
105
|
+
"changed_files": ["path/to/file1.ts", ...],
|
|
106
|
+
"stat_summary": "git diff --stat 输出的摘要行",
|
|
107
|
+
"diff_content": "截断后的 diff 全文",
|
|
108
|
+
"truncated": bool
|
|
109
|
+
}
|
|
110
|
+
非 git 仓库或无变更时返回 None
|
|
111
|
+
"""
|
|
112
|
+
try:
|
|
113
|
+
stat_result = subprocess.run(
|
|
114
|
+
["git", "diff", "HEAD", "--stat"],
|
|
115
|
+
capture_output=True, text=True, timeout=10
|
|
116
|
+
)
|
|
117
|
+
if stat_result.returncode != 0 or not stat_result.stdout.strip():
|
|
118
|
+
logger.info("git diff 无变更")
|
|
119
|
+
return None
|
|
120
|
+
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
121
|
+
logger.info("git diff 无变更")
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
stat_lines = stat_result.stdout.strip().splitlines()
|
|
125
|
+
changed_files = []
|
|
126
|
+
for line in stat_lines[:-1]:
|
|
127
|
+
file_path = line.split("|")[0].strip()
|
|
128
|
+
if file_path:
|
|
129
|
+
changed_files.append(file_path)
|
|
130
|
+
stat_summary = stat_lines[-1].strip() if stat_lines else ""
|
|
131
|
+
|
|
132
|
+
if not changed_files:
|
|
133
|
+
logger.info("git diff 无变更")
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
diff_result = subprocess.run(
|
|
138
|
+
["git", "diff", "HEAD"],
|
|
139
|
+
capture_output=True, text=True, timeout=30
|
|
140
|
+
)
|
|
141
|
+
diff_content = diff_result.stdout if diff_result.returncode == 0 else ""
|
|
142
|
+
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
143
|
+
diff_content = ""
|
|
144
|
+
|
|
145
|
+
lines = diff_content.splitlines()
|
|
146
|
+
truncated = len(lines) > max_diff_lines
|
|
147
|
+
if truncated:
|
|
148
|
+
diff_content = "\n".join(lines[:max_diff_lines]) + "\n... (truncated)"
|
|
149
|
+
|
|
150
|
+
logger.info("git diff: %d 个文件, %s, truncated=%s", len(changed_files), stat_summary, truncated)
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
"changed_files": changed_files,
|
|
154
|
+
"stat_summary": stat_summary,
|
|
155
|
+
"diff_content": diff_content,
|
|
156
|
+
"truncated": truncated,
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def extract_space_ids_from_git_log(
|
|
161
|
+
max_commits: int = DEFAULT_MAX_COMMITS,
|
|
162
|
+
known_prefixes: Optional[List[str]] = None,
|
|
163
|
+
) -> List[str]:
|
|
164
|
+
"""从 git commit message 中提取 iCafe 空间前缀
|
|
165
|
+
|
|
166
|
+
优先使用已知的空间前缀列表进行精确匹配(支持含连字符的前缀如 DevOps-iScan),
|
|
167
|
+
回退到正则启发式提取。
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
max_commits: 扫描的最大提交数量
|
|
171
|
+
known_prefixes: 已知的空间 prefixCode 列表(来自 API)。
|
|
172
|
+
提供时优先用精确匹配;未提供时用正则启发式。
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
去重后的空间前缀列表(保留原始大小写),按提交次数降序排列。
|
|
176
|
+
非 git 仓库或无匹配时返回空列表。
|
|
177
|
+
"""
|
|
178
|
+
try:
|
|
179
|
+
result = subprocess.run(
|
|
180
|
+
["git", "log", "--oneline", "-n", str(max_commits)],
|
|
181
|
+
capture_output=True, text=True, timeout=10
|
|
182
|
+
)
|
|
183
|
+
if result.returncode != 0:
|
|
184
|
+
return []
|
|
185
|
+
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
186
|
+
return []
|
|
187
|
+
|
|
188
|
+
log_text = result.stdout.strip()
|
|
189
|
+
if not log_text:
|
|
190
|
+
return []
|
|
191
|
+
|
|
192
|
+
# 策略 1:用已知前缀精确匹配 "前缀-数字"
|
|
193
|
+
if known_prefixes:
|
|
194
|
+
counter: Dict[str, int] = {}
|
|
195
|
+
for prefix in known_prefixes:
|
|
196
|
+
pat = re.compile(re.escape(prefix) + r'-\d+', re.IGNORECASE)
|
|
197
|
+
count = len(pat.findall(log_text))
|
|
198
|
+
if count > 0:
|
|
199
|
+
counter[prefix] = count
|
|
200
|
+
if counter:
|
|
201
|
+
sorted_prefixes = sorted(counter, key=counter.get, reverse=True)
|
|
202
|
+
logger.info("git log 提取前缀: %s, 策略=精确匹配", sorted_prefixes)
|
|
203
|
+
return sorted_prefixes
|
|
204
|
+
|
|
205
|
+
# 策略 2:正则启发式兜底(无已知前缀或精确匹配无结果)
|
|
206
|
+
# 支持含连字符的复合前缀,如 DevOps-iScan-36261、TPUE-EE-2-22
|
|
207
|
+
# 贪婪匹配:最后一个 -数字 是卡片 ID,前面所有部分都是空间前缀
|
|
208
|
+
pattern = re.compile(r'\b([a-zA-Z][a-zA-Z0-9_]*(?:-[a-zA-Z0-9_]+)*)-(\d+)\b')
|
|
209
|
+
counter_fallback: Dict[str, int] = {}
|
|
210
|
+
|
|
211
|
+
for line in log_text.splitlines():
|
|
212
|
+
for match in pattern.finditer(line):
|
|
213
|
+
raw_prefix = match.group(1)
|
|
214
|
+
prefix_lower = raw_prefix.lower()
|
|
215
|
+
# 检查前缀首段是否是常见误报(如 feat-xxx、fix-xxx)
|
|
216
|
+
first_segment = raw_prefix.split('-', 1)[0].lower()
|
|
217
|
+
if first_segment in GIT_LOG_FALSE_POSITIVE_PREFIXES:
|
|
218
|
+
continue
|
|
219
|
+
if len(raw_prefix) <= 1:
|
|
220
|
+
continue
|
|
221
|
+
counter_fallback[prefix_lower] = counter_fallback.get(prefix_lower, 0) + 1
|
|
222
|
+
|
|
223
|
+
sorted_prefixes = sorted(counter_fallback, key=counter_fallback.get, reverse=True)
|
|
224
|
+
logger.info("git log 提取前缀: %s, 策略=正则启发式", sorted_prefixes)
|
|
225
|
+
return sorted_prefixes
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""iCafe Card Query Client - 卡片条件查询助手
|
|
2
|
+
|
|
3
|
+
提供 iCafe 平台卡片条件查询相关的 API 接口封装,
|
|
4
|
+
支持通过 IQL 表达式、卡片 ID、空间等维度灵活查询卡片信息。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .client import (
|
|
8
|
+
ICafeQueryConfig,
|
|
9
|
+
ICafeQueryClient,
|
|
10
|
+
# 常量
|
|
11
|
+
ICAFE_BASE_URL,
|
|
12
|
+
FARSEER_BASE_URL,
|
|
13
|
+
DEFAULT_TIMEOUT,
|
|
14
|
+
DEFAULT_LOOKBACK_DAYS,
|
|
15
|
+
DEFAULT_MAX_RECORDS,
|
|
16
|
+
DEFAULT_MAX_COMMITS,
|
|
17
|
+
DEFAULT_MAX_DIFF_LINES,
|
|
18
|
+
GIT_LOG_FALSE_POSITIVE_PREFIXES,
|
|
19
|
+
ISSUE_TYPE_MAP,
|
|
20
|
+
ISSUE_TYPE_NAME_MAP,
|
|
21
|
+
)
|
|
22
|
+
from git_utils import (
|
|
23
|
+
get_current_user,
|
|
24
|
+
get_git_diff_summary,
|
|
25
|
+
extract_space_ids_from_git_log,
|
|
26
|
+
)
|
|
27
|
+
from .matching import (
|
|
28
|
+
auto_detect_space,
|
|
29
|
+
find_matching_card,
|
|
30
|
+
match_diff_to_cards,
|
|
31
|
+
build_type_filter_iql,
|
|
32
|
+
format_card_summary,
|
|
33
|
+
format_query_result,
|
|
34
|
+
)
|
|
35
|
+
from .farseer import get_binding_card_types
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
# 配置和客户端
|
|
39
|
+
'ICafeQueryConfig',
|
|
40
|
+
'ICafeQueryClient',
|
|
41
|
+
# 常量
|
|
42
|
+
'ICAFE_BASE_URL',
|
|
43
|
+
'FARSEER_BASE_URL',
|
|
44
|
+
'DEFAULT_TIMEOUT',
|
|
45
|
+
'DEFAULT_LOOKBACK_DAYS',
|
|
46
|
+
'DEFAULT_MAX_RECORDS',
|
|
47
|
+
'DEFAULT_MAX_COMMITS',
|
|
48
|
+
'DEFAULT_MAX_DIFF_LINES',
|
|
49
|
+
'GIT_LOG_FALSE_POSITIVE_PREFIXES',
|
|
50
|
+
'ISSUE_TYPE_MAP',
|
|
51
|
+
'ISSUE_TYPE_NAME_MAP',
|
|
52
|
+
# Git 工具
|
|
53
|
+
'get_current_user',
|
|
54
|
+
'get_git_diff_summary',
|
|
55
|
+
'extract_space_ids_from_git_log',
|
|
56
|
+
# 自动检测和匹配
|
|
57
|
+
'auto_detect_space',
|
|
58
|
+
'find_matching_card',
|
|
59
|
+
'match_diff_to_cards',
|
|
60
|
+
'build_type_filter_iql',
|
|
61
|
+
# 格式化
|
|
62
|
+
'format_card_summary',
|
|
63
|
+
'format_query_result',
|
|
64
|
+
# Farseer API
|
|
65
|
+
'get_binding_card_types',
|
|
66
|
+
]
|