Compare commits
1 Commits
scripts
...
skip-steps
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2d97bc40e6 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,10 +3,7 @@ __pycache__/
|
||||
.DS_Store
|
||||
tmp/
|
||||
|
||||
cache/
|
||||
|
||||
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,252 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
import yaml
|
||||
|
||||
from lib.chatmark import Radio, TextEditor
|
||||
|
||||
|
||||
def _parse_pr_host(pr_url):
|
||||
fields = pr_url.split("/")
|
||||
for field in fields:
|
||||
if field.find(".") > 0:
|
||||
return field
|
||||
return pr_url
|
||||
|
||||
|
||||
def _read_config_value(key):
|
||||
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 key in config_data:
|
||||
return config_data[key]
|
||||
return None
|
||||
|
||||
|
||||
def _save_config_value(key, value):
|
||||
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[key] = value
|
||||
with open(config_path, "w+", encoding="utf-8") as f:
|
||||
json.dump(config_data, f, indent=4)
|
||||
|
||||
|
||||
# 根据PR URL获取不同的仓库管理类型
|
||||
# 支持的类型有:github gitlab bitbucket bitbucket_server azure codecommit gerrit
|
||||
def get_repo_type(url):
|
||||
# 根据URL的特征判断仓库管理类型
|
||||
if "github.com" in url:
|
||||
return "github"
|
||||
elif "gitlab.com" in url or "gitlab" in url:
|
||||
return "gitlab"
|
||||
elif "bitbucket.org" in url:
|
||||
return "bitbucket"
|
||||
elif "bitbucket-server" in url:
|
||||
return "bitbucket_server"
|
||||
elif "dev.azure.com" in url or "visualstudio.com" in url:
|
||||
return "azure"
|
||||
elif "codecommit" in url:
|
||||
return "codecommit"
|
||||
elif "gerrit" in url:
|
||||
return "gerrit"
|
||||
else:
|
||||
pr_host = _parse_pr_host(url)
|
||||
repo_type_map = _read_config_value("repo_type_map")
|
||||
if repo_type_map and pr_host in repo_type_map:
|
||||
return repo_type_map[pr_host]
|
||||
if not repo_type_map:
|
||||
repo_type_map = {}
|
||||
|
||||
radio = Radio(
|
||||
["github", "gitlab", "bitbucket", "bitbucket_server", "azure", "codecommit", "gerrit"],
|
||||
title="Choose the type of your repo:",
|
||||
)
|
||||
radio.render()
|
||||
if radio.selection is None:
|
||||
return None
|
||||
|
||||
rtype = [
|
||||
"github",
|
||||
"gitlab",
|
||||
"bitbucket",
|
||||
"bitbucket_server",
|
||||
"azure",
|
||||
"codecommit",
|
||||
"gerrit",
|
||||
][radio.selection]
|
||||
repo_type_map[pr_host] = rtype
|
||||
_save_config_value("repo_type_map", repo_type_map)
|
||||
return rtype
|
||||
|
||||
|
||||
def read_github_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 "github_token" in config_data:
|
||||
return config_data["github_token"]
|
||||
return ""
|
||||
|
||||
|
||||
def read_server_access_token(repo_type):
|
||||
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 repo_type in config_data and "access_token" in config_data[repo_type]:
|
||||
return config_data[repo_type]["access_token"]
|
||||
return ""
|
||||
|
||||
|
||||
def read_gitlab_host():
|
||||
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_host" in config_data:
|
||||
return config_data["gitlab_host"]
|
||||
return ""
|
||||
|
||||
|
||||
def read_review_inline_config():
|
||||
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 "pr_review_inline" in config_data:
|
||||
return config_data["pr_review_inline"]
|
||||
return False
|
||||
|
||||
|
||||
def save_github_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["github_token"] = github_token
|
||||
with open(config_path, "w+", encoding="utf-8") as f:
|
||||
json.dump(config_data, f, indent=4)
|
||||
|
||||
|
||||
def save_gitlab_host(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_host"] = github_token
|
||||
with open(config_path, "w+", encoding="utf-8") as f:
|
||||
json.dump(config_data, f, indent=4)
|
||||
|
||||
|
||||
def save_server_access_token(repo_type, access_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)
|
||||
|
||||
if repo_type not in config_data:
|
||||
config_data[repo_type] = {}
|
||||
config_data[repo_type]["access_token"] = access_token
|
||||
with open(config_path, "w+", encoding="utf-8") as f:
|
||||
json.dump(config_data, f, indent=4)
|
||||
|
||||
|
||||
def read_github_token_with_input():
|
||||
github_token = read_github_token()
|
||||
if not github_token:
|
||||
# Input your github TOKEN to access github api:
|
||||
github_token_editor = TextEditor("", "Please input your github TOKEN to access:")
|
||||
github_token = github_token_editor.new_text
|
||||
if not github_token:
|
||||
return github_token
|
||||
save_github_token(github_token)
|
||||
return github_token
|
||||
|
||||
|
||||
def read_server_access_token_with_input(pr_url):
|
||||
repo_type = get_repo_type(pr_url)
|
||||
if not repo_type:
|
||||
return ""
|
||||
|
||||
pr_host = _parse_pr_host(pr_url)
|
||||
if repo_type == "gitlab":
|
||||
# get gitlab host
|
||||
gitlab_host_map = _read_config_value("gitlab_host_map")
|
||||
if gitlab_host_map and pr_host in gitlab_host_map:
|
||||
repo_type = gitlab_host_map[pr_host]
|
||||
else:
|
||||
if not gitlab_host_map:
|
||||
gitlab_host_map = {}
|
||||
gitlab_host_editor = TextEditor(
|
||||
"", "Please input your gitlab host(for example: https://www.gitlab.com):"
|
||||
)
|
||||
gitlab_host_editor.render()
|
||||
gitlab_host = gitlab_host_editor.new_text
|
||||
if not gitlab_host:
|
||||
return ""
|
||||
gitlab_host_map[pr_host] = gitlab_host
|
||||
_save_config_value("gitlab_host_map", gitlab_host_map)
|
||||
repo_type = gitlab_host
|
||||
|
||||
server_access_token = read_server_access_token(repo_type)
|
||||
if not server_access_token:
|
||||
# Input your server access TOKEN to access server api:
|
||||
server_access_token_editor = TextEditor(
|
||||
"", f"Please input your {repo_type} access TOKEN to access:"
|
||||
)
|
||||
server_access_token_editor.render()
|
||||
|
||||
server_access_token = server_access_token_editor.new_text
|
||||
if not server_access_token:
|
||||
return server_access_token
|
||||
save_server_access_token(repo_type, server_access_token)
|
||||
return server_access_token
|
||||
|
||||
|
||||
def get_gitlab_host(pr_url):
|
||||
pr_host = _parse_pr_host(pr_url)
|
||||
gitlab_host_map = _read_config_value("gitlab_host_map")
|
||||
if gitlab_host_map and pr_host in gitlab_host_map:
|
||||
return gitlab_host_map[pr_host]
|
||||
if not gitlab_host_map:
|
||||
gitlab_host_map = {}
|
||||
|
||||
gitlab_host_editor = TextEditor(
|
||||
"https://www.gitlab.com",
|
||||
"Please input your gitlab host(for example: https://www.gitlab.com):",
|
||||
)
|
||||
gitlab_host_editor.render()
|
||||
host = gitlab_host_editor.new_text
|
||||
if host:
|
||||
gitlab_host_map[pr_host] = host
|
||||
_save_config_value("gitlab_host_map", gitlab_host_map)
|
||||
return host
|
||||
|
||||
|
||||
def get_model_max_input(model):
|
||||
config_file = os.path.expanduser("~/.chat/config.yml")
|
||||
try:
|
||||
with open(config_file, "r", encoding="utf-8") as file:
|
||||
yaml_contents = file.read()
|
||||
parsed_yaml = yaml.safe_load(yaml_contents)
|
||||
for model_t in parsed_yaml.get("models", {}):
|
||||
if model_t == model:
|
||||
return parsed_yaml["models"][model_t].get("max_input_tokens", 6000)
|
||||
return 6000
|
||||
except Exception:
|
||||
return 6000
|
@ -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
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