Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Tutorial

A walkthrough of repoweave, from joining a project to releasing. Uses a fictional chat product with repos under github/chatly/.

Joining an existing project

Someone already created the project. You want to reproduce their environment:

mkdir ~/work && cd ~/work
rwv fetch chatly/web-app

What happens:

  1. Clones projects/web-app/ from https://github.com/chatly/web-app.git
  2. Reads projects/web-app/rwv.yaml to get the repo list
  3. Clones each repo to its canonical path: github/chatly/server/, github/chatly/web/, github/chatly/protocol/
  4. Runs rwv activate web-app — generates ecosystem workspace files and symlinks them to the weave directory
  5. Writes .rwv-active with “web-app”

Result:

~/work/
├── github/chatly/server/             # clone
├── github/chatly/web/                # clone
├── github/chatly/protocol/           # clone
├── projects/web-app/                 # project repo (clone)
│   ├── rwv.yaml
│   ├── rwv.lock
│   ├── Cargo.toml                    # generated workspace file
│   └── docs/
├── Cargo.toml -> projects/web-app/Cargo.toml   # symlink
├── .rwv-active                       # "web-app"
└── .gitignore

You’re ready to work. Ecosystem tools see the workspace files at the weave directory — cargo test --workspace, npm test --workspaces, go test ./... all work across repos.

For exact reproduction (same revisions your colleague had):

rwv fetch chatly/web-app --locked

For CI (errors if lock is stale):

rwv fetch chatly/web-app --frozen

Day-to-day development

The typical cycle — no special tooling needed:

cd ~/work

# Pull latest across repos
gita super primary pull            # or: cd into each repo and git pull

# Work on a repo
cd github/chatly/server
git checkout -b feature/new-endpoint
# ... edit, test, commit ...

# Test across repos — workspace wiring resolves cross-repo imports
cd ~/work
cargo test --workspace             # or: npm test --workspaces, go test ./...

# Push your work
cd github/chatly/server
git push origin feature/new-endpoint

No workweave needed. No worktree indirection. Just repos, just git.

Managing repos

Adding a repo

rwv add https://github.com/example/some-lib.git --role dependency

This clones to github/example/some-lib/, adds it to the active project’s rwv.yaml, and re-runs integrations (e.g., adds the repo to workspace config files). Run the ecosystem install command afterward to pick up the new package.

rwv add always takes a URL — the manifest needs it for rwv fetch on other machines. If the repo is already on disk, the clone step is a no-op.

To create a brand new repo:

rwv add github/chatly/auth --new
# git init, URL inferred from path convention, added as role: primary

Adding a reference repo

rwv add https://github.com/interesting/library.git --role reference

Reference repos are visible in the workspace but excluded from build graphs. Use this instead of manual git clone so repos are tracked — rwv check reports untracked repos as orphans.

Removing a repo

rwv remove github/example/some-lib

Removes from rwv.yaml and re-runs integrations. The clone stays on disk (other projects might use it). To also delete the clone:

rwv remove github/example/some-lib --delete
# Checks no other project references it, then removes the directory

Creating a new project

You have repos on disk and want to create a project that groups them:

cd ~/work
rwv init web-app --provider github/chatly

This creates projects/web-app/ with an empty rwv.yaml, initializes a git repo, sets up the remote, and activates the project. Then add repos:

rwv add https://github.com/chatly/server.git --role primary
rwv add https://github.com/chatly/web.git --role primary
rwv add https://github.com/chatly/protocol.git --role primary
rwv add https://github.com/socketio/engine.io.git --role fork

The --provider flag is optional — it uses the registry mapping (githubgithub.com) to set up the remote. Without it, no remote is configured.

Working on the project repo

The project repo is a normal git repo containing rwv.yaml, rwv.lock, docs, and generated ecosystem files:

cd ~/work/projects/web-app
vim rwv.yaml                          # edit the manifest
cd ~/work
rwv activate web-app                  # regenerate from updated manifest

Cross-repo docs live here too:

cd projects/web-app/docs
vim architecture.md
git add . && git commit -m "docs: update architecture"
git push

Multiple projects

Fetching a second project

rwv fetch chatly/mobile-app

Clones the project repo and any repos not already on disk. Does NOT activate — the first project stays active. Shared repos (server, protocol) are left alone.

Switching projects

rwv activate mobile-app

Swaps symlinks in the weave directory, regenerates ecosystem files. Tool state (node_modules/, .venv/, target/) needs reconciliation — run the ecosystem install command after switching. This is incremental and fast for small dep diffs.

For large dependency differences, or when you need both projects active simultaneously, use a workweave instead of switching:

rwv workweave mobile-app create dev
cd .workweaves/dev
# independent tool state, no reconciliation needed

Workweaves

Workweaves are worktree-based derivatives of the weave, created on demand for isolation. Each workweave has its own branches, ecosystem files, and tool state. The weave is undisturbed.

rwv workweave web-app create payments

Creates .workweaves/payments/ with a git worktree for each repo on an ephemeral branch, plus ecosystem workspace files:

.workweaves/payments/
├── github/chatly/server/             # worktree, on payments/main
├── github/chatly/web/                # worktree, on payments/feature-A
├── github/chatly/protocol/           # worktree, on payments/main
├── projects/web-app/                 # worktree
├── Cargo.toml -> projects/web-app/Cargo.toml
└── .rwv-active                       # "web-app"

Work in the workweave like you would in the weave — cargo test --workspace, git commit, git push all work. Changes don’t affect the weave.

Use cases

Feature branch spanning multiple repos:

rwv workweave web-app create payments
cd .workweaves/payments/github/chatly/server
# ... make changes across server and protocol, test, commit ...

PR review without disrupting your work:

rwv workweave web-app create review-pr-42
cd .workweaves/review-pr-42/github/chatly/server
git fetch origin pull/42/head:pr-42 && git checkout pr-42
cargo test --workspace
rwv workweave web-app delete review-pr-42    # clean up when done

Agent isolation — each agent gets its own workweave:

rwv workweave web-app create agent-task-99
# agent works in .workweaves/agent-task-99/

# when done, review and merge:
cd ~/work/github/chatly/server
git merge agent-task-99/main
rwv workweave web-app delete agent-task-99

Parallel projects — work on two projects without switching:

# web-app is active in the weave
rwv workweave mobile-app create dev
cd .workweaves/dev
# mobile-app has its own tool state here

Cleanup

rwv workweave web-app delete payments

Removes worktrees, cleans up ephemeral branches, deletes the directory. Commits on ephemeral branches survive in the weave’s repos — merge or discard them with normal git.

Checking project health

rwv check

Reports:

web-app:
  ✓ 4 repos on disk, 4 in manifest
  ✓ rwv.lock matches HEAD revisions
  ⚠ github/chatly/web: 3 commits ahead of locked revision

mobile-app:
  ✓ 4 repos on disk, 4 in manifest
  ✗ rwv.lock stale: github/chatly/server HEAD differs

orphans:
  ⚠ github/example/old-experiment/ not in any project

workweaves:
  .workweaves/payments: web-app (3 repos)
  .workweaves/agent-task-99: web-app (3 repos, stale — 7 days old)

Concepts

This page explains the ideas behind repoweave — why it exists, how it relates to existing tools, and the design trade-offs it makes.

What is a weave?

A weave is a directory containing two kinds of subdirectories: repositories (under {registry}/{owner}/{repo}/) and projects (under projects/{name}/). That’s it — the directory convention is the foundation. Everything else is layered on top: ecosystem workspace files, symlinks, .rwv-active — all ephemeral, regenerated on activation. The repos and projects are the persistent state.

Workspaces

Many major package ecosystems have converged on the concept of a workspace that groups multiple packages under one root for cross-package imports, shared dependency resolution, and coordinated development (go.work, Cargo [workspace], pnpm workspaces, uv workspaces, etc.). They deal with packages, not repositories, but packages and repositories are often 1:1.

Monorepos

Full monorepos eliminate the version dance and solve other problems that workspaces help, but require vendoring or forking everything. Revision logs are polluted, provenance is obscured, and collaboration on or distribution of logical subsets of the monorepo is much more painful than it is with repositories that are scoped appropriately.

The weave metaphor

The goal is to weave independent threads (your repositories) into a single, coherent fabric — a unified workspace. The threads keep their identity and history; they simply work better together.

A weave is a workspace in the same sense as a go.work workspace or a Cargo [workspace], but with superpowers. Often, the workspace configurations can be generated from the repoweave manifest alone. In addition to simple cross-package imports and shared dependency resolution that workspace management tools bring, you get monorepo ergonomics.

repoweave provides a lock mechanism analogous to package manager locks, with a similar feel to the atomic commit you get from working in a monorepo — no more edit → bump version → publish → update dependents → reinstall dance for repos in the same weave. The lock makes it easy to reproduce a weave on another machine or in CI. It also makes it easy to create ephemeral workweaves for isolated work or review, like a multi-repo git worktree. All your code lives in one directory tree, so every tool that touches the filesystem — editors, grep, agents, debuggers, build tools — works across all of it, just like a monorepo.

Core idea

The weave has three layers:

  1. The directory tree — repos and projects under one directory. Every tool benefits: search, navigation, agents, editors. This is the convention alone — no tooling required.
  2. Ecosystem wiring — integrations generate workspace files (package.json, go.work, Cargo.toml, pnpm-workspace.yaml) in the weave directory so ecosystem tools see it as a workspace. Cross-package imports resolve locally. Ecosystem tools don’t know repos exist — they see a workspace directory with packages. import { thing } from '@myorg/shared' just works.
  3. Reproducibility — a committed rwv.yaml file and its rwv.lock pin each repo to an exact revision, making the project state reproducible from a single project repo.

Committing a project gets a coherent cross-repo “revision”, similar to a monorepo commit. You commit in individual repos first, then regenerate and commit the lock file. By default, it’s not atomic as a monorepo is, but it could be scripted to be so. It’s two-phase commit — the lock update is detectable and reversible:

# 1. Commit in individual repos (already done)
# 2. Update and commit the lock file
rwv lock
cd projects/web-app
git add rwv.lock && git commit -m "lock: add payment endpoint"

To reproduce a project from scratch:

mkdir my-workspace && cd my-workspace
rwv fetch chatly/web-app

sha256sum rwv.lock gives a single fingerprint for the project state — the multi-repo equivalent of git rev-parse HEAD on a monorepo.

Locking and the version dance

In a traditional multi-repo setup, changing protocol before server can use it requires: bump protocol’s version, publish, update server’s dependency, install, test — and repeat for every iteration. With repoweave, the ecosystem workspace wiring means server already imports from the local protocol checkout during development. You edit across repos, test, iterate — no bumps, no publishing. The version bump dance is deferred to release time, where it happens once rather than on every change.

This is the same way a monorepo works. The lock file captures your exact state whether or not you’ve done a formal version bump.

Most ecosystem tools also enforce version constraints within the workspace: if you bump protocol to 2.0.0 and server depends on ^1.0, Cargo, Go, and npm catch the incompatibility immediately. You discover constraint issues during development, not after publishing. (Python/uv is an exception — workspace members override constraints silently.)

See Releasing for the release-time workflow when repos publish to external registries.

Prior art: repo coordination tools

There is no universal standard for multi-repo development. “Polyrepo” names the strategy (the counterpart to “monorepo”) but prescribes no conventions. Each ecosystem that needed multi-repo coordination invented its own:

Google repoWestvcstoolgit submodulesrepoweave
EcosystemAndroid/embeddedZephyr RTOSROSGeneralGeneral
ManifestXMLYAMLYAML (.repos).gitmodulesYAML
Lock/freezerepo manifest -rwest manifest --freezevcs export --exactSHA in parent treerwv lock
Fetch allrepo syncwest updatevcs importgit submodule updaterwv fetch
Groupinggroupsgroupsnonenoneroles + projects
Multiple viewsnomanifest importsnonomultiple projects
Ecosystem wiringnonononogenerates workspace configs
Isolationnonononoworkweaves

West is the closest design ancestor — YAML manifest, explicit freeze, groups for filtering, even multi-file manifest imports. repo is similar but XML-based and assumes Gerrit. vcstool’s .repos format is the most portable and is the basis for repoweave’s manifest format.

None of these tools provide workweave-style isolation or generate ecosystem workspace configs. They solve “which repos, at what versions” but stop there — there’s no mechanism for isolated parallel work across repos, and you still have to wire up cross-package imports yourself.

Git submodules

Submodules are the tool people ask about most, even though they’re the least similar. They work fundamentally differently: the pinned revision is part of the parent’s git tree (a “gitlink” entry), not a separate lock file. This gives them atomic locking for free — when you commit the parent, the lock updates atomically. repoweave’s explicit lock file is the price of not using submodules.

But submodules take ownership in ways that conflict with multi-repo development: detached HEAD on checkout, can’t adopt existing clones, parent must be in the loop for every update, clones are nested under the parent (no sharing across projects), and recursive submodules compound all of these.

The design trade-off: submodules get atomic locking for free by taking ownership. repoweave gives up atomic locking to preserve sovereignty — repos stay on normal branches, you work in them normally, and the lock file is an explicit (two-step) operation.

Prior art: ecosystem workspace tools

Independently, ecosystem workspace tools have converged on a shared pattern — a manifest listing directories for cross-package resolution:

ToolManifestPurpose
npm/pnpmpackage.json workspaces / pnpm-workspace.yamlCross-package imports, shared deps
Gogo.workCross-module resolution
CargoCargo.toml [workspace]Shared lock, shared target
uvpyproject.toml [tool.uv.workspace]Cross-package deps

These tools don’t care about repo boundaries — they list directories. They handle dependency resolution but not repo lifecycle (cloning, pinning, reproducing). And they’re per-ecosystem: npm workspaces don’t help your Go modules, and go.work doesn’t help your Cargo crates.

repoweave bridges these two worlds. It reads the project manifest (which describes repos) and generates the ecosystem workspace configs (which describe packages). The result: cross-package imports resolve locally across repos, in every ecosystem, without manual setup. Combined with workweaves for isolation, this is what separates repoweave from the repo coordination tools — they give you repos on disk, but the development experience stops there.

Design decisions

  1. Shared tool state across project switches — acceptable cost. Ecosystem install after rwv activate is incremental. Workweaves are the escalation when switching is too slow.

  2. rwv add takes a URL — the manifest needs URLs for rwv fetch on other machines. If the repo is already on disk, the clone step is a no-op.

  3. rwv fetch updates the lock — fetches at branch HEAD and updates rwv.lock with actual revisions. --locked checks out exact revisions. --frozen errors if lock is stale (CI).

Releasing

Workspace wiring gives you a monorepo-style development experience: cross-repo imports resolve locally, no version bumps needed during development. rwv lock captures the exact cross-repo state — the lock file is the project’s version.

For repos that are only consumed within the project, this is the whole story. The lock file pins exact revisions, semver is optional, and you never need to publish to a registry. sha256sum rwv.lock is the project fingerprint — two machines with the same checksum have identical source.

Some repos are also consumed outside the project — published as packages on npm, crates.io, PyPI, etc. These repos need tagged releases and semver. The workspace wiring still eliminates the development-time version dance, but at release time you tag, publish, and update downstream version pins. The decision is per-repo: a project can have repos on the light internal path alongside repos that publish with full semver.

Locking

When you’re ready to capture the current state:

rwv lock

Reads the HEAD revision from each repo and writes projects/web-app/rwv.lock. If a tag exists at HEAD, the lock records the tag name; otherwise the revision ID.

cd projects/web-app
git add rwv.lock && git commit -m "lock: payment feature"
git push

For the internal model, this is the whole release: the committed lock file pins every repo to an exact revision. Reproduce it anywhere with rwv fetch --locked.

Publishing: what workspace wiring gives you

During development, workspace wiring eliminates the development-time version dance: edit B, test A — no bump, no publish, no install. The iteration loop is just edit and test, across repos, like a monorepo.

At release time, the steps are: bump version, tag, publish, install. But most of these are simpler than they seem, because ecosystem tools and version constraints do the heavy lifting.

Constraint checking during development

When you bump a package version in the workspace, most ecosystem tools immediately tell you if consumers’ constraints are incompatible:

EcosystemCatches constraint mismatch in workspace?Details
CargoYescargo check fails: “failed to select a version for the requirement”
GoYesModule path convention (v2 = different path) forces explicit migration
npmYesnpm install fails: “No matching version found”
uv/PythonNoworkspace = true silently overrides constraints — version mismatch only surfaces outside the workspace

This means you discover constraint incompatibilities during development, before publishing — except in Python where you need to be more careful.

The common case: compatible bumps

If B bumps from 1.0.0 to 1.1.0 and A depends on B ^1.0, no constraint update is needed. The release sequence is just:

rwv lock                                          # capture tested state
cd github/chatly/protocol
git tag v1.1.0 && git push origin v1.1.0          # tag and publish
cd ../server
git tag v2.1.0 && git push origin v2.1.0          # A picks up new B on next install

No reference edits. The ecosystem lock file (Cargo.lock, package-lock.json) resolves the range to the new version at install time.

Breaking changes: constraint updates

If B bumps to 2.0.0 (major/breaking) and A depends on B ^1.0, the ecosystem tool catches this in the workspace. Update A’s constraint, then release in dependency order:

# During development: bump B, ecosystem tool flags A's constraint
# Fix A's constraint to ^2.0
rwv lock
cd github/chatly/protocol
git tag v2.0.0 && git push origin v2.0.0
cd ../server
# Cargo.toml already updated to ^2.0 during development
git tag v3.0.0 && git push origin v3.0.0

Per-ecosystem release sequences

Each ecosystem has its own publish command. The pattern is the same — tag, publish, move on — but the exact invocations differ:

Cargo (crates.io):

cd github/chatly/protocol
cargo publish                       # publishes from Cargo.toml version
git tag v1.1.0 && git push origin v1.1.0

Go (module proxy):

cd github/chatly/protocol
git tag v1.1.0 && git push origin v1.1.0
# consumers: go get github.com/chatly/protocol@v1.1.0

npm:

cd github/chatly/web
npm version 1.1.0                   # bumps package.json, creates git tag
npm publish
git push origin v1.1.0

Python (PyPI):

cd github/chatly/ml-service
# bump version in pyproject.toml
git tag v1.1.0 && git push origin v1.1.0
uv build && uv publish

What the lock file tells you

The lock file format itself encodes release state: entries with tag names (e.g., v1.5.0) are already released; entries with revision IDs are unreleased. Read rwv.lock to see what needs attention:

repositories:
  github/chatly/protocol:
    version: v1.5.0              # released
  github/chatly/server:
    version: e1f2a3b4c5d6...     # unreleased — needs a tag
WhatWho knows
Which repos need a releaserwv.lock — tag = released, revision ID = unreleased
Which repos depend on whichEcosystem manifests (Cargo.toml, package.json, go.mod)
Whether a version bump is compatibleEcosystem tools — Cargo, Go, npm catch mismatches in the workspace
How to update version pinsEcosystem tools (cargo update, npm install, go get)
What was tested togetherrwv.lock — the cross-ecosystem snapshot

repoweave owns the first and last rows — which repos, and the cross-repo snapshot. The middle rows are the ecosystem’s job. In a multi-ecosystem project — a Go service using protobufs that a TypeScript frontend also uses — no single ecosystem tool sees all the repos that were tested together. The lock file is the only artifact that captures that cross-ecosystem state.

Reference

Authoritative reference for repoweave’s terminology, directory layout, file formats, and commands.

Terminology

TermMeaning
weaveA repoweave workspace — a directory containing repos, project directories, and ecosystem wiring generated from the active project
workweaveA worktree-based derivative of a weave, created on demand for isolation (agents, features, PR review)
projectA directory under projects/ containing rwv.yaml, rwv.lock, and project-scoped docs. Itself a git repo
manifest (rwv.yaml)Declares which repos belong to a project, their roles, and integration config
lock file (rwv.lock)Pins repos to exact revisions for reproducibility
activationGenerating ecosystem workspace files from a project’s manifest and symlinking them to the weave directory
roleA repo’s relationship to a project: primary (your code), fork, dependency, reference

Directory layout

repoweave uses a flat, provenance-based layout for repos: {registry}/{owner}/{repo}/. This keeps discovery simple, avoids path collisions when multiple projects share repos, and means every repo is a regular clone — cd github/chatly/server && git status just works. No bare repos, no worktree indirection for the default case.

Hierarchy and grouping happen at the project level (via rwv.yaml, which pulls in any subset of repos) and inside individual repos (organize your code however you like). Generated editor workspaces (VS Code .code-workspace) further let you focus on just the repos in a given project.

The first path segment is a registry — a short name for where the repo lives. rwv ships with built-in defaults for well-known hosts (github.com -> github, gitlab.com -> gitlab, bitbucket.org -> bitbucket); custom registries are configured in rwv’s own config. A registry can be domain-based (e.g., git.mycompany.com -> internal, handles https:// and git@ URLs) or directory-based (e.g., /srv/repos -> local, handles file:// URLs). This follows Go’s GOPATH precedent ($GOPATH/src/github.com/owner/repo), shortened for ergonomics.

Two kinds of directories:

KindPathPurpose
Normal{registry}/{owner}/{repo}/Code. Build tools look here. Other repos import from here. Listed in the weave’s package.json workspaces, go.work, etc.
Projectprojects/{name}/Coordination. rwv.yaml, lock files, docs. Build tools never see these. No importable code.

The path determines the directory’s role — you can tell what a directory is for from its location. Build tools (npm/pnpm, Go, Cargo, uv) are configured to look inside registry directories (github/, gitlab/, etc.), not projects/. Project repos have GitHub URLs (for fetchability) but their local path reflects their role, not their provenance.

Project paths default to projects/{name}/ for ergonomics. If names collide (two owners with a project called web-app), rwv fetch errors and suggests a scoped path: projects/{owner}/{name}/ or projects/{registry}/{owner}/{name}/. rwv commands that take a project path require the path as created — if the project lives at projects/chatly/web-app/, you must use chatly/web-app, not just web-app. Errors if no matching directory with an rwv.yaml file exists.

Example — a team building a chat product with a web app and mobile app:

web-app/                                  # weave
├── github/                               # regular clones
│   ├── chatly/
│   │   ├── server/                       # regular clone, on main
│   │   ├── web/                          # regular clone, on feature-A
│   │   ├── mobile/                       # regular clone, on main
│   │   └── protocol/                     # regular clone, on main
│   │
│   ├── socketio/
│   │   └── engine.io/                    # regular clone (fork)
│   │
│   └── nickel-io/
│       └── push-sdk/                     # regular clone (dependency)
│
├── projects/
│   ├── web-app/
│   │   ├── rwv.yaml                      # source of truth: which repos, what roles
│   │   ├── rwv.lock                      # pinned revisions (committed)
│   │   └── docs/
│   └── mobile-app/
│       ├── rwv.yaml
│       ├── rwv.lock
│       └── docs/
│
├── package.json -> projects/web-app/package.json       # symlink to active project
├── package-lock.json -> projects/web-app/package-lock.json
├── go.work -> projects/web-app/go.work
├── go.sum -> projects/web-app/go.sum
├── node_modules/                         # tool state — gitignored
├── .venv/                                # tool state — gitignored
├── .rwv-active                           # "web-app" — tracks active project
└── .gitignore
  • Repos are regular clonescd github/chatly/server && git status works. No bare repos, no .git file indirection, universal tool compatibility.
  • Ecosystem files are symlinkedpackage.json, go.work, Cargo.toml at the weave directory are symlinks to the active project’s directory. The real files live in projects/web-app/ and are committable in the project repo.
  • Ecosystem lock files are committablepackage-lock.json, pnpm-lock.yaml, uv.lock, go.sum, Cargo.lock live in the project directory alongside their workspace configs, symlinked to the weave directory.
  • Projects are directories with an rwv.yaml file, an rwv.lock file, and docs/. They don’t contain code — build tools are unaware of them.
  • Overlap is naturalserver and protocol appear in both projects’ rwv.yaml files, but there’s one clone on disk.
  • Repos without a project stay on disk — clone something for a quick look; it’s an inert directory until you add it to a project.

Repos files

YAML format with a repositories root key. Each entry is keyed by local path and has type, url, version, and role fields. Based on vcstool’s .repos format, extended with role and an optional integrations key for integration configuration (see Integrations). Each project directory contains an rwv.yaml (the declaration) and optionally an rwv.lock (pinned revisions).

Project rwv.yaml files

The source of truth for which repos belong to a project. Committed in the project repo, with version history:

# projects/web-app/rwv.yaml
repositories:
  github/chatly/server:
    type: git
    url: https://github.com/chatly/server.git
    version: main
    role: primary
  github/chatly/web:
    type: git
    url: https://github.com/chatly/web.git
    version: main
    role: primary
  github/chatly/protocol:
    type: git
    url: https://github.com/chatly/protocol.git
    version: main
    role: primary                # shared message types
  github/socketio/engine.io:
    type: git
    url: https://github.com/chatly/engine.io.git
    version: main
    role: fork                   # added reconnection logic

Lock files

Generated by rwv lock, same format but with resolved revisions instead of branch names. When a tag exists at HEAD, the tag name is recorded; otherwise, the revision ID. Optionally records which workweave it was generated from:

# projects/web-app/rwv.lock — generated, committed
workweave: agent-42    # or omitted for the weave
repositories:
  github/chatly/server:
    type: git
    url: https://github.com/chatly/server.git
    version: v2.1.0              # tagged — human readable
  github/chatly/web:
    type: git
    url: https://github.com/chatly/web.git
    version: e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0  # untagged — revision ID
  github/chatly/protocol:
    type: git
    url: https://github.com/chatly/protocol.git
    version: 7a3b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b  # untagged
  # ...

The workweave field is metadata — it records provenance without mixing responsibilities. It is omitted when the lock was generated from the weave.

Lock files live alongside rwv.yaml in the project directory, committed in the project repo. Each project owns its own lock state.

sha256sum rwv.lock is the project fingerprint. Two developers with the same lock file checksum have identical source for every repo in the project.

Locking and versioning

rwv.lock records whatever revision each repo is at — a snapshot of current state. When a tag exists at HEAD, the lock records the tag name (readable, auditable); otherwise the revision ID. The lock file format itself encodes release state: tag = released, revision ID = unreleased.

Ecosystem lock files (package-lock.json, Cargo.lock, etc.) complement rwv.lock. Together they capture both layers: rwv.lock pins which commit of each repo, and ecosystem lock files pin which versions of external dependencies were resolved. Full reproducibility requires both.

See Concepts for how this changes the development workflow, and Releasing for the release-time workflow.

Versioning guidelines

repoweave doesn’t require any particular versioning scheme, but one practice makes the multi-repo workflow smoother: include the git revision in your package version.

Semver supports build metadata via the + suffix: 2.1.0+7a3b2c1. The revision after + doesn’t affect version precedence but carries provenance. Whatever mechanism a package uses to report its version — package.json version field, pyproject.toml, Cargo.toml, a --version flag — it should include the revision when possible.

This makes the version output useful for debugging across repos:

$ my-server --version
my-server 2.1.0+7a3b2c1

$ my-protocol --version
my-protocol 1.4.0+e1f2a3b

You can immediately tell which commits are running. In a monorepo, git rev-parse HEAD tells you everything. In a multi-repo setup, the +revision suffix gives you the same traceability per package.

Most ecosystems support this:

  • npm: version in package.json supports semver build metadata
  • Cargo: version in Cargo.toml supports + metadata
  • Python: PEP 440 uses +local for local version identifiers (e.g., 2.1.0+7a3b2c1)
  • Go: runtime/debug.BuildInfo can embed VCS revision via build flags

For repos that don’t publish packages (internal services, scripts), the version matters less — rwv.lock captures the exact revision regardless. The +revision convention is most valuable for packages that are consumed as dependencies, where “which version of protocol is server using?” is a common question.

Projects

A project is a directory under projects/ with an rwv.yaml file, a lock file, and a docs/ directory. (We use “project” rather than “workspace” to avoid overloading the term — every ecosystem already uses “workspace” for its own build wiring: npm workspaces, go.work, Cargo workspaces, pnpm workspaces. A project is also more than build wiring — it includes docs, roles, and lock files. We reserve “workspace” for ecosystem workspaces exclusively.)

projects/web-app/
├── rwv.yaml                      # Which repos, with what roles
├── rwv.lock                      # Pinned revisions (rwv lock)
└── docs/                         # Cross-repo architecture docs, roadmap, coordination

What projects do

Projects answer the question: “I’m working on the web app — which repos matter?”

Without projects, you have a flat list of 20 repos and need tribal knowledge to know which ones are relevant. With a project, every rwv command — rwv lock, rwv check — and every workweave is scoped to the repos that matter for that work.

Projects also provide a home for documentation that doesn’t belong to any single repo. An architecture decision that spans server and protocol shouldn’t live in either repo — it lives in projects/web-app/docs/.

Fetching a project

On a new machine, you don’t clone repos one by one:

mkdir ~/work && cd ~/work
rwv fetch chatly/web-app

rwv fetch clones the project repo to projects/web-app/, reads its rwv.yaml, creates regular clones for every listed repo at their canonical paths, and generates ecosystem files at the weave directory. One command, and you have the complete working environment.

Overlap between projects

Same repo, different projects — natural and expected:

projects/web-app/rwv.yaml:
  github/chatly/server           role: primary
  github/chatly/protocol         role: primary

projects/mobile-app/rwv.yaml:
  github/chatly/server           role: primary
  github/chatly/protocol         role: primary

There’s one clone of server on disk. The role annotations may differ between projects — server could be primary in one and dependency in another. Each project’s rwv.yaml determines which role applies.

Project variants via branches

Need a variant of a project — same repos but with an extra dependency, or a different role for one repo? Use a branch in the project repo rather than a separate project:

cd projects/web-app
git checkout -b experiment
# edit rwv.yaml (add a repo, change a role)
rwv workweave web-app create experiment   # new workweave reads the branch's rwv.yaml

This avoids inventing inheritance or “derived project” machinery. A branch is already a variant with full version history.

Ecosystem files and multiple projects

Each project has its own ecosystem files in its project directory (projects/web-app/package.json, projects/mobile-app/package.json). The symlinks at the weave directory point to the active project’s files. Switching projects with rwv activate swaps the symlinks; to reconcile tool state run npm install (or rwv lock, which triggers integration lock hooks) afterward.

If switching is too slow (large dependency diff), create a workweave for the second project — it gets its own node_modules/, .venv/, and ecosystem files with no reconciliation needed.

Roles

Roles signal change resistance — how freely you (or an agent) should modify the code:

RoleChange resistanceMeaning
primaryNoneYour code. Change it if it’s an improvement.
forkLowForked upstream. Ideally changes accepted upstream, but expediency is fine.
dependencyMediumCode you build against. Changes need upstream acceptance, or convert to a fork.
referenceHighCloned for reading/study during design. No local changes. Could be removed when done.

Roles are per-project, not per-repo. The same repo can have different roles in different projects. engine.io is a fork in web-app (patched for reconnection) but could be a dependency in another project (using it unmodified). The active project’s rwv.yaml determines the current role.

Roles are a first-class field in rwv.yaml:

  github/socketio/engine.io:
    type: git
    url: https://github.com/chatly/engine.io.git
    version: main
    role: fork                   # added reconnection logic

Directory owner as heuristicgithub/chatly/ is likely primary, github/{other}/ is likely reference or dependency. But this is a default, not a rule — projects override it.

Workweaves

A workweave is a worktree-based derivative of the weave, created on demand for isolation. Each repo gets a git worktree (not a clone) on an ephemeral branch, with its own ecosystem files and tool state. Workweaves live in .workweaves/{name}/ under the weave directory.

node_modules/, .venv/, branches, and generated files are all per-workweave. One workweave can be on feature-A while another is on main, while the weave stays undisturbed.

See the Tutorial for use-case walkthroughs (feature branches, PR review, agent isolation, parallel projects).

Workspace context

Commands like add, remove, lock, and check infer the project and workspace from your CWD:

  • In a workweave directory — uses that workweave directly.
  • In the weave directory — resolves to the weave.
  • In a project directory — resolves to the weave.
  • Override — use --project flag.

If you edit rwv.yaml in a workweave, sync it with rwv workweave web-app sync {name}. rwv add and rwv remove handle this automatically.

Commands

rwv is a standalone Rust CLI that manages repos following repoweave conventions using direct git commands. Installed out of band — not part of any project. Nothing about the underlying rwv.yaml files changes; rwv is a convenience layer on top.

CommandWhat it does
rwvShow current context (weave, project, workweave, repos).
rwv workweave {project} create [name]Create a workweave (isolated working copy with worktrees on ephemeral branches).
rwv workweave {project} delete {name}Delete a workweave (remove worktrees, clean up ephemeral branches).
rwv workweave {project} sync {name}Sync workweave worktrees and ecosystem files with manifest.
rwv workweave {project} listList workweaves for a project.
rwv init {project}Create a new project directory with empty rwv.yaml. Optional --provider {registry}/{owner} sets up the remote.
rwv activate {project}Set the active project — generate ecosystem files in the project directory, symlink to the weave directory.
rwv fetch {source}Clone a project repo and all its listed repos, activate, update rwv.lock. --locked for exact reproduction, --frozen for CI (errors if lock is stale).
rwv add {url}Clone a repo, register in rwv.yaml, re-run integration hooks. With --role, sets the role. With --new, initializes a new repo at the canonical path (infers URL).
rwv remove {path}Remove from rwv.yaml, re-run integration hooks. With --delete, also removes the clone (confirms unless --force).
rwv lockSnapshot repo versions into the project’s rwv.lock. Errors on uncommitted changes (--dirty to bypass). Runs integration lock hooks.
rwv checkConvention enforcement: orphaned clones, dangling references, missing roles, stale locks, workweave drift, integration checks.
rwv resolvePrint the weave directory (workweave or weave). Useful for scripting: cd $(rwv resolve).

rwv check and multi-project awareness

rwv check scans all projects/*/rwv.yaml files to build a complete inventory of known repos. This prevents false orphan warnings — a repo from another project is not an orphan.

CheckDescription
Orphaned clonesDirectories under registry paths not listed in ANY project rwv.yaml
Dangling referencesEntries in an rwv.yaml pointing to paths not on disk
Missing rolerwv.yaml entries without a role field
Stale lockProject’s rwv.lock doesn’t match current HEAD revisions
Workweave driftWorktrees missing from a workweave or extra worktrees not in manifest
Integration checksEach integration’s check hook reports tool availability, stale config, etc. (see Integrations)

rwv lock

Lock snapshots the active project’s repo versions. It reads HEAD from each repo (regular clones in the weave, worktrees in a workweave) and writes rwv.lock to the project directory. If a tag exists at HEAD, the tag name is recorded; otherwise the revision ID.

Uncommitted changes are an error. If any repo in the project has uncommitted changes, rwv lock refuses to proceed — the lock file would record a revision that doesn’t reflect the actual state on disk. Use --dirty to bypass:

$ rwv lock
error: github/chatly/server has 2 uncommitted changes
error: lock would record HEAD (abc1234), not working tree state
hint: commit your changes first, or use --dirty to lock anyway

$ rwv lock --dirty
⚠ github/chatly/server: 2 uncommitted changes (locking HEAD, not working tree)
wrote projects/web-app/rwv.lock (4 repos)

Integration lock hooks. After writing rwv.lock, each integration’s lock hook runs to ensure ecosystem lock files are up to date (npm install --package-lock-only, uv lock, cargo generate-lockfile, etc.). This way rwv lock means “pin everything” — both repo versions and ecosystem dependency versions.

Integrations

Integrations translate between repoweave’s multi-repo world and ecosystem workspace formats. They generate workspace config files (package.json, go.work, Cargo.toml, etc.) in the weave directory during activation, run install commands during rwv lock, and perform read-only checks during rwv check.

IntegrationDefault enabledAuto-detectsGenerates (activation)Lock hook (rwv lock)
npm-workspacesyesrepos with package.jsonpackage.jsonnpm install
pnpm-workspacesnorepos with package.jsonpnpm-workspace.yamlpnpm install
go-workyesrepos with go.modgo.work
uv-workspaceyesrepos with pyproject.tomlpyproject.tomluv sync
cargo-workspaceyesrepos with Cargo.tomlCargo.tomlcargo generate-lockfile
gitayesall reposgita/ config directory
vscode-workspaceyesall repos{project}.code-workspace
static-filesnon/a (configured explicitly)symlinks declared files to weave directory

See Integrations for generated file formats, configuration, and details on each integration.

Integrations

What ecosystem tools see

The weave directory (or workweave directory) is what ecosystem tools see. npm sees a directory with a package.json listing workspace packages. Go sees a directory with a go.work listing modules. Cargo sees a directory with a Cargo.toml listing workspace members. These tools have no idea that the packages come from different git repos. They see a workspace directory with packages in it — nothing more.

Integrations are the translation layer between repoweave’s multi-repo world (repos, projects, roles) and the ecosystem’s workspace world (package.json, go.work, Cargo.toml). They read the project’s rwv.yaml — which describes repos — and produce the ecosystem workspace files in the weave directory. The result: ecosystem tools work exactly as they would in a monorepo, because from their perspective, it is a workspace directory with packages.

How integrations work

Rather than hardcoding knowledge of each ecosystem and tool, rwv uses integrations — pluggable units that each know how to derive config for one tool from the repo list. Each integration participates in two hook points:

  • Activation hooks (run during workweave creation, sync, add, remove) — generate config files and symlinks, or do nothing. This is the write path.
  • Lock hooks (run during rwv lock) — run install commands (npm install, uv sync, cargo generate-lockfile, etc.) to ensure ecosystem lock files are up to date. This is where package installation happens.
  • Check hooks (rwv check) — read-only inspection. Verify the environment is healthy, report missing tools, stale config, etc.

Each integration provides:

  1. A name — unique identifier (e.g., npm-workspaces).
  2. A default enabled state — whether the integration runs without explicit opt-in.
  3. Activation logic — receives the resolved repo list (paths, URLs, roles) and its config; generates workspace config files, or does nothing. 3a. Lock logic — receives the same inputs; runs install commands to update ecosystem lock files (npm install, uv sync, cargo generate-lockfile, etc.).
  4. Deactivation logic — removes generated files. Called during workweave deletion.
  5. Check logic — receives the same inputs; returns issues and warnings without changing state.

When a project is activated or a workweave is created or synced, integrations are run: the deactivation hook cleans up first, then the activation hook generates fresh config. Each integration auto-detects relevant repos — if none are found, it does nothing.

rwv check runs check hooks across all enabled integrations as part of its broader convention audit.

Ecosystem integrations auto-detect repos with the relevant manifest file. If none are found, they do nothing — no config file is generated, no error is raised.

Generated files are persistent

Generated ecosystem files live in the project directory (symlinked to the weave directory, or the workweave directory for workweaves). They are committable, not ephemeral — they are regenerated on workweave creation, sync, or rwv add, but they persist between runs and can be committed to version control.

Ecosystem lock files (package-lock.json, pnpm-lock.yaml, uv.lock, go.sum, Cargo.lock) are produced by the ecosystem tools during the install step. These are important persistent state that pins exact dependency versions within each ecosystem. They should be committed alongside the ecosystem workspace configs.

Some integrations have lock hooks that run external tools (npm install, pnpm install, uv sync) during rwv lock. These commands create their own tool state (node_modules/, .venv/). Tool state directories are gitignored and managed by the ecosystem tool, not by rwv. You can also run install commands manually after rwv activate. If tool state gets corrupted or out of sync, rwv workweave {project} sync {name} regenerates config and re-runs lock hooks.

Hook configuration in rwv.yaml

Integration config lives in the project’s rwv.yaml under an integrations key. Only overrides need to be listed — integrations not mentioned use their own defaults:

# projects/web-app/rwv.yaml
repositories:
  # ...

integrations:
  npm-workspaces:
    enabled: false                 # this project uses pnpm instead
  pnpm-workspaces:
    enabled: true
  go-work:
    enabled: false                 # this project doesn't use Go

Integration context

Each integration receives an IntegrationContext with:

  • output_dir — where generated files should be written (project directory for activate, workweave project directory for workweaves).
  • workspace_root — where repos live on disk (used for manifest detection like finding package.json).
  • project — the active project name (may be multi-segment, e.g., chatly/web-app).
  • repos — repo entries from the project’s rwv.yaml: {local_path: {type, url, version, role, ...}}.
  • config — per-integration config from the integrations key in rwv.yaml.
  • all_repos_on_disk — all git repos found on disk under registry directories (relative paths). Computed once, shared across integrations.
  • all_project_paths — all project paths (e.g., ['web-app', 'mobile-app']). Computed once, shared across integrations.

The active_repos() method filters out reference repos, which should not be included in ecosystem workspace configs (they are read-only and not part of the build graph). The detect_repos_with_manifest() helper finds active repos containing a given file (e.g., package.json), using workspace_root for detection.

Built-in integrations

IntegrationDefault enabledAuto-detectsGeneratesLock hook (runs during rwv lock)
npm-workspacesyesrepos with package.jsonpackage.jsonnpm install
pnpm-workspacesnorepos with package.jsonpnpm-workspace.yamlpnpm install
go-workyesrepos with go.modgo.work
uv-workspaceyesrepos with pyproject.tomlpyproject.tomluv sync
cargo-workspaceyesrepos with Cargo.tomlCargo.tomlcargo generate-lockfile
gitayesall reposgita/ directory
vscode-workspaceyesall repos{project}.code-workspace
static-filesnon/a (configured explicitly)symlinks declared files to weave directory

npm-workspaces

Generates a package.json with a workspaces array listing every project repo (excluding reference repos) that contains a package.json. The lock hook (run during rwv lock) runs npm install if npm is on PATH to update package-lock.json and node_modules/. To install immediately after activation, run npm install manually.

Generated file

{
  "name": "repoweave",
  "private": true,
  "workspaces": [
    "github/chatly/protocol",
    "github/chatly/server",
    "github/chatly/web"
  ]
}

This file is generated in the project directory and symlinked to the weave directory (or workweave directory). It is committable. The corresponding package-lock.json and node_modules/ are produced by npm installpackage-lock.json is committable persistent state, node_modules/ is gitignored tool state.

Deactivation

Removes the generated package.json. Does not remove node_modules/ or package-lock.json.

Check

Warns if repos with package.json exist but npm is not on PATH.

pnpm-workspaces

Generates a pnpm-workspace.yaml file listing every project repo (excluding reference repos) that contains a package.json. The lock hook (run during rwv lock) runs pnpm install if pnpm is on PATH to update pnpm-lock.yaml and node_modules/. To install immediately after activation, run pnpm install manually.

Disabled by default. Enable explicitly in rwv.yaml for projects that use pnpm instead of npm:

integrations:
  npm-workspaces:
    enabled: false
  pnpm-workspaces:
    enabled: true

Generated file

packages:
  - github/chatly/protocol
  - github/chatly/server
  - github/chatly/web

This file is generated in the project directory and symlinked to the weave directory (or workweave directory). It is committable. The corresponding pnpm-lock.yaml and node_modules/ are produced by pnpm installpnpm-lock.yaml is committable persistent state, node_modules/ is gitignored tool state.

Deactivation

Removes pnpm-workspace.yaml. Does not remove node_modules/ or pnpm-lock.yaml.

Check

Warns if repos with package.json exist but pnpm is not on PATH.

go-work

Generates a go.work file listing every project repo (excluding reference repos) that contains a go.mod.

Generated file

go 1.21

use (
    ./github/chatly/protocol
    ./github/chatly/server
)

This file is generated in the project directory and symlinked to the weave directory (or workweave directory). It is committable. The corresponding go.sum is produced by the Go toolchain and is also committable persistent state.

Deactivation

Removes go.work. Does not remove go.sum.

Check

No checks currently. Could warn if go is not on PATH when Go repos are present.

uv-workspace

Generates a pyproject.toml with a [tool.uv.workspace] section listing every project repo (excluding reference repos) that contains a pyproject.toml. The lock hook (run during rwv lock) runs uv sync if uv is on PATH to update uv.lock and .venv/. To install immediately after activation, run uv sync manually.

Generated file

# Generated by rwv — do not edit
[tool.uv.workspace]
members = [
    "github/chatly/protocol",
    "github/chatly/server",
]

This file is generated in the project directory and symlinked to the weave directory (or workweave directory). It is committable. The corresponding uv.lock and .venv/ are produced by uv syncuv.lock is committable persistent state, .venv/ is gitignored tool state.

Deactivation

Removes the generated pyproject.toml. Does not remove .venv/ or uv.lock.

Check

Warns if repos with pyproject.toml exist but uv is not on PATH.

cargo-workspace

Generates a Cargo.toml with a [workspace] section listing every project repo (excluding reference repos) that contains a Cargo.toml. Uses resolver version 2.

Generated file

# Generated by rwv — do not edit

[workspace]
members = ["github/chatly/protocol", "github/chatly/server"]
resolver = "2"

This file is generated in the project directory and symlinked to the weave directory (or workweave directory). It is committable. The corresponding Cargo.lock and target/ are produced by Cargo — Cargo.lock is committable persistent state, target/ is gitignored tool state.

Deactivation

Removes the generated Cargo.toml only if it starts with the generated-file header (to avoid deleting a hand-written Cargo.toml).

Check

No checks currently. Could warn if cargo is not on PATH when Rust repos are present.

gita

gita provides the multi-repo dashboard (gita ll), cross-repo git delegation (gita super), cross-repo shell commands (gita shell), groups, and context scoping. Rather than reimplement these in rwv, we use gita directly via this integration.

The activation hook generates gita’s config files into a gita/ directory inside the weave (or workweave) directory, scoped to the project’s repos. Point gita at this directory via GITA_PROJECT_HOME:

# .envrc in weave or workweave dir
export GITA_PROJECT_HOME="$PWD/gita"

GITA_PROJECT_HOME replaces (not supplements) gita’s default config directory. Each workweave gets its own gita config — gita commands are always scoped to the current context’s repos.

For build/test/lint across packages, prefer the ecosystem’s own workspace commands (npm run --workspaces test, pnpm -r run test, cargo test --workspace) — they understand package dependency ordering. gita’s value is at the git layer: status, bulk fetch/pull, seeing which repos have uncommitted work.

Generated files

The hook writes two CSV files into gita/:

repos.csv — header row, then one line per repo:

path,name,flags
/home/dev/workspace/github/chatly/server,server,
/home/dev/workspace/github/chatly/web,web,
/home/dev/workspace/github/chatly/protocol,protocol,
  • path: absolute path to the repo (or worktree in a workweave)
  • name: display name used in gita commands (basename of the repo path)
  • flags: extra args inserted after git in delegated commands (currently unused)

groups.csv — header row, groups derived from role annotations:

group,repos
fork,engine-io
primary,server web protocol

This enables role-scoped gita commands:

gita ll primary          # dashboard for primary repos only
gita super primary pull  # pull only primary repos

Deactivation

Removes the entire gita/ directory.

Check

Warns if gita is not on PATH.

Why not gita freeze / gita clone?

gita has its own serialization format (gita freeze outputs CSV with URL, name, path, branch) and can bootstrap from it via gita clone. This overlaps with rwv lock / rwv fetch, but gita freeze records branch names rather than pinned SHAs, so it’s less precise for reproducibility. repoweave’s rwv.lock also carries role annotations and YAML structure. The two mechanisms would overlap awkwardly, so the gita integration only generates the config files that gita needs at runtime — it doesn’t use gita’s own freeze/clone flow.

vscode-workspace

Generates a {project}.code-workspace file in the weave (or workweave) directory. Uses a single-root workspace (the directory itself) with git settings configured for the multi-repo layout.

The file is named after the project (e.g., web-app.code-workspace), making the project visible in the VS Code title bar.

Generated file

{
  "folders": [
    { "path": ".", "name": "web-app (weave)" }
  ],
  "settings": {
    "git.autoRepositoryDetection": "subFolders",
    "git.repositoryScanMaxDepth": 3
  }
}
  • Single root folder at "." — the weave or workweave directory.
  • Folder name includes the context (e.g., "web-app (weave)", "web-app (agent-42)").
  • git.autoRepositoryDetection: subFolders prevents VS Code from walking up to a parent repo.
  • git.repositoryScanMaxDepth: 3 ensures VS Code discovers repos at the registry/owner/repo depth.
  • Merge on activate — only folders and managed settings keys are replaced. Other keys (extensions, launch configs, other settings) are preserved, so user customizations survive re-activation. This merge behavior is meaningful because the generated file persists in the directory and can be customized.

Deactivation

Removes any .code-workspace files from the directory.

Check

Validates that the .code-workspace file exists as a regular file (not a symlink) in the directory.

static-files

Symlinks declared files from the project directory to the weave directory on activation. This is the escape hatch for top-level config files that don’t belong to any ecosystem integration — build orchestrator configs (turbo.json, nx.json), linter configs (.eslintrc.json, .prettierrc), or anything else that tools expect at the weave directory.

Disabled by default. Enable explicitly in rwv.yaml with a list of files:

integrations:
  static-files:
    enabled: true
    files: [turbo.json, nx.json, .eslintrc.json, .prettierrc]

Each file listed in files must exist in the project directory (e.g., projects/web-app/turbo.json). On activation, the integration symlinks each file to the weave directory so that tools like Turborepo or Nx find them where they expect.

How it works

Unlike ecosystem integrations that auto-detect repos and generate config, static-files does no generation and no detection. The files are hand-written and committed in the project directory. The integration simply makes them visible at the weave directory via symlinks.

If a declared file is missing from the project directory, the integration prints a warning but activation still succeeds — the missing file is skipped.

Deactivation

Symlinks are removed by the activation framework (any symlink at the weave directory pointing into projects/ is cleaned up). The original files in the project directory are untouched.

Check

Warns if any declared file is missing from the project directory.

Examples

Turborepo with npm workspaces

A project using Turborepo for build caching alongside npm workspaces:

# projects/web-app/rwv.yaml
repositories:
  github/chatly/protocol:
    url: git@github.com:chatly/protocol.git
  github/chatly/server:
    url: git@github.com:chatly/server.git
  github/chatly/web:
    url: git@github.com:chatly/web.git

integrations:
  static-files:
    enabled: true
    files: [turbo.json]

The turbo.json file lives alongside rwv.yaml in the project directory:

{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"]
    },
    "test": {
      "dependsOn": ["build"]
    },
    "lint": {}
  }
}

After activation, the weave directory contains:

  • package.json — generated by the npm-workspaces integration
  • turbo.json — symlinked by the static-files integration

Turborepo discovers packages from package.json workspaces and reads its pipeline config from turbo.json — both at the weave directory, exactly where it expects them.

Linter and formatter configs (.eslintrc.json, .prettierrc)

ESLint and Prettier expect their config at the weave directory to apply across all packages. With npm or pnpm workspaces, linting commands run from the weave directory — the config must be there too.

# projects/web-app/rwv.yaml
integrations:
  static-files:
    enabled: true
    files: [.eslintrc.json, .prettierrc]
// projects/web-app/.eslintrc.json
{
  "root": true,
  "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
  "parser": "@typescript-eslint/parser"
}
// projects/web-app/.prettierrc
{
  "semi": false,
  "singleQuote": true,
  "tabWidth": 2
}

The "root": true in .eslintrc.json is important — it tells ESLint to stop walking up the directory tree, so it doesn’t accidentally pick up a config from a parent directory outside the weave.

After activation, eslint . and prettier --check . run from the weave directory and apply consistently across every package in the workspace.

Nx build orchestrator (nx.json)

Nx is a build orchestrator that adds dependency-aware task ordering, caching, and affected-build analysis on top of npm/pnpm workspaces. Like Turborepo, it reads a config file from the weave directory.

# projects/web-app/rwv.yaml
integrations:
  pnpm-workspaces:
    enabled: true
  npm-workspaces:
    enabled: false
  static-files:
    enabled: true
    files: [nx.json]
// projects/web-app/nx.json
{
  "$schema": "./node_modules/nx/schemas/nx-schema.json",
  "targetDefaults": {
    "build": {
      "dependsOn": ["^build"],
      "cache": true
    },
    "test": {
      "cache": true
    }
  },
  "defaultBase": "main"
}

After activation:

  • pnpm-workspace.yaml — generated by the pnpm-workspaces integration (Nx works best with pnpm)
  • nx.json — symlinked by the static-files integration

Nx discovers packages from pnpm-workspace.yaml and reads task configuration from nx.json. Run pnpm exec nx run-many -t build to build all affected packages in dependency order.

Toolchain versions (.mise.toml)

mise reads .mise.toml from the directory you cd into, activating the declared toolchain versions. Placing it at the weave directory ensures everyone working on the project uses the same Node, Go, Rust, or Python version — without per-repo .nvmrc or .tool-versions files scattered across repos.

# projects/web-app/rwv.yaml
integrations:
  static-files:
    enabled: true
    files: [.mise.toml]
# projects/web-app/.mise.toml
[tools]
node = "22"
go = "1.22"
rust = "1.78"
python = "3.12"

After activation, mise install at the weave directory installs the declared versions. Combine with direnv (use mise in .envrc) for automatic activation on cd.

Environment activation (.envrc)

direnv reads .envrc from the directory you enter, activating environment variables and shell configuration automatically. At the weave directory, .envrc sets up the full development environment in one step.

# projects/web-app/rwv.yaml
integrations:
  static-files:
    enabled: true
    files: [.envrc]
# projects/web-app/.envrc
use mise                                      # activate toolchain versions from .mise.toml
export GITA_PROJECT_HOME="$PWD/gita"         # point gita at the generated config
export DATABASE_URL="postgres://localhost/web_app_dev"
export NODE_ENV="development"

After activation, entering the weave directory automatically activates toolchains, sets GITA_PROJECT_HOME so gita commands are scoped to the project, and exports any other environment variables developers need. Run direnv allow once after creating or modifying .envrc.

Note: .envrc files often contain developer-local paths or credentials. Consider what belongs in the committed .envrc versus a .envrc.local that each developer maintains separately.

Makefile or justfile

A Makefile or justfile at the weave directory provides a consistent command interface across the multi-repo workspace — shortcuts for common sequences that span repos or require a specific order.

# projects/web-app/rwv.yaml
integrations:
  static-files:
    enabled: true
    files: [justfile]
# projects/web-app/justfile
default:
    @just --list

# Install all dependencies
install:
    rwv lock
    npm install

# Run tests across all packages
test:
    npm run test --workspaces

# Lint and format check
lint:
    eslint .
    prettier --check .

# Lock repos and ecosystem deps, then commit the lock file
lock:
    rwv lock
    cd projects/web-app && git add rwv.lock && git commit -m "chore: update lock"

After activation, just install, just test, and just lint work from the weave directory. A justfile (or Makefile) is particularly useful for documenting the commands that require multi-step sequences — like running rwv lock before npm install, or running lint after a format pass.

Build orchestration

Build orchestration tools (Nx, Turborepo) add three capabilities on top of the workspace files that activation hooks generate:

CapabilityWhat it doesWhen you need it
Dependency-aware task orderingBuilds protocol before web because web imports from protocolMultiple packages with build steps that depend on each other
CachingSkips re-running tasks when inputs haven’t changedSlow builds, CI optimization
Affected analysisDetermines which packages changed since a base ref, runs only thoseLarge workspaces where running everything is too slow

These tools consume the same workspace structure that activation hooks generate. Adding nx.json or turbo.json requires zero restructuring — they discover packages from package.json workspaces, go.work, etc. Use the static-files integration to place turbo.json or nx.json at the weave directory (see static-files above).

For most projects, ecosystem workspace commands are sufficient without a build orchestrator:

EcosystemCross-package commandDependency orderingFiltering
npmnpm run test --workspacesNonpm run test -w pkg-name
pnpmpnpm -r run testYes (topological)pnpm --filter @scope/*
Gogo test ./... (with go.work)NativeN/A
Cargocargo test --workspaceYescargo test -p my-crate
uvuv run --all-packages pytestYesuv run --package my-pkg pytest

gita vs. build orchestration

gita operates at the git/repo layer — it doesn’t know about packages or build graphs. Ecosystem tools and build orchestrators operate at the package layer — they don’t know about git status or multi-repo state. The two are complementary:

TaskBetter toolWhy
Run build/test across packagespnpm/npm/cargo/uv (or Nx/Turbo)Understands package dependency ordering
Git status across reposgitaEcosystem tools don’t know about git
Git operations across reposgitagita super fetch, gita super pull
Arbitrary shell across reposgitaWhen it’s not ecosystem-specific

Writing custom integrations

Integrations ship with rwv as Rust modules in src/integrations/. Each implements the Integration trait:

#![allow(unused)]
fn main() {
pub trait Integration {
    fn name(&self) -> &str;
    fn default_enabled(&self) -> bool;

    fn activate(&self, ctx: &IntegrationContext) -> Result<()>;
    fn deactivate(&self, root: &Path) -> Result<()>;
    fn check(&self, ctx: &IntegrationContext) -> Result<Vec<Issue>>;
    fn lock(&self, ctx: &IntegrationContext) -> Result<()> { Ok(()) }
    fn generated_files(&self, ctx: &IntegrationContext) -> Vec<String> { Vec::new() }
}
}

IntegrationContext provides:

  • output_dir — the directory where generated files should be written (project directory for activate, workweave project directory for workweaves).
  • workspace_root — the directory where repos live on disk (for manifest detection via detect_repos_with_manifest).
  • project — the active project name.
  • repos — repo entries from the project’s rwv.yaml.
  • config — per-integration config from the integrations key in rwv.yaml.
  • all_repos_on_disk — all git repos found on disk under registry directories. Computed once, shared across integrations.
  • all_project_paths — all project paths. Computed once, shared across integrations.

The active_repos() method filters out reference repos. The detect_repos_with_manifest() method finds active repos containing a given manifest file (e.g., package.json), using workspace_root for detection.

New integrations are registered in registry.rs.

Adjacent tools

repoweave solves “which repos, at what versions, in what structure.” Several adjacent tools solve other layers — toolchain versions, environment activation, containerized dev environments, CI checkout. They’re complementary, and the active project’s state makes many of them easier to configure.

What’s derivable from project state

A project’s rwv.yaml + the files generated by integration hooks already imply most of the dev environment:

LayerDerivable?How
Repos on diskYesrwv fetch — the whole point
Toolchains neededYespackage.json exists -> Node, go.work exists -> Go
Toolchain versionsPartiallyEcosystem files often pin versions (.nvmrc, go.mod’s go directive). .mise.toml at the weave directory fills the gap.
Workspace depsYesnpm install, go work sync — deterministic once repos + workspace files exist
Editor workspaceYes.code-workspace folders directly derivable from project rwv.yaml
Base image / OS packagesNoSystem-level, not inferrable from repo structure
ServicesNoDatabases, message queues — runtime deps, not repo deps
Secrets / env varsNoOut of scope

mise / asdf — toolchain versions

mise (formerly rtx) manages language runtime versions with a single .mise.toml:

# .mise.toml
[tools]
node = "22"
go = "1.22"
rust = "1.78"

Place .mise.toml in the project directory and use the static-files integration to symlink it to the weave directory on activation. See static-files — .mise.toml example.

direnv — environment activation

direnv auto-activates environments when you cd into a directory:

# .envrc
use mise                                      # activate toolchain versions
export GITA_PROJECT_HOME="$PWD/gita"          # point gita at derived config

Place .envrc in the project directory and use the static-files integration to symlink it to the weave directory on activation. See static-files — .envrc example.

Devcontainers / Codespaces

rwv fetch replaces a wall of git clone commands in postCreateCommand:

// .devcontainer/devcontainer.json
{
  "features": {
    "ghcr.io/devcontainers/features/node:1": {},
    "ghcr.io/devcontainers/features/go:1": {},
    "ghcr.io/devcontainers/features/rust:1": {}
  },
  "postCreateCommand": "cargo install repoweave && rwv fetch chatly/web-app",
  "forwardPorts": [5432]
}

CI multi-repo checkout

rwv.yaml can drive a reusable checkout action — same pattern as rwv fetch but in CI:

# .github/workflows/ci.yml
- uses: actions/checkout@v4          # this repo (projects/web-app)
- run: rwv fetch   # reads rwv.yaml, clones code repos
- run: npm install && npm test