fuz_repos
a tool for managing many repos 🪄 repos.fuz.dev
fuz_repos is an alternative to the monorepo pattern that more loosely couples repos:
- enables automations across repos without requiring them to be in the same monorepo
- allows each repo to be managed from multiple fuz_repos projects
- runs automations locally on your machine, giving you full control and visibility (big tradeoffs in both directions compared to GitHub actions)
With fuz_repos you can:
- dynamically compose repos
- fetch metadata about collections of repos and import it as typesafe JSON (
repos.ts, written bygro task src/lib/gitops_sync.task.ts) - publish a generated docs website for your collections of repos
- import its components to view and interact with repo collection metadata
- publish metadata about your collections of repos to the web for other users and tools
- publish multiple interdependent packages in dependency order with automatic dependency updates
Scope
fuz_repos runs deterministic, config-driven operations over a declared set
of repos — no LLM in the loop, and the repo set comes from a declared list
(a repos.toml registry, and a gitops.config.ts naming a subset of its keys). Publishing is its flagship capability, not its whole
identity. The Rust repos tool in this repo (crates/fuz_repos) reports
every declared repo's git state (repos status), syncs them (repos sync),
and is the gateway agents push through (repos push).
Deliberately out of scope: single-repo build work (that's gro), work whose resolution differs per repo and needs judgment, machine and server state, and secrets — fuz_repos never stores, transports, or reads secret material as data, including env-file contents. Its own GitHub API token (noted below) is the one credential it uses, to authenticate itself.
See CLAUDE.md for the capability tiers, what
repos never does, and the TS/Rust split.
Usage
npm i -D @fuzdev/fuz_repos
-
install the
reposbinary, which the tasks read repo state from — it isn't in the npm package: in a checkout of this repo,cargo install --path crates/fuz_repos --locked -
configure
gitops.config.tsas a list ofrepos.tomlregistry keys — each repo's dir, URL, branch, visibility, and CI come from the registry:import type { GitopsConfig } from '@fuzdev/fuz_repos/gitops_config.ts'; const config: GitopsConfig = { repos: ['fuz_util', 'gro', 'fuz_ui'] }; export default config;The tasks find the registry the way
reposdoes, walking up from the cwd, or take--registry <path>; a repo the registry has but the disk lacks is cloned byrepos sync <key>. -
fuz_repos calls the GitHub API using the environment variable
SECRET_GITHUB_API_TOKENfor authorization, which is a classic GitHub token (with "public access" for public repos, no options selected) or a fine-grained GitHub token (beta) (with"Public Repositories (read-only)"selected) in eitherprocess.env, a project-local.env, or the parent directory at../.env(gro task src/lib/gitops_sync.task.tsrequires it, and you'll need to select options to support private repos) -
re-export the gitops tasks by creating files in
src/lib/:// gitops_sync.task.ts export * from '@fuzdev/fuz_repos/gitops_sync.task.ts'; // gitops_analyze.task.ts export * from '@fuzdev/fuz_repos/gitops_analyze.task.ts'; // gitops_plan.task.ts export * from '@fuzdev/fuz_repos/gitops_plan.task.ts'; // gitops_release.task.ts export * from '@fuzdev/fuz_repos/gitops_release.task.ts'; // gitops_validate.task.ts export * from '@fuzdev/fuz_repos/gitops_validate.task.ts'; // gitops_run.task.ts export * from '@fuzdev/fuz_repos/gitops_run.task.ts'; -
run
gro task src/lib/gitops_sync.task.tsto generate the dashboard's data from the repos
Architecture
gitops.config.ts (registry keys) → repos status --json → local repos → GitHub API → repos.ts → UI components
- Operations pattern: Dependency injection for side effects (git, npm, fs,
repos) - Fixture testing: In-memory fixture repos with expected publishing outcomes
- Changeset-driven: Automatic version bumps and dependency updates
See CLAUDE.md for detailed documentation.
Quick Start
Running commands across repos
gro task src/lib/gitops_run.task.ts "npm test" # run tests in all repos (parallel, concurrency: 5)
gro task src/lib/gitops_run.task.ts "npm audit" --concurrency 3 # limit parallelism
gro task src/lib/gitops_run.task.ts "git status" --format json # JSON output for scripting
Features:
- Parallel execution with configurable concurrency (default: 5)
- Continue-on-error behavior (shows all results)
- Structured output formats (text or JSON)
- Uses lightweight repo path resolution through
repos status; a configured repo that's missing fails the run, naming it
Generating the dashboard's data
gro task src/lib/gitops_sync.task.ts # fetch, check each repo, then write repos.json + repos.ts
gro task src/lib/gitops_sync.task.ts --check # the readiness report alone: no fetch, no token, nothing written
gro task src/lib/gitops_sync.task.ts --allow_dirty # read repos off their branch, dirty, or mid-operation as they sit, warning instead
It reads each repo as it sits and never moves or installs one (repos sync
moves them), so it refuses a repo off its registry branch, dirty, or
mid-operation — the site would show that tree's modules beside origin's CI —
or missing the installed dependencies its library analysis reads, and warns on
one behind origin.
Diagnostic commands (read-only)
gro task src/lib/gitops_validate.task.ts # run all validation checks (analyze + plan + CI reconcile)
gro task src/lib/gitops_analyze.task.ts # analyze dependency graph, levels, and cycles
gro task src/lib/gitops_plan.task.ts # the release's waves, version changes, and cascades, offline
gro task src/lib/gitops_release.task.ts # preview a release step: the rewrites it would commit and the wave to publish
These read each repo's working tree exactly as it sits on disk — no branch
switching, pulling, or installing — so they're safe to run with feature
branches checked out and uncommitted changes. Each prints the repos that
aren't at rest (off their registry branch, dirty, mid-rebase, or not in sync
with origin as of the last fetch); run repos sync first to read them at
rest.
Releasing packages
A release publishes in waves — a wave's packages don't depend on each other —
and the publishing is yours: gro publish in each repo, inside one npm 2FA
window. The tool steps between waves:
gro task src/lib/gitops_release.task.ts --wetrun # rewrite released dependencies' dependents, name the wave to publish
# publish the wave it names
gro task src/lib/gitops_release.task.ts --wetrun --wait # wait out the registry, then the same for the next wave
Each step derives where the release stands from what it observes — changesets,
each package.json, and what the registry shows — so it keeps no state and
picks up wherever things are, a release made by hand included. It fetches the
repos first and holds a rewrite or publish in one that isn't on its registry
branch, clean, idle, and in sync with origin or ahead of it, naming the fix;
it moves no repo to get there (see docs/publishing.md).
The repos tool
A Rust CLI over the repos a repos.toml registry declares. It moves refs it
didn't author — fetch, fast-forward, clone, push — and rebases a diverged
registry branch onto its fetched upstream where that's safe
(docs/repos.md says when), stopping on any
conflict. It never commits new content, resolves a conflict, merges
anything but a fast-forward, force-pushes, or pushes tags. It needs git
2.44 or newer, and Linux for its detection of live Claude Code sessions.
cargo install --path crates/fuz_repos --locked # install the `repos` binary
repos status # git state of every entry, from local refs
repos status --fetch # fetch from origin first (remote-tracking refs only)
repos sync # fetch, then fast-forward, rebase, push, and clone what's safe
repos push # push the branch checked out here, as a fast-forward (rebased first if it diverged)
Documentation:
- CLAUDE.md - Architecture, commands, testing patterns
- fuz-stack skill - Coding conventions shared across the Fuz stack
- docs/publishing.md - The release step, changeset semantics, examples
- docs/troubleshooting.md - Common errors and debugging tips
- docs/repos.md - The
reposcommand reference
Getting started as a dev? Start with Gro and the Fuz template.
TODO
- figure out better automation than manually running
gro task src/lib/gitops_sync.task.ts - show the rate limit info
- think about how fuz_repos could use both GitHub Actions and Forgejo Actions