From 3e3ced288d3be86eda9e7def47d0474b37e89343 Mon Sep 17 00:00:00 2001
From: JSONbored <49853598+JSONbored@users.noreply.github.com>
Date: Tue, 14 Jul 2026 02:21:57 -0700
Subject: [PATCH 1/4] feat(docker)!: Phase 7 - full-cutover rename
Docker/k8s/systemd manifests to loopover-*
Phase 7 of the gittensory -> loopover rebrand (#5705): renames every
Docker/k8s/systemd resource identifier and the operator-mounted
gittensory-config/ directory convention, full cutover.
- Root Dockerfile: comments, DATABASE_PATH default (gittensory.sqlite ->
loopover.sqlite).
- k8s/miner-deployment.yaml, k8s/miner-secret.example.yaml, k8s/README.md:
StatefulSet/label/service/secret names, image tag, kubectl examples.
- systemd/gittensory-miner.service.example -> loopover-miner.service.example,
systemd/gittensory-miner-ui.service.example -> loopover-miner-ui.service.example
(renamed to match the existing loopover-docker-prune.*.example
convention): unit descriptions, User=, host paths, install steps.
- docker-compose.yml + .gitignore + config/examples/: the operator-mounted
./gittensory-config -> ./loopover-config bind-mount convention, plus the
stale ~/.config/gittensory-miner AMS-ledger-export doc-drift left behind
by the config-directory rename in #5721.
- scripts/gittensory-config-lint.ts -> scripts/loopover-config-lint.ts
(git mv, its own error-message prefix, and every reference: package.json,
docs, tests).
- scripts/selfhost-update.sh, scripts/selfhost-post-update-check.sh: the
same directory-convention comments.
- 6 apps/gittensory-ui self-hosting docs pages (operations, quickstart,
configuration, release-checklist, docs-audit, maintainer-self-hosting):
every gittensory-config/ code example and prose reference.
Explicitly NOT touched: .env.example's Sentry/OTEL/domain-example/hosted-
collector-URL prose (Phase 8 scope, unrelated to Docker/k8s) and
apps/gittensory-*/packages/gittensory-* directory names themselves
(Phase 5).
BREAKING CHANGE: the self-host operator-mounted config directory is now
./loopover-config (was ./gittensory-config); systemd unit filenames
changed; k8s StatefulSet/Secret/label names changed. No dual-read/alias,
per the epic's full-cutover mandate. Self-host operators must rename
their existing ./gittensory-config directory (and any k8s/systemd units
built from the old examples) to match.
---
.env.example | 4 +--
.gitignore | 8 ++---
Dockerfile | 8 ++---
.../routes/docs.maintainer-self-hosting.tsx | 8 ++---
.../docs.self-hosting-configuration.tsx | 8 ++---
.../routes/docs.self-hosting-docs-audit.tsx | 2 +-
.../routes/docs.self-hosting-operations.tsx | 10 +++---
.../routes/docs.self-hosting-quickstart.tsx | 8 ++---
.../docs.self-hosting-release-checklist.tsx | 6 ++--
config/examples/TEMPLATES.md | 16 +++++-----
config/examples/global.loopover.yml | 2 +-
docker-compose.yml | 10 +++---
k8s/README.md | 8 ++---
k8s/miner-deployment.yaml | 26 +++++++--------
k8s/miner-secret.example.yaml | 8 ++---
package.json | 2 +-
...config-lint.ts => loopover-config-lint.ts} | 2 +-
scripts/selfhost-post-update-check.sh | 2 +-
scripts/selfhost-update.sh | 2 +-
...mple => loopover-miner-ui.service.example} | 26 +++++++--------
...example => loopover-miner.service.example} | 32 +++++++++----------
.../docs-selfhost-activation-paths.test.ts | 2 +-
.../docs-selfhost-git-deploy-hygiene.test.ts | 2 +-
.../docs-selfhost-update-rollback.test.ts | 2 +-
...ts => loopover-config-lint-script.test.ts} | 2 +-
test/unit/mcp-cli-basics.test.ts | 6 ++--
26 files changed, 106 insertions(+), 106 deletions(-)
rename scripts/{gittensory-config-lint.ts => loopover-config-lint.ts} (97%)
rename systemd/{gittensory-miner-ui.service.example => loopover-miner-ui.service.example} (71%)
rename systemd/{gittensory-miner.service.example => loopover-miner.service.example} (59%)
rename test/unit/{gittensory-config-lint-script.test.ts => loopover-config-lint-script.test.ts} (99%)
diff --git a/.env.example b/.env.example
index 9d780549cb..7b34a2ae76 100644
--- a/.env.example
+++ b/.env.example
@@ -509,12 +509,12 @@ REDIS_URL=redis://redis:6379 # REQUIRED for the self-host review
# GRAFANA_REPORTING_EXPORT_INTERVAL_SECONDS=30 # refresh cadence for the redacted reporting SQLite export
# LOOPOVER_REPORTING_SOURCE_DB=/appdb/loopover.sqlite # if DATABASE_PATH=/data/custom.sqlite, set /appdb/custom.sqlite
#
-# AMS (gittensory-miner) ledger dashboards — only useful when a miner ALSO runs on this same host (see
+# AMS (loopover-miner) ledger dashboards — only useful when a miner ALSO runs on this same host (see
# packages/gittensory-miner/docs/observability.md). Requires --profile ams-observability, separate from the
# general observability profile above: an engine-only deployment has nothing for this exporter to read.
# Grafana never mounts the live ledgers directly — this exporter reads them read-only and writes a redacted
# snapshot (free-form attempt_log_events.reason/.payload_json dropped) into the same reporting volume.
-# LOOPOVER_MINER_CONFIG_DIR=~/.config/gittensory-miner # host dir the exporter mounts read-only at /ams-ledgers
+# LOOPOVER_MINER_CONFIG_DIR=~/.config/loopover-miner # host dir the exporter mounts read-only at /ams-ledgers
# LOOPOVER_AMS_REPORTING_EXPORT_INTERVAL_SECONDS=30 # refresh cadence for the redacted AMS ledger exports
#
# Claude usage telemetry → OTEL collector → Prometheus → the Claude usage dashboard. OFF by default.
diff --git a/.gitignore b/.gitignore
index faf6ce49fa..6fd180e0f6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,10 +35,10 @@ alertmanager/*_url
alertmanager/*_url_file
# Private self-host operator config. Root AGENTS.md/CLAUDE.md are public project docs;
# repo-scoped review instructions live under this ignored mount.
-gittensory-config/
-gittensory-config.backup-*/
+loopover-config/
+loopover-config.backup-*/
# Operator deploy-backup snapshots -- contains raw .env/.yml backups and config tarballs, same
-# exposure class as gittensory-config/ above (secrets + private review rules), just under a
+# exposure class as loopover-config/ above (secrets + private review rules), just under a
# different directory name that the pattern above does not match.
.deploy-backups/
# Codex/CLI auth state must only live in runtime volumes or operator home dirs.
@@ -55,7 +55,7 @@ apps/gittensory-ui/public/downloads/loopover-extension.zip
.worker-configuration.gen-check.d.ts
# Ad-hoc operator backup files (e.g. `cp file.yml file.yml.bak-notes-20260707`) -- general
# catch-alls so a stray manual snapshot never dirties `git status` on a Git-backed self-host
-# checkout. Trailing on purpose: the narrower gittensory-config.backup-*/ and .deploy-backups/
+# checkout. Trailing on purpose: the narrower loopover-config.backup-*/ and .deploy-backups/
# rules above already cover their specific directories, and nothing tracked in the repo matches
# either pattern (verified via `git ls-files | grep -E '\.bak-|\.backup-'`) (#1660).
*.bak-*
diff --git a/Dockerfile b/Dockerfile
index cba48c683b..0dc7e341c9 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-# Self-host image for gittensory-api (#980). Runs the SAME Worker handlers on Node via src/server.ts —
+# Self-host image for loopover-api (#980). Runs the SAME Worker handlers on Node via src/server.ts —
# the Cloudflare bindings become self-host adapters (D1 -> node:sqlite, Queue -> in-process). The hosted
# Cloudflare Worker (wrangler) deploy is unaffected. SECRETS ARE NEVER BAKED: supply them at run time via
# the .env file or mounted *_FILE secrets (see docker-compose.yml + .env.example).
@@ -13,7 +13,7 @@ WORKDIR /app
# (workspaces: apps/*, packages/*), and `npm ci` only symlinks node_modules/ to a workspace whose
# directory already exists on disk. Copying just the root package*.json first (the usual dependency-layer
# caching trick) left every workspace package.json missing at `npm ci` time, so npm silently skipped every
-# internal symlink -- @loopover/engine (a workspace dependency of gittensory-miner's checked-in
+# internal symlink -- @loopover/engine (a workspace dependency of loopover-miner's checked-in
# lib/*.js artifacts, #2281) then couldn't be resolved by esbuild no matter how/when its own dist/ was built.
COPY . .
# --ignore-scripts: no native builds are needed (SQLite is the built-in node:sqlite; @hono/node-server is
@@ -32,7 +32,7 @@ ARG LOOPOVER_VERSION=
ENV NODE_ENV=production \
PLATFORM=self-hosted \
PORT=8787 \
- DATABASE_PATH=/data/gittensory.sqlite \
+ DATABASE_PATH=/data/loopover.sqlite \
MIGRATIONS_DIR=/app/migrations \
NPM_CONFIG_PREFIX=/home/node/.npm-global \
LOOPOVER_VERSION=${LOOPOVER_VERSION}
@@ -96,7 +96,7 @@ FROM runtime-base AS runtime-prebuilt
COPY --chown=node:node dist/server.mjs ./dist/server.mjs
COPY --chown=node:node migrations ./migrations
# Generic, safe self-host private-config templates (config/examples/, #layered-private-config) — reference only.
-# GITTENSORY_REPO_CONFIG_DIR still points at the operator-mounted /config, so shipping these activates nothing.
+# LOOPOVER_REPO_CONFIG_DIR still points at the operator-mounted /config, so shipping these activates nothing.
COPY --chown=node:node config/examples ./config/examples
# Default local/operator builds still build the bundle inside Docker, but only the JS bundle reaches runtime.
diff --git a/apps/gittensory-ui/src/routes/docs.maintainer-self-hosting.tsx b/apps/gittensory-ui/src/routes/docs.maintainer-self-hosting.tsx
index 9d506bc9e5..059cc16999 100644
--- a/apps/gittensory-ui/src/routes/docs.maintainer-self-hosting.tsx
+++ b/apps/gittensory-ui/src/routes/docs.maintainer-self-hosting.tsx
@@ -223,9 +223,9 @@ function MaintainerSelfHosting() {
Checks: write included — re-approve on existing Apps after permission bumps).
- Mount ./gittensory-config and copy{" "}
+ Mount ./loopover-config and copy{" "}
config/examples/global.loopover.yml →{" "}
- gittensory-config/.loopover.yml for a centralized private default (per-repo
+ loopover-config/.loopover.yml for a centralized private default (per-repo
files deep-merge on top).
@@ -245,12 +245,12 @@ function MaintainerSelfHosting() {
{
title: "Single-command repo onboarding",
description:
- "Today: edit .env allowlist, copy YAML templates, sign into the panel, click activate. Proposed: one CLI/API command that adds owner/repo to LOOPOVER_REVIEW_REPOS, seeds gittensory-config/owner__repo/.loopover.yml from global.loopover.yml, and POSTs activation — idempotent, dry-run aware.",
+ "Today: edit .env allowlist, copy YAML templates, sign into the panel, click activate. Proposed: one CLI/API command that adds owner/repo to LOOPOVER_REVIEW_REPOS, seeds loopover-config/owner__repo/.loopover.yml from global.loopover.yml, and POSTs activation — idempotent, dry-run aware.",
},
{
title: "Centralized private default only",
description:
- "Most fleets need one gittensory-config/.loopover.yml with optional per-repo overrides — docs now treat that as the default story instead of implying every repo needs its own file.",
+ "Most fleets need one loopover-config/.loopover.yml with optional per-repo overrides — docs now treat that as the default story instead of implying every repo needs its own file.",
},
{
title: "Advisory-by-default on first install",
diff --git a/apps/gittensory-ui/src/routes/docs.self-hosting-configuration.tsx b/apps/gittensory-ui/src/routes/docs.self-hosting-configuration.tsx
index f3d747fb7c..afb2acab7b 100644
--- a/apps/gittensory-ui/src/routes/docs.self-hosting-configuration.tsx
+++ b/apps/gittensory-ui/src/routes/docs.self-hosting-configuration.tsx
@@ -135,8 +135,8 @@ function SelfHostingConfiguration() {
cp config/examples/loopover.minimal.yml .loopover.yml
# Self-host private mount (operator-only policy)
-mkdir -p gittensory-config
-cp config/examples/global.loopover.yml gittensory-config/.loopover.yml`}
+mkdir -p loopover-config
+cp config/examples/global.loopover.yml loopover-config/.loopover.yml`}
/>
Keep anti-abuse thresholds, maintainer allowlists, and autonomy dials in the{" "}
@@ -144,7 +144,7 @@ cp config/examples/global.loopover.yml gittensory-config/.loopover.yml`}
read. config/examples/TEMPLATES.md documents the public-vs-private split and
how to apply the templates to gittensory, awesome-claude, and{" "}
metagraphed without committing private policy. Lint before deploy:{" "}
- npx tsx scripts/gittensory-config-lint.ts path/to/.loopover.yml.
+ npx tsx scripts/loopover-config-lint.ts path/to/.loopover.yml.
- Operator-owned paths:gittensory-config/,{" "}
+ Operator-owned paths:loopover-config/,{" "}
loopover-data, and secrets via .env or *_FILE{" "}
mounts — never baked into images.
- ./gittensory-config/ bind mount — private per-repo{" "}
+ ./loopover-config/ bind mount — private per-repo{" "}
.loopover.yml policy.
@@ -984,7 +984,7 @@ SENTRY_ORG_SLUG=cp docker-compose.yml docker-compose.yml.bak-notes-20260707 does not count
against this — the trailing *.bak-*/*.backup-* patterns in{" "}
.gitignore keep stray manual backups out of git status entirely,
- on top of the narrower gittensory-config.backup-*/ and{" "}
+ on top of the narrower loopover-config.backup-*/ and{" "}
.deploy-backups/ patterns that already covered those specific directories.
scripts/selfhost-update.sh (below) checks this for you and refuses to
continue on a dirty tree.
@@ -1048,7 +1048,7 @@ GITTENSORY_IMAGE=ghcr.io/jsonbored/loopover-selfhost@sha256:... ./scripts/deploy
None of this touches operator-owned state: .env, the{" "}
- gittensory-config/ mount, .deploy-backups/, any{" "}
+ loopover-config/ mount, .deploy-backups/, any{" "}
*.local or docker-compose.local-*.yml compose override, or
Alertmanager file, and every named data volume are already gitignored or outside the source
tree entirely, so a fetch-and-rebuild never touches them. See the{" "}
@@ -1191,7 +1191,7 @@ docker inspect --format '{{.Config.Image}}' "$(docker compose ps -q loopover)"`}
docker compose down (without -v) leaves every named volume (
loopover-data, loopover-pg, qdrant-data,{" "}
loopover-backups, grafana-data, and the rest declared in{" "}
- docker-compose.yml) on disk, along with the ./gittensory-config{" "}
+ docker-compose.yml) on disk, along with the ./loopover-config{" "}
host directory (a bind mount, not a named volume, so it is never affected by -v{" "}
either way). Pick one:
@@ -1210,7 +1210,7 @@ docker inspect --format '{{.Config.Image}}' "$(docker compose ps -q loopover)"`}
{
title: "Delete everything",
description:
- "docker compose down -v removes every named volume permanently — the review database, vector index, Grafana dashboards state, and any local backup archives in loopover-backups go with it. This does not touch ./gittensory-config (delete that host directory yourself if it should go too).",
+ "docker compose down -v removes every named volume permanently — the review database, vector index, Grafana dashboards state, and any local backup archives in loopover-backups go with it. This does not touch ./loopover-config (delete that host directory yourself if it should go too).",
},
]}
/>
diff --git a/apps/gittensory-ui/src/routes/docs.self-hosting-quickstart.tsx b/apps/gittensory-ui/src/routes/docs.self-hosting-quickstart.tsx
index 2f03bde8f5..0bd9245da5 100644
--- a/apps/gittensory-ui/src/routes/docs.self-hosting-quickstart.tsx
+++ b/apps/gittensory-ui/src/routes/docs.self-hosting-quickstart.tsx
@@ -226,11 +226,11 @@ SELFHOST_DEPLOYMENT_MODE=dry-run # keep shadowing until you trust output`}
Sign in to the control panel (ADMIN_GITHUB_LOGINS must include your GitHub
diff --git a/apps/gittensory-ui/src/routes/docs.self-hosting-release-checklist.tsx b/apps/gittensory-ui/src/routes/docs.self-hosting-release-checklist.tsx
index 6e65cb0b29..ba96423791 100644
--- a/apps/gittensory-ui/src/routes/docs.self-hosting-release-checklist.tsx
+++ b/apps/gittensory-ui/src/routes/docs.self-hosting-release-checklist.tsx
@@ -239,7 +239,7 @@ git push origin orb-v0.1.0`}
Only the gittensory service restarts (--no-deps);{" "}
- .env, data volumes, and gittensory-config/ are untouched;{" "}
+ .env, data volumes, and loopover-config/ are untouched;{" "}
/ready returns 200 after the health-check wait.
@@ -538,7 +538,7 @@ docker rm -f gt-qdrant && docker network rm gt-rag-smoke`}
{
title: "Private repo config — NOT included",
description:
- "gittensory-config and **/gittensory-config are excluded via .dockerignore; only the generic config/examples/ reference templates are copied, and LOOPOVER_REPO_CONFIG_DIR is resolved against an operator-mounted /config at runtime.",
+ "loopover-config and **/loopover-config are excluded via .dockerignore; only the generic config/examples/ reference templates are copied, and LOOPOVER_REPO_CONFIG_DIR is resolved against an operator-mounted /config at runtime.",
},
{
title: "Data volumes — NOT included",
@@ -675,7 +675,7 @@ The \`latest\` tag now points here.
## Operator-owned
-- \`.env\`, \`gittensory-config/\`, and all data volumes (database, Redis, Qdrant, Grafana) — never
+- \`.env\`, \`loopover-config/\`, and all data volumes (database, Redis, Qdrant, Grafana) — never
overwritten by an update and never baked into the image.
- GitHub App credentials or \`ORB_ENROLLMENT_SECRET\`, AI-provider credentials, and any \`SENTRY_DSN\`.
- Resource limits and profile selection — see [Resource profiles](https://gittensory.aethereal.dev/docs/self-hosting-operations)
diff --git a/config/examples/TEMPLATES.md b/config/examples/TEMPLATES.md
index 53ace9ab1e..34d962d8f4 100644
--- a/config/examples/TEMPLATES.md
+++ b/config/examples/TEMPLATES.md
@@ -39,7 +39,7 @@ for precedence and deep-merge rules.
**Never commit real private policy** (maintainer logins, thresholds, autonomy dials you do not want
contributors to read) into a public repository. Copy `global.loopover.yml` into your gitignored
-`gittensory-config/` mount and edit there.
+`loopover-config/` mount and edit there.
## Quick start
@@ -53,16 +53,16 @@ cp config/examples/loopover.minimal.yml .loopover.yml
### Self-host private mount (operator-only policy)
```bash
-mkdir -p gittensory-config
-cp config/examples/global.loopover.yml gittensory-config/.loopover.yml
+mkdir -p loopover-config
+cp config/examples/global.loopover.yml loopover-config/.loopover.yml
# edit your-admin-login placeholders before going live
# optional per-repo overlay:
-mkdir -p gittensory-config/myorg__myrepo
-cp config/examples/repo-override.loopover.yml gittensory-config/myorg__myrepo/.loopover.yml
+mkdir -p loopover-config/myorg__myrepo
+cp config/examples/repo-override.loopover.yml loopover-config/myorg__myrepo/.loopover.yml
```
Point `GITTENSORY_REPO_CONFIG_DIR` at that directory (default `/config` in `docker-compose.yml` maps
-`./gittensory-config`).
+`./loopover-config`).
## Fleet examples (without committing private policy)
@@ -73,7 +73,7 @@ into public git** — use the private mount for anything marked *private* below.
- **Public** `.loopover.yml` in the repo: work-area guardrails, test expectations, gate dimensions
contributors should understand.
-- **Private** `gittensory-config/` (gitignored locally, operator mount in production): fleet
+- **Private** `loopover-config/` (gitignored locally, operator mount in production): fleet
autonomy, anti-abuse caps, maintainer exemption lists — the same split described in
[`global.loopover.yml`](./global.loopover.yml).
- Start from `loopover.minimal.yml` in the public repo until gate semantics are tuned, then promote
@@ -101,5 +101,5 @@ Every template in this directory is parsed in CI (`test/unit/config-templates.te
`.loopover.yml.example` from `# WHERE IT LIVES` onward. Lint a local file before deploy:
```bash
-npx tsx scripts/gittensory-config-lint.ts path/to/.loopover.yml
+npx tsx scripts/loopover-config-lint.ts path/to/.loopover.yml
```
diff --git a/config/examples/global.loopover.yml b/config/examples/global.loopover.yml
index 27759abdbb..58213e6a9b 100644
--- a/config/examples/global.loopover.yml
+++ b/config/examples/global.loopover.yml
@@ -3,7 +3,7 @@
# ============================================================================
#
# Copy this file to the ROOT of your own GITTENSORY_REPO_CONFIG_DIR mount (e.g.
-# `./gittensory-config/.loopover.yml` for the default docker-compose.yml mount) and edit your
+# `./loopover-config/.loopover.yml` for the default docker-compose.yml mount) and edit your
# copy — never this one. It applies to every repo that has no per-repo file of its own, and is
# deep-merged UNDER any per-repo file that does exist (see ../README.md for the precedence chain
# and merge semantics; see ../../.loopover.yml.example at the repo root for every supported
diff --git a/docker-compose.yml b/docker-compose.yml
index 4ed20e517b..8de0d5fea2 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -142,11 +142,11 @@ services:
# ORB_BROKER_URL: https://api.loopover.ai # override only for a private Orb deployment
volumes:
- loopover-data:/data
- # Container-private per-repo config dir (read-only). Create ./gittensory-config/{owner}__{repo}/.loopover.yml
- # locally, optionally alongside a ./gittensory-config/.loopover.yml global default that every per-repo file
+ # Container-private per-repo config dir (read-only). Create ./loopover-config/{owner}__{repo}/.loopover.yml
+ # locally, optionally alongside a ./loopover-config/.loopover.yml global default that every per-repo file
# is deep-merged over (gitignored — never commit real policy; see config/examples/ for generic templates).
# Absent ⇒ Docker mounts an empty dir ⇒ defaults apply.
- - ./gittensory-config:/config:ro
+ - ./loopover-config:/config:ro
# Mounted read-only at /run/secrets/ (Compose's default target). See the top-level `secrets:`
# block above and secrets/README.md.
#
@@ -835,7 +835,7 @@ services:
start_period: 30s
retries: 3
- # Redacted AMS (gittensory-miner) ledger export -- a DEDICATED profile, not bundled into `observability`: unlike
+ # Redacted AMS (loopover-miner) ledger export -- a DEDICATED profile, not bundled into `observability`: unlike
# the engine's own reporting-exporter above, this only does anything useful when a miner is ALSO running on this
# same host with its ledgers under LOOPOVER_MINER_CONFIG_DIR (an engine-only maintainer deployment has nothing
# for it to read). Grafana must never mount that directory directly -- attempt_log_events.reason/.payload_json
@@ -851,7 +851,7 @@ services:
volumes:
# Read-only live-ledger access for the exporter only -- Grafana does not get this mount (mirrors the
# /appdb:ro pattern above). Point this at the SAME host directory your miner uses as LOOPOVER_MINER_CONFIG_DIR.
- - "${LOOPOVER_MINER_CONFIG_DIR:-~/.config/gittensory-miner}:/ams-ledgers:ro"
+ - "${LOOPOVER_MINER_CONFIG_DIR:-~/.config/loopover-miner}:/ams-ledgers:ro"
- grafana-reporting-data:/reporting
# Directory bind mount, not a single-file one -- see reporting-exporter's own comment above for why
# (a single-file mount pins to the pre-`git pull` inode and silently never picks up a script update).
diff --git a/k8s/README.md b/k8s/README.md
index 4087e756e3..6227ab666e 100644
--- a/k8s/README.md
+++ b/k8s/README.md
@@ -1,4 +1,4 @@
-# Kubernetes manifests — gittensory-miner (AMS) fleet mode
+# Kubernetes manifests — loopover-miner (AMS) fleet mode
Example manifests for running **N isolated miner workers** on a small Kubernetes cluster, as an alternative to
`docker run` / docker-compose on a single host. Built on the existing
@@ -19,8 +19,8 @@ use a StatefulSet.
1. **Build and push the image** from the monorepo root, then set `image:` in `miner-deployment.yaml`:
```sh
- docker build -f packages/gittensory-miner/Dockerfile -t /gittensory-miner:latest .
- docker push /gittensory-miner:latest
+ docker build -f packages/gittensory-miner/Dockerfile -t /loopover-miner:latest .
+ docker push /loopover-miner:latest
```
2. **Create the Secret** (fill in real values first — never commit the filled-in copy):
```sh
@@ -37,7 +37,7 @@ use a StatefulSet.
Each replica is one isolated worker with its own volume. Scale the fleet with:
```sh
-kubectl scale statefulset/gittensory-miner --replicas=
+kubectl scale statefulset/loopover-miner --replicas=
```
or by editing `replicas:` in `miner-deployment.yaml` and re-applying. New replicas each get a fresh
diff --git a/k8s/miner-deployment.yaml b/k8s/miner-deployment.yaml
index 52eca6cd63..501a329f04 100644
--- a/k8s/miner-deployment.yaml
+++ b/k8s/miner-deployment.yaml
@@ -1,4 +1,4 @@
-# Kubernetes workload for gittensory-miner (AMS) fleet mode (#5181).
+# Kubernetes workload for loopover-miner (AMS) fleet mode (#5181).
#
# This is a StatefulSet, NOT a Deployment: the miner keeps all of its state in local SQLite ledgers
# (claim-ledger.sqlite3, plan-store.sqlite3, …) under LOOPOVER_MINER_CONFIG_DIR, and those stores are NOT
@@ -7,27 +7,27 @@
# fully isolated state — the safety property this manifest exists to guarantee. See k8s/README.md.
#
# Image: build from packages/gittensory-miner/Dockerfile at the monorepo root and push to your registry:
-# docker build -f packages/gittensory-miner/Dockerfile -t /gittensory-miner:latest .
+# docker build -f packages/gittensory-miner/Dockerfile -t /loopover-miner:latest .
# Then set `image:` below. Secrets come from k8s/miner-secret.example.yaml (apply that first).
apiVersion: apps/v1
kind: StatefulSet
metadata:
- name: gittensory-miner
+ name: loopover-miner
labels:
- app.kubernetes.io/name: gittensory-miner
+ app.kubernetes.io/name: loopover-miner
app.kubernetes.io/component: fleet-worker
spec:
- serviceName: gittensory-miner
+ serviceName: loopover-miner
# Scale the fleet by editing `replicas` (each replica is one isolated worker) or:
- # kubectl scale statefulset/gittensory-miner --replicas=
+ # kubectl scale statefulset/loopover-miner --replicas=
replicas: 2
selector:
matchLabels:
- app.kubernetes.io/name: gittensory-miner
+ app.kubernetes.io/name: loopover-miner
template:
metadata:
labels:
- app.kubernetes.io/name: gittensory-miner
+ app.kubernetes.io/name: loopover-miner
app.kubernetes.io/component: fleet-worker
spec:
securityContext:
@@ -41,8 +41,8 @@ spec:
containers:
- name: miner
# Replace with the image you built + pushed from packages/gittensory-miner/Dockerfile.
- image: gittensory-miner:latest
- # ENTRYPOINT is `gittensory-miner`; `run` is the continuous fleet-worker loop.
+ image: loopover-miner:latest
+ # ENTRYPOINT is `loopover-miner`; `run` is the continuous fleet-worker loop.
args: ["run"]
env:
- name: LOOPOVER_MINER_CONFIG_DIR
@@ -50,19 +50,19 @@ spec:
- name: GITHUB_TOKEN
valueFrom:
secretKeyRef:
- name: gittensory-miner-secrets
+ name: loopover-miner-secrets
key: GITHUB_TOKEN
# Coding-agent provider credentials — optional; present only for the providers you enable.
- name: ANTHROPIC_API_KEY
valueFrom:
secretKeyRef:
- name: gittensory-miner-secrets
+ name: loopover-miner-secrets
key: ANTHROPIC_API_KEY
optional: true
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
- name: gittensory-miner-secrets
+ name: loopover-miner-secrets
key: OPENAI_API_KEY
optional: true
volumeMounts:
diff --git a/k8s/miner-secret.example.yaml b/k8s/miner-secret.example.yaml
index 2971cc6cab..60fb3399e8 100644
--- a/k8s/miner-secret.example.yaml
+++ b/k8s/miner-secret.example.yaml
@@ -1,10 +1,10 @@
-# Secret template for gittensory-miner fleet workers (#5181).
+# Secret template for loopover-miner fleet workers (#5181).
#
# Values are intentionally EMPTY — this is a committed example, and a real-looking placeholder string in a
# secret-named field trips secret scanners. Fill them in out-of-band; do NOT commit the filled-in copy.
#
# Recommended (keeps secret values out of any file entirely):
-# kubectl create secret generic gittensory-miner-secrets \
+# kubectl create secret generic loopover-miner-secrets \
# --from-literal=GITHUB_TOKEN= \
# --from-literal=ANTHROPIC_API_KEY= --from-literal=OPENAI_API_KEY=
#
@@ -14,9 +14,9 @@
apiVersion: v1
kind: Secret
metadata:
- name: gittensory-miner-secrets
+ name: loopover-miner-secrets
labels:
- app.kubernetes.io/name: gittensory-miner
+ app.kubernetes.io/name: loopover-miner
type: Opaque
stringData:
# Required: a GitHub token the miner uses to read issues and open PRs.
diff --git a/package.json b/package.json
index f505af589b..e9f87976f7 100644
--- a/package.json
+++ b/package.json
@@ -23,7 +23,7 @@
"command-reference": "node scripts/gen-command-reference.mjs",
"command-reference:check": "node scripts/gen-command-reference.mjs --check",
"selfhost:validate-observability": "node scripts/validate-observability-configs.mjs",
- "selfhost:config-lint": "tsx scripts/gittensory-config-lint.ts",
+ "selfhost:config-lint": "tsx scripts/loopover-config-lint.ts",
"cf-typegen": "node scripts/gen-cf-typegen.mjs",
"cf-typegen:check": "node scripts/gen-cf-typegen.mjs --check",
"db:migrate:local": "wrangler d1 migrations apply loopover --local",
diff --git a/scripts/gittensory-config-lint.ts b/scripts/loopover-config-lint.ts
similarity index 97%
rename from scripts/gittensory-config-lint.ts
rename to scripts/loopover-config-lint.ts
index 5d5e9b1a9f..778211b5ca 100644
--- a/scripts/gittensory-config-lint.ts
+++ b/scripts/loopover-config-lint.ts
@@ -56,7 +56,7 @@ function main(): void {
text = readManifestTextForLint(path);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
- console.error(`gittensory-config-lint: ${message}\n\n${usage()}`);
+ console.error(`loopover-config-lint: ${message}\n\n${usage()}`);
process.exit(1);
}
const result = lintManifestText(text);
diff --git a/scripts/selfhost-post-update-check.sh b/scripts/selfhost-post-update-check.sh
index 99501fba6e..d6be360b4c 100755
--- a/scripts/selfhost-post-update-check.sh
+++ b/scripts/selfhost-post-update-check.sh
@@ -5,7 +5,7 @@
# `docker compose up -d --no-deps loopover` that ships a new app image.
#
# Checks /ready, compose health, .env release metadata, and the running container image.
-# Does not modify .env, volumes, gittensory-config/, or any profile service.
+# Does not modify .env, volumes, loopover-config/, or any profile service.
set -euo pipefail
ENV_FILE="${SELFHOST_ENV_FILE:-.env}"
diff --git a/scripts/selfhost-update.sh b/scripts/selfhost-update.sh
index 37ed0f81aa..dcb9e9a70d 100755
--- a/scripts/selfhost-update.sh
+++ b/scripts/selfhost-update.sh
@@ -11,7 +11,7 @@
#
# What this preserves untouched (all already gitignored -- see .gitignore):
# - .env and any *_FILE secret mounts
-# - gittensory-config/ (private per-repo .loopover.yml policy)
+# - loopover-config/ (private per-repo .loopover.yml policy)
# - .deploy-backups/ (operator deploy-backup snapshots)
# - any *.local or docker-compose.local-*.yml compose override, or alertmanager config files
# - named data volumes (loopover-data, loopover-pg, qdrant-data, loopover-backups,
diff --git a/systemd/gittensory-miner-ui.service.example b/systemd/loopover-miner-ui.service.example
similarity index 71%
rename from systemd/gittensory-miner-ui.service.example
rename to systemd/loopover-miner-ui.service.example
index 7fc700fa7c..81d6b11fff 100644
--- a/systemd/gittensory-miner-ui.service.example
+++ b/systemd/loopover-miner-ui.service.example
@@ -1,7 +1,7 @@
# Runs the miner-ui dashboard (apps/gittensory-miner-ui) as a persistent local service via `vite preview`
# (port 4174 by default -- see apps/gittensory-miner-ui/vite.config.ts), so a fleet/bare-host operator gets a
# durable dashboard instead of a dev-server-only experience. This is a companion service to
-# gittensory-miner.service.example, not a replacement -- the loop daemon still does the actual discover ->
+# loopover-miner.service.example, not a replacement -- the loop daemon still does the actual discover ->
# attempt -> manage work; this unit only serves the read-only dashboard over it.
#
# WHY vite preview, NOT a new CLI subcommand: apps/gittensory-miner-ui is a private workspace app (no
@@ -10,27 +10,27 @@
# `configurePreviewServer` (preview), so `npm run preview` already serves the built dashboard AND its data
# routes with no extra server code needed.
#
-# WHY A PERSISTENT SERVICE, NOT A .timer: like gittensory-miner.service.example, this is a long-running HTTP
+# WHY A PERSISTENT SERVICE, NOT A .timer: like loopover-miner.service.example, this is a long-running HTTP
# server, not a periodic batch job -- a Type=simple service supervised by systemd is the right shape, the
# same way loopover-docker-prune.*.example (a genuine periodic job) is NOT.
#
# Install (adjust the placeholders below to your host; run from a full monorepo checkout, not a global
# npm install -- this app has no published package):
-# git clone /var/lib/gittensory-miner/src
-# cd /var/lib/gittensory-miner/src && npm ci
+# git clone /var/lib/loopover-miner/src
+# cd /var/lib/loopover-miner/src && npm ci
# npm --workspace @loopover/ui-miner run build
-# sudo cp systemd/gittensory-miner-ui.service.example /etc/systemd/system/gittensory-miner-ui.service
-# sudo $EDITOR /etc/systemd/system/gittensory-miner-ui.service # fix User/WorkingDirectory/env
+# sudo cp systemd/loopover-miner-ui.service.example /etc/systemd/system/loopover-miner-ui.service
+# sudo $EDITOR /etc/systemd/system/loopover-miner-ui.service # fix User/WorkingDirectory/env
# sudo systemctl daemon-reload
-# sudo systemctl enable --now gittensory-miner-ui.service
-# journalctl -u gittensory-miner-ui -f # follow the dashboard server's output
+# sudo systemctl enable --now loopover-miner-ui.service
+# journalctl -u loopover-miner-ui -f # follow the dashboard server's output
#
-# The dashboard reads the SAME local SQLite stores gittensory-miner.service.example's loop daemon writes to
+# The dashboard reads the SAME local SQLite stores loopover-miner.service.example's loop daemon writes to
# (see packages/gittensory-miner/README.md's "Local storage" table) -- point LOOPOVER_MINER_CONFIG_DIR at
# the same directory as the loop service's unit if you run both on one host.
[Unit]
-Description=Gittensory miner-ui dashboard (read-only, local)
+Description=LoopOver miner-ui dashboard (read-only, local)
# Loopback-only by default (see the miner extension's host-permission scoping); no network dependency to
# wait on, but keep the ordering consistent with the loop service it complements.
After=network-online.target
@@ -38,9 +38,9 @@ After=network-online.target
[Service]
Type=simple
# REQUIRED: run as a dedicated non-root user with read access to the miner's local state directory.
-User=gittensory
+User=loopover
# REQUIRED: absolute path to the monorepo checkout built in the install steps above.
-WorkingDirectory=/var/lib/gittensory-miner/src
+WorkingDirectory=/var/lib/loopover-miner/src
# REQUIRED: absolute path to the workspace-scoped npm binary (see `which npm`; typically /usr/bin or
# /usr/local/bin, or an nvm/asdf shim path). `vite preview` binds 127.0.0.1 by default -- do not add
# `--host` unless you have put real auth in front of this port; the dashboard has none of its own.
@@ -49,7 +49,7 @@ ExecStart=/usr/bin/npm --workspace @loopover/ui-miner run preview
Restart=on-failure
RestartSec=10
# Optional overrides (see packages/gittensory-miner/README.md's "Local storage" table for the full list):
-# Environment=LOOPOVER_MINER_CONFIG_DIR=/var/lib/gittensory-miner/config
+# Environment=LOOPOVER_MINER_CONFIG_DIR=/var/lib/loopover-miner/config
TimeoutStopSec=30
[Install]
diff --git a/systemd/gittensory-miner.service.example b/systemd/loopover-miner.service.example
similarity index 59%
rename from systemd/gittensory-miner.service.example
rename to systemd/loopover-miner.service.example
index 8e18d9f74d..658be91d0e 100644
--- a/systemd/gittensory-miner.service.example
+++ b/systemd/loopover-miner.service.example
@@ -1,25 +1,25 @@
-# Runs `gittensory-miner loop` -- the autonomous discover -> attempt -> manage supervising daemon (#5135) --
+# Runs `loopover-miner loop` -- the autonomous discover -> attempt -> manage supervising daemon (#5135) --
# continuously on a bare host, no Docker required. This is the non-Docker counterpart to DEPLOYMENT.md's
# fleet-mode docker-compose invocation.
#
-# WHY A PERSISTENT SERVICE, NOT A .timer: `gittensory-miner loop` is a long-running daemon that schedules its
+# WHY A PERSISTENT SERVICE, NOT A .timer: `loopover-miner loop` is a long-running daemon that schedules its
# own cycles internally (kill-switch check -> run-loop boundary gate -> attempt -> loop-closure -> reentry), so
# it is a Type=simple service supervised by systemd, NOT a oneshot unit fired by a .timer. (Contrast
# loopover-docker-prune.service.example, which IS a genuine periodic batch and therefore does pair with a
# .timer.) See DEPLOYMENT.md's "Bare-host (systemd, no Docker)" section.
#
# Install (adjust the placeholders below to your host):
-# npm install -g @loopover/miner # puts the `gittensory-miner` bin on PATH
-# gittensory-miner init # one-time local state-dir setup
-# gittensory-miner doctor # confirm Node/state-dir/SQLite readiness
-# sudo cp systemd/gittensory-miner.service.example /etc/systemd/system/gittensory-miner.service
-# sudo $EDITOR /etc/systemd/system/gittensory-miner.service # fix User/WorkingDirectory/ExecStart/env
+# npm install -g @loopover/miner # puts the `loopover-miner` bin on PATH
+# loopover-miner init # one-time local state-dir setup
+# loopover-miner doctor # confirm Node/state-dir/SQLite readiness
+# sudo cp systemd/loopover-miner.service.example /etc/systemd/system/loopover-miner.service
+# sudo $EDITOR /etc/systemd/system/loopover-miner.service # fix User/WorkingDirectory/ExecStart/env
# sudo systemctl daemon-reload
-# sudo systemctl enable --now gittensory-miner.service
-# journalctl -u gittensory-miner -f # follow the loop's output
+# sudo systemctl enable --now loopover-miner.service
+# journalctl -u loopover-miner -f # follow the loop's output
[Unit]
-Description=Gittensory miner autonomous loop (discover -> attempt -> manage)
+Description=LoopOver miner autonomous loop (discover -> attempt -> manage)
# The loop makes GitHub/API calls, so wait for the network to be routable before starting.
After=network-online.target
Wants=network-online.target
@@ -27,20 +27,20 @@ Wants=network-online.target
[Service]
Type=simple
# REQUIRED: run as a dedicated non-root user that owns the state directory below.
-User=gittensory
+User=loopover
# REQUIRED: point this at a writable working directory for the miner (repo clones, worktrees, local state).
-WorkingDirectory=/var/lib/gittensory-miner
-# REQUIRED: absolute path to the globally-installed bin (see `which gittensory-miner`; typically
+WorkingDirectory=/var/lib/loopover-miner
+# REQUIRED: absolute path to the globally-installed bin (see `which loopover-miner`; typically
# /usr/bin or /usr/local/bin, or an nvm/asdf shim path).
-ExecStart=/usr/bin/gittensory-miner loop
+ExecStart=/usr/bin/loopover-miner loop
# A daemon should come back after a transient crash; the loop is idempotent across restarts.
Restart=on-failure
RestartSec=30
# REQUIRED: GITHUB_TOKEN (and any coding-agent credentials) must be present in the service's environment.
# Keep secrets out of this unit file -- use a root-owned 0600 EnvironmentFile instead:
-# EnvironmentFile=/etc/gittensory-miner.env
+# EnvironmentFile=/etc/loopover-miner.env
# Optional overrides (see the package README for the full list):
-# Environment=LOOPOVER_MINER_CONFIG_DIR=/var/lib/gittensory-miner/config
+# Environment=LOOPOVER_MINER_CONFIG_DIR=/var/lib/loopover-miner/config
# Environment=MINER_CODING_AGENT_PROVIDER=claude-code
# Kill switch: `systemctl stop` sends SIGTERM; the loop checks its kill switch each cycle and exits cleanly.
TimeoutStopSec=120
diff --git a/test/unit/docs-selfhost-activation-paths.test.ts b/test/unit/docs-selfhost-activation-paths.test.ts
index 7dc78a45a8..a4200bb5cf 100644
--- a/test/unit/docs-selfhost-activation-paths.test.ts
+++ b/test/unit/docs-selfhost-activation-paths.test.ts
@@ -21,7 +21,7 @@ describe("self-host activation + onboarding docs (#1574)", () => {
it("quickstart documents allowlist, private config seed, activation POST, and Checks: write", () => {
expect(quickstart).toContain("LOOPOVER_REVIEW_REPOS");
expect(quickstart).toContain("config/examples/global.loopover.yml");
- expect(quickstart).toContain("gittensory-config/.loopover.yml");
+ expect(quickstart).toContain("loopover-config/.loopover.yml");
expect(quickstart).toContain("/v1/repos/owner/my-repo/activation");
expect(quickstart).toContain("Checks: write");
expect(quickstart).toContain("INSTALL_AI_CLIS");
diff --git a/test/unit/docs-selfhost-git-deploy-hygiene.test.ts b/test/unit/docs-selfhost-git-deploy-hygiene.test.ts
index ceb26bd6a1..b973f3db2f 100644
--- a/test/unit/docs-selfhost-git-deploy-hygiene.test.ts
+++ b/test/unit/docs-selfhost-git-deploy-hygiene.test.ts
@@ -103,7 +103,7 @@ describe("self-host git-deploy hygiene (#1660)", () => {
});
it("operations docs still name every operator-owned path the script must never touch", () => {
- expect(operations).toContain("gittensory-config/");
+ expect(operations).toContain("loopover-config/");
expect(operations).toContain(".deploy-backups/");
expect(operations).toContain("*.local");
expect(operations).toContain("docker-compose.local-*.yml");
diff --git a/test/unit/docs-selfhost-update-rollback.test.ts b/test/unit/docs-selfhost-update-rollback.test.ts
index 78eba7e73d..97ad407e89 100644
--- a/test/unit/docs-selfhost-update-rollback.test.ts
+++ b/test/unit/docs-selfhost-update-rollback.test.ts
@@ -22,7 +22,7 @@ describe("self-host update + rollback docs (#1823)", () => {
expect(operations).toContain("Preflight checklist");
expect(operations).toContain("Post-update checklist");
expect(operations).toContain("Operator-owned");
- expect(operations).toContain("gittensory-config/");
+ expect(operations).toContain("loopover-config/");
expect(operations).toContain("loopover-data");
expect(operations).toContain("Migrations are forward-only");
});
diff --git a/test/unit/gittensory-config-lint-script.test.ts b/test/unit/loopover-config-lint-script.test.ts
similarity index 99%
rename from test/unit/gittensory-config-lint-script.test.ts
rename to test/unit/loopover-config-lint-script.test.ts
index 7f295e0630..fa44651747 100644
--- a/test/unit/gittensory-config-lint-script.test.ts
+++ b/test/unit/loopover-config-lint-script.test.ts
@@ -2,7 +2,7 @@ import { mkdtempSync, mkdirSync, rmSync, symlinkSync, writeFileSync } from "node
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
-import { formatLintReport, readManifestTextForLint } from "../../scripts/gittensory-config-lint";
+import { formatLintReport, readManifestTextForLint } from "../../scripts/loopover-config-lint";
import { lintManifestText } from "../../src/selfhost/config-lint";
import { MAX_FOCUS_MANIFEST_BYTES } from "../../src/signals/focus-manifest";
diff --git a/test/unit/mcp-cli-basics.test.ts b/test/unit/mcp-cli-basics.test.ts
index 458ec5e7ca..1f6f7c1370 100644
--- a/test/unit/mcp-cli-basics.test.ts
+++ b/test/unit/mcp-cli-basics.test.ts
@@ -241,7 +241,7 @@ describe("loopover-mcp CLI — basics", () => {
});
it("attributes config values to environment overrides without leaking secrets", () => {
- const secretDir = mkdtempSync(join(tmpdir(), "gittensory-config-secret-"));
+ const secretDir = mkdtempSync(join(tmpdir(), "loopover-config-secret-"));
try {
const out = run(["config"], {
GITTENSORY_API_URL: "https://example.test",
@@ -272,7 +272,7 @@ describe("loopover-mcp CLI — basics", () => {
});
it("attributes API URL and token to a named profile from the config file", () => {
- const configDir = mkdtempSync(join(tmpdir(), "gittensory-config-profile-"));
+ const configDir = mkdtempSync(join(tmpdir(), "loopover-config-profile-"));
try {
writeFileSync(
join(configDir, "config.json"),
@@ -304,7 +304,7 @@ describe("loopover-mcp CLI — basics", () => {
});
it("attributes API URL to a global config file reached through a config-path override", () => {
- const dir = mkdtempSync(join(tmpdir(), "gittensory-config-global-"));
+ const dir = mkdtempSync(join(tmpdir(), "loopover-config-global-"));
const file = join(dir, "custom-config.json");
try {
writeFileSync(file, JSON.stringify({ apiUrl: "https://global.example" }), { mode: 0o600 });
From aca8f720aafc07a7fdabc8676c432372f2e76550 Mon Sep 17 00:00:00 2001
From: JSONbored <49853598+JSONbored@users.noreply.github.com>
Date: Tue, 14 Jul 2026 02:36:04 -0700
Subject: [PATCH 2/4] fix(docker): fix systemd cross-references missed by the
Phase 7 sweep
test/unit/miner-ui-systemd-launcher.test.ts, apps/gittensory-miner-ui/README.md,
and packages/gittensory-miner/DEPLOYMENT.md still pointed at the pre-rename
systemd/gittensory-miner{,-ui}.service.example filenames after those files were
git mv'd to systemd/loopover-miner{,-ui}.service.example earlier in this branch.
---
apps/gittensory-miner-ui/README.md | 4 ++--
packages/gittensory-miner/DEPLOYMENT.md | 6 +++---
test/unit/miner-ui-systemd-launcher.test.ts | 6 +++---
3 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/apps/gittensory-miner-ui/README.md b/apps/gittensory-miner-ui/README.md
index f2b0039587..affe89877d 100644
--- a/apps/gittensory-miner-ui/README.md
+++ b/apps/gittensory-miner-ui/README.md
@@ -35,9 +35,9 @@ fleet/bare-host operator who wants the dashboard durably available, `npm run bui
`vite-*-api.ts` plugins register for both `configureServer` and `configurePreviewServer`, so nothing
extra is needed beyond the build step) on port `4174` by default.
-[`systemd/gittensory-miner-ui.service.example`](../../systemd/gittensory-miner-ui.service.example) at
+[`systemd/loopover-miner-ui.service.example`](../../systemd/loopover-miner-ui.service.example) at
the repo root is a ready-to-adapt persistent unit for this — a companion to
-`gittensory-miner.service.example` (the loop daemon), not a replacement for it. Its header comment
+`loopover-miner.service.example` (the loop daemon), not a replacement for it. Its header comment
carries the full install steps. Like the loop daemon, this is a `Type=simple` service, not a `.timer`
job — the dashboard is a long-running HTTP server, not a periodic batch task.
diff --git a/packages/gittensory-miner/DEPLOYMENT.md b/packages/gittensory-miner/DEPLOYMENT.md
index b043835f24..60d5abfcbd 100644
--- a/packages/gittensory-miner/DEPLOYMENT.md
+++ b/packages/gittensory-miner/DEPLOYMENT.md
@@ -137,12 +137,12 @@ docker compose -f docker-compose.miner.yml up -d --build
## Bare-host (systemd, no Docker)
-To run the miner continuously on a plain Linux host without Docker, supervise `loopover-miner loop` — the autonomous discover → attempt → manage daemon (#5135) — with systemd. [`systemd/gittensory-miner.service.example`](../../systemd/gittensory-miner.service.example) is a ready-to-adapt persistent unit; its header carries the full install steps:
+To run the miner continuously on a plain Linux host without Docker, supervise `loopover-miner loop` — the autonomous discover → attempt → manage daemon (#5135) — with systemd. [`systemd/loopover-miner.service.example`](../../systemd/loopover-miner.service.example) is a ready-to-adapt persistent unit; its header carries the full install steps:
```sh
npm install -g @loopover/miner
loopover-miner init --verify-token # optional: validate GITHUB_TOKEN before discovery/attempt runs
-sudo cp systemd/gittensory-miner.service.example /etc/systemd/system/gittensory-miner.service
+sudo cp systemd/loopover-miner.service.example /etc/systemd/system/loopover-miner.service
sudo $EDITOR /etc/systemd/system/gittensory-miner.service # set User / WorkingDirectory / ExecStart / secrets
sudo systemctl daemon-reload
sudo systemctl enable --now loopover-miner.service
@@ -150,7 +150,7 @@ sudo systemctl enable --now loopover-miner.service
Because `loop` is a **long-running daemon that schedules its own cycles**, it is a persistent `Type=simple` service (with `Restart=on-failure`) — **not** a oneshot unit driven by a `.timer`, unlike the periodic `loopover-docker-prune.*.example` hygiene job in [`systemd/`](../../systemd/). Keep `GITHUB_TOKEN` (and any coding-agent credentials) in a root-owned `0600` `EnvironmentFile`, never in the unit file. Follow the loop with `journalctl -u loopover-miner -f`; `systemctl stop` sends SIGTERM, which the loop handles cleanly at its next kill-switch check.
-Want the dashboard too? [`systemd/gittensory-miner-ui.service.example`](../../systemd/gittensory-miner-ui.service.example) is a companion unit that serves `apps/gittensory-miner-ui` persistently over the same local state — see that app's [README](../../apps/gittensory-miner-ui/README.md#running-as-a-persistent-service).
+Want the dashboard too? [`systemd/loopover-miner-ui.service.example`](../../systemd/loopover-miner-ui.service.example) is a companion unit that serves `apps/gittensory-miner-ui` persistently over the same local state — see that app's [README](../../apps/gittensory-miner-ui/README.md#running-as-a-persistent-service).
## Invariants
diff --git a/test/unit/miner-ui-systemd-launcher.test.ts b/test/unit/miner-ui-systemd-launcher.test.ts
index 041ca73de2..2e604270ae 100644
--- a/test/unit/miner-ui-systemd-launcher.test.ts
+++ b/test/unit/miner-ui-systemd-launcher.test.ts
@@ -3,7 +3,7 @@ import { join } from "node:path";
import { describe, expect, it } from "vitest";
const REPO_ROOT = join(process.cwd());
-const SERVICE_PATH = join(REPO_ROOT, "systemd/gittensory-miner-ui.service.example");
+const SERVICE_PATH = join(REPO_ROOT, "systemd/loopover-miner-ui.service.example");
const MINER_UI_README_PATH = join(REPO_ROOT, "apps/gittensory-miner-ui/README.md");
const DEPLOYMENT_PATH = join(REPO_ROOT, "packages/gittensory-miner/DEPLOYMENT.md");
const MINER_UI_PACKAGE_JSON_PATH = join(REPO_ROOT, "apps/gittensory-miner-ui/package.json");
@@ -36,14 +36,14 @@ describe("miner-ui persistent-service launcher (#4852)", () => {
it("documents the persistent-service launcher in the miner-ui README", () => {
const readme = readFileSync(MINER_UI_README_PATH, "utf8");
expect(readme).toContain("## Running as a persistent service");
- expect(readme).toContain("systemd/gittensory-miner-ui.service.example");
+ expect(readme).toContain("systemd/loopover-miner-ui.service.example");
expect(readme).toContain("npm run build");
expect(readme).toContain("npm run preview");
});
it("cross-references the miner-ui service from the main miner's Bare-host deployment doc", () => {
const deployment = readFileSync(DEPLOYMENT_PATH, "utf8");
- expect(deployment).toContain("gittensory-miner-ui.service.example");
+ expect(deployment).toContain("loopover-miner-ui.service.example");
expect(deployment).toContain("apps/gittensory-miner-ui/README.md#running-as-a-persistent-service");
});
});
From 136dfbf8817cb62281b26cda1d2e08bc57459786 Mon Sep 17 00:00:00 2001
From: JSONbored <49853598+JSONbored@users.noreply.github.com>
Date: Tue, 14 Jul 2026 02:42:44 -0700
Subject: [PATCH 3/4] style(ui): fix prettier formatting in
docs.self-hosting-operations.tsx
Phase 7's .gittensory.yml -> .loopover.yml text substitution changed line
lengths enough to trip prettier's wrap rules; auto-fixed via eslint --fix.
---
.../components/site/app-panels/miner-panel.tsx | 5 ++++-
.../src/lib/maintainer-settings-editable.ts | 7 ++++++-
.../src/lib/maintainer-settings-preview.ts | 6 +++++-
.../src/lib/registration-workspace.ts | 6 +++++-
apps/gittensory-ui/src/lib/snapshot-replay.ts | 8 +++++++-
.../src/routes/docs.self-hosting-operations.tsx | 16 ++++++++--------
6 files changed, 35 insertions(+), 13 deletions(-)
diff --git a/apps/gittensory-ui/src/components/site/app-panels/miner-panel.tsx b/apps/gittensory-ui/src/components/site/app-panels/miner-panel.tsx
index 5c549ebfd5..119ef35b00 100644
--- a/apps/gittensory-ui/src/components/site/app-panels/miner-panel.tsx
+++ b/apps/gittensory-ui/src/components/site/app-panels/miner-panel.tsx
@@ -38,7 +38,10 @@ const CHANGE_TONE: Record = {
};
type RecommendationSignalGroup =
- "repo_state" | "contributor_state" | "validation_state" | "policy_context";
+ | "repo_state"
+ | "contributor_state"
+ | "validation_state"
+ | "policy_context";
type RecommendationChange = {
status: "new" | "changed" | "unchanged";
diff --git a/apps/gittensory-ui/src/lib/maintainer-settings-editable.ts b/apps/gittensory-ui/src/lib/maintainer-settings-editable.ts
index 51779e5a4e..4699e4d4c8 100644
--- a/apps/gittensory-ui/src/lib/maintainer-settings-editable.ts
+++ b/apps/gittensory-ui/src/lib/maintainer-settings-editable.ts
@@ -14,7 +14,12 @@ export type CommandAuthorization = {
export type AutonomyLevel = "observe" | "auto_with_approval" | "auto";
export type AgentActionClass =
- "review" | "request_changes" | "approve" | "merge" | "close" | "label";
+ | "review"
+ | "request_changes"
+ | "approve"
+ | "merge"
+ | "close"
+ | "label";
export type AutoMergeMethod = "merge" | "squash" | "rebase";
export type MaintainerSettingsEditable = {
diff --git a/apps/gittensory-ui/src/lib/maintainer-settings-preview.ts b/apps/gittensory-ui/src/lib/maintainer-settings-preview.ts
index 3c160bca7c..1b13b72bef 100644
--- a/apps/gittensory-ui/src/lib/maintainer-settings-preview.ts
+++ b/apps/gittensory-ui/src/lib/maintainer-settings-preview.ts
@@ -11,7 +11,11 @@ export type AuthorAssociation =
| "NONE";
export type PreviewScenarioId =
- "confirmed-miner" | "non-miner" | "bot-author" | "maintainer-author" | "miner-api-unavailable";
+ | "confirmed-miner"
+ | "non-miner"
+ | "bot-author"
+ | "maintainer-author"
+ | "miner-api-unavailable";
export type PreviewScenario = {
id: PreviewScenarioId;
diff --git a/apps/gittensory-ui/src/lib/registration-workspace.ts b/apps/gittensory-ui/src/lib/registration-workspace.ts
index 6d86178fc2..044a303ba1 100644
--- a/apps/gittensory-ui/src/lib/registration-workspace.ts
+++ b/apps/gittensory-ui/src/lib/registration-workspace.ts
@@ -63,7 +63,11 @@ export type OwnerWorkflowState = "accepted" | "needs_cleanup" | "not_ready";
export type OwnerWorkflowRemediationKind = "action" | "manual";
export type OwnerWorkflowBucketId =
- "policy" | "data_quality" | "queue_health" | "docs_onboarding" | "maintainer_capacity";
+ | "policy"
+ | "data_quality"
+ | "queue_health"
+ | "docs_onboarding"
+ | "maintainer_capacity";
export type OwnerWorkflowItem = {
id: string;
diff --git a/apps/gittensory-ui/src/lib/snapshot-replay.ts b/apps/gittensory-ui/src/lib/snapshot-replay.ts
index 3ad1c16942..1a2108b3d6 100644
--- a/apps/gittensory-ui/src/lib/snapshot-replay.ts
+++ b/apps/gittensory-ui/src/lib/snapshot-replay.ts
@@ -14,7 +14,13 @@ export type SnapshotReplayViewer = "public" | "authenticated";
export type SnapshotReplayStatus = "populated" | "stale" | "missing";
export type SnapshotReplayConfidence = "high" | "medium" | "low" | "unknown";
export type SnapshotReplayFreshness =
- "fresh" | "stale" | "rebuilding" | "missing" | "degraded" | "possibly_stale" | "unknown";
+ | "fresh"
+ | "stale"
+ | "rebuilding"
+ | "missing"
+ | "degraded"
+ | "possibly_stale"
+ | "unknown";
export type SnapshotReplaySource = {
name: string;
diff --git a/apps/gittensory-ui/src/routes/docs.self-hosting-operations.tsx b/apps/gittensory-ui/src/routes/docs.self-hosting-operations.tsx
index 35906844f9..0f1eeb4c87 100644
--- a/apps/gittensory-ui/src/routes/docs.self-hosting-operations.tsx
+++ b/apps/gittensory-ui/src/routes/docs.self-hosting-operations.tsx
@@ -937,8 +937,8 @@ SENTRY_ORG_SLUG=LOOPOVER_VERSION (source path).
Named data volumes — especially loopover-data (SQLite DB, Codex/Claude auth
@@ -1048,10 +1048,10 @@ GITTENSORY_IMAGE=ghcr.io/jsonbored/loopover-selfhost@sha256:... ./scripts/deploy
None of this touches operator-owned state: .env, the{" "}
- loopover-config/ mount, .deploy-backups/, any{" "}
- *.local or docker-compose.local-*.yml compose override, or
- Alertmanager file, and every named data volume are already gitignored or outside the source
- tree entirely, so a fetch-and-rebuild never touches them. See the{" "}
+ loopover-config/ mount, .deploy-backups/, any *.local{" "}
+ or docker-compose.local-*.yml compose override, or Alertmanager file, and every
+ named data volume are already gitignored or outside the source tree entirely, so a
+ fetch-and-rebuild never touches them. See the{" "}
Quickstart for the initial clone; this
script assumes that checkout already exists and already tracks origin/main.
@@ -1191,8 +1191,8 @@ docker inspect --format '{{.Config.Image}}' "$(docker compose ps -q loopover)"`}
docker compose down (without -v) leaves every named volume (
loopover-data, loopover-pg, qdrant-data,{" "}
loopover-backups, grafana-data, and the rest declared in{" "}
- docker-compose.yml) on disk, along with the ./loopover-config{" "}
- host directory (a bind mount, not a named volume, so it is never affected by -v{" "}
+ docker-compose.yml) on disk, along with the ./loopover-config host
+ directory (a bind mount, not a named volume, so it is never affected by -v{" "}
either way). Pick one:
Date: Tue, 14 Jul 2026 02:58:01 -0700
Subject: [PATCH 4/4] style(ui): correct prettier formatting using the properly
pinned toolchain
The prior formatting commit ran against an incomplete local node_modules
(prettier wasn't actually installed), producing multi-line union-type
wraps that CI's pinned prettier 3.9.4 disagrees with. Re-running lint --fix
after a clean npm ci reverts those 5 files to the correct single-line form.
---
.../src/components/site/app-panels/miner-panel.tsx | 5 +----
.../gittensory-ui/src/lib/maintainer-settings-editable.ts | 7 +------
apps/gittensory-ui/src/lib/maintainer-settings-preview.ts | 6 +-----
apps/gittensory-ui/src/lib/registration-workspace.ts | 6 +-----
apps/gittensory-ui/src/lib/snapshot-replay.ts | 8 +-------
5 files changed, 5 insertions(+), 27 deletions(-)
diff --git a/apps/gittensory-ui/src/components/site/app-panels/miner-panel.tsx b/apps/gittensory-ui/src/components/site/app-panels/miner-panel.tsx
index 119ef35b00..5c549ebfd5 100644
--- a/apps/gittensory-ui/src/components/site/app-panels/miner-panel.tsx
+++ b/apps/gittensory-ui/src/components/site/app-panels/miner-panel.tsx
@@ -38,10 +38,7 @@ const CHANGE_TONE: Record = {
};
type RecommendationSignalGroup =
- | "repo_state"
- | "contributor_state"
- | "validation_state"
- | "policy_context";
+ "repo_state" | "contributor_state" | "validation_state" | "policy_context";
type RecommendationChange = {
status: "new" | "changed" | "unchanged";
diff --git a/apps/gittensory-ui/src/lib/maintainer-settings-editable.ts b/apps/gittensory-ui/src/lib/maintainer-settings-editable.ts
index 4699e4d4c8..51779e5a4e 100644
--- a/apps/gittensory-ui/src/lib/maintainer-settings-editable.ts
+++ b/apps/gittensory-ui/src/lib/maintainer-settings-editable.ts
@@ -14,12 +14,7 @@ export type CommandAuthorization = {
export type AutonomyLevel = "observe" | "auto_with_approval" | "auto";
export type AgentActionClass =
- | "review"
- | "request_changes"
- | "approve"
- | "merge"
- | "close"
- | "label";
+ "review" | "request_changes" | "approve" | "merge" | "close" | "label";
export type AutoMergeMethod = "merge" | "squash" | "rebase";
export type MaintainerSettingsEditable = {
diff --git a/apps/gittensory-ui/src/lib/maintainer-settings-preview.ts b/apps/gittensory-ui/src/lib/maintainer-settings-preview.ts
index 1b13b72bef..3c160bca7c 100644
--- a/apps/gittensory-ui/src/lib/maintainer-settings-preview.ts
+++ b/apps/gittensory-ui/src/lib/maintainer-settings-preview.ts
@@ -11,11 +11,7 @@ export type AuthorAssociation =
| "NONE";
export type PreviewScenarioId =
- | "confirmed-miner"
- | "non-miner"
- | "bot-author"
- | "maintainer-author"
- | "miner-api-unavailable";
+ "confirmed-miner" | "non-miner" | "bot-author" | "maintainer-author" | "miner-api-unavailable";
export type PreviewScenario = {
id: PreviewScenarioId;
diff --git a/apps/gittensory-ui/src/lib/registration-workspace.ts b/apps/gittensory-ui/src/lib/registration-workspace.ts
index 044a303ba1..6d86178fc2 100644
--- a/apps/gittensory-ui/src/lib/registration-workspace.ts
+++ b/apps/gittensory-ui/src/lib/registration-workspace.ts
@@ -63,11 +63,7 @@ export type OwnerWorkflowState = "accepted" | "needs_cleanup" | "not_ready";
export type OwnerWorkflowRemediationKind = "action" | "manual";
export type OwnerWorkflowBucketId =
- | "policy"
- | "data_quality"
- | "queue_health"
- | "docs_onboarding"
- | "maintainer_capacity";
+ "policy" | "data_quality" | "queue_health" | "docs_onboarding" | "maintainer_capacity";
export type OwnerWorkflowItem = {
id: string;
diff --git a/apps/gittensory-ui/src/lib/snapshot-replay.ts b/apps/gittensory-ui/src/lib/snapshot-replay.ts
index 1a2108b3d6..3ad1c16942 100644
--- a/apps/gittensory-ui/src/lib/snapshot-replay.ts
+++ b/apps/gittensory-ui/src/lib/snapshot-replay.ts
@@ -14,13 +14,7 @@ export type SnapshotReplayViewer = "public" | "authenticated";
export type SnapshotReplayStatus = "populated" | "stale" | "missing";
export type SnapshotReplayConfidence = "high" | "medium" | "low" | "unknown";
export type SnapshotReplayFreshness =
- | "fresh"
- | "stale"
- | "rebuilding"
- | "missing"
- | "degraded"
- | "possibly_stale"
- | "unknown";
+ "fresh" | "stale" | "rebuilding" | "missing" | "degraded" | "possibly_stale" | "unknown";
export type SnapshotReplaySource = {
name: string;