Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
06db684
feat(selfhost): enable Codex review runtime support
JSONbored Jun 28, 2026
e9d69b2
fix(selfhost): make AI reviewer config explicit
JSONbored Jun 28, 2026
df3ae59
fix(observability): restore maintainer dashboard securely
JSONbored Jun 28, 2026
d4913ce
build(selfhost): automate sentry release source maps
JSONbored Jun 28, 2026
5f49818
ci(selfhost): disable release workflow dependency cache
JSONbored Jun 28, 2026
0f465aa
test(selfhost): restore reviewer diagnostics patch coverage
JSONbored Jun 28, 2026
88856ea
fix(observability): include dual Codex review records
JSONbored Jun 28, 2026
463026a
fix(selfhost): default openai reviewer to openai model
JSONbored Jun 28, 2026
601ab7a
fix(signals): keep maintainer work out of contributor lane holds
JSONbored Jun 28, 2026
2912459
ci(selfhost): verify release source map context
JSONbored Jun 28, 2026
e1e4e32
fix(observability): harden reporting export swap
JSONbored Jun 28, 2026
1284cb7
fix(observability): keep reporting exporter healthy
JSONbored Jun 28, 2026
8bae335
ci(selfhost): cover visual release image
JSONbored Jun 28, 2026
90cc467
fix(observability): quote reporting exporter shell inputs
JSONbored Jun 28, 2026
983755a
fix(selfhost): reject ambiguous AI provider config
JSONbored Jun 28, 2026
6b50fed
fix(observability): tolerate reporting usage schema drift
JSONbored Jun 28, 2026
7161a9b
test(observability): skip exporter smoke without sqlite cli
JSONbored Jun 28, 2026
724ccac
fix(selfhost): protect private Codex runtime config
JSONbored Jun 28, 2026
ec3be77
fix(gate): keep readiness advisory
JSONbored Jun 28, 2026
26aa2b7
fix(gate): avoid stale pending check conclusions
JSONbored Jun 28, 2026
14b0f47
fix(observability): refresh maintainer review export
JSONbored Jun 28, 2026
6f8b04a
fix(github): refresh rejected installation tokens
JSONbored Jun 28, 2026
3ae58f0
fix(review): refresh stale surfaces after CI settles
JSONbored Jun 28, 2026
19374be
fix(observability): count failed AI CLI attempts
JSONbored Jun 28, 2026
ef90bc9
fix(gate): defer manual reruns until CI settles
JSONbored Jun 28, 2026
123795b
fix(queue): preserve review jobs during rate limits
JSONbored Jun 28, 2026
6953b4d
fix(review): retry rate-limited public surface refreshes
JSONbored Jun 28, 2026
9d07a9b
fix(review): coalesce self-host review retries
JSONbored Jun 29, 2026
90243b9
fix(queue): coalesce self-host review backlog
JSONbored Jun 29, 2026
2691b47
fix(review): keep readiness verdicts advisory
JSONbored Jun 29, 2026
3a473af
fix(queue): apply shared github rate-limit cooldown
JSONbored Jun 29, 2026
0894f17
fix(queue): honor cooldown for new self-host jobs
JSONbored Jun 29, 2026
aaff59f
fix(queue): reclaim stale processing leases
JSONbored Jun 29, 2026
8ed0d75
fix(queue): count startup lease recovery
JSONbored Jun 29, 2026
a6da5bc
fix(queue): fill workers for due backlog
JSONbored Jun 29, 2026
e0eaf7c
test(queue): cover durable retry pressure paths
JSONbored Jun 29, 2026
71b72cb
fix(webhooks): ignore self-authored CI completions
JSONbored Jun 29, 2026
fde78a7
test(queue): cover retry audit fallback paths
JSONbored Jun 29, 2026
926d2f6
ci(codecov): upload explicit coverage reports
JSONbored Jun 29, 2026
2600da9
fix(gate): wait for active actions suites
JSONbored Jun 29, 2026
4191719
fix(selfhost): repair subscription cli path
JSONbored Jun 29, 2026
d5f2216
fix(review): require complete AI summaries before publish
JSONbored Jun 29, 2026
964da28
fix(review): require real AI assessment before final comments
JSONbored Jun 29, 2026
bb86e83
Merge branch 'main' into codex/selfhost-codex-reviewer
JSONbored Jun 29, 2026
17d082f
fix(selfhost): stabilize review summaries and queue priority
JSONbored Jun 29, 2026
52529d3
chore(sync): merge source-map release baseline
JSONbored Jun 29, 2026
577f993
fix(selfhost): bound retryable review jobs
JSONbored Jun 29, 2026
7ca978e
build(selfhost): bundle review CLIs by default
JSONbored Jun 29, 2026
52a0327
Merge branch 'main' into codex/selfhost-codex-reviewer
JSONbored Jun 29, 2026
1912f5e
fix(selfhost): require Redis review runtime
JSONbored Jun 29, 2026
463e2ed
Merge remote-tracking branch 'origin/main' into codex/selfhost-codex-…
JSONbored Jun 29, 2026
00495e0
fix(selfhost): remove hosted review policy fallback
JSONbored Jun 29, 2026
8da2d62
fix(review): publish final self-host review surfaces
JSONbored Jun 29, 2026
9b2d4cc
build(selfhost): add prebuilt deploy helper
JSONbored Jun 29, 2026
bb7f5eb
fix(selfhost): keep deploy helper cleanup shell-safe
JSONbored Jun 29, 2026
9d7d044
fix(review): align review-agent manual outcomes
JSONbored Jun 29, 2026
a9fefe8
fix(review): complete legacy gate checks after rename
JSONbored Jun 29, 2026
554ab30
test(selfhost): cover queue retry invariants
JSONbored Jun 29, 2026
000ebae
feat(review): improve maintainer signal table
JSONbored Jun 29, 2026
780d6cc
fix(review): preserve partial ai review notes
JSONbored Jun 29, 2026
9b9a867
fix(selfhost): pin sentry cli in release paths
JSONbored Jun 29, 2026
a16bf92
fix(selfhost): remove retired cloud review bindings
JSONbored Jun 29, 2026
92c1d81
fix(selfhost): harden AI review fallback and RAG config
JSONbored Jun 29, 2026
03a451d
fix(review): clarify self-host webhook cutover
JSONbored Jun 29, 2026
8dfe403
fix(review): preserve malformed AI review context
JSONbored Jun 29, 2026
d516830
test(review): cover AI fallback diagnostics
JSONbored Jun 29, 2026
0c612ce
test(review): cover required AI quota fallback
JSONbored Jun 29, 2026
9bd1a2b
fix(review): preserve webhook redelivery on missing queue
JSONbored Jun 29, 2026
7e40a77
fix(observability): preserve self-host reporting history
JSONbored Jun 29, 2026
bf5aa04
fix(review): add REES failure diagnostics
JSONbored Jun 29, 2026
8985773
fix(queue): scope GitHub cooldown detection
JSONbored Jun 29, 2026
259f871
fix(review): identify REES client requests
JSONbored Jun 29, 2026
aaa824d
test(review): raise self-host patch coverage
JSONbored Jun 29, 2026
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 .claude/skills/contributing-to-gittensory/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ it takes a one-shot disposition:

| Situation | Engine action |
|---|---|
| Gate passes **and** every CI check green **and** mergeable-clean (+ approvals) | **auto-approve → MERGE** |
| Review-agent check passes **and** every CI check green **and** mergeable-clean (+ approvals) | **auto-approve → MERGE** |
| **Any** CI check failed — required or not, **`codecov/patch` included** | **CLOSE** (one-shot) |
| Gate **failure**, or base **conflict** (needs rebase), or a linked-issue **hard-rule** violation | **CLOSE** (one-shot) |
| CI still **pending** | no action — waits for checks to finish |
Expand Down Expand Up @@ -224,7 +224,7 @@ If `ui:lint` fails on formatting, run `npm --workspace @jsonbored/gittensory-ui
**Sync with `main` before you push if it moved** — a base conflict auto-closes a contributor PR:
`git fetch upstream && git rebase upstream/main`, resolve, re-run the gate, then push. On the PR, the
required status check is **`validate`** (it aggregates the CI jobs) and the engine posts a check run
named **`Gittensory Gate`** — watch both go green/passing.
named **`Gittensory Orb Review Agent`** — watch both go green/passing.

---

Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/contributing-to-gittensory/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ for maintainer approval (CI shows unverified → the engine **holds**, never clo
The single **required** status check is **`validate`** (it aggregates `changes, lint, test, workers,
mcp, ui, security`; a path-skipped job counts as success). **Codecov** posts `codecov/patch` (the real
coverage gate) and `codecov/project` (informational) independently. The review engine also posts its
own check run named **`Gittensory Gate`** (`src/github/app.ts` `GITTENSORY_GATE_CHECK_NAME`) — the gate
own check run named **`Gittensory Orb Review Agent`** (`src/github/app.ts` `GITTENSORY_GATE_CHECK_NAME`) — the gate
verdict (§3), separate from CI. On a PR, jobs run only if their
path filter matched; on push to `main`, everything runs.

Expand Down
6 changes: 6 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ dist-ssr
coverage
.git
.claude
# Runtime-only private review config and subscription CLI auth must never enter image layers.
gittensory-config
**/gittensory-config
**/.codex
auth.json
**/auth.json
# The review-enrichment service (REES) is a separate Railway service with its own Dockerfile — keep it out of the engine image.
review-enrichment
.DS_Store
Expand Down
98 changes: 79 additions & 19 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ GITTENSORY_REVIEW_SAFETY=false
GITTENSORY_REVIEW_GROUNDING=false

# Retrieval-augmented context: appends semantically related code/docs from the
# codebase vector index to the reviewer prompt. Inert until a VECTORIZE index exists.
# self-host vector index to the reviewer prompt. Inert until Qdrant or sqlite vectors are populated.
GITTENSORY_REVIEW_RAG=false

# Submitter-reputation spend control (internal-only): downgrades new/burst/low-rep
Expand Down Expand Up @@ -127,8 +127,8 @@ GITTENSORY_REVIEW_DRAFT=false
# DATABASE_PATH=/data/gittensory.sqlite # SQLite file on the mounted data volume; all migrations auto-apply
# DATABASE_URL= # set to postgres://user:pw@host:5432/db to use Postgres instead of
# # SQLite (shared DB → multi-instance). Overrides DATABASE_PATH.
# REDIS_URL= # set to redis://host:6379 for distributed rate limiting + webhook dedup
# # cache (prevents double-processing of GitHub retries). Off when unset.
REDIS_URL=redis://redis:6379 # REQUIRED for the self-host review runtime. The default compose stack
# # starts Redis automatically; override for an external Redis.
# QDRANT_URL= # set to http://qdrant:6333 to use Qdrant as the RAG vector store
# # (--profile qdrant). Overrides the built-in sqlite-vec / pgvector.
# DISCORD_WEBHOOK_URL= # one Discord channel for per-action notifications (merged/closed/
Expand All @@ -142,22 +142,26 @@ GITTENSORY_REVIEW_DRAFT=false
# CRON_INTERVAL_MS=120000 # maintain/sweep + sync cadence (default ~2 min)

# --- Continuous backup (optional; the Litestream sidecar in docker-compose.yml) ---
# Blank is valid until --profile litestream is enabled.
# LITESTREAM_ACCESS_KEY_ID=
# LITESTREAM_SECRET_ACCESS_KEY=
# LITESTREAM_ENDPOINT= # e.g. s3.us-west-002.backblazeb2.com (omit for AWS S3)
# LITESTREAM_REGION=us-east-1

# --- Queue worker (#977/#1201) ---
# QUEUE_CONCURRENCY=1 # max concurrent job-processing loops per instance (default 1)
# QUEUE_CONCURRENCY=4 # max concurrent job-processing loops per instance (default 4; set 1 for strict serial processing)
# QUEUE_BACKGROUND_CONCURRENCY=1 # max low-priority/background jobs allowed to occupy QUEUE_CONCURRENCY slots

# --- Caddy HTTPS terminator (#1203; requires --profile caddy) ---
# DOMAIN=gittensory.example.com # fully-qualified domain; Caddy auto-obtains a Let's Encrypt cert

# --- Tailscale sidecar (#1204; requires --profile tailscale) ---
# Blank is valid until --profile tailscale is enabled.
# TS_AUTHKEY= # Tailscale auth key (generate at tailscale.com/admin/settings/keys)
# TS_EXTRA_ARGS= # extra tailscale up flags, e.g. --advertise-tags=tag:self-host

# --- Self-hosted GitHub Actions runner (#1205; requires --profile runners) ---
# Blank tokens/URLs are valid until --profile runners is enabled.
# RUNNER_TOKEN= # runner registration token (Settings → Actions → Runners → New)
# RUNNER_REPO_URL=https://github.com/org/repo
# RUNNER_ACCESS_TOKEN= # PAT with repo scope (alternative to RUNNER_TOKEN)
Expand All @@ -172,9 +176,11 @@ GITTENSORY_REVIEW_DRAFT=false
# GRAFANA_ADMIN_PASSWORD=changeme # REQUIRED when using --profile observability; compose fails if unset
#
# Maintainer dashboards (in addition to the infra dashboard):
# • "Reviews & PRs (maintainer)" — per-repo + combined PR/review analytics (SQLite data source over the app DB).
# • "Reviews & PRs (maintainer)" — per-repo + combined PR/review analytics from a redacted reporting DB export.
# • "Claude usage (OTEL)" — cost/tokens/model/effort from the review CLI's OpenTelemetry export (see below).
# • "Resource hub" — links to every integrated service.
# GRAFANA_REPORTING_EXPORT_INTERVAL_SECONDS=30 # refresh cadence for the redacted reporting SQLite export
# GITTENSORY_REPORTING_SOURCE_DB=/appdb/gittensory.sqlite # if DATABASE_PATH=/data/custom.sqlite, set /appdb/custom.sqlite
#
# Claude usage telemetry → OTEL collector → Prometheus → the Claude usage dashboard. OFF by default.
# CLAUDE_CODE_ENABLE_TELEMETRY=1 # enable; needs --profile observability (starts the otel-collector)
Expand All @@ -198,9 +204,36 @@ GITTENSORY_REVIEW_DRAFT=false
# The ENGINE posts a per-repo review summary when it publishes a review — set a per-repo map and/or a global fallback:
# DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/... # global fallback for any repo without its own
# DISCORD_REPO_WEBHOOKS={"owner/repoA":"https://discord.com/api/webhooks/...","owner/repoB":"https://..."} # per-repo
#
# Sentry error tracking. OFF when SENTRY_DSN is unset. Official self-host release images bake
# GITTENSORY_VERSION=gittensory-selfhost@<version>; initSentry uses that as the release id unless
# SENTRY_RELEASE is set explicitly (useful for custom/local images).
# SENTRY_DSN=
# SENTRY_DSN_FILE= # optional mounted secret file; existing *_FILE loader reads it
# SENTRY_ENVIRONMENT=selfhost
# SENTRY_RELEASE=
# SENTRY_TRACES_SAMPLE_RATE=0

# --- AI review backend (optional; without it reviews run deterministically) ---
# --- AI review backend (optional; without AI_PROVIDER reviews run deterministically) ---
# AI_SUMMARIES_ENABLED=true
# The self-host image bundles the Claude Code and Codex CLIs by default. Credentials and provider choice remain
# runtime-only: set AI_PROVIDER plus the provider-specific auth below. Set INSTALL_AI_CLIS=false only for a
# custom minimal local build that will never use the subscription CLI providers.
# INSTALL_AI_CLIS=true
#
# Optional review-enrichment service (REES). This Railway/private-service companion adds dependency, secret,
# license, provenance, and other heavyweight analysis context to the AI prompt. Leave disabled unless you run or
# have access to a REES endpoint. The secret must match the REES service's REES_SHARED_SECRET exactly.
# GITTENSORY_REVIEW_ENRICHMENT=false
# REES_URL=
# REES_SHARED_SECRET=
# REES_TIMEOUT_MS=8000
#
# Deprecated shared AI_* knobs are intentionally rejected at startup:
# AI_BASE_URL, AI_API_KEY, AI_MODEL, AI_EFFORT, AI_TIMEOUT_MS. Use the explicit
# provider-specific variables below so Claude, Codex, Ollama, OpenAI, and
# Anthropic settings cannot be mixed up.
#
# AI_PROVIDER=ollama # ollama | openai-compatible | openai | anthropic | claude-code |
# # codex. A COMMA-LIST of TWO providers is a DUAL reviewer pair
# # (e.g. "claude-code,codex") combined per AI_COMBINE below; for a
Expand All @@ -211,19 +244,46 @@ GITTENSORY_REVIEW_DRAFT=false
# # merged decision. single = one reviewer's verdict (auto when 1).
# AI_ON_MERGE=either # synthesis merge rule: either (block if EITHER reviewer flags) |
# # both (block only when both do). Ignored unless AI_COMBINE=synthesis.
# AI_BASE_URL=http://ollama:11434/v1 # OpenAI-compatible endpoint (Ollama default; or your provider's)
# AI_API_KEY= # generic key for the openai-compatible endpoint
# ANTHROPIC_API_KEY= # for AI_PROVIDER=anthropic (native Messages API, BYOK)
# OPENAI_API_KEY= # for AI_PROVIDER=openai
# CLAUDE_CODE_OAUTH_TOKEN= # for AI_PROVIDER=claude-code (subscription; from `claude setup-token`)
#
# Codex (ChatGPT subscription) reviewer is disabled by default for self-host PR review: `codex exec` stores its
# OAuth credential in auth.json on the same filesystem that prompt-influenced reviews can read. Do not mount or copy
# ~/.codex/auth.json into the app container; use claude-code, an API-key provider, or a local OpenAI-compatible model.
# AI_MODEL=llama3.1 # the model for your provider (e.g. llama3.1 for Ollama, sonnet
# # for claude-code, gpt-5 for codex). REQUIRED for non-Ollama:
# # without it the adapter falls back to a provider default, never
# # the Cloudflare Workers-AI id the core would otherwise pass.
# Ollama reviewer (AI_PROVIDER=ollama). Defaults: OLLAMA_AI_BASE_URL=http://localhost:11434/v1,
# OLLAMA_AI_MODEL=llama3.1, no API key. Set the base URL to http://ollama:11434/v1 when using the compose
# --profile ollama service.
# OLLAMA_AI_BASE_URL=http://ollama:11434/v1
# OLLAMA_AI_API_KEY=
# OLLAMA_AI_MODEL=llama3.1
#
# Generic OpenAI-compatible reviewer (AI_PROVIDER=openai-compatible). Defaults:
# OPENAI_COMPATIBLE_AI_BASE_URL=http://localhost:11434/v1, OPENAI_COMPATIBLE_AI_MODEL=llama3.1.
# OPENAI_COMPATIBLE_AI_BASE_URL=http://localhost:11434/v1
# OPENAI_COMPATIBLE_AI_API_KEY=
# OPENAI_COMPATIBLE_AI_MODEL=llama3.1
#
# OpenAI API reviewer (AI_PROVIDER=openai). Defaults: OPENAI_AI_BASE_URL=https://api.openai.com/v1,
# OPENAI_AI_MODEL=gpt-5.5 unless set here.
# OPENAI_API_KEY=
# OPENAI_AI_BASE_URL=https://api.openai.com/v1
# OPENAI_AI_MODEL=gpt-5.5
#
# Anthropic API reviewer (AI_PROVIDER=anthropic). Defaults: ANTHROPIC_AI_BASE_URL=https://api.anthropic.com,
# ANTHROPIC_AI_MODEL=claude-sonnet-4-6 unless set here.
# ANTHROPIC_API_KEY=
# ANTHROPIC_AI_BASE_URL=https://api.anthropic.com
# ANTHROPIC_AI_MODEL=claude-sonnet-4-6
#
# Claude Code subscription reviewer (AI_PROVIDER=claude-code).
# CLAUDE_CODE_OAUTH_TOKEN= # from `claude setup-token`
# CLAUDE_AI_MODEL=claude-sonnet-4-6 # any `claude` CLI model id/alias, e.g. sonnet | opus | claude-opus-4-8
# CLAUDE_AI_EFFORT=high # low | medium | high | xhigh | max
# CLAUDE_AI_TIMEOUT_MS= # override CLI timeout in ms; unset scales by effort (low/medium 120s, high 240s, xhigh 360s, max 600s)
#
# Codex (ChatGPT subscription) reviewer is fail-closed by default for self-host PR review: `codex exec` stores its
# OAuth credential in auth.json on the same filesystem that prompt-influenced reviews can read. Isolated maintainer
# deployments can opt in explicitly after mounting auth at /data/codex (the image exposes it as ~/.codex).
# GITTENSORY_ENABLE_UNSAFE_CODEX_REVIEWER=1
# Do NOT set CODEX_HOME for the app container; the provider rejects it so credentials are not advertised in env.
# CODEX_AI_MODEL=gpt-5.5 # omit to let the Codex account default choose; set explicitly for repeatable reviews
# CODEX_AI_EFFORT=high # low | medium | high | xhigh. `max` is accepted and maps to xhigh.
# CODEX_AI_TIMEOUT_MS= # override CLI timeout in ms; unset scales by effort (low/medium 120s, high 240s, xhigh 360s)
# # Codex service speed is standard by default. No fast/priority tier is requested by this stack.
# AI_EMBED_MODEL=bge-m3 # embedding model for RAG (openai-compatible /embeddings). MUST be
# # 1024-dimensional (e.g. bge-m3 or mxbai-embed-large via Ollama).
# # Used only when RAG is enabled (GITTENSORY_REVIEW_RAG + allowlist).
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@ jobs:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./reports/junit/vitest-shard-${{ matrix.shard }}.xml
report_type: test_results
disable_search: true
override_branch: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref_name }}
override_commit: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
override_pr: ${{ github.event_name == 'pull_request' && github.event.pull_request.number || '' }}
fail_ci_if_error: false

# Merge the 3 shard lcovs into one complete report and upload to Codecov ONCE.
Expand Down Expand Up @@ -242,6 +246,10 @@ jobs:
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./merged-lcov/shard-1.info,./merged-lcov/shard-2.info,./merged-lcov/shard-3.info
disable_search: true
override_branch: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref_name }}
override_commit: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
override_pr: ${{ github.event_name == 'pull_request' && github.event.pull_request.number || '' }}
fail_ci_if_error: false

# Worker-pool runtime tests (separate vitest config); split out of `test` so it
Expand Down
Loading
Loading