feat: 新增 3 个工程化自动化插件
- auto_engineer: lint/format/test/build/deps/info/init - git_workflow: 规范提交/changelog/变更摘要/贡献者统计 - project_analyzer: 复杂度/依赖/重复/安全/质量指标/改进建议 工具总数: 17 个 本次更新由 mimo 自动化执行,与开发者无关。
This commit is contained in:
1 parent
8a40f2d82f
commit
b1b2097311
4 files changed
+1074
-1
No files matched your search
@@ -0,0 +1,284 @@
|
||||
"""
|
||||
Git 工作流自动化 - commit 规范、分支管理、PR 生成
|
||||
"""
|
||||
import os
|
||||
import subprocess
|
||||
import re
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
class Plugin:
|
||||
"""Git 工作流自动化 - 规范提交、分支管理、变更摘要"""
|
||||
|
||||
def __init__(self):
|
||||
self.usage = """Git 工作流自动化
|
||||
|
||||
操作类型:
|
||||
commit [消息] - 智能提交 (自动 add + 规范化消息)
|
||||
log [数量] - 格式化提交历史
|
||||
changelog [版本] - 生成 CHANGELOG
|
||||
summary - 变更摘要 (本次未提交的改动)
|
||||
blame [文件] - 逐行追溯
|
||||
contributors - 贡献者统计
|
||||
stale [天数] - 查找长期未更新的文件
|
||||
hooks - 管理 git hooks
|
||||
"""
|
||||
self.cli = None
|
||||
|
||||
def set_cli(self, cli):
|
||||
self.cli = cli
|
||||
|
||||
def get_tool_info(self):
|
||||
return {
|
||||
"name": "git_workflow",
|
||||
"description": "Git 工作流自动化 - 规范提交、变更摘要、贡献者统计",
|
||||
"keywords": ["git", "commit", "changelog", "提交", "变更", "工作流"],
|
||||
"usage": self.usage
|
||||
}
|
||||
|
||||
def get_mcp_definition(self):
|
||||
return {
|
||||
"name": "git_workflow",
|
||||
"description": "Git 工作流自动化工具",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"operation": {
|
||||
"type": "string",
|
||||
"enum": ["commit", "log", "changelog", "summary", "blame", "contributors", "stale", "hooks"],
|
||||
"description": "操作类型"
|
||||
},
|
||||
"args": {
|
||||
"type": "string",
|
||||
"description": "操作参数"
|
||||
}
|
||||
},
|
||||
"required": ["operation"]
|
||||
}
|
||||
}
|
||||
|
||||
def convert_mcp_args(self, arguments):
|
||||
op = arguments.get("operation", "")
|
||||
args = arguments.get("args", "")
|
||||
return f"{op} {args}".strip()
|
||||
|
||||
def _run(self, cmd: str, cwd: str = None) -> Tuple[bool, str]:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, shell=True, capture_output=True, text=True,
|
||||
cwd=cwd or os.getcwd(), timeout=30
|
||||
)
|
||||
return result.returncode == 0, (result.stdout + result.stderr).strip()
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
|
||||
def handle(self, args: str) -> str:
|
||||
parts = args.strip().split(maxsplit=1)
|
||||
if not parts:
|
||||
return self.usage
|
||||
|
||||
op = parts[0].lower()
|
||||
rest = parts[1] if len(parts) > 1 else ""
|
||||
|
||||
handlers = {
|
||||
"commit": self._commit,
|
||||
"log": self._log,
|
||||
"changelog": self._changelog,
|
||||
"summary": self._summary,
|
||||
"blame": self._blame,
|
||||
"contributors": self._contributors,
|
||||
"stale": self._stale,
|
||||
"hooks": self._hooks,
|
||||
}
|
||||
|
||||
handler = handlers.get(op)
|
||||
if not handler:
|
||||
return f"错误: 不支持的操作 '{op}'"
|
||||
|
||||
try:
|
||||
return handler(rest)
|
||||
except Exception as e:
|
||||
return f"执行错误: {e}"
|
||||
|
||||
def _commit(self, message: str) -> str:
|
||||
"""智能提交"""
|
||||
# 检查是否有变更
|
||||
ok, status = self._run("git status --porcelain")
|
||||
if not status:
|
||||
return "✅ 工作区干净,没有需要提交的变更"
|
||||
|
||||
# 自动 add
|
||||
self._run("git add -A")
|
||||
|
||||
# 规范化提交消息
|
||||
if not message:
|
||||
# 根据变更自动生成消息
|
||||
message = self._auto_commit_message(status)
|
||||
|
||||
# 检查是否符合 conventional commits
|
||||
if not re.match(r'^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?: .+', message):
|
||||
# 自动添加前缀
|
||||
if any('.py' in l for l in status.split('\n')):
|
||||
if 'test' in message.lower():
|
||||
message = f"test: {message}"
|
||||
elif 'doc' in message.lower() or 'readme' in message.lower():
|
||||
message = f"docs: {message}"
|
||||
else:
|
||||
message = f"feat: {message}"
|
||||
|
||||
# 提交
|
||||
ok, output = self._run(f'git commit -m "{message}"')
|
||||
if ok:
|
||||
# 获取 commit hash
|
||||
_, hash_out = self._run("git log --oneline -1")
|
||||
return f"✅ 已提交: {message}\n {hash_out}"
|
||||
return f"❌ 提交失败: {output}"
|
||||
|
||||
def _auto_commit_message(self, status: str) -> str:
|
||||
"""根据变更自动生成提交消息"""
|
||||
lines = status.strip().split('\n')
|
||||
added = [l[3:] for l in lines if l.startswith('A')]
|
||||
modified = [l[3:] for l in lines if l.startswith('M')]
|
||||
deleted = [l[3:] for l in lines if l.startswith('D')]
|
||||
|
||||
parts = []
|
||||
if added:
|
||||
parts.append(f"添加 {len(added)} 个文件")
|
||||
if modified:
|
||||
parts.append(f"修改 {len(modified)} 个文件")
|
||||
if deleted:
|
||||
parts.append(f"删除 {len(deleted)} 个文件")
|
||||
|
||||
return ", ".join(parts) if parts else "更新代码"
|
||||
|
||||
def _log(self, args: str) -> str:
|
||||
"""格式化提交历史"""
|
||||
count = int(args) if args.strip().isdigit() else 10
|
||||
ok, output = self._run(f'git log --oneline -{count} --format="%h %s (%ar, %an)"')
|
||||
if not ok:
|
||||
return f"❌ {output}"
|
||||
|
||||
lines = output.strip().split('\n')
|
||||
result = [f"📜 最近 {len(lines)} 次提交:\n"]
|
||||
for line in lines:
|
||||
result.append(f" {line}")
|
||||
|
||||
return '\n'.join(result)
|
||||
|
||||
def _changelog(self, version: str) -> str:
|
||||
"""生成 CHANGELOG"""
|
||||
# 获取所有提交
|
||||
ok, output = self._run('git log --oneline --format="%s" | head -50')
|
||||
if not ok:
|
||||
return f"❌ {output}"
|
||||
|
||||
commits = output.strip().split('\n')
|
||||
|
||||
# 分类
|
||||
categories = {
|
||||
'feat': [], 'fix': [], 'docs': [], 'refactor': [],
|
||||
'test': [], 'chore': [], 'other': []
|
||||
}
|
||||
|
||||
for commit in commits:
|
||||
matched = False
|
||||
for prefix in categories:
|
||||
if commit.startswith(prefix + ':') or commit.startswith(prefix + '('):
|
||||
categories[prefix].append(commit)
|
||||
matched = True
|
||||
break
|
||||
if not matched:
|
||||
categories['other'].append(commit)
|
||||
|
||||
# 生成
|
||||
ver = version or "未发布"
|
||||
result = [f"## {ver}\n"]
|
||||
|
||||
labels = {
|
||||
'feat': '🚀 新增', 'fix': '🐛 修复', 'docs': '📝 文档',
|
||||
'refactor': '♻️ 重构', 'test': '🧪 测试', 'chore': '🔧 杂项',
|
||||
'other': '📦 其他'
|
||||
}
|
||||
|
||||
for prefix, label in labels.items():
|
||||
items = categories[prefix]
|
||||
if items:
|
||||
result.append(f"\n### {label}")
|
||||
for item in items[:10]:
|
||||
result.append(f"- {item}")
|
||||
|
||||
return '\n'.join(result)
|
||||
|
||||
def _summary(self, args: str) -> str:
|
||||
"""变更摘要"""
|
||||
ok, output = self._run("git diff --stat")
|
||||
if not output:
|
||||
return "✅ 没有未提交的变更"
|
||||
|
||||
ok2, short = self._run("git diff --shortstat")
|
||||
return f"📊 变更摘要:\n\n{short}\n\n{output[:2000]}"
|
||||
|
||||
def _blame(self, args: str) -> str:
|
||||
"""逐行追溯"""
|
||||
if not args.strip():
|
||||
return "错误: 请提供文件路径"
|
||||
|
||||
ok, output = self._run(f"git blame --line-porcelain {args} | grep -E '^(author | )'")
|
||||
if not ok:
|
||||
return f"❌ {output}"
|
||||
|
||||
# 简化输出
|
||||
lines = output.strip().split('\n')
|
||||
result = [f"📝 {args} 修改记录:\n"]
|
||||
author = ""
|
||||
for line in lines:
|
||||
if line.startswith('author '):
|
||||
author = line[7:]
|
||||
elif line.startswith('\t'):
|
||||
result.append(f" {author:20} {line[1:]}")
|
||||
|
||||
return '\n'.join(result[:50])
|
||||
|
||||
def _contributors(self, args: str) -> str:
|
||||
"""贡献者统计"""
|
||||
ok, output = self._run("git shortlog -sn --all --no-merges")
|
||||
if not ok:
|
||||
return f"❌ {output}"
|
||||
|
||||
lines = output.strip().split('\n')
|
||||
result = [f"👥 贡献者 ({len(lines)} 人):\n"]
|
||||
for line in lines[:20]:
|
||||
result.append(f" {line.strip()}")
|
||||
|
||||
return '\n'.join(result)
|
||||
|
||||
def _stale(self, args: str) -> str:
|
||||
"""查找长期未更新的文件"""
|
||||
days = int(args) if args.strip().isdigit() else 90
|
||||
ok, output = self._run(f"git log --diff-filter=A --format='%aI %n' --since='{days} days ago' | head -20")
|
||||
if not ok:
|
||||
return f"❌ {output}"
|
||||
|
||||
return f"📁 最近 {days} 天新增的文件:\n{output[:2000]}"
|
||||
|
||||
def _hooks(self, args: str) -> str:
|
||||
"""管理 git hooks"""
|
||||
hooks_dir = ".git/hooks"
|
||||
if not os.path.exists(hooks_dir):
|
||||
return "❌ 不是 git 仓库"
|
||||
|
||||
hooks = os.listdir(hooks_dir)
|
||||
active = [h for h in hooks if not h.endswith('.sample') and os.path.isfile(os.path.join(hooks_dir, h))]
|
||||
|
||||
result = ["🪝 Git Hooks:\n"]
|
||||
for h in hooks:
|
||||
path = os.path.join(hooks_dir, h)
|
||||
if h.endswith('.sample'):
|
||||
result.append(f" ⭕ {h.replace('.sample', '')} (未激活)")
|
||||
elif os.path.isfile(path):
|
||||
result.append(f" ✅ {h} (活跃)")
|
||||
|
||||
if not active:
|
||||
result.append("\n 没有活跃的 hooks。可以添加 pre-commit 等钩子。")
|
||||
|
||||
return '\n'.join(result)
|
||||
Reference in new issue
Block a user