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
4 changes: 2 additions & 2 deletions .github/workflows/fix-remote-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ jobs:
cd ../bot-repo

echo "Running classifier..."
if ! classify-pr-failure \
if ! aieng-bot classify \
--pr-info "$PR_INFO" \
--failed-checks "$FAILED_CHECKS" \
--failure-logs-file /tmp/failure-logs.txt \
Expand Down Expand Up @@ -360,7 +360,7 @@ jobs:
working-directory: target-repo
run: |
# Apply fixes using skills from .claude/skills/
apply-agent-fix \
aieng-bot fix \
--repo "${{ github.event.inputs.target_repo }}" \
--pr-number "${{ github.event.inputs.pr_number }}" \
--pr-title "${{ steps.pr-details.outputs.pr-title }}" \
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/monitor-org-bot-prs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ jobs:

- name: Process repository queue
run: |
process-repo-queue \
aieng-bot queue \
--repo "${{ matrix.repo }}" \
--workflow-run-id "${{ github.run_id }}" \
--all-prs '${{ needs.discover-bot-prs.outputs.prs }}'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:

- name: Run unit tests with coverage
run: |
uv run pytest --cov src/aieng_bot_maintain --cov-report=xml --cov-report=term tests/
uv run pytest --cov src/aieng_bot --cov-report=xml --cov-report=term tests/

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,5 @@ env/
# Project-specific
CLAUDE.md

aieng_bot_maintain.egg-info/
aieng_bot.egg-info/
coverage.xml
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ gh run list --workflow=monitor-org-bot-prs.yml --limit 5
gh run view RUN_ID --log

# Collect metrics manually
gh workflow run collect-bot-metrics.yml
gh workflow run aieng-bot metrics.yml
```

## Documentation
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ gh run list --workflow=monitor-org-bot-prs.yml --limit 5
gh run view RUN_ID --log

# Collect metrics manually
gh workflow run collect-bot-metrics.yml
gh workflow run aieng-bot metrics.yml
```

## Documentation
Expand Down
12 changes: 5 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "aieng-bot-maintain"
name = "aieng-bot"
version = "0.4.0"
description = "Vector Institute AI Engineering Bot for Maintenance Tasks"
readme = "README.md"
Expand All @@ -23,6 +23,7 @@ classifiers = [
dependencies = [
"anthropic>=0.42.0",
"claude-agent-sdk",
"click>=8.0.0",
"pyyaml>=6.0.2",
"rich>=13.0.0",
]
Expand All @@ -34,16 +35,13 @@ Issues = "https://github.com/VectorInstitute/aieng-bot-maintain/issues"
Changelog = "https://github.com/VectorInstitute/aieng-bot-maintain/releases"

[project.scripts]
classify-pr-failure = "aieng_bot_maintain.cli:classify_pr_failure_cli"
apply-agent-fix = "aieng_bot_maintain.cli:apply_agent_fix_cli"
collect-bot-metrics = "aieng_bot_maintain.cli:collect_metrics_cli"
process-repo-queue = "aieng_bot_maintain.cli:process_repo_queue_cli"
aieng-bot = "aieng_bot._cli.main:cli"

[tool.hatchling.build.targets.wheel]
packages = ["src/aieng_bot_maintain"]
packages = ["src/aieng_bot"]

[tool.hatchling.build.targets.wheel.shared-data]
"src/aieng_bot_maintain/py.typed" = "aieng_bot_maintain/py.typed"
"src/aieng_bot/py.typed" = "aieng_bot/py.typed"

[dependency-groups]
dev = [
Expand Down
4 changes: 2 additions & 2 deletions slack_bot/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
# Load environment variables from .env file
load_dotenv()

# Add parent directory to path to import from aieng_bot_maintain
# Add parent directory to path to import from aieng_bot
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))

from aieng_bot_maintain.utils.logging import ( # noqa: E402
from aieng_bot.utils.logging import ( # noqa: E402
log_error,
log_info,
log_success,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
FailureType,
PRContext,
)
from .config import get_model_name
from .metrics import MetricsCollector
from .observability import AgentExecutionTracer, create_tracer_from_env

Expand All @@ -33,5 +34,6 @@
"RepoQueue",
"PRQueueItem",
"PRStatus",
"get_model_name",
"__version__",
]
5 changes: 5 additions & 0 deletions src/aieng_bot/_cli/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""CLI module for aieng-bot."""

from .main import cli

__all__ = ["cli"]
8 changes: 8 additions & 0 deletions src/aieng_bot/_cli/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""CLI commands for aieng-bot."""

from .classify import classify
from .fix import fix
from .metrics import metrics
from .queue import queue

__all__ = ["classify", "fix", "metrics", "queue"]
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
"""CLI command for PR failure classification."""

import argparse
import json
import sys
import tempfile

import click

from ...classifier import PRFailureClassifier
from ...classifier.models import ClassificationResult
from ...utils.logging import get_console, log_error, log_info, log_success
from ..utils import get_version, parse_pr_inputs
from ..utils import parse_pr_inputs


def _get_failure_logs_file(args: argparse.Namespace) -> str | None:
"""Get failure logs file path from args, return None on error."""
if args.failure_logs_file:
return args.failure_logs_file
def _get_failure_logs_file(
failure_logs: str | None, failure_logs_file: str | None
) -> str | None:
"""Get failure logs file path, return None on error."""
if failure_logs_file:
return failure_logs_file

if args.failure_logs:
# Backward compatibility: write logs to temp file
if failure_logs:
# Write logs to temp file
with tempfile.NamedTemporaryFile(
mode="w", delete=False, suffix=".txt"
) as temp_file:
temp_file.write(args.failure_logs)
temp_file.write(failure_logs)
return temp_file.name

log_error("Either --failure-logs or --failure-logs-file must be provided")
Expand Down Expand Up @@ -61,80 +64,87 @@ def _log_summary(result: ClassificationResult) -> None:
sys.exit(1)


def classify_pr_failure_cli() -> None:
"""CLI entry point for PR failure classification.
@click.command()
@click.option(
"--pr-info",
required=True,
help="PR info JSON string containing repo, pr_number, title, author, etc.",
)
@click.option(
"--failed-checks",
required=True,
help="Failed checks JSON array with check names and conclusions",
)
@click.option(
"--failure-logs",
required=False,
help="Failure logs content (truncated). Use --failure-logs-file for large logs.",
)
@click.option(
"--failure-logs-file",
required=False,
type=click.Path(exists=True),
help="Path to file containing failure logs (alternative to --failure-logs)",
)
@click.option(
"--output-format",
type=click.Choice(["json", "github"], case_sensitive=False),
default="github",
help="Output format: 'github' for GitHub Actions variables, 'json' for structured output",
)
def classify(
pr_info: str,
failed_checks: str,
failure_logs: str | None,
failure_logs_file: str | None,
output_format: str,
) -> None:
r"""Classify PR failure type using Claude API.

Analyzes PR context, failed checks, and logs to determine failure category
(test, lint, security, build, merge_conflict, unknown).

Reads PR context, failed checks, and failure logs from command-line arguments
and outputs classification results in GitHub Actions format or JSON.
Examples:
\b
# Classify with GitHub Actions output
aieng-bot classify --pr-info '$PR_JSON' --failed-checks '$CHECKS_JSON' \\
--failure-logs-file logs.txt

\b
# Classify with JSON output
aieng-bot classify --pr-info '$PR_JSON' --failed-checks '$CHECKS_JSON' \\
--failure-logs "$(cat logs.txt)" --output-format json

"""
parser = argparse.ArgumentParser(
prog="classify-pr-failure",
description="Classify PR failure type using Claude API",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Classify with GitHub Actions output
classify-pr-failure --pr-info '$PR_JSON' --failed-checks '$CHECKS_JSON' \\
--failure-logs "$(cat logs.txt)"

# Classify with JSON output
classify-pr-failure --pr-info '$PR_JSON' --failed-checks '$CHECKS_JSON' \\
--failure-logs "$(cat logs.txt)" --output-format json
""",
)
parser.add_argument(
"--version",
action="version",
version=f"%(prog)s {get_version()}",
help="Show version number and exit",
)
parser.add_argument("--pr-info", required=True, help="PR info JSON string")
parser.add_argument(
"--failed-checks", required=True, help="Failed checks JSON array"
)
parser.add_argument(
"--failure-logs", required=False, help="Failure logs (truncated)"
)
parser.add_argument(
"--failure-logs-file",
required=False,
help="Path to file containing failure logs (alternative to --failure-logs)",
)
parser.add_argument(
"--output-format",
choices=["json", "github"],
default="github",
help="Output format (default: github)",
)

args = parser.parse_args()
console = get_console()

try:
# Parse inputs
pr_context, failed_checks = parse_pr_inputs(args)
# Parse inputs - create argparse.Namespace for compatibility with parse_pr_inputs
import argparse # noqa: PLC0415

args = argparse.Namespace(pr_info=pr_info, failed_checks=failed_checks)
pr_context, failed_check_list = parse_pr_inputs(args)

# Get failure logs file path
failure_logs_file = _get_failure_logs_file(args)
if not failure_logs_file:
failure_logs_path = _get_failure_logs_file(failure_logs, failure_logs_file)
if not failure_logs_path:
sys.exit(1)

# Run classification
log_info(f"Classifying PR {pr_context.repo}#{pr_context.pr_number}")
log_info(f"Number of failed checks: {len(failed_checks)}")
log_info(f"Failure logs file: {failure_logs_file}")
log_info(f"Number of failed checks: {len(failed_check_list)}")
log_info(f"Failure logs file: {failure_logs_path}")

classifier = PRFailureClassifier()
result = classifier.classify(pr_context, failed_checks, failure_logs_file)
result = classifier.classify(pr_context, failed_check_list, failure_logs_path)

log_info(
f"Classification result: {result.failure_type.value} "
f"(confidence: {result.confidence:.2f})"
)

# Output results
_output_results(result, args.output_format, console)
_output_results(result, output_format, console)

# Log summary
_log_summary(result)
Expand Down
Loading