chore: cleanup and gitea mcp integration

This commit is contained in:
Björn Fromme committed 2026-10-19 08:49:39 +02:00
1 parent 63f3199076
commit 5c07dd4885
8 files changed
+411 -18

No files matched your search

+112 -15
View File
@@ -7,11 +7,13 @@
- exposes `ddev codex`
- exposes `ddev claude`
- optionally exposes `ddev claude-swap` for juggling multiple Claude accounts (opt-in; see [Multiple Claude accounts](#multiple-claude-accounts))
- optionally keeps read-only mirrors of sibling repos under `.ddev/claude-code/home/refs/` for Claude to grep (opt-in; see [Sibling repo mirrors](#sibling-repo-mirrors))
- installs Codex CLI lazily the first time you run `ddev codex` (or whenever the persisted binary is missing) by downloading the latest release from GitHub: `curl -fsSL https://github.com/openai/codex/releases/latest/download/install.sh | sh`
- installs Claude Code CLI during `ddev start` when the persisted binary is missing or broken by downloading the stable native build: `curl -fsSL https://claude.ai/install.sh | bash -s stable`
- keeps normal `ddev start` fast by skipping Claude Code installation when the persisted binary is already runnable
- persists each CLI's binary, HOME, and XDG paths under **project-local** storage so each DDEV project has its own profiles
- seeds the project-local HOME from the container home on first bootstrap for `.gitignore_global` when present
- keeps its own artifacts out of the project's git via a managed block in `.git/info/exclude`, refreshed on every `ddev start` and `ddev claude` (see [Keeping add-on files out of git](#keeping-add-on-files-out-of-git))
- always runs Codex with `--sandbox danger-full-access --ask-for-approval untrusted`
## Runtime storage
@@ -74,6 +76,8 @@ Then restart:
ddev claude-swap list # opt-in; see "Multiple Claude accounts"
ddev claude-refs # opt-in; see "Sibling repo mirrors"
## Updates
Codex CLI is installed lazily when `ddev codex` runs and the persisted binary is missing or broken.
@@ -151,34 +155,127 @@ The first install needs outbound network access to `astral.sh`, `pypi.org`,
`claude-swap` captures whichever account is signed in at `cswap add` time, so the
initial multi-account setup is a few manual `/login` round-trips.
## Sibling repo mirrors
If a project works alongside sibling repos on the same self-hosted Git host, Claude can be given
read-only, auto-refreshing local clones of them to grep for prior art — plus, optionally, the
`gitea` MCP server for their issues and PRs. This is **opt-in per project** and driven entirely by
one manifest, `.ddev/claude-refs.txt`; projects without that file see no change at all.
`.ddev/claude-refs.txt` is **local by default**: the add-on's [managed git-exclude
block](#keeping-add-on-files-out-of-git) ignores it, since it typically names a LAN-only Git host,
a bot token, and mirrors specific to one person's sandbox. A project that wants the manifest shared
with the rest of the team can still track it with `git add -f .ddev/claude-refs.txt`.
### Manifest: `.ddev/claude-refs.txt`
```
# Settings: one per line, starting with @
@host git.example.com # required
@user claude-ro # bot account; with @token, drives git credentials
@token ~/.gitea-ro-token # default; 0600 file in the sandbox home, never committed
@depth 50 # default
@mcp gitea https://mcp.example.com/mcp # optional: name + URL, registered with the token as Bearer
# Mirrors: <dir-name> <owner/repo> <description, rest of line, becomes the README row>
sibling-api org/sibling-api Go service this project talks to over HTTP.
shared-lib org/shared-lib Shared component library, imported as a dependency.
```
Inline `#` comments are only allowed on setting lines, after all the values the setting reads.
Keep them off mirror lines, or they end up in the description.
Environment overrides, mainly for testing: `CLAUDE_REFS_MANIFEST`, `CLAUDE_REFS_HOME`,
`CLAUDE_REFS_MAX_AGE` (seconds), `CLAUDE_REFS_TIMEOUT` (seconds per clone or fetch), and
`CLAUDE_REFS_DETACHED` (set by `install.sh`; sends output to `refs/.sync.log`).
### What happens once a project has a manifest
- On `ddev start` and on every `ddev claude`, `install.sh` runs the sync in the background
(`setsid nohup`) so neither command waits on the network. A stamp file throttles real syncs to
at most one every 6h (`CLAUDE_REFS_MAX_AGE`), written only after a fully successful run, so a
failure retries next launch. If the Git host is unreachable, the sync logs a line and exits
cleanly rather than failing `ddev start`.
- Mirrors are shallow (`--depth 50 --single-branch` by default), live under
`.ddev/claude-code/home/refs/` (gitignored, survives `ddev restart`), and are **throwaway**:
every sync hard-resets them to the remote's current state, so local changes are lost and nothing
is ever merged. A directory that drops out of the manifest is left in place with a warning
rather than deleted.
- An entry whose repo resolves to the project's own `origin` is skipped, so the project never ends
up with a second, stale copy of itself.
- `refs/README.md` is regenerated from the manifest on every sync — descriptions come from the
manifest, so the table can't drift from what's on disk.
- The bot account's credential file and git config are rewritten from the manifest on every run,
so a renamed bot account heals itself on the next `ddev claude` instead of failing silently.
- Run `ddev claude-refs` to sync immediately instead of waiting for the throttle; inside a Claude
session, running `.ddev/claude-code/refs-sync.sh --force` does the same and is already on the
permission allowlist.
- If `@mcp` is set, the MCP server is registered once at user scope (`claude mcp add --scope
user`), and required permission-allowlist entries are added once — both idempotently, so re-runs
never duplicate them.
Access control (which repos the mirrors can see) stays entirely in the Git host: the bot account's
collaborator access decides what's visible. The manifest only names repos; it grants nothing by
itself.
### Adopting in a project
1. On the Git host, add the bot account as a **read-only** collaborator to each sibling repo.
2. Create `.ddev/claude-refs.txt` with `@host`, `@user`, and the mirrors you want. It stays local
unless you `git add -f` it (see above).
3. Put the bot's token at `.ddev/claude-code/home/.gitea-ro-token`, mode `0600` (matches the
manifest's default `@token ~/.gitea-ro-token`; never commit it).
4. `ddev restart`, then check `.ddev/claude-code/home/refs/.sync.log` after a minute or so.
5. Paste this into the project's `CLAUDE.md`, adding a sentence about the most relevant sibling:
```markdown
## Sibling repos
Read-only shallow clones of sibling projects live in `.ddev/claude-code/home/refs/`
(gitignored, declared in `.ddev/claude-refs.txt`). Grep them for prior art before
re-deriving a solution; `refs/README.md` says what each one is. They are reference
material: never commit or push from them. They sync in the background at most every
6h; run `ddev claude-refs` before trusting anything time-sensitive.
```
The container still needs to resolve and reach the Git host (and the MCP host, if used).
## Installed managed files
.ddev/config.codex-cli.yaml
.ddev/config.claude-code.yaml
.ddev/config.codex-claude.yaml
.ddev/commands/web/codex
.ddev/commands/web/claude
.ddev/commands/web/claude-swap
.ddev/commands/web/claude-refs
.ddev/codex-cli/install.sh
.ddev/claude-code/install.sh
.ddev/claude-code/install-swap.sh
.ddev/claude-code/refs-sync.sh
.ddev/claude-code/git-exclude.sh
## Notes
## Keeping add-on files out of git
Every artifact the add-on drops into a project — `.ddev/claude-code/`, `.ddev/codex-cli/`, the
`commands/web/*` wrappers, `.ddev/config.codex-claude.yaml`, `.ddev/addon-metadata/ddev-codex-claude-cli`,
`.ddev/plans`, and `.ddev/claude-refs.txt` — should stay out of the project's git history.
`.ddev/claude-code/git-exclude.sh` maintains a marker-delimited block for this in the project's
`.git/info/exclude` (not `.gitignore`): a per-clone file that every git process honours, including
git run from the host, regardless of `$HOME` or container config. `install.sh` runs it on every
`ddev start` and every `ddev claude` (and the codex installer runs the same script too), so the
block always matches the add-on's current file list — nothing to copy or keep in sync by hand.
Content outside the `>>> ddev-codex-claude-cli >>>` / `<<< ddev-codex-claude-cli <<<` markers is
left untouched; only the block between them is rewritten. Personal patterns (`.DS_Store`, `.idea`,
`CLAUDE.md`, …) are not part of this block — they belong in your own `~/.gitignore_global`, wired
up via `core.excludesfile` as described below, since whether a given project tracks its own
`CLAUDE.md` is not the add-on's call.
- if the container home has `.gitignore_global` (typically from host `~/.ddev/homeadditions/.gitignore_global`), it is copied into each project-local CLI `HOME` on first bootstrap; git's `core.excludesfile` is pointed at that project-local copy — this only affects git run *inside* the container, which is why the add-on's own artifacts use the `.git/info/exclude` block instead
- the first `ddev codex` in a project can take some time because it downloads the Codex CLI binary; later runs reuse `.ddev/codex-cli/bin/codex`
- the first `ddev start` after installing the add-on can take some time because it downloads Claude Code; later starts reuse `.ddev/claude-code/home/.local/bin/claude`
- if the container home has `.gitignore_global` (typically from host `~/.ddev/homeadditions/.gitignore_global`), it is copied into each project-local CLI `HOME` on first bootstrap; git's `core.excludesfile` is pointed at that project-local copy
- if you do not want add-on or CLI runtime files in your repository, ignore them in git, for example:
/.ddev/addon-metadata/ddev-codex-claude-cli
/.ddev/config.codex-cli.yaml
/.ddev/config.claude-code.yaml
/.ddev/commands/web/codex
/.ddev/commands/web/claude
/.ddev/commands/web/claude-swap
/.ddev/codex-cli/
/.ddev/claude-code/
Removing the add-on deletes `.ddev/codex-cli/` and `.ddev/claude-code/` as well (local auth, config, and CLI binaries). Back up those directories first if you need to keep them.
Removing the add-on deletes `.ddev/codex-cli/` and `.ddev/claude-code/` as well (local auth, config, and CLI binaries). Back up those directories first if you need to keep them. The `.git/info/exclude` block is left in place; an orphaned entry for a path that no longer exists is harmless.
## macOS / Linux / WSL2