feat(review): add config-as-code settings surface for the issue-planning tool#7521
Conversation
…ing tool Adds RepositorySettings.issuePlanEnabled/issuePlanExtraLabels/ issuePlanMilestoneReuse (config-as-code only, no DB column, matching the Batch A/B/C pattern) so a selfhoster can opt loopover_plan_repo_issues out per repo, suggest additional labels beyond the repo's real ones, and control whether milestone creation reuses an existing open milestone by title match or always creates fresh. Wires the fields into src/services/issue-plan-draft.ts: settings are now fetched once per call (previously only fetched when gittensor- enrolled) and reused for both the enable-check and the existing gittensor label-enrichment path, removing its own duplicate fetch. Adds matching type/parser wiring in packages/loopover-engine's FocusManifest, OpenAPI schema entries, .loopover.yml.example and config/examples/loopover.full.yml documentation (kept byte-identical per config-templates.test.ts), and a short selfhoster docs section. Closes #7429
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
loopover-ui | 44f59ca | Commit Preview URL Branch Preview URL |
Jul 20 2026, 08:53 PM |
Bundle ReportChanges will increase total bundle size by 4.04kB (0.05%) ⬆️. This is within the configured threshold ✅ Detailed changes
Affected Assets, Files, and Routes:view changes for bundle: loopover-uiAssets Changed:
|
|
Tip ✅ LoopOver review result - approve/merge recommendedReview updated: 2026-07-20 20:59:52 UTC
Review summary Nits — 5 non-blocking
Decision drivers
Context & advisory signals — never blocks the verdict
Linked issue satisfactionAddressed Review context
Contributor next steps
Signal definitions
🧪 Chat with LoopOverAsk LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.
Full command reference: https://loopover.ai/docs/loopover-commands 🧪 Experimental — new and may change. Visual preview
Click any thumbnail to open the full-size screenshot. Before = production · After = this PR's preview deploy. Scroll preview
A short scroll-through clip (desktop) — click either thumbnail to open the full animation. Evidence for scroll-linked behavior a single screenshot can't show. 🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #7521 +/- ##
==========================================
- Coverage 88.48% 88.47% -0.01%
==========================================
Files 720 720
Lines 75725 75733 +8
Branches 22543 22548 +5
==========================================
Hits 67004 67004
- Misses 7677 7679 +2
- Partials 1044 1050 +6
Flags with carried forward coverage won't be shown. Click here to find out more.
|
The rebase conflict resolution took main's openapi.json wholesale for one commit; regenerate from the final merged schemas so both this PR's and #7521's additions are present.
The rebase conflict resolution took main's openapi.json wholesale for one commit; regenerate from the final merged schemas so both this PR's and #7521's additions are present.
The rebase conflict resolution took main's openapi.json wholesale for one commit; regenerate from the final merged schemas so both this PR's and #7521's additions are present.
… report, contributor trust profiles (#7581) * fix(review): repair the public decision-accuracy metric's reversal detection The reversal query derived "mistake" status from a PR's own state after a close/merge, which can never detect a merge undone by a separate revert PR (a merged PR's state never returns to open on GitHub) and was scoped only to a frozen own-ledger snapshot. Reuse the existing reversal_reopened/ reversal_reverted events already recorded elsewhere, and blend in live fleet-wide accuracy across registered self-hosted instances so the public number reflects the current fleet, not a historical snapshot. Also surfaces the fleet's anti-gaming detection count (#2350) on the same payload. Refs #7567 * feat(site): add a public fairness report page Adds /fairness, linked from the homepage's Decision accuracy tile: the full 8-week accuracy trend, per-repo breakdown, and anti-gaming-flags- caught count, plus a short methodology note. Reuses the existing /v1/public/stats payload rather than adding a new endpoint -- aggregate counts only, no PR content or contributor identities. Refs #7567 * feat(review): add internal contributor trust profiles Per-login, per-repo gate-decision accuracy (mirrors computeGateEval, keyed by contributor_gate_history instead of the actor-free review_audit) and fairness-outlier detection that flags deviation from a project's median in either direction, never asserting fault. Composes with per-repo submission counts and moderation-violation history into one trust profile per contributor, plus a backfill mechanism reconstructing historical gate history from review_audit + pull_requests.author_login for PRs that predate contributor_gate_history. Internal, bearer-gated routes only -- never exposed publicly. Config-as- code throughout: a global LOOPOVER_FAIRNESS_ANALYTICS flag overridable via the self-repo's .loopover.yml (mirroring publicStats/ops), plus a per-repo fairnessAnalyticsMode so any installed repo can opt its own gate decisions and moderation history out of the aggregation via its own private .loopover.yml. Refs #7567 * fix(site): guard against a fleetAccuracy-less /v1/public/stats response Discovered via manual verification: the homepage stats band and the new fairness report page both read data.fleetAccuracy without a null guard, which crashes (and is caught by the route error boundary) against any currently-live API response, since that field doesn't exist until the backend change ships. Add defensive optional chaining so both surfaces degrade to the own-ledger accuracy number instead, matching the intended fallback behavior once the fields genuinely diverge only during a frontend-ahead-of-backend deployment window. Refs #7567 * chore(openapi): regenerate spec after rebase onto main The rebase conflict resolution took main's openapi.json wholesale for one commit; regenerate from the final merged schemas so both this PR's and #7521's additions are present. * fix(review): sync fairnessAnalytics config docs into loopover.full.yml The per-repo fairnessAnalyticsMode setting and fleet-wide fairnessAnalytics block were only documented in .loopover.yml.example, drifting from config/examples/loopover.full.yml which must stay byte-identical from the canonical marker onward (test/unit/config-templates.test.ts). * fix(review): remove stray null byte from contributor-gate-eval.ts A literal NUL byte had been typed into the (login, project) map-key template literal instead of a separator character, making git and other tooling treat the whole file as binary (no diff, no line-level coverage attribution). Replaced with ':', matching the composite-key separator convention used elsewhere (rag.ts, inline-comments-select.ts). * fix(ci): pin Node version in ui-preview-deploy.yml instead of reading .nvmrc node-version-file: .nvmrc (introduced repo-wide by 8bd60f0 / #7408) broke this one job: it deliberately has no actions/checkout step (see the file header -- it never runs fork/PR code), so .nvmrc doesn't exist on the runner and Setup Node has failed on every run since. That failure trips the "Record FAILED deployment for Reviewbot" step, which writes a permanent failure GitHub Deployment status -- so ORB's preview-URL discovery (preview-url.ts's getLatestDeploymentStatus) short-circuits on that failure and never even checks the separately-successful Cloudflare Workers Builds deployment, breaking the "after" screenshot/scroll-preview for every visual PR repo-wide, including this one on PR #7581. Hardcodes node-version instead, matching the same already-established exception in gittensor-impact.yml and visual-capture-fallback.yml. * fix(review): fall back to a placeholder for a missing after-GIF The scroll-preview "after" slot resolved to undefined (rendered as a bare "—") whenever there was no live preview page to capture, unlike the screenshot slot which already substitutes a loading/failed placeholder (afterPlaceholder) in the same situation. That made "GIF capture still building/failed" indistinguishable from "GIF capture isn't configured for this repo at all". actions_fallback never produces a GIF (only static PNGs), so there's no cached fallback artifact to look up here the way resolveFallbackAfterShot does for the screenshot slot -- the placeholder substitution is the whole fix. * test(review): cover the missing-limit branch on the backfill route Closes the last codecov/patch gap on PR #7581 (routes.ts's typeof body?.limit === "number" ternary only had its truthy arm exercised).

Summary
loopover_plan_repo_issues(#7426): three newRepositorySettingsfields, config-as-code only (no DB column — following the Batch A/B/C pattern, config-as-code: migrate comment/audience/public-surface settings off the DB #6442-config-as-code: migrate gate/quality/AI-review mode settings off the DB (settings: + gate: dedup) #6444), settable via.loopover.yml'ssettings:block:issuePlanEnabled(defaulttrue) — per-repo opt-out. The tool is already gated behind MCP maintainer access and the fleet-wideAI_SUMMARIES_ENABLED/AI_PUBLIC_COMMENTS_ENABLEDswitches; this is an additional per-repo layer for a repo that wants those on generally but this specific capability off.issuePlanExtraLabels(default[]) — additional label names suggested to the model alongside the repo's real GitHub labels and (when gittensor-enrolled, #7428) its type-label taxonomy. Purely additive.issuePlanMilestoneReuse(defaulttrue) — whether the milestone resolve-or-create step (#7427) attempts to reuse an existing open milestone by exact title match, or always creates fresh.src/services/issue-plan-draft.ts:resolveRepositorySettingsis now fetched once per call (previously only fetched when gittensor-enrolled, insideresolveGittensorLabelEnrichment) and reused for the new enable-check, the label merge, the milestone-reuse toggle, and the existing gittensor label-enrichment path — removing that function's own duplicate fetch.RepositorySettingsfields are a strict two-layer allowlist (a TSPick<>+ the parser's explicit per-key code — an unrecognized key is silently dropped with zero signal):src/types.ts,packages/loopover-engine/src/focus-manifest.ts(parser +FocusManifestSettingstype),packages/loopover-engine/src/types/manifest-deps-types.ts(the engine's own standalone mirror ofRepositorySettings),src/db/repositories.ts(hardcoded defaults, all 3 occurrences),src/openapi/schemas.ts(RepositorySettingsSchema— checked byui:openapi:settings-parity),.loopover.yml.exampleandconfig/examples/loopover.full.yml(kept byte-identical perconfig-templates.test.ts— these two files must match from a canonical marker onward), and a short new section inapps/loopover-ui/content/docs/self-hosting-configuration.mdx's existing "Config-as-code blocks with no dashboard equivalent" reference.Scope
type(scope): short summaryConventional Commit format, for examplefix(api): restore profile access checks.CONTRIBUTING.mdand does not reintroduce GitHub Pages, VitePress,site/, orCNAME.Closes #123) — a linked open issue is required for every contributor PR.Validation
git diff --checknpm run typecheck(root +@loopover/enginerebuilt first)npm run test --workspace @loopover/engine(592 tests) andnpm run engine-parity:drift-check— both clean;focus-manifest.tsis not a hand-duplicated twin-paired file, so no parity check is even triggered by this edit.npm run test:coveragelocally — full unsharded suite: 1022 files / 19399 tests passed (2 skipped). Zero pre-existing/unrelated failures — the flake in 3 miner test files present earlier this session was independently fixed and merged as test(unit): isolate loopover-mcp/miner tests from real machine state #7515 while this PR was in flight. New/changed code insrc/services/issue-plan-draft.tsis 100% branch-covered.npm run docs:drift-check/npm run manifest:drift-check/npm run db:migrations:check/npm run db:schema-drift:check/npm run selfhost:env-reference:check/npm run ui:openapi:check/npm run ui:openapi:settings-parity/npm run release-manifest:sync:check— all clean.npm run ui:lint/npm run ui:typecheck/npm run ui:build— run (unlike the prior 3 PRs in this series) since this one touchesapps/loopover-ui/content/docs/self-hosting-configuration.mdx. Clean (only pre-existing, unrelated warnings).issuePlanEnabled: false,issuePlanExtraLabelsmerging,issuePlanMilestoneReuse: falseskipping the reuse lookup, plus 2 newfocus-manifest.tsparser test entries and existing milestone/gittensor tests updated for the new unconditional settings fetch.Safety
RepositorySettingsSchemaupdated and parity-checked; no MCP tool input/output shape changed (the 3 fields are read internally, not new tool parameters).UI Evidencesection. (N/A — a documentation-text-only change, not a visual/interactive change.)self-hosting-configuration.mdx;CHANGELOG.mditself untouched (never edited in a normal PR).UI Evidence
N/A — the only
apps/loopover-uichange is static reference-docs prose (a new subsection of text in an existing MDX page), not a visual or interactive change.Notes
Closes #7429