Compare commits

..

2 Commits

173 changed files with 643 additions and 6682 deletions

1
.gitignore vendored
View File

@ -9,4 +9,3 @@ custom/*
!custom/config.yml.example
user_settings.yml
.aider*

View File

@ -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修复、性能优化等任务。它会分析当前添加的所有文件,并提供整体的改进建议。

View File

@ -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()

View File

@ -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"

View File

@ -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目录不存在,会自动创建。

View File

@ -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()

View File

@ -1,5 +0,0 @@
description: "add files to aider"
input: required
help: README.md
steps:
- run: $devchat_python $command_path/command.py "$input"

View File

@ -1,19 +0,0 @@
### aider.files.list
这个命令用于列出当前在aider处理列表中的所有文件。
用途:
显示所有已添加到aider中的文件,提供当前aider正在处理的文件概览。
使用方法:
/aider.files.list
注意事项:
- 如果没有文件被添加到aider,会显示相应的提示消息
- 文件列表按字母顺序排序显示
示例:
/aider.files.list
额外信息:
这个命令会读取.chat/.aider_files文件的内容来获取文件列表。如果该文件不存在,会提示尚未添加任何文件。

View File

@ -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()

View File

@ -1,4 +0,0 @@
description: "list files in aider"
help: README.md
steps:
- run: $devchat_python $command_path/command.py

View File

@ -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文件,从中删除指定的文件路径。如果文件不存在于列表中,操作会安全退出。

View File

@ -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()

View File

@ -1,5 +0,0 @@
description: "remove files from aider"
input: required
help: README.md
steps:
- run: $devchat_python $command_path/command.py "$input"

View File

@ -1,2 +0,0 @@
git+https://gitee.com/imlaji/aider.git@main
git+https://gitee.com/devchat-ai/devchat.git@aider

View File

@ -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

View File

@ -1,5 +0,0 @@
description: 'Generate code task summary.'
input: optional
help: README.md
steps:
- run: $devchat_python $command_path/command.py "$input"

View File

@ -1 +0,0 @@
description: Root of github commands.

View File

@ -1,23 +0,0 @@
### commit
自动生成提交信息并执行Git提交。
#### 用途
- 生成规范的提交信息
- 简化Git提交流程
- 保持提交历史的一致性
#### 使用方法
执行命令: `/github.commit [message]`
- message: 可选的用户输入,用于辅助生成提交信息
#### 操作流程
1. 选择要提交的文件
2. 生成提交信息
3. 允许用户编辑提交信息
4. 执行Git提交
#### 注意事项
- 确保已选择需要提交的文件
- 生成的提交信息可能需要进一步修改以符合项目规范

View File

@ -1,19 +0,0 @@
### config
配置GitHub工作流所需的设置。
#### 用途
- 设置Issue仓库URL
- 配置GitHub Token
#### 使用方法
执行命令: `/github.config`
#### 操作流程
1. 输入Issue仓库URL(可选)
2. 输入GitHub Token
3. 保存配置信息
#### 注意事项
- GitHub Token应妥善保管,不要泄露
- 配置信息将保存在本地文件中

View File

@ -1,4 +0,0 @@
description: 'Config required settings for GIT workflows.'
help: README.md
steps:
- run: $devchat_python $command_path/command.py

View File

@ -1,19 +0,0 @@
### list_issue_tasks
列出指定Issue中的任务列表。
#### 用途
- 查看Issue中的子任务
- 跟踪任务进度
#### 使用方法
执行命令: `/github.list_issue_tasks <issue_url>`
#### 操作流程
1. 获取指定Issue的信息
2. 解析Issue内容中的任务列表
3. 显示任务列表
#### 注意事项
- 需要提供有效的Issue URL
- 任务应以特定格式在Issue中列出(如: - [ ] 任务描述)

View File

@ -1,5 +0,0 @@
description: 'List issue tasks.'
input: required
help: README.md
steps:
- run: $devchat_python $command_path/command.py "$input"

View File

@ -1,21 +0,0 @@
### new_branch
基于当前分支创建新分支并切换到新分支。
#### 用途
- 快速创建新的功能或修复分支
- 保持工作区隔离
#### 使用方法
执行命令: `/github.new_branch <description>`
- description: 新分支的简短描述或相关Issue URL
#### 操作流程
1. 生成多个分支名建议
2. 用户选择或编辑分支名
3. 创建新分支并切换
#### 注意事项
- 确保当前分支的更改已提交
- 如提供Issue URL,会自动关联Issue编号到分支名

View File

@ -1,21 +0,0 @@
### new_issue
创建新的GitHub Issue。
#### 用途
- 快速创建标准格式的Issue
- 记录任务、bug或功能请求
#### 使用方法
执行命令: `/github.new_issue <description>`
- description: Issue的简短描述
#### 操作流程
1. 基于描述生成Issue标题和正文
2. 允许用户编辑Issue内容
3. 创建GitHub Issue
#### 注意事项
- 需要有创建Issue的权限
- 生成的内容可能需要进一步完善

View File

@ -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的权限

View File

@ -1,22 +0,0 @@
### update_issue_tasks
更新指定Issue中的任务列表。
#### 用途
- 添加、修改或删除Issue中的子任务
- 更新任务进度
#### 使用方法
执行命令: `/github.update_issue_tasks`
#### 操作流程
1. 输入Issue URL
2. 显示当前任务列表
3. 用户输入更新建议
4. 生成新的任务列表
5. 允许用户编辑新任务列表
6. 更新Issue内容
#### 注意事项
- 需要有编辑Issue的权限
- 小心不要删除或覆盖重要信息

View File

@ -1,5 +0,0 @@
description: 'Update issue tasks.'
input: required
help: README.md
steps:
- run: $devchat_python $command_path/command.py "$input"

View File

@ -1,20 +0,0 @@
### update_pr
更新现有的Pull Request。
#### 用途
- 更新PR的标题和描述
- 反映最新的代码变更
#### 使用方法
执行命令: `/github.update_pr`
#### 操作流程
1. 获取最近的PR信息
2. 重新生成PR标题和描述
3. 允许用户编辑PR内容
4. 更新Pull Request
#### 注意事项
- 确保有更新PR的权限
- 更新前请确认是否有新的提交需要推送

View File

@ -1,5 +0,0 @@
description: 'Update PR.'
input: required
help: README.md
steps:
- run: $devchat_python $command_path/command.py "$input"

View File

@ -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

View File

@ -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()

View File

@ -1,5 +0,0 @@
description: 'Generate code task summary.'
input: optional
help: README.md
steps:
- run: $devchat_python $command_path/command.py "$input"

View File

@ -1 +0,0 @@
description: Root of gitlab commands.

View File

@ -1,23 +0,0 @@
### commit
自动生成提交信息并执行Git提交。
#### 用途
- 生成规范的提交信息
- 简化Git提交流程
- 保持提交历史的一致性
#### 使用方法
执行命令: `/github.commit [message]`
- message: 可选的用户输入,用于辅助生成提交信息
#### 操作流程
1. 选择要提交的文件
2. 生成提交信息
3. 允许用户编辑提交信息
4. 执行Git提交
#### 注意事项
- 确保已选择需要提交的文件
- 生成的提交信息可能需要进一步修改以符合项目规范

View File

@ -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"

View File

@ -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)

View File

@ -1,19 +0,0 @@
### config
配置GitHub工作流所需的设置。
#### 用途
- 设置Issue仓库URL
- 配置GitHub Token
#### 使用方法
执行命令: `/github.config`
#### 操作流程
1. 输入Issue仓库URL(可选)
2. 输入GitHub Token
3. 保存配置信息
#### 注意事项
- GitHub Token应妥善保管,不要泄露
- 配置信息将保存在本地文件中

View File

@ -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()

View File

@ -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)

View File

@ -1,19 +0,0 @@
### list_issue_tasks
列出指定Issue中的任务列表。
#### 用途
- 查看Issue中的子任务
- 跟踪任务进度
#### 使用方法
执行命令: `/github.list_issue_tasks <issue_url>`
#### 操作流程
1. 获取指定Issue的信息
2. 解析Issue内容中的任务列表
3. 显示任务列表
#### 注意事项
- 需要提供有效的Issue URL
- 任务应以特定格式在Issue中列出(如: - [ ] 任务描述)

View File

@ -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()

View File

@ -1,5 +0,0 @@
description: 'List issue tasks.'
input: required
help: README.md
steps:
- run: $devchat_python $command_path/command.py "$input"

View File

@ -1,21 +0,0 @@
### new_branch
基于当前分支创建新分支并切换到新分支。
#### 用途
- 快速创建新的功能或修复分支
- 保持工作区隔离
#### 使用方法
执行命令: `/github.new_branch <description>`
- description: 新分支的简短描述或相关Issue URL
#### 操作流程
1. 生成多个分支名建议
2. 用户选择或编辑分支名
3. 创建新分支并切换
#### 注意事项
- 确保当前分支的更改已提交
- 如提供Issue URL,会自动关联Issue编号到分支名

View File

@ -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()

View File

@ -1,21 +0,0 @@
### new_issue
创建新的GitHub Issue。
#### 用途
- 快速创建标准格式的Issue
- 记录任务、bug或功能请求
#### 使用方法
执行命令: `/github.new_issue <description>`
- description: Issue的简短描述
#### 操作流程
1. 基于描述生成Issue标题和正文
2. 允许用户编辑Issue内容
3. 创建GitHub Issue
#### 注意事项
- 需要有创建Issue的权限
- 生成的内容可能需要进一步完善

View File

@ -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()

View File

@ -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()

View File

@ -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的权限

View File

@ -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()

View File

@ -1,22 +0,0 @@
### update_issue_tasks
更新指定Issue中的任务列表。
#### 用途
- 添加、修改或删除Issue中的子任务
- 更新任务进度
#### 使用方法
执行命令: `/github.update_issue_tasks`
#### 操作流程
1. 输入Issue URL
2. 显示当前任务列表
3. 用户输入更新建议
4. 生成新的任务列表
5. 允许用户编辑新任务列表
6. 更新Issue内容
#### 注意事项
- 需要有编辑Issue的权限
- 小心不要删除或覆盖重要信息

View File

@ -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()

View File

@ -1,5 +0,0 @@
description: 'Update issue tasks.'
input: required
help: README.md
steps:
- run: $devchat_python $command_path/command.py "$input"

View File

@ -1,20 +0,0 @@
### update_pr
更新现有的Pull Request。
#### 用途
- 更新PR的标题和描述
- 反映最新的代码变更
#### 使用方法
执行命令: `/github.update_pr`
#### 操作流程
1. 获取最近的PR信息
2. 重新生成PR标题和描述
3. 允许用户编辑PR内容
4. 更新Pull Request
#### 注意事项
- 确保有更新PR的权限
- 更新前请确认是否有新的提交需要推送

View File

@ -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()

View File

@ -1,5 +0,0 @@
description: 'Update PR.'
input: required
help: README.md
steps:
- run: $devchat_python $command_path/command.py "$input"

View File

@ -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。

View File

@ -1,24 +0,0 @@
# pr.config
**/pr.config命令用于配置Git工作流所需的设置。**
该命令允许用户配置以下选项:
1. PR Review Inline: 启用或禁用PR内联评审功能。
使用方式:
直接运行 /pr.config 命令,无需额外参数。
命令会引导用户通过交互式界面进行配置。用户可以选择是否启用PR内联评审功能。
配置文件位置:
全局配置文件保存在用户主目录下的 ~/.chat/.workflow_config.json
注意:
- 如需修改访问令牌或主机URL,请直接编辑配置文件。
- 配置更改后会立即生效。
这个命令可以帮助用户快速设置Git工作流所需的重要配置项,提高使用效率。

View File

@ -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()

View File

@ -1,4 +0,0 @@
description: 'Config required settings for GIT workflows.'
help: README.md
steps:
- run: $devchat_python $command_path/command.py

View File

@ -1,2 +0,0 @@
git+https://gitee.com/imlaji/pr-agent.git@ad276e206c7e462a689996ee3ada2769b35d5625
git+https://gitee.com/devchat-ai/devchat.git@pr_env

View File

@ -1,3 +0,0 @@
# /refactor.api
对选中代码进行API重构

View File

@ -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()

View File

@ -1,5 +0,0 @@
description: Refactor for selected api.
input: required
help: README.md
steps:
- run: $devchat_python $command_path/command.py "$input"

View File

@ -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测试工作流将自动使用这些配置信息
- 如需修改配置,重新运行此命令即可

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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"

View File

@ -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()

View File

@ -1,6 +1,6 @@
from .form import Form
from .step import Step
from .widgets import Button, Checkbox, MultiSelect, Radio, TextEditor
from .widgets import Button, Checkbox, Radio, TextEditor
__all__ = [
"Checkbox",
@ -9,5 +9,4 @@ __all__ = [
"Button",
"Form",
"Step",
"MultiSelect",
]

View File

@ -26,7 +26,6 @@ class Step(AbstractContextManager):
def __enter__(self):
print(f"\n```Step\n# {self.title}", flush=True)
print("\n```", flush=True)
def __exit__(self, exc_type, exc_val, exc_tb):
# close the step
@ -34,3 +33,4 @@ class Step(AbstractContextManager):
IDEService().ide_logging(
"debug", f"Step {self.title} took {end_time - self.enter_time:.2f} seconds"
)
print("\n```", flush=True)

View File

@ -163,57 +163,6 @@ class Checkbox(Widget):
self._selections = selections
class MultiSelect(Checkbox):
"""
ChatMark syntax:
```chatmark
Which files would you like to commit? I've suggested a few.
> {x}(file1) devchat/engine/prompter.py
> {x}(file2) devchat/prompt.py
> {}(file3) tests/test_cli_prompt.py
```
Response:
```yaml
file1: checked
file3: checked
```
"""
def __init__(
self,
options: List[str],
check_states: Optional[List[bool]] = None,
title: Optional[str] = None,
submit_button_name: str = "Submit",
cancel_button_name: str = "Cancel",
):
"""
options: options to be selected
check_states: initial check states of options, default to all False
title: title of the widget
"""
super().__init__(options, check_states, title, submit_button_name, cancel_button_name)
def _in_chatmark(self) -> str:
"""
Generate ChatMark syntax for checkbox options
Use the index of option to generate id/key
"""
lines = []
if self._title:
lines.append(self._title)
for idx, (option, state) in enumerate(zip(self._options, self._states)):
mark = "{x}" if state else "{}"
key = self.gen_id(self._id_prefix, idx)
lines.append(f"> {mark}({key}) {option}")
text = "\n".join(lines)
return text
class TextEditor(Widget):
"""
ChatMark syntax:
@ -253,24 +202,14 @@ class TextEditor(Widget):
super().__init__(submit_button_name, cancel_button_name)
self._title = title
self._text = self._handle_block_flag(text)
self._text = text
self._editor_key = self.gen_id(self._id_prefix, 0)
self._new_text: Optional[str] = None
def _handle_block_flag(self, text: str):
"""convert \\ to \\, and ` to \\`"""
return text.replace("```", "\\`\\`\\`") if text else text
def _remove_block_flag(self, text: str):
"""convert \\ to \\, and \\` to `"""
return text.replace("\\`\\`\\`", "```") if text else text
@property
def new_text(self) -> Optional[str]:
if self._new_text is None:
return None
return self._remove_block_flag(self._new_text)
def new_text(self):
return self._new_text
def _in_chatmark(self) -> str:
"""

View File

@ -117,7 +117,7 @@ class IDEService:
return self._result
@rpc_method
def diff_apply(self, filepath, content, autoedit: bool = False) -> bool:
def diff_apply(self, filepath, content) -> bool:
"""
Applies a given diff to a file.
@ -152,55 +152,3 @@ class IDEService:
if self.ide_name() == "vscode":
return selected_range()
return IdeaIDEService().get_selected_range()
@rpc_method
def get_diagnostics_in_range(self, fileName: str, startLine: int, endLine: int) -> List[str]:
"""
Retrieves diagnostics for a specific range of code in the current IDE.
Returns:
A list of diagnostic messages for the specified range.
"""
return self._result
@rpc_method
def get_collapsed_code(self, fileName: str, startLine: int, endLine: int) -> str:
"""
Retrives collapsed code exclude specfic range of code in the current IDE.
Returns:
The collapsed code.
"""
return self._result
@rpc_method
def get_extension_tools_path(self) -> str:
"""
Retrives extension tools path.
Returns:
The extension tools path.
"""
return self._result
@rpc_method
def select_range(
self, fileName: str, startLine: int, startColumn: int, endLine: int, endColumn: int
) -> bool:
"""
Selects a range of text in the specified file.
Args:
fileName: The name of the file.
startLine: The starting line of the selection (0-based).
startColumn: The starting column of the selection (0-based).
endLine: The ending line of the selection (0-based).
endColumn: The ending column of the selection (0-based).
Returns:
A boolean indicating whether the selection was successful.
Note:
If startLine is -1, it cancels the current selection.
"""
return self._result

View File

@ -1,3 +1,5 @@
import os
from .rpc import rpc_call
from .types import LocationWithText
@ -8,7 +10,12 @@ def run_code(code: str):
@rpc_call
def diff_apply(filepath, content, autoedit=False):
def diff_apply(filepath, content):
pass
@rpc_call
def get_symbol_defines_in_selected_code():
pass
@ -65,18 +72,6 @@ def active_text_editor():
return run_code(code=code)
def get_selected_text():
code = """
const editor = vscode.window.activeTextEditor;
if (editor) {
const selection = editor.selection;
return editor.document.getText(selection);
}
return '';
"""
return run_code(code=code)
def open_folder(folder: str):
folder = folder.replace("\\", "/")
code = (
@ -86,21 +81,6 @@ def open_folder(folder: str):
run_code(code=code)
def get_visible_text():
code = """
const editor = vscode.window.activeTextEditor;
if (editor) {
const visibleRanges = editor.visibleRanges;
if (visibleRanges.length > 0) {
const visibleRange = visibleRanges[0];
return editor.document.getText(visibleRange);
}
}
return '';
"""
return run_code(code=code)
def visible_lines():
active_document = active_text_editor()
fail_result = {
@ -111,17 +91,22 @@ def visible_lines():
if not active_document:
return fail_result
if not os.path.exists(active_document["document"]["uri"]["fsPath"]):
return fail_result
file_path = active_document["document"]["uri"]["fsPath"]
start_line = active_document["visibleRanges"][0][0]["line"]
end_line = active_document["visibleRanges"][0][1]["line"]
# 获取可见文本内容
visible_text = get_visible_text()
# read file lines from start_line to end_line
with open(file_path, "r", encoding="utf-8") as file:
_lines = file.readlines()
_visible_lines = _lines[start_line : end_line + 1]
# continue with the rest of the function
return {
"filePath": file_path,
"visibleText": visible_text,
"visibleText": "".join(_visible_lines),
"visibleRange": [start_line, end_line],
}
@ -154,24 +139,24 @@ def selected_lines():
if not active_document:
return fail_result
if not os.path.exists(active_document["document"]["uri"]["fsPath"]):
return fail_result
# 获取活动文档的文件路径
file_path = active_document["document"]["uri"]["fsPath"]
# 获取选择区域的起始行
start_line = active_document["selection"]["start"]["line"]
start_col = active_document["selection"]["start"]["character"]
# 获取选择区域的结束行
end_line = active_document["selection"]["end"]["line"]
# 获取选择区域的结束列
end_col = active_document["selection"]["end"]["character"]
# 获取编辑器当前内容
selected_text = get_selected_text()
# read file lines from start_line to end_line
with open(file_path, "r", encoding="utf-8") as file:
_lines = file.readlines()
_selected_lines = _lines[start_line : end_line + 1]
# continue with the rest of the function
return {
"filePath": file_path,
"selectedText": selected_text,
"selectedText": "".join(_selected_lines),
"selectedRange": [start_line, start_col, end_line, end_col],
}

View File

@ -1,3 +0,0 @@
from .call import workflow_call
__all__ = ["workflow_call"]

View File

@ -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

View File

@ -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 的修复建议。

View File

@ -1,4 +0,0 @@
description: Automatically fix lint errors.
help: README.md
steps:
- run: $devchat_python $command_path/main.py

View File

@ -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()

View File

@ -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])

View File

@ -1,4 +0,0 @@
description: 'Generate workflow command, input command information.'
input: required
steps:
- run: $devchat_python $command_path/command.py "$input"

View File

@ -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代码块都要有明确的语言标识如pythonjsonyamlcode等
代码应该使用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()

View File

@ -1,4 +0,0 @@
description: 'Generate workflow command, input command information.'
input: required
steps:
- run: $devchat_python $command_path/command.py "$input"

View File

@ -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")
```

View File

@ -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")}
"""

View File

@ -1,3 +0,0 @@
from lib.ide_service import IDEService
IDEService().ide_logging("debug", "Hello IDE Service!")

View File

@ -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.

View File

@ -1,35 +1,11 @@
### comments
### 操作指南
这个命令用于自动为选中的代码块生成注释。
#### 用途
- 为选中的代码块快速添加解释性注释
- 提高代码可读性和可维护性
- 帮助开发者更好地理解代码逻辑
#### 使用方法
1. 在IDE中选中需要添加注释的代码块
2. 执行以下命令之一:
- 输入 `/comments` 并回车
#### 注意事项
1. 确保在执行命令前已选中代码块
2. 生成的注释会插入到相应的代码行之前
3. 原有的注释会被保留
4. 代码本身不会被修改,只会添加注释
#### 操作流程
1. 选中需要注释的代码块
2. 执行comments命令
3. 等待注释生成完成
4. 自动弹出Diff View您可以选择接受或拒绝修改
额外信息
注释的语言会根据当前IDE的语言设置自动调整
对于中文环境,会生成中文注释
该命令利用AI技术生成注释可能需要一定的处理时间
生成行间注释,请按照如下步骤操作:
1. 选中行间代码。
2. 输入\/comments回车发送或右键点击**DevChat: Generate Comments**按钮。
3. 开始生成行间注释,等待生成结束。
4. 自动弹出Diff View选择是否接受修改。
如图所示:

View File

@ -259,8 +259,6 @@ def main():
code_text = selected_text.get("text", "")
response = add_comments(selected_text=code_text, file_path=file_path)
if not response:
sys.exit(1)
new_code = extract_markdown_block(response)
if not new_code:

View File

@ -1,6 +1,5 @@
description: 'Writes a well-formatted commit message for selected code changes and commits them via Git. Include an issue number if desired (e.g., input "/commit to close #12").'
hint: to close Issue #issue_number
input: optional
help: README.md
steps:
- run: $devchat_python $command_path/commit.py "$input" "english"
- run: $devchat_python $command_path/commit.py "$input"

View File

@ -4,23 +4,12 @@ import re
import subprocess
import sys
# from llm_api import chat_completion_stream # noqa: E402
from devchat.llm import chat_completion_stream
from lib.chatmark import Button, Checkbox, Form, TextEditor
from lib.chatmark import Checkbox, Form, TextEditor
from lib.ide_service import IDEService
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
from common_util import assert_exit # noqa: E402
from git_api import (
get_github_repo,
get_github_repo_issues,
get_github_username,
get_issue_info,
subprocess_check_output,
subprocess_run,
)
diff_too_large_message_en = (
"Commit failed. The modified content is too long "
"and exceeds the model's length limit. "
@ -35,6 +24,19 @@ diff_too_large_message_zh = (
COMMIT_PROMPT_LIMIT_SIZE = 20000
def _T(en_text, zh_text):
"""
Returns a text in the current language.
:param en_text: The English version of the text
:param zh_text: The Chinese version of the text
:return: The text in the current language
"""
if IDEService().ide_language() == "zh":
return zh_text
else:
return en_text
def extract_markdown_block(text):
"""
Extracts the first Markdown code block from the given text without the language specifier.
@ -76,20 +78,19 @@ def read_prompt_from_file(filename):
- FileNotFoundError: If the file does not exist.
- Exception: If any other error occurs during file reading.
"""
s = IDEService()
try:
with open(filename, "r", encoding="utf-8") as file:
return file.read().strip()
except FileNotFoundError:
IDEService().ide_logging(
"error",
s.ide_logging(
"info",
f"File {filename} not found. "
"Please make sure it exists in the same directory as the script.",
)
sys.exit(1)
except Exception as e:
IDEService().ide_logging(
"error", f"An error occurred while reading the file {filename}: {e}"
)
s.ide_logging("info", f"An error occurred while reading the file {filename}: {e}")
sys.exit(1)
@ -144,7 +145,7 @@ def get_modified_files():
tuple: 包含两个list的元组第一个list包含当前修改过的文件第二个list包含已经staged的文件
"""
""" 获取当前修改文件列表以及已经staged的文件列表"""
output = subprocess_check_output(["git", "status", "-s", "-u"], text=True, encoding="utf-8")
output = subprocess.check_output(["git", "status", "-s", "-u"], text=True, encoding="utf-8")
lines = output.split("\n")
modified_files = []
staged_files = []
@ -161,9 +162,9 @@ def get_modified_files():
# check wether filename is a directory
if os.path.isdir(filename):
continue
modified_files.append(os.path.normpath(strip_file_name(filename)))
if status == "M " or status == "A " or status == "D ":
staged_files.append(os.path.normpath(strip_file_name(filename)))
modified_files.append((os.path.normpath(strip_file_name(filename)), status[1:2]))
if status[0:1] == "M" or status[0:1] == "A" or status[0:1] == "D":
staged_files.append((os.path.normpath(strip_file_name(filename)), status[0:1]))
return modified_files, staged_files
@ -179,10 +180,14 @@ def get_marked_files(modified_files, staged_files):
List[str]: 用户选中的文件列表
"""
# Create two Checkbox instances for staged and unstaged files
staged_checkbox = Checkbox(staged_files, [True] * len(staged_files))
staged_files_show = [f'{file[1] if file[1]!="?" else "U"} {file[0]}' for file in staged_files]
staged_checkbox = Checkbox(staged_files_show, [True] * len(staged_files_show))
unstaged_files = [file for file in modified_files if file not in staged_files]
unstaged_checkbox = Checkbox(unstaged_files, [False] * len(unstaged_files))
unstaged_files = [file for file in modified_files if file[1].strip() != ""]
unstaged_files_show = [
f'{file[1] if file[1]!="?" else "U"} {file[0]}' for file in unstaged_files
]
unstaged_checkbox = Checkbox(unstaged_files_show, [False] * len(unstaged_files_show))
# Create a Form with both Checkbox instances
form_list = []
@ -202,31 +207,37 @@ def get_marked_files(modified_files, staged_files):
# Retrieve the selected files from both Checkbox instances
staged_checkbox_selections = staged_checkbox.selections if staged_checkbox.selections else []
unstaged_selections = unstaged_checkbox.selections if unstaged_checkbox.selections else []
selected_staged_files = [staged_files[idx] for idx in staged_checkbox_selections]
selected_unstaged_files = [unstaged_files[idx] for idx in unstaged_selections]
selected_staged_files = [staged_files[idx][0] for idx in staged_checkbox_selections]
selected_unstaged_files = [unstaged_files[idx][0] for idx in unstaged_selections]
# Combine the selections from both checkboxes
selected_files = selected_staged_files + selected_unstaged_files
return selected_files
return selected_staged_files, selected_unstaged_files
def rebuild_stage_list(user_files):
def rebuild_stage_list(staged_select_files, unstaged_select_files):
"""
根据用户选中文件重新构建stage列表
Args:
user_files: 用户选中的文件列表
staged_select_files: 当前选中的已staged文件列表
unstaged_select_files: 当前选中的未staged文件列表
Returns:
None
"""
# Unstage all files
subprocess_check_output(["git", "reset"])
# Stage all user_files
for file in user_files:
subprocess_run(["git", "add", file])
# 获取当前所有staged文件
current_staged_files = subprocess.check_output(
["git", "diff", "--name-only", "--cached"], text=True
).splitlines()
# 添加unstaged_select_files中的文件到staged
for file in unstaged_select_files:
subprocess.check_output(["git", "add", file])
# 将不在staged_select_files中的文件从staged移除
user_selected_files = staged_select_files + unstaged_select_files
files_to_unstage = [file for file in current_staged_files if file not in user_selected_files]
for file in files_to_unstage:
subprocess.check_output(["git", "reset", file])
def get_diff():
@ -240,13 +251,13 @@ def get_diff():
bytes: 返回bytes类型是git diff --cached命令的输出结果
"""
return subprocess_check_output(["git", "diff", "--cached"])
return subprocess.check_output(["git", "diff", "--cached"])
def get_current_branch():
try:
# 使用git命令获取当前分支名称
result = subprocess_check_output(
result = subprocess.check_output(
["git", "branch", "--show-current"], stderr=subprocess.STDOUT
).strip()
# 将结果从bytes转换为str
@ -260,7 +271,7 @@ def get_current_branch():
return None
def generate_commit_message_base_diff(user_input, diff, issue):
def generate_commit_message_base_diff(user_input, diff):
"""
根据diff信息通过AI生成一个commit消息
@ -274,10 +285,8 @@ def generate_commit_message_base_diff(user_input, diff, issue):
"""
global language
language_prompt = "You must response commit message in chinese。\n" if language == "zh" else ""
prompt = (
PROMPT_COMMIT_MESSAGE_BY_DIFF_USER_INPUT.replace("{__DIFF__}", f"{diff}")
.replace("{__USER_INPUT__}", f"{user_input + language_prompt}")
.replace("{__ISSUE__}", f"{issue}")
prompt = PROMPT_COMMIT_MESSAGE_BY_DIFF_USER_INPUT.replace("{__DIFF__}", f"{diff}").replace(
"{__USER_INPUT__}", f"{user_input + language_prompt}"
)
model_token_limit_error = (
@ -293,7 +302,7 @@ def generate_commit_message_base_diff(user_input, diff, issue):
if (
not response["content"]
and response.get("error", None)
and f"{response['error']}".find("This model's maximum context length is") > 0
and f'{response["error"]}'.find("This model's maximum context length is") > 0
):
print(model_token_limit_error)
sys.exit(0)
@ -320,27 +329,7 @@ def display_commit_message_and_commit(commit_message):
new_commit_message = text_editor.new_text
if not new_commit_message:
return None
return subprocess_check_output(["git", "commit", "-m", new_commit_message])
def extract_issue_id(branch_name):
if "#" in branch_name:
return branch_name.split("#")[-1]
return None
def get_issue_json(issue_id):
issue = {"id": "no issue id", "title": "", "body": ""}
if issue_id:
issue = get_issue_info(issue_id)
assert_exit(not issue, "Failed to retrieve issue with ID: {issue_id}", exit_code=-1)
issue = {
"id": issue_id,
"html_url": issue["html_url"],
"title": issue["title"],
"body": issue["body"],
}
return issue
return subprocess.check_output(["git", "commit", "-m", new_commit_message])
def check_git_installed():
@ -362,142 +351,56 @@ def check_git_installed():
return False
def ask_for_push():
"""
询问用户是否要推送(push)更改到远程仓库
Returns:
bool: 用户是否选择推送
"""
print(
"Step 3/3: Would you like to push your commit to the remote repository?",
end="\n\n",
flush=True,
)
button = Button(["Yes, push now", "No, I'll push later"])
button.render()
return button.clicked == 0 # 如果用户点击第一个按钮(Yes)则返回True
def push_changes():
"""
推送更改到远程仓库
Returns:
bool: 推送是否成功
"""
try:
current_branch = get_current_branch()
if not current_branch:
print(
"Could not determine current branch. Push failed.",
end="\n\n",
file=sys.stderr,
flush=True,
)
return False
print(f"Pushing changes to origin/{current_branch}...", end="\n\n", flush=True)
result = subprocess_run(
["git", "push", "origin", current_branch],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
if result.returncode != 0:
print(f"Push failed: {result.stderr}", end="\n\n", flush=True)
return False
print("Push completed successfully.", end="\n\n", flush=True)
return True
except subprocess.CalledProcessError as e:
print(f"Push failed: {str(e)}", end="\n\n", file=sys.stderr, flush=True)
return False
except Exception as e:
print(
f"An unexpected error occurred: {str(e)}",
end="\n\n",
file=sys.stderr,
flush=True,
)
return False
def get_selected_issue_ids():
"""
获取用户选中的issue id
Returns:
list: 用户选中的issue id列表
"""
name = get_github_username()
issue_repo = get_github_repo(True)
issues = get_github_repo_issues(issue_repo, assignee=name, state="open")
if issues:
checkbox = Checkbox(
[f"#{issue['number']}: {issue['title']}" for issue in issues],
title="Select the issues you want to close",
)
checkbox.render()
return [issues[idx]["number"] for idx in checkbox.selections]
def main():
global language
try:
print("Let's follow the steps below.\n\n")
start_msg = _T("Let's follow the steps below.\n\n", "开始按步骤操作。\n\n")
print(start_msg)
# Ensure enough command line arguments are provided
if len(sys.argv) < 2:
print(
"Usage: python script.py <user_input> <language>",
file=sys.stderr,
flush=True,
)
print("Usage: python script.py <user_input>", file=sys.stderr, flush=True)
sys.exit(-1)
user_input = sys.argv[1]
language = "english"
if len(sys.argv) > 2:
language = sys.argv[2]
language = IDEService().ide_language()
if not check_git_installed():
sys.exit(-1)
print(
"Step 1/3: Select the files you've changed that you wish to include in this commit, "
step1_msg = _T(
"Step 1/2: Select the files you've changed that you wish to include in this commit, "
"then click 'Submit'.",
end="\n\n",
flush=True,
"第一步/2选择您希望包含在这次提交中的文件然后点击“提交”。",
)
print(step1_msg, end="\n\n", flush=True)
modified_files, staged_files = get_modified_files()
if len(modified_files) == 0:
print("No files to commit.", file=sys.stderr, flush=True)
sys.exit(-1)
print("There are no files to commit.", flush=True)
sys.exit(0)
selected_files = get_marked_files(modified_files, staged_files)
if not selected_files:
print("No files selected, commit aborted.")
staged_select_files, unstaged_select_files = get_marked_files(modified_files, staged_files)
if not staged_select_files and not unstaged_select_files:
no_files_msg = _T(
"No files selected, the commit has been aborted.",
"没有选择任何文件,提交已中止。",
)
print(no_files_msg)
return
rebuild_stage_list(selected_files)
rebuild_stage_list(staged_select_files, unstaged_select_files)
print(
"Step 2/3: Review the commit message I've drafted for you. "
step2_msg = _T(
"Step 2/2: Review the commit message I've drafted for you. "
"Edit it below if needed. Then click 'Commit' to proceed with "
"the commit using this message.",
end="\n\n",
flush=True,
"第二步/2查看我为您起草的提交消息。如果需要请在下面编辑它。然后单击“提交”以使用此消息进行提交。",
)
print(step2_msg, end="\n\n", flush=True)
diff = get_diff()
branch_name = get_current_branch()
issue_id = extract_issue_id(branch_name)
issue = str(get_issue_json(issue_id))
if branch_name:
user_input += "\ncurrent repo branch name is:" + branch_name
commit_message = generate_commit_message_base_diff(user_input, diff, issue)
commit_message = generate_commit_message_base_diff(user_input, diff)
# TODO
# remove Closes #IssueNumber in commit message
@ -507,28 +410,20 @@ def main():
.replace("No specific issue to close", "")
.replace("No specific issue mentioned.", "")
)
# add closes #IssueNumber in commit message from issues from user selected
issue_ids = get_selected_issue_ids()
if issue_ids:
issue_repo = get_github_repo(True)
owner_repo = get_github_repo()
closes_issue_contents = []
for issue_id in issue_ids:
closes_issue_contents.append(
f"#{issue_id}" if owner_repo == issue_repo else f"{issue_repo}#{issue_id}"
)
commit_message["content"] += f"\n\nCloses {', '.join(closes_issue_contents)}"
commit_result = display_commit_message_and_commit(commit_message["content"])
if not commit_result:
print("Commit aborted.", flush=True)
commit_abort_msg = _T(
"Commit aborted.",
"提交已中止。",
)
print(commit_abort_msg, flush=True)
else:
# 添加推送步骤
if ask_for_push():
if not push_changes():
print("Push failed.", flush=True)
sys.exit(-1)
print("Commit completed.", flush=True)
commit_completed_msg = _T(
"Commit completed.",
"提交已完成。",
)
print(commit_completed_msg, flush=True)
sys.exit(0)
except Exception as err:
print("Exception:", err, file=sys.stderr, flush=True)

View File

@ -6,21 +6,23 @@ Objective:** Generate a commit message that succinctly describes the codebase ch
3. **Closing Reference (Conditional):** Include the line `Closes #IssueNumber` only if a specific, relevant issue number has been mentioned in the user input.
**Response Format:**
Response should be in the following markdown codeblock format:
```commit
```
type: Title
- Detail message line 1
- Detail message line 2
- Detail message line 3
Closes #IssueNumber
```
Only output the commit message codeblock, don't include any other text.
Only append the \"Closes #IssueNumber\" if the user input explicitly references an issue to close.
**Constraints:**
- Exclude markdown code block indicators (```) and the placeholder \"commit_message\" from your response.
- Follow commit message best practices:
- Limit the title length to 50 characters.
- Limit each summary line to 72 characters.
- If the precise issue number is not known or not stated by the user, do not include the closing reference.
**User Input:** `{__USER_INPUT__}`
@ -31,15 +33,5 @@ Determine if `{__USER_INPUT__}` contains a reference to closing an issue. If so,
{__DIFF__}
```
Related issue:
{__ISSUE__}
Utilize the provided format to craft a commit message that adheres to the stipulated criteria.
example output:
```commit
feature: add update user info API
- add post method api /user/update
- implement update user info logic
```

File diff suppressed because one or more lines are too long

View File

@ -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