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
6 changes: 4 additions & 2 deletions .loopover.yml.example
Original file line number Diff line number Diff line change
Expand Up @@ -924,10 +924,12 @@ settings:
# Bool. Default: true.
backfillEnabled: true

# Render a README status badge for the repo. Bool. Default: false.
# Render a README status badge for the repo. Bool. Default: false. Config-as-code only (Batch A
# follow-up, loopover#6442) -- no DB column or dashboard toggle.
badgeEnabled: false

# Publish a public per-repo review-quality page. Bool. Default: false.
# Publish a public per-repo review-quality page. Bool. Default: false. Config-as-code only (Batch A
# follow-up, loopover#6442) -- no DB column or dashboard toggle.
publicQualityMetrics: false

# Per-repo kill-switch: when true, the agent does nothing on this repo.
Expand Down
10 changes: 6 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,10 +284,12 @@ Public GitHub surfaces:

Config as code (`.loopover.yml`) — 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`,
`reviewCheckMode`, the gate-blocker modes, `autoLabelEnabled`, `gittensorLabel`, `requireLinkedIssue`,
`backfillEnabled`, etc.
- **`settings:`** is a partial of the repository settings. Any behaviour a maintainer can toggle in the
dashboard can also be set here as code — `reviewCheckMode`, the gate-blocker modes, `autoLabelEnabled`,
`gittensorLabel`, `requireLinkedIssue`, etc. A subset is config-as-code **only** (no DB column, no
dashboard toggle — this file is their sole source): `commentMode`, `publicAudienceMode`,
`publicSignalLevel`, `checkRunMode`, `checkRunDetailLevel`, `publicSurface`, `includeMaintainerAuthors`,
`backfillEnabled`, `badgeEnabled`, `publicQualityMetrics`, `regateSweepOrderMode`.
- **`gate:`** is a friendly typed alias for the gate subset — `enabled` (on/off), `linkedIssue`,
`duplicates`, `readiness: { mode, minScore }` (each `off | advisory | block`).
- **`review:`** customizes the public review-panel CONTENT: `footer: { text }` (custom lead copy — the
Expand Down
3 changes: 2 additions & 1 deletion apps/loopover-ui/content/docs/github-app.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@ gates every author identically, regardless of config.

<CodeBlock
lang="yaml"
code={`# Repository settings as code — any dashboard toggle:
code={`# Repository settings as code -- reviewCheckMode is also a dashboard toggle; checkRunMode/commentMode/
# publicSurface are config-as-code only (no DB column or dashboard toggle):
settings:
reviewCheckMode: required # review-agent check on/off (required|visible|disabled)
checkRunMode: enabled # the advisory Context check on/off
Expand Down
3 changes: 2 additions & 1 deletion apps/loopover-ui/content/docs/tuning.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,8 @@ gate:
provider: anthropic
model: claude-3-5-sonnet-latest

# Generic dashboard-equivalent overrides
# Generic settings overrides -- commentMode/checkRunMode/checkRunDetailLevel/badgeEnabled are
# config-as-code only (no DB column or dashboard toggle); this file is their sole source.
settings:
commentMode: detected_contributors_only
checkRunMode: enabled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ const ADVISORY_SETTINGS = {
slopAiAdvisory: false,
autoLabelEnabled: true,
requireLinkedIssue: false,
badgeEnabled: false,
publicQualityMetrics: false,
commandAuthorization: {},
autonomy: {},
autoMaintain: { requireApprovals: 1, mergeMethod: "squash" as const },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,10 @@ const SLOP_FIELDS: FieldDef[] = [
];

// commentMode/publicSurface/publicSignalLevel/publicAudienceMode/checkRunMode/checkRunDetailLevel/
// includeMaintainerAuthors moved off the dashboard entirely (Batch A, loopover#6442) -- configure them via
// this repo's own .loopover.yml settings.* block instead.
// includeMaintainerAuthors/badgeEnabled/publicQualityMetrics moved off the dashboard entirely (Batch A +
// follow-up, loopover#6442) -- configure them via this repo's own .loopover.yml settings.* block instead.
const SURFACE_FIELDS: FieldDef[] = [
{ key: "requireLinkedIssue", label: "Require a linked issue", kind: "toggle" },
{ key: "badgeEnabled", label: "Repo badge", kind: "toggle" },
{ key: "publicQualityMetrics", label: "Public quality page", kind: "toggle" },
];

function repoApiBase(repoFullName: string): string | null {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ const SETTINGS: MaintainerSettingsEditable = {
slopAiAdvisory: false,
autoLabelEnabled: true,
requireLinkedIssue: false,
badgeEnabled: false,
publicQualityMetrics: false,
commandAuthorization: {},
autonomy: {},
autoMaintain: { requireApprovals: 1, mergeMethod: "squash" },
Expand All @@ -35,7 +33,7 @@ describe("maintainer-settings-editable (#2218)", () => {
const payload = buildMaintainerSettingsSavePayload(SETTINGS);
expect(Object.keys(payload).sort()).toEqual([...MAINTAINER_SETTINGS_EDITABLE_KEYS].sort());
expect(payload.linkedIssueGateMode).toBe("advisory");
expect(payload.badgeEnabled).toBe(false);
expect(payload.autoLabelEnabled).toBe(true);
});

it("buildMaintainerSettingsSavePayload merges a partial patch over the base settings", () => {
Expand All @@ -47,7 +45,7 @@ describe("maintainer-settings-editable (#2218)", () => {
expect(payload.duplicatePrGateMode).toBe("block");
// Untouched fields pass through unchanged.
expect(payload.qualityGateMode).toBe("advisory");
expect(payload.badgeEnabled).toBe(false);
expect(payload.autoLabelEnabled).toBe(true);
});

it("an empty patch object is a no-op (same as omitting it)", () => {
Expand Down
4 changes: 0 additions & 4 deletions apps/loopover-ui/src/lib/maintainer-settings-editable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ export type MaintainerSettingsEditable = {
// #6443: gittensorLabel/createMissingLabel removed -- no longer DB-backed, config-as-code only via
// .loopover.yml's settings: block now (the dashboard can no longer write them).
requireLinkedIssue: boolean;
badgeEnabled: boolean;
publicQualityMetrics: boolean;
commandAuthorization: CommandAuthorization;
autonomy: Partial<Record<AgentActionClass, AutonomyLevel>>;
autoMaintain: { requireApprovals: number; mergeMethod: AutoMergeMethod };
Expand All @@ -61,8 +59,6 @@ export const MAINTAINER_SETTINGS_EDITABLE_KEYS: Array<keyof MaintainerSettingsEd
"slopAiAdvisory",
"autoLabelEnabled",
"requireLinkedIssue",
"badgeEnabled",
"publicQualityMetrics",
"commandAuthorization",
"autonomy",
"autoMaintain",
Expand Down
6 changes: 4 additions & 2 deletions config/examples/loopover.full.yml
Original file line number Diff line number Diff line change
Expand Up @@ -938,10 +938,12 @@ settings:
# Bool. Default: true.
backfillEnabled: true

# Render a README status badge for the repo. Bool. Default: false.
# Render a README status badge for the repo. Bool. Default: false. Config-as-code only (Batch A
# follow-up, loopover#6442) -- no DB column or dashboard toggle.
badgeEnabled: false

# Publish a public per-repo review-quality page. Bool. Default: false.
# Publish a public per-repo review-quality page. Bool. Default: false. Config-as-code only (Batch A
# follow-up, loopover#6442) -- no DB column or dashboard toggle.
publicQualityMetrics: false

# Per-repo kill-switch: when true, the agent does nothing on this repo.
Expand Down
10 changes: 10 additions & 0 deletions migrations/0159_drop_badge_quality_metrics_columns.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- Config-as-code migration (Batch A follow-up, loopover#6442/epic #6440): badgeEnabled and
-- publicQualityMetrics were deliberately excluded from 0157's drop because loadPublicRepoBadge/
-- loadPublicRepoQualityMetrics (src/api/routes.ts) read them via a raw getRepositorySettings call that
-- bypasses the manifest overlay -- a perf tradeoff for two unauthenticated, high-frequency public routes.
-- Both routes now read resolveRepositorySettings (manifest-aware) instead, accepting that tradeoff so
-- .loopover.yml is honored for these two fields like every other settings.* field. Each live repo's
-- current effective value was confirmed unchanged (false) before this drop. SQLite 3.35+ / D1 supports
-- DROP COLUMN directly (same precedent as 0122/0146/0150/0157).
ALTER TABLE repository_settings DROP COLUMN badge_enabled;
ALTER TABLE repository_settings DROP COLUMN public_quality_metrics;
18 changes: 6 additions & 12 deletions src/api/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,11 +324,11 @@ type AppContext = Context<AppBindings>;
async function loadPublicRepoBadge(env: Env, owner: string, repo: string): Promise<PublicRepoQuality | null> {
const repository = await getRepository(env, `${owner}/${repo}`);
if (!repository || repository.isPrivate || !repository.isInstalled) return null;
// Intentionally the raw DB row, not resolveRepositorySettings: this is an unauthenticated, high-frequency
// public route (a README-embedded badge image), so it deliberately trades honoring a yml-only `badgeEnabled`
// override for avoiding a manifest-cache lookup (and a possible cold-cache GitHub fetch) on every image load.
// `badgeEnabled` is normally set via the dashboard/API, which persists straight to this same DB row (#2912).
const settings = await getRepositorySettings(env, repository.fullName);
// badgeEnabled has no DB column anymore (Batch A follow-up, loopover#6442) -- config-as-code only, so
// this must read the resolved (manifest-overlaid) settings instead of the old raw-DB-row shortcut. An
// accepted perf tradeoff (a manifest-cache lookup, occasionally a cold-cache GitHub fetch, on this
// unauthenticated high-frequency README-badge route) in exchange for `.loopover.yml` being honored here.
const settings = await resolveRepositorySettings(env, repository.fullName);
if (!settings.badgeEnabled) return null;
const pullRequests = await listPullRequests(env, repository.fullName);
return buildPublicRepoQuality(pullRequests);
Expand All @@ -339,7 +339,7 @@ async function loadPublicRepoBadge(env: Env, owner: string, repo: string): Promi
async function loadPublicRepoQualityMetrics(env: Env, owner: string, repo: string) {
const repository = await getRepository(env, `${owner}/${repo}`);
if (!repository || repository.isPrivate || !repository.isInstalled) return null;
const settings = await getRepositorySettings(env, repository.fullName);
const settings = await resolveRepositorySettings(env, repository.fullName);
if (!settings.publicQualityMetrics) return null;
return loadPublicQualityMetrics(env, repository.fullName);
}
Expand Down Expand Up @@ -683,8 +683,6 @@ const repositorySettingsSchema = z.object({
// #6443: gittensorLabel/blacklistLabel/createMissingLabel/contributorBlacklist removed -- no longer
// DB-backed, config-as-code only via .loopover.yml's settings: block now.
requireLinkedIssue: z.boolean().default(false),
badgeEnabled: z.boolean().default(false),
publicQualityMetrics: z.boolean().default(false),
commandAuthorization: z
.object({
default: z.array(z.enum(["maintainer", "collaborator", "pr_author", "confirmed_miner"])).max(4).optional(),
Expand Down Expand Up @@ -721,8 +719,6 @@ const maintainerSettingsSchema = z
autoLabelEnabled: z.boolean(),
closeOwnerAuthors: z.boolean(),
requireLinkedIssue: z.boolean(),
badgeEnabled: z.boolean(),
publicQualityMetrics: z.boolean(),
agentPaused: z.boolean(),
agentDryRun: z.boolean(),
requireFreshRebaseWindowMinutes: z.number().int().positive().nullable(),
Expand Down Expand Up @@ -4231,8 +4227,6 @@ export function createApp() {
closeOwnerAuthors: parsed.data.closeOwnerAuthors,
autoLabelEnabled: parsed.data.autoLabelEnabled,
requireLinkedIssue: parsed.data.requireLinkedIssue,
badgeEnabled: parsed.data.badgeEnabled,
publicQualityMetrics: parsed.data.publicQualityMetrics,
commandAuthorization: normalizeCommandAuthorizationPolicy(parsed.data.commandAuthorization).policy,
}),
);
Expand Down
15 changes: 7 additions & 8 deletions src/db/repositories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -705,8 +705,9 @@ export async function getRepositorySettings(env: Env, fullName: string): Promise
includeMaintainerAuthors: false,
requireLinkedIssue: row.requireLinkedIssue,
backfillEnabled: true,
badgeEnabled: row.badgeEnabled,
publicQualityMetrics: row.publicQualityMetrics,
// Config-as-code only (Batch A follow-up, loopover#6442): no DB column anymore, see migration 0158.
badgeEnabled: false,
publicQualityMetrics: false,
agentPaused: row.agentPaused,
agentDryRun: row.agentDryRun,
commandAuthorization: parseCommandAuthorizationPolicy(row.commandAuthorizationJson),
Expand Down Expand Up @@ -830,8 +831,10 @@ export async function upsertRepositorySettings(env: Env, settings: Partial<Repos
includeMaintainerAuthors: false,
requireLinkedIssue: settings.requireLinkedIssue ?? false,
backfillEnabled: true,
badgeEnabled: settings.badgeEnabled ?? false,
publicQualityMetrics: settings.publicQualityMetrics ?? false,
// Config-as-code only (Batch A follow-up, loopover#6442): any caller-supplied value is a silent
// no-op now -- there's nothing left to persist it to (migration 0158).
badgeEnabled: false,
publicQualityMetrics: false,
agentPaused: settings.agentPaused ?? false,
agentDryRun: settings.agentDryRun ?? false,
commandAuthorization: normalizeCommandAuthorizationPolicy(settings.commandAuthorization).policy,
Expand Down Expand Up @@ -898,8 +901,6 @@ export async function upsertRepositorySettings(env: Env, settings: Partial<Repos
closeOwnerAuthors: resolved.closeOwnerAuthors,
autoLabelEnabled: resolved.autoLabelEnabled,
requireLinkedIssue: resolved.requireLinkedIssue,
badgeEnabled: resolved.badgeEnabled,
publicQualityMetrics: resolved.publicQualityMetrics,
agentPaused: resolved.agentPaused,
agentDryRun: resolved.agentDryRun,
commandAuthorizationJson: jsonString(resolved.commandAuthorization),
Expand Down Expand Up @@ -964,8 +965,6 @@ export async function upsertRepositorySettings(env: Env, settings: Partial<Repos
closeOwnerAuthors: resolved.closeOwnerAuthors,
autoLabelEnabled: resolved.autoLabelEnabled,
requireLinkedIssue: resolved.requireLinkedIssue,
badgeEnabled: resolved.badgeEnabled,
publicQualityMetrics: resolved.publicQualityMetrics,
agentPaused: resolved.agentPaused,
agentDryRun: resolved.agentDryRun,
commandAuthorizationJson: jsonString(resolved.commandAuthorization),
Expand Down
2 changes: 0 additions & 2 deletions src/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,6 @@ export const repositorySettings = sqliteTable("repository_settings", {
closeOwnerAuthors: integer("close_owner_authors", { mode: "boolean" }).notNull().default(false),
autoLabelEnabled: integer("auto_label_enabled", { mode: "boolean" }).notNull().default(true),
requireLinkedIssue: integer("require_linked_issue", { mode: "boolean" }).notNull().default(false),
badgeEnabled: integer("badge_enabled", { mode: "boolean" }).notNull().default(false),
publicQualityMetrics: integer("public_quality_metrics", { mode: "boolean" }).notNull().default(false),
commandAuthorizationJson: text("command_authorization_json").notNull().default("{}"),
autonomyJson: text("autonomy_json").notNull().default("{}"),
autoMaintainJson: text("auto_maintain_json").notNull().default("{}"),
Expand Down
Loading
Loading