diff --git a/.gittensory.minimal.yml b/.gittensory.minimal.yml
index eff7b3b6b5..9b6b7baa04 100644
--- a/.gittensory.minimal.yml
+++ b/.gittensory.minimal.yml
@@ -4,7 +4,9 @@
#
# Copy this file to your repo root as `.gittensory.yml` and customize from here.
# (This filename is not read directly — only `.gittensory.yml` / `.github/gittensory.yml` are.)
-# For every supported field, defaults, and examples see `.gittensory.yml.example`.
+# Also shipped at config/examples/gittensory.minimal.yml for self-host operators.
+# For every supported field, defaults, and examples see `.gittensory.yml.example` or
+# config/examples/gittensory.full.yml.
#
# Safe by default:
# - Gate off (enable explicitly when you are ready)
diff --git a/.gittensory.yml.example b/.gittensory.yml.example
index 02e5762afe..7d9eddbe32 100644
--- a/.gittensory.yml.example
+++ b/.gittensory.yml.example
@@ -6,9 +6,11 @@
# review engine scores, gates, and comments on its pull requests — as
# config-as-code, versioned alongside the project it governs.
#
-# STARTER TEMPLATES:
-# .gittensory.minimal.yml — smallest safe copy-paste starter (gate off, observe-only autonomy)
-# .gittensory.yml.example — exhaustive, field-by-field reference (this file's expanded form)
+# STARTER TEMPLATES (also shipped under config/examples/ for self-host operators):
+# config/examples/gittensory.minimal.yml — smallest safe starter (gate off, observe-only autonomy)
+# config/examples/gittensory.full.yml — exhaustive reference (body synced with this file)
+# .gittensory.minimal.yml — same minimal starter at repo root
+# .gittensory.yml.example — this file
#
# WHERE IT LIVES (first match wins):
# .gittensory.yml → .github/gittensory.yml → .gittensory.json → .github/gittensory.json
diff --git a/apps/gittensory-ui/src/routes/docs.self-hosting-configuration.tsx b/apps/gittensory-ui/src/routes/docs.self-hosting-configuration.tsx
index 324aff2cb7..985fbe3125 100644
--- a/apps/gittensory-ui/src/routes/docs.self-hosting-configuration.tsx
+++ b/apps/gittensory-ui/src/routes/docs.self-hosting-configuration.tsx
@@ -91,13 +91,81 @@ function SelfHostingConfiguration() {
This page covers the environment layer and the shape of the config file. For the full field
list — every gate: and settings: key, its default, and what it
- does — see Tuning your reviews, and for a complete,
- commented, copy-pasteable manifest see{" "}
-
- .gittensory.yml.example
- {" "}
- in the repo — the authoritative reference for every field, including several documented only
- in its comments (see below).
+ does — see Tuning your reviews, and for copy-paste templates
+ see the table below (also shipped inside the self-host image at{" "}
+ config/examples/).
+
+
+
Config templates
+
+ Start from a template instead of reverse-engineering env flags, private-config precedence,
+ and the parser. Every template uses the same schema for a public repo-root{" "}
+ .gittensory.yml or a container-private GITTENSORY_REPO_CONFIG_DIR{" "}
+ mount — only what you put in each file differs.
+
+
+
+
+ Keep anti-abuse thresholds, maintainer allowlists, and autonomy dials in the{" "}
+ private mount — not in a public .gittensory.yml contributors
+ can read. config/examples/TEMPLATES.md documents the public-vs-private split
+ and how to apply the templates to gittensory, awesome-claude, and{" "}
+ metagraphed without committing private policy. Lint before deploy:{" "}
+ npx tsx scripts/gittensory-config-lint.ts path/to/.gittensory.yml.
+
+
+ Several gate-only fields are documented only in the full template comments — see below for
+ the config-as-code blocks with no dashboard equivalent.
Required baseline env
diff --git a/config/examples/README.md b/config/examples/README.md
index ed15e1dfa1..be75528955 100644
--- a/config/examples/README.md
+++ b/config/examples/README.md
@@ -5,6 +5,10 @@ This directory ships **generic, safe** examples for the self-host **private** co
contains no real policy, thresholds, logins, or repo names — copy what you need into your own
mounted config directory and edit it there (never in this repo).
+See **[TEMPLATES.md](./TEMPLATES.md)** for the full template catalog (minimal + exhaustive
+`gittensory.yml` starters, public-vs-private usage, and fleet notes for `gittensory`,
+`awesome-claude`, and `metagraphed` without committing private policy).
+
The private config directory is read by `src/selfhost/private-config.ts` and is kept **out of the
public GitHub repo** on purpose: contributors can read a public `.gittensory.yml`, so anti-abuse
thresholds, maintainer/admin allowlists, autonomy dials, and model/effort settings belong here
@@ -22,10 +26,11 @@ ${GITTENSORY_REPO_CONFIG_DIR}/.gittensory.yml # 4. global default,
```
`.yaml` and `.json` are accepted everywhere `.yml` is. Every one of these files uses the **exact
-same schema** as the public `.gittensory.yml` — see [`.gittensory.yml.example`](../../.gittensory.yml.example)
-at the repo root for the exhaustive, field-by-field reference (not duplicated here, so the two
-never drift out of sync). For the smallest safe starter, copy [`.gittensory.minimal.yml`](../../.gittensory.minimal.yml)
-to your repo root as `.gittensory.yml` and customize from there.
+same schema** as the public `.gittensory.yml` — see [`gittensory.full.yml`](./gittensory.full.yml)
+(or [`.gittensory.yml.example`](../../.gittensory.yml.example) at the repo root) for the exhaustive,
+field-by-field reference. For the smallest safe starter, copy [`gittensory.minimal.yml`](./gittensory.minimal.yml)
+(or [`.gittensory.minimal.yml`](../../.gittensory.minimal.yml)) to your repo root as `.gittensory.yml`
+or into your private mount and customize from there.
## Precedence chain
diff --git a/config/examples/TEMPLATES.md b/config/examples/TEMPLATES.md
new file mode 100644
index 0000000000..292e08dca6
--- /dev/null
+++ b/config/examples/TEMPLATES.md
@@ -0,0 +1,98 @@
+# Gittensory review config templates
+
+Copy-paste templates for `.gittensory.yml` — the per-repo review manifest. Every file in this
+directory uses the **same schema** whether it lives in a public repo root or a self-host private
+mount (`GITTENSORY_REPO_CONFIG_DIR`).
+
+## Template catalog
+
+| File | Purpose |
+|------|---------|
+| [`gittensory.minimal.yml`](./gittensory.minimal.yml) | Smallest safe starter — gate off, observe-only autonomy, no accidental writes |
+| [`gittensory.full.yml`](./gittensory.full.yml) | Exhaustive commented reference — every `gate:`, `settings:`, `review:`, and `features:` field |
+| [`global.gittensory.yml`](./global.gittensory.yml) | **Private only** — illustrative fleet-wide default for a self-host mount |
+| [`repo-override.gittensory.yml`](./repo-override.gittensory.yml) | **Private only** — per-repo overlay deep-merged over `global.gittensory.yml` |
+
+Canonical copies of the minimal and full templates also live at the repo root as
+[`.gittensory.minimal.yml`](../../.gittensory.minimal.yml) and
+[`.gittensory.yml.example`](../../.gittensory.yml.example). CI keeps the `config/examples/` copies
+in sync with those files.
+
+## Public repo root vs private self-host mount
+
+| Layer | Path | Who can read it | Typical contents |
+|-------|------|-----------------|------------------|
+| **Public** | `.gittensory.yml` or `.github/gittensory.yml` in git | Contributors | `wantedPaths`, test expectations, public review presentation |
+| **Private global** | `${GITTENSORY_REPO_CONFIG_DIR}/.gittensory.yml` | Operator only | Shared autonomy baseline, contributor caps, maintainer allowlists |
+| **Private per-repo** | `${GITTENSORY_REPO_CONFIG_DIR}/owner__repo/.gittensory.yml` | Operator only | Repo-specific CI context names, AI mode, overrides |
+
+When **either** a private global or private per-repo file exists, the loader **never fetches** the
+public repo file for that review — mount private policy deliberately. See [README.md](./README.md)
+for precedence and deep-merge rules.
+
+**Never commit real private policy** (maintainer logins, thresholds, autonomy dials you do not want
+contributors to read) into a public repository. Copy `global.gittensory.yml` into your gitignored
+`gittensory-config/` mount and edit there.
+
+## Quick start
+
+### Public repo (contributor-visible config)
+
+```bash
+cp config/examples/gittensory.minimal.yml .gittensory.yml
+# edit wantedPaths / gate when ready
+```
+
+### Self-host private mount (operator-only policy)
+
+```bash
+mkdir -p gittensory-config
+cp config/examples/global.gittensory.yml gittensory-config/.gittensory.yml
+# edit your-admin-login placeholders before going live
+# optional per-repo overlay:
+mkdir -p gittensory-config/myorg__myrepo
+cp config/examples/repo-override.gittensory.yml gittensory-config/myorg__myrepo/.gittensory.yml
+```
+
+Point `GITTENSORY_REPO_CONFIG_DIR` at that directory (default `/config` in `docker-compose.yml` maps
+`./gittensory-config`).
+
+## Fleet examples (without committing private policy)
+
+These patterns apply to common JSONbored repos. **Do not copy real maintainer logins or thresholds
+into public git** — use the private mount for anything marked *private* below.
+
+### `JSONbored/gittensory` (dogfooding)
+
+- **Public** `.gittensory.yml` in the repo: work-area guardrails, test expectations, gate dimensions
+ contributors should understand.
+- **Private** `gittensory-config/` (gitignored locally, operator mount in production): fleet
+ autonomy, anti-abuse caps, maintainer exemption lists — the same split described in
+ [`global.gittensory.yml`](./global.gittensory.yml).
+- Start from `gittensory.minimal.yml` in the public repo until gate semantics are tuned, then promote
+ fields into the private global default as you enable autonomous review.
+
+### `JSONbored/awesome-claude` (public template repo)
+
+- Prefer **`gittensory.minimal.yml`** or a trimmed public manifest: `wantedPaths`, linked-issue
+ policy, and advisory gate modes only.
+- Keep contributor caps, `autoCloseExemptLogins`, and `autonomy.close: auto` in **private config
+ only** — this repo is meant to be copied; do not bake operator-specific enforcement into its
+ public history.
+
+### `JSONbored/metagraphed` (sibling product repo)
+
+- Same split as `gittensory`: public manifest for transparent contributor guidance; private mount
+ for thresholds and maintainer-only rules.
+- Use `repo-override.gittensory.yml` when one repo needs different `expectedCiContexts` or
+ `gate.checkMode: disabled` while sharing a fleet-wide `global.gittensory.yml` baseline.
+
+## Validation
+
+Every template in this directory is parsed in CI (`test/unit/config-templates.test.ts` and
+`test/unit/selfhost-config-examples.test.ts`). The exhaustive template body is kept identical to
+`.gittensory.yml.example` from `# WHERE IT LIVES` onward. Lint a local file before deploy:
+
+```bash
+npx tsx scripts/gittensory-config-lint.ts path/to/.gittensory.yml
+```
diff --git a/config/examples/gittensory.full.yml b/config/examples/gittensory.full.yml
new file mode 100644
index 0000000000..5a48b72246
--- /dev/null
+++ b/config/examples/gittensory.full.yml
@@ -0,0 +1,874 @@
+# ============================================================================
+# gittensory.full.yml — exhaustive commented template (#1682)
+# ============================================================================
+#
+# WHERE TO COPY (pick one):
+# PUBLIC REPO — repo root as `.gittensory.yml` (or `.github/gittensory.yml`). Use for work-area
+# guidance (`wantedPaths`, test expectations) that contributors may read.
+# PRIVATE SELF-HOST — `${GITTENSORY_REPO_CONFIG_DIR}/.gittensory.yml` or a per-repo file under
+# `owner__repo/`. Use for anti-abuse thresholds, maintainer allowlists, autonomy, and anything
+# contributors must not see or game. See `TEMPLATES.md` for public-vs-private split.
+#
+# Canonical copy also lives at the repo root as `.gittensory.yml.example` (kept in sync by CI).
+#
+# STARTER TEMPLATES (this directory):
+# gittensory.minimal.yml — smallest safe copy-paste starter (gate off, observe-only autonomy)
+# gittensory.full.yml — this file — every gate:/settings:/review:/features: field documented
+# global.gittensory.yml — private fleet global default (illustrative placeholders only)
+# repo-override.gittensory.yml — private per-repo overlay example
+#
+# ============================================================================
+# .gittensory.yml — per-repo configuration for gittensory CI & gittensory review
+# ============================================================================
+#
+# Drop this file at the root of any repo gittensory watches to tune how the
+# review engine scores, gates, and comments on its pull requests — as
+# config-as-code, versioned alongside the project it governs.
+#
+# WHERE IT LIVES (first match wins):
+# .gittensory.yml → .github/gittensory.yml → .gittensory.json → .github/gittensory.json
+#
+# PRECEDENCE (most specific wins):
+# this file > per-repo dashboard/API settings > built-in safe defaults
+# The typed `gate:` block below is an alias for the gate fields and wins over
+# the generic `settings:` block for those same fields.
+# Hard path guardrails are an explicit config-as-code exception: omitted or
+# [] means no path guardrails, never hidden engine defaults.
+#
+# SAFE BY DEFAULT: with no file and no dashboard row, a repo falls back to a
+# quiet, non-blocking profile — gate off, AI review off, slop scoring off,
+# comments only to detected contributors, no check-run published. Everything
+# here is an explicit opt-in; every value shown is the documented default
+# unless a comment says otherwise. Delete any block to inherit the default.
+#
+# WHO CAN BE BLOCKED: gate modes choose *which* deterministic checks run, never
+# *who* gets blocked. A hard block is always confirmed-contributor-gated.
+#
+# NOTE: some capabilities (safety scanning, CI/full-file grounding, RAG,
+# reputation control, the unified comment) are switched on at the deployment
+# level by the operator's GITTENSORY_REVIEW_* feature flags AND a per-repo
+# cutover allowlist. This file tunes behavior; it does not enable those
+# deployment-wide capabilities.
+# ============================================================================
+
+
+# ----------------------------------------------------------------------------
+# 1. FOCUS / GUARDRAILS (focus manifest)
+# ----------------------------------------------------------------------------
+# Declares the repo's work areas and off-limits paths. These feed deterministic
+# findings (e.g. manifest_blocked_path, manifest_missing_tests) and — when
+# `gate.manifestPolicy: block` is set below — can become enforceable blockers.
+
+# Work areas the maintainer wants. PRs touching these are preferred/encouraged.
+# Glob list. Default: [] (no preference).
+wantedPaths:
+ - "src/**"
+
+# Paths off-limits to contributors. Touching one yields a manifest_blocked_path
+# finding (enforceable when `gate.manifestPolicy: block`).
+# Glob list. Default: [] (nothing blocked).
+blockedPaths:
+ - "vendor/**"
+ - ".github/workflows/**"
+
+# Labels the maintainer prefers on incoming PRs; a missing preferred label is
+# surfaced (never blocks). String list. Default: [].
+preferredLabels:
+ - bug
+ - enhancement
+
+# How strongly a linked issue is expected on a PR.
+# Values: required | preferred | optional. Default: optional.
+linkedIssuePolicy: optional
+
+# Test paths/areas expected to change alongside code. When code changes without
+# them, a manifest_missing_tests finding fires. String list. Default: [].
+testExpectations:
+ - "tests/**"
+
+# Whether opening discovery issues is encouraged for this repo.
+# Values: encouraged | neutral | discouraged. Default: neutral.
+issueDiscoveryPolicy: neutral
+
+# PRIVATE review context — never published to any public GitHub surface.
+# Use for triage hints the engine should know but contributors should not see.
+# String list. Default: [].
+maintainerNotes:
+ - "Private triage context only — never appears on a public comment."
+
+# Notes explicitly opted into public output. Each line is public-safe filtered;
+# unsafe lines are silently dropped. String list. Default: [].
+publicNotes:
+ - "Prefer backend changes that tie to safety or release readiness."
+
+
+# ----------------------------------------------------------------------------
+# 2. GATE POLICY (`gate:`) — refines the deterministic gate
+# ----------------------------------------------------------------------------
+# Most gate dimensions are tri-state modes:
+# off — the dimension is not evaluated.
+# advisory — the finding is surfaced (comment/context) but never blocks.
+# block — the finding can become a hard `Gittensory Gate` blocker
+# (always confirmed-contributor-gated).
+gate:
+ # Gate master switch. Turns the whole deterministic gate on; the per-dimension
+ # modes below only refine an already-enabled gate.
+ # Bool. Default: false (gate off).
+ enabled: true
+
+ # Review-CHECK publish mode (#2852) — controls ONLY whether/how the "Gittensory Orb Review Agent"
+ # check-run is created/updated on GitHub. Never affects gate evaluation, comments, labels, audit
+ # records, or autonomous merge/close — all of those run identically in every mode (the autonomous
+ # decision engine already excludes the bot's own check-run from the live CI it merges/closes against,
+ # specifically to avoid a self-deadlock). Takes precedence over the legacy `enabled` boolean above
+ # when both are set; when unset, `enabled: true` maps to `required` and `enabled: false` maps to
+ # `disabled` for backward compatibility.
+ # required — legacy/current behavior: publish/update the check exactly as before. Use this if you
+ # intentionally keep it as a required branch-protection status check.
+ # visible — publish/update the SAME check-run, but for UI visibility only. Never add this check as
+ # a required branch-protection status check — behaves identically to `required` on the
+ # publish side (same API calls); the distinction is purely how you configure GitHub.
+ # disabled — never create/update the check-run at all. RECOMMENDED for high-volume autonomous
+ # self-hosting: avoids GitHub showing "Expected — Waiting for status to be reported"
+ # under review-queue pressure, and reduces GitHub API calls (no check-run create/update).
+ # Before switching to `disabled`, remove "Gittensory Orb Review Agent" from this repo's
+ # branch-protection / ruleset required-status-checks list — Gittensory cannot do this on
+ # your behalf (it is a GitHub branch-protection setting), and leaving it required with
+ # nothing to satisfy it means GitHub will show a pending status forever. Keep your real
+ # CI/codecov/security checks required — this only ever affects Gittensory's own check.
+ # required | visible | disabled. Default: disabled for a never-configured repo (matches the existing
+ # opt-in default); an already-configured repo keeps its current effective behavior. RECOMMENDED for
+ # high-volume autonomous self-hosting: `visible` or `disabled`, not `required` — Gittensory's own
+ # merge/close decisions never depend on this check either way.
+ checkMode: visible
+
+ # Policy pack.
+ # gittensor — confirmed-contributor-gated, registry-aware.
+ # oss-anti-slop — runs the deterministic rules against ANY author on ANY repo.
+ # Default: gittensor.
+ pack: gittensor
+
+ # Linked-issue gate — what happens when a PR has NO linked issue at all. off | advisory | block.
+ # Default: advisory (a missing issue is surfaced in the review panel but never blocks the gate; issues
+ # aren't always available, e.g. small/self-evident fixes). Set `block` here, or turn on the dashboard
+ # "Require linked issue" toggle, to make a missing issue an explicit opt-in blocker.
+ # (If the dashboard toggle is on but this is still `off`, it is auto-promoted to `block`.)
+ # This is UNRELATED to closing a PR that links an INELIGIBLE issue (owner-assigned, wrong label, etc.) —
+ # that is a separate, deterministic rule, not this gate.
+ linkedIssue: advisory
+
+ # Duplicate-PR gate — detects duplicate/superseding PRs.
+ # off | advisory | block. Default: block.
+ duplicates: block
+
+ # Quality / merge-readiness score gate (the PR-quality score).
+ readiness:
+ # off | advisory. Default: advisory. Informational only — this dimension can
+ # never hard-block a PR, so "block" is not an accepted value (a config that
+ # still says "block" is downgraded to "advisory" with a warning). For an
+ # enforceable quality floor, use mergeReadiness or manifestPolicy below.
+ mode: advisory
+ # At/above this score the quality dimension passes.
+ # Number 0–100, or null for the engine default band. Default: null.
+ minScore: null
+
+ # Deterministic anti-slop signal. Opt-in.
+ slop:
+ # off | advisory | block. Default: off.
+ # advisory — surfaces the slop score + warnings.
+ # block — also hard-blocks at/above minScore.
+ mode: off
+ # Slop-risk threshold at/above which `block` blocks.
+ # Number 0–100, or null. Default: null (engine uses 60, the "high" band).
+ minScore: null
+ # When true AND slop is not off, a free Workers-AI pass adds an
+ # advisory-only ai_slop_advisory finding. It never feeds the slop score or
+ # the gate. Bool. Default: false.
+ aiAdvisory: false
+
+ # Oversized-PR gate. A PR at/above EITHER the file-count or line-count threshold
+ # (engine defaults, not configurable here) gets a manual-review HOLD finding —
+ # never a hard blocker; mode only turns this hold signal on or off.
+ # off | advisory | block. Default: off. Config-as-code only — no DB column or
+ # dashboard toggle; this can only be set here.
+ size:
+ mode: off
+
+ # Lockfile-tamper-risk gate. Scans a changed package-lock.json diff for a
+ # resolved/integrity value that changed WITHOUT the same package's version
+ # changing in a changed package.json, or a resolved URL outside
+ # registry.npmjs.org — the classic supply-chain hand-edit tell. Distinct
+ # from the OSV.dev known-CVE dependency scan (a different threat model).
+ # off | advisory | block. Default: off. Config-as-code only — no DB column
+ # or dashboard toggle; this can only be set here.
+ lockfileIntegrity: off
+
+ # CLA / license-compatibility gate (#2564). Confirms contributor license-agreement consent before a PR
+ # can auto-merge — the gittensory analog of a "CLA assistant" bot. off | advisory | block. Default: off.
+ # advisory — surfaces a cla_consent_missing finding but never blocks.
+ # block — also hard-blocks (one-shot close for a contributor) when neither detection method
+ # below confirms consent.
+ # Config-as-code only — no DB column or dashboard toggle; this can only be set here.
+ claMode: off
+ cla:
+ # A phrase gittensory looks for in the PR description (case-insensitive substring match), mirroring
+ # review.pre_merge_checks' descriptionContains. String or null. Default: null (not configured).
+ consentPhrase: "I have read and agree to the CLA"
+ # Name of a separate CLA-bot check-run this repo also runs (e.g. a CLA Assistant GitHub Action). A
+ # success/neutral conclusion for a check-run with this exact name also satisfies consent only when the
+ # run was produced by checkRunAppSlug. Either method configured is enough; both may be set. String or
+ # null. Default: null (not configured).
+ checkRunName: null
+ # Trusted GitHub App slug that must have produced checkRunName. Required for check-run detection so a
+ # contributor-controlled same-name check-run cannot satisfy a blocking CLA/legal gate. String or null.
+ # Default: null (not configured).
+ checkRunAppSlug: null
+
+ # CI check/status context names to treat as REQUIRED when GitHub branch protection returns no
+ # readable required-status-checks (unconfigured, or a 403 from a token lacking
+ # administration:read — common for GitHub App installations, especially self-host). Merged with
+ # any branch-protection required contexts when both are readable; used ALONE when branch
+ # protection is null/empty. A context listed here that never appears on the commit stays
+ # pending; a completed red check for a listed context fails the gate; every listed context
+ # settled clean resolves to a verified "passed" (no completeness warning). List of strings, or
+ # omit. Default: not configured (keeps today's fold-all fail-closed behavior when branch
+ # protection is also unreadable). Config-as-code only — no DB column or dashboard toggle.
+ expectedCiContexts:
+ - build
+ - test
+
+ # Composite merge-readiness gate (no min score).
+ # off | advisory | block. Default: off.
+ mergeReadiness: off
+
+ # Manifest-policy gate. When `block`, this repo's declared policy from
+ # section 1 (blockedPaths, required linked issue, testExpectations) becomes
+ # an enforceable blocker. Independent of mergeReadiness.
+ # off | advisory | block. Default: off.
+ manifestPolicy: off
+
+ # Self-authored-linked-issue gate — blocks (or advises on) a PR whose only
+ # linked issue was opened by the PR's own author, which the readiness/quality
+ # signals can't otherwise catch. off | advisory | block. Default: advisory.
+ # DB-backed (dashboard-settable too); this overrides the stored value.
+ selfAuthoredLinkedIssue: advisory
+
+ # Gate-check dry-run. When true, the posted check conclusion remains the real
+ # non-enforcing verdict while comments/check text may also show the would-be
+ # stricter verdict for AI-review blocker mode. It does not disable downstream
+ # merge/close planning for failures from already-enforced gates. Bool.
+ # Default: false. Config-as-code only — no DB column or dashboard toggle;
+ # this can only be set here.
+ dryRun: false
+
+ # First-time-contributor grace. RESERVED / currently INERT: this value is
+ # parsed and stored, but the gate does not read it — a first-time
+ # contributor with a real blocker is one-shot closed the same as a
+ # repeat contributor (blocker findings must remain closure outcomes).
+ # Setting this to true has no effect today; kept for potential future
+ # use. Bool. Default: false.
+ firstTimeContributorGrace: false
+
+ # Live premerge migrations/** collision recheck (#2550, anti-abuse-adjacent safety net). When true, a PR
+ # touching migrations/** gets a fresh GitHub read of the base branch's CURRENT migration filenames
+ # immediately before an agent-driven merge (not just at CI time against this PR's own branch snapshot) —
+ # catching the case where a DIFFERENT PR merged a same-numbered migration file in the meantime. A live
+ # collision holds the PR (rebase-needed label + comment) instead of merging blind. Config-as-code only (no
+ # dashboard/DB equivalent). Bool. Default: false. Costs one extra GitHub API call per migrations/**-touching
+ # PR, so it is opt-in rather than a new default.
+ premergeContentRecheck: false
+
+ # Force-rebase-before-merge window in minutes (#2552, anti-race). When the base branch has advanced within
+ # this many minutes of the actual merge-decision moment, an agent-driven merge forces an update_branch +
+ # fresh CI recheck cycle before merging, instead of trusting a mergeable_state: clean read that may already
+ # be stale relative to a sibling commit that just landed on the base. A bounded retry cap prevents a
+ # fast-moving base from live-locking the PR — after a few forced attempts it falls through to a normal
+ # merge with an audit note. Positive integer (minutes), or omit/null. Default: null (never force).
+ # DB-backed (dashboard-settable too); this overrides the stored value.
+ requireFreshRebaseWindow: 10
+
+ # AI maintainer review. Opt-in; the AI capabilities are switched on at the
+ # deployment level.
+ aiReview:
+ # off | advisory | block. Default: off.
+ # advisory — posts AI review notes only.
+ # block — a dual-model high-confidence consensus defect may become a
+ # blocker (confirmed-contributors only).
+ mode: off
+ # Use the maintainer's frontier model for the *advisory* write-up when a
+ # provider key is configured. The consensus blocker always uses the free
+ # Workers-AI pair, so BYOK never changes who can be blocked.
+ # Bool. Default: false.
+ byok: false
+ # Review every PR author when AI review is enabled. Keep false to spend
+ # model calls only on the engine's default eligible authors.
+ # Bool. Default: false.
+ allAuthors: false
+ # BYOK provider. anthropic | openai | null. Default: null (use the stored
+ # key's own provider). Must match the stored key's provider or BYOK is
+ # skipped (Workers-AI fallback). The key itself lives only in the encrypted
+ # key store — never in this file.
+ provider: null
+ # Model override for the BYOK advisory write-up (e.g. claude-3-5-sonnet-latest).
+ # String or null. Default: null (the key record's model, else a conservative
+ # per-provider default).
+ model: null
+ # Minimum calibrated AI-reviewer confidence (0-1) recorded for cache and
+ # calibration context. Under `mode: block`, consensus and split AI-review
+ # defects still block regardless of this floor. Number 0–1, or null.
+ # Default: null (engine uses 0.93). Config-as-code only — no DB column or
+ # dashboard toggle; this can only be set here.
+ closeConfidence: null
+ # Per-repo override of the self-host operator's dual-AI combine strategy (#2567).
+ # single | consensus | synthesis, or null. Default: null (the operator's own
+ # AI_REVIEW_PLAN.combine env default, itself "consensus" if unset). A refinement
+ # only -- not floor-clamped like onMerge below, since the three strategies aren't
+ # ordered by strictness.
+ combine: null
+ # Per-repo override of the synthesis merge mode (#2567): "either" is STRICTER (any
+ # one reviewer's blocker blocks/holds); "both" is more permissive (every reviewer
+ # must agree). either | both, or null. Default: null (the operator's own plan). A
+ # repo may only TIGHTEN the operator's floor -- it can never loosen "either" down
+ # to "both".
+ onMerge: null
+ # Per-repo override of the named reviewer pair(s) to run, in place of the
+ # operator's own AI_REVIEW_PLAN.reviewers (or the free Workers-AI pair when the
+ # operator configured none). List of {model, fallback?}, or null. Default: null
+ # (the operator's plan). No operator floor applies to which reviewers run.
+ # reviewers:
+ # - model: claude-3-5-sonnet-latest
+ # fallback: claude-3-haiku-latest
+
+
+# ----------------------------------------------------------------------------
+# 3. GENERIC SETTINGS (`settings:`) — dashboard-equivalent overrides
+# ----------------------------------------------------------------------------
+# Everything a maintainer can toggle in the dashboard can be set here as code.
+# All values shown are the safe defaults; delete any line to inherit it.
+#
+# Review output controls. These tune review output without changing the
+# deterministic gate policy above. Omit the block to keep the byte-identical
+# defaults.
+review:
+ # Deterministic AI review eligibility filters (`review.auto_review`, #1954 / #2038–#2041). Each knob quietly
+ # skips the advisory AI review for matching PRs — never a gate failure. When the Orb review check is enabled,
+ # skipped PRs complete as "skipped" with a human-readable reason (see `evaluateAutoReviewSkipReason` in
+ # `src/signals/focus-manifest.ts` and the public-surface `SKIP_SUMMARY.ignored_author` mapping in
+ # `src/signals/settings-preview.ts`).
+ #
+ # Invariant: every field below defaults to off/empty/null, which is byte-identical to today's behavior — every
+ # PR is reviewed exactly as before. Skips are quiet and never block merge on their own.
+ auto_review:
+ # Bool | null. Default: null (draft PRs are reviewed as today). When true, draft PRs skip AI review.
+ # skip_drafts: true
+
+ # Glob list of PR author logins whose PRs skip AI review. Useful for dependency bump or release automation
+ # that already has separate policy/CI. `*` and `**` match any run; matching is case-insensitive.
+ # Default: [] (every author remains review-eligible).
+ ignore_authors:
+ - "*[bot]"
+ - dependabot
+ - renovate
+
+ # Case-insensitive title substrings that skip AI review (e.g. WIP/DRAFT markers). Default: [].
+ # ignore_title_keywords:
+ # - WIP
+ # - DRAFT
+
+ # Base-ref globs whose PRs ARE reviewed. When non-empty, PRs targeting other bases skip review.
+ # Default: [] (every base branch is in scope).
+ # base_branches:
+ # - main
+ # - release/**
+
+ # Non-negative integer. After N published AI reviews on this PR, pause further re-reviews.
+ # Default: null / 0 (re-review every sync, byte-identical).
+ # auto_pause_after_reviewed_commits: 3
+
+ # Planned eligibility knobs (tracked separately — not parsed yet; omit until shipped):
+ # skip_labels, skip_docs_only, max_added_lines, max_files (#2062–#2065).
+
+ # Planned display toggle (#2069): when enabled, the unified review comment includes a
+ # `review effort: N/5 (~M min)` line derived from the per-PR effort estimate.
+ # effort_score: false
+
+ # Deterministic label suggestions (#2045). Each rule SUGGESTS a non-scoring label when a PR matches ALL of the
+ # `when` criteria it sets (at least one is required): when_paths (any changed path matches a glob), title_contains,
+ # description_contains (both case-insensitive). Suggestions are advisory; they are auto-applied only when the repo's
+ # autoLabelEnabled is on. Reserved `gittensor:` labels (scoring/type) are refused. Empty/unset ⇒ no suggestions.
+ labeling_rules:
+ - label: area:docs
+ when_paths:
+ - "docs/**"
+ - "**/*.md"
+ - label: needs:migration
+ when_paths:
+ - "migrations/**"
+ description_contains: schema
+
+settings:
+ # Who receives the public PR comment.
+ # off | detected_contributors_only | all_prs. Default: detected_contributors_only.
+ commentMode: detected_contributors_only
+
+ # Public audience framing. oss_maintainer | gittensor_only. Default: oss_maintainer.
+ publicAudienceMode: oss_maintainer
+
+ # How much signal the public surface shows. minimal | standard. Default: standard.
+ publicSignalLevel: standard
+
+ # Publish a GitHub check-run. off | enabled. Default: off.
+ # Independent, legacy advisory surface (#2691) -- separate from, and not gated
+ # by, the required `Gittensory Orb Review Agent` gate check under `gate:`
+ # above. At the default `checkRunDetailLevel: minimal` below it posts no
+ # findings at all; at standard/deep it only re-renders findings already shown
+ # in the gate check and the PR comment. One-shot self-host installs should
+ # leave this at the default `off` unless you specifically want a second,
+ # low-detail check-run surface alongside the required gate check.
+ checkRunMode: off
+
+ # Check-run detail. minimal | standard | deep. Default: minimal.
+ checkRunDetailLevel: minimal
+
+ # Which public surfaces are used.
+ # off | comment_and_label | comment_only | label_only. Default: comment_and_label.
+ publicSurface: comment_and_label
+
+ # Apply the gittensor label automatically. Bool. Default: true.
+ autoLabelEnabled: true
+
+ # The label name to apply. String. Default: gittensor.
+ gittensorLabel: gittensor
+
+ # Apply the per-PR TYPE/taxonomy label -- bug/feature by the PR title, or priority via
+ # `linkedIssueLabelPropagation` below (#label-decoupling). Independent of `autoLabelEnabled` above
+ # (which only governs the base `gittensorLabel` context label): a type label is internal triage
+ # metadata, so it applies to every PR regardless of author type (bot/maintainer/missing author) or
+ # `publicAudienceMode`/`publicSurface`, UNLESS `publicAudienceMode: gittensor_only` is muting this
+ # PR's author entirely (that mode's whole point is total silence for a non-confirmed-miner author).
+ # Bool. Default: true.
+ typeLabelsEnabled: true
+
+ # Per-repo TYPE label config: an open `category -> label name` map, not fixed to three names.
+ # `bug`/`feature`/`priority` are the built-in categories `deriveKindFromTitle` and
+ # `linkedIssueLabelPropagation` classify (default gittensor:bug / gittensor:feature /
+ # gittensor:priority, shown below as an EXAMPLE, not an engine requirement) -- omit any of the three
+ # to keep its default. Add any number of your OWN categories beyond them (they are never chosen by
+ # title-classification, only ever by a `linkedIssueLabelPropagation` mapping below, but registering
+ # the name here makes it participate in the same mutual-exclusivity cleanup). An explicit `{}` means
+ # zero configured categories for this repo -- distinct from omitting `typeLabels` entirely, which
+ # inherits whatever the dashboard/API has stored.
+ # typeLabels:
+ # bug: gittensor:bug
+ # feature: gittensor:feature
+ # priority: gittensor:priority
+ # security: area:security
+ # docs: area:docs
+ # dependency: area:dependency
+ # custom: maintainer:custom
+
+ # Linked-issue label propagation (#priority-linked-issue-gate): the ONLY mechanism that can select a
+ # label like `gittensor:priority` -- it is NEVER inferred from a PR's title, changed files, AI
+ # output, or existing PR labels, only ever copied from a linked/closing issue ("Fixes #123") that
+ # ALREADY carries the configured issue label. Generic beyond the priority use case: any issue label
+ # can map to any PR label. `removeOtherTypeLabels: true` REPLACES the type label entirely (bug/
+ # feature are removed), matching how `gittensor:priority` behaves today; `false` applies the mapped
+ # label ADDITIVELY alongside the normal title-based bug/feature label, leaving it untouched -- useful
+ # for a mapping unrelated to the bug/feature/priority triad (e.g. a `customer:vip` issue label
+ # copied to a `triage:vip` PR label). Disabled by default (no mappings) -- a self-hoster opts in per
+ # repo. If your labels carry reward or moderation weight, configure this in PRIVATE per-repo/global
+ # config (see `config/examples/README.md`) rather than the public `.gittensory.yml`, so contributors
+ # cannot see the exact mapping rules. A per-repo `mappings` override in the private-config layer
+ # REPLACES the global default list wholesale -- it does not merge with it.
+ # linkedIssueLabelPropagation:
+ # enabled: true
+ # mode: exclusive_type_label
+ # mappings:
+ # - issueLabel: gittensor:priority
+ # prLabel: gittensor:priority
+ # removeOtherTypeLabels: true
+
+ # Create the label if it does not yet exist. Bool. Default: true.
+ createMissingLabel: true
+
+ # Auto-assign a merged/linked-issue PR to a matching GitHub Project/Milestone (#3186). off | suggest |
+ # auto. Default: off. "suggest" posts an advisory note only; "auto" applies the match directly.
+ # autoProjectMilestoneMatch: off
+ # Which backend the match runs against. github | linear. Default: github. "linear" matches against a
+ # Linear workspace via a per-repo encrypted API key (set via the dashboard, never here).
+ # autoProjectMilestoneMatchBackend: github
+
+ # Also review PRs authored by maintainers (not just detected contributors).
+ # Bool. Default: false.
+ includeMaintainerAuthors: false
+
+ # Standing rule: the repo OWNER's own PRs (and ADMIN_GITHUB_LOGINS fleet-operator PRs, #2133) are NEVER
+ # auto-closed by default -- they may still auto-merge when clean + passing, or fall to a manual hold.
+ # Set true to make owner/admin-authored PRs eligible for auto-close like a contributor's (still gated by
+ # the `close` autonomy class below + adverse-signal conditions). Automation-bot PRs stay exempt regardless
+ # of this setting. Bool. Default: false.
+ closeOwnerAuthors: false
+
+ # Hard manual-review path guardrails are config-as-code only. Omit or use [] for no path guardrails;
+ # set concrete globs when otherwise-mergeable PRs touching those paths must be held for a person.
+ # This never falls back to hidden engine path defaults.
+ hardGuardrailGlobs: []
+
+ # Require a linked issue on every PR (dashboard equivalent of the toggle that
+ # can auto-promote gate.linkedIssue to block). Bool. Default: false.
+ requireLinkedIssue: false
+
+ # Backfill review of pre-existing open PRs when first installed.
+ # Bool. Default: true.
+ backfillEnabled: true
+
+ # Use private trust signals in scoring. Bool. Default: true.
+ privateTrustEnabled: true
+
+ # Render a README status badge for the repo. Bool. Default: false.
+ badgeEnabled: false
+
+ # Per-repo kill-switch: when true, the agent does nothing on this repo.
+ # Bool. Default: false.
+ agentPaused: false
+
+ # Dry-run / shadow mode: compute everything but take no public action.
+ # Bool. Default: false.
+ agentDryRun: false
+
+ # Four independent label families, none of which gates or silently disables another (#label-decoupling,
+ # #label-scoping):
+ # 1. Context label (`gittensorLabel`, gated by `autoLabelEnabled` above) — the base per-PR marker shown
+ # to the public surface, subject to `publicAudienceMode`/`publicSurface`/author-qualification.
+ # 2. Taxonomy/type labels (`typeLabels` above, gated by `typeLabelsEnabled`) — internal triage
+ # metadata, applied independently of #1. Shown above with the gittensor:* default names, but
+ # the category set itself is config, not fixed to three (#label-modularity): bug/feature come
+ # from the PR title; priority (or any other category you register) ONLY from
+ # `linkedIssueLabelPropagation` above — never inferred from title, files, AI, or existing PR
+ # labels (#priority-linked-issue-gate).
+ # 3. Autonomy-outcome labels (`gittensory:ready-to-merge` etc.) — gated by the `review_state_label`
+ # autonomy class below, advisory commentary on the bot's own verdict.
+ # 4. Anti-abuse enforcement labels (blacklist/contributor-cap/review-nag) — gated by the `close`
+ # autonomy class below, applied alongside the close action they accompany.
+ #
+ # Autonomy dial — per-action-class level (observe … auto). Classes: review, request_changes, approve,
+ # merge, close, label, review_state_label, update_branch. Map. Default: {} (= observe everywhere,
+ # deny-by-default).
+ #
+ # `label` vs `review_state_label` (#label-scoping): these are SEPARATE, independent dials.
+ # - `label` alone no longer applies any planner-driven label by itself. The anti-abuse enforcement
+ # labels (blacklist/contributor-cap/review-nag) ride on the SAME dial as their accompanying close
+ # (`close`, below) — set `close: auto` and they close-and-label together with no `label` grant needed.
+ # - `review_state_label` gates the bot's own disposition-communication labels only:
+ # gittensory:ready-to-merge / gittensory:changes-requested / gittensory:needs-human-review /
+ # gittensory:migration-collision. These are advisory signals about the bot's own verdict, not
+ # enforcement — for a one-shot review model (merge/close/hold through the required gate check, no
+ # back-and-forth) leave this at the default `observe` so they never appear; set it to `auto` only if
+ # you specifically want that running commentary as GitHub labels.
+ autonomy: {}
+
+ # Names for the 4 disposition-outcome labels the `review_state_label` autonomy class above applies.
+ # Each is a plain string, or explicit `null` to disable that ONE label without disabling the underlying
+ # merge/close/hold decision it accompanies.
+ # readyToMergeLabel: ready-to-merge # Default: ready-to-merge.
+ # changesRequestedLabel: changes-requested # Default: changes-requested.
+ # manualReviewLabel: manual-review # Default: manual-review. Also the fallback hold label used
+ # # when review_state_label is off but merge/close is acting
+ # # (guardrail holds, migration collisions, etc.).
+ # migrationCollisionLabel: migration-collision # Default: migration-collision.
+
+ # Auto-maintain policy for merges the agent is allowed to perform.
+ # Defaults: mergeMethod = squash, requireApprovals = 1.
+ autoMaintain:
+ mergeMethod: squash
+ requireApprovals: 1
+
+ # Command authorization role policy — which roles (maintainer | collaborator | pr_author |
+ # confirmed_miner) may invoke each bot command. Map. Default: the built-in policy.
+ # Omit to inherit the DB-stored policy, or the built-in default if neither is set. An
+ # invalid top-level shape (not a mapping) is ignored with a warning rather than
+ # overwriting an existing DB-stored policy with the built-in default.
+ # commandAuthorization:
+ # default: [maintainer, collaborator, confirmed_miner]
+ # commands:
+ # gate-override: [maintainer, collaborator]
+
+ # Config-driven contributor ban list (#1425, anti-abuse): a banned login's PR/issue is closed
+ # BEFORE any merit/CI/AI analysis (deterministic, zero-hallucination). `reason`/`evidence`/`addedAt`
+ # are maintainer-only metadata, never published in the automated close comment. Default: [] (no bans).
+ # contributorBlacklist:
+ # - login: some-banned-login
+ # reason: "plagiarized PR content"
+ # evidence: ["https://github.com/owner/repo/pull/123"]
+ # addedAt: "2026-01-01"
+
+ # Label applied alongside a blacklist close above. String. Gated on `autonomy.close` (#label-scoping);
+ # set to explicit `null` to close without any label. Default: slop.
+ # blacklistLabel: slop
+
+ # Per-contributor open-PR/open-issue caps (#2270, anti-abuse): the max PRs/issues a single
+ # non-owner/non-admin/non-bot contributor may have open on this repo at once. Uncomment and set a
+ # number to opt in — a contributor's newest item above the cap is closed with a clear reason on the
+ # PR-opened/issue-opened webhook; their oldest items up to the cap stay open and reviewable.
+ # Both contributorOpenPrCap and contributorOpenIssueCap ARE ENFORCED. Positive whole number, or
+ # omit/null for no cap. Default: null (disabled) for both. Set explicitly to `null` (not just omitted)
+ # to force-clear a cap that a dashboard/DB write previously set.
+ # contributorOpenPrCap: 2
+ # contributorOpenIssueCap: 5
+
+ # Label applied to a PR/issue closed for exceeding a cap above. String. Default: over-contributor-limit.
+ # Gated on `autonomy.close` (#label-scoping), not `autonomy.label` — closing WITHOUT this label is
+ # available too: set this to explicit `null` (not just omitted) to close silently, with no label.
+ # contributorCapLabel: over-contributor-limit
+
+ # Cancel in-flight CI runs when a PR is auto-closed for exceeding contributorOpenPrCap above (#2462).
+ # Requires the App installation to have granted the `actions: write` permission -- degrades gracefully
+ # (skipped + logged, the close itself still succeeds) when it hasn't. Bool or omit/null to fall back to
+ # the CONTRIBUTOR_CAP_CANCEL_CI_DEFAULT env var. Default: null (falls back to the env var, itself off
+ # by default).
+ # contributorCapCancelCi: true
+
+ # Review-request nagging cooldown (#2463, anti-abuse): throttle a non-owner/non-admin/non-bot
+ # contributor who repeatedly pings @gittensory for review on the same PR/issue. "hold" replies with a
+ # cooldown notice and takes no further action; "close" closes the PR (issues degrade to "hold" until
+ # a dedicated closeIssue primitive lands) with a clear reason. Off by default.
+ # reviewNagPolicy: off # off | hold | close. Default: off.
+ # reviewNagMaxPings: 3 # Positive integer. Pings above this within the cooldown window trigger the policy. Default: 3.
+ # reviewNagCooldownDays: 5 # Positive integer up to 365. Window the ping count is measured over. Default: 5.
+ # reviewNagLabel: review-nag-cooldown # Label applied alongside the hold/close action. Default: review-nag-cooldown.
+ # # Gated on autonomy.close (#label-scoping); set to explicit `null` to
+ # # close/hold without any label.
+
+ # Maintainer-mention nag moderation (#label-scoping): GitHub logins ALSO throttled under the SAME
+ # reviewNagPolicy/reviewNagMaxPings/reviewNagCooldownDays/reviewNagLabel cooldown above, on top of the
+ # bot's own @gittensory handle — e.g. a contributor who keeps tagging a specific maintainer for review
+ # instead of (or in addition to) pinging @gittensory. Counted independently per mentioned login and
+ # independently of the @gittensory counter. Only fires for the thread's OWN author, never a third party
+ # commenting on someone else's thread; owner/admin/automation-bot/autoCloseExemptLogins are always exempt.
+ # List of GitHub logins. Default: [] (no logins watched).
+ # reviewNagMonitoredMentions: [your-maintainer-login]
+
+ # Shared repo-scoped exemption list (#2463): GitHub logins never throttled/closed by gittensory's
+ # deterministic anti-abuse mechanisms (review-nag cooldown today; the per-contributor open-item cap
+ # above will reuse this list too), on top of the standing owner/admin/automation-bot exemption.
+ # List of GitHub logins. Default: [] (no additional exemptions).
+ # autoCloseExemptLogins: [some-trusted-regular]
+
+ # Account-age throttle (#2561, anti-abuse): a PR from an account younger than this many days gets
+ # the newAccountLabel below -- friction/visibility only, NEVER an automatic close on account age alone.
+ # Never fires for the repo owner, admin logins, or automation bots. PR-path only. Off by default.
+ # accountAgeThresholdDays: null # Positive integer, or null/omitted to disable. Default: null (off).
+ # newAccountLabel: new-account # Label applied to a below-threshold-age account's PR. Default: new-account.
+
+ # Per-command @gittensory rate limit (#2560, anti-abuse): generalizes review-nag's cooldown pattern to
+ # EVERY @gittensory command (help/ask/preflight/blockers/... and the maintainer queue-digest commands),
+ # not just review-request pings — independent of review-nag's own thread-author-only scope. "hold"
+ # replies with a cooldown notice and skips the command's own dispatch. Off by default.
+ # commandRateLimitPolicy: off # off | hold. Default: off.
+ # commandRateLimitMaxPerWindow: 20 # Positive integer. Per-command invocation limit for a CHEAP command (cache-only, no AI call) within the window. Default: 20.
+ # commandRateLimitAiMaxPerWindow: 5 # Positive integer. Tighter limit for an AI-cost-bearing command (ask/blockers/preflight/reviewability/packet/duplicate-check/next-action/repo-fit). Default: 5.
+ # commandRateLimitWindowHours: 24 # Positive integer. Rolling window (hours) both limits above count against. Default: 24.
+
+ # Moderation-rules engine (#selfhost-mod-engine): a single shared, cross-repo violation tally across the
+ # anti-abuse mechanisms below that already short-circuit a PR/issue's disposition (contributor cap,
+ # blacklist, review-nag, review-evasion). Off by default; the global config (dashboard/API, not this file)
+ # holds the master switch, threshold, and label text. This per-repo override only opts THIS repo in/out and
+ # narrows which mechanisms feed the tally for it.
+ # moderationGateMode: inherit # inherit | off | enabled. Default: inherit (defer to the global master switch).
+ # moderationRules: [contributor_cap, blacklist, review_nag, review_evasion] # Replaces (not unions with) the global rule set for this repo. Default: inherit the global list.
+ # moderationWarningLabel: mod:warning # Label applied at >=1 lifetime violation. Default: the global config's warningLabel.
+ # moderationBannedLabel: mod:banned # Label applied at >= the ban threshold. Default: the global config's bannedLabel.
+
+ # Review-evasion protection (#review-evasion-protection, anti-abuse): a contributor closing or converting
+ # their own PR to draft while gittensory has an ACTIVE review pass running against it is dodging the
+ # one-shot review process, not making an ordinary close. When enabled, gittensory reopens (if needed) and
+ # re-closes the PR as the App -- a close the contributor cannot themselves reopen (#one-shot-reopen) --
+ # posts an explanation comment, applies the configured label, and records a `review_evasion` moderation
+ # strike (subject to moderationRules above including it). Off by default.
+ # reviewEvasionProtection: off # off | close. Default: off.
+ # reviewEvasionLabel: review-evasion # Label applied alongside the enforcement close. Gated on autonomy.close
+ # # (#label-scoping); set to explicit `null` to close without any label.
+ # # Default: review-evasion.
+ # reviewEvasionComment: true # Post the public explanation comment before the enforcement close. Default: true.
+
+ # Linked-issue HARD-RULE auto-close (#linked-issue-hard-rules): a DETERMINISTIC verdict about the
+ # linked issue itself (not an AI verdict), gated per-rule to block|off. Fires regardless of
+ # `guardrailHit`; still respects the `close` autonomy class and the owner/automation exemption. Off
+ # by default for every rule.
+ # linkedIssueHardRules:
+ # ownerAssignedClose: off # Close when the linked issue is assigned to the repo owner. block | off.
+ # assignedIssueClose: off # Close when it's assigned to someone other than the PR author. block | off.
+ # missingPointLabelClose: off # Close when a default-label repo's issue carries none of pointBearingLabels. block | off.
+ # maintainerOnlyLabelClose: off # Close when the issue carries a maintainerOnlyLabels entry. block | off.
+ # pointBearingLabels: [gittensor:bug, gittensor:feature, gittensor:priority]
+ # maintainerOnlyLabels: [maintainer-only]
+ # defaultLabelRepo: false # Whether this repo's default label set makes missingPointLabelClose meaningful.
+ # verifyBeforeClose: true # Two-pass flag-then-close instead of an immediate close (#linked-issue-verify-before-close).
+ # closeDelaySeconds: 30 # Verification window before Pass 2 closes, when verifyBeforeClose is true.
+ # Label applied by Pass 1 of the flag-then-close verification above (present ⇒ Pass 2 may close on the
+ # next check). Gated on `autonomy.review_state_label`. String. Default: pending-closure.
+ # pendingClosureLabel: pending-closure
+
+ # Unlinked-issue-match guardrail (#unlinked-issue-guardrail): when a contributor PR links no issue, a
+ # deterministic pre-filter + AI verification checks whether it silently solves an existing open issue
+ # anyway (a way to dodge linked-issue scrutiny while still farming merge-ratio credibility upstream).
+ # A first confirmed match HOLDS the PR for manual review; a confirmed REPEAT by the same contributor
+ # escalates to a close. Off by default.
+ # unlinkedIssueGuardrail:
+ # mode: off # off | hold. Default: off.
+ # minConfidence: 0.85 # Number 0-1. Minimum AI-verifier confidence to treat a candidate as a real match.
+
+# Maintainer AI review tuning (`.gittensory.yml` top-level `review:` block). These knobs shape the advisory AI
+# review prompt and file selection only — gate/slop/secret-scan are unaffected.
+# review:
+# # Globs whose matching files are dropped from the AI review (lockfiles, generated output, etc.).
+# # Applied before path_filters. Empty/default ⇒ every file is reviewed.
+# exclude_paths:
+# - "**/*.lock"
+# - "dist/**"
+# # Include + `!`-negation globs that positively scope the AI review AFTER exclude_paths.
+# # Plain entries restrict to matching paths; a leading `!` subtracts matches. Both `*` and `**` cross
+# # directory slashes. Empty/default ⇒ every non-excluded file is reviewed (byte-identical).
+# path_filters:
+# - "src/**"
+# - "!src/generated/**"
+# # Public-safe voice brief complementing review.profile (e.g. concise, cite line numbers). null/unset ⇒ byte-identical prompt.
+# tone: "Be concise and cite line numbers."
+# # How nitpicky the AI maintainer review is. chill | balanced | assertive. Default: balanced (absent).
+# # chill = only blocking defects; assertive = also minor nits. Never changes the gate verdict.
+# profile: balanced
+# # When true, the reviewer prioritizes a security-defect category with elevated scrutiny, on top of
+# # whatever `profile` volume is set. Bool or null. Default: null/false (byte-identical prompt).
+# security_focus: false
+# # A repo-level natural-language brief handed to the AI reviewer on EVERY review (vs the per-path
+# # path_instructions below) -- the maintainer's conventions/voice. Bounded + public-safe at parse time.
+# # String or null. Default: null (byte-identical prompt).
+# instructions: "Prefer small, focused PRs. Flag any missing test for a bug fix."
+# # Per-path natural-language guidance handed to the AI reviewer when a changed file matches the glob.
+# # Empty/default ⇒ byte-identical prompt.
+# path_instructions:
+# - path: "src/db/**"
+# instructions: "Flag any migration missing a matching down-path note."
+# # When true, the AI reviewer ALSO leaves quiet, non-blocking inline PR comments on specific changed
+# # lines, in addition to the decision summary. Bool or null. Default: null/false (no inline comments).
+# # Operator-gated too (GITTENSORY_REVIEW_INLINE_COMMENTS + allowlist).
+# inline_comments: false
+# # When true, an inline finding whose fix is precise enough to anchor to one line is ALSO rendered as
+# # a one-click GitHub suggestion block. Only takes effect when inline_comments is already on. Bool or
+# # null. Default: null/false.
+# suggestions: false
+# # When true, the unified review comment (only rendered when the unifiedComment feature is on) gains a
+# # deterministic "Changed files" summary: one row per file category, with counts and +/- totals. Bool
+# # or null. Default: null/false.
+# changed_files_summary: false
+# # When true, an inline finding is ALSO tagged with a category (security/correctness/performance/
+# # maintainability/tests/style) -- the AI reviewer self-categorizes, with a deterministic path/keyword
+# # fallback for whatever it omits. Only takes effect when inline_comments is already on. Bool or null.
+# # Default: null/false.
+# finding_categories: false
+# # Maintainer-declared DETERMINISTIC content assertions (title/description must contain a phrase, a
+# # label must be present), optionally gated to a path glob. A failed check is advisory by default;
+# # `enforce: true` makes it a hard gate blocker. Empty/default ⇒ no finding (no AI judgment involved).
+# pre_merge_checks:
+# - name: "require a linked issue reference in the description"
+# description_contains: "Fixes #"
+# enforce: false
+# # Per-repo REES enrichment-analyzer toggles (analyzer name -> on/off). Unknown keys warn + drop at
+# # parse. Empty/default ⇒ the operator's default analyzer set runs unchanged.
+# enrichment:
+# deep-nesting: true
+# error-swallow: false
+# # Deterministic {label, when} rules that SUGGEST a non-scoring label when a PR's changed paths/
+# # title/description match ALL of the rule's `when` criteria. Auto-applied only when the repo's
+# # autoLabelEnabled is set. Reserved gittensor:* labels are refused at parse. Empty ⇒ no suggestion.
+# # (This block is also active, uncommented, near the top of the live `review:` section above.)
+# labeling_rules:
+# - label: area:docs
+# when_paths: ["docs/**", "**/*.md"]
+# # Per-repo self-host reviewer model/effort overrides (claude-code / codex). Self-host only; a hosted
+# # (Workers-AI) repo ignores this entirely. All-null/default ⇒ the operator's global env vars apply.
+# ai_model:
+# claude_model: null # Overrides CLAUDE_AI_MODEL for this repo. String or null.
+# claude_effort: null # Overrides CLAUDE_AI_EFFORT for this repo. String or null. Default (env unset): medium.
+# codex_model: null # Overrides CODEX_AI_MODEL for this repo. String or null.
+# codex_effort: null # Overrides CODEX_AI_EFFORT for this repo. String or null. Default (env unset): medium.
+# # Per-repo before/after screenshot-capture config (#3609 preview / #3610 routes). Only takes effect when
+# # the operator has ALSO enabled GITTENSORY_REVIEW_SCREENSHOTS + this repo's cutover allowlist -- this
+# # config narrows/redirects that feature, it never turns it on by itself. All-null/empty/default ⇒
+# # byte-identical to today (GitHub-native preview discovery, automatic file-to-route inference).
+# visual:
+# preview:
+# # The repo's "after" preview URL, with {number}/{head_sha}/{head_sha_short} placeholders substituted
+# # at capture time. ALWAYS wins over GitHub-native preview discovery (Deployments API / commit checks /
+# # cloudflare-bot PR comment) when set -- the only option for a provider (e.g. Cloudflare Workers
+# # Builds' non-production branch builds) that never surfaces a GitHub-visible deployment at all. Must
+# # resolve to a valid HTTPS URL targeting a public host. String or null. Default: null (discovery unchanged).
+# url_template: "https://pr-{number}.myapp.workers.dev"
+# routes:
+# # An explicit, always-screenshotted route list. When non-empty, REPLACES automatic file-to-route
+# # inference entirely -- for a repo whose routing convention isn't gittensory-ui's TanStack file-based
+# # one. Empty/default ⇒ automatic inference (falling back to "/" when nothing matches).
+# paths:
+# - "/pricing"
+# - "/docs"
+# # Overrides the built-in cap (2) on how many routes get screenshotted per PR, whether they come from
+# # `paths` above or automatic inference. Positive integer or null. Default: null (built-in default).
+# max_routes: 3
+# # Maintainer overrides for the public review-panel CONTENT (not what gittensory measures). The
+# # Gittensor attribution + register link is always appended to the footer regardless; maintainer text
+# # failing the public-safe filter is dropped, never published.
+# footer:
+# text: "Reviewed by the Acme maintainer bot." # Custom lead line. String or null. Default: null.
+# note: "Run the test suite before requesting review." # Short intro line shown above the panel. String or null.
+# # Per-row show/hide toggles for the panel. Keys: linkedIssue | relatedWork | reviewLoad |
+# # validationEvidence | openPrQueue | contributorContext | gateResult. Default: all shown (true).
+# fields:
+# relatedWork: false
+# openPrQueue: false
+# # See the active `review.auto_review` block above for the full eligibility reference (defaults, types, examples).
+# # The commented snapshot below mirrors a typical self-host setup:
+# auto_review:
+# skip_drafts: true
+# ignore_authors:
+# - "*[bot]"
+# ignore_title_keywords:
+# - WIP
+# - DRAFT
+# base_branches:
+# - main
+# - release/**
+# auto_pause_after_reviewed_commits: 3
+
+# Per-repo activation overrides for the converged review features that ship behind a deployment-wide
+# GITTENSORY_REVIEW_* env kill-switch (rag/reputation/unifiedComment/safety). Each key is `true` (force on
+# for this repo, subject to the env flag still being enabled), `false` (force off), or omitted (falls back
+# to the GITTENSORY_REVIEW_REPOS allowlist default -- an operator who sets nothing keeps today's behavior).
+# features:
+# rag: true
+# reputation: false
+# unifiedComment: true
+# safety: true
+
+# Registry-review lane (#2435): lets a self-hosted maintainer point gittensory at their OWN structured
+# registry (e.g. a subnet/plugin/package catalog) without a gittensory code change -- reviewing additions
+# to a data file the same way it reviews code. Uncomment and set at least entryFileGlob + collectionField
+# (both required; the block is ignored with a warning if either is missing).
+# contentLane:
+# entryFileGlob: registry/*.json # Glob for the structured entry files this lane reviews. Required.
+# collectionField: entries # The JSON field holding the collection this lane diffs. Required.
+# providerFileGlob: providers/*.ts # Optional glob for source files the entries are validated against.
+# artifactGlob: dist/registry.json # Optional glob for a generated/build artifact to cross-check.
+# maxAppendedEntries: 1 # Positive integer cap on new entries per PR. Default: unbounded.
+# duplicateKeyFields: [slug] # Field name(s) used to detect a duplicate entry. Default: [] (no dedup check).
+# validatorId: my-registry-validator # Optional identifier for a custom per-entry validator. Default: none.
+
+# Repo-doc generation (#2993/#3002): opt-in only, off by default. Uncomment to let Gittensory open a PR
+# generating/refreshing AGENTS.md/CLAUDE.md (and later, skill files) from this repo's own profile. There
+# is no DB-backed dashboard counterpart -- precedence is simply this manifest value, or fully disabled
+# when the block (or `enabled`) is absent.
+# repoDocGeneration:
+# enabled: true # Bool. Default: false (never touched by the generator).
+# scope: [agents] # agents | skills, one or more. Default: [] ("skills" is not shipped yet).
+# allowOverwriteExisting: false # Bool. Opt-in to propose overwriting a hand-maintained, non-generated
+# # AGENTS.md/CLAUDE.md. Default: false (left alone if not recognizably generated).
+# refreshIntervalDays: 7 # Positive integer. Minimum days between scheduled refresh attempts. Default: 7.
diff --git a/config/examples/gittensory.minimal.yml b/config/examples/gittensory.minimal.yml
new file mode 100644
index 0000000000..ee3f557056
--- /dev/null
+++ b/config/examples/gittensory.minimal.yml
@@ -0,0 +1,33 @@
+# ============================================================================
+# gittensory.minimal.yml — smallest safe starter config (#1682)
+# ============================================================================
+#
+# WHERE TO COPY (pick one):
+# PUBLIC REPO — repo root as `.gittensory.yml` (or `.github/gittensory.yml`). Contributors can read it.
+# PRIVATE SELF-HOST — `${GITTENSORY_REPO_CONFIG_DIR}/.gittensory.yml` (global default) or
+# `${GITTENSORY_REPO_CONFIG_DIR}/owner__repo/.gittensory.yml` (per-repo override). Never commit real
+# policy here to a public repo — use the private mount for thresholds, allowlists, and autonomy.
+#
+# Canonical copy also lives at the repo root as `.gittensory.minimal.yml` (kept in sync by CI).
+# For every supported field, defaults, and allowed values see `gittensory.full.yml` or
+# `.gittensory.yml.example`. See `TEMPLATES.md` in this directory for fleet examples.
+#
+# Safe by default:
+# - Gate off (enable explicitly when you are ready)
+# - Autonomy observe-only (deny-by-default — no auto-merge, auto-close, or auto-label)
+# - No auto-maintain / write behavior unless you add it later
+# ============================================================================
+
+# Optional: declare work areas you want contributors to focus on.
+# wantedPaths:
+# - "src/**"
+
+gate:
+ enabled: false
+
+settings:
+ autonomy:
+ merge: observe
+ close: observe
+ label: observe
+ review_state_label: observe
diff --git a/test/unit/config-templates.test.ts b/test/unit/config-templates.test.ts
new file mode 100644
index 0000000000..2325ac2178
--- /dev/null
+++ b/test/unit/config-templates.test.ts
@@ -0,0 +1,62 @@
+import { readFileSync } from "node:fs";
+import { describe, expect, it } from "vitest";
+import { isAgentConfigured } from "../../src/settings/autonomy";
+import {
+ gateConfigToJson,
+ parseFocusManifest,
+ parseFocusManifestContent,
+} from "../../src/signals/focus-manifest";
+
+// #1682: self-host operators need discoverable, copy-paste templates under config/examples/ that
+// parse cleanly, stay in sync with the canonical root files, and keep the minimal starter safe.
+
+const CANONICAL_BODY_MARKER = "# WHERE IT LIVES (first match wins):";
+const MINIMAL_BODY_MARKER = "# Safe by default:";
+
+function readConfigExample(name: string): string {
+ return readFileSync(`config/examples/${name}`, "utf8");
+}
+
+function readRoot(name: string): string {
+ return readFileSync(name, "utf8");
+}
+
+function bodyFromMarker(content: string, marker: string): string {
+ const index = content.indexOf(marker);
+ expect(index, `marker ${JSON.stringify(marker)} missing`).toBeGreaterThanOrEqual(0);
+ return content.slice(index);
+}
+
+describe("config/examples review templates (#1682)", () => {
+ it("gittensory.full.yml body matches .gittensory.yml.example from WHERE IT LIVES onward", () => {
+ const full = readConfigExample("gittensory.full.yml");
+ const example = readRoot(".gittensory.yml.example");
+ expect(bodyFromMarker(full, CANONICAL_BODY_MARKER)).toBe(bodyFromMarker(example, CANONICAL_BODY_MARKER));
+ });
+
+ it("gittensory.minimal.yml body matches .gittensory.minimal.yml from Safe by default onward", () => {
+ const minimal = readConfigExample("gittensory.minimal.yml");
+ const root = readRoot(".gittensory.minimal.yml");
+ expect(bodyFromMarker(minimal, MINIMAL_BODY_MARKER)).toBe(bodyFromMarker(root, MINIMAL_BODY_MARKER));
+ });
+
+ it("parses gittensory.full.yml with zero warnings", () => {
+ const manifest = parseFocusManifestContent(readConfigExample("gittensory.full.yml"), "repo_file");
+ expect(manifest.warnings).toEqual([]);
+ expect(manifest.present).toBe(true);
+ expect(manifest.gate.sizeMode).toBe("off");
+ expect(manifest.features.rag).toBeNull();
+ });
+
+ it("parses gittensory.minimal.yml with zero warnings and enables no agent actions", () => {
+ const manifest = parseFocusManifestContent(readConfigExample("gittensory.minimal.yml"), "repo_file");
+ expect(manifest.warnings).toEqual([]);
+ expect(manifest.present).toBe(true);
+ expect(manifest.gate.enabled).toBe(false);
+ expect(isAgentConfigured(manifest.settings.autonomy)).toBe(false);
+ const round = parseFocusManifest({ gate: gateConfigToJson(manifest.gate), settings: { autonomy: manifest.settings.autonomy } });
+ expect(round.warnings).toEqual([]);
+ expect(round.gate.enabled).toBe(false);
+ expect(isAgentConfigured(round.settings.autonomy)).toBe(false);
+ });
+});