Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ Config as code (`.gittensory.yml`) — every repository setting is controllable

- **`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`,
`gateCheckMode`, the gate-blocker modes, `autoLabelEnabled`, `gittensorLabel`, `requireLinkedIssue`,
`reviewCheckMode`, the gate-blocker modes, `autoLabelEnabled`, `gittensorLabel`, `requireLinkedIssue`,
`backfillEnabled`, etc.
- **`gate:`** is a friendly typed alias for the gate subset — `enabled` (on/off), `linkedIssue`,
`duplicates`, `readiness: { mode, minScore }` (each `off | advisory | block`).
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Gittensory CI and gittensory review score, gate, and comment on pull requests. T

**Check-run and comment surfaces, disambiguated** (a common point of confusion — these are three independent, separately-configured things, not layers of the same feature):

- **`Gittensory Orb Review Agent`** (`gate.*` / `settings.gateCheckMode` / `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.
- **`Gittensory 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.
- **`Gittensory 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.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ type MaintainerSettings = {
publicSurface: "off" | "comment_and_label" | "comment_only" | "label_only";
checkRunMode: "off" | "enabled";
checkRunDetailLevel: "minimal" | "standard" | "deep";
gateCheckMode: "off" | "enabled";
// #4618: gateCheckMode is deprecated (a computed read-back value only) -- reviewCheckMode is the real,
// writable authority for whether the review-agent check-run publishes.
reviewCheckMode: "required" | "visible" | "disabled";
gatePack: "gittensor" | "oss-anti-slop";
linkedIssueGateMode: GateMode;
duplicatePrGateMode: GateMode;
Expand Down Expand Up @@ -90,7 +92,7 @@ const EDITABLE_KEYS: Array<keyof MaintainerSettings> = [
"publicSurface",
"checkRunMode",
"checkRunDetailLevel",
"gateCheckMode",
"reviewCheckMode",
"gatePack",
"linkedIssueGateMode",
"duplicatePrGateMode",
Expand Down Expand Up @@ -138,12 +140,15 @@ type FieldDef = SelectFieldDef | ToggleFieldDef | NumberFieldDef;

const GATE_FIELDS: FieldDef[] = [
{
key: "gateCheckMode",
key: "reviewCheckMode",
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
// for that finer-grained mode.
options: [
["off", "off"],
["enabled", "enabled"],
["disabled", "off"],
["required", "enabled"],
],
},
{
Expand Down Expand Up @@ -373,8 +378,8 @@ export function MaintainerSettings({ reviewability }: { reviewability: Array<{ p
</p>
</div>
{settings ? (
<StatusPill status={settings.gateCheckMode === "enabled" ? "ready" : "info"}>
gate {settings.gateCheckMode}
<StatusPill status={settings.reviewCheckMode === "disabled" ? "info" : "ready"}>
gate {settings.reviewCheckMode === "disabled" ? "off" : "enabled"}
</StatusPill>
) : null}
</div>
Expand Down
17 changes: 9 additions & 8 deletions apps/gittensory-ui/src/routes/docs.github-app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,14 @@ GET /v1/installations/:id/repair`}
<h2>Gate modes</h2>
<p>
The deterministic gate is the heart of the gittensory review. Its master switch is{" "}
<code>gateCheckMode</code> (<code>off</code> / <code>enabled</code>); each dimension then
refines an already-enabled gate with a tri-state mode — <code>off</code> (not evaluated),{" "}
<code>advisory</code> (surfaced, never blocks), or <code>block</code> (can become a hard{" "}
<strong>Gittensory Orb Review Agent</strong> blocker). A configured blocker fails the gate
for any author identically — confirmed-Gittensor-contributor status does not change{" "}
<em>who</em> can be blocked; it's carried through only for on-chain scoring, a separate
concern from the gate's own merge/close decision.
<code>reviewCheckMode</code> (<code>required</code> / <code>visible</code> /{" "}
<code>disabled</code>); each dimension then refines an already-enabled gate with a tri-state
mode — <code>off</code> (not evaluated), <code>advisory</code> (surfaced, never blocks), or{" "}
<code>block</code> (can become a hard <strong>Gittensory Orb Review Agent</strong> blocker).
A configured blocker fails the gate for any author identically —
confirmed-Gittensor-contributor status does not change <em>who</em> can be blocked; it's
carried through only for on-chain scoring, a separate concern from the gate's own
merge/close decision.
</p>
<ul>
<li>
Expand Down Expand Up @@ -259,7 +260,7 @@ GET /v1/installations/:id/repair`}
lang="yaml"
code={`# Repository settings as code — any dashboard toggle:
settings:
gateCheckMode: enabled # review-agent check on/off
reviewCheckMode: required # review-agent check on/off (required|visible|disabled)
checkRunMode: enabled # the advisory Context check on/off
commentMode: detected_contributors_only
publicSurface: comment_only
Expand Down
5 changes: 3 additions & 2 deletions apps/gittensory-ui/src/routes/docs.how-reviews-work.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,9 @@ function HowReviewsWork() {
<h2>1. The gate: advisory vs. block</h2>
<p>
The gate is deterministic — same inputs, same verdict, no model in the loop. Its master
switch is <code>gateCheckMode</code> (<code>off</code> / <code>enabled</code>). Once
enabled, each <em>dimension</em> is independently set to one of three modes:
switch is <code>reviewCheckMode</code> (<code>required</code> / <code>visible</code> /{" "}
<code>disabled</code>). Once enabled, each <em>dimension</em> is independently set to one of
three modes:
</p>
<ul>
<li>
Expand Down
5 changes: 5 additions & 0 deletions packages/gittensory-engine/src/focus-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1714,6 +1714,10 @@ function parseSettingsOverride(value: JsonValue | undefined, warnings: string[],
if (checkRunMode !== null) out.checkRunMode = checkRunMode;
const checkRunDetailLevel = normalizeOptionalEnum(r.checkRunDetailLevel, "settings.checkRunDetailLevel", ["minimal", "standard", "deep"] as const, warnings);
if (checkRunDetailLevel !== null) out.checkRunDetailLevel = checkRunDetailLevel;
// #4618: gateCheckMode is deprecated (a computed read-back value everywhere else) but this yml key still
// parses for back-compat with existing `.gittensory.yml` files. A manifest setting ONLY gateCheckMode
// (never the more expressive reviewCheckMode) must keep its historical effect on the actual publish
// authority -- derive reviewCheckMode from it below when reviewCheckMode itself is unset.
const gateCheckMode = normalizeOptionalEnum(r.gateCheckMode, "settings.gateCheckMode", ["off", "enabled"] as const, warnings);
if (gateCheckMode !== null) out.gateCheckMode = gateCheckMode;
const regateSweepOrderMode = normalizeOptionalEnum(r.regateSweepOrderMode, "settings.regateSweepOrderMode", ["staleness", "oldest-first"] as const, warnings);
Expand All @@ -1722,6 +1726,7 @@ function parseSettingsOverride(value: JsonValue | undefined, warnings: string[],
// resolveEffectiveSettings, and wins when both are set).
const reviewCheckMode = normalizeOptionalEnum(r.reviewCheckMode, "settings.reviewCheckMode", ["required", "visible", "disabled"] as const, warnings);
if (reviewCheckMode !== null) out.reviewCheckMode = reviewCheckMode;
else if (gateCheckMode !== null) out.reviewCheckMode = gateCheckMode === "enabled" ? "required" : "disabled";
const autoProjectMilestoneMatch = normalizeOptionalEnum(r.autoProjectMilestoneMatch, "settings.autoProjectMilestoneMatch", ["off", "suggest", "auto"] as const, warnings);
if (autoProjectMilestoneMatch !== null) out.autoProjectMilestoneMatch = autoProjectMilestoneMatch;
const autoProjectMilestoneMatchBackend = normalizeOptionalEnum(r.autoProjectMilestoneMatchBackend, "settings.autoProjectMilestoneMatchBackend", ["github", "linear"] as const, warnings);
Expand Down
27 changes: 4 additions & 23 deletions src/api/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -668,15 +668,10 @@ const repositorySettingsSchema = z.object({
// public output is intentionally minimal by design (see formatCheckRunOutput's doc comment), so a caller of
// this full-replace route that omits this field must land on the same safe default as a never-configured row.
checkRunDetailLevel: z.enum(["minimal", "standard", "deep"]).default("minimal"),
gateCheckMode: z.enum(["off", "enabled"]).default("off"),
regateSweepOrderMode: z.enum(["staleness", "oldest-first"]).default("staleness"),
// #2852: deliberately NO `.default()` here (unlike every sibling field above) -- this is a non-partial,
// full-replace schema (see upsertRepositorySettings call below, which passes every parsed field straight
// through with no read-merge of the current row), so an eager default would mask a legacy caller that only
// sends gateCheckMode: the value would already be "defined" (the default) by the time it reaches
// upsertRepositorySettings, and its own `settings.reviewCheckMode ?? (gateCheckMode==="enabled" ? ...)`
// fallback only fires on `undefined`. Leaving this genuinely optional lets that fallback do its job.
reviewCheckMode: z.enum(["required", "visible", "disabled"]).optional(),
// #4618: gateCheckMode dropped from this write schema -- it is a computed read-back value only (see its
// doc comment on RepositorySettings). Set reviewCheckMode directly.
reviewCheckMode: z.enum(["required", "visible", "disabled"]).default("disabled"),
gatePack: z.enum(["gittensor", "oss-anti-slop"]).default("gittensor"),
linkedIssueGateMode: z.enum(["off", "advisory", "block"]).default("advisory"),
duplicatePrGateMode: z.enum(["off", "advisory", "block"]).default("block"),
Expand Down Expand Up @@ -726,7 +721,6 @@ const maintainerSettingsSchema = z
publicSurface: z.enum(["off", "comment_and_label", "comment_only", "label_only"]),
checkRunMode: z.enum(["off", "enabled"]),
checkRunDetailLevel: z.enum(["minimal", "standard", "deep"]),
gateCheckMode: z.enum(["off", "enabled"]),
regateSweepOrderMode: z.enum(["staleness", "oldest-first"]),
reviewCheckMode: z.enum(["required", "visible", "disabled"]),
gatePack: z.enum(["gittensor", "oss-anti-slop"]),
Expand Down Expand Up @@ -2389,14 +2383,6 @@ export function createApp() {
const current = await getRepositorySettings(c.env, fullName);
const changes = Object.fromEntries(Object.entries(parsed.data).filter(([, value]) => value !== undefined)) as Partial<RepositorySettings>;
if (changes.qualityGateMode !== undefined) changes.qualityGateMode = downgradeQualityGateMode(changes.qualityGateMode);
// #2852: a legacy client (the maintainer dashboard's "Review agent check" toggle) only ever sends
// gateCheckMode, never the newer reviewCheckMode -- which must keep its historical effect on the ACTUAL
// publish authority. Derive it here, before merging onto `current` (which always has a DEFINED
// reviewCheckMode already), because upsertRepositorySettings's own legacy-fallback only fires on
// `undefined` and would never see this change as unset once merged with the current row.
if (changes.gateCheckMode !== undefined && changes.reviewCheckMode === undefined) {
changes.reviewCheckMode = changes.gateCheckMode === "enabled" ? "required" : "disabled";
}
const updated = await upsertRepositorySettings(c.env, { ...current, ...changes, repoFullName: fullName });
await recordAuditEvent(c.env, {
eventType: "repo.settings_updated",
Expand Down Expand Up @@ -3898,13 +3884,8 @@ export function createApp() {
publicSignalLevel: parsed.data.publicSignalLevel,
checkRunMode: parsed.data.checkRunMode,
checkRunDetailLevel: parsed.data.checkRunDetailLevel,
gateCheckMode: parsed.data.gateCheckMode,
regateSweepOrderMode: parsed.data.regateSweepOrderMode,
// #2852: this route is a full-replace, non-partial schema (every sibling field has a `.default()`),
// so a caller that only ever sends gateCheckMode must still get its historical effect on the actual
// publish authority -- derive it explicitly here rather than relying on a passthrough `undefined`
// (which `exactOptionalPropertyTypes` disallows assigning to RepositorySettings anyway).
reviewCheckMode: parsed.data.reviewCheckMode ?? (parsed.data.gateCheckMode === "enabled" ? "required" : "disabled"),
reviewCheckMode: parsed.data.reviewCheckMode,
gatePack: parsed.data.gatePack,
linkedIssueGateMode: parsed.data.linkedIssueGateMode,
duplicatePrGateMode: parsed.data.duplicatePrGateMode,
Expand Down
22 changes: 8 additions & 14 deletions src/db/repositories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,10 @@ export async function getRepositorySettings(env: Env, fullName: string): Promise
publicSignalLevel: row.publicSignalLevel === "minimal" ? "minimal" : "standard",
checkRunMode: parseCheckRunMode(row.checkRunMode),
checkRunDetailLevel: parseCheckRunDetailLevel(row.checkRunDetailLevel),
gateCheckMode: parseGateCheckMode(row.gateCheckMode),
// #4618: gateCheckMode is a computed read-back value, never its own stored source of truth -- derive it
// from the real authority (reviewCheckMode) on every read instead of trusting the DB column, so a row
// whose gate_check_mode column has drifted (e.g. pre-#4618 data) self-heals on the very next read.
gateCheckMode: parseReviewCheckMode(row.reviewCheckMode) === "disabled" ? "off" : "enabled",
regateSweepOrderMode: parseRegateSweepOrderMode(row.regateSweepOrderMode),
reviewCheckMode: parseReviewCheckMode(row.reviewCheckMode),
autoProjectMilestoneMatch: parseProjectMilestoneMatchMode(row.projectMilestoneMatchMode),
Expand Down Expand Up @@ -706,16 +709,11 @@ export async function upsertRepositorySettings(env: Env, settings: Partial<Repos
publicSignalLevel: settings.publicSignalLevel ?? "standard",
checkRunMode: settings.checkRunMode ?? "off",
checkRunDetailLevel: settings.checkRunDetailLevel ?? "minimal",
gateCheckMode: settings.gateCheckMode ?? "off",
// #4618: gateCheckMode is no longer an independent write input (dropped from every write schema) --
// derive it from reviewCheckMode below so the DB column stays a self-consistent read-back value.
gateCheckMode: (settings.reviewCheckMode ?? "disabled") === "disabled" ? "off" : "enabled",
regateSweepOrderMode: settings.regateSweepOrderMode ?? "staleness",
// Legacy-write compatibility (#2852): a caller that sets ONLY gateCheckMode (never touching the newer,
// more expressive reviewCheckMode) must keep its historical effect -- "enabled" still means the check
// publishes. This is safe under this function's existing "no field is preserved from the DB, an absent
// field always gets a fresh default" contract (see the route-handler comment above): a true partial-update
// caller already read-merges the full current settings (including its persisted reviewCheckMode) before
// calling this, so `settings.reviewCheckMode` is never actually undefined for that path -- this fallback
// only fires for callers that never cared about reviewCheckMode at all.
reviewCheckMode: settings.reviewCheckMode ?? (settings.gateCheckMode === "enabled" ? "required" : "disabled"),
reviewCheckMode: settings.reviewCheckMode ?? "disabled",
autoProjectMilestoneMatch: settings.autoProjectMilestoneMatch ?? "off",
autoProjectMilestoneMatchBackend: settings.autoProjectMilestoneMatchBackend ?? "github",
gatePack: parseGatePack(settings.gatePack),
Expand Down Expand Up @@ -7350,10 +7348,6 @@ function parseCheckRunDetailLevel(value: string): RepositorySettings["checkRunDe
return "standard";
}

function parseGateCheckMode(value: string): RepositorySettings["gateCheckMode"] {
return value === "enabled" ? "enabled" : "off";
}

function parseRegateSweepOrderMode(value: string): RepositorySettings["regateSweepOrderMode"] {
return value === "oldest-first" ? "oldest-first" : "staleness";
}
Expand Down
3 changes: 1 addition & 2 deletions src/services/maintainer-activation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,9 @@ function buildSummary(evaluated: number, withFindings: number, currentlyActive:
*/
export function recommendedAdvisoryActivationSettings(): Pick<
RepositorySettings,
"gateCheckMode" | "reviewCheckMode" | "checkRunMode" | "linkedIssueGateMode" | "duplicatePrGateMode" | "qualityGateMode"
"reviewCheckMode" | "checkRunMode" | "linkedIssueGateMode" | "duplicatePrGateMode" | "qualityGateMode"
> {
return {
gateCheckMode: "enabled",
reviewCheckMode: "required",
checkRunMode: "enabled",
linkedIssueGateMode: "advisory",
Expand Down
11 changes: 7 additions & 4 deletions src/signals/focus-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,14 +440,17 @@ export function filterReviewFilesForAi<T extends { path: string }>(
* repo with no `gate:` block resolves byte-identically to before this was split out.
*/
function applyGateConfigOverrides(effective: RepositorySettings, gate: FocusManifestGateConfig): void {
if (gate.enabled !== null) effective.gateCheckMode = gate.enabled ? "enabled" : "off";
// reviewCheckMode (#2852) resolution: explicit `gate.checkMode` is the most-specific signal and always wins
// when set. Otherwise fall back to the legacy `gate.enabled` boolean alias, mapped symmetrically so it keeps
// its historical effect (true -> the check publishes and may be required; false -> it never publishes) even
// though it no longer drives `gateCheckMode` alone. When NEITHER is set, `effective.reviewCheckMode` already
// holds `settings.reviewCheckMode` (yml `settings:` override, else the DB value) from the caller's spread.
// its historical effect (true -> the check publishes and may be required; false -> it never publishes). When
// NEITHER is set, `effective.reviewCheckMode` already holds `settings.reviewCheckMode` (yml `settings:`
// override, else the DB value) from the caller's spread.
if (gate.checkMode !== null) effective.reviewCheckMode = gate.checkMode;
else if (gate.enabled !== null) effective.reviewCheckMode = gate.enabled ? "required" : "disabled";
// #4618: gateCheckMode is a computed read-back value only -- always re-derive it from the reviewCheckMode
// just resolved above (not from gate.enabled alone), so it stays correct even when only gate.checkMode was
// the field actually set in the manifest.
effective.gateCheckMode = effective.reviewCheckMode === "disabled" ? "off" : "enabled";
if (gate.pack !== null) effective.gatePack = gate.pack;
if (gate.linkedIssue !== null) effective.linkedIssueGateMode = gate.linkedIssue;
if (gate.duplicates !== null) effective.duplicatePrGateMode = gate.duplicates;
Expand Down
Loading
Loading