Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,10 @@ These words mean you are GUESSING, not providing facts.

## Development Commands

### Environment Setup
### Running Locally

```bash
# Start development environment (recommended)
./start.sh

# Manual setup if needed
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
```

### Code Quality
Expand Down
10 changes: 10 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ RUN curl -fsSL https://rpm.nodesource.com/setup_24.x | bash - && \
dnf install -y nodejs && \
npm install -g yarn

# Install PHP CLI and Composer for PHPUnit test execution
# Same packages are also installed in CodeBuild (infrastructure/setup-vpc-nat-efs.yml)
# php-cli: PHP command-line interpreter to run PHPUnit
# php-json: JSON encoding/decoding used by PHPUnit for config and result output
# php-mbstring: Multibyte string functions required by PHPUnit for string diffing
# php-xml: DOM/XML parsing used by PHPUnit for phpunit.xml config and JUnit report generation
# php-pdo: Database abstraction layer needed by customer test suites (e.g., SpiderPlus)
RUN dnf install -y php-cli php-json php-mbstring php-xml php-pdo && \
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# Install cloc directly without adding the entire EPEL repository
RUN curl -L https://github.com/AlDanial/cloc/releases/download/v1.98/cloc-1.98.pl -o /usr/local/bin/cloc && \
chmod +x /usr/local/bin/cloc
Expand Down
23 changes: 1 addition & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,6 @@
# GitAuto AI

## 1. What is GitAuto

[GitAuto](https://gitauto.ai) is a GitHub coding agent that opens pull requests from backlog tickets for software engineering managers to complete more bug fixes and feature requests. Assign tasks to GitAuto first, and have people work on more complex tickets.

- Want to give GitAuto a try? Go to [GitAuto installation](https://github.com/apps/gitauto-ai).
- Want to see demo videos? Go to [GitAuto YouTube](https://www.youtube.com/@gitauto).
- Want to know more about GitAuto? Go to [GitAuto homepage](https://gitauto.ai).
- Want to chat about your use case? Feel free to contact us at [email](mailto:info@gitauto.ai), [admin](https://github.com/hiroshinishio), [X](https://x.com/gitautoai), or [LinkedIn](https://www.linkedin.com/company/gitauto/).

## 2. How to use GitAuto

1. Install GitAuto to your repositories from [GitHub Marketplace](https://github.com/apps/gitauto-ai).
1. Choose the repositories where you want to use GitAuto.
2. You can change the repositories later.
2. Trigger GitAuto in one of these ways:
- **Dashboard**: Go to the [GitAuto dashboard](https://gitauto.ai/dashboard/coverage) to select files with low test coverage and create PRs.
- **Schedule**: Enable daily automation on the dashboard. GitAuto creates PRs automatically once a day at 00:00 UTC for files with low test coverage.
3. GitAuto creates a PR and works on the changes. You will get a notification once GitAuto completes.
4. Review the PR and merge it if it looks good.
5. If changes are needed, leave a review comment on the PR. GitAuto responds to review comments and updates the code.

## 3. How to run GitAuto locally
## How to run GitAuto locally

### 3-1. Create your GitHub app for local development

Expand Down
2 changes: 2 additions & 0 deletions constants/files.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
JS_TS_FILE_EXTENSIONS = (".js", ".jsx", ".ts", ".tsx")

PHP_TEST_FILE_EXTENSIONS = ("Test.php",)

TS_TEST_FILE_EXTENSIONS = (
".test.ts",
".test.tsx",
Expand Down
27 changes: 19 additions & 8 deletions infrastructure/setup-vpc-nat-efs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -419,17 +419,28 @@ Resources:
- echo "Installing packages in $EFS_DIR using $PKG_MANAGER"
- LOCAL_DIR="/tmp$EFS_DIR"
- mkdir -p "$LOCAL_DIR"
- tar -cf - -C "$EFS_DIR" --exclude node_modules . | tar -xf - -C "$LOCAL_DIR"
- cd "$LOCAL_DIR"
- |
if [ -n "$NPM_TOKEN" ]; then
export NPM_TOKEN="$NPM_TOKEN"
if [ "$PKG_MANAGER" = "composer" ]; then
tar -cf - -C "$EFS_DIR" --exclude vendor . | tar -xf - -C "$LOCAL_DIR"
cd "$LOCAL_DIR"
# Same PHP packages as Dockerfile - see comments there for details
yum install -y php-cli php-json php-mbstring php-xml php-pdo
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
composer install --no-interaction --no-dev
rm -f "$EFS_DIR/vendor.tar.gz"
tar -czf "$EFS_DIR/vendor.tar.gz" -C "$LOCAL_DIR" vendor
else
tar -cf - -C "$EFS_DIR" --exclude node_modules . | tar -xf - -C "$LOCAL_DIR"
cd "$LOCAL_DIR"
if [ -n "$NPM_TOKEN" ]; then
export NPM_TOKEN="$NPM_TOKEN"
fi
$PKG_MANAGER install
rm -f "$EFS_DIR/node_modules.tar.gz"
tar -czf "$EFS_DIR/node_modules.tar.gz" -C "$LOCAL_DIR" node_modules
fi
- $PKG_MANAGER install
- rm -f "$EFS_DIR/node_modules.tar.gz"
- tar -czf "$EFS_DIR/node_modules.tar.gz" -C "$LOCAL_DIR" node_modules
- rm -rf "$LOCAL_DIR"
- echo "Package install complete - tarball at $EFS_DIR/node_modules.tar.gz"
- echo "Package install complete for $PKG_MANAGER in $EFS_DIR"
TimeoutInMinutes: 60
VpcConfig:
VpcId: !Ref VPC
Expand Down
12 changes: 12 additions & 0 deletions services/agents/test_verify_task_is_complete.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from services.eslint.run_eslint_fix import ESLintResult
from services.github.types.github_types import BaseArgs
from services.jest.run_jest_test import JestResult
from services.phpunit.run_phpunit_test import PhpunitResult
from services.prettier.run_prettier_fix import PrettierResult
from services.tsc.run_tsc_check import TscResult

Expand Down Expand Up @@ -51,6 +52,17 @@ def mock_jest_test():
yield


@pytest.fixture(autouse=True)
def mock_phpunit_test():
"""Auto-mock run_phpunit_test for all tests to prevent actual test execution."""
with patch(
"services.agents.verify_task_is_complete.run_phpunit_test",
new_callable=AsyncMock,
return_value=PhpunitResult(success=True, errors=[], error_files=set()),
):
yield


@pytest.fixture(autouse=True)
def mock_get_eslint_config():
"""Auto-mock get_eslint_config for all tests."""
Expand Down
68 changes: 57 additions & 11 deletions services/agents/test_verify_task_is_ready.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ async def test_valid_file_returns_success(
},
)
result = await verify_task_is_ready(
base_args=base_args, file_paths=["src/index.ts"]
base_args=base_args,
file_paths=["src/index.ts"],
run_tsc=False,
run_jest=False,
run_phpunit=False,
)
assert result.success is True
assert result.errors == []
Expand Down Expand Up @@ -70,7 +74,11 @@ async def test_prettier_fails_returns_errors(
},
)
result = await verify_task_is_ready(
base_args=base_args, file_paths=["src/broken.ts"]
base_args=base_args,
file_paths=["src/broken.ts"],
run_tsc=False,
run_jest=False,
run_phpunit=False,
)
assert result.success is False
assert len(result.errors) == 1
Expand Down Expand Up @@ -105,7 +113,11 @@ async def test_eslint_fails_returns_errors(
},
)
result = await verify_task_is_ready(
base_args=base_args, file_paths=["src/broken.ts"]
base_args=base_args,
file_paths=["src/broken.ts"],
run_tsc=False,
run_jest=False,
run_phpunit=False,
)
assert result.success is False
assert len(result.errors) == 1
Expand All @@ -127,7 +139,11 @@ async def test_non_js_files_skipped(mock_get_raw_content):
},
)
result = await verify_task_is_ready(
base_args=base_args, file_paths=["README.md", "config.json"]
base_args=base_args,
file_paths=["README.md", "config.json"],
run_tsc=False,
run_jest=False,
run_phpunit=False,
)
assert result.success is True
assert result.errors == []
Expand All @@ -147,7 +163,13 @@ async def test_empty_file_list():
"base_branch": "main",
},
)
result = await verify_task_is_ready(base_args=base_args, file_paths=[])
result = await verify_task_is_ready(
base_args=base_args,
file_paths=[],
run_tsc=False,
run_jest=False,
run_phpunit=False,
)
assert result.success is True
assert result.errors == []
assert result.fixes_applied == []
Expand All @@ -173,7 +195,11 @@ async def test_file_not_found_skipped(
},
)
result = await verify_task_is_ready(
base_args=base_args, file_paths=["src/missing.ts"]
base_args=base_args,
file_paths=["src/missing.ts"],
run_tsc=False,
run_jest=False,
run_phpunit=False,
)
assert result.success is True
assert result.errors == []
Expand Down Expand Up @@ -208,7 +234,11 @@ async def test_fixes_applied_and_pushed(
},
)
result = await verify_task_is_ready(
base_args=base_args, file_paths=["src/index.ts"]
base_args=base_args,
file_paths=["src/index.ts"],
run_tsc=False,
run_jest=False,
run_phpunit=False,
)
assert result.success is True
assert result.errors == []
Expand Down Expand Up @@ -245,7 +275,11 @@ async def test_eslint_partial_fix_pushes_and_reports_errors(
},
)
result = await verify_task_is_ready(
base_args=base_args, file_paths=["src/index.ts"]
base_args=base_args,
file_paths=["src/index.ts"],
run_tsc=False,
run_jest=False,
run_phpunit=False,
)
# Lint-only errors (no-unused-vars) are ignored by verify_task_is_ready
# Only coverage-relevant errors cause failure
Expand Down Expand Up @@ -284,7 +318,11 @@ async def test_no_explicit_any_ignored(
},
)
result = await verify_task_is_ready(
base_args=base_args, file_paths=["src/utils/auth0.ts"]
base_args=base_args,
file_paths=["src/utils/auth0.ts"],
run_tsc=False,
run_jest=False,
run_phpunit=False,
)
# no-explicit-any is a lint-only error, not coverage-relevant
# Previously this caused the agent to loop for 900s trying to fix unfixable errors
Expand Down Expand Up @@ -327,7 +365,11 @@ async def test_run_tsc_reports_type_errors(
},
)
result = await verify_task_is_ready(
base_args=base_args, file_paths=["src/index.ts"], run_tsc=True
base_args=base_args,
file_paths=["src/index.ts"],
run_tsc=True,
run_jest=False,
run_phpunit=False,
)
assert result.success is False
assert len(result.errors) == 1
Expand Down Expand Up @@ -372,7 +414,11 @@ async def test_run_jest_reports_test_failures(
},
)
result = await verify_task_is_ready(
base_args=base_args, file_paths=["src/index.test.ts"], run_jest=True
base_args=base_args,
file_paths=["src/index.test.ts"],
run_tsc=False,
run_jest=True,
run_phpunit=False,
)
assert result.success is False
assert len(result.errors) == 2
Expand Down
22 changes: 21 additions & 1 deletion services/agents/verify_task_is_complete.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import os
from dataclasses import dataclass, field

from constants.files import JS_TEST_FILE_EXTENSIONS, TS_TEST_FILE_EXTENSIONS
from constants.files import (
JS_TEST_FILE_EXTENSIONS,
PHP_TEST_FILE_EXTENSIONS,
TS_TEST_FILE_EXTENSIONS,
)
from services.eslint.ensure_eslint_relaxed_for_tests import (
ensure_eslint_relaxed_for_tests,
)
Expand All @@ -20,6 +24,7 @@
from services.node.ensure_tsconfig_relaxed_for_tests import (
ensure_tsconfig_relaxed_for_tests,
)
from services.phpunit.run_phpunit_test import run_phpunit_test
from services.prettier.run_prettier_fix import run_prettier_fix
from services.tsc.create_tsc_issue import create_tsc_issue
from services.tsc.run_tsc_check import run_tsc_check
Expand Down Expand Up @@ -232,6 +237,21 @@ async def verify_task_is_complete(base_args: BaseArgs, **_kwargs):
)
formatting_applied.append(f"- {snap_path}: Snapshot updated")

# Run PHPUnit tests on PHP test files
php_test_files = [
f["filename"]
for f in pr_files
if f["filename"].endswith(PHP_TEST_FILE_EXTENSIONS) and f["status"] != "removed"
]
phpunit_result = await run_phpunit_test(
base_args=base_args,
test_file_paths=php_test_files,
)
if phpunit_result.errors:
for err in phpunit_result.errors:
remaining_errors.append(f"- phpunit: {err}")
error_files.update(phpunit_result.error_files)

if remaining_errors:
error_msg = "\n".join(remaining_errors)
logger.warning("Remaining errors after fixes:\n%s", error_msg)
Expand Down
21 changes: 18 additions & 3 deletions services/agents/verify_task_is_ready.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from dataclasses import dataclass, field

from constants.files import PHP_TEST_FILE_EXTENSIONS
from services.eslint.run_eslint_fix import run_eslint_fix
from services.github.commits.replace_remote_file import replace_remote_file_content
from services.github.files.get_raw_content import get_raw_content
from services.github.types.github_types import BaseArgs
from services.jest.run_jest_test import run_jest_test
from services.phpunit.run_phpunit_test import run_phpunit_test
from services.prettier.run_prettier_fix import run_prettier_fix
from services.tsc.run_tsc_check import run_tsc_check
from utils.error.handle_exceptions import handle_exceptions
Expand All @@ -29,9 +31,10 @@ async def verify_task_is_ready(
*,
base_args: BaseArgs,
file_paths: list[str],
run_tsc: bool = False,
run_jest: bool = False,
) -> VerifyTaskIsReadyResult:
run_tsc: bool,
run_jest: bool,
run_phpunit: bool,
):
owner = base_args.get("owner", "")
repo = base_args.get("repo", "")
token = base_args.get("token", "")
Expand Down Expand Up @@ -128,6 +131,18 @@ async def verify_task_is_ready(
errors.append(f"- {jest_result.runner_name}: {err}")
files_with_errors.update(jest_result.error_files)

# Run PHPUnit tests if requested
if run_phpunit:
php_test_files = [f for f in file_paths if f.endswith(PHP_TEST_FILE_EXTENSIONS)]
phpunit_result = await run_phpunit_test(
base_args=base_args,
test_file_paths=php_test_files,
)
if phpunit_result.errors:
for err in phpunit_result.errors:
errors.append(f"- phpunit: {err}")
files_with_errors.update(phpunit_result.error_files)

if errors:
return VerifyTaskIsReadyResult(
success=False,
Expand Down
Loading
Loading