teamai-cli 0.16.8 → 0.17.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/CHANGELOG.md +6 -0
- package/README.md +229 -29
- package/README.zh-CN.md +226 -26
- package/agents/teamai-recall.md +81 -24
- package/dist/index.js +18421 -12324
- package/package.json +3 -1
- package/skills/team-wiki-codebase/README.md +120 -0
- package/skills/team-wiki-codebase/SKILL.md +909 -0
- package/skills/team-wiki-codebase/references/agents/graph-rag-agent.md +344 -0
- package/skills/team-wiki-codebase/references/agents/kb-doc-generator.md +323 -0
- package/skills/team-wiki-codebase/references/methodology/phase0-collection.md +54 -0
- package/skills/team-wiki-codebase/references/methodology/phase1-reverse-engineering.md +89 -0
- package/skills/team-wiki-codebase/references/methodology/phase2-document-types.md +341 -0
- package/skills/team-wiki-codebase/references/methodology/phase3-ai-enhancement.md +164 -0
- package/skills/team-wiki-codebase/references/methodology/phase4-quality.md +232 -0
- package/skills/team-wiki-codebase/references/templates/project-overview.md +148 -0
- package/skills/team-wiki-codebase/scripts/scan_repo.py +224 -0
- package/skills/team-wiki-codebase/scripts/validate_kb.py +250 -0
- package/skills/teamai-wiki/SKILL.md +0 -1019
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
validate_kb.py — 知识库质量校验工具
|
|
4
|
+
|
|
5
|
+
用途: Phase 4 质量评估阶段,自动校验已生成知识库的:
|
|
6
|
+
1. 链接完整性(检测死链接)
|
|
7
|
+
2. search-anchor 覆盖率
|
|
8
|
+
3. AI 快速理解表覆盖率
|
|
9
|
+
4. 双向链接完整性
|
|
10
|
+
5. README 索引收录率
|
|
11
|
+
|
|
12
|
+
使用方式:
|
|
13
|
+
python3 validate_kb.py /path/to/knowledge-base-dir
|
|
14
|
+
python3 validate_kb.py /path/to/knowledge-base-dir --verbose
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import re
|
|
19
|
+
import sys
|
|
20
|
+
import argparse
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from collections import defaultdict
|
|
23
|
+
|
|
24
|
+
# Markdown 链接正则: [text](path) 或 [text](path#anchor)
|
|
25
|
+
LINK_PATTERN = re.compile(r'\[([^\]]*)\]\(([^)]+)\)')
|
|
26
|
+
# search-anchor 正则
|
|
27
|
+
ANCHOR_PATTERN = re.compile(r'<!--\s*search-anchor\s*:(.*?)-->', re.DOTALL)
|
|
28
|
+
# AI 快速理解表正则
|
|
29
|
+
AI_TABLE_PATTERN = re.compile(r'##\s*🤖\s*AI\s*快速理解', re.IGNORECASE)
|
|
30
|
+
# 双向链接: 链接回主架构/技术架构文档
|
|
31
|
+
BACK_LINK_PATTERN = re.compile(r'\[📘.*(?:主架构|技术架构)|在整体架构中的位置', re.IGNORECASE)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def find_md_files(kb_dir: Path) -> list:
|
|
35
|
+
"""查找所有 .md 文件"""
|
|
36
|
+
md_files = []
|
|
37
|
+
for root, dirs, files in os.walk(kb_dir):
|
|
38
|
+
dirs[:] = [d for d in dirs if not d.startswith('.')]
|
|
39
|
+
for f in files:
|
|
40
|
+
if f.endswith('.md'):
|
|
41
|
+
md_files.append(Path(root) / f)
|
|
42
|
+
return sorted(md_files)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def check_links(md_file: Path, kb_dir: Path) -> list:
|
|
46
|
+
"""检查文件中的链接是否有效"""
|
|
47
|
+
broken = []
|
|
48
|
+
try:
|
|
49
|
+
content = md_file.read_text(encoding='utf-8', errors='ignore')
|
|
50
|
+
except OSError:
|
|
51
|
+
return [("READ_ERROR", str(md_file), "无法读取文件")]
|
|
52
|
+
|
|
53
|
+
for match in LINK_PATTERN.finditer(content):
|
|
54
|
+
link_text = match.group(1)
|
|
55
|
+
link_target = match.group(2)
|
|
56
|
+
|
|
57
|
+
# 跳过外部链接和锚点链接
|
|
58
|
+
if link_target.startswith(('http://', 'https://', 'mailto:', '#')):
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
# 分离路径和锚点
|
|
62
|
+
path_part = link_target.split('#')[0]
|
|
63
|
+
if not path_part:
|
|
64
|
+
continue
|
|
65
|
+
|
|
66
|
+
# 解析相对路径
|
|
67
|
+
target_path = (md_file.parent / path_part).resolve()
|
|
68
|
+
if not target_path.exists():
|
|
69
|
+
rel = str(md_file.relative_to(kb_dir))
|
|
70
|
+
broken.append((rel, link_target, link_text))
|
|
71
|
+
|
|
72
|
+
return broken
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def check_anchor(md_file: Path) -> bool:
|
|
76
|
+
"""检查文件是否包含 search-anchor"""
|
|
77
|
+
try:
|
|
78
|
+
content = md_file.read_text(encoding='utf-8', errors='ignore')
|
|
79
|
+
return bool(ANCHOR_PATTERN.search(content))
|
|
80
|
+
except OSError:
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def check_ai_table(md_file: Path) -> bool:
|
|
85
|
+
"""检查文件是否包含 AI 快速理解表"""
|
|
86
|
+
try:
|
|
87
|
+
content = md_file.read_text(encoding='utf-8', errors='ignore')
|
|
88
|
+
return bool(AI_TABLE_PATTERN.search(content))
|
|
89
|
+
except OSError:
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def check_back_link(md_file: Path) -> bool:
|
|
94
|
+
"""检查组件文档是否有链接回主架构文档"""
|
|
95
|
+
try:
|
|
96
|
+
content = md_file.read_text(encoding='utf-8', errors='ignore')
|
|
97
|
+
return bool(BACK_LINK_PATTERN.search(content))
|
|
98
|
+
except OSError:
|
|
99
|
+
return False
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def check_readme_coverage(kb_dir: Path, md_files: list) -> tuple:
|
|
103
|
+
"""检查 README 是否收录了所有 .md 文件"""
|
|
104
|
+
readme_path = kb_dir / "README.md"
|
|
105
|
+
if not readme_path.exists():
|
|
106
|
+
return [], md_files
|
|
107
|
+
|
|
108
|
+
readme_content = readme_path.read_text(encoding='utf-8', errors='ignore')
|
|
109
|
+
covered = []
|
|
110
|
+
uncovered = []
|
|
111
|
+
|
|
112
|
+
for f in md_files:
|
|
113
|
+
if f.name == "README.md":
|
|
114
|
+
continue
|
|
115
|
+
# 检查 README 中是否提到了这个文件
|
|
116
|
+
fname_no_ext = f.stem
|
|
117
|
+
if fname_no_ext in readme_content or f.name in readme_content:
|
|
118
|
+
covered.append(f)
|
|
119
|
+
else:
|
|
120
|
+
uncovered.append(f)
|
|
121
|
+
|
|
122
|
+
return covered, uncovered
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def main():
|
|
126
|
+
parser = argparse.ArgumentParser(description="知识库质量校验工具")
|
|
127
|
+
parser.add_argument("kb_dir", help="知识库目录路径")
|
|
128
|
+
parser.add_argument("--verbose", "-v", action="store_true", help="输出详细信息")
|
|
129
|
+
args = parser.parse_args()
|
|
130
|
+
|
|
131
|
+
kb_dir = Path(args.kb_dir).resolve()
|
|
132
|
+
if not kb_dir.is_dir():
|
|
133
|
+
print(f"错误: {kb_dir} 不是有效目录", file=sys.stderr)
|
|
134
|
+
sys.exit(1)
|
|
135
|
+
|
|
136
|
+
md_files = find_md_files(kb_dir)
|
|
137
|
+
if not md_files:
|
|
138
|
+
print(f"警告: {kb_dir} 中未找到任何 .md 文件")
|
|
139
|
+
sys.exit(0)
|
|
140
|
+
|
|
141
|
+
# 过滤出组件设计文档(以数字编号开头的文件)
|
|
142
|
+
component_docs = [f for f in md_files if re.match(r'^\d+_', f.name)]
|
|
143
|
+
|
|
144
|
+
print("=" * 70)
|
|
145
|
+
print(f" 知识库质量校验报告")
|
|
146
|
+
print(f" 目录: {kb_dir}")
|
|
147
|
+
print(f" 文件数: {len(md_files)} 个 .md 文件 (其中 {len(component_docs)} 个组件文档)")
|
|
148
|
+
print("=" * 70)
|
|
149
|
+
|
|
150
|
+
total_score = 0
|
|
151
|
+
max_score = 0
|
|
152
|
+
|
|
153
|
+
# 1. 链接完整性
|
|
154
|
+
print(f"\n## 1. 链接完整性检查\n")
|
|
155
|
+
all_broken = []
|
|
156
|
+
for f in md_files:
|
|
157
|
+
broken = check_links(f, kb_dir)
|
|
158
|
+
all_broken.extend(broken)
|
|
159
|
+
|
|
160
|
+
if all_broken:
|
|
161
|
+
print(f"❌ 发现 {len(all_broken)} 个死链接:")
|
|
162
|
+
for src, target, text in all_broken[:20]:
|
|
163
|
+
print(f" {src} → [{text}]({target})")
|
|
164
|
+
if len(all_broken) > 20:
|
|
165
|
+
print(f" ... 还有 {len(all_broken) - 20} 个")
|
|
166
|
+
else:
|
|
167
|
+
print(f"✅ 所有链接有效 (检查了 {len(md_files)} 个文件)")
|
|
168
|
+
total_score += 20
|
|
169
|
+
max_score += 20
|
|
170
|
+
|
|
171
|
+
# 2. search-anchor 覆盖率
|
|
172
|
+
print(f"\n## 2. Search-Anchor 覆盖率\n")
|
|
173
|
+
has_anchor = sum(1 for f in md_files if check_anchor(f))
|
|
174
|
+
anchor_pct = has_anchor / len(md_files) * 100 if md_files else 0
|
|
175
|
+
print(f"{'✅' if anchor_pct >= 80 else '⚠️'} {has_anchor}/{len(md_files)} 个文件有 search-anchor ({anchor_pct:.0f}%)")
|
|
176
|
+
if args.verbose:
|
|
177
|
+
for f in md_files:
|
|
178
|
+
if not check_anchor(f):
|
|
179
|
+
print(f" 缺失: {f.relative_to(kb_dir)}")
|
|
180
|
+
if anchor_pct >= 80:
|
|
181
|
+
total_score += 20
|
|
182
|
+
elif anchor_pct >= 50:
|
|
183
|
+
total_score += 10
|
|
184
|
+
max_score += 20
|
|
185
|
+
|
|
186
|
+
# 3. AI 快速理解表覆盖率(仅检查组件文档)
|
|
187
|
+
print(f"\n## 3. AI 快速理解表覆盖率 (组件文档)\n")
|
|
188
|
+
if component_docs:
|
|
189
|
+
has_ai_table = sum(1 for f in component_docs if check_ai_table(f))
|
|
190
|
+
ai_pct = has_ai_table / len(component_docs) * 100
|
|
191
|
+
print(f"{'✅' if ai_pct >= 90 else '⚠️'} {has_ai_table}/{len(component_docs)} 个组件文档有 AI 快速理解表 ({ai_pct:.0f}%)")
|
|
192
|
+
if args.verbose:
|
|
193
|
+
for f in component_docs:
|
|
194
|
+
if not check_ai_table(f):
|
|
195
|
+
print(f" 缺失: {f.relative_to(kb_dir)}")
|
|
196
|
+
if ai_pct >= 90:
|
|
197
|
+
total_score += 20
|
|
198
|
+
elif ai_pct >= 60:
|
|
199
|
+
total_score += 10
|
|
200
|
+
else:
|
|
201
|
+
print("⚠️ 未发现编号开头的组件文档")
|
|
202
|
+
max_score += 20
|
|
203
|
+
|
|
204
|
+
# 4. 双向链接检查(组件文档是否链接回主架构)
|
|
205
|
+
print(f"\n## 4. 双向链接检查 (组件→主架构)\n")
|
|
206
|
+
if component_docs:
|
|
207
|
+
has_back = sum(1 for f in component_docs if check_back_link(f))
|
|
208
|
+
back_pct = has_back / len(component_docs) * 100
|
|
209
|
+
print(f"{'✅' if back_pct >= 90 else '⚠️'} {has_back}/{len(component_docs)} 个组件文档有回链到主架构 ({back_pct:.0f}%)")
|
|
210
|
+
if back_pct >= 90:
|
|
211
|
+
total_score += 20
|
|
212
|
+
elif back_pct >= 60:
|
|
213
|
+
total_score += 10
|
|
214
|
+
else:
|
|
215
|
+
print("⚠️ 未发现编号开头的组件文档")
|
|
216
|
+
max_score += 20
|
|
217
|
+
|
|
218
|
+
# 5. README 索引覆盖率
|
|
219
|
+
print(f"\n## 5. README 索引覆盖率\n")
|
|
220
|
+
covered, uncovered = check_readme_coverage(kb_dir, md_files)
|
|
221
|
+
if (kb_dir / "README.md").exists():
|
|
222
|
+
cover_pct = len(covered) / (len(covered) + len(uncovered)) * 100 if (covered or uncovered) else 100
|
|
223
|
+
print(f"{'✅' if cover_pct >= 90 else '⚠️'} README 收录了 {len(covered)}/{len(covered)+len(uncovered)} 个文档 ({cover_pct:.0f}%)")
|
|
224
|
+
if uncovered and args.verbose:
|
|
225
|
+
print(" 未收录:")
|
|
226
|
+
for f in uncovered[:10]:
|
|
227
|
+
print(f" {f.relative_to(kb_dir)}")
|
|
228
|
+
if cover_pct >= 90:
|
|
229
|
+
total_score += 20
|
|
230
|
+
elif cover_pct >= 60:
|
|
231
|
+
total_score += 10
|
|
232
|
+
else:
|
|
233
|
+
print("❌ 未找到 README.md")
|
|
234
|
+
max_score += 20
|
|
235
|
+
|
|
236
|
+
# 总结
|
|
237
|
+
final_pct = total_score / max_score * 100 if max_score else 0
|
|
238
|
+
print(f"\n{'=' * 70}")
|
|
239
|
+
print(f" 综合评分: {total_score}/{max_score} ({final_pct:.0f}%)")
|
|
240
|
+
if final_pct >= 90:
|
|
241
|
+
print(f" 评级: ✅ 优秀 — 知识库质量达标")
|
|
242
|
+
elif final_pct >= 70:
|
|
243
|
+
print(f" 评级: ⚠️ 良好 — 建议修复上述问题")
|
|
244
|
+
else:
|
|
245
|
+
print(f" 评级: ❌ 需改进 — 存在较多质量问题")
|
|
246
|
+
print(f"{'=' * 70}")
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
if __name__ == "__main__":
|
|
250
|
+
main()
|