Compare commits
2 Commits
scripts
...
add_repo_t
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a15f1a08a8 | ||
![]() |
573824236f |
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,4 +9,3 @@ custom/*
|
||||
!custom/config.yml.example
|
||||
|
||||
user_settings.yml
|
||||
.aider*
|
||||
|
@ -1,25 +0,0 @@
|
||||
### aider 操作指南
|
||||
|
||||
aider是一个AI辅助的代码编辑工具,可以根据自然语言指令修改代码。
|
||||
|
||||
用途:
|
||||
根据用户提供的指令,自动分析和修改已添加到aider中的代码文件。
|
||||
|
||||
使用方法:
|
||||
1. 使用 `/aider.files.add` 命令添加需要处理的文件
|
||||
2. 输入 `/aider <message>` 命令,其中 `<message>` 是你想要aider执行的任务描述
|
||||
3. 等待aider生成建议的更改
|
||||
4. 在IDE中查看每个文件的Diff视图,选择是否接受修改
|
||||
5. 对于多个文件的更改,系统会在每个文件之后询问是否继续查看下一个文件的更改
|
||||
|
||||
注意事项:
|
||||
- 使用前必须先添加文件到aider,否则会提示使用 'aider.files.add' 命令
|
||||
- 可以使用 `aider.files.remove` 命令从aider中移除文件
|
||||
- 所有更改都会在IDE中以Diff视图形式展示,你可以决定是否应用这些更改
|
||||
- aider使用OpenAI的API,请确保已正确设置API密钥
|
||||
|
||||
示例:
|
||||
/aider 重构这段代码以提高性能
|
||||
|
||||
额外信息:
|
||||
aider支持多种编程语言,可以执行代码重构、bug修复、性能优化等任务。它会分析当前添加的所有文件,并提供整体的改进建议。
|
@ -1,213 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from devchat.ide import IDEService
|
||||
|
||||
from lib.chatmark import Button
|
||||
|
||||
GLOBAL_CONFIG_PATH = os.path.join(os.path.expanduser("~"), ".chat", ".workflow_config.json")
|
||||
|
||||
|
||||
def save_config(config_path, item, value):
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
else:
|
||||
config = {}
|
||||
|
||||
config[item] = value
|
||||
with open(config_path, "w", encoding="utf-8") as f:
|
||||
json.dump(config, f, indent=4)
|
||||
|
||||
|
||||
def write_python_path_to_config():
|
||||
"""
|
||||
Write the current system Python path to the configuration.
|
||||
"""
|
||||
python_path = sys.executable
|
||||
save_config(GLOBAL_CONFIG_PATH, "aider_python", python_path)
|
||||
print(f"Python path '{python_path}' has been written to the configuration.")
|
||||
|
||||
|
||||
def get_aider_files():
|
||||
"""
|
||||
从.chat/.aider_files文件中读取aider文件列表
|
||||
"""
|
||||
aider_files_path = os.path.join(".chat", ".aider_files")
|
||||
if not os.path.exists(aider_files_path):
|
||||
return []
|
||||
|
||||
with open(aider_files_path, "r") as f:
|
||||
return [line.strip() for line in f if line.strip()]
|
||||
|
||||
|
||||
def run_aider(message, files):
|
||||
"""
|
||||
运行aider命令
|
||||
"""
|
||||
python = sys.executable
|
||||
model = os.environ.get("LLM_MODEL", "gpt-3.5-turbo-1106")
|
||||
|
||||
cmd = [
|
||||
python,
|
||||
"-m",
|
||||
"aider",
|
||||
"--model",
|
||||
f"openai/{model}",
|
||||
"--yes",
|
||||
"--no-auto-commits",
|
||||
"--dry-run",
|
||||
"--no-pretty",
|
||||
"--message",
|
||||
message,
|
||||
] + files
|
||||
|
||||
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
|
||||
has_started = False
|
||||
aider_output = ""
|
||||
for line in process.stdout:
|
||||
if "run with --help" in line or 'run "aider --help"' in line:
|
||||
has_started = True
|
||||
continue
|
||||
if has_started:
|
||||
aider_output += line
|
||||
print(line, end="", flush=True)
|
||||
|
||||
return_code = process.wait()
|
||||
|
||||
if return_code != 0:
|
||||
for line in process.stderr:
|
||||
print(f"Error: {line.strip()}", file=sys.stderr)
|
||||
sys.exit(return_code)
|
||||
|
||||
return aider_output
|
||||
|
||||
|
||||
def apply_changes(changes, files):
|
||||
"""
|
||||
应用aider生成的更改
|
||||
"""
|
||||
changes_file = ".chat/changes.txt"
|
||||
os.makedirs(os.path.dirname(changes_file), exist_ok=True)
|
||||
with open(changes_file, "w") as f:
|
||||
f.write(changes)
|
||||
|
||||
python = sys.executable
|
||||
model = os.environ.get("LLM_MODEL", "gpt-3.5-turbo-1106")
|
||||
|
||||
cmd = [
|
||||
python,
|
||||
"-m",
|
||||
"aider",
|
||||
"--model",
|
||||
f"openai/{model}",
|
||||
"--yes",
|
||||
"--no-auto-commits",
|
||||
"--apply",
|
||||
changes_file,
|
||||
] + files
|
||||
|
||||
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
|
||||
has_started = False
|
||||
for line in process.stdout:
|
||||
if "Model:" in line:
|
||||
has_started = True
|
||||
continue
|
||||
if has_started:
|
||||
print(line, end="", flush=True)
|
||||
|
||||
return_code = process.wait()
|
||||
|
||||
if return_code != 0:
|
||||
for line in process.stderr:
|
||||
print(f"Error: {line.strip()}", file=sys.stderr)
|
||||
sys.exit(return_code)
|
||||
|
||||
os.remove(changes_file)
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main function to run the aider command.
|
||||
|
||||
This function performs the following tasks:
|
||||
1. Checks for correct command-line usage
|
||||
2. Writes the current Python path to the configuration
|
||||
3. Retrieves the list of files to be processed
|
||||
4. Runs the aider command with the given message
|
||||
5. Applies the suggested changes
|
||||
6. Displays the differences in the IDE
|
||||
|
||||
Usage: python command.py <message>
|
||||
"""
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python command.py <message>", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
write_python_path_to_config()
|
||||
|
||||
message = sys.argv[1]
|
||||
files = get_aider_files()
|
||||
|
||||
if not files:
|
||||
print(
|
||||
"No files added to aider. Please add files using 'aider.files.add' command.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
print("Running aider...\n", flush=True)
|
||||
changes = run_aider(message, files)
|
||||
|
||||
if not changes:
|
||||
print("No changes suggested by aider.")
|
||||
sys.exit(0)
|
||||
|
||||
print("\nApplying changes...\n", flush=True)
|
||||
|
||||
# 保存原始文件内容
|
||||
original_contents = {}
|
||||
for file in files:
|
||||
with open(file, "r") as f:
|
||||
original_contents[file] = f.read()
|
||||
|
||||
# 应用更改
|
||||
apply_changes(changes, files)
|
||||
|
||||
# 读取更新后的文件内容
|
||||
updated_contents = {}
|
||||
for file in files:
|
||||
with open(file, "r") as f:
|
||||
updated_contents[file] = f.read()
|
||||
|
||||
# 还原原始文件内容
|
||||
for file in files:
|
||||
with open(file, "w") as f:
|
||||
f.write(original_contents[file])
|
||||
|
||||
# 使用 IDEService 展示差异
|
||||
ide_service = IDEService()
|
||||
for index, file in enumerate(files):
|
||||
ide_service.diff_apply(file, updated_contents[file])
|
||||
if index < len(files) - 1:
|
||||
# 等待用户确认
|
||||
button = Button(
|
||||
["Show Next Changes", "Cancel"],
|
||||
)
|
||||
button.render()
|
||||
|
||||
idx = button.clicked
|
||||
if idx == 0:
|
||||
continue
|
||||
else:
|
||||
break
|
||||
|
||||
print("Changes have been displayed in the IDE.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,9 +0,0 @@
|
||||
description: "aider command"
|
||||
workflow_python:
|
||||
env_name: devchat-aider-env
|
||||
version: 3.11.0
|
||||
dependencies: requirements.txt
|
||||
input: required
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $workflow_python $command_path/command.py "$input"
|
@ -1,23 +0,0 @@
|
||||
### aider.files.add
|
||||
|
||||
这个命令用于将文件添加到aider的处理列表中。
|
||||
|
||||
用途:
|
||||
添加指定文件到aider,使其包含在后续的aider操作中。
|
||||
|
||||
使用方法:
|
||||
/aider.files.add <file_path>
|
||||
|
||||
参数:
|
||||
- <file_path>: 要添加的文件路径(必需)
|
||||
|
||||
注意事项:
|
||||
- 文件路径必须是有效的格式
|
||||
- 已存在于列表中的文件不会重复添加
|
||||
- 成功添加后会显示当前的aider文件列表
|
||||
|
||||
示例:
|
||||
/aider.files.add src/main.py
|
||||
|
||||
额外信息:
|
||||
这个命令会将文件路径保存到.chat/.aider_files文件中。如果.chat目录不存在,会自动创建。
|
@ -1,66 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def is_valid_path(path):
|
||||
"""
|
||||
检查路径是否为有效的文件路径形式
|
||||
"""
|
||||
try:
|
||||
# 尝试规范化路径
|
||||
normalized_path = os.path.normpath(path)
|
||||
# 检查路径是否是绝对路径或相对路径
|
||||
return (
|
||||
os.path.isabs(normalized_path)
|
||||
or not os.path.dirname(normalized_path) == normalized_path
|
||||
)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def add_file(file_path):
|
||||
# 1. 检查是否为有效的文件路径形式
|
||||
if not is_valid_path(file_path):
|
||||
print(f"Error: '{file_path}' is not a valid file path format.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# 获取绝对路径
|
||||
abs_file_path = file_path.strip()
|
||||
|
||||
# 2. 将新增文件路径存储到.chat/.aider_files文件中
|
||||
aider_files_path = os.path.join(".chat", ".aider_files")
|
||||
|
||||
# 确保.chat目录存在
|
||||
os.makedirs(os.path.dirname(aider_files_path), exist_ok=True)
|
||||
|
||||
# 读取现有文件列表
|
||||
existing_files = set()
|
||||
if os.path.exists(aider_files_path):
|
||||
with open(aider_files_path, "r") as f:
|
||||
existing_files = set(line.strip() for line in f)
|
||||
|
||||
# 添加新文件
|
||||
existing_files.add(abs_file_path)
|
||||
|
||||
# 写入更新后的文件列表
|
||||
with open(aider_files_path, "w") as f:
|
||||
for file in sorted(existing_files):
|
||||
f.write(f"{file}\n")
|
||||
|
||||
print(f"Added '{abs_file_path}' to aider files.")
|
||||
print("\nCurrent aider files:")
|
||||
for file in sorted(existing_files):
|
||||
print(f"- {file}")
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2 or sys.argv[1].strip() == "":
|
||||
print("Usage: /aider.files.add <file_path>", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
file_path = sys.argv[1]
|
||||
add_file(file_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,5 +0,0 @@
|
||||
description: "add files to aider"
|
||||
input: required
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1,19 +0,0 @@
|
||||
### aider.files.list
|
||||
|
||||
这个命令用于列出当前在aider处理列表中的所有文件。
|
||||
|
||||
用途:
|
||||
显示所有已添加到aider中的文件,提供当前aider正在处理的文件概览。
|
||||
|
||||
使用方法:
|
||||
/aider.files.list
|
||||
|
||||
注意事项:
|
||||
- 如果没有文件被添加到aider,会显示相应的提示消息
|
||||
- 文件列表按字母顺序排序显示
|
||||
|
||||
示例:
|
||||
/aider.files.list
|
||||
|
||||
额外信息:
|
||||
这个命令会读取.chat/.aider_files文件的内容来获取文件列表。如果该文件不存在,会提示尚未添加任何文件。
|
@ -1,31 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def list_files():
|
||||
aider_files_path = os.path.join(".chat", ".aider_files")
|
||||
|
||||
# 确保.chat/.aider_files文件存在
|
||||
if not os.path.exists(aider_files_path):
|
||||
print("No files have been added to aider yet.")
|
||||
sys.exit(0)
|
||||
|
||||
# 读取文件列表
|
||||
with open(aider_files_path, "r") as f:
|
||||
files = [line.strip() for line in f]
|
||||
|
||||
# 打印文件列表
|
||||
if files:
|
||||
print("Aider files:")
|
||||
for file in sorted(files):
|
||||
print(f"- {file}")
|
||||
else:
|
||||
print("No files found in aider.")
|
||||
|
||||
|
||||
def main():
|
||||
list_files()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,4 +0,0 @@
|
||||
description: "list files in aider"
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py
|
@ -1,23 +0,0 @@
|
||||
### aider.files.remove
|
||||
|
||||
这个命令用于从aider处理列表中移除指定的文件。
|
||||
|
||||
用途:
|
||||
将指定文件从aider的处理列表中删除,使其不再包含在后续的aider操作中。
|
||||
|
||||
使用方法:
|
||||
/aider.files.remove <file_path>
|
||||
|
||||
参数:
|
||||
- <file_path>: 要移除的文件路径(必需)
|
||||
|
||||
注意事项:
|
||||
- 文件路径必须是有效的格式
|
||||
- 如果指定的文件不在列表中,会显示相应的提示消息
|
||||
- 成功移除后会显示更新后的aider文件列表
|
||||
|
||||
示例:
|
||||
/aider.files.remove src/main.py
|
||||
|
||||
额外信息:
|
||||
这个命令会更新.chat/.aider_files文件,从中删除指定的文件路径。如果文件不存在于列表中,操作会安全退出。
|
@ -1,72 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def is_valid_path(path):
|
||||
"""
|
||||
检查路径是否为有效的文件路径形式
|
||||
"""
|
||||
try:
|
||||
# 尝试规范化路径
|
||||
normalized_path = os.path.normpath(path)
|
||||
# 检查路径是否是绝对路径或相对路径
|
||||
return (
|
||||
os.path.isabs(normalized_path)
|
||||
or not os.path.dirname(normalized_path) == normalized_path
|
||||
)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def remove_file(file_path):
|
||||
# 1. 检查是否为有效的文件路径形式
|
||||
if not is_valid_path(file_path):
|
||||
print(f"Error: '{file_path}' is not a valid file path format.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# 获取绝对路径
|
||||
abs_file_path = file_path.strip()
|
||||
|
||||
# 2. 从.chat/.aider_files文件中移除指定文件路径
|
||||
aider_files_path = os.path.join(".chat", ".aider_files")
|
||||
|
||||
# 确保.chat目录存在
|
||||
if not os.path.exists(aider_files_path):
|
||||
print(f"Error: '{aider_files_path}' does not exist.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# 读取现有文件列表
|
||||
existing_files = set()
|
||||
with open(aider_files_path, "r") as f:
|
||||
existing_files = set(line.strip() for line in f)
|
||||
|
||||
# 检查文件是否在列表中
|
||||
if abs_file_path not in existing_files:
|
||||
print(f"'{abs_file_path}' is not in aider files.")
|
||||
sys.exit(0)
|
||||
|
||||
# 移除文件
|
||||
existing_files.remove(abs_file_path)
|
||||
|
||||
# 写入更新后的文件列表
|
||||
with open(aider_files_path, "w") as f:
|
||||
for file in sorted(existing_files):
|
||||
f.write(f"{file}\n")
|
||||
|
||||
print(f"Removed '{abs_file_path}' from aider files.")
|
||||
print("\nCurrent aider files:")
|
||||
for file in sorted(existing_files):
|
||||
print(f"- {file}")
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2 or sys.argv[1].strip() == "":
|
||||
print("Usage: /aider.files.remove <file_path>", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
file_path = sys.argv[1]
|
||||
remove_file(file_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,5 +0,0 @@
|
||||
description: "remove files from aider"
|
||||
input: required
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1,2 +0,0 @@
|
||||
git+https://gitee.com/imlaji/aider.git@main
|
||||
git+https://gitee.com/devchat-ai/devchat.git@aider
|
@ -1,24 +0,0 @@
|
||||
### code_task_summary
|
||||
|
||||
根据当前分支或指定的Issue,生成代码任务摘要。
|
||||
|
||||
#### 用途
|
||||
- 自动生成简洁的代码任务描述
|
||||
- 帮助开发者快速理解任务要点
|
||||
- 用于更新项目配置或文档
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.code_task_summary [issue_url]`
|
||||
|
||||
- 如不提供issue_url,将基于当前分支名称提取Issue信息
|
||||
- 如提供issue_url,将直接使用该Issue的内容
|
||||
|
||||
#### 操作流程
|
||||
1. 获取Issue信息
|
||||
2. 生成代码任务摘要
|
||||
3. 允许用户编辑摘要
|
||||
4. 更新项目配置文件
|
||||
|
||||
#### 注意事项
|
||||
- 确保Git仓库配置正确
|
||||
- 需要有效的GitHub Token以访问API
|
@ -1,5 +0,0 @@
|
||||
description: 'Generate code task summary.'
|
||||
input: optional
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1 +0,0 @@
|
||||
description: Root of github commands.
|
@ -1,23 +0,0 @@
|
||||
### commit
|
||||
|
||||
自动生成提交信息并执行Git提交。
|
||||
|
||||
#### 用途
|
||||
- 生成规范的提交信息
|
||||
- 简化Git提交流程
|
||||
- 保持提交历史的一致性
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.commit [message]`
|
||||
|
||||
- message: 可选的用户输入,用于辅助生成提交信息
|
||||
|
||||
#### 操作流程
|
||||
1. 选择要提交的文件
|
||||
2. 生成提交信息
|
||||
3. 允许用户编辑提交信息
|
||||
4. 执行Git提交
|
||||
|
||||
#### 注意事项
|
||||
- 确保已选择需要提交的文件
|
||||
- 生成的提交信息可能需要进一步修改以符合项目规范
|
@ -1,19 +0,0 @@
|
||||
### config
|
||||
|
||||
配置GitHub工作流所需的设置。
|
||||
|
||||
#### 用途
|
||||
- 设置Issue仓库URL
|
||||
- 配置GitHub Token
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.config`
|
||||
|
||||
#### 操作流程
|
||||
1. 输入Issue仓库URL(可选)
|
||||
2. 输入GitHub Token
|
||||
3. 保存配置信息
|
||||
|
||||
#### 注意事项
|
||||
- GitHub Token应妥善保管,不要泄露
|
||||
- 配置信息将保存在本地文件中
|
@ -1,4 +0,0 @@
|
||||
description: 'Config required settings for GIT workflows.'
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py
|
@ -1,19 +0,0 @@
|
||||
### list_issue_tasks
|
||||
|
||||
列出指定Issue中的任务列表。
|
||||
|
||||
#### 用途
|
||||
- 查看Issue中的子任务
|
||||
- 跟踪任务进度
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.list_issue_tasks <issue_url>`
|
||||
|
||||
#### 操作流程
|
||||
1. 获取指定Issue的信息
|
||||
2. 解析Issue内容中的任务列表
|
||||
3. 显示任务列表
|
||||
|
||||
#### 注意事项
|
||||
- 需要提供有效的Issue URL
|
||||
- 任务应以特定格式在Issue中列出(如: - [ ] 任务描述)
|
@ -1,5 +0,0 @@
|
||||
description: 'List issue tasks.'
|
||||
input: required
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1,21 +0,0 @@
|
||||
### new_branch
|
||||
|
||||
基于当前分支创建新分支并切换到新分支。
|
||||
|
||||
#### 用途
|
||||
- 快速创建新的功能或修复分支
|
||||
- 保持工作区隔离
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.new_branch <description>`
|
||||
|
||||
- description: 新分支的简短描述或相关Issue URL
|
||||
|
||||
#### 操作流程
|
||||
1. 生成多个分支名建议
|
||||
2. 用户选择或编辑分支名
|
||||
3. 创建新分支并切换
|
||||
|
||||
#### 注意事项
|
||||
- 确保当前分支的更改已提交
|
||||
- 如提供Issue URL,会自动关联Issue编号到分支名
|
@ -1,21 +0,0 @@
|
||||
### new_issue
|
||||
|
||||
创建新的GitHub Issue。
|
||||
|
||||
#### 用途
|
||||
- 快速创建标准格式的Issue
|
||||
- 记录任务、bug或功能请求
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.new_issue <description>`
|
||||
|
||||
- description: Issue的简短描述
|
||||
|
||||
#### 操作流程
|
||||
1. 基于描述生成Issue标题和正文
|
||||
2. 允许用户编辑Issue内容
|
||||
3. 创建GitHub Issue
|
||||
|
||||
#### 注意事项
|
||||
- 需要有创建Issue的权限
|
||||
- 生成的内容可能需要进一步完善
|
@ -1,22 +0,0 @@
|
||||
### new_pr
|
||||
|
||||
创建新的Pull Request。
|
||||
|
||||
#### 用途
|
||||
- 自动生成PR标题和描述
|
||||
- 简化代码审查流程
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.new_pr [additional_info]`
|
||||
|
||||
- additional_info: 可选的附加信息
|
||||
|
||||
#### 操作流程
|
||||
1. 获取当前分支信息和相关Issue
|
||||
2. 生成PR标题和描述
|
||||
3. 允许用户编辑PR内容
|
||||
4. 创建Pull Request
|
||||
|
||||
#### 注意事项
|
||||
- 确保当前分支有未合并的更改
|
||||
- 需要有创建PR的权限
|
@ -1,22 +0,0 @@
|
||||
### update_issue_tasks
|
||||
|
||||
更新指定Issue中的任务列表。
|
||||
|
||||
#### 用途
|
||||
- 添加、修改或删除Issue中的子任务
|
||||
- 更新任务进度
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.update_issue_tasks`
|
||||
|
||||
#### 操作流程
|
||||
1. 输入Issue URL
|
||||
2. 显示当前任务列表
|
||||
3. 用户输入更新建议
|
||||
4. 生成新的任务列表
|
||||
5. 允许用户编辑新任务列表
|
||||
6. 更新Issue内容
|
||||
|
||||
#### 注意事项
|
||||
- 需要有编辑Issue的权限
|
||||
- 小心不要删除或覆盖重要信息
|
@ -1,5 +0,0 @@
|
||||
description: 'Update issue tasks.'
|
||||
input: required
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1,20 +0,0 @@
|
||||
### update_pr
|
||||
|
||||
更新现有的Pull Request。
|
||||
|
||||
#### 用途
|
||||
- 更新PR的标题和描述
|
||||
- 反映最新的代码变更
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.update_pr`
|
||||
|
||||
#### 操作流程
|
||||
1. 获取最近的PR信息
|
||||
2. 重新生成PR标题和描述
|
||||
3. 允许用户编辑PR内容
|
||||
4. 更新Pull Request
|
||||
|
||||
#### 注意事项
|
||||
- 确保有更新PR的权限
|
||||
- 更新前请确认是否有新的提交需要推送
|
@ -1,5 +0,0 @@
|
||||
description: 'Update PR.'
|
||||
input: required
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1,24 +0,0 @@
|
||||
### code_task_summary
|
||||
|
||||
根据当前分支或指定的Issue,生成代码任务摘要。
|
||||
|
||||
#### 用途
|
||||
- 自动生成简洁的代码任务描述
|
||||
- 帮助开发者快速理解任务要点
|
||||
- 用于更新项目配置或文档
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.code_task_summary [issue_url]`
|
||||
|
||||
- 如不提供issue_url,将基于当前分支名称提取Issue信息
|
||||
- 如提供issue_url,将直接使用该Issue的内容
|
||||
|
||||
#### 操作流程
|
||||
1. 获取Issue信息
|
||||
2. 生成代码任务摘要
|
||||
3. 允许用户编辑摘要
|
||||
4. 更新项目配置文件
|
||||
|
||||
#### 注意事项
|
||||
- 确保Git仓库配置正确
|
||||
- 需要有效的GitHub Token以访问API
|
@ -1,124 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from devchat.llm import chat_json
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
|
||||
from common_util import assert_exit, ui_edit # noqa: E402
|
||||
from git_api import ( # noqa: E402
|
||||
check_git_installed,
|
||||
get_current_branch,
|
||||
get_gitlab_issue_repo,
|
||||
get_issue_info,
|
||||
is_issue_url,
|
||||
read_issue_by_url,
|
||||
)
|
||||
|
||||
|
||||
def extract_issue_id(branch_name):
|
||||
if "#" in branch_name:
|
||||
return branch_name.split("#")[-1]
|
||||
return None
|
||||
|
||||
|
||||
# Function to generate a random branch name
|
||||
PROMPT = (
|
||||
"You are a coding engineer, required to summarize the ISSUE description into a coding task description of no more than 50 words. \n" # noqa: E501
|
||||
"The ISSUE description is as follows: {issue_body}, please summarize the corresponding coding task description.\n" # noqa: E501
|
||||
'The coding task description should be output in JSON format, in the form of: {{"summary": "code task summary"}}\n' # noqa: E501
|
||||
)
|
||||
|
||||
|
||||
@chat_json(prompt=PROMPT)
|
||||
def generate_code_task_summary(issue_body):
|
||||
pass
|
||||
|
||||
|
||||
@ui_edit(ui_type="editor", description="Edit code task summary:")
|
||||
def edit_code_task_summary(task_summary):
|
||||
pass
|
||||
|
||||
|
||||
def get_issue_or_task(task):
|
||||
if is_issue_url(task):
|
||||
issue = read_issue_by_url(task.strip())
|
||||
assert_exit(not issue, "Failed to read issue.", exit_code=-1)
|
||||
|
||||
return json.dumps(
|
||||
{"id": issue["iid"], "title": issue["title"], "description": issue["description"]}
|
||||
)
|
||||
else:
|
||||
return task
|
||||
|
||||
|
||||
def get_issue_json(issue_id, task):
|
||||
issue = {"id": "no issue id", "title": "", "description": task}
|
||||
if issue_id:
|
||||
issue = get_issue_info(issue_id)
|
||||
assert_exit(not issue, f"Failed to retrieve issue with ID: {issue_id}", exit_code=-1)
|
||||
issue = {
|
||||
"id": issue_id,
|
||||
"web_url": issue["web_url"],
|
||||
"title": issue["title"],
|
||||
"description": issue["description"],
|
||||
}
|
||||
return issue
|
||||
|
||||
|
||||
# Main function
|
||||
def main():
|
||||
print("Start update code task summary ...", end="\n\n", flush=True)
|
||||
|
||||
is_git_installed = check_git_installed()
|
||||
assert_exit(not is_git_installed, "Git is not installed.", exit_code=-1)
|
||||
|
||||
task = sys.argv[1]
|
||||
|
||||
repo_name = get_gitlab_issue_repo()
|
||||
branch_name = get_current_branch()
|
||||
issue_id = extract_issue_id(branch_name)
|
||||
|
||||
# print basic info, repo_name, branch_name, issue_id
|
||||
print("repo name:", repo_name, end="\n\n")
|
||||
print("branch name:", branch_name, end="\n\n")
|
||||
print("issue id:", issue_id, end="\n\n")
|
||||
|
||||
issue = get_issue_json(issue_id, task)
|
||||
assert_exit(
|
||||
not issue["description"], f"Failed to retrieve issue with ID: {issue_id}", exit_code=-1
|
||||
)
|
||||
|
||||
# Generate 5 branch names
|
||||
print("Generating code task summary ...", end="\n\n", flush=True)
|
||||
code_task_summary = generate_code_task_summary(issue_body=issue["description"])
|
||||
assert_exit(not code_task_summary, "Failed to generate code task summary.", exit_code=-1)
|
||||
assert_exit(
|
||||
not code_task_summary.get("summary", None),
|
||||
"Failed to generate code task summary, missing summary field in result.",
|
||||
exit_code=-1,
|
||||
)
|
||||
code_task_summary = code_task_summary["summary"]
|
||||
|
||||
# Select branch name
|
||||
code_task_summary = edit_code_task_summary(code_task_summary)
|
||||
assert_exit(not code_task_summary, "Failed to edit code task summary.", exit_code=-1)
|
||||
code_task_summary = code_task_summary[0]
|
||||
|
||||
# create and checkout branch
|
||||
print("Updating code task summary to config:")
|
||||
config_file = os.path.join(".chat", "complete.config")
|
||||
if os.path.exists(config_file):
|
||||
with open(config_file, "r") as f:
|
||||
config = json.load(f)
|
||||
config["taskDescription"] = code_task_summary
|
||||
else:
|
||||
config = {"taskDescription": code_task_summary}
|
||||
with open(config_file, "w") as f:
|
||||
json.dump(config, f, indent=4)
|
||||
print("Code task summary has updated")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,5 +0,0 @@
|
||||
description: 'Generate code task summary.'
|
||||
input: optional
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1 +0,0 @@
|
||||
description: Root of gitlab commands.
|
@ -1,23 +0,0 @@
|
||||
### commit
|
||||
|
||||
自动生成提交信息并执行Git提交。
|
||||
|
||||
#### 用途
|
||||
- 生成规范的提交信息
|
||||
- 简化Git提交流程
|
||||
- 保持提交历史的一致性
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.commit [message]`
|
||||
|
||||
- message: 可选的用户输入,用于辅助生成提交信息
|
||||
|
||||
#### 操作流程
|
||||
1. 选择要提交的文件
|
||||
2. 生成提交信息
|
||||
3. 允许用户编辑提交信息
|
||||
4. 执行Git提交
|
||||
|
||||
#### 注意事项
|
||||
- 确保已选择需要提交的文件
|
||||
- 生成的提交信息可能需要进一步修改以符合项目规范
|
@ -1,5 +0,0 @@
|
||||
description: '为你选定的代码变更生成格式规范的提交信息,并通过 Git 提交。如需要可包含对应 issue 编号(例如,输入“/commit to close #12”)'
|
||||
hint: to close Issue #issue_number
|
||||
input: optional
|
||||
steps:
|
||||
- run: $devchat_python $command_path/../commit.py "$input" "chinese"
|
@ -1,78 +0,0 @@
|
||||
import functools
|
||||
import sys
|
||||
|
||||
from lib.chatmark import Checkbox, Form, Radio, TextEditor
|
||||
|
||||
|
||||
def create_ui_objs(ui_decls, args):
|
||||
ui_objs = []
|
||||
editors = []
|
||||
for i, ui in enumerate(ui_decls):
|
||||
editor = ui[0](args[i])
|
||||
if ui[1]:
|
||||
# this is the title of UI object
|
||||
editors.append(ui[1])
|
||||
editors.append(editor)
|
||||
ui_objs.append(editor)
|
||||
return ui_objs, editors
|
||||
|
||||
|
||||
def edit_form(uis, args):
|
||||
ui_objs, editors = create_ui_objs(uis, args)
|
||||
form = Form(editors)
|
||||
form.render()
|
||||
|
||||
values = []
|
||||
for obj in ui_objs:
|
||||
if isinstance(obj, TextEditor):
|
||||
values.append(obj.new_text)
|
||||
elif isinstance(obj, Radio):
|
||||
values.append(obj.selection)
|
||||
else:
|
||||
# TODO
|
||||
pass
|
||||
return values
|
||||
|
||||
|
||||
def editor(description):
|
||||
def decorator_edit(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
uis = wrapper.uis[::-1]
|
||||
return edit_form(uis, args)
|
||||
|
||||
if hasattr(func, "uis"):
|
||||
wrapper.uis = func.uis
|
||||
else:
|
||||
wrapper.uis = []
|
||||
wrapper.uis.append((TextEditor, description))
|
||||
return wrapper
|
||||
|
||||
return decorator_edit
|
||||
|
||||
|
||||
def ui_edit(ui_type, description):
|
||||
def decorator_edit(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
uis = wrapper.uis[::-1]
|
||||
return edit_form(uis, args)
|
||||
|
||||
if hasattr(func, "uis"):
|
||||
wrapper.uis = func.uis
|
||||
else:
|
||||
wrapper.uis = []
|
||||
ui_type_class = {"editor": TextEditor, "radio": Radio, "checkbox": Checkbox}[ui_type]
|
||||
wrapper.uis.append((ui_type_class, description))
|
||||
return wrapper
|
||||
|
||||
return decorator_edit
|
||||
|
||||
|
||||
def assert_exit(condition, message, exit_code=-1):
|
||||
if condition:
|
||||
if exit_code == 0:
|
||||
print(message, end="\n\n", flush=True)
|
||||
else:
|
||||
print(message, end="\n\n", file=sys.stderr, flush=True)
|
||||
sys.exit(exit_code)
|
@ -1,19 +0,0 @@
|
||||
### config
|
||||
|
||||
配置GitHub工作流所需的设置。
|
||||
|
||||
#### 用途
|
||||
- 设置Issue仓库URL
|
||||
- 配置GitHub Token
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.config`
|
||||
|
||||
#### 操作流程
|
||||
1. 输入Issue仓库URL(可选)
|
||||
2. 输入GitHub Token
|
||||
3. 保存配置信息
|
||||
|
||||
#### 注意事项
|
||||
- GitHub Token应妥善保管,不要泄露
|
||||
- 配置信息将保存在本地文件中
|
@ -1,88 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
|
||||
from common_util import editor # noqa: E402
|
||||
|
||||
|
||||
def read_issue_url():
|
||||
config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json")
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
if "git_issue_repo" in config_data:
|
||||
return config_data["git_issue_repo"]
|
||||
return ""
|
||||
|
||||
|
||||
def save_issue_url(issue_url):
|
||||
config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json")
|
||||
# make dirs
|
||||
os.makedirs(os.path.dirname(config_path), exist_ok=True)
|
||||
|
||||
config_data = {}
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
|
||||
config_data["git_issue_repo"] = issue_url
|
||||
with open(config_path, "w+", encoding="utf-8") as f:
|
||||
json.dump(config_data, f, indent=4)
|
||||
|
||||
|
||||
def read_gitlab_token():
|
||||
config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
if "gitlab_token" in config_data:
|
||||
return config_data["gitlab_token"]
|
||||
return ""
|
||||
|
||||
|
||||
def save_gitlab_token(github_token):
|
||||
config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
||||
|
||||
config_data = {}
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
|
||||
config_data["gitlab_token"] = github_token
|
||||
with open(config_path, "w+", encoding="utf-8") as f:
|
||||
json.dump(config_data, f, indent=4)
|
||||
|
||||
|
||||
@editor(
|
||||
"Please specify the issue's repository, "
|
||||
"If the issue is within this repository, no need to specify. "
|
||||
"Otherwise, format as: username/repository-name"
|
||||
)
|
||||
@editor("Input your github TOKEN to access github api:")
|
||||
def edit_issue(issue_url, github_token):
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
print("start config git settings ...", end="\n\n", flush=True)
|
||||
|
||||
issue_url = read_issue_url()
|
||||
github_token = read_gitlab_token()
|
||||
|
||||
issue_url, github_token = edit_issue(issue_url, github_token)
|
||||
if issue_url:
|
||||
save_issue_url(issue_url)
|
||||
if github_token:
|
||||
save_gitlab_token(github_token)
|
||||
else:
|
||||
print("Please specify the github token to access github api.")
|
||||
sys.exit(0)
|
||||
|
||||
print("config git settings successfully.")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,611 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
import requests
|
||||
|
||||
from lib.chatmark import TextEditor
|
||||
from lib.ide_service import IDEService
|
||||
|
||||
|
||||
def read_gitlab_token():
|
||||
config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
if "gitlab_token" in config_data:
|
||||
return config_data["gitlab_token"]
|
||||
|
||||
# ask user to input gitlab token
|
||||
server_access_token_editor = TextEditor("", "Please input your GitLab access TOKEN to access:")
|
||||
server_access_token_editor.render()
|
||||
|
||||
server_access_token = server_access_token_editor.new_text
|
||||
if not server_access_token:
|
||||
print("Please input your GitLab access TOKEN to continue.")
|
||||
sys.exit(-1)
|
||||
return server_access_token
|
||||
|
||||
|
||||
current_repo_dir = None
|
||||
|
||||
|
||||
def get_current_repo():
|
||||
"""
|
||||
获取当前文件所在的仓库信息
|
||||
"""
|
||||
global current_repo_dir
|
||||
|
||||
if not current_repo_dir:
|
||||
selected_data = IDEService().get_selected_range().dict()
|
||||
current_file = selected_data.get("abspath", None)
|
||||
if not current_file:
|
||||
return None
|
||||
current_dir = os.path.dirname(current_file)
|
||||
try:
|
||||
# 获取仓库根目录
|
||||
current_repo_dir = (
|
||||
subprocess.check_output(
|
||||
["git", "rev-parse", "--show-toplevel"],
|
||||
stderr=subprocess.DEVNULL,
|
||||
cwd=current_dir,
|
||||
)
|
||||
.decode("utf-8")
|
||||
.strip()
|
||||
)
|
||||
except subprocess.CalledProcessError:
|
||||
# 如果发生错误,可能不在git仓库中
|
||||
return None
|
||||
return current_repo_dir
|
||||
|
||||
|
||||
def subprocess_check_output(*popenargs, timeout=None, **kwargs):
|
||||
# 将 current_dir 添加到 kwargs 中的 cwd 参数
|
||||
current_repo = get_current_repo()
|
||||
if current_repo:
|
||||
kwargs["cwd"] = kwargs.get("cwd", current_repo)
|
||||
|
||||
# 调用 subprocess.check_output
|
||||
return subprocess.check_output(*popenargs, timeout=timeout, **kwargs)
|
||||
|
||||
|
||||
def subprocess_run(
|
||||
*popenargs, input=None, capture_output=False, timeout=None, check=False, **kwargs
|
||||
):
|
||||
current_repo = get_current_repo()
|
||||
if current_repo:
|
||||
kwargs["cwd"] = kwargs.get("cwd", current_repo)
|
||||
|
||||
# 调用 subprocess.run
|
||||
return subprocess.run(
|
||||
*popenargs,
|
||||
input=input,
|
||||
capture_output=capture_output,
|
||||
timeout=timeout,
|
||||
check=check,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
def subprocess_call(*popenargs, timeout=None, **kwargs):
|
||||
current_repo = get_current_repo()
|
||||
if current_repo:
|
||||
kwargs["cwd"] = kwargs.get("cwd", current_repo)
|
||||
|
||||
# 调用 subprocess.call
|
||||
return subprocess.call(*popenargs, timeout=timeout, **kwargs)
|
||||
|
||||
|
||||
def subprocess_check_call(*popenargs, timeout=None, **kwargs):
|
||||
current_repo = get_current_repo()
|
||||
if current_repo:
|
||||
kwargs["cwd"] = kwargs.get("cwd", current_repo)
|
||||
|
||||
# 调用 subprocess.check_call
|
||||
return subprocess.check_call(*popenargs, timeout=timeout, **kwargs)
|
||||
|
||||
|
||||
GITLAB_ACCESS_TOKEN = read_gitlab_token()
|
||||
GITLAB_API_URL = "https://gitlab.com/api/v4"
|
||||
|
||||
|
||||
def create_issue(title, description):
|
||||
headers = {
|
||||
"Private-Token": GITLAB_ACCESS_TOKEN,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
data = {
|
||||
"title": title,
|
||||
"description": description,
|
||||
}
|
||||
project_id = get_gitlab_project_id()
|
||||
issue_api_url = f"{GITLAB_API_URL}/projects/{project_id}/issues"
|
||||
response = requests.post(issue_api_url, headers=headers, json=data)
|
||||
|
||||
if response.status_code == 201:
|
||||
print("Issue created successfully!")
|
||||
return response.json()
|
||||
else:
|
||||
print(f"Failed to create issue: {response.content}", file=sys.stderr, end="\n\n")
|
||||
return None
|
||||
|
||||
|
||||
def update_issue_body(issue_iid, issue_body):
|
||||
headers = {
|
||||
"Private-Token": GITLAB_ACCESS_TOKEN,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
data = {
|
||||
"description": issue_body,
|
||||
}
|
||||
|
||||
project_id = get_gitlab_project_id()
|
||||
api_url = f"{GITLAB_API_URL}/projects/{project_id}/issues/{issue_iid}"
|
||||
response = requests.put(api_url, headers=headers, json=data)
|
||||
|
||||
if response.status_code == 200:
|
||||
print("Issue updated successfully!")
|
||||
return response.json()
|
||||
else:
|
||||
print(f"Failed to update issue: {response.status_code}")
|
||||
return None
|
||||
|
||||
|
||||
def get_gitlab_project_id():
|
||||
try:
|
||||
result = subprocess_check_output(
|
||||
["git", "remote", "get-url", "origin"], stderr=subprocess.STDOUT
|
||||
).strip()
|
||||
repo_url = result.decode("utf-8")
|
||||
print(f"Original repo URL: {repo_url}", file=sys.stderr)
|
||||
|
||||
if repo_url.startswith("git@"):
|
||||
# Handle SSH URL format
|
||||
parts = repo_url.split(":")
|
||||
project_path = parts[1].replace(".git", "")
|
||||
elif repo_url.startswith("https://"):
|
||||
# Handle HTTPS URL format
|
||||
parts = repo_url.split("/")
|
||||
project_path = "/".join(parts[3:]).replace(".git", "")
|
||||
else:
|
||||
raise ValueError(f"Unsupported Git URL format: {repo_url}")
|
||||
|
||||
print(f"Extracted project path: {project_path}", file=sys.stderr)
|
||||
encoded_project_path = requests.utils.quote(project_path, safe="")
|
||||
print(f"Encoded project path: {encoded_project_path}", file=sys.stderr)
|
||||
return encoded_project_path
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error executing git command: {e}", file=sys.stderr)
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Error in get_gitlab_project_id: {e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
# parse sub tasks in issue description
|
||||
def parse_sub_tasks(description):
|
||||
sub_tasks = []
|
||||
lines = description.split("\n")
|
||||
for line in lines:
|
||||
if line.startswith("- ["):
|
||||
sub_tasks.append(line[2:])
|
||||
return sub_tasks
|
||||
|
||||
|
||||
def update_sub_tasks(description, tasks):
|
||||
# remove all existing tasks
|
||||
lines = description.split("\n")
|
||||
updated_body = "\n".join(line for line in lines if not line.startswith("- ["))
|
||||
|
||||
# add new tasks
|
||||
updated_body += "\n" + "\n".join(f"- {task}" for task in tasks)
|
||||
|
||||
return updated_body
|
||||
|
||||
|
||||
def update_task_issue_url(description, task, issue_url):
|
||||
# task is like:
|
||||
# [ ] task name
|
||||
# [x] task name
|
||||
# replace task name with issue url, like:
|
||||
# [ ] [task name](url)
|
||||
# [x] [task name](url)
|
||||
if task.find("] ") == -1:
|
||||
return None
|
||||
task = task[task.find("] ") + 2 :]
|
||||
return description.replace(task, f"[{task}]({issue_url})")
|
||||
|
||||
|
||||
def check_git_installed():
|
||||
"""
|
||||
Check if Git is installed on the local machine.
|
||||
|
||||
Tries to execute 'git --version' command to determine the presence of Git.
|
||||
|
||||
Returns:
|
||||
bool: True if Git is installed, False otherwise.
|
||||
"""
|
||||
try:
|
||||
subprocess_run(
|
||||
["git", "--version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||
)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
print("Git is not installed on your system.")
|
||||
return False
|
||||
|
||||
|
||||
def create_and_checkout_branch(branch_name):
|
||||
subprocess_run(["git", "checkout", "-b", branch_name], check=True)
|
||||
|
||||
|
||||
def is_issue_url(task):
|
||||
task = task.strip()
|
||||
|
||||
# 使用正则表达式匹配 http 或 https 开头,issues/数字 结尾的 URL
|
||||
pattern = r"^(http|https)://.*?/issues/\d+$"
|
||||
|
||||
is_issue = bool(re.match(pattern, task))
|
||||
|
||||
# print(f"Task to check: {task}", file=sys.stderr)
|
||||
# print(f"Is issue URL: {is_issue}", file=sys.stderr)
|
||||
|
||||
return is_issue
|
||||
|
||||
|
||||
def read_issue_by_url(issue_url):
|
||||
# Extract the issue number and project path from the URL
|
||||
issue_url = issue_url.replace("/-/", "/")
|
||||
parts = issue_url.split("/")
|
||||
issue_number = parts[-1]
|
||||
project_path = "/".join(
|
||||
parts[3:-2]
|
||||
) # Assumes URL format: https://gitlab.com/project/path/-/issues/number
|
||||
|
||||
# URL encode the project path
|
||||
encoded_project_path = requests.utils.quote(project_path, safe="")
|
||||
|
||||
# Construct the API endpoint URL
|
||||
api_url = f"{GITLAB_API_URL}/projects/{encoded_project_path}/issues/{issue_number}"
|
||||
|
||||
# Send a GET request to the API endpoint
|
||||
headers = {
|
||||
"Private-Token": GITLAB_ACCESS_TOKEN,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
response = requests.get(api_url, headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
print(f"Error fetching issue: {response.status_code}", file=sys.stderr)
|
||||
print(f"Response content: {response.text}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
def get_gitlab_issue_repo(issue_repo=False):
|
||||
try:
|
||||
config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json")
|
||||
if os.path.exists(config_path) and issue_repo:
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
|
||||
if "git_issue_repo" in config_data:
|
||||
issue_repo = requests.utils.quote(config_data["git_issue_repo"], safe="")
|
||||
print(
|
||||
"current issue repo:",
|
||||
config_data["git_issue_repo"],
|
||||
end="\n\n",
|
||||
file=sys.stderr,
|
||||
flush=True,
|
||||
)
|
||||
return config_data["git_issue_repo"]
|
||||
|
||||
return get_gitlab_project_id()
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(e)
|
||||
# 如果发生错误,打印错误信息
|
||||
return None
|
||||
except FileNotFoundError:
|
||||
# 如果未找到git命令,可能是没有安装git或者不在PATH中
|
||||
print("==> File not found...")
|
||||
return None
|
||||
|
||||
|
||||
# 获取当前分支名称
|
||||
def get_current_branch():
|
||||
try:
|
||||
# 使用git命令获取当前分支名称
|
||||
result = subprocess_check_output(
|
||||
["git", "branch", "--show-current"], stderr=subprocess.STDOUT
|
||||
).strip()
|
||||
# 将结果从bytes转换为str
|
||||
current_branch = result.decode("utf-8")
|
||||
return current_branch
|
||||
except subprocess.CalledProcessError:
|
||||
# 如果发生错误,打印错误信息
|
||||
return None
|
||||
except FileNotFoundError:
|
||||
# 如果未找到git命令,可能是没有安装git或者不在PATH中
|
||||
return None
|
||||
|
||||
|
||||
def get_parent_branch():
|
||||
current_branch = get_current_branch()
|
||||
if current_branch is None:
|
||||
return None
|
||||
try:
|
||||
# 使用git命令获取当前分支的父分支引用
|
||||
result = subprocess_check_output(
|
||||
["git", "rev-parse", "--abbrev-ref", f"{current_branch}@{1}"], stderr=subprocess.STDOUT
|
||||
).strip()
|
||||
# 将结果从bytes转换为str
|
||||
parent_branch_ref = result.decode("utf-8")
|
||||
if parent_branch_ref == current_branch:
|
||||
# 如果父分支引用和当前分支相同,说明当前分支可能是基于一个没有父分支的提交创建的
|
||||
return None
|
||||
# 使用git命令获取父分支的名称
|
||||
result = subprocess_check_output(
|
||||
["git", "name-rev", "--name-only", "--exclude=tags/*", parent_branch_ref],
|
||||
stderr=subprocess.STDOUT,
|
||||
).strip()
|
||||
parent_branch_name = result.decode("utf-8")
|
||||
return parent_branch_name
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(e)
|
||||
# 如果发生错误,打印错误信息
|
||||
return None
|
||||
except FileNotFoundError:
|
||||
# 如果未找到git命令,可能是没有安装git或者不在PATH中
|
||||
print("==> File not found...")
|
||||
return None
|
||||
|
||||
|
||||
def get_issue_info(issue_id):
|
||||
# 获取 GitLab 项目 ID
|
||||
project_id = get_gitlab_issue_repo()
|
||||
# 构造 GitLab API 端点 URL
|
||||
api_url = f"{GITLAB_API_URL}/projects/{project_id}/issues/{issue_id}"
|
||||
|
||||
# 发送 GET 请求到 API 端点
|
||||
headers = {
|
||||
"Private-Token": GITLAB_ACCESS_TOKEN,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
response = requests.get(api_url, headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
print(f"Failed to get issue info. Status code: {response.status_code}", file=sys.stderr)
|
||||
print(f"Response content: {response.text}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
def get_issue_info_by_url(issue_url):
|
||||
# get issue id from issue_url
|
||||
def get_issue_id(issue_url):
|
||||
# Extract the issue id from the issue_url
|
||||
issue_id = issue_url.split("/")[-1]
|
||||
return issue_id
|
||||
|
||||
return get_issue_info(get_issue_id(issue_url))
|
||||
|
||||
|
||||
# 获取当前分支自从与base_branch分叉以来的历史提交信息
|
||||
def get_commit_messages(base_branch):
|
||||
# 找到当前分支与base_branch的分叉点
|
||||
merge_base = subprocess_run(
|
||||
["git", "merge-base", "HEAD", base_branch],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
|
||||
# 检查是否成功找到分叉点
|
||||
if merge_base.returncode != 0:
|
||||
raise RuntimeError(f"Error finding merge base: {merge_base.stderr.strip()}")
|
||||
|
||||
# 获取分叉点的提交哈希
|
||||
merge_base_commit = merge_base.stdout.strip()
|
||||
|
||||
# 获取从分叉点到当前分支的所有提交信息
|
||||
result = subprocess_run(
|
||||
["git", "log", f"{merge_base_commit}..HEAD"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
|
||||
# 检查git log命令是否成功执行
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(f"Error retrieving commit messages: {result.stderr.strip()}")
|
||||
|
||||
# 返回提交信息列表
|
||||
return result.stdout
|
||||
|
||||
|
||||
# 创建PR
|
||||
def create_pull_request(title, description, source_branch, target_branch, project_id):
|
||||
url = f"{GITLAB_API_URL}/projects/{project_id}/merge_requests"
|
||||
headers = {"Private-Token": GITLAB_ACCESS_TOKEN, "Content-Type": "application/json"}
|
||||
payload = {
|
||||
"title": title,
|
||||
"description": description,
|
||||
"source_branch": source_branch,
|
||||
"target_branch": target_branch,
|
||||
}
|
||||
|
||||
response = requests.post(url, headers=headers, json=payload)
|
||||
if response.status_code == 201:
|
||||
response_json = response.json()
|
||||
return response_json
|
||||
|
||||
print(response.text, end="\n\n", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
def get_recently_mr(project_id):
|
||||
project_id = requests.utils.quote(project_id, safe="")
|
||||
url = (
|
||||
f"{GITLAB_API_URL}/projects/{project_id}/"
|
||||
"merge_requests?state=opened&order_by=updated_at&sort=desc"
|
||||
)
|
||||
headers = {
|
||||
"Private-Token": GITLAB_ACCESS_TOKEN,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
response = requests.get(url, headers=headers)
|
||||
|
||||
branch_name = get_current_branch()
|
||||
|
||||
if response.status_code == 200:
|
||||
mrs = response.json()
|
||||
for mr in mrs:
|
||||
if mr["source_branch"] == branch_name:
|
||||
return mr
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def run_command_with_retries(command, retries=3, delay=5):
|
||||
for attempt in range(retries):
|
||||
try:
|
||||
subprocess_check_call(command)
|
||||
return True
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Command failed: {e}")
|
||||
if attempt < retries - 1:
|
||||
print(f"Retrying... (attempt {attempt + 1}/{retries})")
|
||||
time.sleep(delay)
|
||||
else:
|
||||
print("All retries failed.")
|
||||
return False
|
||||
|
||||
|
||||
def update_mr(project_id, mr_iid, title, description):
|
||||
project_id = requests.utils.quote(project_id, safe="")
|
||||
url = f"{GITLAB_API_URL}/projects/{project_id}/merge_requests/{mr_iid}"
|
||||
headers = {"Private-Token": GITLAB_ACCESS_TOKEN, "Content-Type": "application/json"}
|
||||
payload = {"title": title, "description": description}
|
||||
response = requests.put(url, headers=headers, json=payload)
|
||||
|
||||
if response.status_code == 200:
|
||||
print(f"MR updated successfully: {response.json()['web_url']}")
|
||||
return response.json()
|
||||
else:
|
||||
print("Failed to update MR.")
|
||||
return None
|
||||
|
||||
|
||||
def check_unpushed_commits():
|
||||
try:
|
||||
# 获取当前分支的本地提交和远程提交的差异
|
||||
result = subprocess_check_output(["git", "cherry", "-v"]).decode("utf-8").strip()
|
||||
# 如果结果不为空,说明存在未push的提交
|
||||
return bool(result)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error checking for unpushed commits: {e}")
|
||||
return True
|
||||
|
||||
|
||||
def auto_push():
|
||||
# 获取当前分支名
|
||||
if not check_unpushed_commits():
|
||||
return True
|
||||
try:
|
||||
branch = (
|
||||
subprocess_check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"])
|
||||
.strip()
|
||||
.decode("utf-8")
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error getting current branch: {e}")
|
||||
return False
|
||||
|
||||
# 检查当前分支是否有对应的远程分支
|
||||
remote_branch_exists = subprocess_call(["git", "ls-remote", "--exit-code", "origin", branch])
|
||||
|
||||
push_command = ["git", "push", "origin", branch]
|
||||
if remote_branch_exists == 0:
|
||||
# 如果存在远程分支,则直接push提交
|
||||
return run_command_with_retries(push_command)
|
||||
else:
|
||||
# 如果不存在远程分支,则发布并push提交
|
||||
push_command.append("-u")
|
||||
return run_command_with_retries(push_command)
|
||||
|
||||
|
||||
def get_recently_pr(repo):
|
||||
url = f"{GITLAB_API_URL}/repos/{repo}/pulls?state=open&sort=updated"
|
||||
headers = {
|
||||
"Authorization": f"token {GITLAB_ACCESS_TOKEN}",
|
||||
"Accept": "application/vnd.github.v3+json",
|
||||
}
|
||||
response = requests.get(url, headers=headers)
|
||||
|
||||
branch_name = get_current_branch()
|
||||
|
||||
if response.status_code == 200:
|
||||
prs = response.json()
|
||||
for pr in prs:
|
||||
if pr["head"]["ref"] == branch_name:
|
||||
return pr
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def update_pr(pr_number, title, description, repo_name):
|
||||
url = f"{GITLAB_API_URL}/repos/{repo_name}/pulls/{pr_number}"
|
||||
headers = {"Authorization": f"token {GITLAB_ACCESS_TOKEN}", "Content-Type": "application/json"}
|
||||
payload = {"title": title, "description": description}
|
||||
response = requests.patch(url, headers=headers, data=json.dumps(payload))
|
||||
|
||||
if response.status_code == 200:
|
||||
print(f"PR updated successfully: {response.json()['web_url']}")
|
||||
return response.json()
|
||||
else:
|
||||
print("Failed to update PR.")
|
||||
return None
|
||||
|
||||
|
||||
def get_last_base_branch(default_branch):
|
||||
"""read last base branch from config file"""
|
||||
|
||||
def read_config_item(config_path, item):
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
return config.get(item)
|
||||
return None
|
||||
|
||||
project_config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json")
|
||||
last_base_branch = read_config_item(project_config_path, "last_base_branch")
|
||||
if last_base_branch:
|
||||
return last_base_branch
|
||||
return default_branch
|
||||
|
||||
|
||||
def save_last_base_branch(base_branch=None):
|
||||
"""save last base branch to config file"""
|
||||
|
||||
def save_config_item(config_path, item, value):
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
else:
|
||||
config = {}
|
||||
|
||||
config[item] = value
|
||||
with open(config_path, "w", encoding="utf-8") as f:
|
||||
json.dump(config, f, indent=4)
|
||||
|
||||
if not base_branch:
|
||||
base_branch = get_current_branch()
|
||||
project_config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json")
|
||||
save_config_item(project_config_path, "last_base_branch", base_branch)
|
@ -1,19 +0,0 @@
|
||||
### list_issue_tasks
|
||||
|
||||
列出指定Issue中的任务列表。
|
||||
|
||||
#### 用途
|
||||
- 查看Issue中的子任务
|
||||
- 跟踪任务进度
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.list_issue_tasks <issue_url>`
|
||||
|
||||
#### 操作流程
|
||||
1. 获取指定Issue的信息
|
||||
2. 解析Issue内容中的任务列表
|
||||
3. 显示任务列表
|
||||
|
||||
#### 注意事项
|
||||
- 需要提供有效的Issue URL
|
||||
- 任务应以特定格式在Issue中列出(如: - [ ] 任务描述)
|
@ -1,53 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from devchat.llm import chat_json
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
|
||||
from common_util import assert_exit, editor # noqa: E402
|
||||
from git_api import create_issue # noqa: E402
|
||||
|
||||
# Function to generate issue title and description using LLM
|
||||
PROMPT = (
|
||||
"Based on the following description, "
|
||||
"suggest a title and a detailed description for a GitHub issue:\n\n"
|
||||
"Description: {description}\n\n"
|
||||
'Output format: {{"title": "<title>", "description": "<description>"}} '
|
||||
)
|
||||
|
||||
|
||||
@chat_json(prompt=PROMPT)
|
||||
def generate_issue_content(description):
|
||||
pass
|
||||
|
||||
|
||||
@editor("Edit issue title:")
|
||||
@editor("Edit issue description:")
|
||||
def edit_issue(title, description):
|
||||
pass
|
||||
|
||||
|
||||
# Main function
|
||||
def main():
|
||||
print("start new_issue ...", end="\n\n", flush=True)
|
||||
|
||||
assert_exit(len(sys.argv) < 2, "Missing argument.", exit_code=-1)
|
||||
description = sys.argv[1]
|
||||
|
||||
print("Generating issue content ...", end="\n\n", flush=True)
|
||||
issue_json_ob = generate_issue_content(description=description)
|
||||
assert_exit(not issue_json_ob, "Failed to generate issue content.", exit_code=-1)
|
||||
|
||||
issue_title, issue_body = edit_issue(issue_json_ob["title"], issue_json_ob["description"])
|
||||
assert_exit(not issue_title, "Issue creation cancelled.", exit_code=0)
|
||||
print("New Issue:", issue_title, "description:", issue_body, end="\n\n", flush=True)
|
||||
|
||||
print("Creating issue ...", end="\n\n", flush=True)
|
||||
issue = create_issue(issue_title, issue_body)
|
||||
assert_exit(not issue, "Failed to create issue.", exit_code=-1)
|
||||
print("New Issue:", issue["web_url"], end="\n\n", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,5 +0,0 @@
|
||||
description: 'List issue tasks.'
|
||||
input: required
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1,21 +0,0 @@
|
||||
### new_branch
|
||||
|
||||
基于当前分支创建新分支并切换到新分支。
|
||||
|
||||
#### 用途
|
||||
- 快速创建新的功能或修复分支
|
||||
- 保持工作区隔离
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.new_branch <description>`
|
||||
|
||||
- description: 新分支的简短描述或相关Issue URL
|
||||
|
||||
#### 操作流程
|
||||
1. 生成多个分支名建议
|
||||
2. 用户选择或编辑分支名
|
||||
3. 创建新分支并切换
|
||||
|
||||
#### 注意事项
|
||||
- 确保当前分支的更改已提交
|
||||
- 如提供Issue URL,会自动关联Issue编号到分支名
|
@ -1,95 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from devchat.llm import chat_json
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
|
||||
from common_util import assert_exit, ui_edit # noqa: E402
|
||||
from git_api import ( # noqa: E402
|
||||
check_git_installed,
|
||||
create_and_checkout_branch,
|
||||
is_issue_url,
|
||||
read_issue_by_url,
|
||||
save_last_base_branch,
|
||||
)
|
||||
|
||||
# Function to generate a random branch name
|
||||
PROMPT = (
|
||||
"Give me 5 different git branch names, "
|
||||
"mainly hoping to express: {task}, "
|
||||
"Good branch name should looks like: <type>/<main content>,"
|
||||
"the final result is output in JSON format, "
|
||||
'as follows: {{"names":["name1", "name2", .. "name5"]}}\n'
|
||||
)
|
||||
|
||||
|
||||
@chat_json(prompt=PROMPT)
|
||||
def generate_branch_name(task):
|
||||
pass
|
||||
|
||||
|
||||
@ui_edit(ui_type="radio", description="Select a branch name")
|
||||
def select_branch_name_ui(branch_names):
|
||||
pass
|
||||
|
||||
|
||||
def select_branch_name(branch_names):
|
||||
[branch_selection] = select_branch_name_ui(branch_names)
|
||||
assert_exit(branch_selection is None, "No branch selected.", exit_code=0)
|
||||
return branch_names[branch_selection]
|
||||
|
||||
|
||||
def get_issue_or_task(task):
|
||||
if is_issue_url(task):
|
||||
issue = read_issue_by_url(task.strip())
|
||||
assert_exit(not issue, "Failed to read issue.", exit_code=-1)
|
||||
|
||||
return json.dumps(
|
||||
{"id": issue["iid"], "title": issue["title"], "description": issue["description"]}
|
||||
), issue["iid"]
|
||||
else:
|
||||
return task, None
|
||||
|
||||
|
||||
# Main function
|
||||
def main():
|
||||
print("Start create branch ...", end="\n\n", flush=True)
|
||||
|
||||
is_git_installed = check_git_installed()
|
||||
assert_exit(not is_git_installed, "Git is not installed.", exit_code=-1)
|
||||
|
||||
task = sys.argv[1]
|
||||
assert_exit(
|
||||
not task,
|
||||
"You need input something about the new branch, or input a issue url.",
|
||||
exit_code=-1,
|
||||
)
|
||||
|
||||
# read issue by url
|
||||
task, issue_id = get_issue_or_task(task)
|
||||
|
||||
# Generate 5 branch names
|
||||
print("Generating branch names ...", end="\n\n", flush=True)
|
||||
branch_names = generate_branch_name(task=task)
|
||||
assert_exit(not branch_names, "Failed to generate branch names.", exit_code=-1)
|
||||
branch_names = branch_names["names"]
|
||||
for index, branch_name in enumerate(branch_names):
|
||||
if issue_id:
|
||||
branch_names[index] = f"{branch_name}-#{issue_id}"
|
||||
|
||||
# Select branch name
|
||||
selected_branch = select_branch_name(branch_names)
|
||||
|
||||
# save base branch name
|
||||
save_last_base_branch()
|
||||
|
||||
# create and checkout branch
|
||||
print(f"Creating and checking out branch: {selected_branch}")
|
||||
create_and_checkout_branch(selected_branch)
|
||||
print("Branch has create and checkout")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,21 +0,0 @@
|
||||
### new_issue
|
||||
|
||||
创建新的GitHub Issue。
|
||||
|
||||
#### 用途
|
||||
- 快速创建标准格式的Issue
|
||||
- 记录任务、bug或功能请求
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.new_issue <description>`
|
||||
|
||||
- description: Issue的简短描述
|
||||
|
||||
#### 操作流程
|
||||
1. 基于描述生成Issue标题和正文
|
||||
2. 允许用户编辑Issue内容
|
||||
3. 创建GitHub Issue
|
||||
|
||||
#### 注意事项
|
||||
- 需要有创建Issue的权限
|
||||
- 生成的内容可能需要进一步完善
|
@ -1,52 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
|
||||
from common_util import assert_exit, editor # noqa: E402
|
||||
from devchat.llm import chat_json # noqa: E402
|
||||
from git_api import create_issue # noqa: E402
|
||||
|
||||
# Function to generate issue title and description using LLM
|
||||
PROMPT = (
|
||||
"Based on the following description, "
|
||||
"suggest a title and a detailed description for a GitHub issue:\n\n"
|
||||
"Description: {description}\n\n"
|
||||
'Output as valid JSON format: {{"title": "<title>", "description": "<description> use \\n as new line flag."}} ' # noqa: E501
|
||||
)
|
||||
|
||||
|
||||
@chat_json(prompt=PROMPT)
|
||||
def generate_issue_content(description):
|
||||
pass
|
||||
|
||||
|
||||
@editor("Edit issue title:")
|
||||
@editor("Edit issue description:")
|
||||
def edit_issue(title, description):
|
||||
pass
|
||||
|
||||
|
||||
# Main function
|
||||
def main():
|
||||
print("start new_issue ...", end="\n\n", flush=True)
|
||||
|
||||
assert_exit(len(sys.argv) < 2, "Missing argument.", exit_code=-1)
|
||||
description = sys.argv[1]
|
||||
|
||||
print("Generating issue content ...", end="\n\n", flush=True)
|
||||
issue_json_ob = generate_issue_content(description=description)
|
||||
assert_exit(not issue_json_ob, "Failed to generate issue content.", exit_code=-1)
|
||||
|
||||
issue_title, issue_body = edit_issue(issue_json_ob["title"], issue_json_ob["description"])
|
||||
assert_exit(not issue_title, "Issue creation cancelled.", exit_code=0)
|
||||
print("New Issue:", issue_title, "description:", issue_body, end="\n\n", flush=True)
|
||||
|
||||
print("Creating issue ...", end="\n\n", flush=True)
|
||||
issue = create_issue(issue_title, issue_body)
|
||||
assert_exit(not issue, "Failed to create issue.", exit_code=-1)
|
||||
print("New Issue:", issue["web_url"], end="\n\n", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,94 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".."))
|
||||
|
||||
from common_util import assert_exit, editor, ui_edit # noqa: E402
|
||||
from devchat.llm import chat_json # noqa: E402
|
||||
from git_api import ( # noqa: E402
|
||||
create_issue,
|
||||
get_issue_info_by_url,
|
||||
parse_sub_tasks,
|
||||
update_issue_body,
|
||||
update_task_issue_url,
|
||||
)
|
||||
|
||||
# Function to generate issue title and description using LLM
|
||||
PROMPT = (
|
||||
"Following is parent issue content:\n"
|
||||
"{issue_content}\n\n"
|
||||
"Based on the following issue task: {task}"
|
||||
"suggest a title and a detailed description for a GitHub issue:\n\n"
|
||||
'Output format: {{"title": "<title>", "description": "<description>"}} '
|
||||
)
|
||||
|
||||
|
||||
@chat_json(prompt=PROMPT)
|
||||
def generate_issue_content(issue_content, task):
|
||||
pass
|
||||
|
||||
|
||||
@editor("Edit issue title:")
|
||||
@editor("Edit issue description:")
|
||||
def edit_issue(title, description):
|
||||
pass
|
||||
|
||||
|
||||
@ui_edit(ui_type="radio", description="Select a task to create issue:")
|
||||
def select_task(tasks):
|
||||
pass
|
||||
|
||||
|
||||
def get_issue_json(issue_url):
|
||||
issue = get_issue_info_by_url(issue_url)
|
||||
assert_exit(not issue, f"Failed to retrieve issue with ID: {issue_url}", exit_code=-1)
|
||||
return {
|
||||
"id": issue["iid"],
|
||||
"web_url": issue["web_url"],
|
||||
"title": issue["title"],
|
||||
"description": issue["description"],
|
||||
}
|
||||
|
||||
|
||||
# Main function
|
||||
def main():
|
||||
print("start new_issue ...", end="\n\n", flush=True)
|
||||
|
||||
assert_exit(len(sys.argv) < 2, "Missing argument.", exit_code=-1)
|
||||
issue_url = sys.argv[1]
|
||||
|
||||
old_issue = get_issue_json(issue_url)
|
||||
assert_exit(not old_issue, "Failed to retrieve issue with: {issue_url}", exit_code=-1)
|
||||
tasks = parse_sub_tasks(old_issue["get_issue_json"])
|
||||
assert_exit(not tasks, "No tasks in issue description.")
|
||||
|
||||
# select task from tasks
|
||||
[task] = select_task(tasks)
|
||||
assert_exit(task is None, "No task selected.")
|
||||
task = tasks[task]
|
||||
print("task:", task, end="\n\n", flush=True)
|
||||
|
||||
print("Generating issue content ...", end="\n\n", flush=True)
|
||||
issue_json_ob = generate_issue_content(issue_content=old_issue, task=task)
|
||||
assert_exit(not issue_json_ob, "Failed to generate issue content.", exit_code=-1)
|
||||
|
||||
issue_title, issue_body = edit_issue(issue_json_ob["title"], issue_json_ob["description"])
|
||||
assert_exit(not issue_title, "Issue creation cancelled.", exit_code=0)
|
||||
print("New Issue:", issue_title, "description:", issue_body, end="\n\n", flush=True)
|
||||
|
||||
print("Creating issue ...", end="\n\n", flush=True)
|
||||
issue = create_issue(issue_title, issue_body)
|
||||
assert_exit(not issue, "Failed to create issue.", exit_code=-1)
|
||||
print("New Issue:", issue["web_url"], end="\n\n", flush=True)
|
||||
|
||||
# update issue task with new issue url
|
||||
new_body = update_task_issue_url(old_issue["description"], task, issue["web_url"])
|
||||
assert_exit(not new_body, f"{task} parse error.")
|
||||
new_issue = update_issue_body(issue_url, new_body)
|
||||
assert_exit(not new_issue, "Failed to update issue description.")
|
||||
|
||||
print("Issue tasks updated successfully!", end="\n\n", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,22 +0,0 @@
|
||||
### new_pr
|
||||
|
||||
创建新的Pull Request。
|
||||
|
||||
#### 用途
|
||||
- 自动生成PR标题和描述
|
||||
- 简化代码审查流程
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.new_pr [additional_info]`
|
||||
|
||||
- additional_info: 可选的附加信息
|
||||
|
||||
#### 操作流程
|
||||
1. 获取当前分支信息和相关Issue
|
||||
2. 生成PR标题和描述
|
||||
3. 允许用户编辑PR内容
|
||||
4. 创建Pull Request
|
||||
|
||||
#### 注意事项
|
||||
- 确保当前分支有未合并的更改
|
||||
- 需要有创建PR的权限
|
@ -1,120 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
|
||||
|
||||
from common_util import assert_exit, ui_edit # noqa: E402
|
||||
from devchat.llm import chat_json # noqa: E402
|
||||
from git_api import ( # noqa: E402
|
||||
auto_push,
|
||||
create_pull_request,
|
||||
get_commit_messages,
|
||||
get_current_branch,
|
||||
get_gitlab_issue_repo,
|
||||
get_issue_info,
|
||||
get_last_base_branch,
|
||||
save_last_base_branch,
|
||||
)
|
||||
|
||||
|
||||
# 从分支名称中提取issue id
|
||||
def extract_issue_id(branch_name):
|
||||
if "#" in branch_name:
|
||||
return branch_name.split("#")[-1]
|
||||
return None
|
||||
|
||||
|
||||
# 使用LLM模型生成PR内容
|
||||
PROMPT = (
|
||||
"Create a pull request title and description based on "
|
||||
"the following issue and commit messages, if there is an "
|
||||
"issue, close that issue in PR description as <user>/<repo>#issue_id:\n"
|
||||
"Issue: {issue}\n"
|
||||
"Commits:\n{commit_messages}\n"
|
||||
"Other information:\n{user_input}\n\n"
|
||||
"The response result should format as JSON object as following:\n"
|
||||
'{{"title": "pr title", "description": "pr description"}}'
|
||||
)
|
||||
|
||||
|
||||
@chat_json(prompt=PROMPT)
|
||||
def generate_pr_content_llm(issue, commit_message, user_input):
|
||||
pass
|
||||
|
||||
|
||||
def generate_pr_content(issue, commit_messages, user_input):
|
||||
response = generate_pr_content_llm(
|
||||
issue=json.dumps(issue), commit_messages=commit_messages, user_input=user_input
|
||||
)
|
||||
assert_exit(not response, "Failed to generate PR content.", exit_code=-1)
|
||||
return response.get("title"), response.get("description")
|
||||
|
||||
|
||||
@ui_edit(ui_type="editor", description="Edit PR title:")
|
||||
@ui_edit(ui_type="editor", description="Edit PR description:")
|
||||
def edit_pr(title, description):
|
||||
pass
|
||||
|
||||
|
||||
@ui_edit(ui_type="editor", description="Edit base branch:")
|
||||
def edit_base_branch(base_branch):
|
||||
pass
|
||||
|
||||
|
||||
def get_issue_json(issue_id):
|
||||
issue = {"id": "no issue id", "title": "", "description": ""}
|
||||
if issue_id:
|
||||
issue = get_issue_info(issue_id)
|
||||
assert_exit(not issue, f"Failed to retrieve issue with ID: {issue_id}", exit_code=-1)
|
||||
issue = {
|
||||
"id": issue_id,
|
||||
"web_url": issue["web_url"],
|
||||
"title": issue["title"],
|
||||
"description": issue["description"],
|
||||
}
|
||||
return issue
|
||||
|
||||
|
||||
# 主函数
|
||||
def main():
|
||||
print("start new_pr ...", end="\n\n", flush=True)
|
||||
|
||||
base_branch = get_last_base_branch("main")
|
||||
base_branch = edit_base_branch(base_branch)
|
||||
if isinstance(base_branch, list) and len(base_branch) > 0:
|
||||
base_branch = base_branch[0]
|
||||
save_last_base_branch(base_branch)
|
||||
|
||||
repo_name = get_gitlab_issue_repo()
|
||||
branch_name = get_current_branch()
|
||||
issue_id = extract_issue_id(branch_name)
|
||||
|
||||
# print basic info, repo_name, branch_name, issue_id
|
||||
print("repo name:", repo_name, end="\n\n")
|
||||
print("branch name:", branch_name, end="\n\n")
|
||||
print("issue id:", issue_id, end="\n\n")
|
||||
|
||||
issue = get_issue_json(issue_id)
|
||||
commit_messages = get_commit_messages(base_branch)
|
||||
|
||||
print("generating pr title and description ...", end="\n\n", flush=True)
|
||||
user_input = sys.argv[1]
|
||||
pr_title, pr_body = generate_pr_content(issue, commit_messages, user_input)
|
||||
assert_exit(not pr_title, "Failed to generate PR content.", exit_code=-1)
|
||||
|
||||
pr_title, pr_body = edit_pr(pr_title, pr_body)
|
||||
assert_exit(not pr_title, "PR creation cancelled.", exit_code=0)
|
||||
|
||||
is_push_success = auto_push()
|
||||
assert_exit(not is_push_success, "Failed to push changes.", exit_code=-1)
|
||||
|
||||
pr = create_pull_request(pr_title, pr_body, branch_name, base_branch, repo_name)
|
||||
assert_exit(not pr, "Failed to create PR.", exit_code=-1)
|
||||
|
||||
print(f"PR created successfully: {pr['web_url']}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,22 +0,0 @@
|
||||
### update_issue_tasks
|
||||
|
||||
更新指定Issue中的任务列表。
|
||||
|
||||
#### 用途
|
||||
- 添加、修改或删除Issue中的子任务
|
||||
- 更新任务进度
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.update_issue_tasks`
|
||||
|
||||
#### 操作流程
|
||||
1. 输入Issue URL
|
||||
2. 显示当前任务列表
|
||||
3. 用户输入更新建议
|
||||
4. 生成新的任务列表
|
||||
5. 允许用户编辑新任务列表
|
||||
6. 更新Issue内容
|
||||
|
||||
#### 注意事项
|
||||
- 需要有编辑Issue的权限
|
||||
- 小心不要删除或覆盖重要信息
|
@ -1,101 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
|
||||
from common_util import assert_exit, editor # noqa: E402
|
||||
from devchat.llm import chat_json # noqa: E402
|
||||
from git_api import ( # noqa: E402
|
||||
get_issue_info_by_url,
|
||||
parse_sub_tasks,
|
||||
update_issue_body,
|
||||
update_sub_tasks,
|
||||
)
|
||||
|
||||
TASKS_PROMPT = (
|
||||
"Following is my git issue content.\n"
|
||||
"{issue_data}\n\n"
|
||||
"Sub task in issue is like:- [ ] task name\n"
|
||||
"'[ ] task name' will be as sub task content\n\n"
|
||||
"Following is my idea to update sub tasks:\n"
|
||||
"{user_input}\n\n"
|
||||
"Please output all tasks in JSON format as:"
|
||||
'{{"tasks": ["[ ] task1", "[ ] task2"]}}'
|
||||
)
|
||||
|
||||
|
||||
@chat_json(prompt=TASKS_PROMPT)
|
||||
def generate_issue_tasks(issue_data, user_input):
|
||||
pass
|
||||
|
||||
|
||||
def to_task_str(tasks):
|
||||
task_str = ""
|
||||
for task in tasks:
|
||||
task_str += task + "\n"
|
||||
return task_str
|
||||
|
||||
|
||||
@editor("Edit issue old tasks:")
|
||||
@editor("Edit issue new tasks:")
|
||||
def edit_issue_tasks(old_tasks, new_tasks):
|
||||
pass
|
||||
|
||||
|
||||
@editor("Input ISSUE url:")
|
||||
def input_issue_url(url):
|
||||
pass
|
||||
|
||||
|
||||
@editor("How to update tasks:")
|
||||
def update_tasks_input(user_input):
|
||||
pass
|
||||
|
||||
|
||||
def get_issue_json(issue_url):
|
||||
issue = get_issue_info_by_url(issue_url)
|
||||
assert_exit(not issue, f"Failed to retrieve issue with ID: {issue_url}", exit_code=-1)
|
||||
return {
|
||||
"id": issue["iid"],
|
||||
"web_url": issue["web_url"],
|
||||
"title": issue["title"],
|
||||
"description": issue["description"],
|
||||
}
|
||||
|
||||
|
||||
# Main function
|
||||
def main():
|
||||
print("start issue tasks update ...", end="\n\n", flush=True)
|
||||
|
||||
[issue_url] = input_issue_url("")
|
||||
assert_exit(not issue_url, "No issue url.")
|
||||
print("issue url:", issue_url, end="\n\n", flush=True)
|
||||
|
||||
issue = get_issue_json(issue_url)
|
||||
old_tasks = parse_sub_tasks(issue["description"])
|
||||
|
||||
print(f"```tasks\n{to_task_str(old_tasks)}\n```", end="\n\n", flush=True)
|
||||
|
||||
[user_input] = update_tasks_input("")
|
||||
assert_exit(not user_input, "No user input")
|
||||
|
||||
new_tasks = generate_issue_tasks(issue_data=issue, user_input=user_input)
|
||||
assert_exit(not new_tasks, "No new tasks.")
|
||||
print("new_tasks:", new_tasks, end="\n\n", flush=True)
|
||||
assert_exit(not new_tasks.get("tasks", []), "No new tasks.")
|
||||
print("new tasks:", to_task_str(new_tasks["tasks"]), end="\n\n", flush=True)
|
||||
new_tasks = new_tasks["tasks"]
|
||||
|
||||
[old_tasks, new_tasks] = edit_issue_tasks(to_task_str(old_tasks), to_task_str(new_tasks))
|
||||
assert_exit(not new_tasks, "No new tasks.")
|
||||
print("new tasks:", new_tasks, end="\n\n", flush=True)
|
||||
|
||||
new_body = update_sub_tasks(issue["description"], new_tasks.split("\n"))
|
||||
new_issue = update_issue_body(issue_url, new_body)
|
||||
assert_exit(not new_issue, "Failed to update issue description.")
|
||||
|
||||
print("Issue tasks updated successfully!", end="\n\n", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,5 +0,0 @@
|
||||
description: 'Update issue tasks.'
|
||||
input: required
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1,20 +0,0 @@
|
||||
### update_pr
|
||||
|
||||
更新现有的Pull Request。
|
||||
|
||||
#### 用途
|
||||
- 更新PR的标题和描述
|
||||
- 反映最新的代码变更
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.update_pr`
|
||||
|
||||
#### 操作流程
|
||||
1. 获取最近的PR信息
|
||||
2. 重新生成PR标题和描述
|
||||
3. 允许用户编辑PR内容
|
||||
4. 更新Pull Request
|
||||
|
||||
#### 注意事项
|
||||
- 确保有更新PR的权限
|
||||
- 更新前请确认是否有新的提交需要推送
|
@ -1,122 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
|
||||
|
||||
from common_util import assert_exit, ui_edit # noqa: E402
|
||||
from devchat.llm import ( # noqa: E402
|
||||
chat_json,
|
||||
)
|
||||
from git_api import ( # noqa: E402
|
||||
auto_push,
|
||||
get_commit_messages,
|
||||
get_current_branch,
|
||||
get_gitlab_issue_repo,
|
||||
get_issue_info,
|
||||
get_last_base_branch,
|
||||
get_recently_pr,
|
||||
save_last_base_branch,
|
||||
update_pr,
|
||||
)
|
||||
|
||||
|
||||
# 从分支名称中提取issue id
|
||||
def extract_issue_id(branch_name):
|
||||
if "#" in branch_name:
|
||||
return branch_name.split("#")[-1]
|
||||
return None
|
||||
|
||||
|
||||
# 使用LLM模型生成PR内容
|
||||
PROMPT = (
|
||||
"Create a pull request title and description based on "
|
||||
"the following issue and commit messages, if there is an "
|
||||
"issue, close that issue in PR description as <user>/<repo>#issue_id:\n"
|
||||
"Issue: {issue}\n"
|
||||
"Commits:\n{commit_messages}\n"
|
||||
"The response result should format as JSON object as following:\n"
|
||||
'{{"title": "pr title", "description": "pr description"}}'
|
||||
)
|
||||
|
||||
|
||||
@chat_json(prompt=PROMPT)
|
||||
def generate_pr_content_llm(issue, commit_messages):
|
||||
pass
|
||||
|
||||
|
||||
def generate_pr_content(issue, commit_messages):
|
||||
response = generate_pr_content_llm(issue=json.dumps(issue), commit_messages=commit_messages)
|
||||
assert_exit(not response, "Failed to generate PR content.", exit_code=-1)
|
||||
return response.get("title"), response.get("description")
|
||||
|
||||
|
||||
@ui_edit(ui_type="editor", description="Edit PR title:")
|
||||
@ui_edit(ui_type="editor", description="Edit PR description:")
|
||||
def edit_pr(title, description):
|
||||
pass
|
||||
|
||||
|
||||
@ui_edit(ui_type="editor", description="Edit base branch:")
|
||||
def edit_base_branch(base_branch):
|
||||
pass
|
||||
|
||||
|
||||
def get_issue_json(issue_id):
|
||||
issue = {"id": "no issue id", "title": "", "description": ""}
|
||||
if issue_id:
|
||||
issue = get_issue_info(issue_id)
|
||||
assert_exit(not issue, f"Failed to retrieve issue with ID: {issue_id}", exit_code=-1)
|
||||
issue = {
|
||||
"id": issue_id,
|
||||
"web_url": issue["web_url"],
|
||||
"title": issue["title"],
|
||||
"description": issue["description"],
|
||||
}
|
||||
return issue
|
||||
|
||||
|
||||
# 主函数
|
||||
def main():
|
||||
print("start update_pr ...", end="\n\n", flush=True)
|
||||
|
||||
base_branch = get_last_base_branch("main")
|
||||
base_branch = edit_base_branch(base_branch)
|
||||
if isinstance(base_branch, list) and len(base_branch) > 0:
|
||||
base_branch = base_branch[0]
|
||||
save_last_base_branch(base_branch)
|
||||
|
||||
repo_name = get_gitlab_issue_repo()
|
||||
branch_name = get_current_branch()
|
||||
issue_id = extract_issue_id(branch_name)
|
||||
|
||||
# print basic info, repo_name, branch_name, issue_id
|
||||
print("repo name:", repo_name, end="\n\n")
|
||||
print("branch name:", branch_name, end="\n\n")
|
||||
print("issue id:", issue_id, end="\n\n")
|
||||
|
||||
issue = get_issue_json(issue_id)
|
||||
commit_messages = get_commit_messages(base_branch)
|
||||
|
||||
recent_pr = get_recently_pr(repo_name)
|
||||
assert_exit(not recent_pr, "Failed to get recent PR.", exit_code=-1)
|
||||
|
||||
print("generating pr title and description ...", end="\n\n", flush=True)
|
||||
pr_title, pr_body = generate_pr_content(issue, commit_messages)
|
||||
assert_exit(not pr_title, "Failed to generate PR content.", exit_code=-1)
|
||||
|
||||
pr_title, pr_body = edit_pr(pr_title, pr_body)
|
||||
assert_exit(not pr_title, "PR creation cancelled.", exit_code=0)
|
||||
|
||||
is_push_success = auto_push()
|
||||
assert_exit(not is_push_success, "Failed to push changes.", exit_code=-1)
|
||||
|
||||
pr = update_pr(recent_pr["iid"], pr_title, pr_body, repo_name)
|
||||
assert_exit(not pr, "Failed to update PR.", exit_code=-1)
|
||||
|
||||
print(f"PR updated successfully: {pr['web_url']}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,5 +0,0 @@
|
||||
description: 'Update PR.'
|
||||
input: required
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1,34 +0,0 @@
|
||||
# pr 命令
|
||||
|
||||
pr命令是一个用于处理Pull Requests (PRs)的主命令。它本身不执行具体操作,而是通过子命令来完成特定功能。
|
||||
|
||||
## 可用子命令
|
||||
|
||||
1. pr.review - 生成PR代码评审描述
|
||||
2. pr.improve - 生成PR的代码建议
|
||||
3. pr.describe - 生成PR描述
|
||||
4. pr.custom_suggestions - 生成PR的自定义代码建议
|
||||
|
||||
## 使用方法
|
||||
|
||||
要使用pr命令的功能,请使用以下格式调用相应的子命令:
|
||||
|
||||
/pr.<子命令> <PR_URL>
|
||||
|
||||
例如:
|
||||
- /pr.review https://github.com/devchat-ai/devchat/pull/301
|
||||
- /pr.improve https://github.com/devchat-ai/devchat/pull/301
|
||||
- /pr.describe https://github.com/devchat-ai/devchat/pull/301
|
||||
- /pr.custom_suggestions https://github.com/devchat-ai/devchat/pull/301
|
||||
|
||||
## 子命令说明
|
||||
|
||||
1. pr.review: 分析PR并生成代码评审描述,帮助审阅者快速了解PR的内容和影响。
|
||||
|
||||
2. pr.improve: 分析PR并提供代码改进建议,帮助开发者优化其代码。
|
||||
|
||||
3. pr.describe: 自动生成PR的描述,总结PR的主要变更和目的。
|
||||
|
||||
4. pr.custom_suggestions: 根据特定需求生成自定义的PR代码建议。
|
||||
|
||||
请根据您的具体需求选择适当的子命令。每个子命令都专注于PR处理的不同方面,帮助您更高效地管理和改进Pull Requests。
|
@ -1,24 +0,0 @@
|
||||
# pr.config
|
||||
|
||||
**/pr.config命令用于配置Git工作流所需的设置。**
|
||||
|
||||
|
||||
该命令允许用户配置以下选项:
|
||||
|
||||
1. PR Review Inline: 启用或禁用PR内联评审功能。
|
||||
|
||||
使用方式:
|
||||
直接运行 /pr.config 命令,无需额外参数。
|
||||
|
||||
命令会引导用户通过交互式界面进行配置。用户可以选择是否启用PR内联评审功能。
|
||||
|
||||
|
||||
配置文件位置:
|
||||
全局配置文件保存在用户主目录下的 ~/.chat/.workflow_config.json
|
||||
|
||||
|
||||
注意:
|
||||
- 如需修改访问令牌或主机URL,请直接编辑配置文件。
|
||||
- 配置更改后会立即生效。
|
||||
|
||||
这个命令可以帮助用户快速设置Git工作流所需的重要配置项,提高使用效率。
|
@ -1,72 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from lib.chatmark import Checkbox
|
||||
|
||||
# Configuration items
|
||||
CONFIG_ITEMS = {
|
||||
"pr_review_inline": "PR Review Inline Enabled",
|
||||
}
|
||||
|
||||
# Configuration file paths
|
||||
GLOBAL_CONFIG_PATH = os.path.join(os.path.expanduser("~"), ".chat", ".workflow_config.json")
|
||||
|
||||
|
||||
def read_config(config_path, item):
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
return config.get(item)
|
||||
return None
|
||||
|
||||
|
||||
def save_config(config_path, item, value):
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
else:
|
||||
config = {}
|
||||
|
||||
config[item] = value
|
||||
with open(config_path, "w", encoding="utf-8") as f:
|
||||
json.dump(config, f, indent=4)
|
||||
|
||||
|
||||
def is_pre_review_inline_enabled(current_value=False):
|
||||
print("\n\nEnable PR Review Inline:\n\n")
|
||||
checkbox = Checkbox(
|
||||
[
|
||||
"PR Review Inline Enabled",
|
||||
],
|
||||
[current_value],
|
||||
)
|
||||
checkbox.render()
|
||||
|
||||
print(f"\n\ncheckbox.selections: {checkbox.selections}\n\n")
|
||||
if len(checkbox.selections) > 0:
|
||||
return True
|
||||
if checkbox.selections is None:
|
||||
return None
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
print("Starting configuration of workflow settings...", end="\n\n", flush=True)
|
||||
print(
|
||||
"If you want to change access token or host url, "
|
||||
"please edit the configuration file directly."
|
||||
)
|
||||
print("Configuration file is located at:", GLOBAL_CONFIG_PATH, end="\n\n", flush=True)
|
||||
|
||||
pr_review_inline_enable = read_config(GLOBAL_CONFIG_PATH, "pr_review_inline")
|
||||
|
||||
pr_review_inline_enable = is_pre_review_inline_enabled(pr_review_inline_enable or False)
|
||||
if pr_review_inline_enable is not None:
|
||||
save_config(GLOBAL_CONFIG_PATH, "pr_review_inline", pr_review_inline_enable)
|
||||
print("Workflow settings configuration successful.")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,4 +0,0 @@
|
||||
description: 'Config required settings for GIT workflows.'
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py
|
@ -1,2 +0,0 @@
|
||||
git+https://gitee.com/imlaji/pr-agent.git@ad276e206c7e462a689996ee3ada2769b35d5625
|
||||
git+https://gitee.com/devchat-ai/devchat.git@pr_env
|
@ -1,3 +0,0 @@
|
||||
# /refactor.api
|
||||
|
||||
对选中代码进行API重构
|
@ -1,132 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
from typing import Dict
|
||||
|
||||
from devchat.llm import chat_json
|
||||
|
||||
from lib.chatmark import Button, Form, TextEditor
|
||||
from lib.ide_service import IDEService
|
||||
from lib.workflow import workflow_call
|
||||
|
||||
# 步骤3: 使用AI识别API路径和METHOD的提示词
|
||||
API_ANALYSIS_PROMPT = """
|
||||
分析以下代码,识别其中的API路径和HTTP方法。
|
||||
|
||||
代码:
|
||||
```
|
||||
{code}
|
||||
```
|
||||
|
||||
请提取代码中定义或使用的API路径和HTTP方法(GET, POST, PUT, DELETE等)。
|
||||
如果代码中有多个API,请识别最主要的一个。
|
||||
如果无法确定HTTP方法,请使用"GET"作为默认值。
|
||||
|
||||
返回JSON格式如下:
|
||||
{{
|
||||
"api_path": "识别到的API路径,例如/api/users",
|
||||
"method": "识别到的HTTP方法,例如GET、POST、PUT、DELETE等"
|
||||
}}
|
||||
"""
|
||||
|
||||
|
||||
@chat_json(prompt=API_ANALYSIS_PROMPT)
|
||||
def analyze_api(code: str) -> Dict[str, str]:
|
||||
"""使用AI分析代码中的API路径和HTTP方法"""
|
||||
pass
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""API重构工作流主函数"""
|
||||
try:
|
||||
# 步骤1: 获取用户输入的重构目标
|
||||
if len(sys.argv) < 2:
|
||||
print("错误: 请提供重构目标")
|
||||
sys.exit(1)
|
||||
|
||||
refactor_target = sys.argv[1]
|
||||
|
||||
# 步骤2: 获取用户选中的代码
|
||||
selected_code = IDEService().get_selected_range()
|
||||
if not selected_code or not selected_code.text.strip():
|
||||
print("错误: 请先选择需要重构的代码")
|
||||
sys.exit(1)
|
||||
|
||||
# 步骤3: 使用AI识别API路径和METHOD
|
||||
print("正在分析选中代码中的API信息...")
|
||||
api_info = analyze_api(code=selected_code.text)
|
||||
|
||||
if not api_info or "api_path" not in api_info or "method" not in api_info:
|
||||
print("错误: 无法识别API信息")
|
||||
sys.exit(1)
|
||||
|
||||
api_path = api_info["api_path"]
|
||||
method = api_info["method"]
|
||||
|
||||
# 步骤4: 显示识别结果并让用户确认
|
||||
print("识别到的API信息:")
|
||||
print(f"API路径: {api_path}")
|
||||
print(f"HTTP方法: {method}")
|
||||
|
||||
api_path_editor = TextEditor(api_path)
|
||||
|
||||
form = Form(
|
||||
[
|
||||
"### 请确认API信息",
|
||||
"API路径:",
|
||||
api_path_editor,
|
||||
f"HTTP方法: {method}",
|
||||
"请确认或修改API路径,然后点击下方按钮继续",
|
||||
]
|
||||
)
|
||||
|
||||
form.render()
|
||||
|
||||
# 获取用户确认后的API路径
|
||||
confirmed_api_path = api_path_editor.new_text
|
||||
|
||||
# 步骤5: 调用重构工作流进行代码重构
|
||||
print(f"正在重构API: {confirmed_api_path}...")
|
||||
refactor_result = workflow_call(f"/refactor {refactor_target}")
|
||||
|
||||
if refactor_result != 0:
|
||||
print("错误: API重构失败")
|
||||
sys.exit(1)
|
||||
|
||||
print("API重构成功!")
|
||||
|
||||
# 步骤6: 显示按钮让用户确认是否继续
|
||||
continue_button = Button(["提交修改并测试API", "结束重构"])
|
||||
continue_button.render()
|
||||
|
||||
if continue_button.clicked == 1: # 用户选择结束
|
||||
print("API重构已完成,未提交修改")
|
||||
return
|
||||
|
||||
# 步骤7: 调用GitHub提交工作流提交修改
|
||||
print("正在提交修改...")
|
||||
commit_result = workflow_call("/github.commit")
|
||||
|
||||
if commit_result != 0:
|
||||
print("警告: 代码提交失败,但将继续进行API测试")
|
||||
else:
|
||||
print("代码提交成功!")
|
||||
|
||||
# 步骤8: 调用API测试工作流对重构API进行测试
|
||||
print("正在准备API测试...")
|
||||
test_command = f"/test.api.upload {confirmed_api_path} {method} {refactor_target}"
|
||||
test_result = workflow_call(test_command)
|
||||
|
||||
if test_result != 0:
|
||||
print("警告: API测试可能未成功完成")
|
||||
|
||||
print("API重构工作流执行完毕!")
|
||||
|
||||
except Exception as e:
|
||||
print(f"错误: 执行过程中发生异常: {str(e)}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,5 +0,0 @@
|
||||
description: Refactor for selected api.
|
||||
input: required
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1,28 +0,0 @@
|
||||
### test.api.config
|
||||
|
||||
配置API测试工作流所需的全局和仓库相关设置。
|
||||
|
||||
#### 用途
|
||||
- 配置服务器连接信息(SERVER_URL, USERNAME, PASSWORD)
|
||||
- 配置项目相关信息(PROJECT_ID, OPENAPI_URL, VERSION_URL)
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/test.api.config`
|
||||
|
||||
#### 操作流程
|
||||
1. 输入服务器URL(例如: http://kagent.merico.cn:8000)
|
||||
2. 输入用户名
|
||||
3. 输入密码
|
||||
4. 输入项目ID(例如: 37)
|
||||
5. 输入OpenAPI文档URL(例如: http://kagent.merico.cn:8080/openapi.json)
|
||||
6. 输入版本信息URL(例如: http://kagent.merico.cn:8080/version)
|
||||
7. 保存配置信息
|
||||
|
||||
#### 配置信息存储位置
|
||||
- 全局配置(SERVER_URL, USERNAME, PASSWORD)保存在 `~/.chat/.workflow_config.json`
|
||||
- 仓库配置(PROJECT_ID, OPENAPI_URL, VERSION_URL)保存在当前仓库的 `.chat/.workflow_config.json`
|
||||
|
||||
#### 注意事项
|
||||
- 密码信息应妥善保管,不要泄露
|
||||
- 配置完成后,其他API测试工作流将自动使用这些配置信息
|
||||
- 如需修改配置,重新运行此命令即可
|
@ -1,151 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from lib.chatmark import Form, TextEditor # 导入 ChatMark 组件
|
||||
|
||||
|
||||
def read_global_config():
|
||||
"""读取全局配置信息"""
|
||||
config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
||||
config_data = {}
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
|
||||
server_url = config_data.get("api_testing_server_url", "")
|
||||
username = config_data.get("api_testing_server_username", "")
|
||||
password = config_data.get("api_testing_server_password", "")
|
||||
|
||||
return server_url, username, password
|
||||
|
||||
|
||||
def save_global_config(server_url, username, password):
|
||||
"""保存全局配置信息"""
|
||||
config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
||||
|
||||
# 确保目录存在
|
||||
os.makedirs(os.path.dirname(config_path), exist_ok=True)
|
||||
|
||||
config_data = {}
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
|
||||
config_data["api_testing_server_url"] = server_url
|
||||
config_data["api_testing_server_username"] = username
|
||||
config_data["api_testing_server_password"] = password
|
||||
|
||||
with open(config_path, "w+", encoding="utf-8") as f:
|
||||
json.dump(config_data, f, indent=4)
|
||||
|
||||
|
||||
def read_repo_config():
|
||||
"""读取仓库相关配置信息"""
|
||||
config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json")
|
||||
config_data = {}
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
|
||||
project_id = config_data.get("test_api_project_id", "")
|
||||
openapi_url = config_data.get("test_api_openapi_url", "")
|
||||
version_url = config_data.get("test_api_version_url", "")
|
||||
|
||||
return project_id, openapi_url, version_url
|
||||
|
||||
|
||||
def save_repo_config(project_id, openapi_url, version_url):
|
||||
"""保存仓库相关配置信息"""
|
||||
config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json")
|
||||
|
||||
# 确保目录存在
|
||||
os.makedirs(os.path.dirname(config_path), exist_ok=True)
|
||||
|
||||
config_data = {}
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
|
||||
config_data["test_api_project_id"] = project_id
|
||||
config_data["test_api_openapi_url"] = openapi_url
|
||||
config_data["test_api_version_url"] = version_url
|
||||
|
||||
with open(config_path, "w+", encoding="utf-8") as f:
|
||||
json.dump(config_data, f, indent=4)
|
||||
|
||||
|
||||
def main():
|
||||
print("开始配置 API 测试所需的设置...", end="\n\n", flush=True)
|
||||
|
||||
# 读取全局配置
|
||||
server_url, username, password = read_global_config()
|
||||
|
||||
# 读取仓库配置
|
||||
project_id, openapi_url, version_url = read_repo_config()
|
||||
|
||||
# 创建表单组件
|
||||
server_url_editor = TextEditor(server_url)
|
||||
username_editor = TextEditor(username)
|
||||
password_editor = TextEditor(password)
|
||||
project_id_editor = TextEditor(project_id)
|
||||
openapi_url_editor = TextEditor(openapi_url)
|
||||
version_url_editor = TextEditor(version_url)
|
||||
|
||||
# 创建表单
|
||||
form = Form(
|
||||
[
|
||||
"## DevChat API 测试服务器配置",
|
||||
"请输入服务器 URL (例如: http://kagent.merico.cn:8000):",
|
||||
server_url_editor,
|
||||
"请输入用户名:",
|
||||
username_editor,
|
||||
"请输入密码:",
|
||||
password_editor,
|
||||
"## 仓库配置",
|
||||
"请输入DevChat API 测试服务器中项目 ID (例如: 37):",
|
||||
project_id_editor,
|
||||
"请输入 OpenAPI URL (例如: http://kagent.merico.cn:8080/openapi.json):",
|
||||
openapi_url_editor,
|
||||
"请输入版本 URL (例如: http://kagent.merico.cn:8080/version),不输入表示不需要检查服务器测试环境版本:",
|
||||
version_url_editor,
|
||||
]
|
||||
)
|
||||
|
||||
# 渲染表单
|
||||
form.render()
|
||||
|
||||
# 获取用户输入
|
||||
server_url = server_url_editor.new_text.strip()
|
||||
username = username_editor.new_text.strip()
|
||||
password = password_editor.new_text.strip()
|
||||
project_id = project_id_editor.new_text.strip()
|
||||
openapi_url = openapi_url_editor.new_text.strip()
|
||||
version_url = version_url_editor.new_text.strip()
|
||||
|
||||
# 保存全局配置
|
||||
if server_url and username and password:
|
||||
save_global_config(server_url, username, password)
|
||||
else:
|
||||
print("请提供完整的全局配置信息 (SERVER_URL, USERNAME, PASSWORD)。")
|
||||
sys.exit(1)
|
||||
|
||||
# 保存仓库配置
|
||||
if project_id and openapi_url and version_url:
|
||||
save_repo_config(project_id, openapi_url, version_url)
|
||||
else:
|
||||
print("请提供完整的仓库配置信息 (PROJECT_ID, OPENAPI_URL, VERSION_URL)。")
|
||||
sys.exit(1)
|
||||
|
||||
print("\n配置信息已成功保存!")
|
||||
print(f"全局配置: SERVER_URL={server_url}, USERNAME={username}, PASSWORD={'*' * len(password)}")
|
||||
print(
|
||||
f"仓库配置: PROJECT_ID={project_id}, OPENAPI_URL={openapi_url}, VERSION_URL={version_url}"
|
||||
)
|
||||
|
||||
print("\n您现在可以使用其他 API 测试工作流了。")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,4 +0,0 @@
|
||||
description: 'Configure global and repository-specific settings for API testing.'
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py
|
@ -1,244 +0,0 @@
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
import requests
|
||||
|
||||
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
sys.path.append(ROOT_DIR)
|
||||
|
||||
# noqa: I001
|
||||
from api.utils import ( # noqa: E402
|
||||
OPENAPI_URL,
|
||||
PROJECT_ID,
|
||||
SERVER_URL,
|
||||
VERSION_URL,
|
||||
get_path_op_id,
|
||||
session,
|
||||
)
|
||||
|
||||
# noqa: E402
|
||||
from lib.chatmark.step import Step # noqa: E402
|
||||
|
||||
|
||||
def get_apidocs():
|
||||
res = session.get(
|
||||
f"{SERVER_URL}/autotest/projects/{PROJECT_ID}/apidocs",
|
||||
params={"page": 1, "size": 100},
|
||||
)
|
||||
return res.json()["docs"]
|
||||
|
||||
|
||||
def delete_old_apidocs(apidocs):
|
||||
for apidoc in apidocs:
|
||||
session.delete(f"{SERVER_URL}/autotest/projects/{PROJECT_ID}/apidocs/{apidoc['id']}")
|
||||
|
||||
|
||||
def get_local_version():
|
||||
cmd = "git rev-parse HEAD"
|
||||
res = subprocess.run(cmd, shell=True, check=True, stdout=subprocess.PIPE)
|
||||
return res.stdout.decode("utf-8").strip()
|
||||
|
||||
|
||||
def check_api_version():
|
||||
# 如果没有配置VERSION_URL,则跳过版本检查
|
||||
if not VERSION_URL:
|
||||
print("未配置VERSION_URL,跳过API版本检查...")
|
||||
return
|
||||
|
||||
local_version = get_local_version()
|
||||
print("检查被测服务器文档是否已经更新到最新版本...", flush=True)
|
||||
while True:
|
||||
try:
|
||||
res = session.get(VERSION_URL)
|
||||
version = res.json()["version"]
|
||||
if version == local_version:
|
||||
print(f"API 文档已更新,当前版本为 {version},开始上传 OpenAPI 文档...", flush=True)
|
||||
break
|
||||
else:
|
||||
print(
|
||||
".",
|
||||
end="",
|
||||
flush=True,
|
||||
)
|
||||
time.sleep(5)
|
||||
except Exception as e:
|
||||
print(f"检查 API 版本失败!{e}", flush=True)
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
def wait_for_testcase_done(testcase_id):
|
||||
while True:
|
||||
try:
|
||||
res = session.get(
|
||||
f"{SERVER_URL}/autotest/projects/{PROJECT_ID}/testcases/{testcase_id}"
|
||||
)
|
||||
data = res.json()
|
||||
status = data["status"]
|
||||
if status == "content_ready":
|
||||
print("文本用例生成完成!", flush=True)
|
||||
break
|
||||
else:
|
||||
print(
|
||||
".",
|
||||
end="",
|
||||
flush=True,
|
||||
)
|
||||
time.sleep(5)
|
||||
except Exception as e:
|
||||
print(f"检查文本用例状态失败!{e}", flush=True)
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
def wait_for_testcode_done(task_id):
|
||||
while True:
|
||||
try:
|
||||
res = session.get(f"{SERVER_URL}/tasks/{task_id}")
|
||||
data = res.json()
|
||||
status = data["status"]
|
||||
if status == "succeeded":
|
||||
print("自动测试脚本生成完成!", flush=True)
|
||||
break
|
||||
else:
|
||||
print(
|
||||
".",
|
||||
end="",
|
||||
flush=True,
|
||||
)
|
||||
time.sleep(5)
|
||||
except Exception as e:
|
||||
print(f"检查自动测试脚本生成失败!{e}", flush=True)
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
def get_testcode(testcase_id):
|
||||
res = session.get(
|
||||
f"{SERVER_URL}/autotest/projects/{PROJECT_ID}/testcodes",
|
||||
params={"testcase_id": testcase_id},
|
||||
)
|
||||
return res.json()["testcodes"][0]
|
||||
|
||||
|
||||
def wait_for_task_done(task_id):
|
||||
while True:
|
||||
try:
|
||||
res = session.get(f"{SERVER_URL}/tasks/{task_id}")
|
||||
data = res.json()
|
||||
status = data["status"]
|
||||
|
||||
CREATED = "created"
|
||||
RUNNING = "running"
|
||||
WAITING = "waiting"
|
||||
if status not in [CREATED, RUNNING, WAITING]:
|
||||
print("自动测试脚本执行完成!", flush=True)
|
||||
break
|
||||
else:
|
||||
print(
|
||||
".",
|
||||
end="",
|
||||
flush=True,
|
||||
)
|
||||
time.sleep(5)
|
||||
except Exception as e:
|
||||
print(f"检查自动测试脚本状态失败!{e}", flush=True)
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
def get_testcase(api_path_id):
|
||||
res = session.get(
|
||||
f"{SERVER_URL}/autotest/projects/{PROJECT_ID}/testcases",
|
||||
params={"page": 1, "size": 100, "pathop_id": api_path_id},
|
||||
)
|
||||
return res.json()["testcases"][0]
|
||||
|
||||
|
||||
def main():
|
||||
error_msg = "请输入要测试的API名称和测试目标!如:/test.api.upload api_path method test_target"
|
||||
if len(sys.argv) < 2:
|
||||
print(error_msg)
|
||||
return
|
||||
args = sys.argv[1].strip().split(" ")
|
||||
if len(args) < 3:
|
||||
print(error_msg)
|
||||
return
|
||||
api_path = args[0]
|
||||
method = args[1]
|
||||
test_target = " ".join(args[2:])
|
||||
docs = get_apidocs()
|
||||
with Step("检查 API 版本是否更新..."):
|
||||
check_api_version()
|
||||
delete_old_apidocs(docs)
|
||||
|
||||
with Step(f"上传 OpenAPI 文档,并且触发 API {api_path} 的测试用例和自动测试脚本生成任务..."):
|
||||
# 使用配置的OPENAPI_URL
|
||||
if not OPENAPI_URL:
|
||||
print("错误:未配置OPENAPI_URL,无法获取OpenAPI文档")
|
||||
return
|
||||
|
||||
res = requests.get(
|
||||
OPENAPI_URL,
|
||||
)
|
||||
res = session.post(
|
||||
f"{SERVER_URL}/autotest/projects/{PROJECT_ID}/apidocs",
|
||||
files={"file": ("openapi.json", res.content, "application/json")},
|
||||
data={"apiauth_id": docs[0]["apiauth_id"]},
|
||||
)
|
||||
if res.status_code == 200:
|
||||
print("上传 OpenAPI 文档成功!\n")
|
||||
else:
|
||||
print(f"上传 OpenAPI 文档失败!{res.text}", flush=True)
|
||||
return
|
||||
apipathop_id = get_path_op_id(api_path, method)
|
||||
with Step("开始生成文本用例..."):
|
||||
res = session.post(
|
||||
f"{SERVER_URL}/autotest/projects/{PROJECT_ID}/testcases",
|
||||
params={"generate_content": True},
|
||||
json={"apipathop_id": apipathop_id, "title": test_target},
|
||||
)
|
||||
if res.status_code == 200:
|
||||
print("提交生成文本用例成功!等待生成完成...", flush=True)
|
||||
testcase_id = res.json()["id"]
|
||||
wait_for_testcase_done(testcase_id)
|
||||
else:
|
||||
print(f"提交生成文本用例失败!{res.text}", flush=True)
|
||||
return
|
||||
with Step("开始生成自动测试脚本..."):
|
||||
res = session.post(
|
||||
f"{SERVER_URL}/autotest/projects/{PROJECT_ID}/tasks/testcode",
|
||||
params={"testcase_id": testcase_id},
|
||||
)
|
||||
if res.status_code == 200:
|
||||
print("提交生成自动测试脚本成功!等待生成完成...", flush=True)
|
||||
task_id = res.json()["id"]
|
||||
wait_for_testcode_done(task_id)
|
||||
else:
|
||||
print(f"提交生成自动测试脚本失败!{res.text}", flush=True)
|
||||
return
|
||||
with Step("开始执行自动测试脚本..."):
|
||||
testcode = get_testcode(testcase_id)
|
||||
testcode_id = testcode["id"]
|
||||
res = session.post(
|
||||
f"{SERVER_URL}/autotest/projects/{PROJECT_ID}/testcodes/{testcode_id}/exec",
|
||||
)
|
||||
if res.status_code == 200:
|
||||
print("提交执行自动测试脚本成功!", flush=True)
|
||||
else:
|
||||
print(f"提交执行自动测试脚本失败!{res.text}")
|
||||
return
|
||||
|
||||
api_path_id = get_path_op_id(api_path, method)
|
||||
with Step("开始查询测试脚本执行结果..."):
|
||||
while True:
|
||||
testcase = get_testcase(api_path_id)
|
||||
last_testcode_passed = testcase["last_testcode_passed"]
|
||||
if last_testcode_passed:
|
||||
print("测试脚本执行成功!", flush=True)
|
||||
break
|
||||
else:
|
||||
print("测试脚本执行失败!", flush=True)
|
||||
break
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,4 +0,0 @@
|
||||
description: 'Upload API documentation, generate test cases and test scripts for the target API, and execute the test code. Input format: APIPATH METHOD API_REFACTOR_DESCRIPTION'
|
||||
input: required
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1,141 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
import requests
|
||||
|
||||
from lib.workflow.call import workflow_call
|
||||
|
||||
# 默认配置,仅在无法读取配置文件时使用
|
||||
|
||||
|
||||
session = requests.Session()
|
||||
_is_login = False
|
||||
|
||||
|
||||
def read_config():
|
||||
"""读取配置文件中的设置"""
|
||||
# 读取全局配置
|
||||
global_config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
||||
global_config = {}
|
||||
if os.path.exists(global_config_path):
|
||||
try:
|
||||
with open(global_config_path, "r", encoding="utf-8") as f:
|
||||
global_config = json.load(f)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 读取仓库配置
|
||||
repo_config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json")
|
||||
repo_config = {}
|
||||
if os.path.exists(repo_config_path):
|
||||
try:
|
||||
with open(repo_config_path, "r", encoding="utf-8") as f:
|
||||
repo_config = json.load(f)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 获取配置值
|
||||
server_url = global_config.get("api_testing_server_url", "")
|
||||
username = global_config.get("api_testing_server_username", "")
|
||||
password = global_config.get("api_testing_server_password", "")
|
||||
project_id = repo_config.get("test_api_project_id", "")
|
||||
openapi_url = repo_config.get("test_api_openapi_url", "")
|
||||
version_url = repo_config.get("test_api_version_url", "")
|
||||
|
||||
return server_url, username, password, project_id, openapi_url, version_url
|
||||
|
||||
|
||||
def ensure_config():
|
||||
"""确保配置存在,如果不存在则调用配置工作流"""
|
||||
global_config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
||||
repo_config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json")
|
||||
|
||||
# 检查全局配置和仓库配置是否存在
|
||||
global_config_exists = os.path.exists(global_config_path)
|
||||
repo_config_exists = os.path.exists(repo_config_path)
|
||||
|
||||
# 检查必填配置项是否存在
|
||||
config_valid = True
|
||||
if global_config_exists and repo_config_exists:
|
||||
# 读取配置
|
||||
global_config = {}
|
||||
repo_config = {}
|
||||
try:
|
||||
with open(global_config_path, "r", encoding="utf-8") as f:
|
||||
global_config = json.load(f)
|
||||
with open(repo_config_path, "r", encoding="utf-8") as f:
|
||||
repo_config = json.load(f)
|
||||
except Exception:
|
||||
config_valid = False
|
||||
|
||||
# 检查必填项
|
||||
if (
|
||||
not global_config.get("api_testing_server_url")
|
||||
or not global_config.get("api_testing_server_username")
|
||||
or not global_config.get("api_testing_server_password")
|
||||
or not repo_config.get("test_api_project_id")
|
||||
or not repo_config.get("test_api_openapi_url")
|
||||
):
|
||||
config_valid = False
|
||||
else:
|
||||
config_valid = False
|
||||
|
||||
if not config_valid:
|
||||
print("缺少API测试所需的配置,将启动配置向导...")
|
||||
workflow_call("/test.api.config")
|
||||
|
||||
# 重新检查配置是否已创建并包含必要项
|
||||
try:
|
||||
if os.path.exists(global_config_path) and os.path.exists(repo_config_path):
|
||||
with open(global_config_path, "r", encoding="utf-8") as f:
|
||||
global_config = json.load(f)
|
||||
with open(repo_config_path, "r", encoding="utf-8") as f:
|
||||
repo_config = json.load(f)
|
||||
|
||||
if (
|
||||
global_config.get("api_testing_server_url")
|
||||
and global_config.get("api_testing_server_username")
|
||||
and global_config.get("api_testing_server_password")
|
||||
and repo_config.get("test_api_project_id")
|
||||
and repo_config.get("test_api_openapi_url")
|
||||
):
|
||||
return True
|
||||
print("配置失败")
|
||||
return False
|
||||
except Exception:
|
||||
print("配置失败")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
# 读取配置
|
||||
result = ensure_config()
|
||||
if not result:
|
||||
print("配置失败,工作流不能继续执行")
|
||||
exit(0)
|
||||
SERVER_URL, USERNAME, PASSWORD, PROJECT_ID, OPENAPI_URL, VERSION_URL = read_config()
|
||||
|
||||
|
||||
def login():
|
||||
global _is_login
|
||||
if _is_login:
|
||||
return
|
||||
session.post(
|
||||
f"{SERVER_URL}/user/auth/login",
|
||||
data={"username": USERNAME, "password": PASSWORD, "grant_type": "password"},
|
||||
)
|
||||
_is_login = True
|
||||
|
||||
|
||||
def get_path_op_id(keyword: str, method: str):
|
||||
res = session.get(
|
||||
f"{SERVER_URL}/autotest/projects/{PROJECT_ID}/apipathops",
|
||||
params={"keyword": keyword, "page": 1, "size": 20},
|
||||
)
|
||||
for pathop in res.json()["pathops"]:
|
||||
if pathop["method"].lower().strip() == method.lower().strip():
|
||||
return pathop["id"]
|
||||
|
||||
|
||||
login()
|
@ -1,6 +1,6 @@
|
||||
from .form import Form
|
||||
from .step import Step
|
||||
from .widgets import Button, Checkbox, MultiSelect, Radio, TextEditor
|
||||
from .widgets import Button, Checkbox, Radio, TextEditor
|
||||
|
||||
__all__ = [
|
||||
"Checkbox",
|
||||
@ -9,5 +9,4 @@ __all__ = [
|
||||
"Button",
|
||||
"Form",
|
||||
"Step",
|
||||
"MultiSelect",
|
||||
]
|
||||
|
@ -26,7 +26,6 @@ class Step(AbstractContextManager):
|
||||
|
||||
def __enter__(self):
|
||||
print(f"\n```Step\n# {self.title}", flush=True)
|
||||
print("\n```", flush=True)
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
# close the step
|
||||
@ -34,3 +33,4 @@ class Step(AbstractContextManager):
|
||||
IDEService().ide_logging(
|
||||
"debug", f"Step {self.title} took {end_time - self.enter_time:.2f} seconds"
|
||||
)
|
||||
print("\n```", flush=True)
|
||||
|
@ -163,57 +163,6 @@ class Checkbox(Widget):
|
||||
self._selections = selections
|
||||
|
||||
|
||||
class MultiSelect(Checkbox):
|
||||
"""
|
||||
ChatMark syntax:
|
||||
```chatmark
|
||||
Which files would you like to commit? I've suggested a few.
|
||||
> {x}(file1) devchat/engine/prompter.py
|
||||
> {x}(file2) devchat/prompt.py
|
||||
> {}(file3) tests/test_cli_prompt.py
|
||||
```
|
||||
|
||||
Response:
|
||||
```yaml
|
||||
file1: checked
|
||||
file3: checked
|
||||
```
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
options: List[str],
|
||||
check_states: Optional[List[bool]] = None,
|
||||
title: Optional[str] = None,
|
||||
submit_button_name: str = "Submit",
|
||||
cancel_button_name: str = "Cancel",
|
||||
):
|
||||
"""
|
||||
options: options to be selected
|
||||
check_states: initial check states of options, default to all False
|
||||
title: title of the widget
|
||||
"""
|
||||
super().__init__(options, check_states, title, submit_button_name, cancel_button_name)
|
||||
|
||||
def _in_chatmark(self) -> str:
|
||||
"""
|
||||
Generate ChatMark syntax for checkbox options
|
||||
Use the index of option to generate id/key
|
||||
"""
|
||||
lines = []
|
||||
|
||||
if self._title:
|
||||
lines.append(self._title)
|
||||
|
||||
for idx, (option, state) in enumerate(zip(self._options, self._states)):
|
||||
mark = "{x}" if state else "{}"
|
||||
key = self.gen_id(self._id_prefix, idx)
|
||||
lines.append(f"> {mark}({key}) {option}")
|
||||
|
||||
text = "\n".join(lines)
|
||||
return text
|
||||
|
||||
|
||||
class TextEditor(Widget):
|
||||
"""
|
||||
ChatMark syntax:
|
||||
@ -253,24 +202,14 @@ class TextEditor(Widget):
|
||||
super().__init__(submit_button_name, cancel_button_name)
|
||||
|
||||
self._title = title
|
||||
self._text = self._handle_block_flag(text)
|
||||
self._text = text
|
||||
|
||||
self._editor_key = self.gen_id(self._id_prefix, 0)
|
||||
self._new_text: Optional[str] = None
|
||||
|
||||
def _handle_block_flag(self, text: str):
|
||||
"""convert \\ to \\, and ` to \\`"""
|
||||
return text.replace("```", "\\`\\`\\`") if text else text
|
||||
|
||||
def _remove_block_flag(self, text: str):
|
||||
"""convert \\ to \\, and \\` to `"""
|
||||
return text.replace("\\`\\`\\`", "```") if text else text
|
||||
|
||||
@property
|
||||
def new_text(self) -> Optional[str]:
|
||||
if self._new_text is None:
|
||||
return None
|
||||
return self._remove_block_flag(self._new_text)
|
||||
def new_text(self):
|
||||
return self._new_text
|
||||
|
||||
def _in_chatmark(self) -> str:
|
||||
"""
|
||||
|
@ -117,7 +117,7 @@ class IDEService:
|
||||
return self._result
|
||||
|
||||
@rpc_method
|
||||
def diff_apply(self, filepath, content, autoedit: bool = False) -> bool:
|
||||
def diff_apply(self, filepath, content) -> bool:
|
||||
"""
|
||||
Applies a given diff to a file.
|
||||
|
||||
@ -152,55 +152,3 @@ class IDEService:
|
||||
if self.ide_name() == "vscode":
|
||||
return selected_range()
|
||||
return IdeaIDEService().get_selected_range()
|
||||
|
||||
@rpc_method
|
||||
def get_diagnostics_in_range(self, fileName: str, startLine: int, endLine: int) -> List[str]:
|
||||
"""
|
||||
Retrieves diagnostics for a specific range of code in the current IDE.
|
||||
|
||||
Returns:
|
||||
A list of diagnostic messages for the specified range.
|
||||
"""
|
||||
return self._result
|
||||
|
||||
@rpc_method
|
||||
def get_collapsed_code(self, fileName: str, startLine: int, endLine: int) -> str:
|
||||
"""
|
||||
Retrives collapsed code exclude specfic range of code in the current IDE.
|
||||
|
||||
Returns:
|
||||
The collapsed code.
|
||||
"""
|
||||
return self._result
|
||||
|
||||
@rpc_method
|
||||
def get_extension_tools_path(self) -> str:
|
||||
"""
|
||||
Retrives extension tools path.
|
||||
|
||||
Returns:
|
||||
The extension tools path.
|
||||
"""
|
||||
return self._result
|
||||
|
||||
@rpc_method
|
||||
def select_range(
|
||||
self, fileName: str, startLine: int, startColumn: int, endLine: int, endColumn: int
|
||||
) -> bool:
|
||||
"""
|
||||
Selects a range of text in the specified file.
|
||||
|
||||
Args:
|
||||
fileName: The name of the file.
|
||||
startLine: The starting line of the selection (0-based).
|
||||
startColumn: The starting column of the selection (0-based).
|
||||
endLine: The ending line of the selection (0-based).
|
||||
endColumn: The ending column of the selection (0-based).
|
||||
|
||||
Returns:
|
||||
A boolean indicating whether the selection was successful.
|
||||
|
||||
Note:
|
||||
If startLine is -1, it cancels the current selection.
|
||||
"""
|
||||
return self._result
|
||||
|
@ -1,3 +1,5 @@
|
||||
import os
|
||||
|
||||
from .rpc import rpc_call
|
||||
from .types import LocationWithText
|
||||
|
||||
@ -8,7 +10,12 @@ def run_code(code: str):
|
||||
|
||||
|
||||
@rpc_call
|
||||
def diff_apply(filepath, content, autoedit=False):
|
||||
def diff_apply(filepath, content):
|
||||
pass
|
||||
|
||||
|
||||
@rpc_call
|
||||
def get_symbol_defines_in_selected_code():
|
||||
pass
|
||||
|
||||
|
||||
@ -65,18 +72,6 @@ def active_text_editor():
|
||||
return run_code(code=code)
|
||||
|
||||
|
||||
def get_selected_text():
|
||||
code = """
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
if (editor) {
|
||||
const selection = editor.selection;
|
||||
return editor.document.getText(selection);
|
||||
}
|
||||
return '';
|
||||
"""
|
||||
return run_code(code=code)
|
||||
|
||||
|
||||
def open_folder(folder: str):
|
||||
folder = folder.replace("\\", "/")
|
||||
code = (
|
||||
@ -86,21 +81,6 @@ def open_folder(folder: str):
|
||||
run_code(code=code)
|
||||
|
||||
|
||||
def get_visible_text():
|
||||
code = """
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
if (editor) {
|
||||
const visibleRanges = editor.visibleRanges;
|
||||
if (visibleRanges.length > 0) {
|
||||
const visibleRange = visibleRanges[0];
|
||||
return editor.document.getText(visibleRange);
|
||||
}
|
||||
}
|
||||
return '';
|
||||
"""
|
||||
return run_code(code=code)
|
||||
|
||||
|
||||
def visible_lines():
|
||||
active_document = active_text_editor()
|
||||
fail_result = {
|
||||
@ -111,17 +91,22 @@ def visible_lines():
|
||||
|
||||
if not active_document:
|
||||
return fail_result
|
||||
if not os.path.exists(active_document["document"]["uri"]["fsPath"]):
|
||||
return fail_result
|
||||
|
||||
file_path = active_document["document"]["uri"]["fsPath"]
|
||||
start_line = active_document["visibleRanges"][0][0]["line"]
|
||||
end_line = active_document["visibleRanges"][0][1]["line"]
|
||||
|
||||
# 获取可见文本内容
|
||||
visible_text = get_visible_text()
|
||||
# read file lines from start_line to end_line
|
||||
with open(file_path, "r", encoding="utf-8") as file:
|
||||
_lines = file.readlines()
|
||||
_visible_lines = _lines[start_line : end_line + 1]
|
||||
|
||||
# continue with the rest of the function
|
||||
return {
|
||||
"filePath": file_path,
|
||||
"visibleText": visible_text,
|
||||
"visibleText": "".join(_visible_lines),
|
||||
"visibleRange": [start_line, end_line],
|
||||
}
|
||||
|
||||
@ -154,24 +139,24 @@ def selected_lines():
|
||||
|
||||
if not active_document:
|
||||
return fail_result
|
||||
if not os.path.exists(active_document["document"]["uri"]["fsPath"]):
|
||||
return fail_result
|
||||
|
||||
# 获取活动文档的文件路径
|
||||
file_path = active_document["document"]["uri"]["fsPath"]
|
||||
# 获取选择区域的起始行
|
||||
start_line = active_document["selection"]["start"]["line"]
|
||||
start_col = active_document["selection"]["start"]["character"]
|
||||
# 获取选择区域的结束行
|
||||
end_line = active_document["selection"]["end"]["line"]
|
||||
# 获取选择区域的结束列
|
||||
end_col = active_document["selection"]["end"]["character"]
|
||||
|
||||
# 获取编辑器当前内容
|
||||
selected_text = get_selected_text()
|
||||
# read file lines from start_line to end_line
|
||||
with open(file_path, "r", encoding="utf-8") as file:
|
||||
_lines = file.readlines()
|
||||
_selected_lines = _lines[start_line : end_line + 1]
|
||||
|
||||
# continue with the rest of the function
|
||||
return {
|
||||
"filePath": file_path,
|
||||
"selectedText": selected_text,
|
||||
"selectedText": "".join(_selected_lines),
|
||||
"selectedRange": [start_line, start_col, end_line, end_col],
|
||||
}
|
||||
|
||||
|
@ -1,3 +0,0 @@
|
||||
from .call import workflow_call
|
||||
|
||||
__all__ = ["workflow_call"]
|
@ -1,149 +0,0 @@
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
def find_workflow_script(command_name: str) -> str:
|
||||
"""
|
||||
根据命令名称查找对应的工作流脚本路径
|
||||
|
||||
Args:
|
||||
command_name: 工作流命令名称,如 "github.commit"
|
||||
|
||||
Returns:
|
||||
找到的脚本路径,如果未找到则返回空字符串
|
||||
"""
|
||||
# 工作流目录优先级: custom > community > merico
|
||||
workflow_base_dirs = [
|
||||
os.path.expanduser("~/.chat/scripts/custom"),
|
||||
os.path.expanduser("~/.chat/scripts/community"),
|
||||
os.path.expanduser("~/.chat/scripts/merico"),
|
||||
]
|
||||
|
||||
# 解析命令名称,处理子命令
|
||||
parts = command_name.split(".")
|
||||
|
||||
for base_dir in workflow_base_dirs:
|
||||
# 检查custom目录下是否有config.yml定义命名空间
|
||||
if base_dir.endswith("/custom"):
|
||||
config_path = os.path.join(base_dir, "config.yml")
|
||||
namespaces = []
|
||||
if os.path.exists(config_path):
|
||||
try:
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config = yaml.safe_load(f)
|
||||
namespaces = config.get("namespaces", [])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 在每个命名空间下查找
|
||||
for namespace in namespaces:
|
||||
namespace_dir = os.path.join(base_dir, namespace)
|
||||
script_path = find_script_in_path(namespace_dir, parts)
|
||||
if script_path:
|
||||
return script_path
|
||||
else:
|
||||
# 直接在base_dir下查找
|
||||
script_path = find_script_in_path(base_dir, parts)
|
||||
if script_path:
|
||||
return script_path
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
def find_script_in_path(base_dir: str, command_parts: list) -> str:
|
||||
"""
|
||||
在指定路径下查找工作流脚本
|
||||
|
||||
Args:
|
||||
base_dir: 基础目录
|
||||
command_parts: 命令名称拆分的部分
|
||||
|
||||
Returns:
|
||||
找到的脚本路径,如果未找到则返回空字符串
|
||||
"""
|
||||
# 构建工作流目录路径
|
||||
workflow_dir = os.path.join(base_dir, *command_parts)
|
||||
|
||||
# 检查目录是否存在
|
||||
if not os.path.isdir(workflow_dir):
|
||||
return ""
|
||||
|
||||
# 查找目录下的Python脚本
|
||||
py_files = [f for f in os.listdir(workflow_dir) if f.endswith(".py")]
|
||||
|
||||
# 如果只有一个Python脚本,直接返回
|
||||
if len(py_files) == 1:
|
||||
return os.path.join(workflow_dir, py_files[0])
|
||||
|
||||
# 如果有多个Python脚本,查找command.yml
|
||||
yml_path = os.path.join(workflow_dir, "command.yml")
|
||||
if os.path.exists(yml_path):
|
||||
try:
|
||||
with open(yml_path, "r", encoding="utf-8") as f:
|
||||
yml_content = f.read()
|
||||
|
||||
# 查找steps部分中的脚本名称
|
||||
for py_file in py_files:
|
||||
py_file_name = os.path.splitext(py_file)[0]
|
||||
# 查找类似 $command_path/script_name.py 的模式
|
||||
if re.search(rf"\$command_path/{py_file_name}\.py", yml_content):
|
||||
return os.path.join(workflow_dir, py_file)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 尝试查找与最后一个命令部分同名的脚本
|
||||
last_part_script = f"{command_parts[-1]}.py"
|
||||
if last_part_script in py_files:
|
||||
return os.path.join(workflow_dir, last_part_script)
|
||||
|
||||
# 尝试查找名为command.py的脚本
|
||||
if "command.py" in py_files:
|
||||
return os.path.join(workflow_dir, "command.py")
|
||||
|
||||
# 没有找到合适的脚本
|
||||
return ""
|
||||
|
||||
|
||||
def workflow_call(command: str) -> int:
|
||||
"""
|
||||
调用工作流命令
|
||||
|
||||
Args:
|
||||
command: 完整的工作流命令,如 "/github.commit message"
|
||||
|
||||
Returns:
|
||||
命令执行的返回码
|
||||
"""
|
||||
# 解析命令和参数
|
||||
parts = command.strip().split(maxsplit=1)
|
||||
cmd_name = parts[0].lstrip("/")
|
||||
cmd_args = parts[1] if len(parts) > 1 else ""
|
||||
|
||||
# 查找对应的工作流脚本
|
||||
script_path = find_workflow_script(cmd_name)
|
||||
|
||||
if not script_path:
|
||||
print(f"找不到工作流命令: {cmd_name}")
|
||||
return 1
|
||||
|
||||
# 使用Popen并将标准输入输出错误流连接到父进程
|
||||
process = subprocess.Popen(
|
||||
[sys.executable, script_path, cmd_args],
|
||||
stdin=sys.stdin, # 父进程的标准输入传递给子进程
|
||||
stdout=sys.stdout, # 子进程的标准输出传递给父进程
|
||||
stderr=sys.stderr, # 子进程的标准错误传递给父进程
|
||||
text=True, # 使用文本模式
|
||||
bufsize=1, # 行缓冲,确保输出及时显示
|
||||
)
|
||||
|
||||
# 等待子进程完成并获取返回码
|
||||
return_code = process.wait()
|
||||
|
||||
if return_code != 0:
|
||||
print(f"命令执行失败,返回码: {return_code}")
|
||||
|
||||
return return_code
|
@ -1,24 +0,0 @@
|
||||
### ask_issue
|
||||
|
||||
自动修复代码中的 lint 错误。
|
||||
|
||||
#### 用途
|
||||
这个命令帮助开发者快速识别和修复代码中的 lint 错误。它利用 AI 分析选中的代码行,识别 lint 问题,并提供智能修复建议。
|
||||
|
||||
#### 使用方法
|
||||
1. 在 IDE 中选择包含 lint 错误的代码行。
|
||||
2. 运行命令:/ask_issue
|
||||
3. 命令会自动处理选中的代码和相关的 lint 诊断信息。
|
||||
4. AI 将生成问题解释和修复方案。
|
||||
|
||||
#### 注意事项
|
||||
- 运行命令前,确保已选择包含 lint 错误的具体代码行。
|
||||
- 命令优先处理 SonarLint 诊断的问题。
|
||||
- 只关注并修复选中行的 lint 错误,不会处理其他潜在问题。
|
||||
- AI 生成的修复方案包含问题解释和修改后的代码片段。
|
||||
- 修改后的代码以 Markdown 格式展示,包含足够的上下文信息以便定位。
|
||||
|
||||
#### 额外信息
|
||||
- 该命令使用 AI 模型进行分析和修复建议。
|
||||
- 修复建议会考虑代码的上下文,确保修改不会影响其他部分的正确性。
|
||||
- 对于复杂的 lint 错误,可能需要人工审核 AI 的修复建议。
|
@ -1,4 +0,0 @@
|
||||
description: Automatically fix lint errors.
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/main.py
|
@ -1,286 +0,0 @@
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from devchat.llm import chat
|
||||
from devchat.memory import FixSizeChatMemory
|
||||
|
||||
from lib.ide_service import IDEService
|
||||
|
||||
|
||||
def extract_edits_block(text):
|
||||
"""
|
||||
Extracts the first Markdown code block from the given text without the language specifier.
|
||||
|
||||
:param text: A string containing Markdown text
|
||||
:return: The content of the first Markdown code block, or None if not found
|
||||
"""
|
||||
index = text.find("```edits")
|
||||
if index == -1:
|
||||
return None
|
||||
else:
|
||||
start = index + len("```edits")
|
||||
end = text.find("```", start)
|
||||
if end == -1:
|
||||
return None
|
||||
else:
|
||||
return text[start:end]
|
||||
|
||||
|
||||
def extract_markdown_block(text):
|
||||
"""
|
||||
Extracts the first Markdown code block from the given text without the language specifier.
|
||||
|
||||
:param text: A string containing Markdown text
|
||||
:return: The content of the first Markdown code block, or None if not found
|
||||
"""
|
||||
edit_code = extract_edits_block(text)
|
||||
if edit_code:
|
||||
return edit_code
|
||||
|
||||
pattern = r"```(?:\w+)?\s*\n(.*?)\n```"
|
||||
match = re.search(pattern, text, re.DOTALL)
|
||||
|
||||
if match:
|
||||
block_content = match.group(1)
|
||||
return block_content
|
||||
else:
|
||||
# whether exist ```language?
|
||||
if text.find("```"):
|
||||
return None
|
||||
return text
|
||||
|
||||
|
||||
# step 1 : get selected code
|
||||
def get_selected_code():
|
||||
selected_data = IDEService().get_selected_range().dict()
|
||||
|
||||
if selected_data["range"]["start"] == -1:
|
||||
return None, None, None
|
||||
|
||||
if selected_data["range"]["start"]["line"] != selected_data["range"]["end"]["line"]:
|
||||
print("Please select the line code of issue reported.\n\n", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
return selected_data["abspath"], selected_data["text"], selected_data["range"]["start"]["line"]
|
||||
|
||||
|
||||
# step 2 : input issue descriptions
|
||||
def input_issue_descriptions(file_path, issue_line_num):
|
||||
diagnostics = IDEService().get_diagnostics_in_range(file_path, issue_line_num, issue_line_num)
|
||||
if not diagnostics:
|
||||
return None
|
||||
|
||||
# select first sonarlint diagnostic
|
||||
for diagnostic in diagnostics:
|
||||
if diagnostic.find("<sonar") > 0:
|
||||
return diagnostic
|
||||
return diagnostics[0]
|
||||
|
||||
|
||||
# step 3 : call llm to generate fix solutions
|
||||
SYSTEM_ROLE_DIFF = """
|
||||
You are a code refactoring assistant.
|
||||
Your task is to refactor the user's code to fix lint diagnostics.
|
||||
You will be provided with a code snippet and a list of diagnostics. \
|
||||
Your response should include two parts:
|
||||
|
||||
1. An explanation of the reason for the diagnostics and how to fix them.
|
||||
2. The edited code snippet with the diagnostics fixed, using markdown format for clarity.
|
||||
|
||||
The markdown block for edits should look like this:
|
||||
|
||||
```edits
|
||||
def hello():
|
||||
print("Call hello():")
|
||||
+ print("hello")
|
||||
|
||||
...
|
||||
|
||||
- hello(20)
|
||||
+ hello()
|
||||
```
|
||||
Or like this, if a variable is not defined:
|
||||
|
||||
```edits
|
||||
...
|
||||
+ cur_file = __file__
|
||||
print(cur_file)
|
||||
```
|
||||
Please note the following important points:
|
||||
|
||||
1. The new code should maintain the correct indentation. \
|
||||
The "+ " sign is followed by two spaces for indentation, \
|
||||
which should be included in the edited code.
|
||||
2. In addition to outputting key editing information, \
|
||||
sufficient context (i.e., key information before and after editing) \
|
||||
should also be provided to help locate the specific position of the edited line.
|
||||
3. Don't output all file lines, if some lines are unchanged, \
|
||||
please use "..." to indicate the ignored lines.
|
||||
4. Use "+ " and "- " at start of the line to indicate the addition and deletion of lines.
|
||||
|
||||
Here are some examples of incorrect responses:
|
||||
|
||||
Incorrect example 1, where the indentation is not correct:
|
||||
|
||||
```edits
|
||||
def hello():
|
||||
print("Call hello():")
|
||||
+ print("hello")
|
||||
```
|
||||
In this case, if the "+ " sign and the extra space are removed, \
|
||||
the print("hello") statement will lack the necessary two spaces for correct indentation.
|
||||
|
||||
Incorrect example 2, where no other code lines are provided:
|
||||
|
||||
```edits
|
||||
+ print("hello")
|
||||
```
|
||||
This is an incorrect example because without additional context, \
|
||||
it's unclear where the new print("hello") statement should be inserted.
|
||||
"""
|
||||
|
||||
SYSTEM_ROLE_CODEBLOCK = """
|
||||
你是一个重构工程师,你需要根据错误描述,对代码进行问题修正,只需要关注描述的问题,不需要关注代码中的其他问题。
|
||||
|
||||
输出的修正代码中,如果修改了多个代码段,中间没有修改的代码段,请使用...表示。
|
||||
每一个被修改的代码段,应该包含前后至少3行未修改的代码,作为修改代码段的边界表示。
|
||||
|
||||
输出一个代码块中,例如:
|
||||
```edits
|
||||
def hello():
|
||||
msg = "hello"
|
||||
print(msg)
|
||||
|
||||
...
|
||||
|
||||
if __name__ == "__main__":
|
||||
hello()
|
||||
```
|
||||
"""
|
||||
|
||||
|
||||
LLM_MODEL = os.environ.get("LLM_MODEL", "gpt-3.5-turbo-1106")
|
||||
if LLM_MODEL in [
|
||||
"qwen2-72b-instruct",
|
||||
"qwen-long",
|
||||
"qwen-turbo",
|
||||
"Yi-34B-Chat",
|
||||
"deepseek-coder",
|
||||
"xinghuo-3.5",
|
||||
]:
|
||||
SYSTEM_ROLE = SYSTEM_ROLE_CODEBLOCK
|
||||
else:
|
||||
SYSTEM_ROLE = SYSTEM_ROLE_DIFF
|
||||
MESSAGES_A = [
|
||||
{
|
||||
"role": "system",
|
||||
"content": SYSTEM_ROLE,
|
||||
},
|
||||
]
|
||||
|
||||
# step 3 : call llm to generate fix solutions
|
||||
PROMPT = """
|
||||
|
||||
Here is the code file:
|
||||
|
||||
{file_content}
|
||||
|
||||
There is an issue in the following code:
|
||||
|
||||
{issue_line_code}
|
||||
|
||||
{issue_description}
|
||||
|
||||
Here is the rule description:
|
||||
|
||||
{rule_description}
|
||||
|
||||
Please focus only on the error described in the prompt. \
|
||||
Other errors in the code should be disregarded.
|
||||
|
||||
"""
|
||||
|
||||
memory = FixSizeChatMemory(max_size=20, messages=MESSAGES_A)
|
||||
|
||||
|
||||
@chat(prompt=PROMPT, stream_out=True, memory=memory)
|
||||
def call_llm_to_generate_fix_solutions(
|
||||
file_content, issue_line_code, issue_description, rule_description
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
# current file content
|
||||
def get_current_file_content(file_path, issue_line_num):
|
||||
try:
|
||||
return IDEService().get_collapsed_code(file_path, issue_line_num, issue_line_num)
|
||||
except Exception:
|
||||
print("Error reading file:", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
# get issue description
|
||||
def get_rule_description(issue_description):
|
||||
def parse_source_code(text):
|
||||
pattern = r"<(\w+):(.+?)>"
|
||||
match = re.search(pattern, text)
|
||||
|
||||
if match:
|
||||
source = match.group(1)
|
||||
code = match.group(2)
|
||||
return source, code
|
||||
else:
|
||||
return None, None
|
||||
|
||||
issue_source, issue_code = parse_source_code(issue_description)
|
||||
if issue_source.find("sonar") == -1:
|
||||
return issue_description
|
||||
|
||||
issue_id = issue_code.split(":")[-1]
|
||||
issue_language = issue_code.split(":")[0]
|
||||
|
||||
tools_path = IDEService().get_extension_tools_path()
|
||||
rules_path = "sonar-rspec"
|
||||
|
||||
rule_path = os.path.join(tools_path, rules_path, "rules", issue_id, issue_language, "rule.adoc")
|
||||
if os.path.exists(rule_path):
|
||||
with open(rule_path, "r", encoding="utf-8") as file:
|
||||
return file.read()
|
||||
return issue_description
|
||||
|
||||
|
||||
def main():
|
||||
print("start fix issue ...\n\n")
|
||||
file_path, issue_line, issue_line_num = get_selected_code()
|
||||
if not file_path or not issue_line:
|
||||
print("No code selected. Please select the code line you want to fix.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
issue_description = input_issue_descriptions(file_path, issue_line_num)
|
||||
if not issue_description:
|
||||
print(
|
||||
"There are no issues to resolve on the current line. "
|
||||
"Please select the line where an issue needs to be resolved."
|
||||
)
|
||||
sys.exit(0)
|
||||
|
||||
print("make llm prompt ...\n\n")
|
||||
current_file_content = get_current_file_content(file_path, issue_line_num)
|
||||
rule_description = get_rule_description(issue_description)
|
||||
# print("Rule description:\n\n", rule_description, end="\n\n")
|
||||
|
||||
print("call llm to fix issue ...\n\n")
|
||||
fix_solutions = call_llm_to_generate_fix_solutions(
|
||||
file_content=current_file_content,
|
||||
issue_line_code=issue_line,
|
||||
issue_description=issue_description,
|
||||
rule_description=rule_description,
|
||||
)
|
||||
if not fix_solutions:
|
||||
sys.exit(1)
|
||||
|
||||
print("\n\n", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,47 +0,0 @@
|
||||
# 在 ask/command.py 中
|
||||
import os
|
||||
import sys
|
||||
|
||||
from devchat.llm import chat
|
||||
|
||||
from lib.ide_service import IDEService
|
||||
|
||||
ROOT_WORKFLOW_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
sys.path.append(ROOT_WORKFLOW_DIR)
|
||||
|
||||
from chatflow.util.contexts import CONTEXTS # noqa: E402
|
||||
|
||||
PROMPT = (
|
||||
f"""
|
||||
{CONTEXTS.replace("{", "{{").replace("}", "}}")}
|
||||
"""
|
||||
+ """
|
||||
当前选中代码:{selected_code}
|
||||
当前打开文件路径:{file_path}
|
||||
用户要求或问题:{question}
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
@chat(prompt=PROMPT, stream_out=True)
|
||||
def ask(question, selected_code, file_path):
|
||||
pass
|
||||
|
||||
|
||||
def get_selected_code():
|
||||
"""Retrieves the selected lines of code from the user's selection."""
|
||||
selected_data = IDEService().get_selected_range().dict()
|
||||
return selected_data
|
||||
|
||||
|
||||
def main(question):
|
||||
selected_text = get_selected_code()
|
||||
file_path = selected_text.get("abspath", "")
|
||||
code_text = selected_text.get("text", "")
|
||||
|
||||
ask(question=question, selected_code=code_text, file_path=file_path)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv[1])
|
@ -1,4 +0,0 @@
|
||||
description: 'Generate workflow command, input command information.'
|
||||
input: required
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1,424 +0,0 @@
|
||||
"""
|
||||
生成工作流命令实现
|
||||
|
||||
步骤1: 根据用户输入的工作流命令定义信息,生成相关工作流实现步骤描述,展示相关信息,等待用户确认;
|
||||
步骤2: 根据用户确认的工作流实现步骤描述,生成工作流命令实现代码,并保存到指定文件中;
|
||||
"""
|
||||
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import yaml
|
||||
from devchat.llm import chat, chat_json
|
||||
|
||||
from lib.chatmark import Form, Radio, Step, TextEditor
|
||||
from lib.ide_service import IDEService
|
||||
|
||||
ROOT_WORKFLOW_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
sys.path.append(ROOT_WORKFLOW_DIR)
|
||||
|
||||
from chatflow.util.contexts import CONTEXTS # noqa: E402
|
||||
|
||||
# 工作流命令定义模板
|
||||
COMMAND_YML_TEMPLATE = """description: {description}
|
||||
{input_section}
|
||||
{help_section}{workflow_python_section}
|
||||
steps:
|
||||
- run: ${python_cmd} $command_path/command.py{input_param}
|
||||
"""
|
||||
|
||||
# 工作流命令实现模板
|
||||
COMMAND_PY_TEMPLATE = """#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
{imports}
|
||||
|
||||
def main():
|
||||
{main_code}
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
"""
|
||||
|
||||
# 从用户输入提取工作流信息的提示
|
||||
EXTRACT_INFO_PROMPT = """
|
||||
请从以下用户输入中提取创建工作流命令所需的关键信息:
|
||||
|
||||
用户输入:
|
||||
{user_input}
|
||||
|
||||
工作流上下文信息:
|
||||
{contexts}
|
||||
|
||||
请提取以下信息并以JSON格式返回:
|
||||
1. command_name: 工作流命令名称,应以/开头,如"/example"或"/category.command"
|
||||
2. description: 工作流命令的简短描述
|
||||
3. input_required: 工作流是否需要输入参数(true/false)
|
||||
4. custom_env: 是否需要自定义Python环境(true/false)
|
||||
5. env_name: 如果需要自定义环境,环境名称是什么
|
||||
6. dependencies: 如果需要自定义环境,依赖文件名是什么(如requirements.txt)
|
||||
7. purpose: 工作流的主要目的和功能
|
||||
8. implementation_ideas: 实现思路的简要描述
|
||||
|
||||
如果某项信息在用户输入中未明确指定,请根据上下文合理推断。
|
||||
|
||||
返回格式示例:
|
||||
{{
|
||||
"command_name": "/example.command",
|
||||
"description": "这是一个示例命令",
|
||||
"input_required": true,
|
||||
"custom_env": false,
|
||||
"env_name": "",
|
||||
"dependencies": "",
|
||||
"purpose": "这个命令的主要目的是...",
|
||||
"implementation_ideas": "可以通过以下步骤实现..."
|
||||
}}
|
||||
"""
|
||||
|
||||
# 工作流步骤生成提示
|
||||
WORKFLOW_STEPS_PROMPT = """
|
||||
请为以下工作流命令对应脚本文件生成详细的实现步骤描述:
|
||||
|
||||
工作流命令名称: {command_name}
|
||||
工作流命令描述: {description}
|
||||
输入要求: {input_requirement}
|
||||
工作流目的: {purpose}
|
||||
实现思路: {implementation_ideas}
|
||||
|
||||
工作流上下文信息:
|
||||
{contexts}
|
||||
|
||||
请提供清晰的步骤描述,包括:
|
||||
1. 每个步骤需要完成的具体任务
|
||||
2. 每个步骤可能需要的输入和输出
|
||||
3. 每个步骤可能需要使用的IDE Service或ChatMark组件
|
||||
4. 任何其他实现细节
|
||||
|
||||
返回格式应为markdown块包裹的步骤列表,每个步骤都有详细描述。输出示例如下:
|
||||
```steps
|
||||
步骤1: 获取用户输入的重构任务要求
|
||||
步骤2: 调用IDE Service获取选中代码
|
||||
步骤3: 根据用户重构任务要求,调用大模型生成选中代码的重构代码
|
||||
步骤4: 调用IDE Service,将生成的重构代码通过DIFF VIEW方式展示给用户
|
||||
```
|
||||
|
||||
不要输出工作流命令的其他构建步骤,只需要清洗描述工作流命令对应command.py中对应的步骤实现即可。
|
||||
"""
|
||||
|
||||
# 代码实现生成提示
|
||||
CODE_IMPLEMENTATION_PROMPT = """
|
||||
请为以下工作流命令生成Python实现代码:
|
||||
|
||||
工作流命令名称: {command_name}
|
||||
工作流命令描述: {description}
|
||||
输入要求: {input_requirement}
|
||||
自定义环境: {custom_env}
|
||||
工作流实现步骤:
|
||||
{workflow_steps}
|
||||
|
||||
工作流上下文信息:
|
||||
{contexts}
|
||||
|
||||
请生成完整的Python代码实现,包括:
|
||||
1. 必要的导入语句
|
||||
2. 主函数实现
|
||||
3. 按照工作流步骤实现具体功能
|
||||
4. 适当的错误处理
|
||||
5. 必要的注释说明
|
||||
6. 所有markdown代码块都要有明确的语言标识,如python、json、yaml、code等
|
||||
|
||||
代码应该使用IDE Service接口与IDE交互,使用ChatMark组件与用户交互。
|
||||
输出格式应为markdown代码块,语言标识为python。仅输出工作流实现对应的脚本代码块,不需要输出其他逻辑信息。
|
||||
|
||||
只需要输出最终PYTHON代码块,不需要其他信息。例如:
|
||||
```python
|
||||
....
|
||||
```
|
||||
"""
|
||||
|
||||
|
||||
@chat_json(prompt=EXTRACT_INFO_PROMPT)
|
||||
def extract_workflow_info(user_input, contexts):
|
||||
"""从用户输入中提取工作流信息"""
|
||||
pass
|
||||
|
||||
|
||||
@chat(prompt=WORKFLOW_STEPS_PROMPT, stream_out=False)
|
||||
def generate_workflow_steps(
|
||||
command_name, description, input_requirement, purpose, implementation_ideas, contexts
|
||||
):
|
||||
"""生成工作流实现步骤描述"""
|
||||
pass
|
||||
|
||||
|
||||
@chat(prompt=CODE_IMPLEMENTATION_PROMPT, stream_out=False)
|
||||
def generate_workflow_code(
|
||||
command_name, description, input_requirement, custom_env, workflow_steps, contexts
|
||||
):
|
||||
"""生成工作流实现代码"""
|
||||
pass
|
||||
|
||||
|
||||
def parse_command_path(command_name):
|
||||
"""
|
||||
解析命令路径,返回目录结构和最终命令名
|
||||
确保工作流创建在custom目录下的有效namespace中
|
||||
"""
|
||||
parts = command_name.strip("/").split(".")
|
||||
|
||||
# 获取custom目录路径
|
||||
custom_dir = os.path.join(os.path.expanduser("~"), ".chat", "scripts", "custom")
|
||||
|
||||
# 获取custom目录下的有效namespace
|
||||
valid_namespaces = []
|
||||
config_path = os.path.join(custom_dir, "config.yml")
|
||||
|
||||
if os.path.exists(config_path):
|
||||
try:
|
||||
with open(config_path, "r") as f:
|
||||
config = yaml.safe_load(f)
|
||||
if config and "namespaces" in config:
|
||||
valid_namespaces = config["namespaces"]
|
||||
except Exception as e:
|
||||
print(f"读取custom配置文件失败: {str(e)}")
|
||||
|
||||
# 如果没有找到有效namespace,使用默认namespace
|
||||
if not valid_namespaces:
|
||||
print("警告: 未找到有效的custom namespace,将使用默认namespace 'default'")
|
||||
valid_namespaces = ["default"]
|
||||
|
||||
# 确保default namespace存在于config.yml中
|
||||
try:
|
||||
os.makedirs(os.path.dirname(config_path), exist_ok=True)
|
||||
with open(config_path, "w") as f:
|
||||
yaml.dump({"namespaces": ["default"]}, f)
|
||||
except Exception as e:
|
||||
print(f"创建默认namespace配置失败: {str(e)}")
|
||||
|
||||
# 使用第一个有效namespace
|
||||
namespace = valid_namespaces[0]
|
||||
|
||||
# 创建最终命令目录路径
|
||||
command_dir = os.path.join(custom_dir, namespace)
|
||||
|
||||
# 创建目录结构
|
||||
for part in parts[:-1]:
|
||||
command_dir = os.path.join(command_dir, part)
|
||||
|
||||
return command_dir, parts[-1]
|
||||
|
||||
|
||||
def parse_markdown_block(response, block_type="steps"):
|
||||
"""
|
||||
从AI响应中解析指定类型的Markdown代码块内容,支持处理嵌套的代码块。
|
||||
|
||||
Args:
|
||||
response (str): AI生成的响应文本
|
||||
block_type (str): 要解析的代码块类型,默认为"steps"
|
||||
|
||||
Returns:
|
||||
str: 解析出的代码块内容
|
||||
|
||||
Raises:
|
||||
Exception: 解析失败时抛出异常
|
||||
"""
|
||||
try:
|
||||
# 处理可能存在的思考过程
|
||||
if response.find("</think>") != -1:
|
||||
response = response.split("</think>")[-1]
|
||||
|
||||
# 构建起始标记
|
||||
start_marker = f"```{block_type}"
|
||||
end_marker = "```"
|
||||
|
||||
# 查找起始位置
|
||||
start_pos = response.find(start_marker)
|
||||
if start_pos == -1:
|
||||
# 如果没有找到指定类型的标记,直接返回原文本
|
||||
return response.strip()
|
||||
|
||||
# 从标记后开始的位置
|
||||
content_start = start_pos + len(start_marker)
|
||||
|
||||
# 从content_start开始找到第一个未配对的```
|
||||
pos = content_start
|
||||
open_blocks = 1 # 已经有一个开放的块
|
||||
|
||||
while True:
|
||||
# 找到下一个```
|
||||
next_marker = response.find(end_marker, pos)
|
||||
if next_marker == -1:
|
||||
# 如果没有找到结束标记,返回剩余所有内容
|
||||
break
|
||||
|
||||
# 检查这是开始还是结束标记
|
||||
# 向后看是否跟着语言标识符
|
||||
after_marker = response[next_marker + 3 :]
|
||||
# 检查是否是新的代码块开始 - 只要```后面跟着非空白字符,就认为是新代码块开始
|
||||
if after_marker.strip() and not after_marker.startswith("\n"):
|
||||
first_word = after_marker.split()[0]
|
||||
if not any(c in first_word for c in ",.;:!?()[]{}"):
|
||||
open_blocks += 1
|
||||
else:
|
||||
open_blocks -= 1
|
||||
else:
|
||||
open_blocks -= 1
|
||||
|
||||
if open_blocks == 0:
|
||||
# 找到匹配的结束标记
|
||||
return response[content_start:next_marker].strip()
|
||||
|
||||
pos = next_marker + 3
|
||||
|
||||
# 如果没有找到匹配的结束标记,返回从content_start到末尾的内容
|
||||
return response[content_start:].strip()
|
||||
|
||||
except Exception as e:
|
||||
import logging
|
||||
|
||||
logging.info(f"Response: {response}")
|
||||
logging.error(f"Exception in parse_markdown_block: {str(e)}")
|
||||
raise Exception(f"解析{block_type}内容失败: {str(e)}") from e
|
||||
|
||||
|
||||
def create_workflow_files(command_dir, command_name, description, input_required, code):
|
||||
"""创建工作流命令文件"""
|
||||
# 创建命令目录
|
||||
os.makedirs(command_dir, exist_ok=True)
|
||||
|
||||
# 创建command.yml
|
||||
input_section = f"input: {'required' if input_required else 'optional'}"
|
||||
help_section = "help: README.md"
|
||||
input_param = ' "$input"' if input_required else ""
|
||||
|
||||
# 添加自定义环境配置
|
||||
workflow_python_section = ""
|
||||
python_cmd = "devchat_python"
|
||||
|
||||
yml_content = COMMAND_YML_TEMPLATE.format(
|
||||
description=description,
|
||||
input_section=input_section,
|
||||
help_section=help_section,
|
||||
workflow_python_section=workflow_python_section,
|
||||
python_cmd=python_cmd,
|
||||
input_param=input_param,
|
||||
)
|
||||
|
||||
with open(os.path.join(command_dir, "command.yml"), "w") as f:
|
||||
f.write(yml_content)
|
||||
|
||||
# 创建command.py
|
||||
with open(os.path.join(command_dir, "command.py"), "w") as f:
|
||||
f.write(code)
|
||||
|
||||
# 设置执行权限
|
||||
os.chmod(os.path.join(command_dir, "command.py"), 0o755)
|
||||
|
||||
# 创建README.md
|
||||
readme_content = f"# {command_name}\n\n{description}\n"
|
||||
with open(os.path.join(command_dir, "README.md"), "w") as f:
|
||||
f.write(readme_content)
|
||||
|
||||
|
||||
def main():
|
||||
# 获取用户输入
|
||||
user_input = sys.argv[1] if len(sys.argv) > 1 else ""
|
||||
|
||||
# 步骤1: 通过AI分析用户输入,提取必要信息
|
||||
with Step("分析用户输入,提取工作流信息..."):
|
||||
workflow_info = extract_workflow_info(user_input=user_input, contexts=CONTEXTS)
|
||||
|
||||
# 步骤3: 生成工作流实现步骤描述
|
||||
with Step("生成工作流实现步骤描述..."):
|
||||
workflow_steps = generate_workflow_steps(
|
||||
command_name=workflow_info.get("command_name", ""),
|
||||
description=workflow_info.get("description", ""),
|
||||
input_requirement="可选",
|
||||
purpose=workflow_info.get("purpose", ""),
|
||||
implementation_ideas=workflow_info.get("implementation_ideas", ""),
|
||||
contexts=CONTEXTS,
|
||||
)
|
||||
|
||||
workflow_steps = parse_markdown_block(workflow_steps, block_type="steps")
|
||||
|
||||
# 步骤2: 使用Form组件一次性展示所有信息,让用户编辑
|
||||
print("\n## 工作流信息\n")
|
||||
|
||||
# 创建所有编辑组件
|
||||
command_name_editor = TextEditor(workflow_info.get("command_name", "/example.command"))
|
||||
description_editor = TextEditor(workflow_info.get("description", "工作流命令描述"))
|
||||
input_radio = Radio(["必选 (required)", "可选 (optional)"])
|
||||
purpose_editor = TextEditor(workflow_info.get("purpose", "请描述工作流的主要目的和功能"))
|
||||
# ideas_editor = TextEditor(workflow_info.get("implementation_ideas", "请描述实现思路"))
|
||||
steps_editor = TextEditor(workflow_steps)
|
||||
|
||||
# 使用Form组件一次性展示所有编辑组件
|
||||
form = Form(
|
||||
[
|
||||
"命令名称:",
|
||||
command_name_editor,
|
||||
"输入要求:",
|
||||
input_radio,
|
||||
"描述:",
|
||||
description_editor,
|
||||
"工作流目的:",
|
||||
purpose_editor,
|
||||
"实现步骤:",
|
||||
steps_editor,
|
||||
]
|
||||
)
|
||||
|
||||
form.render()
|
||||
|
||||
# 获取用户编辑后的值
|
||||
command_name = command_name_editor.new_text
|
||||
description = description_editor.new_text
|
||||
input_required = input_radio.selection == 0
|
||||
# implementation_ideas = ideas_editor.new_text
|
||||
workflow_steps = steps_editor.new_text
|
||||
|
||||
# 步骤4: 生成工作流实现代码
|
||||
with Step("生成工作流实现代码..."):
|
||||
code = generate_workflow_code(
|
||||
command_name=command_name,
|
||||
description=description,
|
||||
input_requirement="必选" if input_required else "可选",
|
||||
custom_env=False,
|
||||
workflow_steps=workflow_steps,
|
||||
contexts=CONTEXTS,
|
||||
)
|
||||
|
||||
code = code.strip()
|
||||
start_index = code.find("```python")
|
||||
end_index = code.rfind("```")
|
||||
code = code[start_index + len("```python") : end_index].strip()
|
||||
# code = parse_markdown_block(code, block_type="python")
|
||||
|
||||
# 解析命令路径
|
||||
command_dir, final_command = parse_command_path(command_name)
|
||||
full_command_dir = os.path.join(command_dir, final_command)
|
||||
|
||||
# 步骤5: 创建工作流文件
|
||||
with Step(f"创建工作流文件到 {full_command_dir}"):
|
||||
create_workflow_files(full_command_dir, command_name, description, input_required, code)
|
||||
|
||||
# 更新斜杠命令
|
||||
with Step("更新斜杠命令..."):
|
||||
IDEService().update_slash_commands()
|
||||
|
||||
# 在新窗口中打开工作流目录
|
||||
try:
|
||||
subprocess.run(["code", full_command_dir], check=True)
|
||||
except subprocess.SubprocessError:
|
||||
print(f"无法自动打开编辑器,请手动打开工作流目录: {full_command_dir}")
|
||||
|
||||
print(f"\n✅ 工作流命令 {command_name} 已成功创建!")
|
||||
print(f"命令文件位置: {full_command_dir}")
|
||||
print("你现在可以在DevChat中使用这个命令了。")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,4 +0,0 @@
|
||||
description: 'Generate workflow command, input command information.'
|
||||
input: required
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1,64 +0,0 @@
|
||||
工作流开发中封装了部分基础函数,方便开发者在工作流中使用。
|
||||
|
||||
**大模型调用**
|
||||
针对大模型调用,封装了几个装饰器函数,分别用于调用大模型生成文本和生成json格式的文本。
|
||||
- chat装饰器
|
||||
用于调用大模型生成文本,并将生成的文本返回给调用者。具体使用示例如下:
|
||||
```python
|
||||
from devchat.llm import chat
|
||||
|
||||
PROMPT = """
|
||||
对以下代码段进行解释:
|
||||
{code}
|
||||
"""
|
||||
@chat(prompt=PROMPT, stream_out=True)
|
||||
# pylint: disable=unused-argument
|
||||
def explain(code):
|
||||
"""
|
||||
call ai to explain selected code
|
||||
"""
|
||||
pass
|
||||
|
||||
ai_explanation = explain(code="def foo(): pass")
|
||||
```
|
||||
调用explain函数时,需要使用参数名称=参数值的方式传递参数,参数名称必须与PROMPT中使用的参数名称一致。
|
||||
|
||||
- chat_json装饰器
|
||||
用于调用大模型生成json对象。具体使用示例如下:
|
||||
```python
|
||||
from devchat.llm import chat_json
|
||||
PROMPT = (
|
||||
"Give me 5 different git branch names, "
|
||||
"mainly hoping to express: {task}, "
|
||||
"Good branch name should looks like: <type>/<main content>,"
|
||||
"the final result is output in JSON format, "
|
||||
'as follows: {{"names":["name1", "name2", .. "name5"]}}\n'
|
||||
)
|
||||
|
||||
|
||||
@chat_json(prompt=PROMPT)
|
||||
def generate_branch_name(task):
|
||||
pass
|
||||
|
||||
task = "fix bug"
|
||||
branch_names = generate_branch_name(task=task)
|
||||
print(branch_names["names"])
|
||||
```
|
||||
调用generate_branch_name函数时,需要使用参数名称=参数值的方式传递参数,参数名称必须与PROMPT中使用的参数名称一致。
|
||||
使用chat, chat_json的注意:
|
||||
1. 使用chat_json装饰器时,返回的结果是一个字典对象,在PROMPT中描述这个字典对象的结构时需要使用{{}}来表示{}。因为后续操作中会使用类似f-string的方式替代PROMPT中的参数,所以在PROMPT中使用{}时需要使用{{}}来表示{}。
|
||||
2. 使用这两个装饰器时,PROMPT中不要使用markdown的代码块语法。
|
||||
|
||||
**工作流调用**
|
||||
目的是对已有工作流进行复用,在A工作流中调用B工作流。
|
||||
- workflow_call函数
|
||||
调用指定的工作流,并将指定的参数传递给被调用的工作流。具体使用示例如下:
|
||||
```python
|
||||
from lib.workflow import workflow_call
|
||||
|
||||
ret_code = workflow_call("/helloworld some name")
|
||||
if ret_code == 0:
|
||||
print("workflow call success")
|
||||
else:
|
||||
print("workflow call failed")
|
||||
```
|
@ -1,100 +0,0 @@
|
||||
"""
|
||||
为workflow命令提供上下文信息
|
||||
|
||||
编写工作流实现代码需要的上下文:
|
||||
1. IDE Service接口定义
|
||||
2. ChatMark接口定义;
|
||||
3. 工作流命令列表;
|
||||
4. 工作流命令组织、实现规范;
|
||||
5. 基础可用的函数信息;
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
|
||||
def load_file_in_user_scripts(filename: str) -> str:
|
||||
"""
|
||||
从用户脚本目录中加载文件内容
|
||||
"""
|
||||
user_path = os.path.expanduser("~/.chat/scripts")
|
||||
file_path = os.path.join(user_path, filename)
|
||||
with open(file_path, "r") as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def load_local_file(filename: str) -> str:
|
||||
"""
|
||||
从当前脚本所在目录的相对目录加载文件内容
|
||||
"""
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
file_path = os.path.join(script_dir, filename)
|
||||
with open(file_path, "r") as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def load_existing_workflow_defines() -> str:
|
||||
"""从user scripts目录遍历找到所有command.yml文件,并加载其内容"""
|
||||
merico_path = os.path.expanduser("~/.chat/scripts/merico")
|
||||
community_path = os.path.expanduser("~/.chat/scripts/community")
|
||||
custom_path = os.path.expanduser("~/.chat/scripts/custom")
|
||||
|
||||
root_paths = [merico_path]
|
||||
# 遍历community_path、custom_path下直接子目录,将其添加到root_paths作为下一步的根目录
|
||||
for path in [community_path, custom_path]:
|
||||
for root, dirs, files in os.walk(path):
|
||||
if root == path:
|
||||
root_paths.extend([os.path.join(root, d) for d in dirs])
|
||||
break
|
||||
|
||||
wrkflow_defines = []
|
||||
# 遍历所有根目录,对每个根目录进行递归遍历,找到所有command.yml文件,并加载其内容
|
||||
# 将目录名称与command.yml内容拼接成一个字符串,添加到wrkflow_defines列表中
|
||||
# 例如:~/.chat/scripts/merico/github/commit/command.yml被找到,那么拼接的字符串为:
|
||||
# 工作流命令/github.commit的定义:\n<command.yml内容>\n\n
|
||||
for root_path in root_paths:
|
||||
for root, dirs, files in os.walk(root_path):
|
||||
if "command.yml" in files:
|
||||
with open(os.path.join(root, "command.yml"), "r") as f:
|
||||
wrkflow_defines.append(
|
||||
(
|
||||
f"工作流命令/{root[len(root_path) + 1 :].replace(os.sep, '.')}的定义:"
|
||||
f"\n{f.read()}\n\n"
|
||||
)
|
||||
)
|
||||
return "\n".join(wrkflow_defines)
|
||||
|
||||
|
||||
CONTEXTS = f"""
|
||||
工作流开发需要的上下文信息:
|
||||
|
||||
|
||||
# IDE Service接口定义及使用示例
|
||||
IDE Service用于在工作流命令中访问与IDE相关的数据,以及调用IDE提供的功能。
|
||||
## IDEService接口定义
|
||||
接口定义:
|
||||
{load_file_in_user_scripts("lib/ide_service/service.py")}
|
||||
涉及类型定义:
|
||||
{load_file_in_user_scripts("lib/ide_service/types.py")}
|
||||
|
||||
## IDEService接口示例
|
||||
{load_local_file("ide_service_demo.py")}
|
||||
|
||||
|
||||
# ChatMark接口使用示例
|
||||
ChatMark用于在工作流命令中与用户交互,展示信息,获取用户输入。
|
||||
{load_file_in_user_scripts("lib/chatmark/chatmark_example/main.py")}
|
||||
ChatMark Form组件类似于HTML表单,用于组合多个组件,获取相关设置结果。
|
||||
|
||||
|
||||
# 工作流命令规范
|
||||
{load_local_file("workflow_guide.md")}
|
||||
|
||||
|
||||
# 已有工作流命令定义
|
||||
{load_existing_workflow_defines()}
|
||||
|
||||
|
||||
# 工作流内部函数定义
|
||||
{load_local_file("base_functions_guide.md")}
|
||||
|
||||
"""
|
@ -1,3 +0,0 @@
|
||||
from lib.ide_service import IDEService
|
||||
|
||||
IDEService().ide_logging("debug", "Hello IDE Service!")
|
@ -1,52 +0,0 @@
|
||||
一个工作流对应一个目录,目录可以进行嵌套,每个工作流在UI视图中对应了一个斜杠命令,例如/github.commit。
|
||||
|
||||
github工作流目录结构简化后示意如下:
|
||||
github
|
||||
|-- commit
|
||||
|-- command.yml
|
||||
|-- command.py
|
||||
|
||||
|-- README.md
|
||||
|-- new_pr
|
||||
|-- command.yml
|
||||
|-- command.py
|
||||
......
|
||||
|
||||
"command.yml"文件定义了工作流命令的元信息,例如命令的名称、描述、参数等。
|
||||
"command.py"文件定义了工作流命令的实现逻辑。
|
||||
|
||||
拿一个hello world工作流命令为例,目录结构如下:
|
||||
helloworld
|
||||
|-- command.yml
|
||||
|-- command.py
|
||||
|-- README.md
|
||||
|
||||
"command.yml"文件内容如下:
|
||||
```yaml
|
||||
description: Hello Workflow, use as /helloworld <name>
|
||||
input: required
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
||||
````
|
||||
"command.py"文件内容如下:
|
||||
```python
|
||||
import sys
|
||||
print(sys.argv[1])
|
||||
```
|
||||
|
||||
使用一个工作流时,在DevChat AI智能问答视图中输入斜杠命令,例如/helloworld <name>,即可执行该工作流命令。/helloworld表示对应系统可用的工作流。<name>表示工作流的输入,是可选的,如果工作流定义中input为required,则必须输入,否则可以不输入。
|
||||
|
||||
工作流命令有重载优先级,merico目录下工作流 < community目录下工作流 < custom目录下工作流。
|
||||
例如,如果merico与community目录下都有github工作流,那么在DevChat AI智能问答视图中输入/github命令时,会优先执行community目录下的github工作流命令。
|
||||
|
||||
在custom目录下,通过config.yml文件定义custom目录下的工作流目录。例如:
|
||||
namespaces:
|
||||
- bobo
|
||||
那么custom/bobo就是一个工作流存储目录,可以在该目录下定义工作流命令。
|
||||
例如:
|
||||
custom/bobo
|
||||
| ---- hello
|
||||
|------ command.yml
|
||||
|------ command.py
|
||||
那么custom/bobo/hello对应的工作流命令就是/hello.
|
||||
|
@ -1,35 +1,11 @@
|
||||
|
||||
### comments
|
||||
### 操作指南
|
||||
|
||||
这个命令用于自动为选中的代码块生成注释。
|
||||
|
||||
#### 用途
|
||||
- 为选中的代码块快速添加解释性注释
|
||||
- 提高代码可读性和可维护性
|
||||
- 帮助开发者更好地理解代码逻辑
|
||||
|
||||
#### 使用方法
|
||||
1. 在IDE中选中需要添加注释的代码块
|
||||
2. 执行以下命令之一:
|
||||
- 输入 `/comments` 并回车
|
||||
|
||||
#### 注意事项
|
||||
1. 确保在执行命令前已选中代码块
|
||||
2. 生成的注释会插入到相应的代码行之前
|
||||
3. 原有的注释会被保留
|
||||
4. 代码本身不会被修改,只会添加注释
|
||||
|
||||
#### 操作流程
|
||||
1. 选中需要注释的代码块
|
||||
2. 执行comments命令
|
||||
3. 等待注释生成完成
|
||||
4. 自动弹出Diff View,您可以选择接受或拒绝修改
|
||||
|
||||
|
||||
额外信息
|
||||
注释的语言会根据当前IDE的语言设置自动调整
|
||||
对于中文环境,会生成中文注释
|
||||
该命令利用AI技术生成注释,可能需要一定的处理时间
|
||||
生成行间注释,请按照如下步骤操作:
|
||||
1. 选中行间代码。
|
||||
2. 输入\/comments,回车发送;或右键点击**DevChat: Generate Comments**按钮。
|
||||
3. 开始生成行间注释,等待生成结束。
|
||||
4. 自动弹出Diff View,选择是否接受修改。
|
||||
|
||||
如图所示:
|
||||
|
||||
|
@ -259,8 +259,6 @@ def main():
|
||||
code_text = selected_text.get("text", "")
|
||||
|
||||
response = add_comments(selected_text=code_text, file_path=file_path)
|
||||
if not response:
|
||||
sys.exit(1)
|
||||
new_code = extract_markdown_block(response)
|
||||
|
||||
if not new_code:
|
||||
|
@ -1,6 +1,5 @@
|
||||
description: 'Writes a well-formatted commit message for selected code changes and commits them via Git. Include an issue number if desired (e.g., input "/commit to close #12").'
|
||||
hint: to close Issue #issue_number
|
||||
input: optional
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/commit.py "$input" "english"
|
||||
- run: $devchat_python $command_path/commit.py "$input"
|
@ -4,23 +4,12 @@ import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
# from llm_api import chat_completion_stream # noqa: E402
|
||||
from devchat.llm import chat_completion_stream
|
||||
|
||||
from lib.chatmark import Button, Checkbox, Form, TextEditor
|
||||
from lib.chatmark import Checkbox, Form, TextEditor
|
||||
from lib.ide_service import IDEService
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
|
||||
from common_util import assert_exit # noqa: E402
|
||||
from git_api import (
|
||||
get_github_repo,
|
||||
get_github_repo_issues,
|
||||
get_github_username,
|
||||
get_issue_info,
|
||||
subprocess_check_output,
|
||||
subprocess_run,
|
||||
)
|
||||
|
||||
diff_too_large_message_en = (
|
||||
"Commit failed. The modified content is too long "
|
||||
"and exceeds the model's length limit. "
|
||||
@ -35,6 +24,19 @@ diff_too_large_message_zh = (
|
||||
COMMIT_PROMPT_LIMIT_SIZE = 20000
|
||||
|
||||
|
||||
def _T(en_text, zh_text):
|
||||
"""
|
||||
Returns a text in the current language.
|
||||
:param en_text: The English version of the text
|
||||
:param zh_text: The Chinese version of the text
|
||||
:return: The text in the current language
|
||||
"""
|
||||
if IDEService().ide_language() == "zh":
|
||||
return zh_text
|
||||
else:
|
||||
return en_text
|
||||
|
||||
|
||||
def extract_markdown_block(text):
|
||||
"""
|
||||
Extracts the first Markdown code block from the given text without the language specifier.
|
||||
@ -76,20 +78,19 @@ def read_prompt_from_file(filename):
|
||||
- FileNotFoundError: If the file does not exist.
|
||||
- Exception: If any other error occurs during file reading.
|
||||
"""
|
||||
s = IDEService()
|
||||
try:
|
||||
with open(filename, "r", encoding="utf-8") as file:
|
||||
return file.read().strip()
|
||||
except FileNotFoundError:
|
||||
IDEService().ide_logging(
|
||||
"error",
|
||||
s.ide_logging(
|
||||
"info",
|
||||
f"File {filename} not found. "
|
||||
"Please make sure it exists in the same directory as the script.",
|
||||
)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
IDEService().ide_logging(
|
||||
"error", f"An error occurred while reading the file {filename}: {e}"
|
||||
)
|
||||
s.ide_logging("info", f"An error occurred while reading the file {filename}: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@ -144,7 +145,7 @@ def get_modified_files():
|
||||
tuple: 包含两个list的元组,第一个list包含当前修改过的文件,第二个list包含已经staged的文件
|
||||
"""
|
||||
""" 获取当前修改文件列表以及已经staged的文件列表"""
|
||||
output = subprocess_check_output(["git", "status", "-s", "-u"], text=True, encoding="utf-8")
|
||||
output = subprocess.check_output(["git", "status", "-s", "-u"], text=True, encoding="utf-8")
|
||||
lines = output.split("\n")
|
||||
modified_files = []
|
||||
staged_files = []
|
||||
@ -161,9 +162,9 @@ def get_modified_files():
|
||||
# check wether filename is a directory
|
||||
if os.path.isdir(filename):
|
||||
continue
|
||||
modified_files.append(os.path.normpath(strip_file_name(filename)))
|
||||
if status == "M " or status == "A " or status == "D ":
|
||||
staged_files.append(os.path.normpath(strip_file_name(filename)))
|
||||
modified_files.append((os.path.normpath(strip_file_name(filename)), status[1:2]))
|
||||
if status[0:1] == "M" or status[0:1] == "A" or status[0:1] == "D":
|
||||
staged_files.append((os.path.normpath(strip_file_name(filename)), status[0:1]))
|
||||
return modified_files, staged_files
|
||||
|
||||
|
||||
@ -179,10 +180,14 @@ def get_marked_files(modified_files, staged_files):
|
||||
List[str]: 用户选中的文件列表
|
||||
"""
|
||||
# Create two Checkbox instances for staged and unstaged files
|
||||
staged_checkbox = Checkbox(staged_files, [True] * len(staged_files))
|
||||
staged_files_show = [f'{file[1] if file[1]!="?" else "U"} {file[0]}' for file in staged_files]
|
||||
staged_checkbox = Checkbox(staged_files_show, [True] * len(staged_files_show))
|
||||
|
||||
unstaged_files = [file for file in modified_files if file not in staged_files]
|
||||
unstaged_checkbox = Checkbox(unstaged_files, [False] * len(unstaged_files))
|
||||
unstaged_files = [file for file in modified_files if file[1].strip() != ""]
|
||||
unstaged_files_show = [
|
||||
f'{file[1] if file[1]!="?" else "U"} {file[0]}' for file in unstaged_files
|
||||
]
|
||||
unstaged_checkbox = Checkbox(unstaged_files_show, [False] * len(unstaged_files_show))
|
||||
|
||||
# Create a Form with both Checkbox instances
|
||||
form_list = []
|
||||
@ -202,31 +207,37 @@ def get_marked_files(modified_files, staged_files):
|
||||
# Retrieve the selected files from both Checkbox instances
|
||||
staged_checkbox_selections = staged_checkbox.selections if staged_checkbox.selections else []
|
||||
unstaged_selections = unstaged_checkbox.selections if unstaged_checkbox.selections else []
|
||||
selected_staged_files = [staged_files[idx] for idx in staged_checkbox_selections]
|
||||
selected_unstaged_files = [unstaged_files[idx] for idx in unstaged_selections]
|
||||
selected_staged_files = [staged_files[idx][0] for idx in staged_checkbox_selections]
|
||||
selected_unstaged_files = [unstaged_files[idx][0] for idx in unstaged_selections]
|
||||
|
||||
# Combine the selections from both checkboxes
|
||||
selected_files = selected_staged_files + selected_unstaged_files
|
||||
|
||||
return selected_files
|
||||
return selected_staged_files, selected_unstaged_files
|
||||
|
||||
|
||||
def rebuild_stage_list(user_files):
|
||||
def rebuild_stage_list(staged_select_files, unstaged_select_files):
|
||||
"""
|
||||
根据用户选中文件,重新构建stage列表
|
||||
|
||||
Args:
|
||||
user_files: 用户选中的文件列表
|
||||
staged_select_files: 当前选中的已staged文件列表
|
||||
unstaged_select_files: 当前选中的未staged文件列表
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
||||
"""
|
||||
# Unstage all files
|
||||
subprocess_check_output(["git", "reset"])
|
||||
# Stage all user_files
|
||||
for file in user_files:
|
||||
subprocess_run(["git", "add", file])
|
||||
# 获取当前所有staged文件
|
||||
current_staged_files = subprocess.check_output(
|
||||
["git", "diff", "--name-only", "--cached"], text=True
|
||||
).splitlines()
|
||||
|
||||
# 添加unstaged_select_files中的文件到staged
|
||||
for file in unstaged_select_files:
|
||||
subprocess.check_output(["git", "add", file])
|
||||
|
||||
# 将不在staged_select_files中的文件从staged移除
|
||||
user_selected_files = staged_select_files + unstaged_select_files
|
||||
files_to_unstage = [file for file in current_staged_files if file not in user_selected_files]
|
||||
for file in files_to_unstage:
|
||||
subprocess.check_output(["git", "reset", file])
|
||||
|
||||
|
||||
def get_diff():
|
||||
@ -240,13 +251,13 @@ def get_diff():
|
||||
bytes: 返回bytes类型,是git diff --cached命令的输出结果
|
||||
|
||||
"""
|
||||
return subprocess_check_output(["git", "diff", "--cached"])
|
||||
return subprocess.check_output(["git", "diff", "--cached"])
|
||||
|
||||
|
||||
def get_current_branch():
|
||||
try:
|
||||
# 使用git命令获取当前分支名称
|
||||
result = subprocess_check_output(
|
||||
result = subprocess.check_output(
|
||||
["git", "branch", "--show-current"], stderr=subprocess.STDOUT
|
||||
).strip()
|
||||
# 将结果从bytes转换为str
|
||||
@ -260,7 +271,7 @@ def get_current_branch():
|
||||
return None
|
||||
|
||||
|
||||
def generate_commit_message_base_diff(user_input, diff, issue):
|
||||
def generate_commit_message_base_diff(user_input, diff):
|
||||
"""
|
||||
根据diff信息,通过AI生成一个commit消息
|
||||
|
||||
@ -274,10 +285,8 @@ def generate_commit_message_base_diff(user_input, diff, issue):
|
||||
"""
|
||||
global language
|
||||
language_prompt = "You must response commit message in chinese。\n" if language == "zh" else ""
|
||||
prompt = (
|
||||
PROMPT_COMMIT_MESSAGE_BY_DIFF_USER_INPUT.replace("{__DIFF__}", f"{diff}")
|
||||
.replace("{__USER_INPUT__}", f"{user_input + language_prompt}")
|
||||
.replace("{__ISSUE__}", f"{issue}")
|
||||
prompt = PROMPT_COMMIT_MESSAGE_BY_DIFF_USER_INPUT.replace("{__DIFF__}", f"{diff}").replace(
|
||||
"{__USER_INPUT__}", f"{user_input + language_prompt}"
|
||||
)
|
||||
|
||||
model_token_limit_error = (
|
||||
@ -293,7 +302,7 @@ def generate_commit_message_base_diff(user_input, diff, issue):
|
||||
if (
|
||||
not response["content"]
|
||||
and response.get("error", None)
|
||||
and f"{response['error']}".find("This model's maximum context length is") > 0
|
||||
and f'{response["error"]}'.find("This model's maximum context length is") > 0
|
||||
):
|
||||
print(model_token_limit_error)
|
||||
sys.exit(0)
|
||||
@ -320,27 +329,7 @@ def display_commit_message_and_commit(commit_message):
|
||||
new_commit_message = text_editor.new_text
|
||||
if not new_commit_message:
|
||||
return None
|
||||
return subprocess_check_output(["git", "commit", "-m", new_commit_message])
|
||||
|
||||
|
||||
def extract_issue_id(branch_name):
|
||||
if "#" in branch_name:
|
||||
return branch_name.split("#")[-1]
|
||||
return None
|
||||
|
||||
|
||||
def get_issue_json(issue_id):
|
||||
issue = {"id": "no issue id", "title": "", "body": ""}
|
||||
if issue_id:
|
||||
issue = get_issue_info(issue_id)
|
||||
assert_exit(not issue, "Failed to retrieve issue with ID: {issue_id}", exit_code=-1)
|
||||
issue = {
|
||||
"id": issue_id,
|
||||
"html_url": issue["html_url"],
|
||||
"title": issue["title"],
|
||||
"body": issue["body"],
|
||||
}
|
||||
return issue
|
||||
return subprocess.check_output(["git", "commit", "-m", new_commit_message])
|
||||
|
||||
|
||||
def check_git_installed():
|
||||
@ -362,142 +351,56 @@ def check_git_installed():
|
||||
return False
|
||||
|
||||
|
||||
def ask_for_push():
|
||||
"""
|
||||
询问用户是否要推送(push)更改到远程仓库
|
||||
|
||||
Returns:
|
||||
bool: 用户是否选择推送
|
||||
"""
|
||||
|
||||
print(
|
||||
"Step 3/3: Would you like to push your commit to the remote repository?",
|
||||
end="\n\n",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
button = Button(["Yes, push now", "No, I'll push later"])
|
||||
button.render()
|
||||
|
||||
return button.clicked == 0 # 如果用户点击第一个按钮(Yes),则返回True
|
||||
|
||||
|
||||
def push_changes():
|
||||
"""
|
||||
推送更改到远程仓库
|
||||
|
||||
Returns:
|
||||
bool: 推送是否成功
|
||||
"""
|
||||
try:
|
||||
current_branch = get_current_branch()
|
||||
if not current_branch:
|
||||
print(
|
||||
"Could not determine current branch. Push failed.",
|
||||
end="\n\n",
|
||||
file=sys.stderr,
|
||||
flush=True,
|
||||
)
|
||||
return False
|
||||
|
||||
print(f"Pushing changes to origin/{current_branch}...", end="\n\n", flush=True)
|
||||
result = subprocess_run(
|
||||
["git", "push", "origin", current_branch],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
print(f"Push failed: {result.stderr}", end="\n\n", flush=True)
|
||||
return False
|
||||
print("Push completed successfully.", end="\n\n", flush=True)
|
||||
return True
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Push failed: {str(e)}", end="\n\n", file=sys.stderr, flush=True)
|
||||
return False
|
||||
except Exception as e:
|
||||
print(
|
||||
f"An unexpected error occurred: {str(e)}",
|
||||
end="\n\n",
|
||||
file=sys.stderr,
|
||||
flush=True,
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
def get_selected_issue_ids():
|
||||
"""
|
||||
获取用户选中的issue id
|
||||
|
||||
Returns:
|
||||
list: 用户选中的issue id列表
|
||||
"""
|
||||
name = get_github_username()
|
||||
issue_repo = get_github_repo(True)
|
||||
issues = get_github_repo_issues(issue_repo, assignee=name, state="open")
|
||||
if issues:
|
||||
checkbox = Checkbox(
|
||||
[f"#{issue['number']}: {issue['title']}" for issue in issues],
|
||||
title="Select the issues you want to close",
|
||||
)
|
||||
checkbox.render()
|
||||
return [issues[idx]["number"] for idx in checkbox.selections]
|
||||
|
||||
|
||||
def main():
|
||||
global language
|
||||
try:
|
||||
print("Let's follow the steps below.\n\n")
|
||||
start_msg = _T("Let's follow the steps below.\n\n", "开始按步骤操作。\n\n")
|
||||
print(start_msg)
|
||||
# Ensure enough command line arguments are provided
|
||||
if len(sys.argv) < 2:
|
||||
print(
|
||||
"Usage: python script.py <user_input> <language>",
|
||||
file=sys.stderr,
|
||||
flush=True,
|
||||
)
|
||||
print("Usage: python script.py <user_input>", file=sys.stderr, flush=True)
|
||||
sys.exit(-1)
|
||||
|
||||
user_input = sys.argv[1]
|
||||
language = "english"
|
||||
if len(sys.argv) > 2:
|
||||
language = sys.argv[2]
|
||||
language = IDEService().ide_language()
|
||||
|
||||
if not check_git_installed():
|
||||
sys.exit(-1)
|
||||
|
||||
print(
|
||||
"Step 1/3: Select the files you've changed that you wish to include in this commit, "
|
||||
step1_msg = _T(
|
||||
"Step 1/2: Select the files you've changed that you wish to include in this commit, "
|
||||
"then click 'Submit'.",
|
||||
end="\n\n",
|
||||
flush=True,
|
||||
"第一步/2:选择您希望包含在这次提交中的文件,然后点击“提交”。",
|
||||
)
|
||||
print(step1_msg, end="\n\n", flush=True)
|
||||
modified_files, staged_files = get_modified_files()
|
||||
if len(modified_files) == 0:
|
||||
print("No files to commit.", file=sys.stderr, flush=True)
|
||||
sys.exit(-1)
|
||||
print("There are no files to commit.", flush=True)
|
||||
sys.exit(0)
|
||||
|
||||
selected_files = get_marked_files(modified_files, staged_files)
|
||||
if not selected_files:
|
||||
print("No files selected, commit aborted.")
|
||||
staged_select_files, unstaged_select_files = get_marked_files(modified_files, staged_files)
|
||||
if not staged_select_files and not unstaged_select_files:
|
||||
no_files_msg = _T(
|
||||
"No files selected, the commit has been aborted.",
|
||||
"没有选择任何文件,提交已中止。",
|
||||
)
|
||||
print(no_files_msg)
|
||||
return
|
||||
|
||||
rebuild_stage_list(selected_files)
|
||||
rebuild_stage_list(staged_select_files, unstaged_select_files)
|
||||
|
||||
print(
|
||||
"Step 2/3: Review the commit message I've drafted for you. "
|
||||
step2_msg = _T(
|
||||
"Step 2/2: Review the commit message I've drafted for you. "
|
||||
"Edit it below if needed. Then click 'Commit' to proceed with "
|
||||
"the commit using this message.",
|
||||
end="\n\n",
|
||||
flush=True,
|
||||
"第二步/2:查看我为您起草的提交消息。如果需要,请在下面编辑它。然后单击“提交”以使用此消息进行提交。",
|
||||
)
|
||||
print(step2_msg, end="\n\n", flush=True)
|
||||
diff = get_diff()
|
||||
branch_name = get_current_branch()
|
||||
issue_id = extract_issue_id(branch_name)
|
||||
issue = str(get_issue_json(issue_id))
|
||||
if branch_name:
|
||||
user_input += "\ncurrent repo branch name is:" + branch_name
|
||||
commit_message = generate_commit_message_base_diff(user_input, diff, issue)
|
||||
commit_message = generate_commit_message_base_diff(user_input, diff)
|
||||
|
||||
# TODO
|
||||
# remove Closes #IssueNumber in commit message
|
||||
@ -507,28 +410,20 @@ def main():
|
||||
.replace("No specific issue to close", "")
|
||||
.replace("No specific issue mentioned.", "")
|
||||
)
|
||||
# add closes #IssueNumber in commit message from issues from user selected
|
||||
issue_ids = get_selected_issue_ids()
|
||||
if issue_ids:
|
||||
issue_repo = get_github_repo(True)
|
||||
owner_repo = get_github_repo()
|
||||
closes_issue_contents = []
|
||||
for issue_id in issue_ids:
|
||||
closes_issue_contents.append(
|
||||
f"#{issue_id}" if owner_repo == issue_repo else f"{issue_repo}#{issue_id}"
|
||||
)
|
||||
commit_message["content"] += f"\n\nCloses {', '.join(closes_issue_contents)}"
|
||||
|
||||
commit_result = display_commit_message_and_commit(commit_message["content"])
|
||||
if not commit_result:
|
||||
print("Commit aborted.", flush=True)
|
||||
commit_abort_msg = _T(
|
||||
"Commit aborted.",
|
||||
"提交已中止。",
|
||||
)
|
||||
print(commit_abort_msg, flush=True)
|
||||
else:
|
||||
# 添加推送步骤
|
||||
if ask_for_push():
|
||||
if not push_changes():
|
||||
print("Push failed.", flush=True)
|
||||
sys.exit(-1)
|
||||
|
||||
print("Commit completed.", flush=True)
|
||||
commit_completed_msg = _T(
|
||||
"Commit completed.",
|
||||
"提交已完成。",
|
||||
)
|
||||
print(commit_completed_msg, flush=True)
|
||||
sys.exit(0)
|
||||
except Exception as err:
|
||||
print("Exception:", err, file=sys.stderr, flush=True)
|
@ -6,21 +6,23 @@ Objective:** Generate a commit message that succinctly describes the codebase ch
|
||||
3. **Closing Reference (Conditional):** Include the line `Closes #IssueNumber` only if a specific, relevant issue number has been mentioned in the user input.
|
||||
|
||||
**Response Format:**
|
||||
Response should be in the following markdown codeblock format:
|
||||
```commit
|
||||
```
|
||||
type: Title
|
||||
|
||||
- Detail message line 1
|
||||
- Detail message line 2
|
||||
- Detail message line 3
|
||||
|
||||
Closes #IssueNumber
|
||||
```
|
||||
Only output the commit message codeblock, don't include any other text.
|
||||
Only append the \"Closes #IssueNumber\" if the user input explicitly references an issue to close.
|
||||
|
||||
**Constraints:**
|
||||
- Exclude markdown code block indicators (```) and the placeholder \"commit_message\" from your response.
|
||||
- Follow commit message best practices:
|
||||
- Limit the title length to 50 characters.
|
||||
- Limit each summary line to 72 characters.
|
||||
- If the precise issue number is not known or not stated by the user, do not include the closing reference.
|
||||
|
||||
**User Input:** `{__USER_INPUT__}`
|
||||
|
||||
@ -31,15 +33,5 @@ Determine if `{__USER_INPUT__}` contains a reference to closing an issue. If so,
|
||||
{__DIFF__}
|
||||
```
|
||||
|
||||
Related issue:
|
||||
{__ISSUE__}
|
||||
Utilize the provided format to craft a commit message that adheres to the stipulated criteria.
|
||||
|
||||
example output:
|
||||
```commit
|
||||
feature: add update user info API
|
||||
|
||||
- add post method api /user/update
|
||||
- implement update user info logic
|
||||
```
|
||||
|
1
merico/commit/test/prompt/commit_cache.json
Normal file
1
merico/commit/test/prompt/commit_cache.json
Normal file
File diff suppressed because one or more lines are too long
@ -0,0 +1,9 @@
|
||||
prompts:
|
||||
[
|
||||
'../../diffCommitMessagePrompt.txt'
|
||||
]
|
||||
providers:
|
||||
- id: "exec:python commit_message_run.py"
|
||||
tests:
|
||||
- commit_message_tests.yaml
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user