@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.
Files changed (88) hide show
  1. package/README.md +8 -0
  2. package/comate-engine/assets/skills/auto-commit/SKILL.md +386 -0
  3. package/comate-engine/assets/skills/auto-commit/references/issue_type_mapping.json +19 -0
  4. package/comate-engine/assets/skills/auto-commit/references/new_version_instruction.md +196 -0
  5. package/comate-engine/assets/skills/auto-commit/references/old_version_instruction.md +189 -0
  6. package/comate-engine/assets/skills/auto-commit/references/query_reference.md +176 -0
  7. package/comate-engine/assets/skills/auto-commit/scripts/compat.py +86 -0
  8. package/comate-engine/assets/skills/auto-commit/scripts/create_card_cli.py +67 -0
  9. package/comate-engine/assets/skills/auto-commit/scripts/git_diff_cli.py +195 -0
  10. package/comate-engine/assets/skills/auto-commit/scripts/git_utils.py +225 -0
  11. package/comate-engine/assets/skills/auto-commit/scripts/icafe/__init__.py +66 -0
  12. package/comate-engine/assets/skills/auto-commit/scripts/icafe/client.py +444 -0
  13. package/comate-engine/assets/skills/auto-commit/scripts/icafe/farseer.py +53 -0
  14. package/comate-engine/assets/skills/auto-commit/scripts/icafe/matching.py +778 -0
  15. package/comate-engine/assets/skills/auto-commit/scripts/logger.py +32 -0
  16. package/comate-engine/assets/skills/auto-commit/scripts/recognize_card_cli.py +63 -0
  17. package/comate-engine/assets/skills/automation-browser-comate/SKILL.md +193 -90
  18. package/comate-engine/assets/skills/figma2code-comate/SKILL.md +2 -2
  19. package/comate-engine/assets/skills/figma2code-comate/references/codeConnect.md +7 -10
  20. package/comate-engine/assets/skills/smart-commit/SKILL.md +646 -0
  21. package/comate-engine/assets/skills/smart-commit/references/issue_type_mapping.json +19 -0
  22. package/comate-engine/assets/skills/smart-commit/references/query_reference.md +176 -0
  23. package/comate-engine/assets/skills/smart-commit/scripts/compat.py +86 -0
  24. package/comate-engine/assets/skills/smart-commit/scripts/create_card_cli.py +67 -0
  25. package/comate-engine/assets/skills/smart-commit/scripts/git_utils.py +220 -0
  26. package/comate-engine/assets/skills/smart-commit/scripts/icafe/__init__.py +66 -0
  27. package/comate-engine/assets/skills/smart-commit/scripts/icafe/client.py +444 -0
  28. package/comate-engine/assets/skills/smart-commit/scripts/icafe/farseer.py +53 -0
  29. package/comate-engine/assets/skills/smart-commit/scripts/icafe/matching.py +728 -0
  30. package/comate-engine/assets/skills/smart-commit/scripts/logger.py +32 -0
  31. package/comate-engine/assets/skills/smart-commit/scripts/recognize_card_cli.py +63 -0
  32. package/comate-engine/node_modules/@comate/plugin-engine/dist/index.js +7 -7
  33. package/comate-engine/node_modules/@comate/plugin-host/dist/index.js +1 -1
  34. package/comate-engine/node_modules/@comate/plugin-host/dist/main.js +1 -1
  35. package/comate-engine/node_modules/@comate/plugin-shared-internals/dist/index.js +8 -8
  36. package/comate-engine/node_modules/@comate/preview-proxy/package.json +2 -2
  37. package/comate-engine/node_modules/better-sqlite3/build/Release/better_sqlite3.node +0 -0
  38. package/comate-engine/package.json +2 -2
  39. package/comate-engine/server.js +61 -44
  40. package/dist/bundle/index.js +8 -8
  41. package/package.json +1 -1
  42. package/comate-engine/node_modules/@comate/plugin-engine/dist/index.d.ts +0 -188
  43. package/comate-engine/node_modules/@comate/plugin-host/dist/main.d.ts +0 -14
  44. package/comate-engine/node_modules/@comate/plugin-shared-internals/dist/index.d.ts +0 -4817
  45. package/comate-engine/node_modules/better-sqlite3/README.md +0 -99
  46. package/comate-engine/node_modules/bindings/LICENSE.md +0 -22
  47. package/comate-engine/node_modules/bindings/README.md +0 -98
  48. package/comate-engine/node_modules/compare-versions/README.md +0 -133
  49. package/comate-engine/node_modules/compare-versions/lib/esm/compare.d.ts +0 -19
  50. package/comate-engine/node_modules/compare-versions/lib/esm/compareVersions.d.ts +0 -8
  51. package/comate-engine/node_modules/compare-versions/lib/esm/index.d.ts +0 -5
  52. package/comate-engine/node_modules/compare-versions/lib/esm/satisfies.d.ts +0 -14
  53. package/comate-engine/node_modules/compare-versions/lib/esm/utils.d.ts +0 -7
  54. package/comate-engine/node_modules/compare-versions/lib/esm/validate.d.ts +0 -28
  55. package/comate-engine/node_modules/file-uri-to-path/History.md +0 -21
  56. package/comate-engine/node_modules/file-uri-to-path/README.md +0 -74
  57. package/comate-engine/node_modules/file-uri-to-path/index.d.ts +0 -2
  58. package/comate-engine/node_modules/pkce-challenge/README.md +0 -55
  59. package/comate-engine/node_modules/pkce-challenge/dist/index.browser.d.ts +0 -19
  60. package/comate-engine/node_modules/pkce-challenge/dist/index.node.d.ts +0 -19
  61. package/comate-engine/node_modules/sqlite-vec/README.md +0 -1
  62. package/comate-engine/node_modules/sqlite-vec/index.d.ts +0 -17
  63. package/comate-engine/node_modules/sqlite-vec-darwin-arm64/README.md +0 -1
  64. package/comate-engine/node_modules/sqlite-vec-darwin-x64/README.md +0 -1
  65. package/comate-engine/node_modules/sqlite-vec-linux-arm64/README.md +0 -1
  66. package/comate-engine/node_modules/sqlite-vec-linux-x64/README.md +0 -1
  67. package/comate-engine/node_modules/sqlite-vec-windows-x64/README.md +0 -1
  68. package/comate-engine/node_modules/tree-sitter-bash/README.md +0 -44
  69. package/comate-engine/node_modules/tree-sitter-bash/bindings/node/binding_test.js +0 -9
  70. package/comate-engine/node_modules/tree-sitter-bash/bindings/node/index.d.ts +0 -28
  71. package/comate-engine/node_modules/tree-sitter-bash/bindings/node/index.js +0 -11
  72. package/comate-engine/node_modules/tree-sitter-bash/prebuilds/darwin-arm64/tree-sitter-bash.node +0 -0
  73. package/comate-engine/node_modules/tree-sitter-bash/prebuilds/darwin-x64/tree-sitter-bash.node +0 -0
  74. package/comate-engine/node_modules/tree-sitter-bash/prebuilds/linux-arm64/tree-sitter-bash.node +0 -0
  75. package/comate-engine/node_modules/tree-sitter-bash/prebuilds/linux-x64/tree-sitter-bash.node +0 -0
  76. package/comate-engine/node_modules/tree-sitter-bash/prebuilds/win32-arm64/tree-sitter-bash.node +0 -0
  77. package/comate-engine/node_modules/tree-sitter-bash/prebuilds/win32-x64/tree-sitter-bash.node +0 -0
  78. package/comate-engine/node_modules/tree-sitter-bash/src/grammar.json +0 -7145
  79. package/comate-engine/node_modules/tree-sitter-bash/src/node-types.json +0 -2894
  80. package/comate-engine/node_modules/web-streams-polyfill/README.md +0 -119
  81. package/comate-engine/node_modules/web-streams-polyfill/types/polyfill.d.ts +0 -28
  82. package/comate-engine/node_modules/web-streams-polyfill/types/ponyfill.d.ts +0 -809
  83. package/comate-engine/node_modules/web-tree-sitter/README.md +0 -269
  84. package/comate-engine/node_modules/web-tree-sitter/debug/tree-sitter.cjs +0 -4558
  85. package/comate-engine/node_modules/web-tree-sitter/debug/tree-sitter.js +0 -4516
  86. package/comate-engine/node_modules/web-tree-sitter/debug/tree-sitter.wasm +0 -0
  87. package/comate-engine/node_modules/web-tree-sitter/web-tree-sitter.d.ts +0 -1030
  88. package/comate-engine/node_modules/win-ca/README.md +0 -648
@@ -0,0 +1,176 @@
1
+ # iCafe 卡片查询 IQL 完整参考
2
+
3
+ ## API 端点
4
+
5
+ ### 条件查询卡片
6
+
7
+ ```
8
+ GET {base_url}/api/spaces/{space_id}/cards?iql={iql_expression}
9
+ ```
10
+
11
+ **请求参数:**
12
+
13
+ | 参数 | 位置 | 类型 | 必填 | 说明 |
14
+ |------|------|------|------|------|
15
+ | space_id | path | string | 是 | 空间 ID |
16
+ | iql | query | string | 是 | IQL 查询表达式 |
17
+ | page | query | int | 否 | 页码 |
18
+ | showDetail | query | flag | 否 | 显示详情(传空字符串即可) |
19
+ | showAssociations | query | flag | 否 | 显示关联信息 |
20
+ | isDesc | query | flag | 否 | 降序排列 |
21
+ | order | query | string | 否 | 排序字段 |
22
+ | showChildren | query | flag | 否 | 显示子卡片 |
23
+ | maxRecords | query | int | 否 | 最大返回数 |
24
+ | showOkr | query | flag | 否 | 显示 OKR |
25
+ | showAccumulate | query | flag | 否 | 显示累计信息 |
26
+
27
+ **响应示例:**
28
+
29
+ ```json
30
+ {
31
+ "cards": [
32
+ {
33
+ "id": "12345",
34
+ "sequence": "628",
35
+ "title": "示例卡片",
36
+ "type": "Bug",
37
+ "status": "新建",
38
+ "assignee": "dongkexin01",
39
+ "priority": "P1-High",
40
+ "createdAt": "2025-01-15 10:30:00",
41
+ "updatedAt": "2025-01-16 14:20:00"
42
+ }
43
+ ],
44
+ "total": 1,
45
+ "page": 1,
46
+ "pageSize": 20
47
+ }
48
+ ```
49
+
50
+ ### 获取单张卡片
51
+
52
+ ```
53
+ GET {base_url}/api/spaces/{space_id}/cards/{card_id}
54
+ ```
55
+
56
+ ### 查询最近访问空间
57
+
58
+ ```
59
+ GET {base_url}/api/v2/space/latest?currentUser={user_id}
60
+ ```
61
+
62
+ ### 获取空间计划
63
+
64
+ ```
65
+ GET {base_url}/api/v2/space/{space_id}/plans
66
+ ```
67
+
68
+ ## IQL 查询语法完整参考
69
+
70
+ ### 运算符
71
+
72
+ | 运算符 | 含义 | 适用类型 | 示例 |
73
+ |--------|------|----------|------|
74
+ | `=` | 等于 | 所有类型 | `类型 = Bug` |
75
+ | `!=` | 不等于 | 所有类型 | `状态 != 已完成` |
76
+ | `>` | 大于 | 日期、数字、流程状态 | `创建时间 > "2025-01-01"` |
77
+ | `<` | 小于 | 日期、数字、流程状态 | `优先级数值 < 3` |
78
+ | `>=` | 大于等于 | 流程状态、数字 | `流程状态 >= 开发中` |
79
+ | `<=` | 小于等于 | 流程状态、数字 | `流程状态 <= 开发中` |
80
+ | `in ()` | 在列表中 | 人员、类型、状态、标签等 | `类型 in (Bug, Story)` |
81
+ | `not in ()` | 不在列表中 | 人员、类型、状态、标签等 | `状态 not in (已完成, 已关闭)` |
82
+ | `is empty` | 为空 | 所有类型 | `负责人 is empty` |
83
+ | `is not empty` | 不为空 | 所有类型 | `截止日期 is not empty` |
84
+ | `~` | 包含(模糊匹配) | 标题、文本、关键字、URL | `标题 ~ 登录` |
85
+ | `!~` | 不包含 | 标题、文本 | `标题 !~ 测试` |
86
+ | `AND` | 逻辑与 | 连接多个条件 | `类型 = Bug AND 状态 = 新建` |
87
+ | `OR` | 逻辑或 | 连接多个条件 | `类型 = Bug OR 类型 = Story` |
88
+ | `()` | 分组 | 控制优先级 | `(类型 = Bug OR 类型 = Story) AND 负责人 = currentUser` |
89
+
90
+ ### 字段参考
91
+
92
+ | 字段名 | 类型 | 说明 |
93
+ |--------|------|------|
94
+ | 类型 | 枚举 | Bug, Story, Task, Epic 等 |
95
+ | 负责人 | 人员 | 用户 ID,如 "dongkexin01" |
96
+ | 创建人 | 人员 | 用户 ID |
97
+ | 流程状态 | 状态枚举 | 新建, 待开发, 开发中, 进行中, 已完成 等 |
98
+ | 优先级 | 枚举 | P0-Urgent, P1-High, P2-Medium, P3-Low 等 |
99
+ | 创建时间 | 日期 | 格式 "YYYY-MM-DD" 或 "YYYY-MM-DD HH:mm:ss" |
100
+ | 更新时间 | 日期 | 格式同上 |
101
+ | 截止日期 | 日期 | 格式同上 |
102
+ | 标题 | 文本 | 卡片标题 |
103
+ | 关键字 | 文本 | 全文搜索关键字 |
104
+ | Label | 标签 | 卡片标签 |
105
+ | 所属计划 | 树 | 需要完整路径,如 "测试/测试1" |
106
+
107
+ ### 常用查询模式
108
+
109
+ #### 按类型查询
110
+
111
+ ```
112
+ 类型 = Bug
113
+ 类型 in (Bug, Story, Task)
114
+ 类型 != Epic
115
+ ```
116
+
117
+ #### 按人员查询
118
+
119
+ ```
120
+ 负责人 = currentUser
121
+ 负责人 = dongkexin01
122
+ 创建人 = v_liuxiang
123
+ 负责人 in (user1, user2, user3)
124
+ 负责人 is empty
125
+ ```
126
+
127
+ #### 按状态查询
128
+
129
+ ```
130
+ 流程状态 = 新建
131
+ 流程状态 in (新建, 开发中, 待开发, 进行中)
132
+ 流程状态 <= 开发中
133
+ 流程状态 != 已完成
134
+ ```
135
+
136
+ #### 按时间查询
137
+
138
+ ```
139
+ 创建时间 > "2025-01-01"
140
+ 创建时间 > "2025-01-01 00:00:00" AND 创建时间 < "2025-06-30 23:59:59"
141
+ 更新时间 > "2025-03-01"
142
+ ```
143
+
144
+ #### 按内容查询
145
+
146
+ ```
147
+ 标题 ~ 登录
148
+ 标题 !~ 测试
149
+ 关键字 ~ 性能优化
150
+ ```
151
+
152
+ #### 组合查询
153
+
154
+ ```
155
+ 类型 = Bug AND 负责人 = currentUser AND 流程状态 != 已完成
156
+ (类型 = Bug OR 类型 = Story) AND 优先级 = P1-High
157
+ 创建人 = dongkexin01 AND 创建时间 > "2025-01-01" AND 流程状态 in (新建, 开发中)
158
+ ```
159
+
160
+ ## 认证说明
161
+
162
+ 所有请求需要 `x-ac-Authorization` header。Token 获取优先级:
163
+
164
+ 1. 环境变量 `COMATE_AUTH_TOKEN`
165
+ 2. 文件 `~/.comate/login`
166
+
167
+ ## 错误码
168
+
169
+ | HTTP 状态码 | 说明 |
170
+ |-------------|------|
171
+ | 200 | 成功 |
172
+ | 400 | 请求参数错误(如 IQL 语法错误) |
173
+ | 401 | 认证失败(token 无效或过期) |
174
+ | 403 | 无权限访问该空间 |
175
+ | 404 | 空间或卡片不存在 |
176
+ | 500 | 服务端错误 |
@@ -0,0 +1,86 @@
1
+ """
2
+ 版本兼容性检测模块
3
+
4
+ 用于检测当前 IDE 版本是否支持自定义交互式 UI
5
+ """
6
+
7
+ # 支持自定义 UI 的最低版本
8
+ MIN_INTERACTIVE_VERSION = '1.2.0'
9
+
10
+
11
+ def parse_version(version_str: str) -> tuple:
12
+ """
13
+ 解析版本号字符串为可比较的元组
14
+
15
+ Args:
16
+ version_str: 版本号字符串,如 "1.2.3"
17
+
18
+ Returns:
19
+ 版本号元组,如 (1, 2, 3)
20
+ """
21
+ try:
22
+ parts = version_str.split('.')
23
+ return tuple(int(p) for p in parts[:3])
24
+ except (ValueError, AttributeError):
25
+ return (0, 0, 0)
26
+
27
+
28
+ def is_interactive_ui_supported(ide_name: str, plugin_version: str) -> bool:
29
+ """
30
+ 检测当前 IDE 版本是否支持自定义交互式 UI
31
+
32
+ Args:
33
+ ide_name: 从 ${COMATE_IDE_NAME} 获取的值
34
+ plugin_version: 从 ${COMATE_PLUGIN_VERSION} 获取的值
35
+
36
+ Returns:
37
+ True 如果支持自定义 UI,否则 False
38
+ """
39
+ # 1. 检测占位符是否被替换
40
+ # 旧版本 CLI 不支持这些占位符,值仍为 "${...}" 原样
41
+ if not ide_name or ide_name.startswith('${'):
42
+ return False
43
+ if not plugin_version or plugin_version.startswith('${'):
44
+ return False
45
+
46
+ # 2. 版本号比较
47
+ current = parse_version(plugin_version)
48
+ minimum = parse_version(MIN_INTERACTIVE_VERSION)
49
+
50
+ return current >= minimum
51
+
52
+
53
+ def check_interactive_support(ide_name: str, plugin_version: str) -> str:
54
+ """
55
+ 检测并返回应使用的流程模式
56
+
57
+ Args:
58
+ ide_name: 从 ${COMATE_IDE_NAME} 获取的值
59
+ plugin_version: 从 ${COMATE_PLUGIN_VERSION} 获取的值
60
+
61
+ Returns:
62
+ "INTERACTIVE_MODE" 或 "FALLBACK_MODE"
63
+ """
64
+ if is_interactive_ui_supported(ide_name, plugin_version):
65
+ return "INTERACTIVE_MODE"
66
+ return "FALLBACK_MODE"
67
+
68
+
69
+ if __name__ == "__main__":
70
+ # 测试用例
71
+ test_cases = [
72
+ ("vscode", "1.2.0", "INTERACTIVE_MODE"),
73
+ ("vscode", "1.3.5", "INTERACTIVE_MODE"),
74
+ ("vscode", "1.1.9", "FALLBACK_MODE"),
75
+ ("vscode", "0.9.0", "FALLBACK_MODE"),
76
+ ("${COMATE_IDE_NAME}", "1.2.0", "FALLBACK_MODE"),
77
+ ("vscode", "${COMATE_PLUGIN_VERSION}", "FALLBACK_MODE"),
78
+ ("", "1.2.0", "FALLBACK_MODE"),
79
+ ("vscode", "", "FALLBACK_MODE"),
80
+ ]
81
+
82
+ print("Running compatibility check tests...")
83
+ for ide, version, expected in test_cases:
84
+ result = check_interactive_support(ide, version)
85
+ status = "PASS" if result == expected else "FAIL"
86
+ print(f"[{status}] ide={ide!r}, version={version!r} -> {result} (expected: {expected})")
@@ -0,0 +1,67 @@
1
+ """命令行工具:创建 iCafe 卡片
2
+
3
+ 用法:
4
+ python3 create_card_cli.py --title "标题" --space-prefix "dkx" --type-name "Bug" --username "dongkexin01"
5
+
6
+ 输出:
7
+ JSON 格式的创建结果,包含 sequence、title、spacePrefix 等字段。
8
+ 失败时输出 {"error": "错误信息"}。
9
+ """
10
+
11
+ import argparse
12
+ import json
13
+ import os
14
+ import sys
15
+
16
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
17
+
18
+ from icafe.client import ICafeQueryClient
19
+ from git_utils import get_current_user
20
+
21
+
22
+ def main():
23
+ """解析命令行参数并调用 iCafe 接口创建卡片。"""
24
+ parser = argparse.ArgumentParser(description="创建 iCafe 卡片")
25
+ parser.add_argument("--title", required=True, help="卡片标题")
26
+ parser.add_argument("--space-prefix", required=True, help="空间前缀,如 dkx")
27
+ parser.add_argument("--type-name", required=True, help="卡片类型名称,如 Bug")
28
+ parser.add_argument("--description", default="", help="卡片描述")
29
+ parser.add_argument("--username", default=None, help="用户名,如 dongkexin01")
30
+ args = parser.parse_args()
31
+
32
+ try:
33
+ client = ICafeQueryClient()
34
+ assignee = get_current_user(username=args.username)
35
+
36
+ created = client.create_card(
37
+ title=args.title,
38
+ space_id=args.space_prefix,
39
+ card_type=args.type_name,
40
+ description=args.description,
41
+ assignee_id=assignee,
42
+ )
43
+
44
+ if created:
45
+ issues = created.get("issues", [])
46
+ if issues:
47
+ sequence = issues[0].get("sequence")
48
+ print(json.dumps({
49
+ "success": True,
50
+ "sequence": str(sequence),
51
+ "title": args.title,
52
+ "type": args.type_name,
53
+ "status": "新建",
54
+ "spacePrefix": args.space_prefix,
55
+ }))
56
+ else:
57
+ print(json.dumps({"error": "创建卡片成功但无法获取序列号"}))
58
+ else:
59
+ print(json.dumps({"error": "创建卡片失败,请重试"}))
60
+ except Exception as e:
61
+ err_name = type(e).__name__
62
+ print(json.dumps({"error": f"创建卡片异常: {err_name}: {e}"}))
63
+ sys.exit(1)
64
+
65
+
66
+ if __name__ == "__main__":
67
+ main()
@@ -0,0 +1,220 @@
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
+ pattern = re.compile(r'\b([a-zA-Z][a-zA-Z0-9_]*)-(\d+)\b')
207
+ counter_fallback: Dict[str, int] = {}
208
+
209
+ for line in log_text.splitlines():
210
+ for match in pattern.finditer(line):
211
+ prefix = match.group(1).lower()
212
+ if prefix in GIT_LOG_FALSE_POSITIVE_PREFIXES:
213
+ continue
214
+ if len(prefix) <= 1:
215
+ continue
216
+ counter_fallback[prefix] = counter_fallback.get(prefix, 0) + 1
217
+
218
+ sorted_prefixes = sorted(counter_fallback, key=counter_fallback.get, reverse=True)
219
+ logger.info("git log 提取前缀: %s, 策略=正则启发式", sorted_prefixes)
220
+ 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
+ ]