From 6076b12a070518a438394673bed7af74bcee4059 Mon Sep 17 00:00:00 2001
From: oktofeesh1 <287075021+oktofeesh1@users.noreply.github.com>
Date: Thu, 16 Jul 2026 08:13:46 +0200
Subject: [PATCH] docs(ams): port docs/miner-goal-spec.md to a website docs
page (docs.ams-goal-spec.tsx)
Add content/docs/ams-goal-spec.mdx covering every MinerGoalSpec field
(.loopover-miner.yml) -- minerEnabled, wantedPaths/blockedPaths,
preferredLabels/blockedLabels, maxConcurrentClaims, issueDiscoveryPolicy,
feasibilityGate, selfPlagiarism, killSwitch, and execution -- and its
relationship to .loopover.yml, rendered via the existing DocsPage/Callout/
FeatureRow primitives. Add the page to docs-nav.tsx's "AMS: deployment"
subgroup and to docs.index.tsx's Maintainers audience card, and repoint
ams-deployment.mdx's and ams-config-precedence.mdx's GitHub-blob
miner-goal-spec.md cross-references to the new in-app route.
packages/loopover-miner/docs/miner-goal-spec.md stays as the canonical
source (ships inside the published @loopover/miner package; its content is
directly asserted by miner-goal-spec-doc.test.ts) with a short pointer added
to the new website page.
---
.../content/docs/ams-config-precedence.mdx | 2 +-
.../content/docs/ams-deployment.mdx | 3 +-
.../content/docs/ams-goal-spec.mdx | 128 ++++++++++++++++++
.../src/components/site/docs-nav.tsx | 1 +
apps/loopover-ui/src/routeTree.gen.ts | 21 +++
.../src/routes/docs.ams-goal-spec.tsx | 49 +++++++
apps/loopover-ui/src/routes/docs.index.tsx | 1 +
.../loopover-miner/docs/miner-goal-spec.md | 4 +
8 files changed, 206 insertions(+), 3 deletions(-)
create mode 100644 apps/loopover-ui/content/docs/ams-goal-spec.mdx
create mode 100644 apps/loopover-ui/src/routes/docs.ams-goal-spec.tsx
diff --git a/apps/loopover-ui/content/docs/ams-config-precedence.mdx b/apps/loopover-ui/content/docs/ams-config-precedence.mdx
index ebbe74655f..d50bbfd4df 100644
--- a/apps/loopover-ui/content/docs/ams-config-precedence.mdx
+++ b/apps/loopover-ui/content/docs/ams-config-precedence.mdx
@@ -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
diff --git a/apps/loopover-ui/content/docs/ams-deployment.mdx b/apps/loopover-ui/content/docs/ams-deployment.mdx
index a28071346b..6f02675bb6 100644
--- a/apps/loopover-ui/content/docs/ams-deployment.mdx
+++ b/apps/loopover-ui/content/docs/ams-deployment.mdx
@@ -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
diff --git a/apps/loopover-ui/content/docs/ams-goal-spec.mdx b/apps/loopover-ui/content/docs/ams-goal-spec.mdx
new file mode 100644
index 0000000000..505c3705ec
--- /dev/null
+++ b/apps/loopover-ui/content/docs/ams-goal-spec.mdx
@@ -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`
+
+
+ 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.
+
+
+## Relationship to `.loopover.yml`
+
+
+
+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.
+
+
+ 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.
+
+
+- `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.
+
+
+ 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.
+
+
+- `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.
diff --git a/apps/loopover-ui/src/components/site/docs-nav.tsx b/apps/loopover-ui/src/components/site/docs-nav.tsx
index 1868a2d0f1..5138d1994a 100644
--- a/apps/loopover-ui/src/components/site/docs-nav.tsx
+++ b/apps/loopover-ui/src/components/site/docs-nav.tsx
@@ -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" },
],
},
],
diff --git a/apps/loopover-ui/src/routeTree.gen.ts b/apps/loopover-ui/src/routeTree.gen.ts
index 9b86c00d8e..8c54eaf2e4 100644
--- a/apps/loopover-ui/src/routeTree.gen.ts
+++ b/apps/loopover-ui/src/routeTree.gen.ts
@@ -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'
@@ -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',
@@ -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
@@ -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
@@ -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
@@ -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'
@@ -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'
@@ -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'
@@ -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'
@@ -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
@@ -1526,6 +1546,7 @@ const DocsRouteChildren: DocsRouteChildren = {
DocsAmsDeploymentRoute: DocsAmsDeploymentRoute,
DocsAmsDiscoveryPlaneRoute: DocsAmsDiscoveryPlaneRoute,
DocsAmsEnvReferenceRoute: DocsAmsEnvReferenceRoute,
+ DocsAmsGoalSpecRoute: DocsAmsGoalSpecRoute,
DocsAmsObservabilityRoute: DocsAmsObservabilityRoute,
DocsAmsOperationsRunbookRoute: DocsAmsOperationsRunbookRoute,
DocsAmsSizingRoute: DocsAmsSizingRoute,
diff --git a/apps/loopover-ui/src/routes/docs.ams-goal-spec.tsx b/apps/loopover-ui/src/routes/docs.ams-goal-spec.tsx
new file mode 100644
index 0000000000..773aed0d8b
--- /dev/null
+++ b/apps/loopover-ui/src/routes/docs.ams-goal-spec.tsx
@@ -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 (
+
+ Loading…
}>
+
+
+
+ );
+}
diff --git a/apps/loopover-ui/src/routes/docs.index.tsx b/apps/loopover-ui/src/routes/docs.index.tsx
index 6c55c0d0b8..f99497e665 100644
--- a/apps/loopover-ui/src/routes/docs.index.tsx
+++ b/apps/loopover-ui/src/routes/docs.index.tsx
@@ -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" },
diff --git a/packages/loopover-miner/docs/miner-goal-spec.md b/packages/loopover-miner/docs/miner-goal-spec.md
index 389803c521..91af94a881 100644
--- a/packages/loopover-miner/docs/miner-goal-spec.md
+++ b/packages/loopover-miner/docs/miner-goal-spec.md
@@ -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):