@haaaiawd/anws 2.0.4 → 2.0.5
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/package.json +1 -1
- package/templates/.agents/skills/nexus-query/SKILL.md +114 -0
- package/templates/.agents/skills/nexus-query/scripts/extract_ast.py +706 -0
- package/templates/.agents/skills/nexus-query/scripts/git_detective.py +194 -0
- package/templates/.agents/skills/nexus-query/scripts/languages.json +127 -0
- package/templates/.agents/skills/nexus-query/scripts/query_graph.py +556 -0
- package/templates/.agents/skills/nexus-query/scripts/requirements.txt +6 -0
- package/templates/.agents/skills/runtime-inspector/SKILL.md +8 -2
- package/templates/.agents/skills/sequential-thinking/SKILL.md +44 -7
- package/templates/.agents/skills/task-planner/SKILL.md +25 -1
- package/templates/.agents/skills/task-planner/references/TASK_TEMPLATE.md +25 -6
- package/templates/.agents/workflows/blueprint.md +9 -1
- package/templates/.agents/workflows/challenge.md +7 -6
- package/templates/.agents/workflows/design-system.md +14 -5
- package/templates/.agents/workflows/explore.md +42 -8
- package/templates/.agents/workflows/forge.md +6 -1
- package/templates/.agents/workflows/probe.md +105 -35
- package/templates/.agents/workflows/quickstart.md +2 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
git_detective.py — Git 历史热点与文件逻辑耦合分析器
|
|
4
|
+
|
|
5
|
+
用途:分析 Git 仓库的变更历史,识别热点文件和文件逻辑耦合对
|
|
6
|
+
用法:python git_detective.py <repo_path> [--days 90] [--top-n 20]
|
|
7
|
+
|
|
8
|
+
方法论:Adam Tornhill「Your Code as a Crime Scene」
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
import json
|
|
13
|
+
import argparse
|
|
14
|
+
import subprocess
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from collections import Counter
|
|
17
|
+
from itertools import combinations
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def run_git(repo_path: Path, args: list[str]) -> str:
|
|
21
|
+
"""运行 git 命令,返回 stdout。失败时抛出 RuntimeError。"""
|
|
22
|
+
cmd = ['git', '-C', str(repo_path)] + args
|
|
23
|
+
result = subprocess.run(
|
|
24
|
+
cmd,
|
|
25
|
+
capture_output=True,
|
|
26
|
+
text=True,
|
|
27
|
+
encoding='utf-8',
|
|
28
|
+
errors='replace',
|
|
29
|
+
)
|
|
30
|
+
if result.returncode != 0:
|
|
31
|
+
raise RuntimeError(
|
|
32
|
+
f"git command failed: {' '.join(cmd)}\n{result.stderr.strip()}"
|
|
33
|
+
)
|
|
34
|
+
return result.stdout
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_commit_file_changes(repo_path: Path, days: int) -> list[list[str]]:
|
|
38
|
+
"""
|
|
39
|
+
返回分析窗口内每次 commit 修改的文件列表。
|
|
40
|
+
|
|
41
|
+
输出格式: [[file1, file2], [file3], ...]
|
|
42
|
+
使用 COMMIT:<hash> 前缀格式确保解析稳定。
|
|
43
|
+
"""
|
|
44
|
+
output = run_git(repo_path, [
|
|
45
|
+
'log',
|
|
46
|
+
f'--since={days} days ago',
|
|
47
|
+
'--pretty=format:COMMIT:%H',
|
|
48
|
+
'--name-only',
|
|
49
|
+
])
|
|
50
|
+
|
|
51
|
+
commits: list[list[str]] = []
|
|
52
|
+
current_files: list[str] = []
|
|
53
|
+
|
|
54
|
+
for line in output.splitlines():
|
|
55
|
+
line = line.strip()
|
|
56
|
+
if not line:
|
|
57
|
+
continue
|
|
58
|
+
if line.startswith('COMMIT:'):
|
|
59
|
+
if current_files:
|
|
60
|
+
commits.append(current_files)
|
|
61
|
+
current_files = []
|
|
62
|
+
else:
|
|
63
|
+
current_files.append(line)
|
|
64
|
+
|
|
65
|
+
if current_files:
|
|
66
|
+
commits.append(current_files)
|
|
67
|
+
|
|
68
|
+
return commits
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def compute_hotspots(commits: list[list[str]], top_n: int) -> list[dict]:
|
|
72
|
+
"""
|
|
73
|
+
计算文件变更频率热点,按 changes 降序排列。
|
|
74
|
+
|
|
75
|
+
风险阈值(Adam Tornhill 方法论):
|
|
76
|
+
low: changes < 5
|
|
77
|
+
medium: 5 <= changes < 15
|
|
78
|
+
high: changes >= 15
|
|
79
|
+
"""
|
|
80
|
+
counter: Counter[str] = Counter()
|
|
81
|
+
for files in commits:
|
|
82
|
+
counter.update(files)
|
|
83
|
+
|
|
84
|
+
results = []
|
|
85
|
+
for path, changes in counter.most_common(top_n):
|
|
86
|
+
if changes < 5:
|
|
87
|
+
risk = 'low'
|
|
88
|
+
elif changes < 15:
|
|
89
|
+
risk = 'medium'
|
|
90
|
+
else:
|
|
91
|
+
risk = 'high'
|
|
92
|
+
results.append({'path': path, 'changes': changes, 'risk': risk})
|
|
93
|
+
|
|
94
|
+
return results
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def compute_coupling_pairs(commits: list[list[str]], top_n: int) -> list[dict]:
|
|
98
|
+
"""
|
|
99
|
+
计算文件逻辑耦合对:在同一 commit 中共同修改的文件对。
|
|
100
|
+
|
|
101
|
+
coupling_score = co_changes / min(total_changes_A, total_changes_B)
|
|
102
|
+
过滤:co_changes < 2 的对不输出(噪声过多)。
|
|
103
|
+
"""
|
|
104
|
+
pair_counter: Counter[tuple[str, str]] = Counter()
|
|
105
|
+
file_counter: Counter[str] = Counter()
|
|
106
|
+
|
|
107
|
+
for files in commits:
|
|
108
|
+
unique_files = list(dict.fromkeys(files)) # 去重,保持顺序
|
|
109
|
+
file_counter.update(unique_files)
|
|
110
|
+
if len(unique_files) >= 2:
|
|
111
|
+
for a, b in combinations(sorted(unique_files), 2):
|
|
112
|
+
pair_counter[(a, b)] += 1
|
|
113
|
+
|
|
114
|
+
results = []
|
|
115
|
+
for (file_a, file_b), co_changes in pair_counter.most_common():
|
|
116
|
+
if co_changes < 2:
|
|
117
|
+
continue
|
|
118
|
+
min_changes = min(file_counter[file_a], file_counter[file_b])
|
|
119
|
+
score = round(co_changes / min_changes, 3) if min_changes > 0 else 0.0
|
|
120
|
+
results.append({
|
|
121
|
+
'file_a': file_a,
|
|
122
|
+
'file_b': file_b,
|
|
123
|
+
'co_changes': co_changes,
|
|
124
|
+
'coupling_score': score,
|
|
125
|
+
})
|
|
126
|
+
if len(results) >= top_n:
|
|
127
|
+
break
|
|
128
|
+
|
|
129
|
+
return results
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def get_repo_stats(repo_path: Path, days: int) -> dict:
|
|
133
|
+
"""获取分析窗口内的 commit 数量和作者数量。"""
|
|
134
|
+
try:
|
|
135
|
+
commit_out = run_git(repo_path, [
|
|
136
|
+
'log', f'--since={days} days ago', '--pretty=format:%H',
|
|
137
|
+
])
|
|
138
|
+
total_commits = sum(1 for line in commit_out.splitlines() if line.strip())
|
|
139
|
+
|
|
140
|
+
author_out = run_git(repo_path, [
|
|
141
|
+
'log', f'--since={days} days ago', '--pretty=format:%ae',
|
|
142
|
+
])
|
|
143
|
+
total_authors = len({line.strip() for line in author_out.splitlines() if line.strip()})
|
|
144
|
+
except RuntimeError:
|
|
145
|
+
total_commits = 0
|
|
146
|
+
total_authors = 0
|
|
147
|
+
|
|
148
|
+
return {'total_commits': total_commits, 'total_authors': total_authors}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def main() -> None:
|
|
152
|
+
parser = argparse.ArgumentParser(
|
|
153
|
+
description='Analyze Git history for hotspots and coupling'
|
|
154
|
+
)
|
|
155
|
+
parser.add_argument('repo_path', help='Target repository path')
|
|
156
|
+
parser.add_argument('--days', type=int, default=90,
|
|
157
|
+
help='Analysis window in days (default: 90)')
|
|
158
|
+
parser.add_argument('--top-n', type=int, default=20,
|
|
159
|
+
help='Max items in hotspots/coupling output (default: 20)')
|
|
160
|
+
args = parser.parse_args()
|
|
161
|
+
|
|
162
|
+
repo_path = Path(args.repo_path).resolve()
|
|
163
|
+
if not repo_path.exists():
|
|
164
|
+
sys.stderr.write(f"[ERROR] repo_path not found: {repo_path}\n")
|
|
165
|
+
sys.exit(1)
|
|
166
|
+
if not (repo_path / '.git').exists():
|
|
167
|
+
sys.stderr.write(
|
|
168
|
+
f"[ERROR] .git not found in {repo_path}. "
|
|
169
|
+
"This tool requires a git repository.\n"
|
|
170
|
+
)
|
|
171
|
+
sys.exit(1)
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
commits = get_commit_file_changes(repo_path, args.days)
|
|
175
|
+
except RuntimeError as e:
|
|
176
|
+
sys.stderr.write(f"[ERROR] Git command failed: {e}\n")
|
|
177
|
+
sys.exit(1)
|
|
178
|
+
|
|
179
|
+
stats = get_repo_stats(repo_path, args.days)
|
|
180
|
+
hotspots = compute_hotspots(commits, args.top_n)
|
|
181
|
+
coupling_pairs = compute_coupling_pairs(commits, args.top_n)
|
|
182
|
+
|
|
183
|
+
result = {
|
|
184
|
+
'analysis_period_days': args.days,
|
|
185
|
+
'stats': stats,
|
|
186
|
+
'hotspots': hotspots,
|
|
187
|
+
'coupling_pairs': coupling_pairs,
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
if __name__ == '__main__':
|
|
194
|
+
main()
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extensions": {
|
|
3
|
+
".py": "python",
|
|
4
|
+
".pyw": "python",
|
|
5
|
+
".pyi": "python",
|
|
6
|
+
".js": "javascript",
|
|
7
|
+
".mjs": "javascript",
|
|
8
|
+
".cjs": "javascript",
|
|
9
|
+
".jsx": "javascript",
|
|
10
|
+
".ts": "typescript",
|
|
11
|
+
".mts": "typescript",
|
|
12
|
+
".tsx": "tsx",
|
|
13
|
+
".sh": "bash",
|
|
14
|
+
".bash": "bash",
|
|
15
|
+
".zsh": "bash",
|
|
16
|
+
".java": "java",
|
|
17
|
+
".go": "go",
|
|
18
|
+
".rs": "rust",
|
|
19
|
+
".cs": "csharp",
|
|
20
|
+
".c": "c",
|
|
21
|
+
".h": "c",
|
|
22
|
+
".cpp": "cpp",
|
|
23
|
+
".cc": "cpp",
|
|
24
|
+
".cxx": "cpp",
|
|
25
|
+
".hpp": "cpp",
|
|
26
|
+
".hxx": "cpp",
|
|
27
|
+
".kt": "kotlin",
|
|
28
|
+
".kts": "kotlin",
|
|
29
|
+
".rb": "ruby",
|
|
30
|
+
".php": "php",
|
|
31
|
+
".lua": "lua",
|
|
32
|
+
".swift": "swift",
|
|
33
|
+
".scala": "scala",
|
|
34
|
+
".sc": "scala",
|
|
35
|
+
".ex": "elixir",
|
|
36
|
+
".exs": "elixir",
|
|
37
|
+
".gd": "gdscript",
|
|
38
|
+
".dart": "dart",
|
|
39
|
+
".hs": "haskell",
|
|
40
|
+
".clj": "clojure",
|
|
41
|
+
".cljs": "clojure",
|
|
42
|
+
".cljc": "clojure",
|
|
43
|
+
".sql": "sql",
|
|
44
|
+
".proto": "proto",
|
|
45
|
+
".sol": "solidity",
|
|
46
|
+
".vue": "vue",
|
|
47
|
+
".svelte": "svelte",
|
|
48
|
+
".r": "r",
|
|
49
|
+
".pl": "perl",
|
|
50
|
+
".pm": "perl"
|
|
51
|
+
},
|
|
52
|
+
"queries": {
|
|
53
|
+
"python": {
|
|
54
|
+
"struct": "(class_definition name: (identifier) @class.name) @class.def\n(function_definition name: (identifier) @func.name) @func.def",
|
|
55
|
+
"imports": "(import_statement name: (dotted_name) @mod)\n(import_from_statement module_name: (dotted_name) @mod)"
|
|
56
|
+
},
|
|
57
|
+
"javascript": {
|
|
58
|
+
"struct": "(class_declaration name: (identifier) @class.name) @class.def\n(function_declaration name: (identifier) @func.name) @func.def\n(method_definition name: (property_identifier) @func.name) @func.def",
|
|
59
|
+
"imports": "(import_statement source: (string (string_fragment) @mod))"
|
|
60
|
+
},
|
|
61
|
+
"typescript": {
|
|
62
|
+
"struct": "(class_declaration name: (type_identifier) @class.name) @class.def\n(function_declaration name: (identifier) @func.name) @func.def\n(method_definition name: (property_identifier) @func.name) @func.def",
|
|
63
|
+
"imports": "(import_statement source: (string (string_fragment) @mod))"
|
|
64
|
+
},
|
|
65
|
+
"tsx": {
|
|
66
|
+
"struct": "(class_declaration name: (type_identifier) @class.name) @class.def\n(function_declaration name: (identifier) @func.name) @func.def\n(method_definition name: (property_identifier) @func.name) @func.def",
|
|
67
|
+
"imports": "(import_statement source: (string (string_fragment) @mod))"
|
|
68
|
+
},
|
|
69
|
+
"java": {
|
|
70
|
+
"struct": "(class_declaration name: (identifier) @class.name) @class.def\n(method_declaration name: (identifier) @func.name) @func.def\n(interface_declaration name: (identifier) @class.name) @class.def",
|
|
71
|
+
"imports": "(import_declaration (scoped_identifier) @mod)"
|
|
72
|
+
},
|
|
73
|
+
"go": {
|
|
74
|
+
"struct": "(type_declaration (type_spec name: (type_identifier) @class.name)) @class.def\n(function_declaration name: (identifier) @func.name) @func.def\n(method_declaration name: (field_identifier) @func.name) @func.def",
|
|
75
|
+
"imports": "(import_spec path: (interpreted_string_literal) @mod)"
|
|
76
|
+
},
|
|
77
|
+
"rust": {
|
|
78
|
+
"struct": "(struct_item name: (type_identifier) @class.name) @class.def\n(enum_item name: (type_identifier) @class.name) @class.def\n(function_item name: (identifier) @func.name) @func.def",
|
|
79
|
+
"imports": "(use_declaration argument: (scoped_identifier) @mod)\n(use_declaration argument: (identifier) @mod)"
|
|
80
|
+
},
|
|
81
|
+
"csharp": {
|
|
82
|
+
"struct": "(class_declaration name: (identifier) @class.name) @class.def\n(method_declaration name: (identifier) @func.name) @func.def\n(interface_declaration name: (identifier) @class.name) @class.def",
|
|
83
|
+
"imports": "(using_directive (qualified_name) @mod)\n(using_directive (identifier) @mod)"
|
|
84
|
+
},
|
|
85
|
+
"cpp": {
|
|
86
|
+
"struct": "(class_specifier name: (type_identifier) @class.name) @class.def\n(function_definition\n declarator: (function_declarator\n declarator: (identifier) @func.name)) @func.def",
|
|
87
|
+
"imports": "(preproc_include path: (system_lib_string) @mod)\n(preproc_include path: (string_literal) @mod)"
|
|
88
|
+
},
|
|
89
|
+
"c": {
|
|
90
|
+
"struct": "(struct_specifier name: (type_identifier) @class.name) @class.def\n(function_definition\n declarator: (function_declarator\n declarator: (identifier) @func.name)) @func.def",
|
|
91
|
+
"imports": "(preproc_include path: (system_lib_string) @mod)\n(preproc_include path: (string_literal) @mod)"
|
|
92
|
+
},
|
|
93
|
+
"kotlin": {
|
|
94
|
+
"struct": "(class_declaration (type_identifier) @class.name) @class.def\n(function_declaration (simple_identifier) @func.name) @func.def",
|
|
95
|
+
"imports": "(import_header (identifier) @mod)"
|
|
96
|
+
},
|
|
97
|
+
"ruby": {
|
|
98
|
+
"struct": "(class name: (constant) @class.name) @class.def\n(method name: (identifier) @func.name) @func.def",
|
|
99
|
+
"imports": ""
|
|
100
|
+
},
|
|
101
|
+
"swift": {
|
|
102
|
+
"struct": "(class_declaration name: (type_identifier) @class.name) @class.def\n(function_declaration name: (simple_identifier) @func.name) @func.def",
|
|
103
|
+
"imports": "(import_declaration (identifier) @mod)"
|
|
104
|
+
},
|
|
105
|
+
"scala": {
|
|
106
|
+
"struct": "(class_definition name: (identifier) @class.name) @class.def\n(function_definition name: (identifier) @func.name) @func.def",
|
|
107
|
+
"imports": "(import_declaration importees: (import_selectors selector: (import_selector name: (identifier) @mod)))"
|
|
108
|
+
},
|
|
109
|
+
"lua": {
|
|
110
|
+
"struct": "(function_declaration name: (identifier) @func.name) @func.def",
|
|
111
|
+
"imports": ""
|
|
112
|
+
},
|
|
113
|
+
"php": {
|
|
114
|
+
"struct": "(class_declaration name: (name) @class.name) @class.def\n(method_declaration name: (name) @func.name) @func.def\n(function_definition name: (name) @func.name) @func.def",
|
|
115
|
+
"imports": "(namespace_use_declaration (namespace_use_clause (qualified_name (name) @mod)))"
|
|
116
|
+
},
|
|
117
|
+
"elixir": {
|
|
118
|
+
"struct": "(call target: (identifier) @_keyword\n arguments: (arguments (alias) @class.name)\n (#match? @_keyword \"^(defmodule|defprotocol)$\")) @class.def\n(call target: (identifier) @_keyword\n arguments: (arguments (identifier) @func.name)\n (#match? @_keyword \"^(def|defp)$\")) @func.def",
|
|
119
|
+
"imports": ""
|
|
120
|
+
},
|
|
121
|
+
"gdscript": {
|
|
122
|
+
"struct": "(class_name_statement name: (name) @class.name) @class.def\n(function_definition name: (name) @func.name) @func.def",
|
|
123
|
+
"imports": ""
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
"unsupported_extensions": {}
|
|
127
|
+
}
|