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):