refactor: 插件大优化 17→13 个,5689→4309 行

合并:
- code_editor + code_search → code_editor (编辑+搜索统一)
- git_tools + git_workflow → git_tools (基础+工作流统一)
- auto_engineer + project_analyzer → auto_engineer (质量+分析统一)

修复:
- cmd_executor: 移除 Windows-only 限制,跨平台兼容
- cmd_executor: 移除坏掉的 from ai_cli import

精简:
- file_manager: 移除和 code_editor 重叠的行编辑操作

删除:
- scheduler.py (空文件)
- git_workflow.py (已合并)
- code_search.py (已合并)
- project_analyzer.py (已合并)
This commit is contained in:
xiaoli-cli committed 2026-04-26 05:36:18 +08:00
1 parent 119e939a23
commit 9aae8b2ca2
9 files changed
+1295 -2676

No files matched your search

+255 -171
View File
@@ -1,38 +1,46 @@
"""
Git 集成插件 - 版本控制能力
对标 Claude Code 的 Git 工作流支持
Git 工具插件 - 统一的版本控制能力
合并原 git_tools + git_workflow,消除重复
"""
import os
import re
import subprocess
from typing import Optional, Tuple
from typing import Tuple, Optional
class Plugin:
"""Git 集成工具 - 提供完整的 Git 操作支持"""
"""Git 版本控制 - 基础操作 + 工作流自动化"""
def __init__(self):
self.usage = """Git 集成工具
self.usage = """Git 版本控制工具
操作类型:
status - 查看仓库状态
diff [文件路径] - 查看未暂存的变更
diff --cached - 查看已暂存的变更
log [数量] - 查看提交历史
log --oneline [数量] - 查看简洁提交历史
add <文件路径> - 暂存文件
add . - 暂存所有变更
commit <提交信息> - 提交变更
branch - 查看分支列表
branch <名称> - 创建新分支
checkout <分支名> - 切换分支
checkout -b <分支名> - 创建并切换分支
stash - 暂存当前变更
stash pop - 恢复暂存的变更
show <commit> - 查看指定提交
blame <文件路径> - 查看文件逐行修改记录
remote - 查看远程仓库
init - 初始化仓库
clone <URL> [目录] - 克隆仓库
基础操作:
status - 查看仓库状态
diff [文件路径] - 查看未暂存变更
diff --cached - 查看已暂存变更
log [数量] - 提交历史 (默认10条)
log --oneline [数量] - 简洁提交历史
add <文件路径> - 暂存文件
add . - 暂存所有变更
commit <提交信息> - 提交变更
branch - 查看分支列表
branch <名称> - 创建新分支
checkout <分支名> - 切换分支
checkout -b <分支名> - 创建并切换分支
stash - 暂存当前变更
stash pop - 恢复暂存的变更
show <commit> - 查看指定提交
blame <文件路径> - 逐行修改记录
remote - 查看远程仓库
init - 初始化仓库
clone <URL> [目录] - 克隆仓库
工作流:
smart-commit [消息] - 智能提交 (自动 add + 规范化消息)
changelog [版本] - 生成 CHANGELOG
summary - 变更摘要 (未提交改动)
contributors - 贡献者统计
stale [天数] - 查找长期未更新文件
"""
self.cli = None
@@ -42,8 +50,9 @@ class Plugin:
def get_tool_info(self):
return {
"name": "git_tools",
"description": "Git 版本控制工具 - 查看状态、提交、分支管理、查看历史等",
"keywords": ["git", "版本控制", "提交", "分支", "commit", "branch", "status"],
"description": "Git 版本控制 - 状态查看、提交、分支管理、diff、历史、blame、工作流自动化",
"keywords": ["git", "版本控制", "提交", "分支", "commit", "branch", "status", "diff",
"changelog", "log", "blame", "stash", "checkout", "merge"],
"usage": self.usage
}
@@ -55,8 +64,7 @@ class Plugin:
"type": "object",
"properties": {
"operation": {"type": "string", "description": "Git 操作"},
"args": {"type": "string", "description": "操作参数"},
"cwd": {"type": "string", "description": "工作目录"}
"args": {"type": "string", "description": "操作参数"}
},
"required": ["operation"]
}
@@ -67,95 +75,114 @@ class Plugin:
args = arguments.get("args", "")
return f"{op} {args}".strip()
# ── 执行 Git 命令 ──
def _run_git(self, args: list, cwd: Optional[str] = None) -> Tuple[bool, str]:
"""执行 git 命令"""
try:
result = subprocess.run(
["git"] + args,
capture_output=True,
text=True,
cwd=cwd,
timeout=30
capture_output=True, text=True,
cwd=cwd, timeout=30
)
output = result.stdout
if result.stderr:
output += result.stderr
return result.returncode == 0, output.strip()
output = (result.stdout + result.stderr).strip()
return result.returncode == 0, output
except FileNotFoundError:
return False, "错误:git 未安装。请先安装 Git。"
return False, "git 未安装,请先安装 Git"
except subprocess.TimeoutExpired:
return False, "错误:Git 命令执行超时"
return False, "Git 命令执行超时"
except Exception as e:
return False, f"错误:{str(e)}"
return False, str(e)
def _run_shell(self, cmd: str, cwd: Optional[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 _get_cwd(self):
if self.cli and hasattr(self.cli, 'current_dir'):
return self.cli.current_dir
return None
# ── 路由 ──
def handle(self, args: str) -> str:
try:
parts = args.strip().split(maxsplit=1)
if not parts:
return self._git_status("")
return self._status()
operation = parts[0].lower()
op = parts[0].lower()
rest = parts[1] if len(parts) > 1 else ""
# 获取工作目录
cwd = None
if self.cli and hasattr(self.cli, 'current_dir'):
cwd = self.cli.current_dir
cwd = self._get_cwd()
handlers = {
"status": self._git_status,
"diff": self._git_diff,
"log": self._git_log,
"add": self._git_add,
"commit": self._git_commit,
"branch": self._git_branch,
"checkout": self._git_checkout,
"stash": self._git_stash,
"show": self._git_show,
"blame": self._git_blame,
"remote": self._git_remote,
"init": self._git_init,
"clone": self._git_clone,
# 基础
"status": lambda r: self._status(cwd),
"diff": lambda r: self._diff(r, cwd),
"log": lambda r: self._log(r, cwd),
"add": lambda r: self._add(r, cwd),
"commit": lambda r: self._commit(r, cwd),
"branch": lambda r: self._branch(r, cwd),
"checkout": lambda r: self._checkout(r, cwd),
"stash": lambda r: self._stash(r, cwd),
"show": lambda r: self._show(r, cwd),
"blame": lambda r: self._blame(r, cwd),
"remote": lambda r: self._remote(cwd),
"init": lambda r: self._init(r, cwd),
"clone": lambda r: self._clone(r, cwd),
# 工作流
"smart-commit": lambda r: self._smart_commit(r, cwd),
"changelog": lambda r: self._changelog(r, cwd),
"summary": lambda r: self._summary(cwd),
"contributors": lambda r: self._contributors(cwd),
"stale": lambda r: self._stale(r, cwd),
}
handler = handlers.get(operation)
if not handler:
# 尝试直接执行 git 命令
return self._git_raw(args, cwd)
handler = handlers.get(op)
if handler:
return handler(rest)
return handler(rest, cwd)
# 回退:直接执行 git 命令
ok, output = self._run_git(args.split(), cwd)
return output if ok else f"Git 错误: {output}"
except Exception as e:
return f"Git 工具错误: {str(e)}"
def _git_status(self, args: str, cwd=None) -> str:
# ── 基础操作 ──
def _status(self, cwd=None) -> str:
ok, output = self._run_git(["status", "--porcelain"], cwd)
if not ok:
return f"Git 错误: {output}"
if not output:
return "✅ 工作区干净,没有未提交的变更"
# 获取分支
_, branch = self._run_git(["branch", "--show-current"], cwd)
return f"✅ 工作区干净 (分支: {branch})"
# 解析状态
lines = output.split('\n')
staged = []
unstaged = []
untracked = []
staged, unstaged, untracked = [], [], []
for line in lines:
if len(line) < 4:
continue
status = line[:2]
filename = line[3:]
s, filename = line[:2], line[3:]
if s[0] in 'MADRC':
staged.append(f" ✓ {s[0]} {filename}")
if s[1] in 'MD':
unstaged.append(f" ! {s[1]} {filename}")
if s == '??':
untracked.append(f" ? {filename}")
if status[0] in ('M', 'A', 'D', 'R', 'C'):
staged.append(f" ✅ {status[0]} {filename}")
if status[1] in ('M', 'D'):
unstaged.append(f" ⚠️ {status[1]} {filename}")
if status == '??':
untracked.append(f" 🆕 {filename}")
_, branch = self._run_git(["branch", "--show-current"], cwd)
result = [f"📊 Git 状态 (分支: {branch})"]
result = ["📊 Git 状态:"]
if staged:
result.append(f"\n已暂存 ({len(staged)}):")
result.extend(staged)
@@ -166,37 +193,30 @@ class Plugin:
result.append(f"\n未跟踪 ({len(untracked)}):")
result.extend(untracked)
# 获取分支信息
ok2, branch = self._run_git(["branch", "--show-current"], cwd)
if ok2 and branch:
result.insert(1, f"🌿 分支: {branch}")
return '\n'.join(result)
def _git_diff(self, args: str, cwd=None) -> str:
if args.strip() == "--cached":
ok, output = self._run_git(["diff", "--cached"], cwd)
elif args.strip():
ok, output = self._run_git(["diff", args.strip()], cwd)
def _diff(self, args: str, cwd=None) -> str:
args = args.strip()
if args == "--cached":
git_args = ["diff", "--cached"]
elif args:
git_args = ["diff", args]
else:
ok, output = self._run_git(["diff"], cwd)
git_args = ["diff"]
ok, output = self._run_git(git_args, cwd)
if not ok:
return f"Git 错误: {output}"
if not output:
return "没有变更"
# 限制输出长度
if len(output) > 5000:
output = output[:5000] + f"\n... (输出截断,共 {len(output)} 字符)"
output = output[:5000] + f"\n... (截断,共 {len(output)} 字符)"
return output
return f"📝 变更:\n{output}"
def _git_log(self, args: str, cwd=None) -> str:
def _log(self, args: str, cwd=None) -> str:
parts = args.split()
oneline = "--oneline" in parts
count = 10
for p in parts:
if p.isdigit():
count = int(p)
@@ -210,137 +230,201 @@ class Plugin:
if not ok:
return f"Git 错误: {output}"
if not output:
return "暂无提交历史"
return output or "暂无提交历史"
return f"📜 提交历史:\n{output}"
def _git_add(self, args: str, cwd=None) -> str:
def _add(self, args: str, cwd=None) -> str:
target = args.strip()
if not target:
return "错误:请提供文件路径(使用 . 暂存所有)"
ok, output = self._run_git(["add", target], cwd)
if not ok:
return f"Git add 失败: {output}"
return f"✅ 已暂存: {target}" if ok else f"Git add 失败: {output}"
return f"✅ 已暂存: {target}"
def _git_commit(self, args: str, cwd=None) -> str:
def _commit(self, args: str, cwd=None) -> str:
message = args.strip()
if not message:
return "错误:请提供提交信息"
ok, output = self._run_git(["commit", "-m", message], cwd)
if not ok:
return f"Git commit 失败: {output}"
return f"✅ 提交成功:\n{output}" if ok else f"Git commit 失败: {output}"
return f"✅ 提交成功:\n{output}"
def _git_branch(self, args: str, cwd=None) -> str:
def _branch(self, args: str, cwd=None) -> str:
if not args.strip():
ok, output = self._run_git(["branch", "-a"], cwd)
if not ok:
return f"Git 错误: {output}"
return f"🌿 分支列表:\n{output}"
return f"🌿 分支列表:\n{output}" if ok else f"Git 错误: {output}"
else:
ok, output = self._run_git(["branch", args.strip()], cwd)
if not ok:
return f"创建分支失败: {output}"
return f"✅ 已创建分支: {args.strip()}"
return f"✅ 已创建分支: {args.strip()}" if ok else f"创建分支失败: {output}"
def _git_checkout(self, args: str, cwd=None) -> str:
def _checkout(self, args: str, cwd=None) -> str:
args = args.strip()
if args.startswith("-b "):
branch = args[3:].strip()
ok, output = self._run_git(["checkout", "-b", branch], cwd)
else:
branch = args.strip()
branch = args
ok, output = self._run_git(["checkout", branch], cwd)
return f"✅ 已切换到: {branch}" if ok else f"切换失败: {output}"
if not ok:
return f"切换分支失败: {output}"
return f"✅ 已切换到分支: {branch}"
def _git_stash(self, args: str, cwd=None) -> str:
def _stash(self, args: str, cwd=None) -> str:
if args.strip() == "pop":
ok, output = self._run_git(["stash", "pop"], cwd)
else:
ok, output = self._run_git(["stash"], cwd)
return f"✅ {output}" if ok else f"Git stash 失败: {output}"
if not ok:
return f"Git stash 失败: {output}"
return f"✅ {output}"
def _git_show(self, args: str, cwd=None) -> str:
def _show(self, args: str, cwd=None) -> str:
commit = args.strip() or "HEAD"
ok, output = self._run_git(["show", "--stat", commit], cwd)
if not ok:
return f"Git show 失败: {output}"
if len(output) > 3000:
output = output[:3000] + "\n... (输出截断)"
output = output[:3000] + "\n... (截断)"
return output
def _git_blame(self, args: str, cwd=None) -> str:
def _blame(self, args: str, cwd=None) -> str:
path = args.strip()
if not path:
return "错误:请提供文件路径"
ok, output = self._run_git(["blame", "--line-porcelain", path], cwd)
if not ok:
return f"Git blame 失败: {output}"
# 简化输出
lines = output.split('\n')
simplified = []
result = []
author = ""
for line in lines:
if line.startswith('author '):
author = line[7:]
elif line.startswith('\t'):
simplified.append(f" {author}: {line[1:]}")
result.append(f" {author}: {line[1:]}")
if len(simplified) > 100:
simplified = simplified[:100]
simplified.append(f" ... (共 {len(simplified)} 行)")
if len(result) > 100:
result = result[:100]
result.append(f" ... (共 {len(result)} 行)")
return f"📝 {path} 修改记录:\n" + '\n'.join(simplified)
return f"📝 {path} 修改记录:\n" + '\n'.join(result)
def _git_remote(self, args: str, cwd=None) -> str:
def _remote(self, cwd=None) -> str:
ok, output = self._run_git(["remote", "-v"], cwd)
if not ok:
return f"Git 错误: {output}"
if not output:
return "没有配置远程仓库"
return f"🌐 远程仓库:\n{output}"
return output or "没有配置远程仓库"
def _git_init(self, args: str, cwd=None) -> str:
def _init(self, args: str, cwd=None) -> str:
path = args.strip() or "."
ok, output = self._run_git(["init", path], cwd)
if not ok:
return f"Git init 失败: {output}"
return f"✅ {output}"
return f"✅ {output}" if ok else f"Git init 失败: {output}"
def _git_clone(self, args: str, cwd=None) -> str:
def _clone(self, args: str, cwd=None) -> str:
parts = args.split()
if not parts:
return "错误:请提供仓库 URL"
url = parts[0]
target = parts[1] if len(parts) > 1 else ""
cmd = ["clone", url]
if target:
cmd.append(target)
cmd = ["clone", parts[0]]
if len(parts) > 1:
cmd.append(parts[1])
ok, output = self._run_git(cmd, cwd)
if not ok:
return f"Git clone 失败: {output}"
return f"✅ 克隆成功:\n{output}"
return f"✅ 克隆成功:\n{output}" if ok else f"Git clone 失败: {output}"
def _git_raw(self, args: str, cwd=None) -> str:
"""直接执行 git 命令"""
parts = args.split()
ok, output = self._run_git(parts, cwd)
# ── 工作流 ──
def _smart_commit(self, message: str, cwd=None) -> str:
"""智能提交:自动 add + 规范化消息"""
ok, status = self._run_git(["status", "--porcelain"], cwd)
if not status:
return "✅ 工作区干净,没有需要提交的变更"
self._run_git(["add", "-A"], cwd)
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 '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_git(["commit", "-m", message], cwd)
if ok:
_, hash_out = self._run_git(["log", "--oneline", "-1"], cwd)
return f"✅ 已提交: {message}\n {hash_out}"
return f"❌ 提交失败: {output}"
def _auto_commit_message(self, status: str) -> str:
lines = status.strip().split('\n')
added = len([l for l in lines if l.startswith('A ') or l.startswith('A') and l[1] == ' '])
modified = len([l for l in lines if l.startswith('M ') or (len(l) > 1 and l[1] == 'M')])
deleted = len([l for l in lines if l.startswith('D ') or (len(l) > 1 and l[1] == 'D')])
parts = []
if added:
parts.append(f"添加 {added} 个文件")
if modified:
parts.append(f"修改 {modified} 个文件")
if deleted:
parts.append(f"删除 {deleted} 个文件")
return ", ".join(parts) if parts else "更新代码"
def _changelog(self, version: str, cwd=None) -> str:
ok, output = self._run_shell(
'git log --oneline --format="%s" | head -50', cwd)
if not ok:
return f"Git 错误: {output}"
return output
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)
labels = {
'feat': '🚀 新增', 'fix': '🐛 修复', 'docs': '📝 文档',
'refactor': '♻️ 重构', 'test': '🧪 测试', 'chore': '🔧 杂项',
'other': '📦 其他'
}
ver = version or "未发布"
result = [f"## {ver}\n"]
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, cwd=None) -> str:
ok, output = self._run_git(["diff", "--stat"], cwd)
if not output:
return "✅ 没有未提交的变更"
_, short = self._run_git(["diff", "--shortstat"], cwd)
return f"📊 变更摘要:\n\n{short}\n\n{output[:2000]}"
def _contributors(self, cwd=None) -> str:
ok, output = self._run_git(["shortlog", "-sn", "--all", "--no-merges"], cwd)
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, cwd=None) -> str:
days = int(args) if args.strip().isdigit() else 90
ok, output = self._run_shell(
f"git log --diff-filter=A --format='%aI %n' --since='{days} days ago' | head -20", cwd)
return f"📁 最近 {days} 天新增的文件:\n{output[:2000]}" if ok else f"❌ {output}"