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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ Multiple specialized agents — including Explorer, Planner, Coder, and Reviewer

Most AI coding tools are a **single model behind a chat box**. GitPilot is fundamentally different: it deploys a **team of four specialized AI agents** that collaborate on every task — just like a real engineering team.

> **Matrix‑native:** GitPilot is the worker for [Matrix Builder](https://github.com/agent-matrix/matrix-builder) — it runs a signed Matrix Bundle under contract via `POST /api/v1/gitpilot/runs` (A2A‑secured), returns a controlled diff, and never approves or commits its own work.

| Agent | Role | What it does |
|---|---|---|
| **Explorer** | Context | Reads your full repo, git log, test suite, and dependencies so the plan starts with real knowledge — not guesses |
Expand Down
6 changes: 5 additions & 1 deletion deploy/huggingface/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,13 @@ EXPOSE 7860
# Docker HEALTHCHECK directive.

# Direct CMD — no shell script, fewer failure points.
# Single worker: the Matrix run registry (gitpilot.matrix_runs_router._RUNS) is
# in-memory per worker, so multiple workers would scatter a run's status/diff/
# logs across processes and 404 intermittently. One worker keeps run state
# consistent; back the registry with a shared store to scale workers later.
CMD ["python", "-m", "uvicorn", "gitpilot.api:app", \
"--host", "0.0.0.0", \
"--port", "7860", \
"--workers", "2", \
"--workers", "1", \
"--limit-concurrency", "10", \
"--timeout-keep-alive", "120"]
45 changes: 45 additions & 0 deletions docs/commit-attribution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Make GitPilot's commits appear as "GitPilot" (with an icon)

GitHub picks the avatar on a commit / PR / contributors graph from the commit
**author/committer** or a **`Co-authored-by:`** trailer whose email maps to a
GitHub account. GitPilot uses both:

## 1. GitHub App — the icon (recommended)

GitPilot ships a GitHub App (`GITHUB_APP_SLUG=gitpilota`, `GITHUB_APP_ID=2313985`).
When commits/PRs are created through the **App installation token**, GitHub
attributes them to **`gitpilota[bot]`** and shows the App's avatar automatically.

To get the icon: **GitHub → Settings → Developer settings → GitHub Apps →
GitPilot → Display information → upload a logo.** That logo is the icon you'll see
on every `gitpilota[bot]` commit and PR — the same way Claude Code shows its mark.

Install the App on the target repos and have GitPilot use the installation token
for writes (the API helpers in `github_app.py` / `github_pulls.py` already accept
a token).

## 2. Co-authored-by trailer — credit on human-authored commits

When a human's token authors the commit, GitPilot appends a trailer so it's still
credited as a contributor (this is what Claude Code does):

```text
<your message>

🤖 Generated with GitPilot

Co-authored-by: GitPilot <gitpilota[bot]@users.noreply.github.com>
```

This is applied automatically by `gitpilot.commit_attribution.with_attribution()`
on the file-commit path. Configure:

| Env | Default | Purpose |
|---|---|---|
| `GITPILOT_COMMIT_ATTRIBUTION` | `true` | Toggle the trailer on/off |
| `GITPILOT_BOT_NAME` | `GitPilot` | Display name in the trailer |
| `GITPILOT_BOT_EMAIL` | `gitpilota[bot]@users.noreply.github.com` | Set to a real GitPilot bot account's email so its avatar resolves |

> The avatar next to a co-author only renders if the email maps to a GitHub
> account. Use the App bot's no‑reply email (default) or a dedicated `gitpilot`
> bot account.
85 changes: 85 additions & 0 deletions docs/deploy/gitpilot-ruslanmv-com.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Production deploy — `gitpilot.ruslanmv.com`

This is the runbook for GitPilot's public production deployment and the target
for the Matrix Builder → GitPilot cloud handoff.

## Architecture

```text
gitpilot.ruslanmv.com (Vercel, Vite/React UI)
│ cross-origin calls via VITE_BACKEND_URL
ruslanmv-gitpilot.hf.space (Hugging Face Docker Space, FastAPI backend)
│ GITPILOT_PROVIDER=ollabridge
ruslanmv-ollabridge.hf.space (OllaBridge, OpenAI-compatible /v1 gateway)
```

- **Frontend (Vercel):** the `frontend/` Vite app, built per `vercel.json`.
The DNS is already pointed: `gitpilot.ruslanmv.com` CNAME →
`b2aab4fcc3b40c0d.vercel-dns-017.com`, with the `_vercel` verification TXT
present. Set **`VITE_BACKEND_URL=https://ruslanmv-gitpilot.hf.space`** in the
Vercel project so the UI calls the HF backend.
- **Backend (HF Space):** the multi-stage Docker image in
`deploy/huggingface/Dockerfile`, listening on port 7860. It is force-deployed
by the `.github/workflows/sync-hf-space.yml` GitHub Action **on push to
`main`** (or manual `workflow_dispatch`). The Space needs the repo secrets
`HF_TOKEN`, `HF_USERNAME` (`ruslanmv`), `SPACE_NAME` (`gitpilot`).
- **Inference (OllaBridge):** the HF Space sets `GITPILOT_PROVIDER=ollabridge`
and `OLLABRIDGE_BASE_URL=https://ruslanmv-ollabridge.hf.space`. GitPilot routes
LLM calls through litellm with an `openai/<model>` prefix pointed at
`${OLLABRIDGE_BASE_URL}/v1`.

## Backend environment (HF Space)

Set on the Space (already baked into `deploy/huggingface/Dockerfile`):

| Variable | Value |
|---|---|
| `GITPILOT_PROVIDER` | `ollabridge` |
| `OLLABRIDGE_BASE_URL` | `https://ruslanmv-ollabridge.hf.space` |
| `GITPILOT_OLLABRIDGE_MODEL` | `qwen2.5:1.5b` |
| `CORS_ORIGINS` | `*` (allows `gitpilot.ruslanmv.com` → Space cross-origin) |
| `GITPILOT_CONFIG_DIR` | `/tmp/gitpilot` |

For the Matrix cloud handoff (Batch 5+), also set on the Space:

| Variable | Value |
|---|---|
| `GITPILOT_A2A_REQUIRE_AUTH` | `true` |
| `GITPILOT_A2A_SHARED_SECRET` | a long random secret (shared with Matrix Builder) |

## Health & verification

```bash
# Frontend (Vercel)
curl -I https://gitpilot.ruslanmv.com/ # 200

# Backend (HF Space)
curl https://ruslanmv-gitpilot.hf.space/api/health # {"status":"healthy",...}
curl https://ruslanmv-gitpilot.hf.space/api/health/deep # provider:"ollabridge", provider_reachable:true

# Inference (OllaBridge) — the chat/plan path
curl https://ruslanmv-ollabridge.hf.space/v1/chat/completions \
-H 'content-type: application/json' -H 'authorization: Bearer ollabridge' \
-d '{"model":"qwen2.5:1.5b","messages":[{"role":"user","content":"ping"}],"max_tokens":8}'
```

Last verified (Batch 4): `/api/health` and `/api/health/deep` return **200** with
`provider:"ollabridge"`, `provider_reachable:true`, `crewai_loaded:true`; the
OllaBridge `/v1/chat/completions` path returns a completion;
`gitpilot.ruslanmv.com` serves the UI (200).

## Deploying the Matrix facade to production

The Matrix-native run facade (`/api/v1/gitpilot/runs`, `/api/matrix/runs` and
their `/health` siblings) ships with the backend source, so it reaches the Space
the moment the branch lands on `main`:

1. Merge the feature branch into `main`.
2. The `sync-hf-space.yml` action force-pushes the deploy tree to the Space and
it rebuilds (~a few minutes).
3. Confirm: `curl https://ruslanmv-gitpilot.hf.space/api/matrix/health` → `200`.

A manual `workflow_dispatch` of the same action deploys without a code change
(e.g. to re-sync the Space).
19 changes: 18 additions & 1 deletion gitpilot/_api_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# Re-exported here so endpoint authors can `@wrap_errors_envelope` without
# reaching into the implementation module. Importing the symbol is a no-op
# when the flag is off, so this is fully backwards compatible.
from .commit_attribution import with_attribution
from .errors import GitPilotError, wrap_errors_envelope # noqa: F401
from .github_api import (
list_user_repos,
Expand Down Expand Up @@ -346,6 +347,20 @@ def _env_bool(name: str, default: bool) -> bool:
except Exception: # noqa: BLE001
logger.exception("Coder API failed to mount; /repair will be unavailable")

# Matrix runs facade (the Matrix-native AI coder path): POST /api/v1/gitpilot/runs.
# Maps a signed Matrix Bundle + contract onto the repair pipeline, always denying
# the Matrix control files, gated by the A2A shared secret. Non-fatal mount.
try:
from .matrix_runs_router import build_matrix_runs_alias_router, build_matrix_runs_router

app.include_router(build_matrix_runs_router())
# Local-bridge alias (/api/matrix/*) used by Matrix Builder's "Send to local
# GitPilot": same handlers, second namespace.
app.include_router(build_matrix_runs_alias_router())
logger.info("Matrix runs API enabled (mounting /api/v1/gitpilot/* + /api/matrix/*)")
except Exception: # noqa: BLE001
logger.exception("Matrix runs API failed to mount; /api/v1/gitpilot/runs will be unavailable")

# GitPilot-as-MCP-server (turns GitPilot into an MCP server other agents
# can drive). Off by default; mount only when GITPILOT_EXPOSE_MCP_SERVER=true.
try:
Expand Down Expand Up @@ -1087,8 +1102,10 @@ async def api_put_file(
authorization: Optional[str] = Header(None),
):
token = get_github_token(authorization)
# Attribute the commit to GitPilot (Co-authored-by trailer) so it shows up as
# a GitPilot contribution — like Claude Code. See gitpilot.commit_attribution.
result = await put_file(
owner, repo, payload.path, payload.content, payload.message, token=token
owner, repo, payload.path, payload.content, with_attribution(payload.message), token=token
)
return CommitResponse(**result)

Expand Down
65 changes: 65 additions & 0 deletions gitpilot/commit_attribution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""GitPilot commit attribution — make GitPilot's commits show up as "GitPilot".

GitHub decides the avatar in the contributors graph and on commits/PRs from the
commit author/committer, or from a ``Co-authored-by:`` trailer whose email maps
to a GitHub account. So GitPilot's work can appear as "GitPilot" with an icon in
two complementary ways:

1. **GitHub App (the icon).** When commits/PRs are created through GitPilot's
GitHub App installation token, GitHub attributes them to ``gitpilota[bot]``
and shows the App's avatar automatically — set the picture in the App's
settings (Developer settings → GitHub Apps → GitPilot → Display information).
This is the cleanest "appears as GitPilot with an icon" path; no commit-message
change is needed because the App *is* the author.

2. **Co-authored-by trailer (this module).** When a human's token authors the
commit, append a ``Co-authored-by: GitPilot <…>`` trailer so GitPilot is still
credited as a contributor with its avatar — exactly how Claude Code attributes
its commits.

Configure with: ``GITPILOT_COMMIT_ATTRIBUTION`` (on by default), ``GITPILOT_BOT_NAME``,
``GITPILOT_BOT_EMAIL`` (default derives from ``GITHUB_APP_SLUG`` so the avatar
resolves to the App bot).
"""

from __future__ import annotations

import os

SIGNATURE = "\U0001f916 Generated with GitPilot"


def attribution_enabled() -> bool:
return os.getenv("GITPILOT_COMMIT_ATTRIBUTION", "true").strip().lower() in {
"1",
"true",
"yes",
"on",
}


def bot_name() -> str:
return os.getenv("GITPILOT_BOT_NAME", "GitPilot")


def bot_email() -> str:
# Default to the App bot's noreply so the avatar resolves to <slug>[bot].
# Override with GITPILOT_BOT_EMAIL to point at a real GitPilot bot account.
slug = os.getenv("GITHUB_APP_SLUG", "gitpilota")
return os.getenv("GITPILOT_BOT_EMAIL", f"{slug}[bot]@users.noreply.github.com")


def co_authored_by() -> str:
return f"Co-authored-by: {bot_name()} <{bot_email()}>"


def with_attribution(message: str) -> str:
"""Append the GitPilot signature + Co-authored-by trailer to a commit message.

Idempotent (won't double‑add) and a no‑op when attribution is disabled.
"""
msg = (message or "").rstrip()
if not attribution_enabled() or co_authored_by() in msg:
return msg
trailer = f"{SIGNATURE}\n\n{co_authored_by()}"
return f"{msg}\n\n{trailer}" if msg else trailer
Loading
Loading