Compare commits
2 Commits
scripts
...
add_repo_t
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a15f1a08a8 | ||
![]() |
573824236f |
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,4 +9,3 @@ custom/*
|
|||||||
!custom/config.yml.example
|
!custom/config.yml.example
|
||||||
|
|
||||||
user_settings.yml
|
user_settings.yml
|
||||||
.aider*
|
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
### aider 操作指南
|
|
||||||
|
|
||||||
aider是一个AI辅助的代码编辑工具,可以根据自然语言指令修改代码。
|
|
||||||
|
|
||||||
用途:
|
|
||||||
根据用户提供的指令,自动分析和修改已添加到aider中的代码文件。
|
|
||||||
|
|
||||||
使用方法:
|
|
||||||
1. 使用 `/aider.files.add` 命令添加需要处理的文件
|
|
||||||
2. 输入 `/aider <message>` 命令,其中 `<message>` 是你想要aider执行的任务描述
|
|
||||||
3. 等待aider生成建议的更改
|
|
||||||
4. 在IDE中查看每个文件的Diff视图,选择是否接受修改
|
|
||||||
5. 对于多个文件的更改,系统会在每个文件之后询问是否继续查看下一个文件的更改
|
|
||||||
|
|
||||||
注意事项:
|
|
||||||
- 使用前必须先添加文件到aider,否则会提示使用 'aider.files.add' 命令
|
|
||||||
- 可以使用 `aider.files.remove` 命令从aider中移除文件
|
|
||||||
- 所有更改都会在IDE中以Diff视图形式展示,你可以决定是否应用这些更改
|
|
||||||
- aider使用OpenAI的API,请确保已正确设置API密钥
|
|
||||||
|
|
||||||
示例:
|
|
||||||
/aider 重构这段代码以提高性能
|
|
||||||
|
|
||||||
额外信息:
|
|
||||||
aider支持多种编程语言,可以执行代码重构、bug修复、性能优化等任务。它会分析当前添加的所有文件,并提供整体的改进建议。
|
|
@ -1,213 +0,0 @@
|
|||||||
import json
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from devchat.ide import IDEService
|
|
||||||
|
|
||||||
from lib.chatmark import Button
|
|
||||||
|
|
||||||
GLOBAL_CONFIG_PATH = os.path.join(os.path.expanduser("~"), ".chat", ".workflow_config.json")
|
|
||||||
|
|
||||||
|
|
||||||
def save_config(config_path, item, value):
|
|
||||||
if os.path.exists(config_path):
|
|
||||||
with open(config_path, "r", encoding="utf-8") as f:
|
|
||||||
config = json.load(f)
|
|
||||||
else:
|
|
||||||
config = {}
|
|
||||||
|
|
||||||
config[item] = value
|
|
||||||
with open(config_path, "w", encoding="utf-8") as f:
|
|
||||||
json.dump(config, f, indent=4)
|
|
||||||
|
|
||||||
|
|
||||||
def write_python_path_to_config():
|
|
||||||
"""
|
|
||||||
Write the current system Python path to the configuration.
|
|
||||||
"""
|
|
||||||
python_path = sys.executable
|
|
||||||
save_config(GLOBAL_CONFIG_PATH, "aider_python", python_path)
|
|
||||||
print(f"Python path '{python_path}' has been written to the configuration.")
|
|
||||||
|
|
||||||
|
|
||||||
def get_aider_files():
|
|
||||||
"""
|
|
||||||
从.chat/.aider_files文件中读取aider文件列表
|
|
||||||
"""
|
|
||||||
aider_files_path = os.path.join(".chat", ".aider_files")
|
|
||||||
if not os.path.exists(aider_files_path):
|
|
||||||
return []
|
|
||||||
|
|
||||||
with open(aider_files_path, "r") as f:
|
|
||||||
return [line.strip() for line in f if line.strip()]
|
|
||||||
|
|
||||||
|
|
||||||
def run_aider(message, files):
|
|
||||||
"""
|
|
||||||
运行aider命令
|
|
||||||
"""
|
|
||||||
python = sys.executable
|
|
||||||
model = os.environ.get("LLM_MODEL", "gpt-3.5-turbo-1106")
|
|
||||||
|
|
||||||
cmd = [
|
|
||||||
python,
|
|
||||||
"-m",
|
|
||||||
"aider",
|
|
||||||
"--model",
|
|
||||||
f"openai/{model}",
|
|
||||||
"--yes",
|
|
||||||
"--no-auto-commits",
|
|
||||||
"--dry-run",
|
|
||||||
"--no-pretty",
|
|
||||||
"--message",
|
|
||||||
message,
|
|
||||||
] + files
|
|
||||||
|
|
||||||
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
|
||||||
|
|
||||||
has_started = False
|
|
||||||
aider_output = ""
|
|
||||||
for line in process.stdout:
|
|
||||||
if "run with --help" in line or 'run "aider --help"' in line:
|
|
||||||
has_started = True
|
|
||||||
continue
|
|
||||||
if has_started:
|
|
||||||
aider_output += line
|
|
||||||
print(line, end="", flush=True)
|
|
||||||
|
|
||||||
return_code = process.wait()
|
|
||||||
|
|
||||||
if return_code != 0:
|
|
||||||
for line in process.stderr:
|
|
||||||
print(f"Error: {line.strip()}", file=sys.stderr)
|
|
||||||
sys.exit(return_code)
|
|
||||||
|
|
||||||
return aider_output
|
|
||||||
|
|
||||||
|
|
||||||
def apply_changes(changes, files):
|
|
||||||
"""
|
|
||||||
应用aider生成的更改
|
|
||||||
"""
|
|
||||||
changes_file = ".chat/changes.txt"
|
|
||||||
os.makedirs(os.path.dirname(changes_file), exist_ok=True)
|
|
||||||
with open(changes_file, "w") as f:
|
|
||||||
f.write(changes)
|
|
||||||
|
|
||||||
python = sys.executable
|
|
||||||
model = os.environ.get("LLM_MODEL", "gpt-3.5-turbo-1106")
|
|
||||||
|
|
||||||
cmd = [
|
|
||||||
python,
|
|
||||||
"-m",
|
|
||||||
"aider",
|
|
||||||
"--model",
|
|
||||||
f"openai/{model}",
|
|
||||||
"--yes",
|
|
||||||
"--no-auto-commits",
|
|
||||||
"--apply",
|
|
||||||
changes_file,
|
|
||||||
] + files
|
|
||||||
|
|
||||||
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
|
||||||
|
|
||||||
has_started = False
|
|
||||||
for line in process.stdout:
|
|
||||||
if "Model:" in line:
|
|
||||||
has_started = True
|
|
||||||
continue
|
|
||||||
if has_started:
|
|
||||||
print(line, end="", flush=True)
|
|
||||||
|
|
||||||
return_code = process.wait()
|
|
||||||
|
|
||||||
if return_code != 0:
|
|
||||||
for line in process.stderr:
|
|
||||||
print(f"Error: {line.strip()}", file=sys.stderr)
|
|
||||||
sys.exit(return_code)
|
|
||||||
|
|
||||||
os.remove(changes_file)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""
|
|
||||||
Main function to run the aider command.
|
|
||||||
|
|
||||||
This function performs the following tasks:
|
|
||||||
1. Checks for correct command-line usage
|
|
||||||
2. Writes the current Python path to the configuration
|
|
||||||
3. Retrieves the list of files to be processed
|
|
||||||
4. Runs the aider command with the given message
|
|
||||||
5. Applies the suggested changes
|
|
||||||
6. Displays the differences in the IDE
|
|
||||||
|
|
||||||
Usage: python command.py <message>
|
|
||||||
"""
|
|
||||||
if len(sys.argv) < 2:
|
|
||||||
print("Usage: python command.py <message>", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
write_python_path_to_config()
|
|
||||||
|
|
||||||
message = sys.argv[1]
|
|
||||||
files = get_aider_files()
|
|
||||||
|
|
||||||
if not files:
|
|
||||||
print(
|
|
||||||
"No files added to aider. Please add files using 'aider.files.add' command.",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
print("Running aider...\n", flush=True)
|
|
||||||
changes = run_aider(message, files)
|
|
||||||
|
|
||||||
if not changes:
|
|
||||||
print("No changes suggested by aider.")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
print("\nApplying changes...\n", flush=True)
|
|
||||||
|
|
||||||
# 保存原始文件内容
|
|
||||||
original_contents = {}
|
|
||||||
for file in files:
|
|
||||||
with open(file, "r") as f:
|
|
||||||
original_contents[file] = f.read()
|
|
||||||
|
|
||||||
# 应用更改
|
|
||||||
apply_changes(changes, files)
|
|
||||||
|
|
||||||
# 读取更新后的文件内容
|
|
||||||
updated_contents = {}
|
|
||||||
for file in files:
|
|
||||||
with open(file, "r") as f:
|
|
||||||
updated_contents[file] = f.read()
|
|
||||||
|
|
||||||
# 还原原始文件内容
|
|
||||||
for file in files:
|
|
||||||
with open(file, "w") as f:
|
|
||||||
f.write(original_contents[file])
|
|
||||||
|
|
||||||
# 使用 IDEService 展示差异
|
|
||||||
ide_service = IDEService()
|
|
||||||
for index, file in enumerate(files):
|
|
||||||
ide_service.diff_apply(file, updated_contents[file])
|
|
||||||
if index < len(files) - 1:
|
|
||||||
# 等待用户确认
|
|
||||||
button = Button(
|
|
||||||
["Show Next Changes", "Cancel"],
|
|
||||||
)
|
|
||||||
button.render()
|
|
||||||
|
|
||||||
idx = button.clicked
|
|
||||||
if idx == 0:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
print("Changes have been displayed in the IDE.")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,9 +0,0 @@
|
|||||||
description: "aider command"
|
|
||||||
workflow_python:
|
|
||||||
env_name: devchat-aider-env
|
|
||||||
version: 3.11.0
|
|
||||||
dependencies: requirements.txt
|
|
||||||
input: required
|
|
||||||
help: README.md
|
|
||||||
steps:
|
|
||||||
- run: $workflow_python $command_path/command.py "$input"
|
|
@ -1,23 +0,0 @@
|
|||||||
### aider.files.add
|
|
||||||
|
|
||||||
这个命令用于将文件添加到aider的处理列表中。
|
|
||||||
|
|
||||||
用途:
|
|
||||||
添加指定文件到aider,使其包含在后续的aider操作中。
|
|
||||||
|
|
||||||
使用方法:
|
|
||||||
/aider.files.add <file_path>
|
|
||||||
|
|
||||||
参数:
|
|
||||||
- <file_path>: 要添加的文件路径(必需)
|
|
||||||
|
|
||||||
注意事项:
|
|
||||||
- 文件路径必须是有效的格式
|
|
||||||
- 已存在于列表中的文件不会重复添加
|
|
||||||
- 成功添加后会显示当前的aider文件列表
|
|
||||||
|
|
||||||
示例:
|
|
||||||
/aider.files.add src/main.py
|
|
||||||
|
|
||||||
额外信息:
|
|
||||||
这个命令会将文件路径保存到.chat/.aider_files文件中。如果.chat目录不存在,会自动创建。
|
|
@ -1,66 +0,0 @@
|
|||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
def is_valid_path(path):
|
|
||||||
"""
|
|
||||||
检查路径是否为有效的文件路径形式
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# 尝试规范化路径
|
|
||||||
normalized_path = os.path.normpath(path)
|
|
||||||
# 检查路径是否是绝对路径或相对路径
|
|
||||||
return (
|
|
||||||
os.path.isabs(normalized_path)
|
|
||||||
or not os.path.dirname(normalized_path) == normalized_path
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def add_file(file_path):
|
|
||||||
# 1. 检查是否为有效的文件路径形式
|
|
||||||
if not is_valid_path(file_path):
|
|
||||||
print(f"Error: '{file_path}' is not a valid file path format.", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# 获取绝对路径
|
|
||||||
abs_file_path = file_path.strip()
|
|
||||||
|
|
||||||
# 2. 将新增文件路径存储到.chat/.aider_files文件中
|
|
||||||
aider_files_path = os.path.join(".chat", ".aider_files")
|
|
||||||
|
|
||||||
# 确保.chat目录存在
|
|
||||||
os.makedirs(os.path.dirname(aider_files_path), exist_ok=True)
|
|
||||||
|
|
||||||
# 读取现有文件列表
|
|
||||||
existing_files = set()
|
|
||||||
if os.path.exists(aider_files_path):
|
|
||||||
with open(aider_files_path, "r") as f:
|
|
||||||
existing_files = set(line.strip() for line in f)
|
|
||||||
|
|
||||||
# 添加新文件
|
|
||||||
existing_files.add(abs_file_path)
|
|
||||||
|
|
||||||
# 写入更新后的文件列表
|
|
||||||
with open(aider_files_path, "w") as f:
|
|
||||||
for file in sorted(existing_files):
|
|
||||||
f.write(f"{file}\n")
|
|
||||||
|
|
||||||
print(f"Added '{abs_file_path}' to aider files.")
|
|
||||||
print("\nCurrent aider files:")
|
|
||||||
for file in sorted(existing_files):
|
|
||||||
print(f"- {file}")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if len(sys.argv) != 2 or sys.argv[1].strip() == "":
|
|
||||||
print("Usage: /aider.files.add <file_path>", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
file_path = sys.argv[1]
|
|
||||||
add_file(file_path)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,5 +0,0 @@
|
|||||||
description: "add files to aider"
|
|
||||||
input: required
|
|
||||||
help: README.md
|
|
||||||
steps:
|
|
||||||
- run: $devchat_python $command_path/command.py "$input"
|
|
@ -1,19 +0,0 @@
|
|||||||
### aider.files.list
|
|
||||||
|
|
||||||
这个命令用于列出当前在aider处理列表中的所有文件。
|
|
||||||
|
|
||||||
用途:
|
|
||||||
显示所有已添加到aider中的文件,提供当前aider正在处理的文件概览。
|
|
||||||
|
|
||||||
使用方法:
|
|
||||||
/aider.files.list
|
|
||||||
|
|
||||||
注意事项:
|
|
||||||
- 如果没有文件被添加到aider,会显示相应的提示消息
|
|
||||||
- 文件列表按字母顺序排序显示
|
|
||||||
|
|
||||||
示例:
|
|
||||||
/aider.files.list
|
|
||||||
|
|
||||||
额外信息:
|
|
||||||
这个命令会读取.chat/.aider_files文件的内容来获取文件列表。如果该文件不存在,会提示尚未添加任何文件。
|
|
@ -1,31 +0,0 @@
|
|||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
def list_files():
|
|
||||||
aider_files_path = os.path.join(".chat", ".aider_files")
|
|
||||||
|
|
||||||
# 确保.chat/.aider_files文件存在
|
|
||||||
if not os.path.exists(aider_files_path):
|
|
||||||
print("No files have been added to aider yet.")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
# 读取文件列表
|
|
||||||
with open(aider_files_path, "r") as f:
|
|
||||||
files = [line.strip() for line in f]
|
|
||||||
|
|
||||||
# 打印文件列表
|
|
||||||
if files:
|
|
||||||
print("Aider files:")
|
|
||||||
for file in sorted(files):
|
|
||||||
print(f"- {file}")
|
|
||||||
else:
|
|
||||||
print("No files found in aider.")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
list_files()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,4 +0,0 @@
|
|||||||
description: "list files in aider"
|
|
||||||
help: README.md
|
|
||||||
steps:
|
|
||||||
- run: $devchat_python $command_path/command.py
|
|
@ -1,23 +0,0 @@
|
|||||||
### aider.files.remove
|
|
||||||
|
|
||||||
这个命令用于从aider处理列表中移除指定的文件。
|
|
||||||
|
|
||||||
用途:
|
|
||||||
将指定文件从aider的处理列表中删除,使其不再包含在后续的aider操作中。
|
|
||||||
|
|
||||||
使用方法:
|
|
||||||
/aider.files.remove <file_path>
|
|
||||||
|
|
||||||
参数:
|
|
||||||
- <file_path>: 要移除的文件路径(必需)
|
|
||||||
|
|
||||||
注意事项:
|
|
||||||
- 文件路径必须是有效的格式
|
|
||||||
- 如果指定的文件不在列表中,会显示相应的提示消息
|
|
||||||
- 成功移除后会显示更新后的aider文件列表
|
|
||||||
|
|
||||||
示例:
|
|
||||||
/aider.files.remove src/main.py
|
|
||||||
|
|
||||||
额外信息:
|
|
||||||
这个命令会更新.chat/.aider_files文件,从中删除指定的文件路径。如果文件不存在于列表中,操作会安全退出。
|
|
@ -1,72 +0,0 @@
|
|||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
def is_valid_path(path):
|
|
||||||
"""
|
|
||||||
检查路径是否为有效的文件路径形式
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# 尝试规范化路径
|
|
||||||
normalized_path = os.path.normpath(path)
|
|
||||||
# 检查路径是否是绝对路径或相对路径
|
|
||||||
return (
|
|
||||||
os.path.isabs(normalized_path)
|
|
||||||
or not os.path.dirname(normalized_path) == normalized_path
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def remove_file(file_path):
|
|
||||||
# 1. 检查是否为有效的文件路径形式
|
|
||||||
if not is_valid_path(file_path):
|
|
||||||
print(f"Error: '{file_path}' is not a valid file path format.", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# 获取绝对路径
|
|
||||||
abs_file_path = file_path.strip()
|
|
||||||
|
|
||||||
# 2. 从.chat/.aider_files文件中移除指定文件路径
|
|
||||||
aider_files_path = os.path.join(".chat", ".aider_files")
|
|
||||||
|
|
||||||
# 确保.chat目录存在
|
|
||||||
if not os.path.exists(aider_files_path):
|
|
||||||
print(f"Error: '{aider_files_path}' does not exist.", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# 读取现有文件列表
|
|
||||||
existing_files = set()
|
|
||||||
with open(aider_files_path, "r") as f:
|
|
||||||
existing_files = set(line.strip() for line in f)
|
|
||||||
|
|
||||||
# 检查文件是否在列表中
|
|
||||||
if abs_file_path not in existing_files:
|
|
||||||
print(f"'{abs_file_path}' is not in aider files.")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
# 移除文件
|
|
||||||
existing_files.remove(abs_file_path)
|
|
||||||
|
|
||||||
# 写入更新后的文件列表
|
|
||||||
with open(aider_files_path, "w") as f:
|
|
||||||
for file in sorted(existing_files):
|
|
||||||
f.write(f"{file}\n")
|
|
||||||
|
|
||||||
print(f"Removed '{abs_file_path}' from aider files.")
|
|
||||||
print("\nCurrent aider files:")
|
|
||||||
for file in sorted(existing_files):
|
|
||||||
print(f"- {file}")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if len(sys.argv) != 2 or sys.argv[1].strip() == "":
|
|
||||||
print("Usage: /aider.files.remove <file_path>", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
file_path = sys.argv[1]
|
|
||||||
remove_file(file_path)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,5 +0,0 @@
|
|||||||
description: "remove files from aider"
|
|
||||||
input: required
|
|
||||||
help: README.md
|
|
||||||
steps:
|
|
||||||
- run: $devchat_python $command_path/command.py "$input"
|
|
@ -1,2 +0,0 @@
|
|||||||
git+https://gitee.com/imlaji/aider.git@main
|
|
||||||
git+https://gitee.com/devchat-ai/devchat.git@aider
|
|
@ -1,24 +0,0 @@
|
|||||||
### code_task_summary
|
|
||||||
|
|
||||||
根据当前分支或指定的Issue,生成代码任务摘要。
|
|
||||||
|
|
||||||
#### 用途
|
|
||||||
- 自动生成简洁的代码任务描述
|
|
||||||
- 帮助开发者快速理解任务要点
|
|
||||||
- 用于更新项目配置或文档
|
|
||||||
|
|
||||||
#### 使用方法
|
|
||||||
执行命令: `/github.code_task_summary [issue_url]`
|
|
||||||
|
|
||||||
- 如不提供issue_url,将基于当前分支名称提取Issue信息
|
|
||||||
- 如提供issue_url,将直接使用该Issue的内容
|
|
||||||
|
|
||||||
#### 操作流程
|
|
||||||
1. 获取Issue信息
|
|
||||||
2. 生成代码任务摘要
|
|
||||||
3. 允许用户编辑摘要
|
|
||||||
4. 更新项目配置文件
|
|
||||||
|
|
||||||
#### 注意事项
|
|
||||||
- 确保Git仓库配置正确
|
|
||||||
- 需要有效的GitHub Token以访问API
|
|
@ -1,5 +0,0 @@
|
|||||||
description: 'Generate code task summary.'
|
|
||||||
input: optional
|
|
||||||
help: README.md
|
|
||||||
steps:
|
|
||||||
- run: $devchat_python $command_path/command.py "$input"
|
|
@ -1 +0,0 @@
|
|||||||
description: Root of github commands.
|
|
@ -1,23 +0,0 @@
|
|||||||
### commit
|
|
||||||
|
|
||||||
自动生成提交信息并执行Git提交。
|
|
||||||
|
|
||||||
#### 用途
|
|
||||||
- 生成规范的提交信息
|
|
||||||
- 简化Git提交流程
|
|
||||||
- 保持提交历史的一致性
|
|
||||||
|
|
||||||
#### 使用方法
|
|
||||||
执行命令: `/github.commit [message]`
|
|
||||||
|
|
||||||
- message: 可选的用户输入,用于辅助生成提交信息
|
|
||||||
|
|
||||||
#### 操作流程
|
|
||||||
1. 选择要提交的文件
|
|
||||||
2. 生成提交信息
|
|
||||||
3. 允许用户编辑提交信息
|
|
||||||
4. 执行Git提交
|
|
||||||
|
|
||||||
#### 注意事项
|
|
||||||
- 确保已选择需要提交的文件
|
|
||||||
- 生成的提交信息可能需要进一步修改以符合项目规范
|
|
@ -1,19 +0,0 @@
|
|||||||
### config
|
|
||||||
|
|
||||||
配置GitHub工作流所需的设置。
|
|
||||||
|
|
||||||
#### 用途
|
|
||||||
- 设置Issue仓库URL
|
|
||||||
- 配置GitHub Token
|
|
||||||
|
|
||||||
#### 使用方法
|
|
||||||
执行命令: `/github.config`
|
|
||||||
|
|
||||||
#### 操作流程
|
|
||||||
1. 输入Issue仓库URL(可选)
|
|
||||||
2. 输入GitHub Token
|
|
||||||
3. 保存配置信息
|
|
||||||
|
|
||||||
#### 注意事项
|
|
||||||
- GitHub Token应妥善保管,不要泄露
|
|
||||||
- 配置信息将保存在本地文件中
|
|
@ -1,4 +0,0 @@
|
|||||||
description: 'Config required settings for GIT workflows.'
|
|
||||||
help: README.md
|
|
||||||
steps:
|
|
||||||
- run: $devchat_python $command_path/command.py
|
|
@ -1,19 +0,0 @@
|
|||||||
### list_issue_tasks
|
|
||||||
|
|
||||||
列出指定Issue中的任务列表。
|
|
||||||
|
|
||||||
#### 用途
|
|
||||||
- 查看Issue中的子任务
|
|
||||||
- 跟踪任务进度
|
|
||||||
|
|
||||||
#### 使用方法
|
|
||||||
执行命令: `/github.list_issue_tasks <issue_url>`
|
|
||||||
|
|
||||||
#### 操作流程
|
|
||||||
1. 获取指定Issue的信息
|
|
||||||
2. 解析Issue内容中的任务列表
|
|
||||||
3. 显示任务列表
|
|
||||||
|
|
||||||
#### 注意事项
|
|
||||||
- 需要提供有效的Issue URL
|
|
||||||
- 任务应以特定格式在Issue中列出(如: - [ ] 任务描述)
|
|
@ -1,5 +0,0 @@
|
|||||||
description: 'List issue tasks.'
|
|
||||||
input: required
|
|
||||||
help: README.md
|
|
||||||
steps:
|
|
||||||
- run: $devchat_python $command_path/command.py "$input"
|
|
@ -1,21 +0,0 @@
|
|||||||
### new_branch
|
|
||||||
|
|
||||||
基于当前分支创建新分支并切换到新分支。
|
|
||||||
|
|
||||||
#### 用途
|
|
||||||
- 快速创建新的功能或修复分支
|
|
||||||
- 保持工作区隔离
|
|
||||||
|
|
||||||
#### 使用方法
|
|
||||||
执行命令: `/github.new_branch <description>`
|
|
||||||
|
|
||||||
- description: 新分支的简短描述或相关Issue URL
|
|
||||||
|
|
||||||
#### 操作流程
|
|
||||||
1. 生成多个分支名建议
|
|
||||||
2. 用户选择或编辑分支名
|
|
||||||
3. 创建新分支并切换
|
|
||||||
|
|
||||||
#### 注意事项
|
|
||||||
- 确保当前分支的更改已提交
|
|
||||||
- 如提供Issue URL,会自动关联Issue编号到分支名
|
|
@ -1,21 +0,0 @@
|
|||||||
### new_issue
|
|
||||||
|
|
||||||
创建新的GitHub Issue。
|
|
||||||
|
|
||||||
#### 用途
|
|
||||||
- 快速创建标准格式的Issue
|
|
||||||
- 记录任务、bug或功能请求
|
|
||||||
|
|
||||||
#### 使用方法
|
|
||||||
执行命令: `/github.new_issue <description>`
|
|
||||||
|
|
||||||
- description: Issue的简短描述
|
|
||||||
|
|
||||||
#### 操作流程
|
|
||||||
1. 基于描述生成Issue标题和正文
|
|
||||||
2. 允许用户编辑Issue内容
|
|
||||||
3. 创建GitHub Issue
|
|
||||||
|
|
||||||
#### 注意事项
|
|
||||||
- 需要有创建Issue的权限
|
|
||||||
- 生成的内容可能需要进一步完善
|
|
@ -1,22 +0,0 @@
|
|||||||
### new_pr
|
|
||||||
|
|
||||||
创建新的Pull Request。
|
|
||||||
|
|
||||||
#### 用途
|
|
||||||
- 自动生成PR标题和描述
|
|
||||||
- 简化代码审查流程
|
|
||||||
|
|
||||||
#### 使用方法
|
|
||||||
执行命令: `/github.new_pr [additional_info]`
|
|
||||||
|
|
||||||
- additional_info: 可选的附加信息
|
|
||||||
|
|
||||||
#### 操作流程
|
|
||||||
1. 获取当前分支信息和相关Issue
|
|
||||||
2. 生成PR标题和描述
|
|
||||||
3. 允许用户编辑PR内容
|
|
||||||
4. 创建Pull Request
|
|
||||||
|
|
||||||
#### 注意事项
|
|
||||||
- 确保当前分支有未合并的更改
|
|
||||||
- 需要有创建PR的权限
|
|
@ -1,22 +0,0 @@
|
|||||||
### update_issue_tasks
|
|
||||||
|
|
||||||
更新指定Issue中的任务列表。
|
|
||||||
|
|
||||||
#### 用途
|
|
||||||
- 添加、修改或删除Issue中的子任务
|
|
||||||
- 更新任务进度
|
|
||||||
|
|
||||||
#### 使用方法
|
|
||||||
执行命令: `/github.update_issue_tasks`
|
|
||||||
|
|
||||||
#### 操作流程
|
|
||||||
1. 输入Issue URL
|
|
||||||
2. 显示当前任务列表
|
|
||||||
3. 用户输入更新建议
|
|
||||||
4. 生成新的任务列表
|
|
||||||
5. 允许用户编辑新任务列表
|
|
||||||
6. 更新Issue内容
|
|
||||||
|
|
||||||
#### 注意事项
|
|
||||||
- 需要有编辑Issue的权限
|
|
||||||
- 小心不要删除或覆盖重要信息
|
|
@ -1,5 +0,0 @@
|
|||||||
description: 'Update issue tasks.'
|
|
||||||
input: required
|
|
||||||
help: README.md
|
|
||||||
steps:
|
|
||||||
- run: $devchat_python $command_path/command.py "$input"
|
|
@ -1,20 +0,0 @@
|
|||||||
### update_pr
|
|
||||||
|
|
||||||
更新现有的Pull Request。
|
|
||||||
|
|
||||||
#### 用途
|
|
||||||
- 更新PR的标题和描述
|
|
||||||
- 反映最新的代码变更
|
|
||||||
|
|
||||||
#### 使用方法
|
|
||||||
执行命令: `/github.update_pr`
|
|
||||||
|
|
||||||
#### 操作流程
|
|
||||||
1. 获取最近的PR信息
|
|
||||||
2. 重新生成PR标题和描述
|
|
||||||
3. 允许用户编辑PR内容
|
|
||||||
4. 更新Pull Request
|
|
||||||
|
|
||||||
#### 注意事项
|
|
||||||
- 确保有更新PR的权限
|
|
||||||
- 更新前请确认是否有新的提交需要推送
|
|
@ -1,5 +0,0 @@
|
|||||||
description: 'Update PR.'
|
|
||||||
input: required
|
|
||||||
help: README.md
|
|
||||||
steps:
|
|
||||||
- run: $devchat_python $command_path/command.py "$input"
|
|
@ -1,24 +0,0 @@
|
|||||||
### code_task_summary
|
|
||||||
|
|
||||||
根据当前分支或指定的Issue,生成代码任务摘要。
|
|
||||||
|
|
||||||
#### 用途
|
|
||||||
- 自动生成简洁的代码任务描述
|
|
||||||
- 帮助开发者快速理解任务要点
|
|
||||||
- 用于更新项目配置或文档
|
|
||||||
|
|
||||||
#### 使用方法
|
|
||||||
执行命令: `/github.code_task_summary [issue_url]`
|
|
||||||
|
|
||||||
- 如不提供issue_url,将基于当前分支名称提取Issue信息
|
|
||||||
- 如提供issue_url,将直接使用该Issue的内容
|
|
||||||
|
|
||||||
#### 操作流程
|
|
||||||
1. 获取Issue信息
|
|
||||||
2. 生成代码任务摘要
|
|
||||||
3. 允许用户编辑摘要
|
|
||||||
4. 更新项目配置文件
|
|
||||||
|
|
||||||
#### 注意事项
|
|
||||||
- 确保Git仓库配置正确
|
|
||||||
- 需要有效的GitHub Token以访问API
|
|
@ -1,124 +0,0 @@
|
|||||||
import json
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from devchat.llm import chat_json
|
|
||||||
|
|
||||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
|
||||||
|
|
||||||
from common_util import assert_exit, ui_edit # noqa: E402
|
|
||||||
from git_api import ( # noqa: E402
|
|
||||||
check_git_installed,
|
|
||||||
get_current_branch,
|
|
||||||
get_gitlab_issue_repo,
|
|
||||||
get_issue_info,
|
|
||||||
is_issue_url,
|
|
||||||
read_issue_by_url,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def extract_issue_id(branch_name):
|
|
||||||
if "#" in branch_name:
|
|
||||||
return branch_name.split("#")[-1]
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
# Function to generate a random branch name
|
|
||||||
PROMPT = (
|
|
||||||
"You are a coding engineer, required to summarize the ISSUE description into a coding task description of no more than 50 words. \n" # noqa: E501
|
|
||||||
"The ISSUE description is as follows: {issue_body}, please summarize the corresponding coding task description.\n" # noqa: E501
|
|
||||||
'The coding task description should be output in JSON format, in the form of: {{"summary": "code task summary"}}\n' # noqa: E501
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@chat_json(prompt=PROMPT)
|
|
||||||
def generate_code_task_summary(issue_body):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@ui_edit(ui_type="editor", description="Edit code task summary:")
|
|
||||||
def edit_code_task_summary(task_summary):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def get_issue_or_task(task):
|
|
||||||
if is_issue_url(task):
|
|
||||||
issue = read_issue_by_url(task.strip())
|
|
||||||
assert_exit(not issue, "Failed to read issue.", exit_code=-1)
|
|
||||||
|
|
||||||
return json.dumps(
|
|
||||||
{"id": issue["iid"], "title": issue["title"], "description": issue["description"]}
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return task
|
|
||||||
|
|
||||||
|
|
||||||
def get_issue_json(issue_id, task):
|
|
||||||
issue = {"id": "no issue id", "title": "", "description": task}
|
|
||||||
if issue_id:
|
|
||||||
issue = get_issue_info(issue_id)
|
|
||||||
assert_exit(not issue, f"Failed to retrieve issue with ID: {issue_id}", exit_code=-1)
|
|
||||||
issue = {
|
|
||||||
"id": issue_id,
|
|
||||||
"web_url": issue["web_url"],
|
|
||||||
"title": issue["title"],
|
|
||||||
"description": issue["description"],
|
|
||||||
}
|
|
||||||
return issue
|
|
||||||
|
|
||||||
|
|
||||||
# Main function
|
|
||||||
def main():
|
|
||||||
print("Start update code task summary ...", end="\n\n", flush=True)
|
|
||||||
|
|
||||||
is_git_installed = check_git_installed()
|
|
||||||
assert_exit(not is_git_installed, "Git is not installed.", exit_code=-1)
|
|
||||||
|
|
||||||
task = sys.argv[1]
|
|
||||||
|
|
||||||
repo_name = get_gitlab_issue_repo()
|
|
||||||
branch_name = get_current_branch()
|
|
||||||
issue_id = extract_issue_id(branch_name)
|
|
||||||
|
|
||||||
# print basic info, repo_name, branch_name, issue_id
|
|
||||||
print("repo name:", repo_name, end="\n\n")
|
|
||||||
print("branch name:", branch_name, end="\n\n")
|
|
||||||
print("issue id:", issue_id, end="\n\n")
|
|
||||||
|
|
||||||
issue = get_issue_json(issue_id, task)
|
|
||||||
assert_exit(
|
|
||||||
not issue["description"], f"Failed to retrieve issue with ID: {issue_id}", exit_code=-1
|
|
||||||
)
|
|
||||||
|
|
||||||
# Generate 5 branch names
|
|
||||||
print("Generating code task summary ...", end="\n\n", flush=True)
|
|
||||||
code_task_summary = generate_code_task_summary(issue_body=issue["description"])
|
|
||||||
assert_exit(not code_task_summary, "Failed to generate code task summary.", exit_code=-1)
|
|
||||||
assert_exit(
|
|
||||||
not code_task_summary.get("summary", None),
|
|
||||||
"Failed to generate code task summary, missing summary field in result.",
|
|
||||||
exit_code=-1,
|
|
||||||
)
|
|
||||||
code_task_summary = code_task_summary["summary"]
|
|
||||||
|
|
||||||
# Select branch name
|
|
||||||
code_task_summary = edit_code_task_summary(code_task_summary)
|
|
||||||
assert_exit(not code_task_summary, "Failed to edit code task summary.", exit_code=-1)
|
|
||||||
code_task_summary = code_task_summary[0]
|
|
||||||
|
|
||||||
# create and checkout branch
|
|
||||||
print("Updating code task summary to config:")
|
|
||||||
config_file = os.path.join(".chat", "complete.config")
|
|
||||||
if os.path.exists(config_file):
|
|
||||||
with open(config_file, "r") as f:
|
|
||||||
config = json.load(f)
|
|
||||||
config["taskDescription"] = code_task_summary
|
|
||||||
else:
|
|
||||||
config = {"taskDescription": code_task_summary}
|
|
||||||
with open(config_file, "w") as f:
|
|
||||||
json.dump(config, f, indent=4)
|
|
||||||
print("Code task summary has updated")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,5 +0,0 @@
|
|||||||
description: 'Generate code task summary.'
|
|
||||||
input: optional
|
|
||||||
help: README.md
|
|
||||||
steps:
|
|
||||||
- run: $devchat_python $command_path/command.py "$input"
|
|
@ -1 +0,0 @@
|
|||||||
description: Root of gitlab commands.
|
|
@ -1,23 +0,0 @@
|
|||||||
### commit
|
|
||||||
|
|
||||||
自动生成提交信息并执行Git提交。
|
|
||||||
|
|
||||||
#### 用途
|
|
||||||
- 生成规范的提交信息
|
|
||||||
- 简化Git提交流程
|
|
||||||
- 保持提交历史的一致性
|
|
||||||
|
|
||||||
#### 使用方法
|
|
||||||
执行命令: `/github.commit [message]`
|
|
||||||
|
|
||||||
- message: 可选的用户输入,用于辅助生成提交信息
|
|
||||||
|
|
||||||
#### 操作流程
|
|
||||||
1. 选择要提交的文件
|
|
||||||
2. 生成提交信息
|
|
||||||
3. 允许用户编辑提交信息
|
|
||||||
4. 执行Git提交
|
|
||||||
|
|
||||||
#### 注意事项
|
|
||||||
- 确保已选择需要提交的文件
|
|
||||||
- 生成的提交信息可能需要进一步修改以符合项目规范
|
|
@ -1,5 +0,0 @@
|
|||||||
description: '为你选定的代码变更生成格式规范的提交信息,并通过 Git 提交。如需要可包含对应 issue 编号(例如,输入“/commit to close #12”)'
|
|
||||||
hint: to close Issue #issue_number
|
|
||||||
input: optional
|
|
||||||
steps:
|
|
||||||
- run: $devchat_python $command_path/../commit.py "$input" "chinese"
|
|
@ -1,78 +0,0 @@
|
|||||||
import functools
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from lib.chatmark import Checkbox, Form, Radio, TextEditor
|
|
||||||
|
|
||||||
|
|
||||||
def create_ui_objs(ui_decls, args):
|
|
||||||
ui_objs = []
|
|
||||||
editors = []
|
|
||||||
for i, ui in enumerate(ui_decls):
|
|
||||||
editor = ui[0](args[i])
|
|
||||||
if ui[1]:
|
|
||||||
# this is the title of UI object
|
|
||||||
editors.append(ui[1])
|
|
||||||
editors.append(editor)
|
|
||||||
ui_objs.append(editor)
|
|
||||||
return ui_objs, editors
|
|
||||||
|
|
||||||
|
|
||||||
def edit_form(uis, args):
|
|
||||||
ui_objs, editors = create_ui_objs(uis, args)
|
|
||||||
form = Form(editors)
|
|
||||||
form.render()
|
|
||||||
|
|
||||||
values = []
|
|
||||||
for obj in ui_objs:
|
|
||||||
if isinstance(obj, TextEditor):
|
|
||||||
values.append(obj.new_text)
|
|
||||||
elif isinstance(obj, Radio):
|
|
||||||
values.append(obj.selection)
|
|
||||||
else:
|
|
||||||
# TODO
|
|
||||||
pass
|
|
||||||
return values
|
|
||||||
|
|
||||||
|
|
||||||
def editor(description):
|
|
||||||
def decorator_edit(func):
|
|
||||||
@functools.wraps(func)
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
uis = wrapper.uis[::-1]
|
|
||||||
return edit_form(uis, args)
|
|
||||||
|
|
||||||
if hasattr(func, "uis"):
|
|
||||||
wrapper.uis = func.uis
|
|
||||||
else:
|
|
||||||
wrapper.uis = []
|
|
||||||
wrapper.uis.append((TextEditor, description))
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
return decorator_edit
|
|
||||||
|
|
||||||
|
|
||||||
def ui_edit(ui_type, description):
|
|
||||||
def decorator_edit(func):
|
|
||||||
@functools.wraps(func)
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
uis = wrapper.uis[::-1]
|
|
||||||
return edit_form(uis, args)
|
|
||||||
|
|
||||||
if hasattr(func, "uis"):
|
|
||||||
wrapper.uis = func.uis
|
|
||||||
else:
|
|
||||||
wrapper.uis = []
|
|
||||||
ui_type_class = {"editor": TextEditor, "radio": Radio, "checkbox": Checkbox}[ui_type]
|
|
||||||
wrapper.uis.append((ui_type_class, description))
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
return decorator_edit
|
|
||||||
|
|
||||||
|
|
||||||
def assert_exit(condition, message, exit_code=-1):
|
|
||||||
if condition:
|
|
||||||
if exit_code == 0:
|
|
||||||
print(message, end="\n\n", flush=True)
|
|
||||||
else:
|
|
||||||
print(message, end="\n\n", file=sys.stderr, flush=True)
|
|
||||||
sys.exit(exit_code)
|
|
@ -1,19 +0,0 @@
|
|||||||
### config
|
|
||||||
|
|
||||||
配置GitHub工作流所需的设置。
|
|
||||||
|
|
||||||
#### 用途
|
|
||||||
- 设置Issue仓库URL
|
|
||||||
- 配置GitHub Token
|
|
||||||
|
|
||||||
#### 使用方法
|
|
||||||
执行命令: `/github.config`
|
|
||||||
|
|
||||||
#### 操作流程
|
|
||||||
1. 输入Issue仓库URL(可选)
|
|
||||||
2. 输入GitHub Token
|
|
||||||
3. 保存配置信息
|
|
||||||
|
|
||||||
#### 注意事项
|
|
||||||
- GitHub Token应妥善保管,不要泄露
|
|
||||||
- 配置信息将保存在本地文件中
|
|
@ -1,88 +0,0 @@
|
|||||||
import json
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
|
||||||
|
|
||||||
from common_util import editor # noqa: E402
|
|
||||||
|
|
||||||
|
|
||||||
def read_issue_url():
|
|
||||||
config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json")
|
|
||||||
if os.path.exists(config_path):
|
|
||||||
with open(config_path, "r", encoding="utf-8") as f:
|
|
||||||
config_data = json.load(f)
|
|
||||||
if "git_issue_repo" in config_data:
|
|
||||||
return config_data["git_issue_repo"]
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def save_issue_url(issue_url):
|
|
||||||
config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json")
|
|
||||||
# make dirs
|
|
||||||
os.makedirs(os.path.dirname(config_path), exist_ok=True)
|
|
||||||
|
|
||||||
config_data = {}
|
|
||||||
if os.path.exists(config_path):
|
|
||||||
with open(config_path, "r", encoding="utf-8") as f:
|
|
||||||
config_data = json.load(f)
|
|
||||||
|
|
||||||
config_data["git_issue_repo"] = issue_url
|
|
||||||
with open(config_path, "w+", encoding="utf-8") as f:
|
|
||||||
json.dump(config_data, f, indent=4)
|
|
||||||
|
|
||||||
|
|
||||||
def read_gitlab_token():
|
|
||||||
config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
|
||||||
if os.path.exists(config_path):
|
|
||||||
with open(config_path, "r", encoding="utf-8") as f:
|
|
||||||
config_data = json.load(f)
|
|
||||||
if "gitlab_token" in config_data:
|
|
||||||
return config_data["gitlab_token"]
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def save_gitlab_token(github_token):
|
|
||||||
config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
|
||||||
|
|
||||||
config_data = {}
|
|
||||||
if os.path.exists(config_path):
|
|
||||||
with open(config_path, "r", encoding="utf-8") as f:
|
|
||||||
config_data = json.load(f)
|
|
||||||
|
|
||||||
config_data["gitlab_token"] = github_token
|
|
||||||
with open(config_path, "w+", encoding="utf-8") as f:
|
|
||||||
json.dump(config_data, f, indent=4)
|
|
||||||
|
|
||||||
|
|
||||||
@editor(
|
|
||||||
"Please specify the issue's repository, "
|
|
||||||
"If the issue is within this repository, no need to specify. "
|
|
||||||
"Otherwise, format as: username/repository-name"
|
|
||||||
)
|
|
||||||
@editor("Input your github TOKEN to access github api:")
|
|
||||||
def edit_issue(issue_url, github_token):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
print("start config git settings ...", end="\n\n", flush=True)
|
|
||||||
|
|
||||||
issue_url = read_issue_url()
|
|
||||||
github_token = read_gitlab_token()
|
|
||||||
|
|
||||||
issue_url, github_token = edit_issue(issue_url, github_token)
|
|
||||||
if issue_url:
|
|
||||||
save_issue_url(issue_url)
|
|
||||||
if github_token:
|
|
||||||
save_gitlab_token(github_token)
|
|
||||||
else:
|
|
||||||
print("Please specify the github token to access github api.")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
print("config git settings successfully.")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,611 +0,0 @@
|
|||||||
import json
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from lib.chatmark import TextEditor
|
|
||||||
from lib.ide_service import IDEService
|
|
||||||
|
|
||||||
|
|
||||||
def read_gitlab_token():
|
|
||||||
config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
|
||||||
if os.path.exists(config_path):
|
|
||||||
with open(config_path, "r", encoding="utf-8") as f:
|
|
||||||
config_data = json.load(f)
|
|
||||||
if "gitlab_token" in config_data:
|
|
||||||
return config_data["gitlab_token"]
|
|
||||||
|
|
||||||
# ask user to input gitlab token
|
|
||||||
server_access_token_editor = TextEditor("", "Please input your GitLab access TOKEN to access:")
|
|
||||||
server_access_token_editor.render()
|
|
||||||
|
|
||||||
server_access_token = server_access_token_editor.new_text
|
|
||||||
if not server_access_token:
|
|
||||||
print("Please input your GitLab access TOKEN to continue.")
|
|
||||||
sys.exit(-1)
|
|
||||||
return server_access_token
|
|
||||||
|
|
||||||
|
|
||||||
current_repo_dir = None
|
|
||||||
|
|
||||||
|
|
||||||
def get_current_repo():
|
|
||||||
"""
|
|
||||||
获取当前文件所在的仓库信息
|
|
||||||
"""
|
|
||||||
global current_repo_dir
|
|
||||||
|
|
||||||
if not current_repo_dir:
|
|
||||||
selected_data = IDEService().get_selected_range().dict()
|
|
||||||
current_file = selected_data.get("abspath", None)
|
|
||||||
if not current_file:
|
|
||||||
return None
|
|
||||||
current_dir = os.path.dirname(current_file)
|
|
||||||
try:
|
|
||||||
# 获取仓库根目录
|
|
||||||
current_repo_dir = (
|
|
||||||
subprocess.check_output(
|
|
||||||
["git", "rev-parse", "--show-toplevel"],
|
|
||||||
stderr=subprocess.DEVNULL,
|
|
||||||
cwd=current_dir,
|
|
||||||
)
|
|
||||||
.decode("utf-8")
|
|
||||||
.strip()
|
|
||||||
)
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
# 如果发生错误,可能不在git仓库中
|
|
||||||
return None
|
|
||||||
return current_repo_dir
|
|
||||||
|
|
||||||
|
|
||||||
def subprocess_check_output(*popenargs, timeout=None, **kwargs):
|
|
||||||
# 将 current_dir 添加到 kwargs 中的 cwd 参数
|
|
||||||
current_repo = get_current_repo()
|
|
||||||
if current_repo:
|
|
||||||
kwargs["cwd"] = kwargs.get("cwd", current_repo)
|
|
||||||
|
|
||||||
# 调用 subprocess.check_output
|
|
||||||
return subprocess.check_output(*popenargs, timeout=timeout, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def subprocess_run(
|
|
||||||
*popenargs, input=None, capture_output=False, timeout=None, check=False, **kwargs
|
|
||||||
):
|
|
||||||
current_repo = get_current_repo()
|
|
||||||
if current_repo:
|
|
||||||
kwargs["cwd"] = kwargs.get("cwd", current_repo)
|
|
||||||
|
|
||||||
# 调用 subprocess.run
|
|
||||||
return subprocess.run(
|
|
||||||
*popenargs,
|
|
||||||
input=input,
|
|
||||||
capture_output=capture_output,
|
|
||||||
timeout=timeout,
|
|
||||||
check=check,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def subprocess_call(*popenargs, timeout=None, **kwargs):
|
|
||||||
current_repo = get_current_repo()
|
|
||||||
if current_repo:
|
|
||||||
kwargs["cwd"] = kwargs.get("cwd", current_repo)
|
|
||||||
|
|
||||||
# 调用 subprocess.call
|
|
||||||
return subprocess.call(*popenargs, timeout=timeout, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def subprocess_check_call(*popenargs, timeout=None, **kwargs):
|
|
||||||
current_repo = get_current_repo()
|
|
||||||
if current_repo:
|
|
||||||
kwargs["cwd"] = kwargs.get("cwd", current_repo)
|
|
||||||
|
|
||||||
# 调用 subprocess.check_call
|
|
||||||
return subprocess.check_call(*popenargs, timeout=timeout, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
GITLAB_ACCESS_TOKEN = read_gitlab_token()
|
|
||||||
GITLAB_API_URL = "https://gitlab.com/api/v4"
|
|
||||||
|
|
||||||
|
|
||||||
def create_issue(title, description):
|
|
||||||
headers = {
|
|
||||||
"Private-Token": GITLAB_ACCESS_TOKEN,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
}
|
|
||||||
data = {
|
|
||||||
"title": title,
|
|
||||||
"description": description,
|
|
||||||
}
|
|
||||||
project_id = get_gitlab_project_id()
|
|
||||||
issue_api_url = f"{GITLAB_API_URL}/projects/{project_id}/issues"
|
|
||||||
response = requests.post(issue_api_url, headers=headers, json=data)
|
|
||||||
|
|
||||||
if response.status_code == 201:
|
|
||||||
print("Issue created successfully!")
|
|
||||||
return response.json()
|
|
||||||
else:
|
|
||||||
print(f"Failed to create issue: {response.content}", file=sys.stderr, end="\n\n")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def update_issue_body(issue_iid, issue_body):
|
|
||||||
headers = {
|
|
||||||
"Private-Token": GITLAB_ACCESS_TOKEN,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
}
|
|
||||||
data = {
|
|
||||||
"description": issue_body,
|
|
||||||
}
|
|
||||||
|
|
||||||
project_id = get_gitlab_project_id()
|
|
||||||
api_url = f"{GITLAB_API_URL}/projects/{project_id}/issues/{issue_iid}"
|
|
||||||
response = requests.put(api_url, headers=headers, json=data)
|
|
||||||
|
|
||||||
if response.status_code == 200:
|
|
||||||
print("Issue updated successfully!")
|
|
||||||
return response.json()
|
|
||||||
else:
|
|
||||||
print(f"Failed to update issue: {response.status_code}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def get_gitlab_project_id():
|
|
||||||
try:
|
|
||||||
result = subprocess_check_output(
|
|
||||||
["git", "remote", "get-url", "origin"], stderr=subprocess.STDOUT
|
|
||||||
).strip()
|
|
||||||
repo_url = result.decode("utf-8")
|
|
||||||
print(f"Original repo URL: {repo_url}", file=sys.stderr)
|
|
||||||
|
|
||||||
if repo_url.startswith("git@"):
|
|
||||||
# Handle SSH URL format
|
|
||||||
parts = repo_url.split(":")
|
|
||||||
project_path = parts[1].replace(".git", "")
|
|
||||||
elif repo_url.startswith("https://"):
|
|
||||||
# Handle HTTPS URL format
|
|
||||||
parts = repo_url.split("/")
|
|
||||||
project_path = "/".join(parts[3:]).replace(".git", "")
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Unsupported Git URL format: {repo_url}")
|
|
||||||
|
|
||||||
print(f"Extracted project path: {project_path}", file=sys.stderr)
|
|
||||||
encoded_project_path = requests.utils.quote(project_path, safe="")
|
|
||||||
print(f"Encoded project path: {encoded_project_path}", file=sys.stderr)
|
|
||||||
return encoded_project_path
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
print(f"Error executing git command: {e}", file=sys.stderr)
|
|
||||||
return None
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error in get_gitlab_project_id: {e}", file=sys.stderr)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
# parse sub tasks in issue description
|
|
||||||
def parse_sub_tasks(description):
|
|
||||||
sub_tasks = []
|
|
||||||
lines = description.split("\n")
|
|
||||||
for line in lines:
|
|
||||||
if line.startswith("- ["):
|
|
||||||
sub_tasks.append(line[2:])
|
|
||||||
return sub_tasks
|
|
||||||
|
|
||||||
|
|
||||||
def update_sub_tasks(description, tasks):
|
|
||||||
# remove all existing tasks
|
|
||||||
lines = description.split("\n")
|
|
||||||
updated_body = "\n".join(line for line in lines if not line.startswith("- ["))
|
|
||||||
|
|
||||||
# add new tasks
|
|
||||||
updated_body += "\n" + "\n".join(f"- {task}" for task in tasks)
|
|
||||||
|
|
||||||
return updated_body
|
|
||||||
|
|
||||||
|
|
||||||
def update_task_issue_url(description, task, issue_url):
|
|
||||||
# task is like:
|
|
||||||
# [ ] task name
|
|
||||||
# [x] task name
|
|
||||||
# replace task name with issue url, like:
|
|
||||||
# [ ] [task name](url)
|
|
||||||
# [x] [task name](url)
|
|
||||||
if task.find("] ") == -1:
|
|
||||||
return None
|
|
||||||
task = task[task.find("] ") + 2 :]
|
|
||||||
return description.replace(task, f"[{task}]({issue_url})")
|
|
||||||
|
|
||||||
|
|
||||||
def check_git_installed():
|
|
||||||
"""
|
|
||||||
Check if Git is installed on the local machine.
|
|
||||||
|
|
||||||
Tries to execute 'git --version' command to determine the presence of Git.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: True if Git is installed, False otherwise.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
subprocess_run(
|
|
||||||
["git", "--version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
print("Git is not installed on your system.")
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def create_and_checkout_branch(branch_name):
|
|
||||||
subprocess_run(["git", "checkout", "-b", branch_name], check=True)
|
|
||||||
|
|
||||||
|
|
||||||
def is_issue_url(task):
|
|
||||||
task = task.strip()
|
|
||||||
|
|
||||||
# 使用正则表达式匹配 http 或 https 开头,issues/数字 结尾的 URL
|
|
||||||
pattern = r"^(http|https)://.*?/issues/\d+$"
|
|
||||||
|
|
||||||
is_issue = bool(re.match(pattern, task))
|
|
||||||
|
|
||||||
# print(f"Task to check: {task}", file=sys.stderr)
|
|
||||||
# print(f"Is issue URL: {is_issue}", file=sys.stderr)
|
|
||||||
|
|
||||||
return is_issue
|
|
||||||
|
|
||||||
|
|
||||||
def read_issue_by_url(issue_url):
|
|
||||||
# Extract the issue number and project path from the URL
|
|
||||||
issue_url = issue_url.replace("/-/", "/")
|
|
||||||
parts = issue_url.split("/")
|
|
||||||
issue_number = parts[-1]
|
|
||||||
project_path = "/".join(
|
|
||||||
parts[3:-2]
|
|
||||||
) # Assumes URL format: https://gitlab.com/project/path/-/issues/number
|
|
||||||
|
|
||||||
# URL encode the project path
|
|
||||||
encoded_project_path = requests.utils.quote(project_path, safe="")
|
|
||||||
|
|
||||||
# Construct the API endpoint URL
|
|
||||||
api_url = f"{GITLAB_API_URL}/projects/{encoded_project_path}/issues/{issue_number}"
|
|
||||||
|
|
||||||
# Send a GET request to the API endpoint
|
|
||||||
headers = {
|
|
||||||
"Private-Token": GITLAB_ACCESS_TOKEN,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
}
|
|
||||||
response = requests.get(api_url, headers=headers)
|
|
||||||
|
|
||||||
if response.status_code == 200:
|
|
||||||
return response.json()
|
|
||||||
else:
|
|
||||||
print(f"Error fetching issue: {response.status_code}", file=sys.stderr)
|
|
||||||
print(f"Response content: {response.text}", file=sys.stderr)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def get_gitlab_issue_repo(issue_repo=False):
|
|
||||||
try:
|
|
||||||
config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json")
|
|
||||||
if os.path.exists(config_path) and issue_repo:
|
|
||||||
with open(config_path, "r", encoding="utf-8") as f:
|
|
||||||
config_data = json.load(f)
|
|
||||||
|
|
||||||
if "git_issue_repo" in config_data:
|
|
||||||
issue_repo = requests.utils.quote(config_data["git_issue_repo"], safe="")
|
|
||||||
print(
|
|
||||||
"current issue repo:",
|
|
||||||
config_data["git_issue_repo"],
|
|
||||||
end="\n\n",
|
|
||||||
file=sys.stderr,
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
return config_data["git_issue_repo"]
|
|
||||||
|
|
||||||
return get_gitlab_project_id()
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
print(e)
|
|
||||||
# 如果发生错误,打印错误信息
|
|
||||||
return None
|
|
||||||
except FileNotFoundError:
|
|
||||||
# 如果未找到git命令,可能是没有安装git或者不在PATH中
|
|
||||||
print("==> File not found...")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
# 获取当前分支名称
|
|
||||||
def get_current_branch():
|
|
||||||
try:
|
|
||||||
# 使用git命令获取当前分支名称
|
|
||||||
result = subprocess_check_output(
|
|
||||||
["git", "branch", "--show-current"], stderr=subprocess.STDOUT
|
|
||||||
).strip()
|
|
||||||
# 将结果从bytes转换为str
|
|
||||||
current_branch = result.decode("utf-8")
|
|
||||||
return current_branch
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
# 如果发生错误,打印错误信息
|
|
||||||
return None
|
|
||||||
except FileNotFoundError:
|
|
||||||
# 如果未找到git命令,可能是没有安装git或者不在PATH中
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def get_parent_branch():
|
|
||||||
current_branch = get_current_branch()
|
|
||||||
if current_branch is None:
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
# 使用git命令获取当前分支的父分支引用
|
|
||||||
result = subprocess_check_output(
|
|
||||||
["git", "rev-parse", "--abbrev-ref", f"{current_branch}@{1}"], stderr=subprocess.STDOUT
|
|
||||||
).strip()
|
|
||||||
# 将结果从bytes转换为str
|
|
||||||
parent_branch_ref = result.decode("utf-8")
|
|
||||||
if parent_branch_ref == current_branch:
|
|
||||||
# 如果父分支引用和当前分支相同,说明当前分支可能是基于一个没有父分支的提交创建的
|
|
||||||
return None
|
|
||||||
# 使用git命令获取父分支的名称
|
|
||||||
result = subprocess_check_output(
|
|
||||||
["git", "name-rev", "--name-only", "--exclude=tags/*", parent_branch_ref],
|
|
||||||
stderr=subprocess.STDOUT,
|
|
||||||
).strip()
|
|
||||||
parent_branch_name = result.decode("utf-8")
|
|
||||||
return parent_branch_name
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
print(e)
|
|
||||||
# 如果发生错误,打印错误信息
|
|
||||||
return None
|
|
||||||
except FileNotFoundError:
|
|
||||||
# 如果未找到git命令,可能是没有安装git或者不在PATH中
|
|
||||||
print("==> File not found...")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def get_issue_info(issue_id):
|
|
||||||
# 获取 GitLab 项目 ID
|
|
||||||
project_id = get_gitlab_issue_repo()
|
|
||||||
# 构造 GitLab API 端点 URL
|
|
||||||
api_url = f"{GITLAB_API_URL}/projects/{project_id}/issues/{issue_id}"
|
|
||||||
|
|
||||||
# 发送 GET 请求到 API 端点
|
|
||||||
headers = {
|
|
||||||
"Private-Token": GITLAB_ACCESS_TOKEN,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
}
|
|
||||||
response = requests.get(api_url, headers=headers)
|
|
||||||
|
|
||||||
if response.status_code == 200:
|
|
||||||
return response.json()
|
|
||||||
else:
|
|
||||||
print(f"Failed to get issue info. Status code: {response.status_code}", file=sys.stderr)
|
|
||||||
print(f"Response content: {response.text}", file=sys.stderr)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def get_issue_info_by_url(issue_url):
|
|
||||||
# get issue id from issue_url
|
|
||||||
def get_issue_id(issue_url):
|
|
||||||
# Extract the issue id from the issue_url
|
|
||||||
issue_id = issue_url.split("/")[-1]
|
|
||||||
return issue_id
|
|
||||||
|
|
||||||
return get_issue_info(get_issue_id(issue_url))
|
|
||||||
|
|
||||||
|
|
||||||
# 获取当前分支自从与base_branch分叉以来的历史提交信息
|
|
||||||
def get_commit_messages(base_branch):
|
|
||||||
# 找到当前分支与base_branch的分叉点
|
|
||||||
merge_base = subprocess_run(
|
|
||||||
["git", "merge-base", "HEAD", base_branch],
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
text=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# 检查是否成功找到分叉点
|
|
||||||
if merge_base.returncode != 0:
|
|
||||||
raise RuntimeError(f"Error finding merge base: {merge_base.stderr.strip()}")
|
|
||||||
|
|
||||||
# 获取分叉点的提交哈希
|
|
||||||
merge_base_commit = merge_base.stdout.strip()
|
|
||||||
|
|
||||||
# 获取从分叉点到当前分支的所有提交信息
|
|
||||||
result = subprocess_run(
|
|
||||||
["git", "log", f"{merge_base_commit}..HEAD"],
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
text=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# 检查git log命令是否成功执行
|
|
||||||
if result.returncode != 0:
|
|
||||||
raise RuntimeError(f"Error retrieving commit messages: {result.stderr.strip()}")
|
|
||||||
|
|
||||||
# 返回提交信息列表
|
|
||||||
return result.stdout
|
|
||||||
|
|
||||||
|
|
||||||
# 创建PR
|
|
||||||
def create_pull_request(title, description, source_branch, target_branch, project_id):
|
|
||||||
url = f"{GITLAB_API_URL}/projects/{project_id}/merge_requests"
|
|
||||||
headers = {"Private-Token": GITLAB_ACCESS_TOKEN, "Content-Type": "application/json"}
|
|
||||||
payload = {
|
|
||||||
"title": title,
|
|
||||||
"description": description,
|
|
||||||
"source_branch": source_branch,
|
|
||||||
"target_branch": target_branch,
|
|
||||||
}
|
|
||||||
|
|
||||||
response = requests.post(url, headers=headers, json=payload)
|
|
||||||
if response.status_code == 201:
|
|
||||||
response_json = response.json()
|
|
||||||
return response_json
|
|
||||||
|
|
||||||
print(response.text, end="\n\n", file=sys.stderr)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def get_recently_mr(project_id):
|
|
||||||
project_id = requests.utils.quote(project_id, safe="")
|
|
||||||
url = (
|
|
||||||
f"{GITLAB_API_URL}/projects/{project_id}/"
|
|
||||||
"merge_requests?state=opened&order_by=updated_at&sort=desc"
|
|
||||||
)
|
|
||||||
headers = {
|
|
||||||
"Private-Token": GITLAB_ACCESS_TOKEN,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
}
|
|
||||||
response = requests.get(url, headers=headers)
|
|
||||||
|
|
||||||
branch_name = get_current_branch()
|
|
||||||
|
|
||||||
if response.status_code == 200:
|
|
||||||
mrs = response.json()
|
|
||||||
for mr in mrs:
|
|
||||||
if mr["source_branch"] == branch_name:
|
|
||||||
return mr
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def run_command_with_retries(command, retries=3, delay=5):
|
|
||||||
for attempt in range(retries):
|
|
||||||
try:
|
|
||||||
subprocess_check_call(command)
|
|
||||||
return True
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
print(f"Command failed: {e}")
|
|
||||||
if attempt < retries - 1:
|
|
||||||
print(f"Retrying... (attempt {attempt + 1}/{retries})")
|
|
||||||
time.sleep(delay)
|
|
||||||
else:
|
|
||||||
print("All retries failed.")
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def update_mr(project_id, mr_iid, title, description):
|
|
||||||
project_id = requests.utils.quote(project_id, safe="")
|
|
||||||
url = f"{GITLAB_API_URL}/projects/{project_id}/merge_requests/{mr_iid}"
|
|
||||||
headers = {"Private-Token": GITLAB_ACCESS_TOKEN, "Content-Type": "application/json"}
|
|
||||||
payload = {"title": title, "description": description}
|
|
||||||
response = requests.put(url, headers=headers, json=payload)
|
|
||||||
|
|
||||||
if response.status_code == 200:
|
|
||||||
print(f"MR updated successfully: {response.json()['web_url']}")
|
|
||||||
return response.json()
|
|
||||||
else:
|
|
||||||
print("Failed to update MR.")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def check_unpushed_commits():
|
|
||||||
try:
|
|
||||||
# 获取当前分支的本地提交和远程提交的差异
|
|
||||||
result = subprocess_check_output(["git", "cherry", "-v"]).decode("utf-8").strip()
|
|
||||||
# 如果结果不为空,说明存在未push的提交
|
|
||||||
return bool(result)
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
print(f"Error checking for unpushed commits: {e}")
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def auto_push():
|
|
||||||
# 获取当前分支名
|
|
||||||
if not check_unpushed_commits():
|
|
||||||
return True
|
|
||||||
try:
|
|
||||||
branch = (
|
|
||||||
subprocess_check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"])
|
|
||||||
.strip()
|
|
||||||
.decode("utf-8")
|
|
||||||
)
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
print(f"Error getting current branch: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# 检查当前分支是否有对应的远程分支
|
|
||||||
remote_branch_exists = subprocess_call(["git", "ls-remote", "--exit-code", "origin", branch])
|
|
||||||
|
|
||||||
push_command = ["git", "push", "origin", branch]
|
|
||||||
if remote_branch_exists == 0:
|
|
||||||
# 如果存在远程分支,则直接push提交
|
|
||||||
return run_command_with_retries(push_command)
|
|
||||||
else:
|
|
||||||
# 如果不存在远程分支,则发布并push提交
|
|
||||||
push_command.append("-u")
|
|
||||||
return run_command_with_retries(push_command)
|
|
||||||
|
|
||||||
|
|
||||||
def get_recently_pr(repo):
|
|
||||||
url = f"{GITLAB_API_URL}/repos/{repo}/pulls?state=open&sort=updated"
|
|
||||||
headers = {
|
|
||||||
"Authorization": f"token {GITLAB_ACCESS_TOKEN}",
|
|
||||||
"Accept": "application/vnd.github.v3+json",
|
|
||||||
}
|
|
||||||
response = requests.get(url, headers=headers)
|
|
||||||
|
|
||||||
branch_name = get_current_branch()
|
|
||||||
|
|
||||||
if response.status_code == 200:
|
|
||||||
prs = response.json()
|
|
||||||
for pr in prs:
|
|
||||||
if pr["head"]["ref"] == branch_name:
|
|
||||||
return pr
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def update_pr(pr_number, title, description, repo_name):
|
|
||||||
url = f"{GITLAB_API_URL}/repos/{repo_name}/pulls/{pr_number}"
|
|
||||||
headers = {"Authorization": f"token {GITLAB_ACCESS_TOKEN}", "Content-Type": "application/json"}
|
|
||||||
payload = {"title": title, "description": description}
|
|
||||||
response = requests.patch(url, headers=headers, data=json.dumps(payload))
|
|
||||||
|
|
||||||
if response.status_code == 200:
|
|
||||||
print(f"PR updated successfully: {response.json()['web_url']}")
|
|
||||||
return response.json()
|
|
||||||
else:
|
|
||||||
print("Failed to update PR.")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def get_last_base_branch(default_branch):
|
|
||||||
"""read last base branch from config file"""
|
|
||||||
|
|
||||||
def read_config_item(config_path, item):
|
|
||||||
if os.path.exists(config_path):
|
|
||||||
with open(config_path, "r", encoding="utf-8") as f:
|
|
||||||
config = json.load(f)
|
|
||||||
return config.get(item)
|
|
||||||
return None
|
|
||||||
|
|
||||||
project_config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json")
|
|
||||||
last_base_branch = read_config_item(project_config_path, "last_base_branch")
|
|
||||||
if last_base_branch:
|
|
||||||
return last_base_branch
|
|
||||||
return default_branch
|
|
||||||
|
|
||||||
|
|
||||||
def save_last_base_branch(base_branch=None):
|
|
||||||
"""save last base branch to config file"""
|
|
||||||
|
|
||||||
def save_config_item(config_path, item, value):
|
|
||||||
if os.path.exists(config_path):
|
|
||||||
with open(config_path, "r", encoding="utf-8") as f:
|
|
||||||
config = json.load(f)
|
|
||||||
else:
|
|
||||||
config = {}
|
|
||||||
|
|
||||||
config[item] = value
|
|
||||||
with open(config_path, "w", encoding="utf-8") as f:
|
|
||||||
json.dump(config, f, indent=4)
|
|
||||||
|
|
||||||
if not base_branch:
|
|
||||||
base_branch = get_current_branch()
|
|
||||||
project_config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json")
|
|
||||||
save_config_item(project_config_path, "last_base_branch", base_branch)
|
|
@ -1,19 +0,0 @@
|
|||||||
### list_issue_tasks
|
|
||||||
|
|
||||||
列出指定Issue中的任务列表。
|
|
||||||
|
|
||||||
#### 用途
|
|
||||||
- 查看Issue中的子任务
|
|
||||||
- 跟踪任务进度
|
|
||||||
|
|
||||||
#### 使用方法
|
|
||||||
执行命令: `/github.list_issue_tasks <issue_url>`
|
|
||||||
|
|
||||||
#### 操作流程
|
|
||||||
1. 获取指定Issue的信息
|
|
||||||
2. 解析Issue内容中的任务列表
|
|
||||||
3. 显示任务列表
|
|
||||||
|
|
||||||
#### 注意事项
|
|
||||||
- 需要提供有效的Issue URL
|
|
||||||
- 任务应以特定格式在Issue中列出(如: - [ ] 任务描述)
|
|
@ -1,53 +0,0 @@
|
|||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from devchat.llm import chat_json
|
|
||||||
|
|
||||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
|
||||||
|
|
||||||
from common_util import assert_exit, editor # noqa: E402
|
|
||||||
from git_api import create_issue # noqa: E402
|
|
||||||
|
|
||||||
# Function to generate issue title and description using LLM
|
|
||||||
PROMPT = (
|
|
||||||
"Based on the following description, "
|
|
||||||
"suggest a title and a detailed description for a GitHub issue:\n\n"
|
|
||||||
"Description: {description}\n\n"
|
|
||||||
'Output format: {{"title": "<title>", "description": "<description>"}} '
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@chat_json(prompt=PROMPT)
|
|
||||||
def generate_issue_content(description):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@editor("Edit issue title:")
|
|
||||||
@editor("Edit issue description:")
|
|
||||||
def edit_issue(title, description):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# Main function
|
|
||||||
def main():
|
|
||||||
print("start new_issue ...", end="\n\n", flush=True)
|
|
||||||
|
|
||||||
assert_exit(len(sys.argv) < 2, "Missing argument.", exit_code=-1)
|
|
||||||
description = sys.argv[1]
|
|
||||||
|
|
||||||
print("Generating issue content ...", end="\n\n", flush=True)
|
|
||||||
issue_json_ob = generate_issue_content(description=description)
|
|
||||||
assert_exit(not issue_json_ob, "Failed to generate issue content.", exit_code=-1)
|
|
||||||
|
|
||||||
issue_title, issue_body = edit_issue(issue_json_ob["title"], issue_json_ob["description"])
|
|
||||||
assert_exit(not issue_title, "Issue creation cancelled.", exit_code=0)
|
|
||||||
print("New Issue:", issue_title, "description:", issue_body, end="\n\n", flush=True)
|
|
||||||
|
|
||||||
print("Creating issue ...", end="\n\n", flush=True)
|
|
||||||
issue = create_issue(issue_title, issue_body)
|
|
||||||
assert_exit(not issue, "Failed to create issue.", exit_code=-1)
|
|
||||||
print("New Issue:", issue["web_url"], end="\n\n", flush=True)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,5 +0,0 @@
|
|||||||
description: 'List issue tasks.'
|
|
||||||
input: required
|
|
||||||
help: README.md
|
|
||||||
steps:
|
|
||||||
- run: $devchat_python $command_path/command.py "$input"
|
|
@ -1,21 +0,0 @@
|
|||||||
### new_branch
|
|
||||||
|
|
||||||
基于当前分支创建新分支并切换到新分支。
|
|
||||||
|
|
||||||
#### 用途
|
|
||||||
- 快速创建新的功能或修复分支
|
|
||||||
- 保持工作区隔离
|
|
||||||
|
|
||||||
#### 使用方法
|
|
||||||
执行命令: `/github.new_branch <description>`
|
|
||||||
|
|
||||||
- description: 新分支的简短描述或相关Issue URL
|
|
||||||
|
|
||||||
#### 操作流程
|
|
||||||
1. 生成多个分支名建议
|
|
||||||
2. 用户选择或编辑分支名
|
|
||||||
3. 创建新分支并切换
|
|
||||||
|
|
||||||
#### 注意事项
|
|
||||||
- 确保当前分支的更改已提交
|
|
||||||
- 如提供Issue URL,会自动关联Issue编号到分支名
|
|
@ -1,95 +0,0 @@
|
|||||||
import json
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from devchat.llm import chat_json
|
|
||||||
|
|
||||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
|
||||||
|
|
||||||
from common_util import assert_exit, ui_edit # noqa: E402
|
|
||||||
from git_api import ( # noqa: E402
|
|
||||||
check_git_installed,
|
|
||||||
create_and_checkout_branch,
|
|
||||||
is_issue_url,
|
|
||||||
read_issue_by_url,
|
|
||||||
save_last_base_branch,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Function to generate a random branch name
|
|
||||||
PROMPT = (
|
|
||||||
"Give me 5 different git branch names, "
|
|
||||||
"mainly hoping to express: {task}, "
|
|
||||||
"Good branch name should looks like: <type>/<main content>,"
|
|
||||||
"the final result is output in JSON format, "
|
|
||||||
'as follows: {{"names":["name1", "name2", .. "name5"]}}\n'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@chat_json(prompt=PROMPT)
|
|
||||||
def generate_branch_name(task):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@ui_edit(ui_type="radio", description="Select a branch name")
|
|
||||||
def select_branch_name_ui(branch_names):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def select_branch_name(branch_names):
|
|
||||||
[branch_selection] = select_branch_name_ui(branch_names)
|
|
||||||
assert_exit(branch_selection is None, "No branch selected.", exit_code=0)
|
|
||||||
return branch_names[branch_selection]
|
|
||||||
|
|
||||||
|
|
||||||
def get_issue_or_task(task):
|
|
||||||
if is_issue_url(task):
|
|
||||||
issue = read_issue_by_url(task.strip())
|
|
||||||
assert_exit(not issue, "Failed to read issue.", exit_code=-1)
|
|
||||||
|
|
||||||
return json.dumps(
|
|
||||||
{"id": issue["iid"], "title": issue["title"], "description": issue["description"]}
|
|
||||||
), issue["iid"]
|
|
||||||
else:
|
|
||||||
return task, None
|
|
||||||
|
|
||||||
|
|
||||||
# Main function
|
|
||||||
def main():
|
|
||||||
print("Start create branch ...", end="\n\n", flush=True)
|
|
||||||
|
|
||||||
is_git_installed = check_git_installed()
|
|
||||||
assert_exit(not is_git_installed, "Git is not installed.", exit_code=-1)
|
|
||||||
|
|
||||||
task = sys.argv[1]
|
|
||||||
assert_exit(
|
|
||||||
not task,
|
|
||||||
"You need input something about the new branch, or input a issue url.",
|
|
||||||
exit_code=-1,
|
|
||||||
)
|
|
||||||
|
|
||||||
# read issue by url
|
|
||||||
task, issue_id = get_issue_or_task(task)
|
|
||||||
|
|
||||||
# Generate 5 branch names
|
|
||||||
print("Generating branch names ...", end="\n\n", flush=True)
|
|
||||||
branch_names = generate_branch_name(task=task)
|
|
||||||
assert_exit(not branch_names, "Failed to generate branch names.", exit_code=-1)
|
|
||||||
branch_names = branch_names["names"]
|
|
||||||
for index, branch_name in enumerate(branch_names):
|
|
||||||
if issue_id:
|
|
||||||
branch_names[index] = f"{branch_name}-#{issue_id}"
|
|
||||||
|
|
||||||
# Select branch name
|
|
||||||
selected_branch = select_branch_name(branch_names)
|
|
||||||
|
|
||||||
# save base branch name
|
|
||||||
save_last_base_branch()
|
|
||||||
|
|
||||||
# create and checkout branch
|
|
||||||
print(f"Creating and checking out branch: {selected_branch}")
|
|
||||||
create_and_checkout_branch(selected_branch)
|
|
||||||
print("Branch has create and checkout")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,21 +0,0 @@
|
|||||||
### new_issue
|
|
||||||
|
|
||||||
创建新的GitHub Issue。
|
|
||||||
|
|
||||||
#### 用途
|
|
||||||
- 快速创建标准格式的Issue
|
|
||||||
- 记录任务、bug或功能请求
|
|
||||||
|
|
||||||
#### 使用方法
|
|
||||||
执行命令: `/github.new_issue <description>`
|
|
||||||
|
|
||||||
- description: Issue的简短描述
|
|
||||||
|
|
||||||
#### 操作流程
|
|
||||||
1. 基于描述生成Issue标题和正文
|
|
||||||
2. 允许用户编辑Issue内容
|
|
||||||
3. 创建GitHub Issue
|
|
||||||
|
|
||||||
#### 注意事项
|
|
||||||
- 需要有创建Issue的权限
|
|
||||||
- 生成的内容可能需要进一步完善
|
|
@ -1,52 +0,0 @@
|
|||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
|
||||||
|
|
||||||
from common_util import assert_exit, editor # noqa: E402
|
|
||||||
from devchat.llm import chat_json # noqa: E402
|
|
||||||
from git_api import create_issue # noqa: E402
|
|
||||||
|
|
||||||
# Function to generate issue title and description using LLM
|
|
||||||
PROMPT = (
|
|
||||||
"Based on the following description, "
|
|
||||||
"suggest a title and a detailed description for a GitHub issue:\n\n"
|
|
||||||
"Description: {description}\n\n"
|
|
||||||
'Output as valid JSON format: {{"title": "<title>", "description": "<description> use \\n as new line flag."}} ' # noqa: E501
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@chat_json(prompt=PROMPT)
|
|
||||||
def generate_issue_content(description):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@editor("Edit issue title:")
|
|
||||||
@editor("Edit issue description:")
|
|
||||||
def edit_issue(title, description):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# Main function
|
|
||||||
def main():
|
|
||||||
print("start new_issue ...", end="\n\n", flush=True)
|
|
||||||
|
|
||||||
assert_exit(len(sys.argv) < 2, "Missing argument.", exit_code=-1)
|
|
||||||
description = sys.argv[1]
|
|
||||||
|
|
||||||
print("Generating issue content ...", end="\n\n", flush=True)
|
|
||||||
issue_json_ob = generate_issue_content(description=description)
|
|
||||||
assert_exit(not issue_json_ob, "Failed to generate issue content.", exit_code=-1)
|
|
||||||
|
|
||||||
issue_title, issue_body = edit_issue(issue_json_ob["title"], issue_json_ob["description"])
|
|
||||||
assert_exit(not issue_title, "Issue creation cancelled.", exit_code=0)
|
|
||||||
print("New Issue:", issue_title, "description:", issue_body, end="\n\n", flush=True)
|
|
||||||
|
|
||||||
print("Creating issue ...", end="\n\n", flush=True)
|
|
||||||
issue = create_issue(issue_title, issue_body)
|
|
||||||
assert_exit(not issue, "Failed to create issue.", exit_code=-1)
|
|
||||||
print("New Issue:", issue["web_url"], end="\n\n", flush=True)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,94 +0,0 @@
|
|||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".."))
|
|
||||||
|
|
||||||
from common_util import assert_exit, editor, ui_edit # noqa: E402
|
|
||||||
from devchat.llm import chat_json # noqa: E402
|
|
||||||
from git_api import ( # noqa: E402
|
|
||||||
create_issue,
|
|
||||||
get_issue_info_by_url,
|
|
||||||
parse_sub_tasks,
|
|
||||||
update_issue_body,
|
|
||||||
update_task_issue_url,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Function to generate issue title and description using LLM
|
|
||||||
PROMPT = (
|
|
||||||
"Following is parent issue content:\n"
|
|
||||||
"{issue_content}\n\n"
|
|
||||||
"Based on the following issue task: {task}"
|
|
||||||
"suggest a title and a detailed description for a GitHub issue:\n\n"
|
|
||||||
'Output format: {{"title": "<title>", "description": "<description>"}} '
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@chat_json(prompt=PROMPT)
|
|
||||||
def generate_issue_content(issue_content, task):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@editor("Edit issue title:")
|
|
||||||
@editor("Edit issue description:")
|
|
||||||
def edit_issue(title, description):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@ui_edit(ui_type="radio", description="Select a task to create issue:")
|
|
||||||
def select_task(tasks):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def get_issue_json(issue_url):
|
|
||||||
issue = get_issue_info_by_url(issue_url)
|
|
||||||
assert_exit(not issue, f"Failed to retrieve issue with ID: {issue_url}", exit_code=-1)
|
|
||||||
return {
|
|
||||||
"id": issue["iid"],
|
|
||||||
"web_url": issue["web_url"],
|
|
||||||
"title": issue["title"],
|
|
||||||
"description": issue["description"],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Main function
|
|
||||||
def main():
|
|
||||||
print("start new_issue ...", end="\n\n", flush=True)
|
|
||||||
|
|
||||||
assert_exit(len(sys.argv) < 2, "Missing argument.", exit_code=-1)
|
|
||||||
issue_url = sys.argv[1]
|
|
||||||
|
|
||||||
old_issue = get_issue_json(issue_url)
|
|
||||||
assert_exit(not old_issue, "Failed to retrieve issue with: {issue_url}", exit_code=-1)
|
|
||||||
tasks = parse_sub_tasks(old_issue["get_issue_json"])
|
|
||||||
assert_exit(not tasks, "No tasks in issue description.")
|
|
||||||
|
|
||||||
# select task from tasks
|
|
||||||
[task] = select_task(tasks)
|
|
||||||
assert_exit(task is None, "No task selected.")
|
|
||||||
task = tasks[task]
|
|
||||||
print("task:", task, end="\n\n", flush=True)
|
|
||||||
|
|
||||||
print("Generating issue content ...", end="\n\n", flush=True)
|
|
||||||
issue_json_ob = generate_issue_content(issue_content=old_issue, task=task)
|
|
||||||
assert_exit(not issue_json_ob, "Failed to generate issue content.", exit_code=-1)
|
|
||||||
|
|
||||||
issue_title, issue_body = edit_issue(issue_json_ob["title"], issue_json_ob["description"])
|
|
||||||
assert_exit(not issue_title, "Issue creation cancelled.", exit_code=0)
|
|
||||||
print("New Issue:", issue_title, "description:", issue_body, end="\n\n", flush=True)
|
|
||||||
|
|
||||||
print("Creating issue ...", end="\n\n", flush=True)
|
|
||||||
issue = create_issue(issue_title, issue_body)
|
|
||||||
assert_exit(not issue, "Failed to create issue.", exit_code=-1)
|
|
||||||
print("New Issue:", issue["web_url"], end="\n\n", flush=True)
|
|
||||||
|
|
||||||
# update issue task with new issue url
|
|
||||||
new_body = update_task_issue_url(old_issue["description"], task, issue["web_url"])
|
|
||||||
assert_exit(not new_body, f"{task} parse error.")
|
|
||||||
new_issue = update_issue_body(issue_url, new_body)
|
|
||||||
assert_exit(not new_issue, "Failed to update issue description.")
|
|
||||||
|
|
||||||
print("Issue tasks updated successfully!", end="\n\n", flush=True)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,22 +0,0 @@
|
|||||||
### new_pr
|
|
||||||
|
|
||||||
创建新的Pull Request。
|
|
||||||
|
|
||||||
#### 用途
|
|
||||||
- 自动生成PR标题和描述
|
|
||||||
- 简化代码审查流程
|
|
||||||
|
|
||||||
#### 使用方法
|
|
||||||
执行命令: `/github.new_pr [additional_info]`
|
|
||||||
|
|
||||||
- additional_info: 可选的附加信息
|
|
||||||
|
|
||||||
#### 操作流程
|
|
||||||
1. 获取当前分支信息和相关Issue
|
|
||||||
2. 生成PR标题和描述
|
|
||||||
3. 允许用户编辑PR内容
|
|
||||||
4. 创建Pull Request
|
|
||||||
|
|
||||||
#### 注意事项
|
|
||||||
- 确保当前分支有未合并的更改
|
|
||||||
- 需要有创建PR的权限
|
|
@ -1,120 +0,0 @@
|
|||||||
import json
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
|
||||||
|
|
||||||
|
|
||||||
from common_util import assert_exit, ui_edit # noqa: E402
|
|
||||||
from devchat.llm import chat_json # noqa: E402
|
|
||||||
from git_api import ( # noqa: E402
|
|
||||||
auto_push,
|
|
||||||
create_pull_request,
|
|
||||||
get_commit_messages,
|
|
||||||
get_current_branch,
|
|
||||||
get_gitlab_issue_repo,
|
|
||||||
get_issue_info,
|
|
||||||
get_last_base_branch,
|
|
||||||
save_last_base_branch,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# 从分支名称中提取issue id
|
|
||||||
def extract_issue_id(branch_name):
|
|
||||||
if "#" in branch_name:
|
|
||||||
return branch_name.split("#")[-1]
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
# 使用LLM模型生成PR内容
|
|
||||||
PROMPT = (
|
|
||||||
"Create a pull request title and description based on "
|
|
||||||
"the following issue and commit messages, if there is an "
|
|
||||||
"issue, close that issue in PR description as <user>/<repo>#issue_id:\n"
|
|
||||||
"Issue: {issue}\n"
|
|
||||||
"Commits:\n{commit_messages}\n"
|
|
||||||
"Other information:\n{user_input}\n\n"
|
|
||||||
"The response result should format as JSON object as following:\n"
|
|
||||||
'{{"title": "pr title", "description": "pr description"}}'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@chat_json(prompt=PROMPT)
|
|
||||||
def generate_pr_content_llm(issue, commit_message, user_input):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def generate_pr_content(issue, commit_messages, user_input):
|
|
||||||
response = generate_pr_content_llm(
|
|
||||||
issue=json.dumps(issue), commit_messages=commit_messages, user_input=user_input
|
|
||||||
)
|
|
||||||
assert_exit(not response, "Failed to generate PR content.", exit_code=-1)
|
|
||||||
return response.get("title"), response.get("description")
|
|
||||||
|
|
||||||
|
|
||||||
@ui_edit(ui_type="editor", description="Edit PR title:")
|
|
||||||
@ui_edit(ui_type="editor", description="Edit PR description:")
|
|
||||||
def edit_pr(title, description):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@ui_edit(ui_type="editor", description="Edit base branch:")
|
|
||||||
def edit_base_branch(base_branch):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def get_issue_json(issue_id):
|
|
||||||
issue = {"id": "no issue id", "title": "", "description": ""}
|
|
||||||
if issue_id:
|
|
||||||
issue = get_issue_info(issue_id)
|
|
||||||
assert_exit(not issue, f"Failed to retrieve issue with ID: {issue_id}", exit_code=-1)
|
|
||||||
issue = {
|
|
||||||
"id": issue_id,
|
|
||||||
"web_url": issue["web_url"],
|
|
||||||
"title": issue["title"],
|
|
||||||
"description": issue["description"],
|
|
||||||
}
|
|
||||||
return issue
|
|
||||||
|
|
||||||
|
|
||||||
# 主函数
|
|
||||||
def main():
|
|
||||||
print("start new_pr ...", end="\n\n", flush=True)
|
|
||||||
|
|
||||||
base_branch = get_last_base_branch("main")
|
|
||||||
base_branch = edit_base_branch(base_branch)
|
|
||||||
if isinstance(base_branch, list) and len(base_branch) > 0:
|
|
||||||
base_branch = base_branch[0]
|
|
||||||
save_last_base_branch(base_branch)
|
|
||||||
|
|
||||||
repo_name = get_gitlab_issue_repo()
|
|
||||||
branch_name = get_current_branch()
|
|
||||||
issue_id = extract_issue_id(branch_name)
|
|
||||||
|
|
||||||
# print basic info, repo_name, branch_name, issue_id
|
|
||||||
print("repo name:", repo_name, end="\n\n")
|
|
||||||
print("branch name:", branch_name, end="\n\n")
|
|
||||||
print("issue id:", issue_id, end="\n\n")
|
|
||||||
|
|
||||||
issue = get_issue_json(issue_id)
|
|
||||||
commit_messages = get_commit_messages(base_branch)
|
|
||||||
|
|
||||||
print("generating pr title and description ...", end="\n\n", flush=True)
|
|
||||||
user_input = sys.argv[1]
|
|
||||||
pr_title, pr_body = generate_pr_content(issue, commit_messages, user_input)
|
|
||||||
assert_exit(not pr_title, "Failed to generate PR content.", exit_code=-1)
|
|
||||||
|
|
||||||
pr_title, pr_body = edit_pr(pr_title, pr_body)
|
|
||||||
assert_exit(not pr_title, "PR creation cancelled.", exit_code=0)
|
|
||||||
|
|
||||||
is_push_success = auto_push()
|
|
||||||
assert_exit(not is_push_success, "Failed to push changes.", exit_code=-1)
|
|
||||||
|
|
||||||
pr = create_pull_request(pr_title, pr_body, branch_name, base_branch, repo_name)
|
|
||||||
assert_exit(not pr, "Failed to create PR.", exit_code=-1)
|
|
||||||
|
|
||||||
print(f"PR created successfully: {pr['web_url']}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,22 +0,0 @@
|
|||||||
### update_issue_tasks
|
|
||||||
|
|
||||||
更新指定Issue中的任务列表。
|
|
||||||
|
|
||||||
#### 用途
|
|
||||||
- 添加、修改或删除Issue中的子任务
|
|
||||||
- 更新任务进度
|
|
||||||
|
|
||||||
#### 使用方法
|
|
||||||
执行命令: `/github.update_issue_tasks`
|
|
||||||
|
|
||||||
#### 操作流程
|
|
||||||
1. 输入Issue URL
|
|
||||||
2. 显示当前任务列表
|
|
||||||
3. 用户输入更新建议
|
|
||||||
4. 生成新的任务列表
|
|
||||||
5. 允许用户编辑新任务列表
|
|
||||||
6. 更新Issue内容
|
|
||||||
|
|
||||||
#### 注意事项
|
|
||||||
- 需要有编辑Issue的权限
|
|
||||||
- 小心不要删除或覆盖重要信息
|
|
@ -1,101 +0,0 @@
|
|||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
|
||||||
|
|
||||||
from common_util import assert_exit, editor # noqa: E402
|
|
||||||
from devchat.llm import chat_json # noqa: E402
|
|
||||||
from git_api import ( # noqa: E402
|
|
||||||
get_issue_info_by_url,
|
|
||||||
parse_sub_tasks,
|
|
||||||
update_issue_body,
|
|
||||||
update_sub_tasks,
|
|
||||||
)
|
|
||||||
|
|
||||||
TASKS_PROMPT = (
|
|
||||||
"Following is my git issue content.\n"
|
|
||||||
"{issue_data}\n\n"
|
|
||||||
"Sub task in issue is like:- [ ] task name\n"
|
|
||||||
"'[ ] task name' will be as sub task content\n\n"
|
|
||||||
"Following is my idea to update sub tasks:\n"
|
|
||||||
"{user_input}\n\n"
|
|
||||||
"Please output all tasks in JSON format as:"
|
|
||||||
'{{"tasks": ["[ ] task1", "[ ] task2"]}}'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@chat_json(prompt=TASKS_PROMPT)
|
|
||||||
def generate_issue_tasks(issue_data, user_input):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def to_task_str(tasks):
|
|
||||||
task_str = ""
|
|
||||||
for task in tasks:
|
|
||||||
task_str += task + "\n"
|
|
||||||
return task_str
|
|
||||||
|
|
||||||
|
|
||||||
@editor("Edit issue old tasks:")
|
|
||||||
@editor("Edit issue new tasks:")
|
|
||||||
def edit_issue_tasks(old_tasks, new_tasks):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@editor("Input ISSUE url:")
|
|
||||||
def input_issue_url(url):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@editor("How to update tasks:")
|
|
||||||
def update_tasks_input(user_input):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def get_issue_json(issue_url):
|
|
||||||
issue = get_issue_info_by_url(issue_url)
|
|
||||||
assert_exit(not issue, f"Failed to retrieve issue with ID: {issue_url}", exit_code=-1)
|
|
||||||
return {
|
|
||||||
"id": issue["iid"],
|
|
||||||
"web_url": issue["web_url"],
|
|
||||||
"title": issue["title"],
|
|
||||||
"description": issue["description"],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Main function
|
|
||||||
def main():
|
|
||||||
print("start issue tasks update ...", end="\n\n", flush=True)
|
|
||||||
|
|
||||||
[issue_url] = input_issue_url("")
|
|
||||||
assert_exit(not issue_url, "No issue url.")
|
|
||||||
print("issue url:", issue_url, end="\n\n", flush=True)
|
|
||||||
|
|
||||||
issue = get_issue_json(issue_url)
|
|
||||||
old_tasks = parse_sub_tasks(issue["description"])
|
|
||||||
|
|
||||||
print(f"```tasks\n{to_task_str(old_tasks)}\n```", end="\n\n", flush=True)
|
|
||||||
|
|
||||||
[user_input] = update_tasks_input("")
|
|
||||||
assert_exit(not user_input, "No user input")
|
|
||||||
|
|
||||||
new_tasks = generate_issue_tasks(issue_data=issue, user_input=user_input)
|
|
||||||
assert_exit(not new_tasks, "No new tasks.")
|
|
||||||
print("new_tasks:", new_tasks, end="\n\n", flush=True)
|
|
||||||
assert_exit(not new_tasks.get("tasks", []), "No new tasks.")
|
|
||||||
print("new tasks:", to_task_str(new_tasks["tasks"]), end="\n\n", flush=True)
|
|
||||||
new_tasks = new_tasks["tasks"]
|
|
||||||
|
|
||||||
[old_tasks, new_tasks] = edit_issue_tasks(to_task_str(old_tasks), to_task_str(new_tasks))
|
|
||||||
assert_exit(not new_tasks, "No new tasks.")
|
|
||||||
print("new tasks:", new_tasks, end="\n\n", flush=True)
|
|
||||||
|
|
||||||
new_body = update_sub_tasks(issue["description"], new_tasks.split("\n"))
|
|
||||||
new_issue = update_issue_body(issue_url, new_body)
|
|
||||||
assert_exit(not new_issue, "Failed to update issue description.")
|
|
||||||
|
|
||||||
print("Issue tasks updated successfully!", end="\n\n", flush=True)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,5 +0,0 @@
|
|||||||
description: 'Update issue tasks.'
|
|
||||||
input: required
|
|
||||||
help: README.md
|
|
||||||
steps:
|
|
||||||
- run: $devchat_python $command_path/command.py "$input"
|
|
@ -1,20 +0,0 @@
|
|||||||
### update_pr
|
|
||||||
|
|
||||||
更新现有的Pull Request。
|
|
||||||
|
|
||||||
#### 用途
|
|
||||||
- 更新PR的标题和描述
|
|
||||||
- 反映最新的代码变更
|
|
||||||
|
|
||||||
#### 使用方法
|
|
||||||
执行命令: `/github.update_pr`
|
|
||||||
|
|
||||||
#### 操作流程
|
|
||||||
1. 获取最近的PR信息
|
|
||||||
2. 重新生成PR标题和描述
|
|
||||||
3. 允许用户编辑PR内容
|
|
||||||
4. 更新Pull Request
|
|
||||||
|
|
||||||
#### 注意事项
|
|
||||||
- 确保有更新PR的权限
|
|
||||||
- 更新前请确认是否有新的提交需要推送
|
|
@ -1,122 +0,0 @@
|
|||||||
import json
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
|
||||||
|
|
||||||
|
|
||||||
from common_util import assert_exit, ui_edit # noqa: E402
|
|
||||||
from devchat.llm import ( # noqa: E402
|
|
||||||
chat_json,
|
|
||||||
)
|
|
||||||
from git_api import ( # noqa: E402
|
|
||||||
auto_push,
|
|
||||||
get_commit_messages,
|
|
||||||
get_current_branch,
|
|
||||||
get_gitlab_issue_repo,
|
|
||||||
get_issue_info,
|
|
||||||
get_last_base_branch,
|
|
||||||
get_recently_pr,
|
|
||||||
save_last_base_branch,
|
|
||||||
update_pr,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# 从分支名称中提取issue id
|
|
||||||
def extract_issue_id(branch_name):
|
|
||||||
if "#" in branch_name:
|
|
||||||
return branch_name.split("#")[-1]
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
# 使用LLM模型生成PR内容
|
|
||||||
PROMPT = (
|
|
||||||
"Create a pull request title and description based on "
|
|
||||||
"the following issue and commit messages, if there is an "
|
|
||||||
"issue, close that issue in PR description as <user>/<repo>#issue_id:\n"
|
|
||||||
"Issue: {issue}\n"
|
|
||||||
"Commits:\n{commit_messages}\n"
|
|
||||||
"The response result should format as JSON object as following:\n"
|
|
||||||
'{{"title": "pr title", "description": "pr description"}}'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@chat_json(prompt=PROMPT)
|
|
||||||
def generate_pr_content_llm(issue, commit_messages):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def generate_pr_content(issue, commit_messages):
|
|
||||||
response = generate_pr_content_llm(issue=json.dumps(issue), commit_messages=commit_messages)
|
|
||||||
assert_exit(not response, "Failed to generate PR content.", exit_code=-1)
|
|
||||||
return response.get("title"), response.get("description")
|
|
||||||
|
|
||||||
|
|
||||||
@ui_edit(ui_type="editor", description="Edit PR title:")
|
|
||||||
@ui_edit(ui_type="editor", description="Edit PR description:")
|
|
||||||
def edit_pr(title, description):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@ui_edit(ui_type="editor", description="Edit base branch:")
|
|
||||||
def edit_base_branch(base_branch):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def get_issue_json(issue_id):
|
|
||||||
issue = {"id": "no issue id", "title": "", "description": ""}
|
|
||||||
if issue_id:
|
|
||||||
issue = get_issue_info(issue_id)
|
|
||||||
assert_exit(not issue, f"Failed to retrieve issue with ID: {issue_id}", exit_code=-1)
|
|
||||||
issue = {
|
|
||||||
"id": issue_id,
|
|
||||||
"web_url": issue["web_url"],
|
|
||||||
"title": issue["title"],
|
|
||||||
"description": issue["description"],
|
|
||||||
}
|
|
||||||
return issue
|
|
||||||
|
|
||||||
|
|
||||||
# 主函数
|
|
||||||
def main():
|
|
||||||
print("start update_pr ...", end="\n\n", flush=True)
|
|
||||||
|
|
||||||
base_branch = get_last_base_branch("main")
|
|
||||||
base_branch = edit_base_branch(base_branch)
|
|
||||||
if isinstance(base_branch, list) and len(base_branch) > 0:
|
|
||||||
base_branch = base_branch[0]
|
|
||||||
save_last_base_branch(base_branch)
|
|
||||||
|
|
||||||
repo_name = get_gitlab_issue_repo()
|
|
||||||
branch_name = get_current_branch()
|
|
||||||
issue_id = extract_issue_id(branch_name)
|
|
||||||
|
|
||||||
# print basic info, repo_name, branch_name, issue_id
|
|
||||||
print("repo name:", repo_name, end="\n\n")
|
|
||||||
print("branch name:", branch_name, end="\n\n")
|
|
||||||
print("issue id:", issue_id, end="\n\n")
|
|
||||||
|
|
||||||
issue = get_issue_json(issue_id)
|
|
||||||
commit_messages = get_commit_messages(base_branch)
|
|
||||||
|
|
||||||
recent_pr = get_recently_pr(repo_name)
|
|
||||||
assert_exit(not recent_pr, "Failed to get recent PR.", exit_code=-1)
|
|
||||||
|
|
||||||
print("generating pr title and description ...", end="\n\n", flush=True)
|
|
||||||
pr_title, pr_body = generate_pr_content(issue, commit_messages)
|
|
||||||
assert_exit(not pr_title, "Failed to generate PR content.", exit_code=-1)
|
|
||||||
|
|
||||||
pr_title, pr_body = edit_pr(pr_title, pr_body)
|
|
||||||
assert_exit(not pr_title, "PR creation cancelled.", exit_code=0)
|
|
||||||
|
|
||||||
is_push_success = auto_push()
|
|
||||||
assert_exit(not is_push_success, "Failed to push changes.", exit_code=-1)
|
|
||||||
|
|
||||||
pr = update_pr(recent_pr["iid"], pr_title, pr_body, repo_name)
|
|
||||||
assert_exit(not pr, "Failed to update PR.", exit_code=-1)
|
|
||||||
|
|
||||||
print(f"PR updated successfully: {pr['web_url']}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,5 +0,0 @@
|
|||||||
description: 'Update PR.'
|
|
||||||
input: required
|
|
||||||
help: README.md
|
|
||||||
steps:
|
|
||||||
- run: $devchat_python $command_path/command.py "$input"
|
|
@ -1,34 +0,0 @@
|
|||||||
# pr 命令
|
|
||||||
|
|
||||||
pr命令是一个用于处理Pull Requests (PRs)的主命令。它本身不执行具体操作,而是通过子命令来完成特定功能。
|
|
||||||
|
|
||||||
## 可用子命令
|
|
||||||
|
|
||||||
1. pr.review - 生成PR代码评审描述
|
|
||||||
2. pr.improve - 生成PR的代码建议
|
|
||||||
3. pr.describe - 生成PR描述
|
|
||||||
4. pr.custom_suggestions - 生成PR的自定义代码建议
|
|
||||||
|
|
||||||
## 使用方法
|
|
||||||
|
|
||||||
要使用pr命令的功能,请使用以下格式调用相应的子命令:
|
|
||||||
|
|
||||||
/pr.<子命令> <PR_URL>
|
|
||||||
|
|
||||||
例如:
|
|
||||||
- /pr.review https://github.com/devchat-ai/devchat/pull/301
|
|
||||||
- /pr.improve https://github.com/devchat-ai/devchat/pull/301
|
|
||||||
- /pr.describe https://github.com/devchat-ai/devchat/pull/301
|
|
||||||
- /pr.custom_suggestions https://github.com/devchat-ai/devchat/pull/301
|
|
||||||
|
|
||||||
## 子命令说明
|
|
||||||
|
|
||||||
1. pr.review: 分析PR并生成代码评审描述,帮助审阅者快速了解PR的内容和影响。
|
|
||||||
|
|
||||||
2. pr.improve: 分析PR并提供代码改进建议,帮助开发者优化其代码。
|
|
||||||
|
|
||||||
3. pr.describe: 自动生成PR的描述,总结PR的主要变更和目的。
|
|
||||||
|
|
||||||
4. pr.custom_suggestions: 根据特定需求生成自定义的PR代码建议。
|
|
||||||
|
|
||||||
请根据您的具体需求选择适当的子命令。每个子命令都专注于PR处理的不同方面,帮助您更高效地管理和改进Pull Requests。
|
|
@ -1,24 +0,0 @@
|
|||||||
# pr.config
|
|
||||||
|
|
||||||
**/pr.config命令用于配置Git工作流所需的设置。**
|
|
||||||
|
|
||||||
|
|
||||||
该命令允许用户配置以下选项:
|
|
||||||
|
|
||||||
1. PR Review Inline: 启用或禁用PR内联评审功能。
|
|
||||||
|
|
||||||
使用方式:
|
|
||||||
直接运行 /pr.config 命令,无需额外参数。
|
|
||||||
|
|
||||||
命令会引导用户通过交互式界面进行配置。用户可以选择是否启用PR内联评审功能。
|
|
||||||
|
|
||||||
|
|
||||||
配置文件位置:
|
|
||||||
全局配置文件保存在用户主目录下的 ~/.chat/.workflow_config.json
|
|
||||||
|
|
||||||
|
|
||||||
注意:
|
|
||||||
- 如需修改访问令牌或主机URL,请直接编辑配置文件。
|
|
||||||
- 配置更改后会立即生效。
|
|
||||||
|
|
||||||
这个命令可以帮助用户快速设置Git工作流所需的重要配置项,提高使用效率。
|
|
@ -1,72 +0,0 @@
|
|||||||
import json
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from lib.chatmark import Checkbox
|
|
||||||
|
|
||||||
# Configuration items
|
|
||||||
CONFIG_ITEMS = {
|
|
||||||
"pr_review_inline": "PR Review Inline Enabled",
|
|
||||||
}
|
|
||||||
|
|
||||||
# Configuration file paths
|
|
||||||
GLOBAL_CONFIG_PATH = os.path.join(os.path.expanduser("~"), ".chat", ".workflow_config.json")
|
|
||||||
|
|
||||||
|
|
||||||
def read_config(config_path, item):
|
|
||||||
if os.path.exists(config_path):
|
|
||||||
with open(config_path, "r", encoding="utf-8") as f:
|
|
||||||
config = json.load(f)
|
|
||||||
return config.get(item)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def save_config(config_path, item, value):
|
|
||||||
if os.path.exists(config_path):
|
|
||||||
with open(config_path, "r", encoding="utf-8") as f:
|
|
||||||
config = json.load(f)
|
|
||||||
else:
|
|
||||||
config = {}
|
|
||||||
|
|
||||||
config[item] = value
|
|
||||||
with open(config_path, "w", encoding="utf-8") as f:
|
|
||||||
json.dump(config, f, indent=4)
|
|
||||||
|
|
||||||
|
|
||||||
def is_pre_review_inline_enabled(current_value=False):
|
|
||||||
print("\n\nEnable PR Review Inline:\n\n")
|
|
||||||
checkbox = Checkbox(
|
|
||||||
[
|
|
||||||
"PR Review Inline Enabled",
|
|
||||||
],
|
|
||||||
[current_value],
|
|
||||||
)
|
|
||||||
checkbox.render()
|
|
||||||
|
|
||||||
print(f"\n\ncheckbox.selections: {checkbox.selections}\n\n")
|
|
||||||
if len(checkbox.selections) > 0:
|
|
||||||
return True
|
|
||||||
if checkbox.selections is None:
|
|
||||||
return None
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
print("Starting configuration of workflow settings...", end="\n\n", flush=True)
|
|
||||||
print(
|
|
||||||
"If you want to change access token or host url, "
|
|
||||||
"please edit the configuration file directly."
|
|
||||||
)
|
|
||||||
print("Configuration file is located at:", GLOBAL_CONFIG_PATH, end="\n\n", flush=True)
|
|
||||||
|
|
||||||
pr_review_inline_enable = read_config(GLOBAL_CONFIG_PATH, "pr_review_inline")
|
|
||||||
|
|
||||||
pr_review_inline_enable = is_pre_review_inline_enabled(pr_review_inline_enable or False)
|
|
||||||
if pr_review_inline_enable is not None:
|
|
||||||
save_config(GLOBAL_CONFIG_PATH, "pr_review_inline", pr_review_inline_enable)
|
|
||||||
print("Workflow settings configuration successful.")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,4 +0,0 @@
|
|||||||
description: 'Config required settings for GIT workflows.'
|
|
||||||
help: README.md
|
|
||||||
steps:
|
|
||||||
- run: $devchat_python $command_path/command.py
|
|
@ -1,2 +0,0 @@
|
|||||||
git+https://gitee.com/imlaji/pr-agent.git@ad276e206c7e462a689996ee3ada2769b35d5625
|
|
||||||
git+https://gitee.com/devchat-ai/devchat.git@pr_env
|
|
@ -1,3 +0,0 @@
|
|||||||
# /refactor.api
|
|
||||||
|
|
||||||
对选中代码进行API重构
|
|
@ -1,132 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import sys
|
|
||||||
from typing import Dict
|
|
||||||
|
|
||||||
from devchat.llm import chat_json
|
|
||||||
|
|
||||||
from lib.chatmark import Button, Form, TextEditor
|
|
||||||
from lib.ide_service import IDEService
|
|
||||||
from lib.workflow import workflow_call
|
|
||||||
|
|
||||||
# 步骤3: 使用AI识别API路径和METHOD的提示词
|
|
||||||
API_ANALYSIS_PROMPT = """
|
|
||||||
分析以下代码,识别其中的API路径和HTTP方法。
|
|
||||||
|
|
||||||
代码:
|
|
||||||
```
|
|
||||||
{code}
|
|
||||||
```
|
|
||||||
|
|
||||||
请提取代码中定义或使用的API路径和HTTP方法(GET, POST, PUT, DELETE等)。
|
|
||||||
如果代码中有多个API,请识别最主要的一个。
|
|
||||||
如果无法确定HTTP方法,请使用"GET"作为默认值。
|
|
||||||
|
|
||||||
返回JSON格式如下:
|
|
||||||
{{
|
|
||||||
"api_path": "识别到的API路径,例如/api/users",
|
|
||||||
"method": "识别到的HTTP方法,例如GET、POST、PUT、DELETE等"
|
|
||||||
}}
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
@chat_json(prompt=API_ANALYSIS_PROMPT)
|
|
||||||
def analyze_api(code: str) -> Dict[str, str]:
|
|
||||||
"""使用AI分析代码中的API路径和HTTP方法"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
"""API重构工作流主函数"""
|
|
||||||
try:
|
|
||||||
# 步骤1: 获取用户输入的重构目标
|
|
||||||
if len(sys.argv) < 2:
|
|
||||||
print("错误: 请提供重构目标")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
refactor_target = sys.argv[1]
|
|
||||||
|
|
||||||
# 步骤2: 获取用户选中的代码
|
|
||||||
selected_code = IDEService().get_selected_range()
|
|
||||||
if not selected_code or not selected_code.text.strip():
|
|
||||||
print("错误: 请先选择需要重构的代码")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# 步骤3: 使用AI识别API路径和METHOD
|
|
||||||
print("正在分析选中代码中的API信息...")
|
|
||||||
api_info = analyze_api(code=selected_code.text)
|
|
||||||
|
|
||||||
if not api_info or "api_path" not in api_info or "method" not in api_info:
|
|
||||||
print("错误: 无法识别API信息")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
api_path = api_info["api_path"]
|
|
||||||
method = api_info["method"]
|
|
||||||
|
|
||||||
# 步骤4: 显示识别结果并让用户确认
|
|
||||||
print("识别到的API信息:")
|
|
||||||
print(f"API路径: {api_path}")
|
|
||||||
print(f"HTTP方法: {method}")
|
|
||||||
|
|
||||||
api_path_editor = TextEditor(api_path)
|
|
||||||
|
|
||||||
form = Form(
|
|
||||||
[
|
|
||||||
"### 请确认API信息",
|
|
||||||
"API路径:",
|
|
||||||
api_path_editor,
|
|
||||||
f"HTTP方法: {method}",
|
|
||||||
"请确认或修改API路径,然后点击下方按钮继续",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
form.render()
|
|
||||||
|
|
||||||
# 获取用户确认后的API路径
|
|
||||||
confirmed_api_path = api_path_editor.new_text
|
|
||||||
|
|
||||||
# 步骤5: 调用重构工作流进行代码重构
|
|
||||||
print(f"正在重构API: {confirmed_api_path}...")
|
|
||||||
refactor_result = workflow_call(f"/refactor {refactor_target}")
|
|
||||||
|
|
||||||
if refactor_result != 0:
|
|
||||||
print("错误: API重构失败")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
print("API重构成功!")
|
|
||||||
|
|
||||||
# 步骤6: 显示按钮让用户确认是否继续
|
|
||||||
continue_button = Button(["提交修改并测试API", "结束重构"])
|
|
||||||
continue_button.render()
|
|
||||||
|
|
||||||
if continue_button.clicked == 1: # 用户选择结束
|
|
||||||
print("API重构已完成,未提交修改")
|
|
||||||
return
|
|
||||||
|
|
||||||
# 步骤7: 调用GitHub提交工作流提交修改
|
|
||||||
print("正在提交修改...")
|
|
||||||
commit_result = workflow_call("/github.commit")
|
|
||||||
|
|
||||||
if commit_result != 0:
|
|
||||||
print("警告: 代码提交失败,但将继续进行API测试")
|
|
||||||
else:
|
|
||||||
print("代码提交成功!")
|
|
||||||
|
|
||||||
# 步骤8: 调用API测试工作流对重构API进行测试
|
|
||||||
print("正在准备API测试...")
|
|
||||||
test_command = f"/test.api.upload {confirmed_api_path} {method} {refactor_target}"
|
|
||||||
test_result = workflow_call(test_command)
|
|
||||||
|
|
||||||
if test_result != 0:
|
|
||||||
print("警告: API测试可能未成功完成")
|
|
||||||
|
|
||||||
print("API重构工作流执行完毕!")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"错误: 执行过程中发生异常: {str(e)}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,5 +0,0 @@
|
|||||||
description: Refactor for selected api.
|
|
||||||
input: required
|
|
||||||
help: README.md
|
|
||||||
steps:
|
|
||||||
- run: $devchat_python $command_path/command.py "$input"
|
|
@ -1,28 +0,0 @@
|
|||||||
### test.api.config
|
|
||||||
|
|
||||||
配置API测试工作流所需的全局和仓库相关设置。
|
|
||||||
|
|
||||||
#### 用途
|
|
||||||
- 配置服务器连接信息(SERVER_URL, USERNAME, PASSWORD)
|
|
||||||
- 配置项目相关信息(PROJECT_ID, OPENAPI_URL, VERSION_URL)
|
|
||||||
|
|
||||||
#### 使用方法
|
|
||||||
执行命令: `/test.api.config`
|
|
||||||
|
|
||||||
#### 操作流程
|
|
||||||
1. 输入服务器URL(例如: http://kagent.merico.cn:8000)
|
|
||||||
2. 输入用户名
|
|
||||||
3. 输入密码
|
|
||||||
4. 输入项目ID(例如: 37)
|
|
||||||
5. 输入OpenAPI文档URL(例如: http://kagent.merico.cn:8080/openapi.json)
|
|
||||||
6. 输入版本信息URL(例如: http://kagent.merico.cn:8080/version)
|
|
||||||
7. 保存配置信息
|
|
||||||
|
|
||||||
#### 配置信息存储位置
|
|
||||||
- 全局配置(SERVER_URL, USERNAME, PASSWORD)保存在 `~/.chat/.workflow_config.json`
|
|
||||||
- 仓库配置(PROJECT_ID, OPENAPI_URL, VERSION_URL)保存在当前仓库的 `.chat/.workflow_config.json`
|
|
||||||
|
|
||||||
#### 注意事项
|
|
||||||
- 密码信息应妥善保管,不要泄露
|
|
||||||
- 配置完成后,其他API测试工作流将自动使用这些配置信息
|
|
||||||
- 如需修改配置,重新运行此命令即可
|
|
@ -1,151 +0,0 @@
|
|||||||
import json
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from lib.chatmark import Form, TextEditor # 导入 ChatMark 组件
|
|
||||||
|
|
||||||
|
|
||||||
def read_global_config():
|
|
||||||
"""读取全局配置信息"""
|
|
||||||
config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
|
||||||
config_data = {}
|
|
||||||
if os.path.exists(config_path):
|
|
||||||
with open(config_path, "r", encoding="utf-8") as f:
|
|
||||||
config_data = json.load(f)
|
|
||||||
|
|
||||||
server_url = config_data.get("api_testing_server_url", "")
|
|
||||||
username = config_data.get("api_testing_server_username", "")
|
|
||||||
password = config_data.get("api_testing_server_password", "")
|
|
||||||
|
|
||||||
return server_url, username, password
|
|
||||||
|
|
||||||
|
|
||||||
def save_global_config(server_url, username, password):
|
|
||||||
"""保存全局配置信息"""
|
|
||||||
config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
|
||||||
|
|
||||||
# 确保目录存在
|
|
||||||
os.makedirs(os.path.dirname(config_path), exist_ok=True)
|
|
||||||
|
|
||||||
config_data = {}
|
|
||||||
if os.path.exists(config_path):
|
|
||||||
with open(config_path, "r", encoding="utf-8") as f:
|
|
||||||
config_data = json.load(f)
|
|
||||||
|
|
||||||
config_data["api_testing_server_url"] = server_url
|
|
||||||
config_data["api_testing_server_username"] = username
|
|
||||||
config_data["api_testing_server_password"] = password
|
|
||||||
|
|
||||||
with open(config_path, "w+", encoding="utf-8") as f:
|
|
||||||
json.dump(config_data, f, indent=4)
|
|
||||||
|
|
||||||
|
|
||||||
def read_repo_config():
|
|
||||||
"""读取仓库相关配置信息"""
|
|
||||||
config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json")
|
|
||||||
config_data = {}
|
|
||||||
if os.path.exists(config_path):
|
|
||||||
with open(config_path, "r", encoding="utf-8") as f:
|
|
||||||
config_data = json.load(f)
|
|
||||||
|
|
||||||
project_id = config_data.get("test_api_project_id", "")
|
|
||||||
openapi_url = config_data.get("test_api_openapi_url", "")
|
|
||||||
version_url = config_data.get("test_api_version_url", "")
|
|
||||||
|
|
||||||
return project_id, openapi_url, version_url
|
|
||||||
|
|
||||||
|
|
||||||
def save_repo_config(project_id, openapi_url, version_url):
|
|
||||||
"""保存仓库相关配置信息"""
|
|
||||||
config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json")
|
|
||||||
|
|
||||||
# 确保目录存在
|
|
||||||
os.makedirs(os.path.dirname(config_path), exist_ok=True)
|
|
||||||
|
|
||||||
config_data = {}
|
|
||||||
if os.path.exists(config_path):
|
|
||||||
with open(config_path, "r", encoding="utf-8") as f:
|
|
||||||
config_data = json.load(f)
|
|
||||||
|
|
||||||
config_data["test_api_project_id"] = project_id
|
|
||||||
config_data["test_api_openapi_url"] = openapi_url
|
|
||||||
config_data["test_api_version_url"] = version_url
|
|
||||||
|
|
||||||
with open(config_path, "w+", encoding="utf-8") as f:
|
|
||||||
json.dump(config_data, f, indent=4)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
print("开始配置 API 测试所需的设置...", end="\n\n", flush=True)
|
|
||||||
|
|
||||||
# 读取全局配置
|
|
||||||
server_url, username, password = read_global_config()
|
|
||||||
|
|
||||||
# 读取仓库配置
|
|
||||||
project_id, openapi_url, version_url = read_repo_config()
|
|
||||||
|
|
||||||
# 创建表单组件
|
|
||||||
server_url_editor = TextEditor(server_url)
|
|
||||||
username_editor = TextEditor(username)
|
|
||||||
password_editor = TextEditor(password)
|
|
||||||
project_id_editor = TextEditor(project_id)
|
|
||||||
openapi_url_editor = TextEditor(openapi_url)
|
|
||||||
version_url_editor = TextEditor(version_url)
|
|
||||||
|
|
||||||
# 创建表单
|
|
||||||
form = Form(
|
|
||||||
[
|
|
||||||
"## DevChat API 测试服务器配置",
|
|
||||||
"请输入服务器 URL (例如: http://kagent.merico.cn:8000):",
|
|
||||||
server_url_editor,
|
|
||||||
"请输入用户名:",
|
|
||||||
username_editor,
|
|
||||||
"请输入密码:",
|
|
||||||
password_editor,
|
|
||||||
"## 仓库配置",
|
|
||||||
"请输入DevChat API 测试服务器中项目 ID (例如: 37):",
|
|
||||||
project_id_editor,
|
|
||||||
"请输入 OpenAPI URL (例如: http://kagent.merico.cn:8080/openapi.json):",
|
|
||||||
openapi_url_editor,
|
|
||||||
"请输入版本 URL (例如: http://kagent.merico.cn:8080/version),不输入表示不需要检查服务器测试环境版本:",
|
|
||||||
version_url_editor,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
# 渲染表单
|
|
||||||
form.render()
|
|
||||||
|
|
||||||
# 获取用户输入
|
|
||||||
server_url = server_url_editor.new_text.strip()
|
|
||||||
username = username_editor.new_text.strip()
|
|
||||||
password = password_editor.new_text.strip()
|
|
||||||
project_id = project_id_editor.new_text.strip()
|
|
||||||
openapi_url = openapi_url_editor.new_text.strip()
|
|
||||||
version_url = version_url_editor.new_text.strip()
|
|
||||||
|
|
||||||
# 保存全局配置
|
|
||||||
if server_url and username and password:
|
|
||||||
save_global_config(server_url, username, password)
|
|
||||||
else:
|
|
||||||
print("请提供完整的全局配置信息 (SERVER_URL, USERNAME, PASSWORD)。")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# 保存仓库配置
|
|
||||||
if project_id and openapi_url and version_url:
|
|
||||||
save_repo_config(project_id, openapi_url, version_url)
|
|
||||||
else:
|
|
||||||
print("请提供完整的仓库配置信息 (PROJECT_ID, OPENAPI_URL, VERSION_URL)。")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
print("\n配置信息已成功保存!")
|
|
||||||
print(f"全局配置: SERVER_URL={server_url}, USERNAME={username}, PASSWORD={'*' * len(password)}")
|
|
||||||
print(
|
|
||||||
f"仓库配置: PROJECT_ID={project_id}, OPENAPI_URL={openapi_url}, VERSION_URL={version_url}"
|
|
||||||
)
|
|
||||||
|
|
||||||
print("\n您现在可以使用其他 API 测试工作流了。")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,4 +0,0 @@
|
|||||||
description: 'Configure global and repository-specific settings for API testing.'
|
|
||||||
help: README.md
|
|
||||||
steps:
|
|
||||||
- run: $devchat_python $command_path/command.py
|
|
@ -1,244 +0,0 @@
|
|||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
||||||
sys.path.append(ROOT_DIR)
|
|
||||||
|
|
||||||
# noqa: I001
|
|
||||||
from api.utils import ( # noqa: E402
|
|
||||||
OPENAPI_URL,
|
|
||||||
PROJECT_ID,
|
|
||||||
SERVER_URL,
|
|
||||||
VERSION_URL,
|
|
||||||
get_path_op_id,
|
|
||||||
session,
|
|
||||||
)
|
|
||||||
|
|
||||||
# noqa: E402
|
|
||||||
from lib.chatmark.step import Step # noqa: E402
|
|
||||||
|
|
||||||
|
|
||||||
def get_apidocs():
|
|
||||||
res = session.get(
|
|
||||||
f"{SERVER_URL}/autotest/projects/{PROJECT_ID}/apidocs",
|
|
||||||
params={"page": 1, "size": 100},
|
|
||||||
)
|
|
||||||
return res.json()["docs"]
|
|
||||||
|
|
||||||
|
|
||||||
def delete_old_apidocs(apidocs):
|
|
||||||
for apidoc in apidocs:
|
|
||||||
session.delete(f"{SERVER_URL}/autotest/projects/{PROJECT_ID}/apidocs/{apidoc['id']}")
|
|
||||||
|
|
||||||
|
|
||||||
def get_local_version():
|
|
||||||
cmd = "git rev-parse HEAD"
|
|
||||||
res = subprocess.run(cmd, shell=True, check=True, stdout=subprocess.PIPE)
|
|
||||||
return res.stdout.decode("utf-8").strip()
|
|
||||||
|
|
||||||
|
|
||||||
def check_api_version():
|
|
||||||
# 如果没有配置VERSION_URL,则跳过版本检查
|
|
||||||
if not VERSION_URL:
|
|
||||||
print("未配置VERSION_URL,跳过API版本检查...")
|
|
||||||
return
|
|
||||||
|
|
||||||
local_version = get_local_version()
|
|
||||||
print("检查被测服务器文档是否已经更新到最新版本...", flush=True)
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
res = session.get(VERSION_URL)
|
|
||||||
version = res.json()["version"]
|
|
||||||
if version == local_version:
|
|
||||||
print(f"API 文档已更新,当前版本为 {version},开始上传 OpenAPI 文档...", flush=True)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
print(
|
|
||||||
".",
|
|
||||||
end="",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
time.sleep(5)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"检查 API 版本失败!{e}", flush=True)
|
|
||||||
time.sleep(5)
|
|
||||||
|
|
||||||
|
|
||||||
def wait_for_testcase_done(testcase_id):
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
res = session.get(
|
|
||||||
f"{SERVER_URL}/autotest/projects/{PROJECT_ID}/testcases/{testcase_id}"
|
|
||||||
)
|
|
||||||
data = res.json()
|
|
||||||
status = data["status"]
|
|
||||||
if status == "content_ready":
|
|
||||||
print("文本用例生成完成!", flush=True)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
print(
|
|
||||||
".",
|
|
||||||
end="",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
time.sleep(5)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"检查文本用例状态失败!{e}", flush=True)
|
|
||||||
time.sleep(5)
|
|
||||||
|
|
||||||
|
|
||||||
def wait_for_testcode_done(task_id):
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
res = session.get(f"{SERVER_URL}/tasks/{task_id}")
|
|
||||||
data = res.json()
|
|
||||||
status = data["status"]
|
|
||||||
if status == "succeeded":
|
|
||||||
print("自动测试脚本生成完成!", flush=True)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
print(
|
|
||||||
".",
|
|
||||||
end="",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
time.sleep(5)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"检查自动测试脚本生成失败!{e}", flush=True)
|
|
||||||
time.sleep(5)
|
|
||||||
|
|
||||||
|
|
||||||
def get_testcode(testcase_id):
|
|
||||||
res = session.get(
|
|
||||||
f"{SERVER_URL}/autotest/projects/{PROJECT_ID}/testcodes",
|
|
||||||
params={"testcase_id": testcase_id},
|
|
||||||
)
|
|
||||||
return res.json()["testcodes"][0]
|
|
||||||
|
|
||||||
|
|
||||||
def wait_for_task_done(task_id):
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
res = session.get(f"{SERVER_URL}/tasks/{task_id}")
|
|
||||||
data = res.json()
|
|
||||||
status = data["status"]
|
|
||||||
|
|
||||||
CREATED = "created"
|
|
||||||
RUNNING = "running"
|
|
||||||
WAITING = "waiting"
|
|
||||||
if status not in [CREATED, RUNNING, WAITING]:
|
|
||||||
print("自动测试脚本执行完成!", flush=True)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
print(
|
|
||||||
".",
|
|
||||||
end="",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
time.sleep(5)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"检查自动测试脚本状态失败!{e}", flush=True)
|
|
||||||
time.sleep(5)
|
|
||||||
|
|
||||||
|
|
||||||
def get_testcase(api_path_id):
|
|
||||||
res = session.get(
|
|
||||||
f"{SERVER_URL}/autotest/projects/{PROJECT_ID}/testcases",
|
|
||||||
params={"page": 1, "size": 100, "pathop_id": api_path_id},
|
|
||||||
)
|
|
||||||
return res.json()["testcases"][0]
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
error_msg = "请输入要测试的API名称和测试目标!如:/test.api.upload api_path method test_target"
|
|
||||||
if len(sys.argv) < 2:
|
|
||||||
print(error_msg)
|
|
||||||
return
|
|
||||||
args = sys.argv[1].strip().split(" ")
|
|
||||||
if len(args) < 3:
|
|
||||||
print(error_msg)
|
|
||||||
return
|
|
||||||
api_path = args[0]
|
|
||||||
method = args[1]
|
|
||||||
test_target = " ".join(args[2:])
|
|
||||||
docs = get_apidocs()
|
|
||||||
with Step("检查 API 版本是否更新..."):
|
|
||||||
check_api_version()
|
|
||||||
delete_old_apidocs(docs)
|
|
||||||
|
|
||||||
with Step(f"上传 OpenAPI 文档,并且触发 API {api_path} 的测试用例和自动测试脚本生成任务..."):
|
|
||||||
# 使用配置的OPENAPI_URL
|
|
||||||
if not OPENAPI_URL:
|
|
||||||
print("错误:未配置OPENAPI_URL,无法获取OpenAPI文档")
|
|
||||||
return
|
|
||||||
|
|
||||||
res = requests.get(
|
|
||||||
OPENAPI_URL,
|
|
||||||
)
|
|
||||||
res = session.post(
|
|
||||||
f"{SERVER_URL}/autotest/projects/{PROJECT_ID}/apidocs",
|
|
||||||
files={"file": ("openapi.json", res.content, "application/json")},
|
|
||||||
data={"apiauth_id": docs[0]["apiauth_id"]},
|
|
||||||
)
|
|
||||||
if res.status_code == 200:
|
|
||||||
print("上传 OpenAPI 文档成功!\n")
|
|
||||||
else:
|
|
||||||
print(f"上传 OpenAPI 文档失败!{res.text}", flush=True)
|
|
||||||
return
|
|
||||||
apipathop_id = get_path_op_id(api_path, method)
|
|
||||||
with Step("开始生成文本用例..."):
|
|
||||||
res = session.post(
|
|
||||||
f"{SERVER_URL}/autotest/projects/{PROJECT_ID}/testcases",
|
|
||||||
params={"generate_content": True},
|
|
||||||
json={"apipathop_id": apipathop_id, "title": test_target},
|
|
||||||
)
|
|
||||||
if res.status_code == 200:
|
|
||||||
print("提交生成文本用例成功!等待生成完成...", flush=True)
|
|
||||||
testcase_id = res.json()["id"]
|
|
||||||
wait_for_testcase_done(testcase_id)
|
|
||||||
else:
|
|
||||||
print(f"提交生成文本用例失败!{res.text}", flush=True)
|
|
||||||
return
|
|
||||||
with Step("开始生成自动测试脚本..."):
|
|
||||||
res = session.post(
|
|
||||||
f"{SERVER_URL}/autotest/projects/{PROJECT_ID}/tasks/testcode",
|
|
||||||
params={"testcase_id": testcase_id},
|
|
||||||
)
|
|
||||||
if res.status_code == 200:
|
|
||||||
print("提交生成自动测试脚本成功!等待生成完成...", flush=True)
|
|
||||||
task_id = res.json()["id"]
|
|
||||||
wait_for_testcode_done(task_id)
|
|
||||||
else:
|
|
||||||
print(f"提交生成自动测试脚本失败!{res.text}", flush=True)
|
|
||||||
return
|
|
||||||
with Step("开始执行自动测试脚本..."):
|
|
||||||
testcode = get_testcode(testcase_id)
|
|
||||||
testcode_id = testcode["id"]
|
|
||||||
res = session.post(
|
|
||||||
f"{SERVER_URL}/autotest/projects/{PROJECT_ID}/testcodes/{testcode_id}/exec",
|
|
||||||
)
|
|
||||||
if res.status_code == 200:
|
|
||||||
print("提交执行自动测试脚本成功!", flush=True)
|
|
||||||
else:
|
|
||||||
print(f"提交执行自动测试脚本失败!{res.text}")
|
|
||||||
return
|
|
||||||
|
|
||||||
api_path_id = get_path_op_id(api_path, method)
|
|
||||||
with Step("开始查询测试脚本执行结果..."):
|
|
||||||
while True:
|
|
||||||
testcase = get_testcase(api_path_id)
|
|
||||||
last_testcode_passed = testcase["last_testcode_passed"]
|
|
||||||
if last_testcode_passed:
|
|
||||||
print("测试脚本执行成功!", flush=True)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
print("测试脚本执行失败!", flush=True)
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,4 +0,0 @@
|
|||||||
description: 'Upload API documentation, generate test cases and test scripts for the target API, and execute the test code. Input format: APIPATH METHOD API_REFACTOR_DESCRIPTION'
|
|
||||||
input: required
|
|
||||||
steps:
|
|
||||||
- run: $devchat_python $command_path/command.py "$input"
|
|
@ -1,141 +0,0 @@
|
|||||||
import json
|
|
||||||
import os
|
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from lib.workflow.call import workflow_call
|
|
||||||
|
|
||||||
# 默认配置,仅在无法读取配置文件时使用
|
|
||||||
|
|
||||||
|
|
||||||
session = requests.Session()
|
|
||||||
_is_login = False
|
|
||||||
|
|
||||||
|
|
||||||
def read_config():
|
|
||||||
"""读取配置文件中的设置"""
|
|
||||||
# 读取全局配置
|
|
||||||
global_config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
|
||||||
global_config = {}
|
|
||||||
if os.path.exists(global_config_path):
|
|
||||||
try:
|
|
||||||
with open(global_config_path, "r", encoding="utf-8") as f:
|
|
||||||
global_config = json.load(f)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 读取仓库配置
|
|
||||||
repo_config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json")
|
|
||||||
repo_config = {}
|
|
||||||
if os.path.exists(repo_config_path):
|
|
||||||
try:
|
|
||||||
with open(repo_config_path, "r", encoding="utf-8") as f:
|
|
||||||
repo_config = json.load(f)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 获取配置值
|
|
||||||
server_url = global_config.get("api_testing_server_url", "")
|
|
||||||
username = global_config.get("api_testing_server_username", "")
|
|
||||||
password = global_config.get("api_testing_server_password", "")
|
|
||||||
project_id = repo_config.get("test_api_project_id", "")
|
|
||||||
openapi_url = repo_config.get("test_api_openapi_url", "")
|
|
||||||
version_url = repo_config.get("test_api_version_url", "")
|
|
||||||
|
|
||||||
return server_url, username, password, project_id, openapi_url, version_url
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_config():
|
|
||||||
"""确保配置存在,如果不存在则调用配置工作流"""
|
|
||||||
global_config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
|
||||||
repo_config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json")
|
|
||||||
|
|
||||||
# 检查全局配置和仓库配置是否存在
|
|
||||||
global_config_exists = os.path.exists(global_config_path)
|
|
||||||
repo_config_exists = os.path.exists(repo_config_path)
|
|
||||||
|
|
||||||
# 检查必填配置项是否存在
|
|
||||||
config_valid = True
|
|
||||||
if global_config_exists and repo_config_exists:
|
|
||||||
# 读取配置
|
|
||||||
global_config = {}
|
|
||||||
repo_config = {}
|
|
||||||
try:
|
|
||||||
with open(global_config_path, "r", encoding="utf-8") as f:
|
|
||||||
global_config = json.load(f)
|
|
||||||
with open(repo_config_path, "r", encoding="utf-8") as f:
|
|
||||||
repo_config = json.load(f)
|
|
||||||
except Exception:
|
|
||||||
config_valid = False
|
|
||||||
|
|
||||||
# 检查必填项
|
|
||||||
if (
|
|
||||||
not global_config.get("api_testing_server_url")
|
|
||||||
or not global_config.get("api_testing_server_username")
|
|
||||||
or not global_config.get("api_testing_server_password")
|
|
||||||
or not repo_config.get("test_api_project_id")
|
|
||||||
or not repo_config.get("test_api_openapi_url")
|
|
||||||
):
|
|
||||||
config_valid = False
|
|
||||||
else:
|
|
||||||
config_valid = False
|
|
||||||
|
|
||||||
if not config_valid:
|
|
||||||
print("缺少API测试所需的配置,将启动配置向导...")
|
|
||||||
workflow_call("/test.api.config")
|
|
||||||
|
|
||||||
# 重新检查配置是否已创建并包含必要项
|
|
||||||
try:
|
|
||||||
if os.path.exists(global_config_path) and os.path.exists(repo_config_path):
|
|
||||||
with open(global_config_path, "r", encoding="utf-8") as f:
|
|
||||||
global_config = json.load(f)
|
|
||||||
with open(repo_config_path, "r", encoding="utf-8") as f:
|
|
||||||
repo_config = json.load(f)
|
|
||||||
|
|
||||||
if (
|
|
||||||
global_config.get("api_testing_server_url")
|
|
||||||
and global_config.get("api_testing_server_username")
|
|
||||||
and global_config.get("api_testing_server_password")
|
|
||||||
and repo_config.get("test_api_project_id")
|
|
||||||
and repo_config.get("test_api_openapi_url")
|
|
||||||
):
|
|
||||||
return True
|
|
||||||
print("配置失败")
|
|
||||||
return False
|
|
||||||
except Exception:
|
|
||||||
print("配置失败")
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
# 读取配置
|
|
||||||
result = ensure_config()
|
|
||||||
if not result:
|
|
||||||
print("配置失败,工作流不能继续执行")
|
|
||||||
exit(0)
|
|
||||||
SERVER_URL, USERNAME, PASSWORD, PROJECT_ID, OPENAPI_URL, VERSION_URL = read_config()
|
|
||||||
|
|
||||||
|
|
||||||
def login():
|
|
||||||
global _is_login
|
|
||||||
if _is_login:
|
|
||||||
return
|
|
||||||
session.post(
|
|
||||||
f"{SERVER_URL}/user/auth/login",
|
|
||||||
data={"username": USERNAME, "password": PASSWORD, "grant_type": "password"},
|
|
||||||
)
|
|
||||||
_is_login = True
|
|
||||||
|
|
||||||
|
|
||||||
def get_path_op_id(keyword: str, method: str):
|
|
||||||
res = session.get(
|
|
||||||
f"{SERVER_URL}/autotest/projects/{PROJECT_ID}/apipathops",
|
|
||||||
params={"keyword": keyword, "page": 1, "size": 20},
|
|
||||||
)
|
|
||||||
for pathop in res.json()["pathops"]:
|
|
||||||
if pathop["method"].lower().strip() == method.lower().strip():
|
|
||||||
return pathop["id"]
|
|
||||||
|
|
||||||
|
|
||||||
login()
|
|
@ -1,6 +1,6 @@
|
|||||||
from .form import Form
|
from .form import Form
|
||||||
from .step import Step
|
from .step import Step
|
||||||
from .widgets import Button, Checkbox, MultiSelect, Radio, TextEditor
|
from .widgets import Button, Checkbox, Radio, TextEditor
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Checkbox",
|
"Checkbox",
|
||||||
@ -9,5 +9,4 @@ __all__ = [
|
|||||||
"Button",
|
"Button",
|
||||||
"Form",
|
"Form",
|
||||||
"Step",
|
"Step",
|
||||||
"MultiSelect",
|
|
||||||
]
|
]
|
||||||
|
@ -26,7 +26,6 @@ class Step(AbstractContextManager):
|
|||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
print(f"\n```Step\n# {self.title}", flush=True)
|
print(f"\n```Step\n# {self.title}", flush=True)
|
||||||
print("\n```", flush=True)
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
# close the step
|
# close the step
|
||||||
@ -34,3 +33,4 @@ class Step(AbstractContextManager):
|
|||||||
IDEService().ide_logging(
|
IDEService().ide_logging(
|
||||||
"debug", f"Step {self.title} took {end_time - self.enter_time:.2f} seconds"
|
"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
|
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):
|
class TextEditor(Widget):
|
||||||
"""
|
"""
|
||||||
ChatMark syntax:
|
ChatMark syntax:
|
||||||
@ -253,24 +202,14 @@ class TextEditor(Widget):
|
|||||||
super().__init__(submit_button_name, cancel_button_name)
|
super().__init__(submit_button_name, cancel_button_name)
|
||||||
|
|
||||||
self._title = title
|
self._title = title
|
||||||
self._text = self._handle_block_flag(text)
|
self._text = text
|
||||||
|
|
||||||
self._editor_key = self.gen_id(self._id_prefix, 0)
|
self._editor_key = self.gen_id(self._id_prefix, 0)
|
||||||
self._new_text: Optional[str] = None
|
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
|
@property
|
||||||
def new_text(self) -> Optional[str]:
|
def new_text(self):
|
||||||
if self._new_text is None:
|
return self._new_text
|
||||||
return None
|
|
||||||
return self._remove_block_flag(self._new_text)
|
|
||||||
|
|
||||||
def _in_chatmark(self) -> str:
|
def _in_chatmark(self) -> str:
|
||||||
"""
|
"""
|
||||||
|
@ -117,7 +117,7 @@ class IDEService:
|
|||||||
return self._result
|
return self._result
|
||||||
|
|
||||||
@rpc_method
|
@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.
|
Applies a given diff to a file.
|
||||||
|
|
||||||
@ -152,55 +152,3 @@ class IDEService:
|
|||||||
if self.ide_name() == "vscode":
|
if self.ide_name() == "vscode":
|
||||||
return selected_range()
|
return selected_range()
|
||||||
return IdeaIDEService().get_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 .rpc import rpc_call
|
||||||
from .types import LocationWithText
|
from .types import LocationWithText
|
||||||
|
|
||||||
@ -8,7 +10,12 @@ def run_code(code: str):
|
|||||||
|
|
||||||
|
|
||||||
@rpc_call
|
@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
|
pass
|
||||||
|
|
||||||
|
|
||||||
@ -65,18 +72,6 @@ def active_text_editor():
|
|||||||
return run_code(code=code)
|
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):
|
def open_folder(folder: str):
|
||||||
folder = folder.replace("\\", "/")
|
folder = folder.replace("\\", "/")
|
||||||
code = (
|
code = (
|
||||||
@ -86,21 +81,6 @@ def open_folder(folder: str):
|
|||||||
run_code(code=code)
|
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():
|
def visible_lines():
|
||||||
active_document = active_text_editor()
|
active_document = active_text_editor()
|
||||||
fail_result = {
|
fail_result = {
|
||||||
@ -111,17 +91,22 @@ def visible_lines():
|
|||||||
|
|
||||||
if not active_document:
|
if not active_document:
|
||||||
return fail_result
|
return fail_result
|
||||||
|
if not os.path.exists(active_document["document"]["uri"]["fsPath"]):
|
||||||
|
return fail_result
|
||||||
|
|
||||||
file_path = active_document["document"]["uri"]["fsPath"]
|
file_path = active_document["document"]["uri"]["fsPath"]
|
||||||
start_line = active_document["visibleRanges"][0][0]["line"]
|
start_line = active_document["visibleRanges"][0][0]["line"]
|
||||||
end_line = active_document["visibleRanges"][0][1]["line"]
|
end_line = active_document["visibleRanges"][0][1]["line"]
|
||||||
|
|
||||||
# 获取可见文本内容
|
# read file lines from start_line to end_line
|
||||||
visible_text = get_visible_text()
|
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 {
|
return {
|
||||||
"filePath": file_path,
|
"filePath": file_path,
|
||||||
"visibleText": visible_text,
|
"visibleText": "".join(_visible_lines),
|
||||||
"visibleRange": [start_line, end_line],
|
"visibleRange": [start_line, end_line],
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,24 +139,24 @@ def selected_lines():
|
|||||||
|
|
||||||
if not active_document:
|
if not active_document:
|
||||||
return fail_result
|
return fail_result
|
||||||
|
if not os.path.exists(active_document["document"]["uri"]["fsPath"]):
|
||||||
|
return fail_result
|
||||||
|
|
||||||
# 获取活动文档的文件路径
|
|
||||||
file_path = active_document["document"]["uri"]["fsPath"]
|
file_path = active_document["document"]["uri"]["fsPath"]
|
||||||
# 获取选择区域的起始行
|
|
||||||
start_line = active_document["selection"]["start"]["line"]
|
start_line = active_document["selection"]["start"]["line"]
|
||||||
start_col = active_document["selection"]["start"]["character"]
|
start_col = active_document["selection"]["start"]["character"]
|
||||||
# 获取选择区域的结束行
|
|
||||||
end_line = active_document["selection"]["end"]["line"]
|
end_line = active_document["selection"]["end"]["line"]
|
||||||
# 获取选择区域的结束列
|
|
||||||
end_col = active_document["selection"]["end"]["character"]
|
end_col = active_document["selection"]["end"]["character"]
|
||||||
|
|
||||||
# 获取编辑器当前内容
|
# read file lines from start_line to end_line
|
||||||
selected_text = get_selected_text()
|
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
|
# continue with the rest of the function
|
||||||
return {
|
return {
|
||||||
"filePath": file_path,
|
"filePath": file_path,
|
||||||
"selectedText": selected_text,
|
"selectedText": "".join(_selected_lines),
|
||||||
"selectedRange": [start_line, start_col, end_line, end_col],
|
"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. 选中行间代码。
|
||||||
#### 用途
|
2. 输入\/comments,回车发送;或右键点击**DevChat: Generate Comments**按钮。
|
||||||
- 为选中的代码块快速添加解释性注释
|
3. 开始生成行间注释,等待生成结束。
|
||||||
- 提高代码可读性和可维护性
|
4. 自动弹出Diff View,选择是否接受修改。
|
||||||
- 帮助开发者更好地理解代码逻辑
|
|
||||||
|
|
||||||
#### 使用方法
|
|
||||||
1. 在IDE中选中需要添加注释的代码块
|
|
||||||
2. 执行以下命令之一:
|
|
||||||
- 输入 `/comments` 并回车
|
|
||||||
|
|
||||||
#### 注意事项
|
|
||||||
1. 确保在执行命令前已选中代码块
|
|
||||||
2. 生成的注释会插入到相应的代码行之前
|
|
||||||
3. 原有的注释会被保留
|
|
||||||
4. 代码本身不会被修改,只会添加注释
|
|
||||||
|
|
||||||
#### 操作流程
|
|
||||||
1. 选中需要注释的代码块
|
|
||||||
2. 执行comments命令
|
|
||||||
3. 等待注释生成完成
|
|
||||||
4. 自动弹出Diff View,您可以选择接受或拒绝修改
|
|
||||||
|
|
||||||
|
|
||||||
额外信息
|
|
||||||
注释的语言会根据当前IDE的语言设置自动调整
|
|
||||||
对于中文环境,会生成中文注释
|
|
||||||
该命令利用AI技术生成注释,可能需要一定的处理时间
|
|
||||||
|
|
||||||
如图所示:
|
如图所示:
|
||||||
|
|
||||||
|
@ -259,8 +259,6 @@ def main():
|
|||||||
code_text = selected_text.get("text", "")
|
code_text = selected_text.get("text", "")
|
||||||
|
|
||||||
response = add_comments(selected_text=code_text, file_path=file_path)
|
response = add_comments(selected_text=code_text, file_path=file_path)
|
||||||
if not response:
|
|
||||||
sys.exit(1)
|
|
||||||
new_code = extract_markdown_block(response)
|
new_code = extract_markdown_block(response)
|
||||||
|
|
||||||
if not new_code:
|
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").'
|
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
|
hint: to close Issue #issue_number
|
||||||
input: optional
|
input: optional
|
||||||
help: README.md
|
|
||||||
steps:
|
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 subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
# from llm_api import chat_completion_stream # noqa: E402
|
||||||
from devchat.llm import chat_completion_stream
|
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
|
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 = (
|
diff_too_large_message_en = (
|
||||||
"Commit failed. The modified content is too long "
|
"Commit failed. The modified content is too long "
|
||||||
"and exceeds the model's length limit. "
|
"and exceeds the model's length limit. "
|
||||||
@ -35,6 +24,19 @@ diff_too_large_message_zh = (
|
|||||||
COMMIT_PROMPT_LIMIT_SIZE = 20000
|
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):
|
def extract_markdown_block(text):
|
||||||
"""
|
"""
|
||||||
Extracts the first Markdown code block from the given text without the language specifier.
|
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.
|
- FileNotFoundError: If the file does not exist.
|
||||||
- Exception: If any other error occurs during file reading.
|
- Exception: If any other error occurs during file reading.
|
||||||
"""
|
"""
|
||||||
|
s = IDEService()
|
||||||
try:
|
try:
|
||||||
with open(filename, "r", encoding="utf-8") as file:
|
with open(filename, "r", encoding="utf-8") as file:
|
||||||
return file.read().strip()
|
return file.read().strip()
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
IDEService().ide_logging(
|
s.ide_logging(
|
||||||
"error",
|
"info",
|
||||||
f"File {filename} not found. "
|
f"File {filename} not found. "
|
||||||
"Please make sure it exists in the same directory as the script.",
|
"Please make sure it exists in the same directory as the script.",
|
||||||
)
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
IDEService().ide_logging(
|
s.ide_logging("info", f"An error occurred while reading the file {filename}: {e}")
|
||||||
"error", f"An error occurred while reading the file {filename}: {e}"
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
@ -144,7 +145,7 @@ def get_modified_files():
|
|||||||
tuple: 包含两个list的元组,第一个list包含当前修改过的文件,第二个list包含已经staged的文件
|
tuple: 包含两个list的元组,第一个list包含当前修改过的文件,第二个list包含已经staged的文件
|
||||||
"""
|
"""
|
||||||
""" 获取当前修改文件列表以及已经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")
|
lines = output.split("\n")
|
||||||
modified_files = []
|
modified_files = []
|
||||||
staged_files = []
|
staged_files = []
|
||||||
@ -161,9 +162,9 @@ def get_modified_files():
|
|||||||
# check wether filename is a directory
|
# check wether filename is a directory
|
||||||
if os.path.isdir(filename):
|
if os.path.isdir(filename):
|
||||||
continue
|
continue
|
||||||
modified_files.append(os.path.normpath(strip_file_name(filename)))
|
modified_files.append((os.path.normpath(strip_file_name(filename)), status[1:2]))
|
||||||
if status == "M " or status == "A " or status == "D ":
|
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)))
|
staged_files.append((os.path.normpath(strip_file_name(filename)), status[0:1]))
|
||||||
return modified_files, staged_files
|
return modified_files, staged_files
|
||||||
|
|
||||||
|
|
||||||
@ -179,10 +180,14 @@ def get_marked_files(modified_files, staged_files):
|
|||||||
List[str]: 用户选中的文件列表
|
List[str]: 用户选中的文件列表
|
||||||
"""
|
"""
|
||||||
# Create two Checkbox instances for staged and unstaged files
|
# 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_files = [file for file in modified_files if file[1].strip() != ""]
|
||||||
unstaged_checkbox = Checkbox(unstaged_files, [False] * len(unstaged_files))
|
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
|
# Create a Form with both Checkbox instances
|
||||||
form_list = []
|
form_list = []
|
||||||
@ -202,31 +207,37 @@ def get_marked_files(modified_files, staged_files):
|
|||||||
# Retrieve the selected files from both Checkbox instances
|
# Retrieve the selected files from both Checkbox instances
|
||||||
staged_checkbox_selections = staged_checkbox.selections if staged_checkbox.selections else []
|
staged_checkbox_selections = staged_checkbox.selections if staged_checkbox.selections else []
|
||||||
unstaged_selections = unstaged_checkbox.selections if unstaged_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_staged_files = [staged_files[idx][0] for idx in staged_checkbox_selections]
|
||||||
selected_unstaged_files = [unstaged_files[idx] for idx in unstaged_selections]
|
selected_unstaged_files = [unstaged_files[idx][0] for idx in unstaged_selections]
|
||||||
|
|
||||||
# Combine the selections from both checkboxes
|
return selected_staged_files, selected_unstaged_files
|
||||||
selected_files = selected_staged_files + selected_unstaged_files
|
|
||||||
|
|
||||||
return selected_files
|
|
||||||
|
|
||||||
|
|
||||||
def rebuild_stage_list(user_files):
|
def rebuild_stage_list(staged_select_files, unstaged_select_files):
|
||||||
"""
|
"""
|
||||||
根据用户选中文件,重新构建stage列表
|
根据用户选中文件,重新构建stage列表
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
user_files: 用户选中的文件列表
|
staged_select_files: 当前选中的已staged文件列表
|
||||||
|
unstaged_select_files: 当前选中的未staged文件列表
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
None
|
None
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Unstage all files
|
# 获取当前所有staged文件
|
||||||
subprocess_check_output(["git", "reset"])
|
current_staged_files = subprocess.check_output(
|
||||||
# Stage all user_files
|
["git", "diff", "--name-only", "--cached"], text=True
|
||||||
for file in user_files:
|
).splitlines()
|
||||||
subprocess_run(["git", "add", file])
|
|
||||||
|
# 添加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():
|
def get_diff():
|
||||||
@ -240,13 +251,13 @@ def get_diff():
|
|||||||
bytes: 返回bytes类型,是git diff --cached命令的输出结果
|
bytes: 返回bytes类型,是git diff --cached命令的输出结果
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return subprocess_check_output(["git", "diff", "--cached"])
|
return subprocess.check_output(["git", "diff", "--cached"])
|
||||||
|
|
||||||
|
|
||||||
def get_current_branch():
|
def get_current_branch():
|
||||||
try:
|
try:
|
||||||
# 使用git命令获取当前分支名称
|
# 使用git命令获取当前分支名称
|
||||||
result = subprocess_check_output(
|
result = subprocess.check_output(
|
||||||
["git", "branch", "--show-current"], stderr=subprocess.STDOUT
|
["git", "branch", "--show-current"], stderr=subprocess.STDOUT
|
||||||
).strip()
|
).strip()
|
||||||
# 将结果从bytes转换为str
|
# 将结果从bytes转换为str
|
||||||
@ -260,7 +271,7 @@ def get_current_branch():
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def generate_commit_message_base_diff(user_input, diff, issue):
|
def generate_commit_message_base_diff(user_input, diff):
|
||||||
"""
|
"""
|
||||||
根据diff信息,通过AI生成一个commit消息
|
根据diff信息,通过AI生成一个commit消息
|
||||||
|
|
||||||
@ -274,10 +285,8 @@ def generate_commit_message_base_diff(user_input, diff, issue):
|
|||||||
"""
|
"""
|
||||||
global language
|
global language
|
||||||
language_prompt = "You must response commit message in chinese。\n" if language == "zh" else ""
|
language_prompt = "You must response commit message in chinese。\n" if language == "zh" else ""
|
||||||
prompt = (
|
prompt = PROMPT_COMMIT_MESSAGE_BY_DIFF_USER_INPUT.replace("{__DIFF__}", f"{diff}").replace(
|
||||||
PROMPT_COMMIT_MESSAGE_BY_DIFF_USER_INPUT.replace("{__DIFF__}", f"{diff}")
|
"{__USER_INPUT__}", f"{user_input + language_prompt}"
|
||||||
.replace("{__USER_INPUT__}", f"{user_input + language_prompt}")
|
|
||||||
.replace("{__ISSUE__}", f"{issue}")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
model_token_limit_error = (
|
model_token_limit_error = (
|
||||||
@ -293,7 +302,7 @@ def generate_commit_message_base_diff(user_input, diff, issue):
|
|||||||
if (
|
if (
|
||||||
not response["content"]
|
not response["content"]
|
||||||
and response.get("error", None)
|
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)
|
print(model_token_limit_error)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
@ -320,27 +329,7 @@ def display_commit_message_and_commit(commit_message):
|
|||||||
new_commit_message = text_editor.new_text
|
new_commit_message = text_editor.new_text
|
||||||
if not new_commit_message:
|
if not new_commit_message:
|
||||||
return None
|
return None
|
||||||
return subprocess_check_output(["git", "commit", "-m", new_commit_message])
|
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
|
|
||||||
|
|
||||||
|
|
||||||
def check_git_installed():
|
def check_git_installed():
|
||||||
@ -362,142 +351,56 @@ def check_git_installed():
|
|||||||
return False
|
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():
|
def main():
|
||||||
global language
|
global language
|
||||||
try:
|
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
|
# Ensure enough command line arguments are provided
|
||||||
if len(sys.argv) < 2:
|
if len(sys.argv) < 2:
|
||||||
print(
|
print("Usage: python script.py <user_input>", file=sys.stderr, flush=True)
|
||||||
"Usage: python script.py <user_input> <language>",
|
|
||||||
file=sys.stderr,
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
user_input = sys.argv[1]
|
user_input = sys.argv[1]
|
||||||
language = "english"
|
language = IDEService().ide_language()
|
||||||
if len(sys.argv) > 2:
|
|
||||||
language = sys.argv[2]
|
|
||||||
|
|
||||||
if not check_git_installed():
|
if not check_git_installed():
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
print(
|
step1_msg = _T(
|
||||||
"Step 1/3: Select the files you've changed that you wish to include in this commit, "
|
"Step 1/2: Select the files you've changed that you wish to include in this commit, "
|
||||||
"then click 'Submit'.",
|
"then click 'Submit'.",
|
||||||
end="\n\n",
|
"第一步/2:选择您希望包含在这次提交中的文件,然后点击“提交”。",
|
||||||
flush=True,
|
|
||||||
)
|
)
|
||||||
|
print(step1_msg, end="\n\n", flush=True)
|
||||||
modified_files, staged_files = get_modified_files()
|
modified_files, staged_files = get_modified_files()
|
||||||
if len(modified_files) == 0:
|
if len(modified_files) == 0:
|
||||||
print("No files to commit.", file=sys.stderr, flush=True)
|
print("There are no files to commit.", flush=True)
|
||||||
sys.exit(-1)
|
sys.exit(0)
|
||||||
|
|
||||||
selected_files = get_marked_files(modified_files, staged_files)
|
staged_select_files, unstaged_select_files = get_marked_files(modified_files, staged_files)
|
||||||
if not selected_files:
|
if not staged_select_files and not unstaged_select_files:
|
||||||
print("No files selected, commit aborted.")
|
no_files_msg = _T(
|
||||||
|
"No files selected, the commit has been aborted.",
|
||||||
|
"没有选择任何文件,提交已中止。",
|
||||||
|
)
|
||||||
|
print(no_files_msg)
|
||||||
return
|
return
|
||||||
|
|
||||||
rebuild_stage_list(selected_files)
|
rebuild_stage_list(staged_select_files, unstaged_select_files)
|
||||||
|
|
||||||
print(
|
step2_msg = _T(
|
||||||
"Step 2/3: Review the commit message I've drafted for you. "
|
"Step 2/2: Review the commit message I've drafted for you. "
|
||||||
"Edit it below if needed. Then click 'Commit' to proceed with "
|
"Edit it below if needed. Then click 'Commit' to proceed with "
|
||||||
"the commit using this message.",
|
"the commit using this message.",
|
||||||
end="\n\n",
|
"第二步/2:查看我为您起草的提交消息。如果需要,请在下面编辑它。然后单击“提交”以使用此消息进行提交。",
|
||||||
flush=True,
|
|
||||||
)
|
)
|
||||||
|
print(step2_msg, end="\n\n", flush=True)
|
||||||
diff = get_diff()
|
diff = get_diff()
|
||||||
branch_name = get_current_branch()
|
branch_name = get_current_branch()
|
||||||
issue_id = extract_issue_id(branch_name)
|
|
||||||
issue = str(get_issue_json(issue_id))
|
|
||||||
if branch_name:
|
if branch_name:
|
||||||
user_input += "\ncurrent repo branch name is:" + 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
|
# TODO
|
||||||
# remove Closes #IssueNumber in commit message
|
# remove Closes #IssueNumber in commit message
|
||||||
@ -507,28 +410,20 @@ def main():
|
|||||||
.replace("No specific issue to close", "")
|
.replace("No specific issue to close", "")
|
||||||
.replace("No specific issue mentioned.", "")
|
.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"])
|
commit_result = display_commit_message_and_commit(commit_message["content"])
|
||||||
if not commit_result:
|
if not commit_result:
|
||||||
print("Commit aborted.", flush=True)
|
commit_abort_msg = _T(
|
||||||
|
"Commit aborted.",
|
||||||
|
"提交已中止。",
|
||||||
|
)
|
||||||
|
print(commit_abort_msg, flush=True)
|
||||||
else:
|
else:
|
||||||
# 添加推送步骤
|
commit_completed_msg = _T(
|
||||||
if ask_for_push():
|
"Commit completed.",
|
||||||
if not push_changes():
|
"提交已完成。",
|
||||||
print("Push failed.", flush=True)
|
)
|
||||||
sys.exit(-1)
|
print(commit_completed_msg, flush=True)
|
||||||
|
|
||||||
print("Commit completed.", flush=True)
|
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
print("Exception:", err, file=sys.stderr, flush=True)
|
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.
|
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 Format:**
|
||||||
Response should be in the following markdown codeblock format:
|
```
|
||||||
```commit
|
|
||||||
type: Title
|
type: Title
|
||||||
|
|
||||||
- Detail message line 1
|
- Detail message line 1
|
||||||
- Detail message line 2
|
- Detail message line 2
|
||||||
- Detail message line 3
|
- 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:**
|
**Constraints:**
|
||||||
- Exclude markdown code block indicators (```) and the placeholder \"commit_message\" from your response.
|
- Exclude markdown code block indicators (```) and the placeholder \"commit_message\" from your response.
|
||||||
- Follow commit message best practices:
|
- Follow commit message best practices:
|
||||||
- Limit the title length to 50 characters.
|
- Limit the title length to 50 characters.
|
||||||
- Limit each summary line to 72 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__}`
|
**User Input:** `{__USER_INPUT__}`
|
||||||
|
|
||||||
@ -31,15 +33,5 @@ Determine if `{__USER_INPUT__}` contains a reference to closing an issue. If so,
|
|||||||
{__DIFF__}
|
{__DIFF__}
|
||||||
```
|
```
|
||||||
|
|
||||||
Related issue:
|
|
||||||
{__ISSUE__}
|
|
||||||
Utilize the provided format to craft a commit message that adheres to the stipulated criteria.
|
Utilize the provided format to craft a commit message that adheres to the stipulated criteria.
|
||||||
|
|
||||||
example output:
|
|
||||||
```commit
|
|
||||||
feature: add update user info API
|
|
||||||
|
|
||||||
- add post method api /user/update
|
|
||||||
- implement update user info logic
|
|
||||||
```
|
|
||||||
|
|
1
merico/commit/test/prompt/commit_cache.json
Normal file
1
merico/commit/test/prompt/commit_cache.json
Normal file
File diff suppressed because one or more lines are too long
@ -0,0 +1,9 @@
|
|||||||
|
prompts:
|
||||||
|
[
|
||||||
|
'../../diffCommitMessagePrompt.txt'
|
||||||
|
]
|
||||||
|
providers:
|
||||||
|
- id: "exec:python commit_message_run.py"
|
||||||
|
tests:
|
||||||
|
- commit_message_tests.yaml
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user