From 05b70a880e0f16149d560e7e483c4d6e5405b148 Mon Sep 17 00:00:00 2001 From: Chino Franco Date: Sat, 9 Aug 2025 11:42:26 +0900 Subject: [PATCH 1/2] feat: officially name as codexa --- .github/workflows/testing.yaml | 4 +- Dockerfile | 4 +- README.md | 2 +- {testclerk => codexa}/__init__.py | 0 {testclerk => codexa}/client/__init__.py | 0 codexa/client/accessor.py | 167 +++++++++++++++++++++ {testclerk => codexa}/client/executor.py | 0 {testclerk => codexa}/client/versioning.py | 4 +- {testclerk => codexa}/commands/__init__.py | 0 {testclerk => codexa}/commands/compare.py | 10 +- {testclerk => codexa}/commands/list.py | 8 +- codexa/commands/run.py | 70 +++++++++ {testclerk => codexa}/core/constants.py | 0 {testclerk => codexa}/core/env.py | 8 +- {testclerk => codexa}/core/errors.py | 14 +- {testclerk => codexa}/core/handler.py | 6 +- {testclerk => codexa}/core/output.py | 0 {testclerk => codexa}/main.py | 14 +- justfile | 10 +- pyproject.toml | 6 +- testclerk/client/accessor.py | 10 +- testclerk/commands/run.py | 14 +- tests/conftest.py | 4 +- tests/test_clients.py | 8 +- tests/test_executor.py | 2 +- tests/test_utils.py | 6 +- tests/tools.py | 4 +- 27 files changed, 306 insertions(+), 69 deletions(-) rename {testclerk => codexa}/__init__.py (100%) rename {testclerk => codexa}/client/__init__.py (100%) create mode 100644 codexa/client/accessor.py rename {testclerk => codexa}/client/executor.py (100%) rename {testclerk => codexa}/client/versioning.py (88%) rename {testclerk => codexa}/commands/__init__.py (100%) rename {testclerk => codexa}/commands/compare.py (87%) rename {testclerk => codexa}/commands/list.py (84%) create mode 100644 codexa/commands/run.py rename {testclerk => codexa}/core/constants.py (100%) rename {testclerk => codexa}/core/env.py (63%) rename {testclerk => codexa}/core/errors.py (89%) rename {testclerk => codexa}/core/handler.py (87%) rename {testclerk => codexa}/core/output.py (100%) rename {testclerk => codexa}/main.py (78%) diff --git a/.github/workflows/testing.yaml b/.github/workflows/testing.yaml index f159f42..9eb12d3 100644 --- a/.github/workflows/testing.yaml +++ b/.github/workflows/testing.yaml @@ -33,7 +33,7 @@ jobs: - name: Run tests with coverage run: | poetry run coverage run \ - --source=testclerk \ + --source=codexa \ --omit="*/__*.py,*/test_*.py,/tmp/*" \ -m pytest poetry run coverage report > coverage.txt @@ -43,7 +43,7 @@ jobs: if: always() run: | TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S") - echo "# TestClerk Unit Testing Report" >> $GITHUB_STEP_SUMMARY + echo "# Codexa Unit Testing Report" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "## Pytest coverage" >> $GITHUB_STEP_SUMMARY echo '```text' >> $GITHUB_STEP_SUMMARY diff --git a/Dockerfile b/Dockerfile index affe81d..2cd0ae8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,11 +21,11 @@ RUN curl -sSL https://install.python-poetry.org | python3 - WORKDIR /app COPY README.md pyproject.toml poetry.lock* ./ -COPY testclerk ./testclerk +COPY codexa ./codexa RUN poetry config virtualenvs.create false \ && poetry install --no-interaction --no-ansi --only main \ && poetry build \ && pip install dist/*.whl -ENTRYPOINT ["testclerk"] +ENTRYPOINT ["codexa"] CMD ["--help"] diff --git a/README.md b/README.md index 2e90292..f893e35 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ -# TestClerk +# Codexa Simple CLI to analyze code for testing. diff --git a/testclerk/__init__.py b/codexa/__init__.py similarity index 100% rename from testclerk/__init__.py rename to codexa/__init__.py diff --git a/testclerk/client/__init__.py b/codexa/client/__init__.py similarity index 100% rename from testclerk/client/__init__.py rename to codexa/client/__init__.py diff --git a/codexa/client/accessor.py b/codexa/client/accessor.py new file mode 100644 index 0000000..1956c1f --- /dev/null +++ b/codexa/client/accessor.py @@ -0,0 +1,167 @@ +from openai import OpenAI + +from codexa.core.errors import CodexaAccessorError + + +class RemoteAIAccessor: + """Class for interacting with remote LLM APIs. + + Uses the DeepSeek R1 free model by default. + """ + + def __init__( + self, + api_key: str, + prompt: str, + base_url: str = "https://openrouter.ai/api/v1", + model: str = "deepseek/deepseek-r1:free", + ) -> None: + if not api_key: + raise CodexaAccessorError("API key is required") + if not prompt: + raise CodexaAccessorError("Accessor requires setup prompt") + self.__api_key = api_key + self.__base_url = base_url + self.__model = model + self.__client = OpenAI(api_key=self.__api_key, base_url=self.__base_url) + self.__setup_prompt = prompt + + @property + def setup_prompt(self) -> str: + """Return the setup prompt.""" + return self.__setup_prompt + + def make_request(self, message: str, timeout: float = 60.0) -> str: + """Make a request to the LLM API. + + Args: + message (str): Interaction message + timeout (float, optional): Request timeout (s), defaults to 60.0. + + Raises: + CodexaGenerationError: If the LLM API call fails + + Returns: + str: LLM response text + """ + response = self.__client.chat.completions.create( + model=self.__model, + messages=[ + {"role": "system", "content": self.__setup_prompt}, + {"role": "user", "content": message}, + ], + stream=False, + timeout=timeout, + ) + content = response.choices[0].message.content + if not content: + reason = response.choices[0].message.refusal + raise CodexaAccessorError( + message=f"Failed to generate code: {reason}", + help_text="Please try again re-running the command", + ) + return content + + +class ReportScanner(RemoteAIAccessor): + """Class for generating test report summary.""" + + def __init__(self, api_key: str): + self.__setup_prompt = """Take on the role of a Senior QA Engineer. + + I need to implement testing in Python. I am using Pytest as my test harness. + Your primary task is to read the Pytest execution output and write a summary + report. + + Key requirements: + 1. Report should be written in a way that is easy to understand. + 2. Report must provide actionable steps for fixing the issues. + 3. Report should be written in Markdown syntax, properly formatted. + + Sections that the report must include: + 1. Summary of the test run + 2. Per error, a summary of the error and the steps to fix it + 3. Any other relevant information + + Please consider: + - Test writing best practices + - ISTQB Tester guidelines + + From this step forward, I will provide you with the execution output and you + will write the report. Minimize chat response, focus on the code. Provide ONLY + the summary, not write additional comments or greetings. + """ + + super().__init__(api_key, prompt=self.__setup_prompt) + + def analyze_tests(self, test_output: str, timeout: float = 60.0) -> str: + """Read the test execution output and generate a report. + + Args: + test_output (str): Pytest execution output + + Returns: + str: Generated test summary report + """ + message = f"Generate a report for the following test output:\n\n{test_output}" + return self.make_request(message, timeout) + + +class RepoAnalyzer(RemoteAIAccessor): + """Class for analyzing the repository.""" + + def __init__(self, api_key: str): + self.__setup_prompt = """# Overview + + Take on the role of a Senior Software Engineer, + specializing in automated testing, code review, and test strategy. + + You will be given a `git diff` between the current working tree and a remote reference + (usually the main branch). Your task is to analyze the diff and identify what areas of + the codebase have changed, and what testing actions are necessary based on those changes. + + Your objective is to **guide the author on what tests are required or should be updated**. + + ## Goals + - Review the changed files and modified code in the diff. + - Identify which functions, classes, or modules have been added, modified, or removed. + - Detect if new logic paths, branches, conditions, or data flows are introduced. + - Determine whether the existing tests need to be updated or if new tests should be created. + - Recommend specific **types of tests** required (e.g., unit, integration, regression, edge cases). + - If test files are included in the diff, comment on their adequacy and suggest improvements. + - Flag if changes to existing test cases might break or misrepresent the new behavior. + - If no changes in test files are detected, but logic changes exist, point out the test coverage gap. + + ## Output Format + + Respond in **Markdown** with the following sections: + + - Summary + - Areas Requiring Tests + - Suggested Tests + - Risks + + Follow proper Markdownlint formatting and syntax. Ensure to add spaces after headers. + Don't put the triple-backticks (```) in the output, format the response as if it were + to be pasted into a Markdown file directly. + + ## Additional Notes + + - Be precise and concise; prefer bullet points where helpful. + - Favor actionable suggestions over verbose explanations. + - You are not writing the tests — you are identifying and planning them. + """ + + super().__init__(api_key, prompt=self.__setup_prompt) + + def compare_diff(self, diff: str, timeout: float = 60.0) -> str: + """Assess the repository changes and generate a report. + + Args: + diff (str): Repository changes + + Returns: + str: Generated test summary report + """ + message = f"Prepare an analysis and report for this diff:\n\n{diff}" + return self.make_request(message, timeout) diff --git a/testclerk/client/executor.py b/codexa/client/executor.py similarity index 100% rename from testclerk/client/executor.py rename to codexa/client/executor.py diff --git a/testclerk/client/versioning.py b/codexa/client/versioning.py similarity index 88% rename from testclerk/client/versioning.py rename to codexa/client/versioning.py index 06c204a..c11a94e 100644 --- a/testclerk/client/versioning.py +++ b/codexa/client/versioning.py @@ -3,7 +3,7 @@ from git import Repo -from testclerk.core.errors import TestClerkRuntimeError +from codexa.core.errors import CodexaRuntimeError logger = logging.getLogger(__name__) @@ -32,4 +32,4 @@ def compare_git_diff(remote_ref: str, repo_path: str = os.getcwd()) -> str: diff_index = head_commit.diff(remote_commit, create_patch=True) return "\n".join(str(d.diff) for d in diff_index if d.diff) except Exception as e: - raise TestClerkRuntimeError(f"Failed to get git diff: {e}") + raise CodexaRuntimeError(f"Failed to get git diff: {e}") diff --git a/testclerk/commands/__init__.py b/codexa/commands/__init__.py similarity index 100% rename from testclerk/commands/__init__.py rename to codexa/commands/__init__.py diff --git a/testclerk/commands/compare.py b/codexa/commands/compare.py similarity index 87% rename from testclerk/commands/compare.py rename to codexa/commands/compare.py index 35988ec..0a502fd 100644 --- a/testclerk/commands/compare.py +++ b/codexa/commands/compare.py @@ -5,10 +5,10 @@ import click -from testclerk.client.accessor import RepoAnalyzer -from testclerk.client.versioning import compare_git_diff -from testclerk.core.env import load_api_key -from testclerk.core.errors import TestClerkInputError, TestClerkOutputError +from codexa.client.accessor import RepoAnalyzer +from codexa.client.versioning import compare_git_diff +from codexa.core.env import load_api_key +from codexa.core.errors import CodexaInputError, CodexaOutputError logger = logging.getLogger(__name__) @@ -51,7 +51,7 @@ def compare_command( ) -> None: """Generate smart analysis from diff comparison..""" if output is not None and output.suffix != ".md": - raise TestClerkInputError( + raise CodexaInputError( message=f"Output file must be a Markdown file, got {output.suffix}", help_text=f"Rename the output file to a {output.stem}.md", ) diff --git a/testclerk/commands/list.py b/codexa/commands/list.py similarity index 84% rename from testclerk/commands/list.py rename to codexa/commands/list.py index ea6b9ee..7044405 100644 --- a/testclerk/commands/list.py +++ b/codexa/commands/list.py @@ -5,10 +5,10 @@ import click -from testclerk.client.accessor import ReportScanner -from testclerk.client.executor import TestExecutor -from testclerk.core.env import load_api_key -from testclerk.core.errors import TestClerkAccessorError, TestClerkInputError +from codexa.client.accessor import ReportScanner +from codexa.client.executor import TestExecutor +from codexa.core.env import load_api_key +from codexa.core.errors import CodexaAccessorError, CodexaInputError logger = logging.getLogger(__name__) diff --git a/codexa/commands/run.py b/codexa/commands/run.py new file mode 100644 index 0000000..40688a9 --- /dev/null +++ b/codexa/commands/run.py @@ -0,0 +1,70 @@ +import logging +from pathlib import Path +from typing import List + +import click + +from codexa.client.accessor import ReportScanner +from codexa.client.executor import TestExecutor +from codexa.core.env import load_api_key +from codexa.core.errors import CodexaAccessorError, CodexaInputError + +logger = logging.getLogger(__name__) + + +@click.command("run") +@click.argument("test_ids", nargs=-1) +@click.option( + "--output", + "output", + "-o", + type=click.Path(exists=False, dir_okay=False, resolve_path=True, path_type=Path), + required=False, + default=Path(Path.cwd(), "report.md"), + help="Output file with generated code", +) +@click.option( + "--quiet", + "quiet", + "-q", + is_flag=True, + help="Suppress test execution shell output", +) +def run_command(test_ids: List[str], output: Path, quiet: bool) -> None: + """Analyze the contents of a file for testing.""" + if output.suffix != ".md": + raise CodexaInputError( + message=f"Output file must be a Markdown file, got {output.suffix}", + help_text=f"Rename the output file to a {output.stem}.md", + ) + key = load_api_key() + client = ReportScanner(key) + if not test_ids: + logger.info("No test IDs provided, running all tests") + + executor = TestExecutor(test_ids) + exit_code, shell_output, error_output = executor.run(verbose=not quiet) + if not quiet: + click.echo(shell_output) + click.echo(error_output) + if exit_code != 0: + raise CodexaAccessorError( + message=f"Failed to generate tests: {error_output}", + help_text=f"Please check the output for more information", + ) + + logger.debug(f"Test execution complete, proceeding to results analysis") + response = client.analyze_tests(shell_output) + try: + with open(output, "w") as f: + f.write(response) + except IOError as e: + raise CodexaAccessorError( + message=f"Failed to generate test file: {e}", + ) + + click.secho( + f"\n[!] Test summary generated! Report file: {output}", + fg="green", + bold=True, + ) diff --git a/testclerk/core/constants.py b/codexa/core/constants.py similarity index 100% rename from testclerk/core/constants.py rename to codexa/core/constants.py diff --git a/testclerk/core/env.py b/codexa/core/env.py similarity index 63% rename from testclerk/core/env.py rename to codexa/core/env.py index f0de4cb..811509e 100644 --- a/testclerk/core/env.py +++ b/codexa/core/env.py @@ -1,21 +1,21 @@ import os -from testclerk.core.constants import Environment -from testclerk.core.errors import TestClerkEnvironmentError +from codexa.core.constants import Environment +from codexa.core.errors import CodexaEnvironmentError def load_api_key() -> str: """Load the API key from the environment. Raises: - TestClerkEnvironmentError: If the API key env variable not set + CodexaEnvironmentError: If the API key env variable not set Returns: str: LLM API key """ key = os.environ.get(Environment.API_KEY, None) if not key: - raise TestClerkEnvironmentError( + raise CodexaEnvironmentError( message=f"{Environment.API_KEY} environment variable is not set", help_text=f"Set {Environment.API_KEY} and try again", ) diff --git a/testclerk/core/errors.py b/codexa/core/errors.py similarity index 89% rename from testclerk/core/errors.py rename to codexa/core/errors.py index 7c92ec3..5e79744 100644 --- a/testclerk/core/errors.py +++ b/codexa/core/errors.py @@ -16,7 +16,7 @@ class ExitCode: OUTPUT_ERROR: Final[int] = 7 -class TestClerkBaseError(Exception): +class CodexaBaseError(Exception): """A base CLI Error class. Contains a message, exit_code and help text show to the user @@ -34,7 +34,7 @@ def __init__(self, message: str, exit_code: int, help_text: Optional[str]): super().__init__(self.message) -class TestClerkRuntimeError(TestClerkBaseError): +class CodexaRuntimeError(CodexaBaseError): """General CLI CLI Error class.""" def __init__( @@ -47,7 +47,7 @@ def __init__( super().__init__(self.message, ExitCode.RUNTIME_ERROR, help_text) -class TestClerkInputError(TestClerkBaseError): +class CodexaInputError(CodexaBaseError): """CLI user input error class.""" def __init__( @@ -60,7 +60,7 @@ def __init__( super().__init__(self.message, ExitCode.INPUT_ERROR, help_text) -class TestClerkEnvironmentError(TestClerkBaseError): +class CodexaEnvironmentError(CodexaBaseError): """CLI environmnt error class.""" def __init__( @@ -73,7 +73,7 @@ def __init__( super().__init__(self.message, ExitCode.ENVIRONMENT_ERROR, help_text) -class TestClerkExecutionError(TestClerkBaseError): +class CodexaExecutionError(CodexaBaseError): """CLI test execution error class.""" def __init__( @@ -86,7 +86,7 @@ def __init__( super().__init__(self.message, ExitCode.GENERATION_ERROR, help_text) -class TestClerkAccessorError(TestClerkBaseError): +class CodexaAccessorError(CodexaBaseError): """CLI LLM generation error class.""" def __init__( @@ -99,7 +99,7 @@ def __init__( super().__init__(self.message, ExitCode.GENERATION_ERROR, help_text) -class TestClerkOutputError(TestClerkBaseError): +class CodexaOutputError(CodexaBaseError): """CLI LLM generation error class.""" def __init__( diff --git a/testclerk/core/handler.py b/codexa/core/handler.py similarity index 87% rename from testclerk/core/handler.py rename to codexa/core/handler.py index 042453f..1deb5f2 100644 --- a/testclerk/core/handler.py +++ b/codexa/core/handler.py @@ -4,8 +4,8 @@ import click -from testclerk.core.errors import ExitCode, TestClerkBaseError -from testclerk.core.output import print_warning +from codexa.core.errors import CodexaBaseError, ExitCode +from codexa.core.output import print_warning logger = logging.getLogger(__name__) @@ -23,7 +23,7 @@ def invoke(self, ctx: click.Context) -> Any: try: return super().invoke(ctx) - except TestClerkBaseError as err: + except CodexaBaseError as err: logger.exception(err) print_warning(f"{err.help_text}") sys.exit(err.exit_code) diff --git a/testclerk/core/output.py b/codexa/core/output.py similarity index 100% rename from testclerk/core/output.py rename to codexa/core/output.py diff --git a/testclerk/main.py b/codexa/main.py similarity index 78% rename from testclerk/main.py rename to codexa/main.py index aae1f2e..f21747d 100644 --- a/testclerk/main.py +++ b/codexa/main.py @@ -3,12 +3,12 @@ import click import colorama -from testclerk import __version__ -from testclerk.commands.compare import compare_command -from testclerk.commands.list import list_command -from testclerk.commands.run import run_command -from testclerk.core.handler import CliHandler -from testclerk.core.output import ColorHandler +from codexa import __version__ +from codexa.commands.compare import compare_command +from codexa.commands.list import list_command +from codexa.commands.run import run_command +from codexa.core.handler import CliHandler +from codexa.core.output import ColorHandler colorama.init(autoreset=True) @@ -46,7 +46,7 @@ def __set_logger(level: int): help="Increase verbosity. Use multiple times for more detail (e.g., -vv for debug).", ) def cli(context: click.Context, verbose: int): - """TestClerk: CLI tool for test automation assistance.""" + """Codexa: CLI tool for test automation assistance.""" __set_logger(verbose) context.ensure_object(dict) diff --git a/justfile b/justfile index a2406ea..4dc6aa9 100644 --- a/justfile +++ b/justfile @@ -10,16 +10,16 @@ install: @echo "Installed dependencies!" # Run the CLI tool with Poetry -testclerk *ARGS: - @poetry run testclerk {{ ARGS }} +codexa *ARGS: + @poetry run codexa {{ ARGS }} # Build Docker image docker-build: - docker build -t testclerk:0.0.0 . + docker build -t codexa:0.0.0 . # Run CLI through Docker docker-run *ARGS: - docker run --rm testclerk:0.0.0 {{ ARGS }} + docker run --rm codexa:0.0.0 {{ ARGS }} # Run pytest via poetry pytest *ARGS: @@ -27,5 +27,5 @@ pytest *ARGS: # Run test coverage coverage: - poetry run coverage run --source=testclerk --omit="*/__*.py,*/test_*.py,/tmp/*" -m pytest + poetry run coverage run --source=codexa --omit="*/__*.py,*/test_*.py,/tmp/*" -m pytest poetry run coverage report -m diff --git a/pyproject.toml b/pyproject.toml index 72a0ad3..0e2af95 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,14 +3,14 @@ requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" [tool.poetry] -name = "testclerk" +name = "codexa" version = "0.0.1" description = "Python-based CLI for test report analysis" authors = ["Joaquin Franco "] license = "MIT" readme = "README.md" packages = [ - { include = "testclerk" } + { include = "codexa" } ] [tool.poetry.dependencies] @@ -33,4 +33,4 @@ coverage = "^7.6.0" poetry = "^1.8.3" [tool.poetry.scripts] -testclerk = "testclerk.main:cli" +codexa = "codexa.main:cli" diff --git a/testclerk/client/accessor.py b/testclerk/client/accessor.py index fae7879..1956c1f 100644 --- a/testclerk/client/accessor.py +++ b/testclerk/client/accessor.py @@ -1,6 +1,6 @@ from openai import OpenAI -from testclerk.core.errors import TestClerkAccessorError +from codexa.core.errors import CodexaAccessorError class RemoteAIAccessor: @@ -17,9 +17,9 @@ def __init__( model: str = "deepseek/deepseek-r1:free", ) -> None: if not api_key: - raise TestClerkAccessorError("API key is required") + raise CodexaAccessorError("API key is required") if not prompt: - raise TestClerkAccessorError("Accessor requires setup prompt") + raise CodexaAccessorError("Accessor requires setup prompt") self.__api_key = api_key self.__base_url = base_url self.__model = model @@ -39,7 +39,7 @@ def make_request(self, message: str, timeout: float = 60.0) -> str: timeout (float, optional): Request timeout (s), defaults to 60.0. Raises: - TestClerkGenerationError: If the LLM API call fails + CodexaGenerationError: If the LLM API call fails Returns: str: LLM response text @@ -56,7 +56,7 @@ def make_request(self, message: str, timeout: float = 60.0) -> str: content = response.choices[0].message.content if not content: reason = response.choices[0].message.refusal - raise TestClerkAccessorError( + raise CodexaAccessorError( message=f"Failed to generate code: {reason}", help_text="Please try again re-running the command", ) diff --git a/testclerk/commands/run.py b/testclerk/commands/run.py index 1819e58..40688a9 100644 --- a/testclerk/commands/run.py +++ b/testclerk/commands/run.py @@ -4,10 +4,10 @@ import click -from testclerk.client.accessor import ReportScanner -from testclerk.client.executor import TestExecutor -from testclerk.core.env import load_api_key -from testclerk.core.errors import TestClerkAccessorError, TestClerkInputError +from codexa.client.accessor import ReportScanner +from codexa.client.executor import TestExecutor +from codexa.core.env import load_api_key +from codexa.core.errors import CodexaAccessorError, CodexaInputError logger = logging.getLogger(__name__) @@ -33,7 +33,7 @@ def run_command(test_ids: List[str], output: Path, quiet: bool) -> None: """Analyze the contents of a file for testing.""" if output.suffix != ".md": - raise TestClerkInputError( + raise CodexaInputError( message=f"Output file must be a Markdown file, got {output.suffix}", help_text=f"Rename the output file to a {output.stem}.md", ) @@ -48,7 +48,7 @@ def run_command(test_ids: List[str], output: Path, quiet: bool) -> None: click.echo(shell_output) click.echo(error_output) if exit_code != 0: - raise TestClerkAccessorError( + raise CodexaAccessorError( message=f"Failed to generate tests: {error_output}", help_text=f"Please check the output for more information", ) @@ -59,7 +59,7 @@ def run_command(test_ids: List[str], output: Path, quiet: bool) -> None: with open(output, "w") as f: f.write(response) except IOError as e: - raise TestClerkAccessorError( + raise CodexaAccessorError( message=f"Failed to generate test file: {e}", ) diff --git a/tests/conftest.py b/tests/conftest.py index ec7109c..3274a33 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -21,7 +21,7 @@ def logger() -> MockLogger: @pytest.fixture def mock_datetime() -> Iterator[MagicMock]: - with patch("testclerk.core.models.dt.datetime") as mock_datetime: + with patch("codexa.core.models.dt.datetime") as mock_datetime: yield mock_datetime @@ -37,7 +37,7 @@ def mock_pytest_file(tmp_path: Path) -> Iterator[Path]: @pytest.fixture def mock_openai_client() -> Iterator[MagicMock]: - with patch("testclerk.client.accessor.OpenAI") as mock_client: + with patch("codexa.client.accessor.OpenAI") as mock_client: mock_client_instance = MagicMock() mock_response = MagicMock() mock_response.choices = [ diff --git a/tests/test_clients.py b/tests/test_clients.py index 810b6d1..cb9cfe2 100644 --- a/tests/test_clients.py +++ b/tests/test_clients.py @@ -2,8 +2,8 @@ import pytest -from testclerk.client.accessor import RemoteAIAccessor, ReportScanner -from testclerk.core.errors import TestClerkAccessorError +from codexa.client.accessor import RemoteAIAccessor, ReportScanner +from codexa.core.errors import CodexaAccessorError def test_accessor_init_ok(): @@ -19,7 +19,7 @@ def test_accessor_init_ok(): ], ) def test_accessor_init_fail_missing_input(mock_api_key: str, mock_prompt: str): - with pytest.raises(TestClerkAccessorError): + with pytest.raises(CodexaAccessorError): _ = RemoteAIAccessor(api_key=mock_api_key, prompt=mock_prompt) @@ -49,6 +49,6 @@ def test_analyze_tests_create_failure(mock_openai_client: MagicMock): mock_openai_client.return_value = mock_client_instance generator = ReportScanner(api_key="dummy-key") - with pytest.raises(TestClerkAccessorError, match="Failed to generate code"): + with pytest.raises(CodexaAccessorError, match="Failed to generate code"): _ = generator.analyze_tests(test_output="Some mock report output") mock_client_instance.chat.completions.create.assert_called_once() diff --git a/tests/test_executor.py b/tests/test_executor.py index 2be090d..8f5eb31 100644 --- a/tests/test_executor.py +++ b/tests/test_executor.py @@ -1,6 +1,6 @@ from pathlib import Path -from testclerk.client.executor import TestExecutor +from codexa.client.executor import TestExecutor def test_executor_collect_all_tests(tmp_path: Path, mock_pytest_file: Path): diff --git a/tests/test_utils.py b/tests/test_utils.py index 1ffa4da..9de07b6 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,7 +1,7 @@ from pytest import MonkeyPatch, raises -from testclerk.core.env import load_api_key -from testclerk.core.errors import TestClerkEnvironmentError +from codexa.core.env import load_api_key +from codexa.core.errors import CodexaEnvironmentError def test_load_api_key_success(monkeypatch: MonkeyPatch): @@ -15,5 +15,5 @@ def test_load_api_key_success(monkeypatch: MonkeyPatch): def test_load_api_key_not_set_failure(monkeypatch: MonkeyPatch): """Test that the API key is loaded correctly.""" monkeypatch.delenv("TESTCLERK_API_KEY", raising=False) - with raises(TestClerkEnvironmentError): + with raises(CodexaEnvironmentError): _ = load_api_key() diff --git a/tests/tools.py b/tests/tools.py index be0c870..1b12772 100644 --- a/tests/tools.py +++ b/tests/tools.py @@ -5,8 +5,8 @@ import pytest from click.testing import CliRunner, Result -from testclerk.core.output import ColorHandler -from testclerk.main import cli +from codexa.core.output import ColorHandler +from codexa.main import cli class CommandRunner: From bd21f87b450ae6941b1fdab97fbe70d3894282ab Mon Sep 17 00:00:00 2001 From: Chino Franco Date: Sat, 9 Aug 2025 12:01:51 +0900 Subject: [PATCH 2/2] fix: update repo templates and docs --- .github/pull_request_template.md | 26 +++++++++++- CODEOWNERS | 3 ++ LICENSE | 29 +++++++++++++ README.md | 70 +++++++++++++++++++++++++++++++- 4 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 CODEOWNERS create mode 100644 LICENSE diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 72554b5..ee7822b 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,6 +1,6 @@ -# PULL REQUEST +# CODEXA PULL REQUEST ## Description @@ -15,9 +15,31 @@ ## Testing - +## Review Checklist + +The following checklist is used to ensure that the PR meets the project's quality standards. +Please check each item as you complete it. + +- [ ] Funcationality changes have been tested locally +- [ ] Code changes are covered by `pytest` +- [ ] Documentation updated + - [ ] README.md updated + - [ ] Configuration files updated +- [ ] Linted with `pre-commit` + +The following items are case-by-case and may not apply to all PRs. Please check them if they +are relevant; this is primarily for the reviewers to understand the nature of the PR. + +- [ ] This PR contains a **breaking change** and includes migration steps +- [ ] This PR is a **bug fix** and includes a test that reproduces the bug +- [ ] This PR is a **new feature** and includes a test that verifies the feature works +- [ ] This PR is a **refactor** and does not change functionality +- [ ] This PR is a **documentation update** and does not change functionality +- [ ] This PR is a **performance improvement** and includes benchmarks +- [ ] This PR is a **security fix** and includes tests that verify the fix + ## Future Work diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..e5430e4 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,3 @@ +# Codexa Developers + +* @jgfranco17 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..55b8c07 --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +# BSD 3-Clause License + +Copyright (c) 2025, Joaquin Gabriel A. Franco. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index f893e35..51befa6 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,71 @@ # Codexa -Simple CLI to analyze code for testing. +**Codexa** is a CLI-powered test execution and analysis toolkit that runs your automated tests, +feeds the results into a Large Language Model, and returns actionable, human-friendly insights +for your next development steps. + +Think of it as your **post-test intelligence officer** — it doesn't just tell you _what_ failed, +it explains _why_ and _what to do about it_. + +--- + +## Features + +- **Config-driven execution**: Define your tests, frameworks, and environments in a single + configuration file. +- **LLM-powered analysis**: Get context-aware interpretations of failures, flakiness, and + performance issues. +- **Actionable next steps**: Receive step-by-step recommendations, not just raw logs. +- **Framework-agnostic**: Works with popular testing frameworks (e.g., Pytest, Jest, Go test, + JUnit) or custom commands. +- **Clear reporting**: Generates concise summaries and detailed diagnostics directly in your + terminal. + +--- + +## Quick Start + +### Install + +Install Codexa using `pip`: + +```shell +pip install codexa +``` + +### Configure + +```yaml filename=".codexa.yaml" +tests: + - id: python + command: pytest --maxfail=1 --disable-warnings + - id: javascript + command: npm test -- --maxWorkers=1 + - id: go + command: go test -v -timeout 30s + +llm: + provider: openai + model: gpt-4o + api_key: $OPENAI_API_KEY +``` + +### Run + +Codexa will automatically detect your configuration file and execute the tests defined within +it. It will then analyze the results using the specified LLM provider and model. + +```shell +codexa run +``` + +Codexa uses a YAML configuration file to define: + +- Test commands to run +- LLM provider and model +- Output format and destination +- Filters for warnings, failures, and flaky tests + +## License + +MIT License — see LICENSE for details.