Ctrl K
ComunidadGuías…

CONTRIBUTING.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Maintainer notes

## One worktree per change (sessions share a checkout)

This repo is often edited by several Claude Code sessions at once. Switching
branches in the shared primary checkout disrupts other sessions, so do every new
change in its own git worktree. Do not use `git stash` from any checkout:
`refs/stash` is repository-global, so a pop can apply another worktree's entry.

```
git worktree add ../exomem-<topic> -b <branch>
# work, commit, push from the worktree
git worktree remove ../exomem-<topic>
```

Commit and push from the worktree; leave the primary checkout on whatever branch
the other session is using.

## uv is pinned — install it project-locally, don't downgrade your global one

`uv sync` in a fresh checkout fails on any uv other than the pinned writer
version:

```
error: Required uv version `==0.11.28` does not match the running version `0.11.31`.
Update `uv` by running `uv self update 0.11.28`.
```

**Do not run that suggestion.** uv is normally one shared installation managing
many unrelated tools, and `uv self update` downgrades all of them to satisfy this
repo. Install the pinned version project-locally instead:

```
UV_INSTALL_DIR=.uvbin curl -LsSf https://astral.sh/uv/0.11.28/install.sh | sh
PATH="$PWD/.uvbin:$PATH" uv sync
```

The pin is deliberate and is not just about this file. A single writer version
keeps lockfile marker normalization identical across Windows, WSL, Docker and
CI, and `required-version` is what makes that bind — `Dockerfile`,
`infra/tool-versions.env` and `.github/workflows/hosted-infrastructure.yml` all
declare the same version, and
`tests/test_uv_lock_policy.py::test_uv_writer_version_is_pinned_across_repository_surfaces`
fails if any of the four drifts. Bumping uv means bumping all four together.

## The privacy gate runs anywhere — run it before you push

`public_artifact_privacy` refuses a drive-absolute path literal in any
repository input or shipped wheel member. Docstrings count: they travel inside
the wheel.

```
uv run python scripts/validate-public-artifacts.py --repository
```

It is pure Python over files and takes a couple of seconds on any platform. It
is worth running deliberately because the CI job that enforces it is
Linux-only, so a Windows-only contributor's first encounter with it is a red
PR. That is not hypothetical: a branch that had passed two independent clean
reviews landed 16 findings and six red checks this way (#574).

### The %PROGRAMDATA% fallback: import it, don't retype it

The Windows machine-wide base genuinely needs `ProgramData`, so the last tier
of the fallback chain is written as a concatenation, which keeps the literal
from ever appearing contiguously. Read quickly that looks like odd formatting,
which is exactly how it gets dropped when the chain is reproduced by hand.

- **Python inside the package:** import `mode.windows_machine_wide_root()`.
  Nothing else may re-derive the chain; `tests/test_windows_machine_wide_root.py`
  fails if a new file does.
- **The standalone hook scripts** under `src/exomem/_hooks/` run as bare files
  under the client's interpreter and cannot import the package, so they carry a
  deliberate mirror. The same test pins their spelling.
- **PowerShell** cannot import a Python helper either. Use the same split --
  `"C:" + "\ProgramData"` -- as `scripts/install-service.ps1` does, or
  `$env:SystemDrive`.
- **Prose and docstrings:** write `%PROGRAMDATA%`. The gate permits the
  environment-variable spelling, and one canonical form is what makes the
  convention imitable.

## Installing the service on a CPU-only host

`scripts/install-service.sh --profile hybrid` pulls `torch`, which is pinned to
the CUDA index on Linux and Windows — multi-GB, and unusable without a GPU. Use
`--profile onnx` instead: the same bi-encoder served through ONNX Runtime, with
`EXOMEM_EMBED_BACKEND=onnx` written into the service environment. It is the
right default for any host without a GPU.

## Upgrade barriers: a one-way or operator-step change adds its entry

The Cloud release automation moves each release onto the cells without a person.
It stops only at an entry in `infra/contracts/upgrade-barriers-v1.json`. A pull
request that adds a one-way change or a change that needs an operator step must
add its entry in the same pull request:

- a one-way change cannot be undone by a rollback or a restore, such as a key
  conversion or a migration of state outside the cell's volume;
- an operator step is something a person must do before the upgrade, such as a
  new secret to apply.

Each entry names the first release on the far side (`version`), its `scope`
(`platform` or `cell`), its `kind` (`one-way` or `operator-step`), the runbook
section that states the step, and a one-line summary. Copy the file to
`infra/helm/platform/files/upgrade-barriers-v1.json`; the chart test keeps the two
equal. `tests/test_upgrade_barriers.py` checks the schema and that each runbook
section exists. No tool detects a one-way change, so the author decides.

Review checklist: for a change to cell state, keys, secrets or the control
database, ask whether it is one-way or needs an operator step, and if so, find its
barrier entry.

## The skill scaffold is hand-authored — keep it generic

`src/exomem/_scaffold/_Schema/` (the skill shipped to new users via `init` /
`install-skill`) is a **hand-authored, deliberately-generic starter** — a lean
example schema, not a copy of any private vault. Edit it directly.

**The one rule: keep it generic.** `tests/test_scaffold_no_leak.py` fails if any
personal name, product, podcast, domain, or vault-structure label appears in the
scaffold — or anywhere under `src/exomem/`. That test is the hard wall against the
leak class that once shipped a maintainer's real names into a friend's clone. If
it flags a token, genericize it (don't add it to an allowlist).

### Maintainer-only: the personal claude.ai skill
The maintainer derives a personal claude.ai `.skill` zip via
`scripts/rebuild-schema-zip.py`, which reads the **public scaffold**
(`src/exomem/_scaffold/_Schema/`) and overlays their real `project-keys.yaml` from
their vault. There is no private canonical or marker system — the scaffold is the
single source. That path is maintainer-only and unrelated to the public scaffold
above; contributors can ignore it.