codespector 0.1.2__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {codespector-0.1.2 → codespector-0.2.0}/PKG-INFO +1 -2
- codespector-0.2.0/cicd_examples/.github-ci.yml +64 -0
- codespector-0.2.0/cicd_examples/.gitlab-ci.yml +33 -0
- codespector-0.2.0/codespector/base.py +7 -0
- codespector-0.2.0/codespector/codespector.py +84 -0
- codespector-0.2.0/codespector/errors.py +14 -0
- codespector-0.1.2/codespector/local/prepare.py → codespector-0.2.0/codespector/local.py +10 -11
- {codespector-0.1.2 → codespector-0.2.0}/codespector/main.py +19 -24
- codespector-0.2.0/codespector/reviewer.py +84 -0
- codespector-0.2.0/codespector/types.py +50 -0
- {codespector-0.1.2 → codespector-0.2.0}/pyproject.toml +1 -2
- codespector-0.2.0/tests/unit/test_codespector.py +78 -0
- {codespector-0.1.2 → codespector-0.2.0}/uv.lock +83 -27
- codespector-0.1.2/codespector/clients/codestral.py +0 -5
- codespector-0.1.2/codespector/controller.py +0 -44
- codespector-0.1.2/codespector/local/__init__.py +0 -1
- codespector-0.1.2/codespector/local/main.py +0 -35
- codespector-0.1.2/codespector/local/reviewer.py +0 -108
- codespector-0.1.2/tests/unit/__init__.py +0 -0
- codespector-0.1.2/tests/unit/test_prepare.py +0 -26
- codespector-0.1.2/tests/unit/test_reviewer.py +0 -74
- {codespector-0.1.2 → codespector-0.2.0}/.github/workflows/build-publish.yml +0 -0
- {codespector-0.1.2 → codespector-0.2.0}/.github/workflows/tests.yml +0 -0
- {codespector-0.1.2 → codespector-0.2.0}/.gitignore +0 -0
- {codespector-0.1.2 → codespector-0.2.0}/LICENSE +0 -0
- {codespector-0.1.2 → codespector-0.2.0}/Makefile +0 -0
- {codespector-0.1.2 → codespector-0.2.0}/README.md +0 -0
- {codespector-0.1.2 → codespector-0.2.0}/codespector/__init__.py +0 -0
- {codespector-0.1.2/codespector/clients → codespector-0.2.0/tests}/__init__.py +0 -0
- {codespector-0.1.2 → codespector-0.2.0}/tests/conftest.py +0 -0
- {codespector-0.1.2/tests → codespector-0.2.0/tests/unit}/__init__.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: codespector
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.2.0
|
4
4
|
Summary: Assistant for reviewing your code
|
5
5
|
Project-URL: Repository, https://github.com/Vladimir-Titov/codespector
|
6
6
|
Project-URL: Issues, https://github.com/Vladimir-Titov/codespector/issues
|
@@ -15,7 +15,6 @@ Requires-Dist: click>=8.1.8
|
|
15
15
|
Requires-Dist: environs>=14.1.1
|
16
16
|
Requires-Dist: loguru>=0.7.3
|
17
17
|
Requires-Dist: requests>=2.32.3
|
18
|
-
Requires-Dist: ujson>=5.10.0
|
19
18
|
Description-Content-Type: text/markdown
|
20
19
|
|
21
20
|
# CodeSpector
|
@@ -0,0 +1,64 @@
|
|
1
|
+
name: Codespector Review
|
2
|
+
|
3
|
+
on:
|
4
|
+
pull_request:
|
5
|
+
types: [opened, synchronize]
|
6
|
+
|
7
|
+
env:
|
8
|
+
CODESPECTOR_RESULT_FILE: "codespector/result.md"
|
9
|
+
CODESPECTOR_COMBINED_FILE: "codespector/combined.json"
|
10
|
+
|
11
|
+
jobs:
|
12
|
+
codespector:
|
13
|
+
runs-on: ubuntu-latest
|
14
|
+
|
15
|
+
steps:
|
16
|
+
- uses: actions/checkout@v4
|
17
|
+
with:
|
18
|
+
fetch-depth: 0
|
19
|
+
|
20
|
+
- name: Set up Python
|
21
|
+
uses: actions/setup-python@v5
|
22
|
+
with:
|
23
|
+
python-version: '3.12'
|
24
|
+
|
25
|
+
- name: Install dependencies
|
26
|
+
run: |
|
27
|
+
sudo apt-get update
|
28
|
+
sudo apt-get install -y jq
|
29
|
+
pip install codespector
|
30
|
+
|
31
|
+
- name: Fetch target branch
|
32
|
+
run: git fetch origin ${{ github.base_ref }}
|
33
|
+
|
34
|
+
- name: Run Codespector
|
35
|
+
env:
|
36
|
+
CODESPECTOR_CHAT_TOKEN: ${{ secrets.CODESPECTOR_CHAT_TOKEN }}
|
37
|
+
CODESPECTOR_SYSTEM_CONTENT: ${{ secrets.CODESPECTOR_SYSTEM_CONTENT }}
|
38
|
+
CODESPECTOR_PROMPT_CONTENT: ${{ secrets.CODESPECTOR_PROMPT_CONTENT }}
|
39
|
+
CODESPECTOR_RESULT_FILE: ${{ secrets.CODESPECTOR_RESULT_FILE }}
|
40
|
+
CODESPECTOR_CHAT_AGENT: ${{ secrets.CODESPECTOR_CHAT_AGENT }}
|
41
|
+
run: |
|
42
|
+
codespector \
|
43
|
+
--chat-token $CODESPECTOR_CHAT_TOKEN \
|
44
|
+
--chat-agent $CODESPECTOR_CHAT_AGENT \
|
45
|
+
--compare-branch origin/${{ github.base_ref }}...HEAD
|
46
|
+
--system-content $CODESPECTOR_SYSTEM_CONTENT
|
47
|
+
--prompt-content $CODESPECTOR_PROMPT_CONTENT
|
48
|
+
--result-file $CODESPECTOR_RESULT_FILE
|
49
|
+
|
50
|
+
- name: Post review comment
|
51
|
+
env:
|
52
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
53
|
+
run: |
|
54
|
+
COMMENT_CONTENT=$(cat ${CODESPECTOR_RESULT_FILE} | jq -Rs .)
|
55
|
+
gh pr comment ${{ github.event.pull_request.number }} --body "${COMMENT_CONTENT}"
|
56
|
+
|
57
|
+
- name: Upload artifacts
|
58
|
+
uses: actions/upload-artifact@v4
|
59
|
+
with:
|
60
|
+
name: codespector-results
|
61
|
+
path: |
|
62
|
+
${{ env.CODESPECTOR_RESULT_FILE }}
|
63
|
+
${{ env.CODESPECTOR_COMBINED_FILE }}
|
64
|
+
retention-days: 1
|
@@ -0,0 +1,33 @@
|
|
1
|
+
workflow:
|
2
|
+
rules:
|
3
|
+
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
|
4
|
+
|
5
|
+
variables:
|
6
|
+
GIT_DEPTH: 0
|
7
|
+
CODESPECTOR_RESULT_FILE: "codespector/result.md"
|
8
|
+
CODESPECTOR_COMBINED_FILE: "codespector/combined.json"
|
9
|
+
|
10
|
+
stages:
|
11
|
+
- codespector
|
12
|
+
|
13
|
+
codespector-job:
|
14
|
+
stage: codespector
|
15
|
+
image: python:3.12
|
16
|
+
script:
|
17
|
+
- apt-get update && apt-get install -y jq
|
18
|
+
- git fetch --no-tags origin $CI_MERGE_REQUEST_TARGET_BRANCH_NAME
|
19
|
+
- pip install codespector
|
20
|
+
- codespector --chat-token $CODESPECTOR_CHAT_TOKEN --chat-agent codestral --compare-branch origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME...HEAD
|
21
|
+
- |
|
22
|
+
COMMENT_CONTENT=$(cat ${CODESPECTOR_RESULT_FILE} | jq -Rs .)
|
23
|
+
echo "Posting comment to MR..."
|
24
|
+
curl -X POST \
|
25
|
+
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
|
26
|
+
--header "Content-Type: application/json" \
|
27
|
+
--data "{\"body\": ${COMMENT_CONTENT}}" \
|
28
|
+
"$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes"
|
29
|
+
artifacts:
|
30
|
+
paths:
|
31
|
+
- ${CODESPECTOR_RESULT_FILE}
|
32
|
+
- ${CODESPECTOR_COMBINED_FILE}
|
33
|
+
expire_in: 10 minutes
|
@@ -0,0 +1,84 @@
|
|
1
|
+
from codespector.base import BasePipe
|
2
|
+
from codespector.local import CodeSpectorDataPreparer
|
3
|
+
from codespector.reviewer import CodeSpectorReviewer
|
4
|
+
from codespector.types import AgentInfo
|
5
|
+
|
6
|
+
|
7
|
+
class CodeSpector:
|
8
|
+
def __init__(
|
9
|
+
self,
|
10
|
+
chat_token: str,
|
11
|
+
chat_agent: str,
|
12
|
+
compare_branch: str,
|
13
|
+
system_content: str,
|
14
|
+
prompt_content: str,
|
15
|
+
data_preparer: CodeSpectorDataPreparer,
|
16
|
+
reviewer: CodeSpectorReviewer,
|
17
|
+
pipeline: list[BasePipe],
|
18
|
+
result_file: str,
|
19
|
+
output_dir: str,
|
20
|
+
chat_model: str | None = None,
|
21
|
+
):
|
22
|
+
self.chat_token = chat_token
|
23
|
+
self.chat_agent = chat_agent
|
24
|
+
self.compare_branch = compare_branch
|
25
|
+
self.output_dir = output_dir
|
26
|
+
self.system_content = system_content
|
27
|
+
self.prompt_content = prompt_content
|
28
|
+
self.data_preparer = data_preparer
|
29
|
+
self.reviewer = reviewer
|
30
|
+
self.pipeline = pipeline
|
31
|
+
self.result_file = result_file
|
32
|
+
self.chat_model = chat_model
|
33
|
+
|
34
|
+
@classmethod
|
35
|
+
def create(
|
36
|
+
cls,
|
37
|
+
chat_token: str,
|
38
|
+
chat_agent: str,
|
39
|
+
compare_branch: str,
|
40
|
+
system_content: str,
|
41
|
+
prompt_content: str,
|
42
|
+
result_file: str,
|
43
|
+
output_dir: str,
|
44
|
+
chat_model: str | None = None,
|
45
|
+
) -> 'CodeSpector':
|
46
|
+
agent_info = AgentInfo.create(
|
47
|
+
chat_agent=chat_agent,
|
48
|
+
chat_token=chat_token,
|
49
|
+
chat_model=chat_model,
|
50
|
+
)
|
51
|
+
data_preparer = CodeSpectorDataPreparer(
|
52
|
+
output_dir=output_dir,
|
53
|
+
compare_branch=compare_branch,
|
54
|
+
)
|
55
|
+
reviewer = CodeSpectorReviewer(
|
56
|
+
diff_file=data_preparer.combined_file,
|
57
|
+
chat_token=chat_token,
|
58
|
+
chat_agent=chat_agent,
|
59
|
+
system_content=system_content,
|
60
|
+
output_dir=output_dir,
|
61
|
+
chat_model=chat_model,
|
62
|
+
agent_info=agent_info,
|
63
|
+
prompt_content=prompt_content,
|
64
|
+
result_file=result_file,
|
65
|
+
)
|
66
|
+
pipeline = [data_preparer, reviewer]
|
67
|
+
|
68
|
+
return CodeSpector(
|
69
|
+
chat_token=chat_token,
|
70
|
+
chat_agent=chat_agent,
|
71
|
+
chat_model=chat_model,
|
72
|
+
compare_branch=compare_branch,
|
73
|
+
system_content=system_content,
|
74
|
+
prompt_content=prompt_content,
|
75
|
+
data_preparer=data_preparer,
|
76
|
+
reviewer=reviewer,
|
77
|
+
pipeline=pipeline,
|
78
|
+
output_dir=output_dir,
|
79
|
+
result_file=result_file,
|
80
|
+
)
|
81
|
+
|
82
|
+
def review(self):
|
83
|
+
for pipe in self.pipeline:
|
84
|
+
pipe.start()
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class NotValidCfgError(Exception):
|
2
|
+
"""Exception raised when the configuration is not valid."""
|
3
|
+
|
4
|
+
def __init__(self, message):
|
5
|
+
super().__init__(message)
|
6
|
+
self.message = message
|
7
|
+
|
8
|
+
|
9
|
+
class AppError(Exception):
|
10
|
+
"""Exception raised for application errors."""
|
11
|
+
|
12
|
+
def __init__(self, message):
|
13
|
+
super().__init__(message)
|
14
|
+
self.message = message
|
@@ -1,9 +1,11 @@
|
|
1
|
-
import
|
1
|
+
import json
|
2
2
|
import subprocess
|
3
3
|
import os
|
4
4
|
|
5
|
+
from codespector.base import BasePipe
|
5
6
|
|
6
|
-
|
7
|
+
|
8
|
+
class CodeSpectorDataPreparer(BasePipe):
|
7
9
|
def __init__(
|
8
10
|
self,
|
9
11
|
output_dir: str,
|
@@ -38,18 +40,18 @@ class CodeSpectorDataPreparer:
|
|
38
40
|
diff_json = {'diff': '\n'.join(filtered_diff)}
|
39
41
|
diff_filepath = os.path.join(self.output_dir, self.diff_file)
|
40
42
|
with open(diff_filepath, 'w', encoding='utf-8') as f:
|
41
|
-
|
43
|
+
json.dump(diff_json, f, indent=4, ensure_ascii=False)
|
42
44
|
|
43
45
|
with open(os.path.join(self.output_dir, self.original_files_tmp), 'r', encoding='utf-8') as f:
|
44
|
-
original_files_data =
|
46
|
+
original_files_data = json.load(f)
|
45
47
|
|
46
48
|
with open(diff_filepath, 'r', encoding='utf-8') as f:
|
47
|
-
diff_data =
|
49
|
+
diff_data = json.load(f)
|
48
50
|
|
49
51
|
combined_data = {**original_files_data, **diff_data}
|
50
52
|
|
51
53
|
with open(os.path.join(self.output_dir, self.combined_file), 'w', encoding='utf-8') as f:
|
52
|
-
|
54
|
+
json.dump(combined_data, f, indent=4, ensure_ascii=False)
|
53
55
|
|
54
56
|
def _prepare_name_only_file(self):
|
55
57
|
changed_files = subprocess.run(
|
@@ -70,12 +72,9 @@ class CodeSpectorDataPreparer:
|
|
70
72
|
filepath = os.path.join(self.output_dir, self.original_files_tmp)
|
71
73
|
|
72
74
|
with open(filepath, 'w', encoding='utf-8') as f:
|
73
|
-
|
75
|
+
json.dump(result, f, indent=4, ensure_ascii=False)
|
74
76
|
|
75
|
-
def
|
77
|
+
def start(self):
|
76
78
|
self._prepare_dir()
|
77
79
|
self._prepare_name_only_file()
|
78
80
|
self._prepare_diff_file()
|
79
|
-
|
80
|
-
def start(self):
|
81
|
-
self.prepare_data()
|
@@ -3,8 +3,7 @@ from pathlib import Path
|
|
3
3
|
import click
|
4
4
|
from environs import Env
|
5
5
|
|
6
|
-
from .
|
7
|
-
from loguru import logger
|
6
|
+
from codespector.codespector import CodeSpector
|
8
7
|
|
9
8
|
BASE_PATH = Path(__file__).parent.parent
|
10
9
|
|
@@ -12,10 +11,23 @@ env = Env()
|
|
12
11
|
env.read_env(path=str(BASE_PATH / '.env'))
|
13
12
|
|
14
13
|
|
14
|
+
@click.option(
|
15
|
+
'--result-file',
|
16
|
+
type=str,
|
17
|
+
help='Set file for saving the result',
|
18
|
+
envvar='CODESPECTOR_RESULT_FILE',
|
19
|
+
show_envvar=True,
|
20
|
+
)
|
21
|
+
@click.option(
|
22
|
+
'--prompt-content',
|
23
|
+
type=str,
|
24
|
+
help='Prompt content which included to review prompt',
|
25
|
+
envvar='CODESPECTOR_PROMPT_CONTENT',
|
26
|
+
show_envvar=True,
|
27
|
+
)
|
15
28
|
@click.option(
|
16
29
|
'--system-content',
|
17
30
|
type=str,
|
18
|
-
default='Ты код ревьювер. Отвечай на русском языке.',
|
19
31
|
envvar='CODESPECTOR_SYSTEM_CONTENT',
|
20
32
|
show_envvar=True,
|
21
33
|
help='Content which used in system field for agent',
|
@@ -23,8 +35,8 @@ env.read_env(path=str(BASE_PATH / '.env'))
|
|
23
35
|
@click.option(
|
24
36
|
'--output-dir',
|
25
37
|
type=str,
|
26
|
-
default='codespector',
|
27
38
|
envvar='CODESPECTOR_OUTPUT_DIR',
|
39
|
+
default='codespector',
|
28
40
|
show_envvar=True,
|
29
41
|
help='Select the output directory',
|
30
42
|
)
|
@@ -32,16 +44,13 @@ env.read_env(path=str(BASE_PATH / '.env'))
|
|
32
44
|
'-b',
|
33
45
|
'--compare-branch',
|
34
46
|
type=str,
|
35
|
-
default='develop',
|
36
47
|
help='Select the branch to compare the current one with',
|
37
48
|
)
|
38
49
|
@click.option(
|
39
50
|
'--chat-agent',
|
40
|
-
type=click.Choice(['codestral', 'chatgpt'], case_sensitive=False),
|
41
51
|
envvar='CODESPECTOR_CHAT_AGENT',
|
42
52
|
show_envvar=True,
|
43
|
-
|
44
|
-
help='Choose the chat agent to use',
|
53
|
+
help='Choose the chat agent to use you can use one from [codestral, chatgpr, deepseek]. Or set yours chat agent',
|
45
54
|
)
|
46
55
|
@click.option(
|
47
56
|
'--chat-model',
|
@@ -56,25 +65,11 @@ env.read_env(path=str(BASE_PATH / '.env'))
|
|
56
65
|
envvar='CODESPECTOR_CHAT_TOKEN',
|
57
66
|
show_envvar=True,
|
58
67
|
)
|
59
|
-
@click.option(
|
60
|
-
'--mode',
|
61
|
-
type=click.Choice(['local'], case_sensitive=False),
|
62
|
-
default='local',
|
63
|
-
help='Choose the mode of the application',
|
64
|
-
)
|
65
68
|
@click.version_option(message='%(version)s')
|
66
69
|
@click.command()
|
67
70
|
def main(*args, **kwargs):
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
def start(*args, **kwargs):
|
72
|
-
codespector = CodeSpectorController(*args, **kwargs)
|
73
|
-
try:
|
74
|
-
codespector.start()
|
75
|
-
logger.info('Review completed successfully.See result.txt in {} directory', kwargs['output_dir'])
|
76
|
-
except Exception as e:
|
77
|
-
logger.error('Error while review: {}', e)
|
71
|
+
codespector = CodeSpector.create(*args, **kwargs)
|
72
|
+
codespector.review()
|
78
73
|
|
79
74
|
|
80
75
|
if __name__ == '__main__':
|
@@ -0,0 +1,84 @@
|
|
1
|
+
import os.path
|
2
|
+
|
3
|
+
import json
|
4
|
+
from urllib.error import HTTPError
|
5
|
+
|
6
|
+
import requests
|
7
|
+
|
8
|
+
from loguru import logger
|
9
|
+
|
10
|
+
from codespector.errors import AppError
|
11
|
+
from codespector.types import AgentInfo
|
12
|
+
|
13
|
+
|
14
|
+
class CodeSpectorReviewer:
|
15
|
+
def __init__(
|
16
|
+
self,
|
17
|
+
diff_file: str,
|
18
|
+
chat_token: str,
|
19
|
+
chat_agent: str,
|
20
|
+
chat_model: str,
|
21
|
+
system_content: str,
|
22
|
+
prompt_content: str,
|
23
|
+
output_dir: str,
|
24
|
+
result_file: str,
|
25
|
+
agent_info: AgentInfo,
|
26
|
+
):
|
27
|
+
self.diff_file = diff_file
|
28
|
+
self.chat_token = chat_token
|
29
|
+
self.chat_agent = chat_agent
|
30
|
+
self.chat_model = chat_model
|
31
|
+
self.system_content = system_content
|
32
|
+
self.prompt_content = prompt_content
|
33
|
+
self.output_dir = output_dir
|
34
|
+
self.result_file = result_file
|
35
|
+
self.agent_info = agent_info
|
36
|
+
|
37
|
+
self.request_file = 'request.json'
|
38
|
+
self.response_file = 'response.json'
|
39
|
+
|
40
|
+
def _request_to_chat_agent(self, prompt: str):
|
41
|
+
request_data = {
|
42
|
+
'model': self.agent_info.model,
|
43
|
+
'messages': [{'role': 'system', 'content': self.system_content}, {'role': 'user', 'content': prompt}],
|
44
|
+
}
|
45
|
+
|
46
|
+
with open(os.path.join(self.output_dir, self.request_file), 'w', encoding='utf-8') as f:
|
47
|
+
json.dump(request_data, f, indent=4, ensure_ascii=False)
|
48
|
+
|
49
|
+
response = requests.post(
|
50
|
+
self.agent_info.url,
|
51
|
+
json=request_data,
|
52
|
+
headers=self.agent_info.headers,
|
53
|
+
timeout=100,
|
54
|
+
)
|
55
|
+
response.raise_for_status()
|
56
|
+
return response
|
57
|
+
|
58
|
+
def send_to_review(self):
|
59
|
+
with open(os.path.join(self.output_dir, self.diff_file), 'r', encoding='utf-8') as f:
|
60
|
+
diff_data = json.load(f)
|
61
|
+
|
62
|
+
diff_content = diff_data.get('diff', '')
|
63
|
+
original_files = diff_data.get('original files', [])
|
64
|
+
|
65
|
+
original_files_str = json.dumps(original_files, indent=4, ensure_ascii=False)
|
66
|
+
|
67
|
+
prompt = f'{self.prompt_content}\n\nDIFF:\n{diff_content}\n\nORIGINAL FILES:\n{original_files_str}'
|
68
|
+
try:
|
69
|
+
response = self._request_to_chat_agent(prompt=prompt)
|
70
|
+
except HTTPError as e:
|
71
|
+
logger.error('Error while send request: {}', e)
|
72
|
+
raise AppError('Error while send request') from e
|
73
|
+
|
74
|
+
with open(os.path.join(self.output_dir, self.response_file), 'w', encoding='utf-8') as f:
|
75
|
+
json.dump(response.json(), f, indent=4, ensure_ascii=False)
|
76
|
+
|
77
|
+
resp = response.json()
|
78
|
+
clear_response = resp['choices'][0]['message']['content']
|
79
|
+
|
80
|
+
with open(os.path.join(self.output_dir, self.result_file), 'w', encoding='utf-8') as f:
|
81
|
+
f.write(clear_response)
|
82
|
+
|
83
|
+
def start(self):
|
84
|
+
self.send_to_review()
|
@@ -0,0 +1,50 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
from loguru import logger
|
3
|
+
from codespector.errors import NotValidCfgError
|
4
|
+
|
5
|
+
AGENT_URL_MAPPING = {
|
6
|
+
'codestral': 'https://api.mistral.ai/v1/chat/completions',
|
7
|
+
'chatgpt': 'https://api.openai.com/v1/chat/completions',
|
8
|
+
'deepseek': 'https://api.deepseek.com/v1/chat/completions',
|
9
|
+
}
|
10
|
+
|
11
|
+
DEFAULT_AGENT_MODEL = {
|
12
|
+
'codestral': 'codestral-latest',
|
13
|
+
'chatgpt': 'gpt-4o',
|
14
|
+
'deepseek': 'deepseek-chat',
|
15
|
+
}
|
16
|
+
|
17
|
+
|
18
|
+
@dataclass
|
19
|
+
class AgentInfo:
|
20
|
+
model: str
|
21
|
+
url: str
|
22
|
+
headers: dict
|
23
|
+
|
24
|
+
@classmethod
|
25
|
+
def create(
|
26
|
+
cls,
|
27
|
+
chat_agent: str,
|
28
|
+
chat_token: str,
|
29
|
+
chat_model: str | None = None,
|
30
|
+
completion_url: str | None = None,
|
31
|
+
) -> 'AgentInfo':
|
32
|
+
if completion_url:
|
33
|
+
url = completion_url
|
34
|
+
else:
|
35
|
+
url = AGENT_URL_MAPPING.get(chat_agent)
|
36
|
+
|
37
|
+
if chat_model:
|
38
|
+
model = chat_model
|
39
|
+
else:
|
40
|
+
model = DEFAULT_AGENT_MODEL.get(chat_agent)
|
41
|
+
|
42
|
+
if model is None or url is None:
|
43
|
+
raise NotValidCfgError('Invalid chat model or completions url')
|
44
|
+
|
45
|
+
headers = {'Authorization': f'Bearer {chat_token}'}
|
46
|
+
return cls(
|
47
|
+
url=url,
|
48
|
+
model=model,
|
49
|
+
headers=headers,
|
50
|
+
)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "codespector"
|
3
|
-
version = "0.
|
3
|
+
version = "0.2.0"
|
4
4
|
authors = [
|
5
5
|
{ name = "vtitov", email = "v.v.titov94@gmail.com" }
|
6
6
|
]
|
@@ -12,7 +12,6 @@ dependencies = [
|
|
12
12
|
"environs>=14.1.1",
|
13
13
|
"loguru>=0.7.3",
|
14
14
|
"requests>=2.32.3",
|
15
|
-
"ujson>=5.10.0",
|
16
15
|
]
|
17
16
|
|
18
17
|
classifiers = [
|
@@ -0,0 +1,78 @@
|
|
1
|
+
import pytest
|
2
|
+
from unittest.mock import Mock, patch
|
3
|
+
from codespector.codespector import CodeSpector
|
4
|
+
from codespector.types import AgentInfo
|
5
|
+
from codespector.errors import NotValidCfgError
|
6
|
+
|
7
|
+
|
8
|
+
@pytest.fixture
|
9
|
+
def mock_response():
|
10
|
+
return {'choices': [{'message': {'content': 'Test review content'}}]}
|
11
|
+
|
12
|
+
|
13
|
+
@pytest.fixture
|
14
|
+
def test_params():
|
15
|
+
return {
|
16
|
+
'chat_token': 'test_token',
|
17
|
+
'chat_agent': 'chatgpt',
|
18
|
+
'compare_branch': 'main',
|
19
|
+
'system_content': 'test system content',
|
20
|
+
'prompt_content': 'test prompt content',
|
21
|
+
'result_file': 'result.txt',
|
22
|
+
'output_dir': 'test_output',
|
23
|
+
}
|
24
|
+
|
25
|
+
|
26
|
+
def test_codespector_create(test_params):
|
27
|
+
codespector = CodeSpector.create(**test_params)
|
28
|
+
|
29
|
+
assert codespector.chat_token == test_params['chat_token']
|
30
|
+
assert codespector.chat_agent == test_params['chat_agent']
|
31
|
+
assert codespector.compare_branch == test_params['compare_branch']
|
32
|
+
assert len(codespector.pipeline) == 2
|
33
|
+
|
34
|
+
|
35
|
+
@pytest.mark.parametrize(
|
36
|
+
'chat_agent,chat_model,expected_url,expected_model',
|
37
|
+
[
|
38
|
+
('chatgpt', None, 'https://api.openai.com/v1/chat/completions', 'gpt-4o'),
|
39
|
+
('codestral', 'custom-model', 'https://api.mistral.ai/v1/chat/completions', 'custom-model'),
|
40
|
+
('deepseek', None, 'https://api.deepseek.com/v1/chat/completions', 'deepseek-chat'),
|
41
|
+
],
|
42
|
+
)
|
43
|
+
def test_agent_info_create(chat_agent, chat_model, expected_url, expected_model):
|
44
|
+
agent_info = AgentInfo.create(chat_agent=chat_agent, chat_token='test_token', chat_model=chat_model)
|
45
|
+
|
46
|
+
assert agent_info.url == expected_url
|
47
|
+
assert agent_info.model == expected_model
|
48
|
+
assert agent_info.headers == {'Authorization': 'Bearer test_token'}
|
49
|
+
|
50
|
+
|
51
|
+
def test_agent_info_create_invalid():
|
52
|
+
with pytest.raises(NotValidCfgError):
|
53
|
+
AgentInfo.create(chat_agent='invalid_agent', chat_token='test_token')
|
54
|
+
|
55
|
+
|
56
|
+
@patch('builtins.open', create=True)
|
57
|
+
@patch('subprocess.run')
|
58
|
+
@patch('os.path.exists')
|
59
|
+
@patch('os.makedirs')
|
60
|
+
def test_result_file_writing(mock_makedirs, mock_exists, mock_run, mock_open, test_params, mock_response):
|
61
|
+
mock_file = Mock()
|
62
|
+
mock_open.return_value.__enter__.return_value = mock_file
|
63
|
+
mock_exists.return_value = False
|
64
|
+
|
65
|
+
mock_run.return_value.stdout = 'test diff'
|
66
|
+
|
67
|
+
mock_file.read.return_value = '{"original files": [], "diff": "test diff"}'
|
68
|
+
|
69
|
+
with patch('requests.post') as mock_post:
|
70
|
+
mock_post.return_value.json.return_value = mock_response
|
71
|
+
mock_post.return_value.raise_for_status = Mock()
|
72
|
+
|
73
|
+
codespector = CodeSpector.create(**test_params)
|
74
|
+
codespector.review()
|
75
|
+
|
76
|
+
write_calls = [args[0] for name, args, kwargs in mock_file.write.mock_calls]
|
77
|
+
|
78
|
+
assert any('Test review content' in str(call) for call in write_calls)
|
@@ -1,5 +1,5 @@
|
|
1
1
|
version = 1
|
2
|
-
requires-python = ">=3.
|
2
|
+
requires-python = ">=3.10"
|
3
3
|
|
4
4
|
[[package]]
|
5
5
|
name = "certifi"
|
@@ -16,6 +16,32 @@ version = "3.4.1"
|
|
16
16
|
source = { registry = "https://pypi.org/simple" }
|
17
17
|
sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 }
|
18
18
|
wheels = [
|
19
|
+
{ url = "https://files.pythonhosted.org/packages/0d/58/5580c1716040bc89206c77d8f74418caf82ce519aae06450393ca73475d1/charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", size = 198013 },
|
20
|
+
{ url = "https://files.pythonhosted.org/packages/d0/11/00341177ae71c6f5159a08168bcb98c6e6d196d372c94511f9f6c9afe0c6/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", size = 141285 },
|
21
|
+
{ url = "https://files.pythonhosted.org/packages/01/09/11d684ea5819e5a8f5100fb0b38cf8d02b514746607934134d31233e02c8/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", size = 151449 },
|
22
|
+
{ url = "https://files.pythonhosted.org/packages/08/06/9f5a12939db324d905dc1f70591ae7d7898d030d7662f0d426e2286f68c9/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", size = 143892 },
|
23
|
+
{ url = "https://files.pythonhosted.org/packages/93/62/5e89cdfe04584cb7f4d36003ffa2936681b03ecc0754f8e969c2becb7e24/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", size = 146123 },
|
24
|
+
{ url = "https://files.pythonhosted.org/packages/a9/ac/ab729a15c516da2ab70a05f8722ecfccc3f04ed7a18e45c75bbbaa347d61/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", size = 147943 },
|
25
|
+
{ url = "https://files.pythonhosted.org/packages/03/d2/3f392f23f042615689456e9a274640c1d2e5dd1d52de36ab8f7955f8f050/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", size = 142063 },
|
26
|
+
{ url = "https://files.pythonhosted.org/packages/f2/e3/e20aae5e1039a2cd9b08d9205f52142329f887f8cf70da3650326670bddf/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", size = 150578 },
|
27
|
+
{ url = "https://files.pythonhosted.org/packages/8d/af/779ad72a4da0aed925e1139d458adc486e61076d7ecdcc09e610ea8678db/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", size = 153629 },
|
28
|
+
{ url = "https://files.pythonhosted.org/packages/c2/b6/7aa450b278e7aa92cf7732140bfd8be21f5f29d5bf334ae987c945276639/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", size = 150778 },
|
29
|
+
{ url = "https://files.pythonhosted.org/packages/39/f4/d9f4f712d0951dcbfd42920d3db81b00dd23b6ab520419626f4023334056/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", size = 146453 },
|
30
|
+
{ url = "https://files.pythonhosted.org/packages/49/2b/999d0314e4ee0cff3cb83e6bc9aeddd397eeed693edb4facb901eb8fbb69/charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", size = 95479 },
|
31
|
+
{ url = "https://files.pythonhosted.org/packages/2d/ce/3cbed41cff67e455a386fb5e5dd8906cdda2ed92fbc6297921f2e4419309/charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", size = 102790 },
|
32
|
+
{ url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 },
|
33
|
+
{ url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 },
|
34
|
+
{ url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 },
|
35
|
+
{ url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335 },
|
36
|
+
{ url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862 },
|
37
|
+
{ url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673 },
|
38
|
+
{ url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211 },
|
39
|
+
{ url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039 },
|
40
|
+
{ url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939 },
|
41
|
+
{ url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075 },
|
42
|
+
{ url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340 },
|
43
|
+
{ url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205 },
|
44
|
+
{ url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441 },
|
19
45
|
{ url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 },
|
20
46
|
{ url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 },
|
21
47
|
{ url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 },
|
@@ -59,14 +85,13 @@ wheels = [
|
|
59
85
|
|
60
86
|
[[package]]
|
61
87
|
name = "codespector"
|
62
|
-
version = "0.
|
88
|
+
version = "0.2.0"
|
63
89
|
source = { editable = "." }
|
64
90
|
dependencies = [
|
65
91
|
{ name = "click" },
|
66
92
|
{ name = "environs" },
|
67
93
|
{ name = "loguru" },
|
68
94
|
{ name = "requests" },
|
69
|
-
{ name = "ujson" },
|
70
95
|
]
|
71
96
|
|
72
97
|
[package.dev-dependencies]
|
@@ -81,7 +106,6 @@ requires-dist = [
|
|
81
106
|
{ name = "environs", specifier = ">=14.1.1" },
|
82
107
|
{ name = "loguru", specifier = ">=0.7.3" },
|
83
108
|
{ name = "requests", specifier = ">=2.32.3" },
|
84
|
-
{ name = "ujson", specifier = ">=5.10.0" },
|
85
109
|
]
|
86
110
|
|
87
111
|
[package.metadata.requires-dev]
|
@@ -106,12 +130,22 @@ source = { registry = "https://pypi.org/simple" }
|
|
106
130
|
dependencies = [
|
107
131
|
{ name = "marshmallow" },
|
108
132
|
{ name = "python-dotenv" },
|
133
|
+
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
109
134
|
]
|
110
135
|
sdist = { url = "https://files.pythonhosted.org/packages/31/d3/e82bdbb8cc332e751f67a3f668c5d134d57f983497d9f3a59a375b6e8fd8/environs-14.1.1.tar.gz", hash = "sha256:03db7ee2d50ec697b68814cd175a3a05a7c7954804e4e419ca8b570dc5a835cf", size = 32050 }
|
111
136
|
wheels = [
|
112
137
|
{ url = "https://files.pythonhosted.org/packages/f4/1c/ab9752f02d32d981d647c05822be9ff93809be8953dacea2da2bec9a9de9/environs-14.1.1-py3-none-any.whl", hash = "sha256:45bc56f1d53bbc59d8dd69bba97377dd88ec28b8229d81cedbd455b21789445b", size = 15566 },
|
113
138
|
]
|
114
139
|
|
140
|
+
[[package]]
|
141
|
+
name = "exceptiongroup"
|
142
|
+
version = "1.2.2"
|
143
|
+
source = { registry = "https://pypi.org/simple" }
|
144
|
+
sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 }
|
145
|
+
wheels = [
|
146
|
+
{ url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 },
|
147
|
+
]
|
148
|
+
|
115
149
|
[[package]]
|
116
150
|
name = "idna"
|
117
151
|
version = "3.10"
|
@@ -179,9 +213,11 @@ version = "8.3.5"
|
|
179
213
|
source = { registry = "https://pypi.org/simple" }
|
180
214
|
dependencies = [
|
181
215
|
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
216
|
+
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
|
182
217
|
{ name = "iniconfig" },
|
183
218
|
{ name = "packaging" },
|
184
219
|
{ name = "pluggy" },
|
220
|
+
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
185
221
|
]
|
186
222
|
sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 }
|
187
223
|
wheels = [
|
@@ -238,31 +274,51 @@ wheels = [
|
|
238
274
|
]
|
239
275
|
|
240
276
|
[[package]]
|
241
|
-
name = "
|
242
|
-
version = "
|
277
|
+
name = "tomli"
|
278
|
+
version = "2.2.1"
|
279
|
+
source = { registry = "https://pypi.org/simple" }
|
280
|
+
sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 }
|
281
|
+
wheels = [
|
282
|
+
{ url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 },
|
283
|
+
{ url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 },
|
284
|
+
{ url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 },
|
285
|
+
{ url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 },
|
286
|
+
{ url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 },
|
287
|
+
{ url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 },
|
288
|
+
{ url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 },
|
289
|
+
{ url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 },
|
290
|
+
{ url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 },
|
291
|
+
{ url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 },
|
292
|
+
{ url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 },
|
293
|
+
{ url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 },
|
294
|
+
{ url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 },
|
295
|
+
{ url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 },
|
296
|
+
{ url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 },
|
297
|
+
{ url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 },
|
298
|
+
{ url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 },
|
299
|
+
{ url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 },
|
300
|
+
{ url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 },
|
301
|
+
{ url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 },
|
302
|
+
{ url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 },
|
303
|
+
{ url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 },
|
304
|
+
{ url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 },
|
305
|
+
{ url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 },
|
306
|
+
{ url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 },
|
307
|
+
{ url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 },
|
308
|
+
{ url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 },
|
309
|
+
{ url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 },
|
310
|
+
{ url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 },
|
311
|
+
{ url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 },
|
312
|
+
{ url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 },
|
313
|
+
]
|
314
|
+
|
315
|
+
[[package]]
|
316
|
+
name = "typing-extensions"
|
317
|
+
version = "4.13.0"
|
243
318
|
source = { registry = "https://pypi.org/simple" }
|
244
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
319
|
+
sdist = { url = "https://files.pythonhosted.org/packages/0e/3e/b00a62db91a83fff600de219b6ea9908e6918664899a2d85db222f4fbf19/typing_extensions-4.13.0.tar.gz", hash = "sha256:0a4ac55a5820789d87e297727d229866c9650f6521b64206413c4fbada24d95b", size = 106520 }
|
245
320
|
wheels = [
|
246
|
-
{ url = "https://files.pythonhosted.org/packages/
|
247
|
-
{ url = "https://files.pythonhosted.org/packages/a8/47/dd03fd2b5ae727e16d5d18919b383959c6d269c7b948a380fdd879518640/ujson-5.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9d2edbf1556e4f56e50fab7d8ff993dbad7f54bac68eacdd27a8f55f433578e", size = 51807 },
|
248
|
-
{ url = "https://files.pythonhosted.org/packages/25/23/079a4cc6fd7e2655a473ed9e776ddbb7144e27f04e8fc484a0fb45fe6f71/ujson-5.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6627029ae4f52d0e1a2451768c2c37c0c814ffc04f796eb36244cf16b8e57043", size = 51972 },
|
249
|
-
{ url = "https://files.pythonhosted.org/packages/04/81/668707e5f2177791869b624be4c06fb2473bf97ee33296b18d1cf3092af7/ujson-5.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8ccb77b3e40b151e20519c6ae6d89bfe3f4c14e8e210d910287f778368bb3d1", size = 53686 },
|
250
|
-
{ url = "https://files.pythonhosted.org/packages/bd/50/056d518a386d80aaf4505ccf3cee1c40d312a46901ed494d5711dd939bc3/ujson-5.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3caf9cd64abfeb11a3b661329085c5e167abbe15256b3b68cb5d914ba7396f3", size = 58591 },
|
251
|
-
{ url = "https://files.pythonhosted.org/packages/fc/d6/aeaf3e2d6fb1f4cfb6bf25f454d60490ed8146ddc0600fae44bfe7eb5a72/ujson-5.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6e32abdce572e3a8c3d02c886c704a38a1b015a1fb858004e03d20ca7cecbb21", size = 997853 },
|
252
|
-
{ url = "https://files.pythonhosted.org/packages/f8/d5/1f2a5d2699f447f7d990334ca96e90065ea7f99b142ce96e85f26d7e78e2/ujson-5.10.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a65b6af4d903103ee7b6f4f5b85f1bfd0c90ba4eeac6421aae436c9988aa64a2", size = 1140689 },
|
253
|
-
{ url = "https://files.pythonhosted.org/packages/f2/2c/6990f4ccb41ed93744aaaa3786394bca0875503f97690622f3cafc0adfde/ujson-5.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:604a046d966457b6cdcacc5aa2ec5314f0e8c42bae52842c1e6fa02ea4bda42e", size = 1043576 },
|
254
|
-
{ url = "https://files.pythonhosted.org/packages/14/f5/a2368463dbb09fbdbf6a696062d0c0f62e4ae6fa65f38f829611da2e8fdd/ujson-5.10.0-cp312-cp312-win32.whl", hash = "sha256:6dea1c8b4fc921bf78a8ff00bbd2bfe166345f5536c510671bccececb187c80e", size = 38764 },
|
255
|
-
{ url = "https://files.pythonhosted.org/packages/59/2d/691f741ffd72b6c84438a93749ac57bf1a3f217ac4b0ea4fd0e96119e118/ujson-5.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:38665e7d8290188b1e0d57d584eb8110951a9591363316dd41cf8686ab1d0abc", size = 42211 },
|
256
|
-
{ url = "https://files.pythonhosted.org/packages/0d/69/b3e3f924bb0e8820bb46671979770c5be6a7d51c77a66324cdb09f1acddb/ujson-5.10.0-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:618efd84dc1acbd6bff8eaa736bb6c074bfa8b8a98f55b61c38d4ca2c1f7f287", size = 55646 },
|
257
|
-
{ url = "https://files.pythonhosted.org/packages/32/8a/9b748eb543c6cabc54ebeaa1f28035b1bd09c0800235b08e85990734c41e/ujson-5.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38d5d36b4aedfe81dfe251f76c0467399d575d1395a1755de391e58985ab1c2e", size = 51806 },
|
258
|
-
{ url = "https://files.pythonhosted.org/packages/39/50/4b53ea234413b710a18b305f465b328e306ba9592e13a791a6a6b378869b/ujson-5.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67079b1f9fb29ed9a2914acf4ef6c02844b3153913eb735d4bf287ee1db6e557", size = 51975 },
|
259
|
-
{ url = "https://files.pythonhosted.org/packages/b4/9d/8061934f960cdb6dd55f0b3ceeff207fcc48c64f58b43403777ad5623d9e/ujson-5.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7d0e0ceeb8fe2468c70ec0c37b439dd554e2aa539a8a56365fd761edb418988", size = 53693 },
|
260
|
-
{ url = "https://files.pythonhosted.org/packages/f5/be/7bfa84b28519ddbb67efc8410765ca7da55e6b93aba84d97764cd5794dbc/ujson-5.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59e02cd37bc7c44d587a0ba45347cc815fb7a5fe48de16bf05caa5f7d0d2e816", size = 58594 },
|
261
|
-
{ url = "https://files.pythonhosted.org/packages/48/eb/85d465abafb2c69d9699cfa5520e6e96561db787d36c677370e066c7e2e7/ujson-5.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a890b706b64e0065f02577bf6d8ca3b66c11a5e81fb75d757233a38c07a1f20", size = 997853 },
|
262
|
-
{ url = "https://files.pythonhosted.org/packages/9f/76/2a63409fc05d34dd7d929357b7a45e3a2c96f22b4225cd74becd2ba6c4cb/ujson-5.10.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:621e34b4632c740ecb491efc7f1fcb4f74b48ddb55e65221995e74e2d00bbff0", size = 1140694 },
|
263
|
-
{ url = "https://files.pythonhosted.org/packages/45/ed/582c4daba0f3e1688d923b5cb914ada1f9defa702df38a1916c899f7c4d1/ujson-5.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9500e61fce0cfc86168b248104e954fead61f9be213087153d272e817ec7b4f", size = 1043580 },
|
264
|
-
{ url = "https://files.pythonhosted.org/packages/d7/0c/9837fece153051e19c7bade9f88f9b409e026b9525927824cdf16293b43b/ujson-5.10.0-cp313-cp313-win32.whl", hash = "sha256:4c4fc16f11ac1612f05b6f5781b384716719547e142cfd67b65d035bd85af165", size = 38766 },
|
265
|
-
{ url = "https://files.pythonhosted.org/packages/d7/72/6cb6728e2738c05bbe9bd522d6fc79f86b9a28402f38663e85a28fddd4a0/ujson-5.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:4573fd1695932d4f619928fd09d5d03d917274381649ade4328091ceca175539", size = 42212 },
|
321
|
+
{ url = "https://files.pythonhosted.org/packages/e0/86/39b65d676ec5732de17b7e3c476e45bb80ec64eb50737a8dce1a4178aba1/typing_extensions-4.13.0-py3-none-any.whl", hash = "sha256:c8dd92cc0d6425a97c18fbb9d1954e5ff92c1ca881a309c45f06ebc0b79058e5", size = 45683 },
|
266
322
|
]
|
267
323
|
|
268
324
|
[[package]]
|
@@ -1,44 +0,0 @@
|
|
1
|
-
from codespector import local
|
2
|
-
from loguru import logger
|
3
|
-
|
4
|
-
|
5
|
-
class CodeSpectorController:
|
6
|
-
__slots__ = (
|
7
|
-
'mode',
|
8
|
-
'chat_token',
|
9
|
-
'chat_agent',
|
10
|
-
'compare_branch',
|
11
|
-
'output_dir',
|
12
|
-
'system_content',
|
13
|
-
'chat_model',
|
14
|
-
)
|
15
|
-
|
16
|
-
def __init__(
|
17
|
-
self,
|
18
|
-
mode: str,
|
19
|
-
chat_token: str,
|
20
|
-
chat_agent: str,
|
21
|
-
compare_branch: str,
|
22
|
-
output_dir: str,
|
23
|
-
system_content: str,
|
24
|
-
chat_model: str,
|
25
|
-
):
|
26
|
-
self.mode = mode
|
27
|
-
self.chat_token = chat_token
|
28
|
-
self.chat_agent = chat_agent
|
29
|
-
self.compare_branch = compare_branch
|
30
|
-
self.output_dir = output_dir
|
31
|
-
self.system_content = system_content
|
32
|
-
self.chat_model = chat_model
|
33
|
-
|
34
|
-
def start(self):
|
35
|
-
codespector = local.LocalCodespector(
|
36
|
-
chat_token=self.chat_token,
|
37
|
-
chat_agent=self.chat_agent,
|
38
|
-
compare_branch=self.compare_branch,
|
39
|
-
output_dir=self.output_dir,
|
40
|
-
system_content=self.system_content,
|
41
|
-
chat_model=self.chat_model,
|
42
|
-
)
|
43
|
-
codespector.review()
|
44
|
-
logger.info('Review completed successfully.See result.txt in {} directory', self.output_dir)
|
@@ -1 +0,0 @@
|
|
1
|
-
from .main import LocalCodespector
|
@@ -1,35 +0,0 @@
|
|
1
|
-
from .prepare import CodeSpectorDataPreparer
|
2
|
-
from .reviewer import CodeSpectorReviewer
|
3
|
-
|
4
|
-
|
5
|
-
class LocalCodespector:
|
6
|
-
def __init__(
|
7
|
-
self,
|
8
|
-
chat_token: str,
|
9
|
-
chat_agent: str,
|
10
|
-
compare_branch: str,
|
11
|
-
output_dir: str,
|
12
|
-
system_content: str,
|
13
|
-
chat_model: str,
|
14
|
-
):
|
15
|
-
self.chat_token = chat_token
|
16
|
-
self.chat_agent = chat_agent
|
17
|
-
self.compare_branch = compare_branch
|
18
|
-
self.output_dir = output_dir
|
19
|
-
self.system_content = system_content
|
20
|
-
self.chat_model = chat_model
|
21
|
-
|
22
|
-
self.data_preparer = CodeSpectorDataPreparer(output_dir=self.output_dir, compare_branch=self.compare_branch)
|
23
|
-
self.reviewer = CodeSpectorReviewer(
|
24
|
-
diff_file=self.data_preparer.combined_file,
|
25
|
-
chat_token=self.chat_token,
|
26
|
-
chat_agent=self.chat_agent,
|
27
|
-
system_content=self.system_content,
|
28
|
-
output_dir=self.output_dir,
|
29
|
-
chat_model=self.chat_model,
|
30
|
-
)
|
31
|
-
self.processes = [self.data_preparer, self.reviewer]
|
32
|
-
|
33
|
-
def review(self):
|
34
|
-
for process in self.processes:
|
35
|
-
process.start()
|
@@ -1,108 +0,0 @@
|
|
1
|
-
import os.path
|
2
|
-
from dataclasses import dataclass
|
3
|
-
|
4
|
-
import ujson
|
5
|
-
import requests
|
6
|
-
|
7
|
-
from loguru import logger
|
8
|
-
|
9
|
-
AGENT_URL_MAPPING = {
|
10
|
-
'codestral': 'https://api.mistral.ai/v1/chat/completions',
|
11
|
-
'chatgpt': 'https://api.openai.com/v1/chat/completions',
|
12
|
-
}
|
13
|
-
|
14
|
-
DEFAULT_AGENT_MODEL = {'codestral': 'codestral-latest', 'chatgpt': 'gpt-4o'}
|
15
|
-
|
16
|
-
|
17
|
-
@dataclass
|
18
|
-
class AgentInfo:
|
19
|
-
model: str
|
20
|
-
url: str
|
21
|
-
headers: dict
|
22
|
-
|
23
|
-
@classmethod
|
24
|
-
def create(cls, chat_agent: str, chat_token: str, chat_model: str | None = None) -> 'AgentInfo':
|
25
|
-
url = AGENT_URL_MAPPING[chat_agent]
|
26
|
-
model = chat_model if chat_model else DEFAULT_AGENT_MODEL[chat_agent]
|
27
|
-
headers = {'Authorization': f'Bearer {chat_token}'}
|
28
|
-
return cls(
|
29
|
-
url=url,
|
30
|
-
model=model,
|
31
|
-
headers=headers,
|
32
|
-
)
|
33
|
-
|
34
|
-
|
35
|
-
class CodeSpectorReviewer:
|
36
|
-
def __init__(
|
37
|
-
self,
|
38
|
-
diff_file: str,
|
39
|
-
chat_token: str,
|
40
|
-
chat_agent: str,
|
41
|
-
chat_model: str | None,
|
42
|
-
system_content: str,
|
43
|
-
output_dir: str,
|
44
|
-
):
|
45
|
-
self.diff_file = diff_file
|
46
|
-
self.chat_token = chat_token
|
47
|
-
self.chat_agent = chat_agent
|
48
|
-
self.chat_model = chat_model
|
49
|
-
self.system_content = system_content
|
50
|
-
self.output_dir = output_dir
|
51
|
-
|
52
|
-
self.request_file = 'request.json'
|
53
|
-
self.response_file = 'response.json'
|
54
|
-
self.result_file = 'result.md'
|
55
|
-
|
56
|
-
def _request_to_chat_agent(self, prompt: str):
|
57
|
-
agent_info = AgentInfo.create(self.chat_agent, self.chat_token, self.chat_model)
|
58
|
-
request_data = {
|
59
|
-
'model': agent_info.model,
|
60
|
-
'messages': [{'role': 'system', 'content': self.system_content}, {'role': 'user', 'content': prompt}],
|
61
|
-
}
|
62
|
-
|
63
|
-
with open(os.path.join(self.output_dir, self.request_file), 'w', encoding='utf-8') as f:
|
64
|
-
ujson.dump(request_data, f, indent=4, ensure_ascii=False)
|
65
|
-
|
66
|
-
response = requests.post(
|
67
|
-
agent_info.url,
|
68
|
-
json=request_data,
|
69
|
-
headers=agent_info.headers,
|
70
|
-
timeout=100,
|
71
|
-
)
|
72
|
-
response.raise_for_status()
|
73
|
-
return response
|
74
|
-
|
75
|
-
def send_to_review(self):
|
76
|
-
with open(os.path.join(self.output_dir, self.diff_file), 'r', encoding='utf-8') as f:
|
77
|
-
diff_data = ujson.load(f)
|
78
|
-
|
79
|
-
diff_content = diff_data.get('diff', '')
|
80
|
-
original_files = diff_data.get('original files', [])
|
81
|
-
|
82
|
-
original_files_str = ujson.dumps(original_files, indent=4, ensure_ascii=False)
|
83
|
-
|
84
|
-
prompt = (
|
85
|
-
'Пожалуйста, проверь следующие изменения в коде на наличие очевидных проблем с качеством или безопасностью. '
|
86
|
-
'Предоставь краткий отчет в формате markdown:\n\n'
|
87
|
-
'DIFF:\n'
|
88
|
-
f'{diff_content}\n\n'
|
89
|
-
'ORIGINAL FILES:\n'
|
90
|
-
f'{original_files_str}'
|
91
|
-
)
|
92
|
-
try:
|
93
|
-
response = self._request_to_chat_agent(prompt=prompt)
|
94
|
-
except Exception as e:
|
95
|
-
logger.error('Error while send request: {}', e)
|
96
|
-
raise e
|
97
|
-
|
98
|
-
with open(os.path.join(self.output_dir, self.response_file), 'w', encoding='utf-8') as f:
|
99
|
-
ujson.dump(response.json(), f, indent=4, ensure_ascii=False)
|
100
|
-
|
101
|
-
resp = response.json()
|
102
|
-
clear_response = resp['choices'][0]['message']['content']
|
103
|
-
|
104
|
-
with open(os.path.join(self.output_dir, self.result_file), 'w', encoding='utf-8') as f:
|
105
|
-
f.write(clear_response)
|
106
|
-
|
107
|
-
def start(self):
|
108
|
-
self.send_to_review()
|
File without changes
|
@@ -1,26 +0,0 @@
|
|
1
|
-
import pytest
|
2
|
-
from unittest.mock import patch
|
3
|
-
from codespector.local.prepare import CodeSpectorDataPreparer
|
4
|
-
|
5
|
-
|
6
|
-
@pytest.fixture
|
7
|
-
def preparer():
|
8
|
-
return CodeSpectorDataPreparer(output_dir='test_output', compare_branch='develop')
|
9
|
-
|
10
|
-
|
11
|
-
def test_prepare_dir(preparer):
|
12
|
-
with patch('os.makedirs') as mock_makedirs, patch('os.path.exists', return_value=False):
|
13
|
-
preparer._prepare_dir()
|
14
|
-
mock_makedirs.assert_called_once_with('test_output')
|
15
|
-
|
16
|
-
|
17
|
-
def test_prepare_data(preparer):
|
18
|
-
with (
|
19
|
-
patch.object(preparer, '_prepare_dir') as mock_prepare_dir,
|
20
|
-
patch.object(preparer, '_prepare_name_only_file') as mock_prepare_name_only_file,
|
21
|
-
patch.object(preparer, '_prepare_diff_file') as mock_prepare_diff_file,
|
22
|
-
):
|
23
|
-
preparer.prepare_data()
|
24
|
-
mock_prepare_dir.assert_called_once()
|
25
|
-
mock_prepare_name_only_file.assert_called_once()
|
26
|
-
mock_prepare_diff_file.assert_called_once()
|
@@ -1,74 +0,0 @@
|
|
1
|
-
import pytest
|
2
|
-
from unittest.mock import patch, mock_open
|
3
|
-
from codespector.local.reviewer import CodeSpectorReviewer
|
4
|
-
import os
|
5
|
-
import ujson
|
6
|
-
from unittest.mock import MagicMock
|
7
|
-
|
8
|
-
|
9
|
-
@pytest.fixture
|
10
|
-
def reviewer():
|
11
|
-
return CodeSpectorReviewer(
|
12
|
-
diff_file='diff.json',
|
13
|
-
chat_token='test_token',
|
14
|
-
chat_agent='codestral',
|
15
|
-
chat_model=None,
|
16
|
-
system_content='Test system content',
|
17
|
-
output_dir='test_output',
|
18
|
-
)
|
19
|
-
|
20
|
-
|
21
|
-
def test_request_to_chat_agent(reviewer):
|
22
|
-
prompt = 'Test prompt'
|
23
|
-
mock_response = MagicMock()
|
24
|
-
mock_response.json.return_value = {'choices': [{'message': {'content': 'Test response'}}]}
|
25
|
-
|
26
|
-
with (
|
27
|
-
patch('requests.post', return_value=mock_response) as mock_post,
|
28
|
-
patch('builtins.open', mock_open()) as mock_file,
|
29
|
-
):
|
30
|
-
response = reviewer._request_to_chat_agent(prompt)
|
31
|
-
|
32
|
-
mock_post.assert_called_once_with(
|
33
|
-
'https://api.mistral.ai/v1/chat/completions',
|
34
|
-
json={
|
35
|
-
'model': 'codestral-latest',
|
36
|
-
'messages': [{'role': 'system', 'content': 'Test system content'}, {'role': 'user', 'content': prompt}],
|
37
|
-
},
|
38
|
-
headers={'Authorization': 'Bearer test_token'},
|
39
|
-
timeout=100,
|
40
|
-
)
|
41
|
-
mock_file.assert_called_with(os.path.join('test_output', 'request.json'), 'w', encoding='utf-8')
|
42
|
-
assert response == mock_response
|
43
|
-
|
44
|
-
|
45
|
-
# tests/unit/test_reviewer.py
|
46
|
-
def test_send_to_review(reviewer):
|
47
|
-
diff_data = {'diff': 'Test diff', 'original files': ['file1.py', 'file2.py']}
|
48
|
-
mock_response = MagicMock()
|
49
|
-
mock_response.json.return_value = {'choices': [{'message': {'content': 'Test response'}}]}
|
50
|
-
|
51
|
-
with (
|
52
|
-
patch('builtins.open', mock_open(read_data=ujson.dumps(diff_data))) as mock_file,
|
53
|
-
patch.object(reviewer, '_request_to_chat_agent', return_value=mock_response) as mock_request,
|
54
|
-
patch('ujson.dump') as _,
|
55
|
-
):
|
56
|
-
reviewer.send_to_review()
|
57
|
-
|
58
|
-
mock_file.assert_any_call(os.path.join('test_output', 'diff.json'), 'r', encoding='utf-8')
|
59
|
-
mock_request.assert_called_once_with(
|
60
|
-
prompt='Пожалуйста, проверь следующие изменения в коде на наличие очевидных проблем с качеством или безопасностью. '
|
61
|
-
'Предоставь краткий отчет в формате markdown:\n\n'
|
62
|
-
'DIFF:\n'
|
63
|
-
'Test diff\n\n'
|
64
|
-
'ORIGINAL FILES:\n'
|
65
|
-
'[\n "file1.py",\n "file2.py"\n]'
|
66
|
-
)
|
67
|
-
mock_file.assert_any_call(os.path.join('test_output', 'response.json'), 'w', encoding='utf-8')
|
68
|
-
mock_file.assert_any_call(os.path.join('test_output', 'result.md'), 'w', encoding='utf-8')
|
69
|
-
|
70
|
-
|
71
|
-
def test_start(reviewer):
|
72
|
-
with patch.object(reviewer, 'send_to_review') as mock_send_to_review:
|
73
|
-
reviewer.start()
|
74
|
-
mock_send_to_review.assert_called_once()
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|