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
21 changes: 21 additions & 0 deletions .loopover.yml.example
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,27 @@ settings:
# dependency: area:dependency
# custom: maintainer:custom

# Per-repo opt-out for `loopover_plan_repo_issues` (#7429), the selfhoster-facing AI issue-planning MCP
# tool -- when false, the tool returns a `disabled` status without ever calling the model. The tool is
# already gated behind MCP maintainer access and the fleet-wide AI_SUMMARIES_ENABLED/
# AI_PUBLIC_COMMENTS_ENABLED switches, so this is an additional per-repo layer, not the primary gate.
# Bool. Default: true.
issuePlanEnabled: true

# Additional label names `loopover_plan_repo_issues` suggests to the model alongside the repo's real
# GitHub labels and (when gittensor-enrolled) its type-label taxonomy -- e.g. a project-specific
# category not yet created as a real label. Purely additive; never removes or replaces the repo's own
# labels. Array of strings. Default: [].
# issuePlanExtraLabels:
# - area:docs
# - needs-triage

# Whether `loopover_plan_repo_issues` attempts to reuse an existing OPEN milestone by exact
# normalized-title match before creating a new one. A repo that always wants a fresh milestone per
# planning session (e.g. date-stamped titles) can set this false to skip the reuse lookup entirely.
# Bool. Default: true.
issuePlanMilestoneReuse: true

# 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,14 @@ A cluster of contributor-abuse guardrails, all config-as-code only, all off/unse
- **Exemptions and account age** — `autoCloseExemptLogins` is a shared, repo-scoped list of logins never throttled or closed by these deterministic mechanisms, on top of the standing owner/admin/bot exemption. `accountAgeThresholdDays` (default `null`, off) applies `newAccountLabel` (default `new-account`) to a PR from a below-threshold-age account — friction/visibility only, never an automatic close on account age alone, and never for the owner, admins, or bots.
- **Command rate limit** — `commandRateLimitPolicy` (`off`/`hold`, default `off`) generalizes the review-nag pattern to every `@loopover` command, not just review-request pings. `commandRateLimitMaxPerWindow` (default `20`) bounds cheap, cache-only commands; `commandRateLimitAiMaxPerWindow` (default `5`) is the tighter limit for AI-cost-bearing commands (ask/blockers/preflight/etc.); `commandRateLimitWindowHours` (default `24`) is the rolling window both limits count against.

### settings.issuePlan*

Config for `loopover_plan_repo_issues`, the AI issue-planning MCP tool: given a maintainer-supplied free-form goal, it proposes a small set of GitHub issue drafts (dry-run by default) and, on explicit request, creates them via the same installation-token/Orb-broker path every other GitHub write in this repo uses — never a flat PAT, so it only works on a repo the App/Orb is actually installed on.

- `issuePlanEnabled` — per-repo opt-out. The tool is already gated behind MCP maintainer access and the fleet-wide `AI_SUMMARIES_ENABLED`/`AI_PUBLIC_COMMENTS_ENABLED` switches; this is an additional layer for a repo that wants those switches on generally but this specific capability off. Default `true`.
- `issuePlanExtraLabels` — additional label names suggested to the model alongside the repo's real GitHub labels (and, on a gittensor-enrolled repo, its type-label taxonomy) — useful for a project-specific category not yet created as a real label. Purely additive; never removes or replaces the repo's own labels. Default `[]`.
- `issuePlanMilestoneReuse` — whether the tool reuses an existing open milestone by exact normalized-title match before creating a new one, when the caller asks it to target a milestone. Set `false` for a repo that always wants a fresh milestone per planning session (e.g. date-stamped titles). Default `true`.

### contentLane

Lets a self-hosted maintainer point LoopOver at their own structured registry (a subnet/plugin/package catalog, for example) without a LoopOver code change — reviewing additions to a data file the same way it reviews code. Unconfigured by default; uncomment and set at least `entryFileGlob` and `collectionField` (both required — the whole block is ignored with a warning if either is missing).
Expand Down
12 changes: 12 additions & 0 deletions apps/loopover-ui/public/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -9693,6 +9693,18 @@
"close"
],
"description": "Off by default (opt-in, unlike reviewEvasionProtection's default-close). \"close\" enforces on ANY draft PR, including the very first one, before a review pass has had a chance to run -- distinct from reviewEvasionProtection's family, which only enforces after a review already ran or on the 2nd+ draft conversion."
},
"issuePlanEnabled": {
"type": "boolean"
},
"issuePlanExtraLabels": {
"type": "array",
"items": {
"type": "string"
}
},
"issuePlanMilestoneReuse": {
"type": "boolean"
}
},
"required": [
Expand Down
21 changes: 21 additions & 0 deletions config/examples/loopover.full.yml
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,27 @@ settings:
# dependency: area:dependency
# custom: maintainer:custom

# Per-repo opt-out for `loopover_plan_repo_issues` (#7429), the selfhoster-facing AI issue-planning MCP
# tool -- when false, the tool returns a `disabled` status without ever calling the model. The tool is
# already gated behind MCP maintainer access and the fleet-wide AI_SUMMARIES_ENABLED/
# AI_PUBLIC_COMMENTS_ENABLED switches, so this is an additional per-repo layer, not the primary gate.
# Bool. Default: true.
issuePlanEnabled: true

# Additional label names `loopover_plan_repo_issues` suggests to the model alongside the repo's real
# GitHub labels and (when gittensor-enrolled) its type-label taxonomy -- e.g. a project-specific
# category not yet created as a real label. Purely additive; never removes or replaces the repo's own
# labels. Array of strings. Default: [].
# issuePlanExtraLabels:
# - area:docs
# - needs-triage

# Whether `loopover_plan_repo_issues` attempts to reuse an existing OPEN milestone by exact
# normalized-title match before creating a new one. A repo that always wants a fresh milestone per
# planning session (e.g. date-stamped titles) can set this false to skip the reuse lookup entirely.
# Bool. Default: true.
issuePlanMilestoneReuse: true

# 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
Expand Down
18 changes: 17 additions & 1 deletion packages/loopover-engine/src/focus-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,9 @@ export type FocusManifestSettings = Partial<
| "plannerMode"
| "autoLabelEnabled"
| "typeLabelsEnabled"
| "issuePlanEnabled"
| "issuePlanExtraLabels"
| "issuePlanMilestoneReuse"
| "badgeEnabled"
| "publicQualityMetrics"
| "gittensorLabel"
Expand Down Expand Up @@ -2402,7 +2405,7 @@ function parseSettingsOverride(value: JsonValue | undefined, warnings: string[])
}
const publicSurface = normalizeOptionalEnum(r.publicSurface, "settings.publicSurface", ["off", "comment_and_label", "comment_only", "label_only"] as const, warnings);
if (publicSurface !== null) out.publicSurface = publicSurface;
for (const key of ["aiReviewByok", "aiReviewAllAuthors", "aiReviewConfirmedContributorsOnly", "closeOwnerAuthors", "autoLabelEnabled", "typeLabelsEnabled", "badgeEnabled", "publicQualityMetrics", "createMissingLabel", "includeMaintainerAuthors", "requireLinkedIssue", "backfillEnabled", "agentPaused", "agentDryRun", "hardGuardrailGlobsOverridesInvariants"] as const) {
for (const key of ["aiReviewByok", "aiReviewAllAuthors", "aiReviewConfirmedContributorsOnly", "closeOwnerAuthors", "autoLabelEnabled", "typeLabelsEnabled", "issuePlanEnabled", "issuePlanMilestoneReuse", "badgeEnabled", "publicQualityMetrics", "createMissingLabel", "includeMaintainerAuthors", "requireLinkedIssue", "backfillEnabled", "agentPaused", "agentDryRun", "hardGuardrailGlobsOverridesInvariants"] as const) {
const flag = normalizeOptionalBoolean(r[key], `settings.${key}`, warnings);
if (flag !== null) out[key] = flag;
}
Expand Down Expand Up @@ -2658,6 +2661,19 @@ function parseSettingsOverride(value: JsonValue | undefined, warnings: string[])
} else if (r.hardGuardrailGlobs !== undefined) {
warnings.push(`Manifest "settings.hardGuardrailGlobs" must be an array of path globs; ignoring it and keeping any existing guardrails.`);
}
// Additional label names loopover_plan_repo_issues (#7429) suggests to the model, purely additive to the
// repo's real GitHub labels -- same array-replace shape as hardGuardrailGlobs above (explicit [] or a
// non-empty valid list replaces; malformed/omitted is ignored, never clears an existing DB value).
if (Array.isArray(r.issuePlanExtraLabels)) {
const issuePlanExtraLabels = normalizeStringList(r.issuePlanExtraLabels, "settings.issuePlanExtraLabels", warnings);
if (r.issuePlanExtraLabels.length === 0 || issuePlanExtraLabels.length > 0) {
out.issuePlanExtraLabels = issuePlanExtraLabels;
} else {
warnings.push(`Manifest "settings.issuePlanExtraLabels" did not contain any valid label names; ignoring it and keeping any existing value.`);
}
} else if (r.issuePlanExtraLabels !== undefined) {
warnings.push(`Manifest "settings.issuePlanExtraLabels" must be an array of label names; ignoring it and keeping any existing value.`);
}
// Manual-review label is deliberately separate from review_state_label so operators can use one hold label
// without enabling the old ready/changes disposition labels. Null disables only the label, not the hold.
if (r.manualReviewLabel === null) {
Expand Down
18 changes: 18 additions & 0 deletions packages/loopover-engine/src/types/manifest-deps-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,24 @@ export type RepositorySettings = {
* built-in three (e.g. `security: "area:security"`) for its own taxonomy. Always populated by the DB
* layer; optional so existing settings fixtures/callers need not be touched. */
typeLabels?: PrTypeLabelSet | undefined;
/** Per-repo opt-out for `loopover_plan_repo_issues` (#7429, the selfhoster-facing AI issue-planning MCP
* tool, `src/services/issue-plan-draft.ts`, #7426): when explicitly `false`, the tool returns a `disabled`
* status without ever calling the model. Default TRUE — the tool is already gated behind MCP
* `requireRepoManageAccess` and the fleet-wide `AI_SUMMARIES_ENABLED`/`AI_PUBLIC_COMMENTS_ENABLED`
* switches, so this is an ADDITIONAL per-repo layer, not the primary gate. Config-as-code only (no DB
* column, matching the Batch A/B/C pattern, #6442-#6444) — settable via `.loopover.yml settings:` only. */
issuePlanEnabled?: boolean | undefined;
/** Additional label names `loopover_plan_repo_issues` (#7429) suggests to the model alongside the repo's
* real GitHub labels and (when gittensor-enrolled) its type-label taxonomy — e.g. a project-specific
* category not yet created as a real label. Purely additive; never removes or replaces the repo's own
* labels. Default empty. Config-as-code only (no DB column) — settable via `.loopover.yml settings:` only. */
issuePlanExtraLabels?: string[] | undefined;
/** Whether `loopover_plan_repo_issues` (#7429) attempts to reuse an existing OPEN milestone by exact
* normalized-title match before creating a new one (`resolveOrCreateIssuePlanMilestone`,
* `src/services/issue-plan-draft.ts`). Default TRUE. A repo that always wants a fresh milestone per
* planning session (e.g. date-stamped titles) can set this `false` to skip the reuse lookup entirely.
* Config-as-code only (no DB column) — settable via `.loopover.yml settings:` only. */
issuePlanMilestoneReuse?: boolean | undefined;
/** Linked-issue label propagation (#priority-linked-issue-gate): the ONLY mechanism that can ever
* select the configured priority label (or any other configured mapping's PR label) — never
* inferred from a PR's title, changed files, AI output, or existing PR labels. Default disabled
Expand Down
9 changes: 9 additions & 0 deletions src/db/repositories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,9 @@ export async function getRepositorySettings(env: Env, fullName: string): Promise
autoLabelEnabled: true,
typeLabelsEnabled: true,
typeLabels: { ...DEFAULT_TYPE_LABELS },
issuePlanEnabled: true,
issuePlanExtraLabels: [],
issuePlanMilestoneReuse: true,
linkedIssueLabelPropagation: { ...DEFAULT_LINKED_ISSUE_LABEL_PROPAGATION, mappings: [] },
linkedIssueHardRules: { ...DEFAULT_LINKED_ISSUE_HARD_RULES, pointBearingLabels: [], maintainerOnlyLabels: [] },
gittensorLabel: "gittensor",
Expand Down Expand Up @@ -694,6 +697,9 @@ export async function getRepositorySettings(env: Env, fullName: string): Promise
// linkedIssueLabelPropagation now always resolves to the built-in default, never a stale DB customization).
typeLabelsEnabled: true,
typeLabels: { ...DEFAULT_TYPE_LABELS },
issuePlanEnabled: true,
issuePlanExtraLabels: [],
issuePlanMilestoneReuse: true,
linkedIssueLabelPropagation: { ...DEFAULT_LINKED_ISSUE_LABEL_PROPAGATION, mappings: [] },
linkedIssueHardRules: { ...DEFAULT_LINKED_ISSUE_HARD_RULES, pointBearingLabels: [], maintainerOnlyLabels: [] },
gittensorLabel: "gittensor",
Expand Down Expand Up @@ -829,6 +835,9 @@ export async function upsertRepositorySettings(env: Env, settings: Partial<Repos
// there's no column to write it to.
typeLabelsEnabled: true,
typeLabels: { ...DEFAULT_TYPE_LABELS },
issuePlanEnabled: true,
issuePlanExtraLabels: [],
issuePlanMilestoneReuse: true,
linkedIssueLabelPropagation: { ...DEFAULT_LINKED_ISSUE_LABEL_PROPAGATION, mappings: [] },
gittensorLabel: "gittensor",
blacklistLabel: "slop",
Expand Down
5 changes: 5 additions & 0 deletions src/openapi/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,11 @@ export const RepositorySettingsSchema = z
// Open `category -> label name` record (#label-modularity): bug/feature/priority are the built-in
// categories, but a self-hoster may register any number of additional ones (e.g. `security`).
typeLabels: z.record(z.string(), z.string()).optional(),
// loopover_plan_repo_issues config-as-code surface (#7429) -- see the field's own doc comment on
// RepositorySettings (src/types.ts) for the full contract.
issuePlanEnabled: z.boolean().optional(),
issuePlanExtraLabels: z.array(z.string()).optional(),
issuePlanMilestoneReuse: z.boolean().optional(),
linkedIssueLabelPropagation: z
.object({
enabled: z.boolean(),
Expand Down
Loading
Loading