zco-claude 0.0.8__py3-none-any.whl
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.
- ClaudeSettings/DOT.claudeignore +7 -0
- ClaudeSettings/README.md +100 -0
- ClaudeSettings/commands/generate_changelog.sh +49 -0
- ClaudeSettings/commands/show_env +92 -0
- ClaudeSettings/commands/zco-clean +164 -0
- ClaudeSettings/commands/zco-git-summary +15 -0
- ClaudeSettings/commands/zco-git-tag +42 -0
- ClaudeSettings/hooks/CHANGELOG.md +157 -0
- ClaudeSettings/hooks/README.md +254 -0
- ClaudeSettings/hooks/save_chat_plain.py +148 -0
- ClaudeSettings/hooks/save_chat_spec.py +398 -0
- ClaudeSettings/rules/README.md +270 -0
- ClaudeSettings/rules/go/.golangci.yml.template +170 -0
- ClaudeSettings/rules/go/GoBuildAutoVersion.v250425.md +95 -0
- ClaudeSettings/rules/go/check-standards.sh +128 -0
- ClaudeSettings/rules/go/coding-standards.md +973 -0
- ClaudeSettings/rules/go/example.go +207 -0
- ClaudeSettings/rules/go/go-testing.md +691 -0
- ClaudeSettings/rules/go/list-comments.sh +85 -0
- ClaudeSettings/settings.sample.json +71 -0
- ClaudeSettings/skills/README.md +225 -0
- ClaudeSettings/skills/zco-docs-update/SKILL.md +381 -0
- ClaudeSettings/skills/zco-help/SKILL.md +601 -0
- ClaudeSettings/skills/zco-plan/SKILL.md +661 -0
- ClaudeSettings/skills/zco-plan-new/SKILL.md +585 -0
- ClaudeSettings/zco-scripts/co-docs-update.sh +150 -0
- ClaudeSettings/zco-scripts/test_update_plan_metadata.py +328 -0
- ClaudeSettings/zco-scripts/update-plan-metadata.py +324 -0
- zco_claude-0.0.8.dist-info/METADATA +190 -0
- zco_claude-0.0.8.dist-info/RECORD +34 -0
- zco_claude-0.0.8.dist-info/WHEEL +5 -0
- zco_claude-0.0.8.dist-info/entry_points.txt +3 -0
- zco_claude-0.0.8.dist-info/top_level.txt +1 -0
- zco_claude_init.py +1732 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Claude Code 对话自动保存脚本(增强版)
|
|
4
|
+
保存完整对话 + 工具调用 + 参考资源
|
|
5
|
+
|
|
6
|
+
Environment Variables:
|
|
7
|
+
- YJ_CLAUDE_CHAT_SAVE_SPEC: Must be "1" to enable this hook
|
|
8
|
+
- YJ_CLAUDE_CHAT_SAVE_DIR: Output directory (default: ${GIT_ROOT}/_.claude_hist)
|
|
9
|
+
"""
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
import re
|
|
14
|
+
import subprocess
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import List, Dict, Any, Set
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def extract_keywords(text: str, max_keywords: int = 3) -> str:
|
|
21
|
+
"""从文本中提取关键词"""
|
|
22
|
+
text = re.sub(r'[^\w\s\u4e00-\u9fff]', ' ', text)
|
|
23
|
+
words = text.split()
|
|
24
|
+
|
|
25
|
+
stop_words = {'的', '了', '是', '在', '我', '有', '和', '就', '不', '人', '都', '一', '一个',
|
|
26
|
+
'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'is', 'are',
|
|
27
|
+
'请', '帮', '我要', '能否', '如何', '什么', '怎么', '吗', '呢'}
|
|
28
|
+
|
|
29
|
+
keywords = []
|
|
30
|
+
for word in words:
|
|
31
|
+
word = word.strip()
|
|
32
|
+
if len(word) >= 2 and word not in stop_words:
|
|
33
|
+
if word not in keywords:
|
|
34
|
+
keywords.append(word)
|
|
35
|
+
if len(keywords) >= max_keywords:
|
|
36
|
+
break
|
|
37
|
+
|
|
38
|
+
if not keywords:
|
|
39
|
+
return "对话记录"
|
|
40
|
+
|
|
41
|
+
return '_'.join(keywords[:max_keywords])
|
|
42
|
+
|
|
43
|
+
def get_git_root(project_dir: Path=None) -> Path:
|
|
44
|
+
"""获取当前 Git 仓库根目录"""
|
|
45
|
+
try:
|
|
46
|
+
# 执行 git rev-parse --show-toplevel 命令
|
|
47
|
+
if project_dir:
|
|
48
|
+
result = subprocess.run(
|
|
49
|
+
['git', '-C', str(project_dir), 'rev-parse', '--show-toplevel'],
|
|
50
|
+
capture_output=True, text=True, check=True
|
|
51
|
+
)
|
|
52
|
+
else:
|
|
53
|
+
result = subprocess.run(
|
|
54
|
+
['git', 'rev-parse', '--show-toplevel'],
|
|
55
|
+
capture_output=True, text=True, check=True
|
|
56
|
+
)
|
|
57
|
+
return Path(result.stdout.strip())
|
|
58
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
59
|
+
return Path.cwd()
|
|
60
|
+
|
|
61
|
+
def get_hist_dir(project_dir: Path=None) -> Path:
|
|
62
|
+
"""获取历史记录目录"""
|
|
63
|
+
hist_dir_name = os.environ.get('YJ_CLAUDE_CHAT_SAVE_DIR', None)
|
|
64
|
+
git_root = get_git_root(project_dir)
|
|
65
|
+
if not hist_dir_name:
|
|
66
|
+
hist_dir = git_root / '_.claude_hist'
|
|
67
|
+
else:
|
|
68
|
+
hist_dir = os.path.abspath(os.path.join(str(git_root), hist_dir_name))
|
|
69
|
+
hist_dir.mkdir(exist_ok=True)
|
|
70
|
+
return hist_dir
|
|
71
|
+
|
|
72
|
+
def format_message_content(msg_data: Any) -> str:
|
|
73
|
+
"""格式化消息内容(支持 Claude Code 格式)"""
|
|
74
|
+
# Claude Code 格式:外层 message 对象包含 role 和 content
|
|
75
|
+
if isinstance(msg_data, dict) and 'message' in msg_data:
|
|
76
|
+
msg_data = msg_data.get('message', {})
|
|
77
|
+
|
|
78
|
+
# 提取 content
|
|
79
|
+
content = msg_data.get('content', msg_data) if isinstance(msg_data, dict) else msg_data
|
|
80
|
+
if not content:
|
|
81
|
+
return ''
|
|
82
|
+
if isinstance(content, str):
|
|
83
|
+
return content
|
|
84
|
+
elif isinstance(content, list):
|
|
85
|
+
parts = []
|
|
86
|
+
for item in content:
|
|
87
|
+
if isinstance(item, dict):
|
|
88
|
+
if item.get('type') == 'text':
|
|
89
|
+
parts.append(item.get('text', ''))
|
|
90
|
+
elif item.get('type') == 'tool_use':
|
|
91
|
+
tool_name = item.get('name', 'unknown')
|
|
92
|
+
parts.append(f"\n[使用工具: {tool_name}]\n")
|
|
93
|
+
elif isinstance(item, str):
|
|
94
|
+
parts.append(item)
|
|
95
|
+
return ''.join(parts)
|
|
96
|
+
return str(content)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def extract_tool_calls(messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
100
|
+
"""提取所有工具调用(支持 Claude Code 格式)"""
|
|
101
|
+
tool_calls = []
|
|
102
|
+
|
|
103
|
+
for msg in messages:
|
|
104
|
+
# Claude Code 格式:type 在外层
|
|
105
|
+
msg_type = msg.get('type', '')
|
|
106
|
+
if msg_type != 'assistant':
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
# 内层 message 对象
|
|
110
|
+
inner_msg = msg.get('message', {})
|
|
111
|
+
content = inner_msg.get('content', [])
|
|
112
|
+
if not isinstance(content, list):
|
|
113
|
+
continue
|
|
114
|
+
|
|
115
|
+
for item in content:
|
|
116
|
+
if isinstance(item, dict) and item.get('type') == 'tool_use':
|
|
117
|
+
tool_calls.append({
|
|
118
|
+
'name': item.get('name', 'unknown'),
|
|
119
|
+
'input': item.get('input', {}),
|
|
120
|
+
'id': item.get('id', '')
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
return tool_calls
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def extract_tool_results(messages: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
127
|
+
"""提取工具返回结果(支持 Claude Code 格式)"""
|
|
128
|
+
tool_results = {}
|
|
129
|
+
|
|
130
|
+
for msg in messages:
|
|
131
|
+
# Claude Code 格式:检查外层 type
|
|
132
|
+
msg_type = msg.get('type', '')
|
|
133
|
+
if msg_type != 'user':
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
# 检查是否有 toolUseResult
|
|
137
|
+
tool_result = msg.get('toolUseResult')
|
|
138
|
+
if tool_result and isinstance(tool_result, dict):
|
|
139
|
+
tool_id = tool_result.get('tool_use_id', '')
|
|
140
|
+
result_content = tool_result.get('content', '')
|
|
141
|
+
|
|
142
|
+
# 解析结果内容
|
|
143
|
+
if isinstance(result_content, list):
|
|
144
|
+
text_parts = []
|
|
145
|
+
for part in result_content:
|
|
146
|
+
if isinstance(part, dict) and part.get('type') == 'text':
|
|
147
|
+
text_parts.append(part.get('text', ''))
|
|
148
|
+
result_content = '\n'.join(text_parts)
|
|
149
|
+
|
|
150
|
+
tool_results[tool_id] = result_content
|
|
151
|
+
|
|
152
|
+
# 也检查内层 message 中的 content
|
|
153
|
+
inner_msg = msg.get('message', {})
|
|
154
|
+
content = inner_msg.get('content', [])
|
|
155
|
+
if not isinstance(content, list):
|
|
156
|
+
continue
|
|
157
|
+
|
|
158
|
+
for item in content:
|
|
159
|
+
if isinstance(item, dict) and item.get('type') == 'tool_result':
|
|
160
|
+
tool_id = item.get('tool_use_id', '')
|
|
161
|
+
result_content = item.get('content', '')
|
|
162
|
+
|
|
163
|
+
# 解析结果内容
|
|
164
|
+
if isinstance(result_content, list):
|
|
165
|
+
text_parts = []
|
|
166
|
+
for part in result_content:
|
|
167
|
+
if isinstance(part, dict) and part.get('type') == 'text':
|
|
168
|
+
text_parts.append(part.get('text', ''))
|
|
169
|
+
result_content = '\n'.join(text_parts)
|
|
170
|
+
|
|
171
|
+
tool_results[tool_id] = result_content
|
|
172
|
+
|
|
173
|
+
return tool_results
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def extract_references(tool_calls: List[Dict], tool_results: Dict) -> Set[str]:
|
|
177
|
+
"""提取参考资源(URLs、文件路径等)"""
|
|
178
|
+
references = set()
|
|
179
|
+
|
|
180
|
+
# 从工具调用中提取
|
|
181
|
+
for call in tool_calls:
|
|
182
|
+
tool_name = call.get('name', '')
|
|
183
|
+
tool_input = call.get('input', {})
|
|
184
|
+
|
|
185
|
+
# WebFetch 工具
|
|
186
|
+
if tool_name == 'WebFetch' or tool_name == 'WebSearch':
|
|
187
|
+
url = tool_input.get('url', '')
|
|
188
|
+
if url:
|
|
189
|
+
references.add(f"🌐 {url}")
|
|
190
|
+
|
|
191
|
+
# Read 工具
|
|
192
|
+
elif tool_name == 'Read':
|
|
193
|
+
file_path = tool_input.get('file_path', '')
|
|
194
|
+
if file_path:
|
|
195
|
+
references.add(f"📄 {file_path}")
|
|
196
|
+
|
|
197
|
+
# Task 工具(Agent 调用)
|
|
198
|
+
elif tool_name == 'Task':
|
|
199
|
+
agent_type = tool_input.get('subagent_type', '')
|
|
200
|
+
if agent_type:
|
|
201
|
+
references.add(f"🤖 Agent: {agent_type}")
|
|
202
|
+
|
|
203
|
+
# 从工具结果中提取 URLs
|
|
204
|
+
for result in tool_results.values():
|
|
205
|
+
if isinstance(result, str):
|
|
206
|
+
# 提取 http/https URLs
|
|
207
|
+
urls = re.findall(r'https?://[^\s<>"{}|\\^`\[\]]+', result)
|
|
208
|
+
for url in urls:
|
|
209
|
+
references.add(f"🌐 {url}")
|
|
210
|
+
|
|
211
|
+
return references
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def parse_transcript(transcript_path: str) -> List[Dict[str, Any]]:
|
|
215
|
+
"""解析 Claude Code 的会话文件(JSONL 格式)"""
|
|
216
|
+
messages = []
|
|
217
|
+
|
|
218
|
+
try:
|
|
219
|
+
with open(transcript_path, 'r', encoding='utf-8') as f:
|
|
220
|
+
for line in f:
|
|
221
|
+
try:
|
|
222
|
+
msg = json.loads(line.strip())
|
|
223
|
+
# 只保留 user 和 assistant 类型的消息
|
|
224
|
+
if msg and msg.get('type') in ['user', 'assistant']:
|
|
225
|
+
messages.append(msg)
|
|
226
|
+
except json.JSONDecodeError:
|
|
227
|
+
continue
|
|
228
|
+
except Exception as e:
|
|
229
|
+
print(f"Error reading transcript: {e}", file=sys.stderr)
|
|
230
|
+
return []
|
|
231
|
+
|
|
232
|
+
return messages
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def generate_markdown(messages: List[Dict[str, Any]],
|
|
236
|
+
tool_calls: List[Dict],
|
|
237
|
+
references: Set[str]) -> str:
|
|
238
|
+
"""将消息列表转换为 Markdown 格式(增强版)"""
|
|
239
|
+
if not messages:
|
|
240
|
+
return "# 对话记录\n\n无对话内容。\n"
|
|
241
|
+
|
|
242
|
+
lines = []
|
|
243
|
+
lines.append("# Claude Code 对话记录\n")
|
|
244
|
+
lines.append(f"**时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
|
|
245
|
+
|
|
246
|
+
# 添加参考资源部分
|
|
247
|
+
if references:
|
|
248
|
+
lines.append("\n## 📚 参考资源\n")
|
|
249
|
+
for ref in sorted(references):
|
|
250
|
+
lines.append(f"- {ref}\n")
|
|
251
|
+
|
|
252
|
+
# 添加工具使用统计
|
|
253
|
+
if tool_calls:
|
|
254
|
+
lines.append(f"\n**使用工具**: {len(tool_calls)} 次\n")
|
|
255
|
+
tool_counts = {}
|
|
256
|
+
for call in tool_calls:
|
|
257
|
+
name = call.get('name', 'unknown')
|
|
258
|
+
tool_counts[name] = tool_counts.get(name, 0) + 1
|
|
259
|
+
for name, count in sorted(tool_counts.items()):
|
|
260
|
+
lines.append(f" - {name}: {count} 次\n")
|
|
261
|
+
|
|
262
|
+
lines.append("\n---\n")
|
|
263
|
+
|
|
264
|
+
# 对话内容
|
|
265
|
+
for idx, msg in enumerate(messages, 1):
|
|
266
|
+
msg_type = msg.get('type', 'unknown')
|
|
267
|
+
text = format_message_content(msg)
|
|
268
|
+
if text.strip() == '':
|
|
269
|
+
continue
|
|
270
|
+
|
|
271
|
+
if msg_type == 'user':
|
|
272
|
+
lines.append(f"\n## 👤 用户提问 #{idx}\n")
|
|
273
|
+
lines.append(f"{text}\n")
|
|
274
|
+
elif msg_type == 'assistant':
|
|
275
|
+
lines.append(f"\n## 🤖 Claude 回答 #{idx}\n")
|
|
276
|
+
lines.append(f"{text}\n")
|
|
277
|
+
|
|
278
|
+
lines.append("\n---\n")
|
|
279
|
+
|
|
280
|
+
# 附录:详细的工具调用记录
|
|
281
|
+
if tool_calls:
|
|
282
|
+
lines.append("\n## 📋 附录:工具调用详情\n")
|
|
283
|
+
for idx, call in enumerate(tool_calls, 1):
|
|
284
|
+
lines.append(f"\n### 工具 {idx}: {call.get('name', 'unknown')}\n")
|
|
285
|
+
lines.append("```json\n")
|
|
286
|
+
lines.append(json.dumps(call, indent=2, ensure_ascii=False, default=str))
|
|
287
|
+
lines.append("\n```\n")
|
|
288
|
+
|
|
289
|
+
lines.append(f"\n---\n")
|
|
290
|
+
lines.append(f"*自动生成于 {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*\n")
|
|
291
|
+
|
|
292
|
+
return '\n'.join(lines)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def save_resources(references: Set[str], output_dir: Path, base_filename: str):
|
|
296
|
+
"""保存参考资源列表到单独文件"""
|
|
297
|
+
if not references:
|
|
298
|
+
return
|
|
299
|
+
|
|
300
|
+
resources_file = output_dir / f"{base_filename}_resources.txt"
|
|
301
|
+
|
|
302
|
+
try:
|
|
303
|
+
with open(resources_file, 'w', encoding='utf-8') as f:
|
|
304
|
+
f.write(f"# 参考资源\n")
|
|
305
|
+
f.write(f"# 生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
|
|
306
|
+
f.write(f"# 对话文件: {base_filename}.md\n\n")
|
|
307
|
+
|
|
308
|
+
for ref in sorted(references):
|
|
309
|
+
f.write(f"{ref}\n")
|
|
310
|
+
|
|
311
|
+
print(f"Resources saved to: {resources_file}", file=sys.stderr)
|
|
312
|
+
except Exception as e:
|
|
313
|
+
print(f"Error saving resources: {e}", file=sys.stderr)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def save_conversation(transcript_path: str, project_dir: str):
|
|
317
|
+
"""保存对话到 Markdown 文件(增强版)"""
|
|
318
|
+
try:
|
|
319
|
+
# 解析会话文件
|
|
320
|
+
messages = parse_transcript(transcript_path)
|
|
321
|
+
if not messages:
|
|
322
|
+
print("No messages to save", file=sys.stderr)
|
|
323
|
+
return
|
|
324
|
+
|
|
325
|
+
# 提取工具调用和结果
|
|
326
|
+
tool_calls = extract_tool_calls(messages)
|
|
327
|
+
tool_results = extract_tool_results(messages)
|
|
328
|
+
references = extract_references(tool_calls, tool_results)
|
|
329
|
+
|
|
330
|
+
# 提取第一个用户提问作为关键词来源
|
|
331
|
+
first_user_msg = ""
|
|
332
|
+
for msg in messages:
|
|
333
|
+
if msg.get('type') == 'user':
|
|
334
|
+
first_user_msg = format_message_content(msg)
|
|
335
|
+
if first_user_msg:
|
|
336
|
+
break
|
|
337
|
+
|
|
338
|
+
# 提取关键词
|
|
339
|
+
keywords = extract_keywords(first_user_msg) if first_user_msg else "spec"
|
|
340
|
+
|
|
341
|
+
# 生成文件名: YYmmddHH_{关键词}
|
|
342
|
+
timestamp = datetime.now().strftime('%y%m%d_%H%M%S')
|
|
343
|
+
base_filename = f"claude_log_{timestamp}_{keywords}"
|
|
344
|
+
filename = f"{base_filename}.md"
|
|
345
|
+
|
|
346
|
+
# 使用环境变量指定的目录,默认 _.claude_hist
|
|
347
|
+
hist_dir = get_hist_dir(project_dir)
|
|
348
|
+
|
|
349
|
+
# 生成 Markdown 内容
|
|
350
|
+
markdown_content = generate_markdown(messages, tool_calls, references)
|
|
351
|
+
|
|
352
|
+
# 保存主文件
|
|
353
|
+
output_file = hist_dir / filename
|
|
354
|
+
with open(output_file, 'w', encoding='utf-8') as f:
|
|
355
|
+
f.write(markdown_content)
|
|
356
|
+
|
|
357
|
+
print(f"Conversation saved to: {output_file}", file=sys.stderr)
|
|
358
|
+
|
|
359
|
+
# 保存参考资源列表
|
|
360
|
+
save_resources(references, hist_dir, base_filename)
|
|
361
|
+
|
|
362
|
+
except Exception as e:
|
|
363
|
+
print(f"Error saving conversation: {e}", file=sys.stderr)
|
|
364
|
+
import traceback
|
|
365
|
+
traceback.print_exc(file=sys.stderr)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def main():
|
|
369
|
+
"""主函数"""
|
|
370
|
+
try:
|
|
371
|
+
# Check if this hook is enabled via environment variable
|
|
372
|
+
if os.environ.get('YJ_CLAUDE_CHAT_SAVE_SPEC') != '1':
|
|
373
|
+
# Silently exit if not enabled
|
|
374
|
+
sys.exit(0)
|
|
375
|
+
|
|
376
|
+
input_data = json.load(sys.stdin)
|
|
377
|
+
hook_event = input_data.get('hook_event_name', '')
|
|
378
|
+
|
|
379
|
+
if hook_event == 'Stop':
|
|
380
|
+
transcript_path = input_data.get('transcript_path', '')
|
|
381
|
+
cwd = input_data.get('cwd', '')
|
|
382
|
+
|
|
383
|
+
if transcript_path and cwd:
|
|
384
|
+
save_conversation(transcript_path, cwd)
|
|
385
|
+
else:
|
|
386
|
+
print(f"Missing required data: transcript_path={transcript_path}, cwd={cwd}", file=sys.stderr)
|
|
387
|
+
|
|
388
|
+
sys.exit(0)
|
|
389
|
+
|
|
390
|
+
except Exception as e:
|
|
391
|
+
print(f"Hook error: {e}", file=sys.stderr)
|
|
392
|
+
import traceback
|
|
393
|
+
traceback.print_exc(file=sys.stderr)
|
|
394
|
+
sys.exit(1)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
if __name__ == '__main__':
|
|
398
|
+
main()
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# 项目开发规范
|
|
2
|
+
|
|
3
|
+
本目录包含项目的编程标准和开发规范。
|
|
4
|
+
|
|
5
|
+
## 📚 规范文档
|
|
6
|
+
|
|
7
|
+
### Go 语言规范
|
|
8
|
+
|
|
9
|
+
- **[coding-standards.md](go/coding-standards.md)** - Go 项目编程标准
|
|
10
|
+
- 注释规范(`//` vs `//;` 约定)
|
|
11
|
+
- 命名规范
|
|
12
|
+
- 代码组织
|
|
13
|
+
- 错误处理
|
|
14
|
+
- 并发编程
|
|
15
|
+
- 测试规范
|
|
16
|
+
- 性能优化
|
|
17
|
+
|
|
18
|
+
## 🛠️ 工具脚本
|
|
19
|
+
|
|
20
|
+
### Go 项目工具
|
|
21
|
+
|
|
22
|
+
1. **`check-standards.sh`** - 全面的代码标准检查
|
|
23
|
+
```bash
|
|
24
|
+
# 在项目根目录运行
|
|
25
|
+
./.claude/rules/go/check-standards.sh
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
检查项:
|
|
29
|
+
- ✅ 代码格式(gofmt)
|
|
30
|
+
- ✅ Imports 顺序(goimports)
|
|
31
|
+
- ✅ 测试通过率
|
|
32
|
+
- ✅ 测试覆盖率(≥ 80%)
|
|
33
|
+
- ✅ 竞态条件检测
|
|
34
|
+
- ✅ Linter 检查
|
|
35
|
+
- ✅ 注释规范统计
|
|
36
|
+
|
|
37
|
+
2. **`list-comments.sh`** - 列出所有非代码注释
|
|
38
|
+
```bash
|
|
39
|
+
# 在项目根目录运行
|
|
40
|
+
./.claude/rules/go/list-comments.sh
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
显示:
|
|
44
|
+
- 📋 TODO 列表
|
|
45
|
+
- 🔧 FIXME 列表
|
|
46
|
+
- ⚠️ HACK 列表
|
|
47
|
+
- ⚡ OPTIMIZE 列表
|
|
48
|
+
- 🗑️ DEPRECATED 列表
|
|
49
|
+
- 📝 NOTE 列表
|
|
50
|
+
|
|
51
|
+
## 🚀 快速开始
|
|
52
|
+
|
|
53
|
+
### 1. 安装必要工具
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# 安装 golangci-lint
|
|
57
|
+
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin
|
|
58
|
+
|
|
59
|
+
# 安装 goimports
|
|
60
|
+
go install golang.org/x/tools/cmd/goimports@latest
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 2. 配置项目
|
|
64
|
+
|
|
65
|
+
在项目根目录创建 `.golangci.yml`:
|
|
66
|
+
|
|
67
|
+
```yaml
|
|
68
|
+
linters:
|
|
69
|
+
enable:
|
|
70
|
+
- gofmt
|
|
71
|
+
- goimports
|
|
72
|
+
- govet
|
|
73
|
+
- errcheck
|
|
74
|
+
- staticcheck
|
|
75
|
+
- unused
|
|
76
|
+
- gosimple
|
|
77
|
+
- ineffassign
|
|
78
|
+
- deadcode
|
|
79
|
+
- typecheck
|
|
80
|
+
- gocyclo
|
|
81
|
+
- funlen
|
|
82
|
+
|
|
83
|
+
linters-settings:
|
|
84
|
+
gocyclo:
|
|
85
|
+
min-complexity: 10
|
|
86
|
+
funlen:
|
|
87
|
+
lines: 50
|
|
88
|
+
statements: 40
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 3. 设置 Git Hooks(可选)
|
|
92
|
+
|
|
93
|
+
创建 `.git/hooks/pre-commit`:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
#!/bin/bash
|
|
97
|
+
./.claude/rules/go/check-standards.sh
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
chmod +x .git/hooks/pre-commit
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 4. 在代码中使用注释约定
|
|
105
|
+
|
|
106
|
+
```go
|
|
107
|
+
package main
|
|
108
|
+
|
|
109
|
+
// Add 计算两个整数的和
|
|
110
|
+
// 这是标准的代码注释,使用 //
|
|
111
|
+
func Add(a, b int) int {
|
|
112
|
+
//; TODO: 添加溢出检查
|
|
113
|
+
//; NOTE: 这个函数会在 v2.0 中支持浮点数
|
|
114
|
+
return a + b
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
//; DEPRECATED: 使用 Add 函数替代
|
|
118
|
+
//; 此函数将在 v2.0 移除
|
|
119
|
+
func AddLegacy(a, b int) int {
|
|
120
|
+
return a + b
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## 📋 注释规范速查
|
|
125
|
+
|
|
126
|
+
| 前缀 | 用途 | 示例 |
|
|
127
|
+
|------|------|------|
|
|
128
|
+
| `//` | 代码功能注释 | `// CalculateTotal 计算订单总金额` |
|
|
129
|
+
| `//; TODO:` | 待实现功能 | `//; TODO: 添加参数验证` |
|
|
130
|
+
| `//; FIXME:` | 需要修复的问题 | `//; FIXME: Redis 连接超时问题` |
|
|
131
|
+
| `//; HACK:` | 临时解决方案 | `//; HACK: 等待 API v2 升级后移除` |
|
|
132
|
+
| `//; OPTIMIZE:` | 性能优化点 | `//; OPTIMIZE: 可以使用批量查询` |
|
|
133
|
+
| `//; DEPRECATED:` | 已废弃代码 | `//; DEPRECATED: 使用 NewAPI 替代` |
|
|
134
|
+
| `//; NOTE:` | 开发者备注 | `//; NOTE: 这里需要考虑并发安全` |
|
|
135
|
+
| `//; DEBUG:` | 调试信息 | `//; DEBUG: 临时日志,发布前删除` |
|
|
136
|
+
|
|
137
|
+
## 🎯 质量标准
|
|
138
|
+
|
|
139
|
+
### 强制要求(MUST)
|
|
140
|
+
|
|
141
|
+
- ✅ 代码覆盖率 ≥ 80%
|
|
142
|
+
- ✅ 无编译错误和 linter 错误
|
|
143
|
+
- ✅ 所有测试通过
|
|
144
|
+
- ✅ 无竞态条件
|
|
145
|
+
- ✅ 正确使用注释标记(`//` vs `//;`)
|
|
146
|
+
|
|
147
|
+
### 推荐要求(SHOULD)
|
|
148
|
+
|
|
149
|
+
- ⭐ 函数长度 ≤ 50 行
|
|
150
|
+
- ⭐ 圈复杂度 ≤ 10
|
|
151
|
+
- ⭐ 参数数量 ≤ 5 个
|
|
152
|
+
- ⭐ 无重复代码
|
|
153
|
+
|
|
154
|
+
## 📖 使用示例
|
|
155
|
+
|
|
156
|
+
### 示例 1:检查代码标准
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
# 运行完整检查
|
|
160
|
+
$ ./.claude/rules/go/check-standards.sh
|
|
161
|
+
|
|
162
|
+
🔍 开始检查 Go 编程标准...
|
|
163
|
+
|
|
164
|
+
📝 [1/7] 检查代码格式...
|
|
165
|
+
✓ 代码格式正确
|
|
166
|
+
|
|
167
|
+
📦 [2/7] 检查 imports 顺序...
|
|
168
|
+
✓ Imports 顺序正确
|
|
169
|
+
|
|
170
|
+
🧪 [3/7] 运行测试...
|
|
171
|
+
✓ 所有测试通过
|
|
172
|
+
|
|
173
|
+
📊 [4/7] 检查测试覆盖率...
|
|
174
|
+
✓ 测试覆盖率: 87.5% (≥ 80%)
|
|
175
|
+
|
|
176
|
+
🏃 [5/7] 检查竞态条件...
|
|
177
|
+
✓ 无竞态条件
|
|
178
|
+
|
|
179
|
+
🔍 [6/7] 运行 linter...
|
|
180
|
+
✓ Linter 检查通过
|
|
181
|
+
|
|
182
|
+
💬 [7/7] 检查注释规范...
|
|
183
|
+
- TODO: 3 项
|
|
184
|
+
- FIXME: 1 项
|
|
185
|
+
- HACK: 0 项
|
|
186
|
+
- DEPRECATED: 0 项
|
|
187
|
+
|
|
188
|
+
================================
|
|
189
|
+
✓ 所有检查通过!
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### 示例 2:查看待办事项
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
# 列出所有非代码注释
|
|
196
|
+
$ ./.claude/rules/go/list-comments.sh
|
|
197
|
+
|
|
198
|
+
💬 Go 项目非代码注释列表
|
|
199
|
+
======================================
|
|
200
|
+
|
|
201
|
+
📋 TODO 列表:
|
|
202
|
+
./service/user.go:45://; TODO: 添加邮箱验证
|
|
203
|
+
./handler/order.go:78://; TODO: 实现订单取消功能
|
|
204
|
+
|
|
205
|
+
🔧 FIXME 列表:
|
|
206
|
+
./cache/redis.go:23://; FIXME: Redis 连接偶尔超时
|
|
207
|
+
|
|
208
|
+
⚠️ HACK 列表:
|
|
209
|
+
(无)
|
|
210
|
+
|
|
211
|
+
======================================
|
|
212
|
+
📊 统计信息:
|
|
213
|
+
TODO: 2 项
|
|
214
|
+
FIXME: 1 项
|
|
215
|
+
HACK: 0 项
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## 🔧 集成到 IDE
|
|
219
|
+
|
|
220
|
+
### VS Code
|
|
221
|
+
|
|
222
|
+
创建 `.vscode/settings.json`:
|
|
223
|
+
|
|
224
|
+
```json
|
|
225
|
+
{
|
|
226
|
+
"go.lintTool": "golangci-lint",
|
|
227
|
+
"go.lintOnSave": "package",
|
|
228
|
+
"go.formatTool": "gofmt",
|
|
229
|
+
"editor.formatOnSave": true
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### GoLand / IntelliJ IDEA
|
|
234
|
+
|
|
235
|
+
1. Settings → Tools → File Watchers
|
|
236
|
+
2. 添加 gofmt 和 goimports
|
|
237
|
+
3. Settings → Editor → Inspections → Go
|
|
238
|
+
4. 启用所有推荐的检查项
|
|
239
|
+
|
|
240
|
+
## 📝 提交代码前检查清单
|
|
241
|
+
|
|
242
|
+
在提交代码前,确保:
|
|
243
|
+
|
|
244
|
+
- [ ] 运行 `check-standards.sh` 并全部通过
|
|
245
|
+
- [ ] 使用 `list-comments.sh` 检查是否有未完成的 TODO/FIXME
|
|
246
|
+
- [ ] 所有新增代码都有单元测试
|
|
247
|
+
- [ ] 所有导出的函数都有文档注释(使用 `//`)
|
|
248
|
+
- [ ] 所有 TODO、FIXME 等使用 `//;` 前缀
|
|
249
|
+
- [ ] 代码已通过 code review
|
|
250
|
+
|
|
251
|
+
## 🤝 贡献规范更新
|
|
252
|
+
|
|
253
|
+
如果需要更新或改进这些规范:
|
|
254
|
+
|
|
255
|
+
1. 修改对应的 `.md` 文件
|
|
256
|
+
2. 如有必要,更新检查脚本
|
|
257
|
+
3. 在团队中讨论并达成共识
|
|
258
|
+
4. 提交 PR 并说明修改原因
|
|
259
|
+
|
|
260
|
+
## 📚 参考资源
|
|
261
|
+
|
|
262
|
+
- [Effective Go](https://golang.org/doc/effective_go)
|
|
263
|
+
- [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments)
|
|
264
|
+
- [Uber Go Style Guide](https://github.com/uber-go/guide)
|
|
265
|
+
- [Google Go Style Guide](https://google.github.io/styleguide/go/)
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
**维护者**: 开发团队
|
|
270
|
+
**最后更新**: 2026-01-06
|