From c717075895fae7c62ce40537cd6950e34ee593cd Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Sun, 12 Jul 2026 22:41:39 -0700 Subject: [PATCH] refactor(selfhost): dual-read .loopover.yml config filename alongside .gittensory.yml Both the self-host per-repo config loader (src/selfhost/private-config.ts) and the public-repo focus-manifest loader (src/signals/focus-manifest-loader.ts) now try every .loopover.{yml,yaml,json} candidate, at every location, before any .gittensory.{yml,yaml,json} candidate -- an existing repo/self-hoster with only the legacy filename keeps working byte-identical, and a new-brand file at the same location wins outright over a stale legacy one sitting next to it. src/review/guardrail-config.ts's config-as-code guardrail globs gain the same 6 new-brand entries so a .loopover.yml edit gets the identical contributor-hold protection a .gittensory.yml edit already gets. Fixes a real latent bug found while extending the candidate list: the flat {owner}__{repo}. fallback path was built via `base.slice(".gittensory".length)`, a magic-number slice that would have silently produced garbage paths (".loopover.yml".slice(11) -> "ml") once a 13-character new-brand basename was prepended. Replaced with a brand-agnostic CONFIG_EXTENSIONS constant derived positionally. Also fixes two gaps the diff's own verification caught, neither in the originally-scoped file list: - packages/gittensory-engine/src/review/guardrail-config.ts -- a hand-duplicated, CI-enforced twin of the src/ file above (scripts/check-engine-parity.ts) that had drifted after only the src/ copy was updated. - packages/gittensory-engine/src/objective-anchor.ts's CONFIG_FILENAMES set (feeds anti-slop/objective-anchor change-kind scoring) still only recognized the legacy filename; added the new-brand entry plus a new vitest-side test, since this file's own change-kind logic is otherwise covered almost exclusively by its node:test suite, which Codecov's vitest-based coverage can't see. Updates every self-hosting docs page, the config-generator UI's rendered filename, and README/CONTRIBUTING/example templates to present the new filename as canonical while noting the legacy one still works. Deliberately unchanged: docker-compose.yml's ./gittensory-config volume mount and the gittensory-config/ example directory name (a hard bind path, not a graceful-fallback read -- renaming it would silently mount a new, empty directory for anyone still using the stock default, with no error to surface); the .gittensory.yml/.gittensory.yml.example template files themselves; scripts/gittensory-config-lint.ts's CLI default (explicit-arg tool, not a silent runtime path). Closes #4773. --- CONTRIBUTING.md | 9 +- README.md | 6 +- .../ai-provider-mode-field-group.tsx | 4 +- .../config-generator-yaml-preview.test.tsx | 4 +- .../config-generator-yaml-preview.tsx | 12 +- .../site/app-panels/maintainer-settings.tsx | 8 +- .../src/lib/config-generator-yaml.test.ts | 2 +- .../src/lib/config-generator-yaml.ts | 8 +- .../src/routes/app.config-generator.tsx | 2 +- .../src/routes/docs.beta-onboarding.tsx | 2 +- .../src/routes/docs.github-app.tsx | 9 +- .../src/routes/docs.gittensory-commands.tsx | 7 +- .../src/routes/docs.how-reviews-work.tsx | 9 +- .../routes/docs.maintainer-self-hosting.tsx | 22 ++-- .../src/routes/docs.owner-checklist.tsx | 12 +- .../src/routes/docs.privacy-security.tsx | 7 +- .../docs.self-hosting-configuration.tsx | 61 +++++---- .../routes/docs.self-hosting-operations.tsx | 2 +- .../routes/docs.self-hosting-quickstart.tsx | 7 +- apps/gittensory-ui/src/routes/docs.tuning.tsx | 59 +++++---- config/examples/README.md | 124 ++++++++++-------- config/examples/TEMPLATES.md | 40 ++++-- packages/gittensory-engine/README.md | 9 +- .../gittensory-engine/src/objective-anchor.ts | 1 + .../src/review/guardrail-config.ts | 10 ++ packages/gittensory-mcp/README.md | 2 +- .../docs/config-precedence.md | 2 +- .../gittensory-miner/docs/miner-goal-spec.md | 6 +- src/review/guardrail-config.ts | 10 ++ src/selfhost/private-config.ts | 76 ++++++++--- src/signals/focus-manifest-loader.ts | 33 +++-- ...ctive-anchor-config-classification.test.ts | 20 +++ test/unit/focus-manifest-loader.test.ts | 84 +++++++++++- test/unit/gittensory-focus-manifest.test.ts | 7 +- test/unit/guardrail-config.test.ts | 12 ++ test/unit/private-config.test.ts | 112 +++++++++++++++- test/unit/queue-2.test.ts | 4 + test/unit/queue-4.test.ts | 16 +++ test/unit/selfhost-config-examples.test.ts | 22 ++++ 39 files changed, 612 insertions(+), 230 deletions(-) create mode 100644 test/unit/engine-objective-anchor-config-classification.test.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8243a9fa49..e874a551b7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -282,7 +282,7 @@ Public GitHub surfaces: app/infra state) resolves to a neutral, non-blocking gate. Adding a blocker must keep it confirmed-contributor-gated through `evaluateGateCheck`. -Config as code (`.gittensory.yml`) — every repository setting is controllable from the config file: +Config as code (`.loopover.yml`; the legacy `.gittensory.yml` filename is dual-read indefinitely, #4773) — every repository setting is controllable from the config file: - **`settings:`** is a partial of the repository settings: any behaviour a maintainer can toggle in the dashboard can be set here as code — `commentMode`, `publicAudienceMode`, `publicSurface`, `checkRunMode`, @@ -296,14 +296,15 @@ Config as code (`.gittensory.yml`) — every repository setting is controllable `validationEvidence`, `openPrQueue`, `contributorContext`, `gateResult`). Maintainer text that fails the public-safe filter (reward/score/wallet/hotkey/etc.) is dropped, never published. - **`repoDocGeneration:`** opts a repo into the AGENTS.md/CLAUDE.md generation roadmap (#2993) — a - `.gittensory.yml`-only surface with no dashboard/DB counterpart. `enabled` (default `false`) turns it on; + `.loopover.yml`-only surface with no dashboard/DB counterpart. `enabled` (default `false`) turns it on; `scope` (default `["agents"]`) picks which generated file types are in play (`"agents"` for AGENTS.md/CLAUDE.md, plus `"skills"` once skill-file generation lands); `allowOverwriteExisting` (default `false`) is a separate opt-in required before the engine proposes an overwrite for a repo that already has a hand-maintained AGENTS.md/CLAUDE.md — absent it, an existing hand-written file is left alone and generation is skipped. -- Precedence: `.gittensory.yml` `gate:` > `.gittensory.yml` `settings:` > dashboard repository settings > - safe defaults; unset fields fall back to the next layer. The committed root `.gittensory.yml` is the +- Precedence: `.loopover.yml` `gate:` > `.loopover.yml` `settings:` > dashboard repository settings > + safe defaults; unset fields fall back to the next layer (the legacy `.gittensory.yml` filename works + identically wherever `.loopover.yml` does, #4773). The committed root `.gittensory.yml` is the worked example. Resolved once in `resolveRepositorySettings`, so the whole app honours the file. - The config chooses **what** LoopOver does (gate on/off, blockers, comments, labels, surface, panel content); it never changes **who** can be blocked — only confirmed Gittensor contributors are ever diff --git a/README.md b/README.md index 780cffe324..f4a7d607c5 100644 --- a/README.md +++ b/README.md @@ -37,16 +37,16 @@ LoopOver CI and LoopOver review score, gate, and comment on pull requests. The r - **CI + full-file grounding** — grounds the AI reviewer with the PR's finished CI status and the full post-change content of the changed files, so claims are verified against reality instead of predicted. - **Codebase RAG** — retrieval-augmented context that queries the codebase vector index for related callers, modules, and conventions and appends them to the reviewer prompt (additive only; inert until an index exists). - **Submitter-reputation gating** — an internal-only spend control that downgrades new / burst / low-reputation submitters to a deterministic-only review, never surfaced on any public comment, label, or check. -- **Unified review comment** — renders the public PR feedback as one in-place comment instead of multiple panels. With `.gittensory.yml`'s `review.changed_files_summary` also on (off by default), it gains a deterministic, no-AI "Changed files" collapsible: one row per file category (source/test/docs/config/generated), with file counts and +/- totals. +- **Unified review comment** — renders the public PR feedback as one in-place comment instead of multiple panels. With `.loopover.yml`'s `review.changed_files_summary` also on (off by default; legacy `.gittensory.yml` name also still works, indefinitely), it gains a deterministic, no-AI "Changed files" collapsible: one row per file category (source/test/docs/config/generated), with file counts and +/- totals. - **Per-repo activation** — capabilities roll forward (and back) one flag and one repo at a time via the `GITTENSORY_REVIEW_REPOS` allowlist. **Check-run and comment surfaces, disambiguated** (a common point of confusion — these are three independent, separately-configured things, not layers of the same feature): - **`LoopOver Orb Review Agent`** (`gate.*` / `settings.reviewCheckMode`, off by default) — the authoritative GitHub Check Run carrying the gate's pass/fail verdict. This is the one worth making a required status check. - **`LoopOver Context`** (`settings.checkRunMode` / `settings.checkRunDetailLevel`, off by default) — a separate, purely advisory Check Run. At its default `checkRunDetailLevel: minimal` it publishes no findings at all; even at `standard`/`deep` it only re-renders content already shown elsewhere. Never make this one required. -- **Inline review comments** (`GITTENSORY_REVIEW_INLINE_COMMENTS` + `.gittensory.yml`'s `review.inline_comments`, off by both by default) — real, reply-able line-anchored PR review comment threads (CodeRabbit-style). This is the ONLY one of the three that posts an interactive per-line thread; the two check runs above never do. With `.gittensory.yml`'s `review.suggestions` also on, a precise line-anchored fix is additionally rendered as a one-click, committable GitHub suggested-change block. With `review.finding_categories` also on (off by default), each finding is additionally tagged with a category — security/correctness/performance/maintainability/tests/style — in both the inline comment label and the unified comment's "Finding categories" collapsible; a deterministic path/keyword fallback covers whatever the model omits. +- **Inline review comments** (`GITTENSORY_REVIEW_INLINE_COMMENTS` + `.loopover.yml`'s `review.inline_comments`, off by both by default) — real, reply-able line-anchored PR review comment threads (CodeRabbit-style). This is the ONLY one of the three that posts an interactive per-line thread; the two check runs above never do. With `.loopover.yml`'s `review.suggestions` also on, a precise line-anchored fix is additionally rendered as a one-click, committable GitHub suggested-change block. With `review.finding_categories` also on (off by default), each finding is additionally tagged with a category — security/correctness/performance/maintainability/tests/style — in both the inline comment label and the unified comment's "Finding categories" collapsible; a deterministic path/keyword fallback covers whatever the model omits. -See [Tuning your reviews](https://gittensory.aethereal.dev/docs/tuning) for the full flag, setting, and `.gittensory.yml` reference. +See [Tuning your reviews](https://gittensory.aethereal.dev/docs/tuning) for the full flag, setting, and `.loopover.yml` reference (the legacy `.gittensory.yml` filename is dual-read indefinitely — #4773 — so an existing repo's committed file keeps working unchanged). ## Start Here diff --git a/apps/gittensory-ui/src/components/site/app-panels/ai-provider-mode-field-group.tsx b/apps/gittensory-ui/src/components/site/app-panels/ai-provider-mode-field-group.tsx index 1a9dea2e1d..290533d480 100644 --- a/apps/gittensory-ui/src/components/site/app-panels/ai-provider-mode-field-group.tsx +++ b/apps/gittensory-ui/src/components/site/app-panels/ai-provider-mode-field-group.tsx @@ -66,8 +66,8 @@ export function AiProviderModeFieldGroup({ Provider API keys are configured via environment variables, encrypted key storage, or the maintainer BYOK dashboard — never in generated{" "} - .gittensory.yml files. This field group only records - mode and model names. + .loopover.yml files. This field group only records mode + and model names. diff --git a/apps/gittensory-ui/src/components/site/app-panels/config-generator-yaml-preview.test.tsx b/apps/gittensory-ui/src/components/site/app-panels/config-generator-yaml-preview.test.tsx index c57ac34c7d..43902c70ee 100644 --- a/apps/gittensory-ui/src/components/site/app-panels/config-generator-yaml-preview.test.tsx +++ b/apps/gittensory-ui/src/components/site/app-panels/config-generator-yaml-preview.test.tsx @@ -13,7 +13,9 @@ describe("ConfigGeneratorYamlPreview", () => { expect(screen.getByText("Preview")).toBeTruthy(); // The filename appears both in the descriptive copy and the CodeBlock's own filename label. - expect(screen.getAllByText(".gittensory.yml").length).toBeGreaterThanOrEqual(2); + expect(screen.getAllByText(".loopover.yml").length).toBeGreaterThanOrEqual(2); + // The legacy filename is still mentioned as also-supported (#4773), not silently dropped. + expect(screen.getAllByText(".gittensory.yml").length).toBeGreaterThanOrEqual(1); expect(screen.getByText(/combine: consensus/)).toBeTruthy(); expect(screen.getByText(/provider: anthropic/)).toBeTruthy(); expect(screen.getByRole("button", { name: "Copy code" })).toBeTruthy(); diff --git a/apps/gittensory-ui/src/components/site/app-panels/config-generator-yaml-preview.tsx b/apps/gittensory-ui/src/components/site/app-panels/config-generator-yaml-preview.tsx index 5968e88cd3..789e1e1692 100644 --- a/apps/gittensory-ui/src/components/site/app-panels/config-generator-yaml-preview.tsx +++ b/apps/gittensory-ui/src/components/site/app-panels/config-generator-yaml-preview.tsx @@ -2,21 +2,23 @@ import { CodeBlock } from "@/components/site/primitives"; import { formStateToYaml, type GeneratorFormState } from "@/lib/config-generator-yaml"; /** - * Read-only `.gittensory.yml` preview for the config generator (#2210, part of #1683): renders the + * Read-only `.loopover.yml` preview for the config generator (#2210, part of #1683): renders the * current GeneratorFormState as text via CodeBlock (built-in copy-to-clipboard) so the output is * explicit and reviewable before a self-hoster saves or copies it. Purely presentational — field-group - * panels own collecting the form state. + * panels own collecting the form state. The legacy `.gittensory.yml` filename works identically if a + * self-hoster already has one committed (#4773) — this preview just names the new canonical filename. */ export function ConfigGeneratorYamlPreview({ formState }: { formState: GeneratorFormState }) { return (

Preview

- The exact .gittensory.yml this configuration would - produce. Nothing is saved until you copy it into your repo. + The exact .loopover.yml this configuration would produce + (the legacy .gittensory.yml name also still works). + Nothing is saved until you copy it into your repo.

- +
); diff --git a/apps/gittensory-ui/src/components/site/app-panels/maintainer-settings.tsx b/apps/gittensory-ui/src/components/site/app-panels/maintainer-settings.tsx index 9d712ee98c..9ae062fd7f 100644 --- a/apps/gittensory-ui/src/components/site/app-panels/maintainer-settings.tsx +++ b/apps/gittensory-ui/src/components/site/app-panels/maintainer-settings.tsx @@ -142,7 +142,7 @@ const GATE_FIELDS: FieldDef[] = [ label: "Review agent check", kind: "select", // "visible" (publishes but never required in branch protection) is deliberately not offered here -- - // this toggle keeps its historical off/enabled shape; set .gittensory.yml gate.checkMode: visible directly + // this toggle keeps its historical off/enabled shape; set .loopover.yml gate.checkMode: visible directly // for that finer-grained mode. options: [ ["disabled", "off"], @@ -718,7 +718,8 @@ type FocusManifestResponse = { manifest: unknown }; /** * Edit the repo's focus manifest as JSON. The manifest is repo-public config-as-code (it mirrors - * `.gittensory.yml`); this surface lets a maintainer edit the API-record copy without committing a file. + * `.loopover.yml`, or the legacy `.gittensory.yml`, #4773); this surface lets a maintainer edit the + * API-record copy without committing a file. */ function FocusManifestEditor({ base }: { base: string | null }) { const [text, setText] = useState(""); @@ -776,8 +777,7 @@ function FocusManifestEditor({ base }: { base: string | null }) {

The repo's maintainer focus policy as JSON — wanted paths, linked-issue policy, test - expectations, and gate overrides. Mirrors .gittensory.yml - . + expectations, and gate overrides. Mirrors .loopover.yml.