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
64 changes: 64 additions & 0 deletions apps/loopover-ui/content/docs/ams-fleet-manifest.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
title: Fleet run-manifest
description: The top-level config a fleet operator authors to run the miner across many repos at once -- which repos are in scope and how the worktree/concurrency budget is split.
---

The **fleet run-manifest** is the top-level config a *fleet operator* authors to run the miner
across many repos at once: it declares which repos are in scope for a fleet run and how a finite
worktree/concurrency budget is split between them. It is parsed by
`parseFleetRunManifestContent` / `parseFleetRunManifest` in `@loopover/engine`
(`packages/loopover-engine/src/fleet-run-manifest.ts`).

It is **not** the same file as `.loopover-miner.yml` (see the [MinerGoalSpec field
reference](/docs/ams-goal-spec)) — the naming is easy to conflate. Same tolerant-parser
convention (every field optional, unknown keys ignored, a malformed field degrades to a
documented default with a warning rather than throwing), but the opposite author and the
opposite direction of intent:

<FeatureRow
items={[
{
title: ".loopover-miner.yml (goal spec)",
description:
"Author: a target repo's maintainer. Lives in: the target repo. Direction: how this one repo wants to be approached. Scope: one repo. Key fields: minerEnabled, wantedPaths, blockedPaths, preferredLabels, blockedLabels, maxConcurrentClaims, issueDiscoveryPolicy.",
},
{
title: "Fleet run-manifest",
description:
"Author: the miner (fleet) operator. Lives in: the operator's fleet-run config. Direction: which repos to work across, and how to split the budget. Scope: many repos in one run. Key fields: repos (each owner/repo + maxConcurrentWorktrees), totalConcurrentWorktrees.",
},
]}
/>

## Schema

<Callout variant="note">
Every field is optional; unknown keys are ignored; a malformed field falls back to a documented
default with a warning rather than hard-failing the run.
</Callout>

- **`repos`** — a list of target repos. Each entry is either a bare `"owner/repo"` string (uses
the default per-repo budget) or a `{ repoFullName, maxConcurrentWorktrees }` mapping. Invalid or
duplicate entries are skipped with a warning. `repoFullName` is a canonical `owner/repo`,
compatible with `opportunity-fanout.js`'s target list. Default: `[]`.
- **`repos[].maxConcurrentWorktrees`** — max concurrent worktrees (in-flight attempts) for that
repo. A positive integer (floored; sub-1 falls back to the default). Default: `1`.
- **`totalConcurrentWorktrees`** — total concurrent worktrees across the whole fleet, regardless
of per-repo budgets. A positive integer. Default: `1`.

## Wiring

This module produces only the parsed, typed manifest. Driving the fleet concurrency allocator
from it is the allocator's own concern, and the cross-repo `portfolio-queue.js` backlog reads the
same repo list — both *consume* this manifest; neither wiring lives here.

## Example (`fleet-run.yml`)

<CodeBlock
lang="yaml"
code={`totalConcurrentWorktrees: 4
repos:
- owner/repo-a
- repoFullName: owner/repo-b
maxConcurrentWorktrees: 2`}
/>
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 @@ -84,6 +84,7 @@ export const docsNav: DocsGroup[] = [
{ 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/ams-fleet-manifest", label: "Fleet run-manifest" },
],
},
],
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 @@ -62,6 +62,7 @@ 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 DocsAmsFleetManifestRouteImport } from './routes/docs.ams-fleet-manifest'
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 @@ -364,6 +365,11 @@ const DocsAmsGoalSpecRoute = DocsAmsGoalSpecRouteImport.update({
path: '/ams-goal-spec',
getParentRoute: () => DocsRoute,
} as any)
const DocsAmsFleetManifestRoute = DocsAmsFleetManifestRouteImport.update({
id: '/ams-fleet-manifest',
path: '/ams-fleet-manifest',
getParentRoute: () => DocsRoute,
} as any)
const DocsAmsEnvReferenceRoute = DocsAmsEnvReferenceRouteImport.update({
id: '/ams-env-reference',
path: '/ams-env-reference',
Expand Down Expand Up @@ -495,6 +501,7 @@ export interface FileRoutesByFullPath {
'/docs/ams-deployment': typeof DocsAmsDeploymentRoute
'/docs/ams-discovery-plane': typeof DocsAmsDiscoveryPlaneRoute
'/docs/ams-env-reference': typeof DocsAmsEnvReferenceRoute
'/docs/ams-fleet-manifest': typeof DocsAmsFleetManifestRoute
'/docs/ams-goal-spec': typeof DocsAmsGoalSpecRoute
'/docs/ams-observability': typeof DocsAmsObservabilityRoute
'/docs/ams-operations-runbook': typeof DocsAmsOperationsRunbookRoute
Expand Down Expand Up @@ -567,6 +574,7 @@ export interface FileRoutesByTo {
'/docs/ams-deployment': typeof DocsAmsDeploymentRoute
'/docs/ams-discovery-plane': typeof DocsAmsDiscoveryPlaneRoute
'/docs/ams-env-reference': typeof DocsAmsEnvReferenceRoute
'/docs/ams-fleet-manifest': typeof DocsAmsFleetManifestRoute
'/docs/ams-goal-spec': typeof DocsAmsGoalSpecRoute
'/docs/ams-observability': typeof DocsAmsObservabilityRoute
'/docs/ams-operations-runbook': typeof DocsAmsOperationsRunbookRoute
Expand Down Expand Up @@ -643,6 +651,7 @@ export interface FileRoutesById {
'/docs/ams-deployment': typeof DocsAmsDeploymentRoute
'/docs/ams-discovery-plane': typeof DocsAmsDiscoveryPlaneRoute
'/docs/ams-env-reference': typeof DocsAmsEnvReferenceRoute
'/docs/ams-fleet-manifest': typeof DocsAmsFleetManifestRoute
'/docs/ams-goal-spec': typeof DocsAmsGoalSpecRoute
'/docs/ams-observability': typeof DocsAmsObservabilityRoute
'/docs/ams-operations-runbook': typeof DocsAmsOperationsRunbookRoute
Expand Down Expand Up @@ -720,6 +729,7 @@ export interface FileRouteTypes {
| '/docs/ams-deployment'
| '/docs/ams-discovery-plane'
| '/docs/ams-env-reference'
| '/docs/ams-fleet-manifest'
| '/docs/ams-goal-spec'
| '/docs/ams-observability'
| '/docs/ams-operations-runbook'
Expand Down Expand Up @@ -792,6 +802,7 @@ export interface FileRouteTypes {
| '/docs/ams-deployment'
| '/docs/ams-discovery-plane'
| '/docs/ams-env-reference'
| '/docs/ams-fleet-manifest'
| '/docs/ams-goal-spec'
| '/docs/ams-observability'
| '/docs/ams-operations-runbook'
Expand Down Expand Up @@ -867,6 +878,7 @@ export interface FileRouteTypes {
| '/docs/ams-deployment'
| '/docs/ams-discovery-plane'
| '/docs/ams-env-reference'
| '/docs/ams-fleet-manifest'
| '/docs/ams-goal-spec'
| '/docs/ams-observability'
| '/docs/ams-operations-runbook'
Expand Down Expand Up @@ -1300,6 +1312,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof DocsAmsGoalSpecRouteImport
parentRoute: typeof DocsRoute
}
'/docs/ams-fleet-manifest': {
id: '/docs/ams-fleet-manifest'
path: '/ams-fleet-manifest'
fullPath: '/docs/ams-fleet-manifest'
preLoaderRoute: typeof DocsAmsFleetManifestRouteImport
parentRoute: typeof DocsRoute
}
'/docs/ams-env-reference': {
id: '/docs/ams-env-reference'
path: '/ams-env-reference'
Expand Down Expand Up @@ -1497,6 +1516,7 @@ interface DocsRouteChildren {
DocsAmsDeploymentRoute: typeof DocsAmsDeploymentRoute
DocsAmsDiscoveryPlaneRoute: typeof DocsAmsDiscoveryPlaneRoute
DocsAmsEnvReferenceRoute: typeof DocsAmsEnvReferenceRoute
DocsAmsFleetManifestRoute: typeof DocsAmsFleetManifestRoute
DocsAmsGoalSpecRoute: typeof DocsAmsGoalSpecRoute
DocsAmsObservabilityRoute: typeof DocsAmsObservabilityRoute
DocsAmsOperationsRunbookRoute: typeof DocsAmsOperationsRunbookRoute
Expand Down Expand Up @@ -1546,6 +1566,7 @@ const DocsRouteChildren: DocsRouteChildren = {
DocsAmsDeploymentRoute: DocsAmsDeploymentRoute,
DocsAmsDiscoveryPlaneRoute: DocsAmsDiscoveryPlaneRoute,
DocsAmsEnvReferenceRoute: DocsAmsEnvReferenceRoute,
DocsAmsFleetManifestRoute: DocsAmsFleetManifestRoute,
DocsAmsGoalSpecRoute: DocsAmsGoalSpecRoute,
DocsAmsObservabilityRoute: DocsAmsObservabilityRoute,
DocsAmsOperationsRunbookRoute: DocsAmsOperationsRunbookRoute,
Expand Down
49 changes: 49 additions & 0 deletions apps/loopover-ui/src/routes/docs.ams-fleet-manifest.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-fleet-manifest.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-fleet-manifest")({
loader: async () => {
const { docsSource } = await import("@/lib/docs-source");
const page = docsSource.getPage(["ams-fleet-manifest"]);
if (!page) throw notFound();
return { path: page.path, title: page.data.title, description: page.data.description };
},
head: () => ({
meta: [
{ title: "Fleet run-manifest — LoopOver docs" },
{
name: "description",
content:
"The top-level config a fleet operator authors to run the miner across many repos at once -- which repos are in scope and how the worktree/concurrency budget is split.",
},
{ property: "og:title", content: "Fleet run-manifest — LoopOver docs" },
{
property: "og:description",
content:
"The top-level config a fleet operator authors to run the miner across many repos at once -- which repos are in scope and how the worktree/concurrency budget is split.",
},
{ property: "og:url", content: "/docs/ams-fleet-manifest" },
],
links: [{ rel: "canonical", href: "/docs/ams-fleet-manifest" }],
}),
component: AmsFleetManifest,
});

function AmsFleetManifest() {
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 @@ -82,6 +82,7 @@ const AUDIENCES: Audience[] = [
{ 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/ams-fleet-manifest", label: "Fleet run-manifest" },
{ 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/fleet-run-manifest.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Fleet run-manifest

> Also published on the docs website: [Fleet run-manifest](https://loopover.ai/docs/ams-fleet-manifest)
> (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.

The **fleet run-manifest** is the top-level config a *fleet operator* authors to run the miner across many repos
at once: it declares which repos are in scope for a fleet run and how a finite worktree/concurrency budget is
split between them. It is parsed by `parseFleetRunManifestContent` / `parseFleetRunManifest` in
Expand Down
Loading