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 apps/loopover-ui/content/docs/ams-config-precedence.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,6 @@ goal-spec override.

## See also

- [`packages/loopover-miner/docs/miner-goal-spec.md`](https://github.com/JSONbored/loopover/blob/main/packages/loopover-miner/docs/miner-goal-spec.md) — goal-spec field reference
- [MinerGoalSpec field reference](/docs/ams-goal-spec) — goal-spec field reference
- [AMS environment variable reference](/docs/ams-env-reference) — generated operator env list
- ORB's `.loopover.yml` precedence (`yml > DB > defaults`) in [Self-hosting configuration](/docs/self-hosting-configuration) — analogous documentation style, different runtime
3 changes: 1 addition & 2 deletions apps/loopover-ui/content/docs/ams-deployment.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,7 @@ exists and is readable on disk.
### Optional per-repo miner goals

Copy `.loopover-miner.yml.example` (repo root) to a target repo as `.loopover-miner.yml`. See the
`.loopover-miner.yml` field reference in
[`packages/loopover-miner/docs/miner-goal-spec.md`](https://github.com/JSONbored/loopover/blob/main/packages/loopover-miner/docs/miner-goal-spec.md).
[MinerGoalSpec field reference](/docs/ams-goal-spec).

## Fleet mode walkthrough

Expand Down
128 changes: 128 additions & 0 deletions apps/loopover-ui/content/docs/ams-goal-spec.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
---
title: "MinerGoalSpec (.loopover-miner.yml)"
description: Per-repo configuration telling an autonomous LoopOver miner what to look for and how to behave when targeting a repo -- the full field reference.
---

Per-repo configuration telling an autonomous LoopOver miner what to look for and how to behave
when targeting a repo. Parsed by `@loopover/engine` (`parseMinerGoalSpec` /
`parseMinerGoalSpecContent`); this page is the field reference. Machine-readable shape:
`packages/loopover-miner/schema/miner-goal-spec.schema.json`. Copy
`.loopover-miner.yml.example` (repo root) to `.loopover-miner.yml` and edit.

Discovery order (first match wins):

- `.loopover-miner.yml`
- `.github/loopover-miner.yml`
- `.loopover-miner.json`
- `.github/loopover-miner.json`

<Callout variant="note">
Every field is optional. Unknown keys are ignored; a malformed field falls back to its
documented default with a warning — a broken file never hard-fails the miner.
</Callout>

## Relationship to `.loopover.yml`

<FeatureRow
items={[
{
title: ".loopover.yml (review stack)",
description:
"How a maintainer's repo reviews incoming PRs (focus manifest, gate, scoring knobs).",
},
{
title: ".loopover-miner.yml (miner runtime)",
description:
"How a miner searches for and prioritizes work in a target repo. Unrelated naming concern -- not affected by the review-config naming above.",
},
]}
/>

They are read by different components and do not conflict. A miner should still treat a target
repo's public `.loopover.yml` `wantedPaths` / `blockedPaths` as a hard floor when both files
exist.

## Fields

### `minerEnabled` (boolean, default: `true`)

Explicit opt-out: a public repo with no file remains minable. Set `false` to halt all miner
targeting.

### `wantedPaths` (string list, default: `[]`)

Work areas the maintainer wants a miner to focus on. Glob list. Empty means no preference.

### `blockedPaths` (string list, default: `[]`)

Paths off-limits to a miner; candidates touching one should be skipped. Glob list. Mirrors
`.loopover.yml` `blockedPaths` semantics.

### `preferredLabels` (string list, default: `[]`)

Issue labels a miner should favor. Empty means no preference.

### `blockedLabels` (string list, default: `[]`)

Issue labels a miner must skip.

### `maxConcurrentClaims` (integer `>= 1`, default: `1`)

Maximum issues one miner may hold claimed on this repo at once.

### `issueDiscoveryPolicy` (`encouraged` | `neutral` | `discouraged`, default: `neutral`)

How strongly this repo encourages a miner to open discovery issues.

### `feasibilityGate` (object, default: `{ enabled: true, suppressedReasons: [] }`)

Per-repo tuning for the feasibility gate (`buildFeasibilityVerdict`) a miner consults before
starting work. This is config-parsing surface only — a caller wiring the gate into a decision
flow is responsible for reading and applying this policy.

- `enabled` (boolean, default: `true`) — whether the feasibility gate is consulted at all before a
miner starts work.
- `suppressedReasons` (string list, default: `[]`) — specific avoid/raise reason codes (e.g.
`duplicate_cluster_high`) this repo wants ignored.

### `selfPlagiarism` (object, default: `{ similarityThreshold: 0.85 }`)

Per-repo tuning for the Governor self-plagiarism throttle consulted before `open_pr`. Compares a
prospective PR's diff fingerprint against the miner's own recent submission history.

- `similarityThreshold` (number in `[0, 1]`, default: `0.85`) — Jaccard similarity at/above which
two fingerprints read as near-duplicates across repos.

### `killSwitch` (object, default: `{ paused: false }`)

Per-repo kill-switch consulted by the Governor chokepoint before every write action.

<Callout variant="note">
Distinct from `minerEnabled`: `minerEnabled` is a discovery-time opt-out (a miner never even
considers the repo), while `killSwitch.paused` is a runtime halt of an already-in-flight queue —
un-pausing resumes exactly where the queue left off. A separate, operator-controlled GLOBAL
kill-switch (env var `LOOPOVER_MINER_KILL_SWITCH`) halts every repo at once and always wins over
this per-repo flag.
</Callout>

- `paused` (boolean, default: `false`) — halts all miner WRITE actions for this repo without
deregistering it from targeting/discovery.

### `execution` (object, default: `{ liveModeOptIn: null }`)

Per-repo dry-run/live execution opt-in consulted by the Governor chokepoint.

<Callout variant="warn">
A freshly-configured miner always defaults to dry-run (observe/log only, never execute a
write) — this field is the only per-repo path to live mode, and it alone is not sufficient: the
miner's own operator must also separately opt in globally (env var
`LOOPOVER_MINER_LIVE_MODE=live`) before writes actually execute. A repo that wants to guarantee
it never receives live automated writes, regardless of any operator's global setting, should use
`killSwitch.paused: true` instead — the kill-switch always takes precedence over any live-mode
opt-in.
</Callout>

- `liveModeOptIn` (string or `null`, default: `null`) — must equal EXACTLY the literal `"live"` to
opt in. Any other value (a typo, `"yes"`, `"on"`, or a boolean `true` from a malformed file) is
treated as not opted in — deliberately not a boolean flag, so a fat-fingered config can never
accidentally enable live writes.
1 change: 1 addition & 0 deletions apps/loopover-ui/src/components/site/docs-nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export const docsNav: DocsGroup[] = [
{ to: "/docs/ams-config-precedence", label: "Config precedence" },
{ to: "/docs/ams-env-reference", label: "Env var reference" },
{ to: "/docs/ams-discovery-plane", label: "Discovery plane (opt-in)" },
{ to: "/docs/ams-goal-spec", label: "Miner goal spec" },
],
},
],
Expand Down
21 changes: 21 additions & 0 deletions apps/loopover-ui/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import { Route as DocsAmsUnattendedSchedulingRouteImport } from './routes/docs.a
import { Route as DocsAmsSizingRouteImport } from './routes/docs.ams-sizing'
import { Route as DocsAmsOperationsRunbookRouteImport } from './routes/docs.ams-operations-runbook'
import { Route as DocsAmsObservabilityRouteImport } from './routes/docs.ams-observability'
import { Route as DocsAmsGoalSpecRouteImport } from './routes/docs.ams-goal-spec'
import { Route as DocsAmsEnvReferenceRouteImport } from './routes/docs.ams-env-reference'
import { Route as DocsAmsDiscoveryPlaneRouteImport } from './routes/docs.ams-discovery-plane'
import { Route as DocsAmsDeploymentRouteImport } from './routes/docs.ams-deployment'
Expand Down Expand Up @@ -358,6 +359,11 @@ const DocsAmsObservabilityRoute = DocsAmsObservabilityRouteImport.update({
path: '/ams-observability',
getParentRoute: () => DocsRoute,
} as any)
const DocsAmsGoalSpecRoute = DocsAmsGoalSpecRouteImport.update({
id: '/ams-goal-spec',
path: '/ams-goal-spec',
getParentRoute: () => DocsRoute,
} as any)
const DocsAmsEnvReferenceRoute = DocsAmsEnvReferenceRouteImport.update({
id: '/ams-env-reference',
path: '/ams-env-reference',
Expand Down Expand Up @@ -489,6 +495,7 @@ export interface FileRoutesByFullPath {
'/docs/ams-deployment': typeof DocsAmsDeploymentRoute
'/docs/ams-discovery-plane': typeof DocsAmsDiscoveryPlaneRoute
'/docs/ams-env-reference': typeof DocsAmsEnvReferenceRoute
'/docs/ams-goal-spec': typeof DocsAmsGoalSpecRoute
'/docs/ams-observability': typeof DocsAmsObservabilityRoute
'/docs/ams-operations-runbook': typeof DocsAmsOperationsRunbookRoute
'/docs/ams-sizing': typeof DocsAmsSizingRoute
Expand Down Expand Up @@ -560,6 +567,7 @@ export interface FileRoutesByTo {
'/docs/ams-deployment': typeof DocsAmsDeploymentRoute
'/docs/ams-discovery-plane': typeof DocsAmsDiscoveryPlaneRoute
'/docs/ams-env-reference': typeof DocsAmsEnvReferenceRoute
'/docs/ams-goal-spec': typeof DocsAmsGoalSpecRoute
'/docs/ams-observability': typeof DocsAmsObservabilityRoute
'/docs/ams-operations-runbook': typeof DocsAmsOperationsRunbookRoute
'/docs/ams-sizing': typeof DocsAmsSizingRoute
Expand Down Expand Up @@ -635,6 +643,7 @@ export interface FileRoutesById {
'/docs/ams-deployment': typeof DocsAmsDeploymentRoute
'/docs/ams-discovery-plane': typeof DocsAmsDiscoveryPlaneRoute
'/docs/ams-env-reference': typeof DocsAmsEnvReferenceRoute
'/docs/ams-goal-spec': typeof DocsAmsGoalSpecRoute
'/docs/ams-observability': typeof DocsAmsObservabilityRoute
'/docs/ams-operations-runbook': typeof DocsAmsOperationsRunbookRoute
'/docs/ams-sizing': typeof DocsAmsSizingRoute
Expand Down Expand Up @@ -711,6 +720,7 @@ export interface FileRouteTypes {
| '/docs/ams-deployment'
| '/docs/ams-discovery-plane'
| '/docs/ams-env-reference'
| '/docs/ams-goal-spec'
| '/docs/ams-observability'
| '/docs/ams-operations-runbook'
| '/docs/ams-sizing'
Expand Down Expand Up @@ -782,6 +792,7 @@ export interface FileRouteTypes {
| '/docs/ams-deployment'
| '/docs/ams-discovery-plane'
| '/docs/ams-env-reference'
| '/docs/ams-goal-spec'
| '/docs/ams-observability'
| '/docs/ams-operations-runbook'
| '/docs/ams-sizing'
Expand Down Expand Up @@ -856,6 +867,7 @@ export interface FileRouteTypes {
| '/docs/ams-deployment'
| '/docs/ams-discovery-plane'
| '/docs/ams-env-reference'
| '/docs/ams-goal-spec'
| '/docs/ams-observability'
| '/docs/ams-operations-runbook'
| '/docs/ams-sizing'
Expand Down Expand Up @@ -1281,6 +1293,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof DocsAmsObservabilityRouteImport
parentRoute: typeof DocsRoute
}
'/docs/ams-goal-spec': {
id: '/docs/ams-goal-spec'
path: '/ams-goal-spec'
fullPath: '/docs/ams-goal-spec'
preLoaderRoute: typeof DocsAmsGoalSpecRouteImport
parentRoute: typeof DocsRoute
}
'/docs/ams-env-reference': {
id: '/docs/ams-env-reference'
path: '/ams-env-reference'
Expand Down Expand Up @@ -1478,6 +1497,7 @@ interface DocsRouteChildren {
DocsAmsDeploymentRoute: typeof DocsAmsDeploymentRoute
DocsAmsDiscoveryPlaneRoute: typeof DocsAmsDiscoveryPlaneRoute
DocsAmsEnvReferenceRoute: typeof DocsAmsEnvReferenceRoute
DocsAmsGoalSpecRoute: typeof DocsAmsGoalSpecRoute
DocsAmsObservabilityRoute: typeof DocsAmsObservabilityRoute
DocsAmsOperationsRunbookRoute: typeof DocsAmsOperationsRunbookRoute
DocsAmsSizingRoute: typeof DocsAmsSizingRoute
Expand Down Expand Up @@ -1526,6 +1546,7 @@ const DocsRouteChildren: DocsRouteChildren = {
DocsAmsDeploymentRoute: DocsAmsDeploymentRoute,
DocsAmsDiscoveryPlaneRoute: DocsAmsDiscoveryPlaneRoute,
DocsAmsEnvReferenceRoute: DocsAmsEnvReferenceRoute,
DocsAmsGoalSpecRoute: DocsAmsGoalSpecRoute,
DocsAmsObservabilityRoute: DocsAmsObservabilityRoute,
DocsAmsOperationsRunbookRoute: DocsAmsOperationsRunbookRoute,
DocsAmsSizingRoute: DocsAmsSizingRoute,
Expand Down
49 changes: 49 additions & 0 deletions apps/loopover-ui/src/routes/docs.ams-goal-spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { createFileRoute, notFound } from "@tanstack/react-router";
import { Suspense } from "react";

import { DocsPage } from "@/components/site/docs-page";
import { docsClientLoader } from "@/lib/docs-client-loader";

// Rendered from content/docs/ams-goal-spec.mdx via fumadocs-mdx's browser entry
// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow
// primitives -- not fumadocs-ui's bundled components. See docs-source.ts's comment
// for why the loader below resolves only a plain, serializable path string.
export const Route = createFileRoute("/docs/ams-goal-spec")({
loader: async () => {
const { docsSource } = await import("@/lib/docs-source");
const page = docsSource.getPage(["ams-goal-spec"]);
if (!page) throw notFound();
return { path: page.path, title: page.data.title, description: page.data.description };
},
head: () => ({
meta: [
{ title: "MinerGoalSpec (.loopover-miner.yml) — LoopOver docs" },
{
name: "description",
content:
"Per-repo configuration telling an autonomous LoopOver miner what to look for and how to behave when targeting a repo -- the full field reference.",
},
{ property: "og:title", content: "MinerGoalSpec (.loopover-miner.yml) — LoopOver docs" },
{
property: "og:description",
content:
"Per-repo configuration telling an autonomous LoopOver miner what to look for and how to behave when targeting a repo -- the full field reference.",
},
{ property: "og:url", content: "/docs/ams-goal-spec" },
],
links: [{ rel: "canonical", href: "/docs/ams-goal-spec" }],
}),
component: AmsGoalSpec,
});

function AmsGoalSpec() {
const { path, title, description } = Route.useLoaderData();
const Content = docsClientLoader.getComponent(path);
return (
<DocsPage eyebrow="Maintainers" title={title} description={description}>
<Suspense fallback={<p className="text-token-sm text-muted-foreground">Loading…</p>}>
<Content />
</Suspense>
</DocsPage>
);
}
1 change: 1 addition & 0 deletions apps/loopover-ui/src/routes/docs.index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ const AUDIENCES: Audience[] = [
{ to: "/docs/ams-config-precedence", label: "Config precedence" },
{ to: "/docs/ams-env-reference", label: "Env var reference" },
{ to: "/docs/ams-discovery-plane", label: "Discovery plane (opt-in)" },
{ to: "/docs/ams-goal-spec", label: "Miner goal spec" },
{ to: "/docs/self-hosting-docs-audit", label: "Self-host docs audit" },
{ to: "/docs/maintainer-install-trust", label: "Install & trust guide" },
{ to: "/docs/github-app", label: "GitHub App configuration" },
Expand Down
4 changes: 4 additions & 0 deletions packages/loopover-miner/docs/miner-goal-spec.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# MinerGoalSpec (`.loopover-miner.yml`)

> Also published on the docs website: [MinerGoalSpec](https://loopover.ai/docs/ams-goal-spec)
> (same content, rendered with search and the rest of the maintainer docs nav). This file remains
> the canonical source and ships inside the published `@loopover/miner` package.

Per-repo configuration telling an autonomous LoopOver miner what to look for and how to behave when targeting a repo. Parsed by `@loopover/engine` (`parseMinerGoalSpec` / `parseMinerGoalSpecContent`); this document is the field reference. Machine-readable shape: [`../schema/miner-goal-spec.schema.json`](../schema/miner-goal-spec.schema.json). Copy [`.loopover-miner.yml.example`](../../../.loopover-miner.yml.example) to `.loopover-miner.yml` and edit.

Discovery order (first match wins):
Expand Down
Loading