Metadata-Version: 2.4
Name: clsync
Version: 1.0.6
Summary: Sync Claude Code knowledge across machines via a shared git repository and AI-powered synthesis
Project-URL: Homepage, https://gitlab.hahtse.de/hahtse/claudesync
Project-URL: Repository, https://gitlab.hahtse.de/hahtse/claudesync
Project-URL: Bug Tracker, https://gitlab.hahtse.de/hahtse/claudesync/-/issues
Author-email: Hans-Christian Heinz <hans-christian.heinz@hahtse.de>
License: MIT
License-File: LICENSE
Keywords: anthropic,claude,claude-code,github,gitlab,knowledge-management,sync
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Utilities
Requires-Python: >=3.11
Requires-Dist: gitpython>=3.1.40
Requires-Dist: httpx>=0.27.0
Requires-Dist: pydantic>=2.0
Requires-Dist: rich>=13.0
Requires-Dist: tomli-w>=1.0
Requires-Dist: typer>=0.12.0
Provides-Extra: bootstrap
Requires-Dist: anthropic>=0.40.0; extra == 'bootstrap'
Provides-Extra: dev
Requires-Dist: anthropic>=0.40.0; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Description-Content-Type: text/markdown

# clsync

Synchronize Claude Code knowledge (`CLAUDE.md` files, todos) across multiple development machines using a shared git repository and an AI-powered synthesis pipeline.

## How it works

Each machine runs the `clsync` CLI to push snapshots of its `.claude/` artifacts into a shared git repo. A CI pipeline then calls the Anthropic API to intelligently merge the knowledge from all machines into a single consolidated version. Machines pull and apply the consolidated result locally.

```
Machine A ──push──> sync repo ──CI pipeline──> consolidated/ ──pull──> Machine A
Machine B ──push──>            (Claude API merge)              ──pull──> Machine B
```

The system has two components:

- **Client CLI** (`clsync`) — installed on each dev machine, handles push/pull/apply
- **Pipeline** (`pipeline/synthesize.py`) — lives inside the sync repo, runs on CI (or locally)

The Anthropic API key lives in your CI environment (or local shell for the `local` provider). It never reaches other client machines.

---

## Repository layout (sync repo)

```
claude-knowledge-sync/
├── .gitlab-ci.yml             # or .github/workflows/synthesize.yml for GitHub
├── machines/                  # raw snapshots, one subdir per machine
│   ├── desktop/
│   │   ├── global/
│   │   │   ├── CLAUDE.md
│   │   │   └── settings.json
│   │   └── projects/
│   │       └── my-app/
│   │           ├── CLAUDE.md
│   │           └── memory/
│   │               └── MEMORY.md
│   └── laptop/
│       └── global/
│           └── CLAUDE.md
├── consolidated/              # merged synthesis output
│   ├── global/
│   │   ├── CLAUDE.md
│   │   └── settings.json
│   └── projects/
│       └── my-app/
│           ├── CLAUDE.md
│           └── memory/
│               └── MEMORY.md
├── meta/
│   ├── last-synthesis.json    # hashes of inputs at last synthesis
│   └── sync-log.json          # append-only log of synthesis runs
└── pipeline/
    ├── synthesize.py
    ├── models.py
    ├── requirements.txt
    └── prompts/
        ├── synthesize_claude_md.txt
        └── synthesize_critic.txt
```

---

## Prerequisites

- Python 3.11+
- Git
- A git-hosting account (GitLab, GitHub, or any platform) for the sync repo

---

## Choosing a CI provider

clsync supports three providers for running the synthesis pipeline:

| Provider | How synthesis runs | Anthropic API key location |
|----------|--------------------|---------------------------|
| `gitlab` | GitLab CI pipeline (self-hosted or gitlab.com) | GitLab CI/CD variable |
| `github` | GitHub Actions workflow | GitHub repository secret |
| `local` | Subprocess on your machine | Your local environment |

Use `--provider` during `clsync init` to select one. The default is `gitlab`.

---

## Part 1: Sync repo setup (one-time, manual)

Create an empty git repository on your chosen platform (e.g. `claude-knowledge-sync`), then clone it locally.

### GitLab setup

1. **Create the project** and note the **project ID** (shown at the top of Settings → General).

2. **Protect the main branch** — Settings → Repository → Protected Branches. Required for protected CI/CD variables.

3. **Add the Anthropic API key** — Settings → CI/CD → Variables → Add variable:

   | Field | Value |
   |-------|-------|
   | Key | `ANTHROPIC_API_KEY` |
   | Value | your Anthropic API key |
   | Masked | yes |
   | Protected | yes |
   | Expand variable reference | no |

4. **Create a Pipeline Trigger Token** — Settings → CI/CD → Pipeline trigger tokens → Add new token. Copy the `glptt-...` value.

5. **Create a Project Access Token for status polling** — Settings → Access Tokens → Add new token with role Maintainer and scope `read_api`. Copy the `glpat-...` value.

6. **(Optional) Create a scheduled pipeline** — Build → Pipeline schedules → New schedule. Suggested cron: `0 3 * * *` (daily at 3 AM UTC), branch `main`.

7. **Ensure a runner is available** — the pipeline uses `python:3.12-slim`. Verify a runner with the Docker executor is attached (Settings → CI/CD → Runners).

8. **Allow CI push-back** — Settings → CI/CD → Token Access → enable **"Allow Git push requests to the repository"**. Without this the CI job fails with a 403 when it tries to push synthesized results.

### GitHub setup

1. **Create the repository** (public or private).

2. **Add the Anthropic API key** — Settings → Secrets and variables → Actions → New repository secret: `ANTHROPIC_API_KEY`.

3. **Create a Personal Access Token** with scopes `repo` and `workflow` (Settings → Developer settings → Personal access tokens → Fine-grained tokens or classic tokens). This is what you enter as `--github-token` during `init`.

### Local setup

No remote CI is needed. You must have `ANTHROPIC_API_KEY` set in your shell environment before running `clsync trigger` or `clsync sync`.

---

## Part 2: Client machine setup

Repeat these steps on every machine you want to sync.

### 1. Clone the sync repo locally

```bash
git clone https://github.com/yourname/claude-knowledge-sync.git ~/claude-knowledge-sync
# or for GitLab:
git clone https://gitlab.example.com/yourname/claude-knowledge-sync.git ~/claude-knowledge-sync
```

### 2. Install the clsync CLI

Use `pipx`, which installs CLI tools in isolated environments and automatically adds them to your PATH:

```bash
pip install pipx
py -m pipx ensurepath   # one-time: adds pipx's bin dir to PATH
```

> **Windows note:** `pip install pipx` may put `pipx.exe` in a Scripts directory that is not yet on PATH. If `pipx ensurepath` is not recognized, use `py -m pipx ensurepath` instead — this runs pipx as a Python module and bypasses the PATH issue.

Restart your terminal, then install from PyPI:

```bash
pipx install clsync
```

Alternatively, install from a local clone of this repository:

```bash
pipx install .
```

Verify:

```bash
clsync --help
```

For development (editable install with test dependencies — use pip, not pipx):

```bash
pip install -e ".[dev]"
```

### 3. Initialize clsync

Run `clsync init` and follow the prompts, or pass flags directly.

**GitLab:**

```bash
clsync init \
  --provider gitlab \
  --repo-path ~/claude-knowledge-sync \
  --gitlab-url https://gitlab.example.com \
  --project-id 42 \
  --trigger-token glptt-... \
  --access-token glpat-...
```

**GitHub:**

```bash
clsync init \
  --provider github \
  --repo-path ~/claude-knowledge-sync \
  --github-token ghp_... \
  --github-repo yourname/claude-knowledge-sync
```

**Local:**

```bash
clsync init \
  --provider local \
  --repo-path ~/claude-knowledge-sync
```

`init` prompts interactively for any values not supplied as flags. The **machine ID** defaults to your hostname. If `--repo-path` does not yet contain a git repo, you will be offered the option to clone from a remote URL.

After init, `clsync` scaffolds the appropriate CI config into the sync repo (`.gitlab-ci.yml` for GitLab, `.github/workflows/synthesize.yml` for GitHub) and offers to commit and push it.

Credentials are stored in `~/.claude-sync/config.toml`, outside any repository. On macOS/Linux the file permissions are set to `600` automatically.

### 4. Register projects

Register each local project directory you want to sync:

```bash
clsync add-project ~/dev/my-app
clsync add-project ~/dev/another-project
```

Or scan a directory tree to discover projects automatically:

```bash
clsync discover --root ~/dev
```

This walks `~/dev` up to 2 levels deep, finds directories containing a `.claude/` directory or existing Claude memory context, and offers to add them.

### Manage registered projects

List all registered projects and their sync status:

```bash
clsync list-projects
```

Remove a project from syncing:

```bash
clsync remove-project ~/dev/my-app

# Also delete local Claude memory files (~/.claude/projects/{slug}/)
clsync remove-project ~/dev/my-app --purge
```

Move a registered project to a new path (updates config and Claude internal files):

```bash
clsync move-project ~/dev/old-path ~/dev/new-path

# Also rename the directory on disk
clsync move-project ~/dev/old-path ~/dev/new-path --rename-dir
```

---

## Configuration reference

Config file location: `~/.claude-sync/config.toml`

**GitLab provider:**

```toml
[client]
machine_id = "desktop"

[provider]
type = "gitlab"

[gitlab]
url = "https://gitlab.example.com"
project_id = 42
trigger_token = "glptt-..."   # pipeline trigger token
access_token  = "glpat-..."   # personal access token (read_api scope)

[repo]
local_path = "/home/user/claude-knowledge-sync"

[projects]
paths = [
    "/home/user/dev/my-app",
    "/home/user/dev/another-project",
]
auto_discover = false          # if true, scan discover_roots on every push
discover_roots = []            # directories to scan when auto_discover = true
discover_max_depth = 2         # how many directory levels deep to scan

[safety]
backup_before_apply = true     # back up local CLAUDE.md before overwriting
```

**GitHub provider** — replace `[provider]` and `[gitlab]` with:

```toml
[provider]
type = "github"

[github]
token = "ghp_..."              # PAT with repo + workflow scopes
repo = "yourname/claude-knowledge-sync"
workflow_id = "synthesize.yml" # filename in .github/workflows/
```

**Local provider** — replace `[provider]` with, and omit `[gitlab]`/`[github]`:

```toml
[provider]
type = "local"
```

Credentials are stored directly in the config file. Ensure its permissions are restricted (`chmod 600 ~/.claude-sync/config.toml` on macOS/Linux — done automatically by `init`).

---

## Daily usage

### Push your current state and trigger synthesis

```bash
clsync push
```

This:
1. Pulls the latest remote state (rebase)
2. Copies your local `.claude/CLAUDE.md` files into `machines/{machine_id}/` in the sync repo
3. Commits and pushes if anything changed
4. Triggers the CI synthesis pipeline

To push without triggering the pipeline:

```bash
clsync push --no-trigger
```

### Pull and apply the latest consolidated result

```bash
clsync pull
```

This:
1. Pulls the latest remote state (rebase)
2. Checks whether Claude Code is currently running (warns and aborts if so)
3. For each scope with large changes (>50% of lines differ), asks for confirmation
4. Applies consolidated `CLAUDE.md` files to your local `.claude/` directories
5. Prints warnings for any `<!-- CONFLICT: -->` or `<!-- UNVERIFIED: -->` markers found in applied files

To apply even if Claude Code is running (still confirms large diffs):

```bash
clsync pull --allow-running
```

To skip all safety checks (running process detection and large-diff confirmation):

```bash
clsync pull --force
```

### Push, synthesize, and pull in one command

```bash
clsync sync
```

This runs push → waits for the pipeline to complete → pull, in sequence.

### Trigger the pipeline without pushing

```bash
clsync trigger          # fire and forget
clsync trigger --wait   # wait for completion and print result
```

### Bootstrap CLAUDE.md files from existing context

If you already have Claude Code memory and session history on a machine but no `CLAUDE.md` files yet, `bootstrap` can generate them automatically using the Anthropic API:

```bash
# Bootstrap all registered projects and global ~/.claude/CLAUDE.md
clsync bootstrap --api-key sk-ant-...

# Or set the environment variable and omit the flag
export ANTHROPIC_API_KEY=sk-ant-...
clsync bootstrap

# Bootstrap only the global CLAUDE.md
clsync bootstrap --global-only

# Bootstrap only a specific registered project
clsync bootstrap --project ~/dev/my-app

# Overwrite existing files without confirmation
clsync bootstrap --force
```

When an existing `CLAUDE.md` would be overwritten, `bootstrap` shows a line-count diff and asks for confirmation unless `--force` is given.

> **Note:** `bootstrap` calls the Anthropic API directly from the client machine using your API key. Unlike the sync pipeline, this is a one-time local operation — it is not required for normal push/pull sync to work.

### Check synthesis status

```bash
clsync status
```

Prints the last synthesis timestamp and a table of scopes with machine IDs and file hashes.

---

## Command reference

| Command | Description |
|---------|-------------|
| `clsync init` | Initialize config for this machine |
| `clsync add-project PATH` | Register a project directory |
| `clsync remove-project PATH` | Unregister a project (optionally `--purge` local Claude data) |
| `clsync move-project OLD NEW` | Move a registered project to a new path |
| `clsync list-projects` | List registered projects and their sync status |
| `clsync discover --root PATH` | Find and add projects under a directory |
| `clsync bootstrap` | Generate CLAUDE.md files from existing Claude memory and session history |
| `clsync push` | Snapshot local files, push, trigger pipeline |
| `clsync pull` | Pull consolidated files and apply locally |
| `clsync sync` | Full push → synthesize → pull cycle |
| `clsync trigger` | Trigger the CI pipeline |
| `clsync status` | Show last synthesis metadata |

Run `clsync COMMAND --help` for full option details on any command.

---

## How synthesis works

When the CI pipeline runs, `pipeline/synthesize.py`:

1. Scans `machines/` to find all scopes (`global`, `projects/my-app`, etc.)
2. Compares SHA-256 hashes of current input files against the previous run (stored in `meta/last-synthesis.json`)
3. For each scope that changed, calls the Anthropic API (Claude) with a merge prompt containing all machine versions
4. Runs a second **critic pass** — another Claude call that checks the merged output for claims not traceable to any source file (hallucination detection)
5. If the critic finds more than 3 unverified claims, the scope is rejected and the previous consolidated file is left untouched
6. If 1–3 unverified claims are found, they are embedded as `<!-- UNVERIFIED: ... -->` HTML comments in the output
7. Writes the merged result to `consolidated/{scope}/CLAUDE.md`
8. Updates `meta/last-synthesis.json` and appends to `meta/sync-log.json`

If only one machine has content for a scope, it is copied directly without an API call.

### Conflict markers

When two machines contradict each other and the synthesizer cannot determine which is correct, it writes:

```
<!-- CONFLICT: [description] — resolve manually -->
```

`clsync pull` scans applied files for these markers and prints them as warnings so you can resolve them manually.

---

## Security notes

| Secret | Where it lives | Never in |
|--------|---------------|----------|
| `ANTHROPIC_API_KEY` | CI/CD variable (GitLab or GitHub) or local env | Client config file, code, repo |
| GitLab trigger token (`glptt-...`) | `~/.claude-sync/config.toml` | Code, repo, CI |
| GitLab access token (`glpat-...`) | `~/.claude-sync/config.toml` | Code, repo, CI |
| GitHub token (`ghp_...`) | `~/.claude-sync/config.toml` | Code, repo, CI |

**GitLab:** The pipeline push-back uses `CI_JOB_TOKEN`, injected automatically by GitLab. No extra token is needed. GitLab does not trigger a new pipeline when `CI_JOB_TOKEN` is used for a push.

**GitHub:** Synthesis commits are pushed using the `GITHUB_TOKEN` provided automatically by Actions. The workflow has `contents: write` permission.

The config file lives at `~/.claude-sync/config.toml`, outside any repository, and is never committed. Its permissions are restricted to `600` automatically on Unix. Do not check it into version control.

Do not store credentials, passwords, or API keys inside `CLAUDE.md` files — they are committed to the sync repo and visible to anyone with repository access.

---

## Running tests

```bash
pip install -e ".[dev]"
pytest tests/ -q
```

All tests use temporary directories and mocked API calls. No real GitLab or Anthropic credentials are needed to run the test suite.

---

## Development

The client CLI source is under `src/claude_sync/`:

| File | Purpose |
|------|---------|
| `cli.py` | Typer CLI entry point — all commands |
| `config.py` | Config loading and saving |
| `snapshot.py` | Copy local `.claude/` files into the sync repo |
| `apply.py` | Copy consolidated files back to local `.claude/` |
| `bootstrap.py` | Generate CLAUDE.md from existing Claude memory/session history |
| `git_ops.py` | Git wrapper (pull, commit, push) |
| `gitlab_api.py` | Low-level GitLab REST API calls |
| `ci_provider.py` | CI provider abstraction (GitLab, GitHub Actions, local) |
| `lock.py` | Detect whether Claude Code is running |
| `scaffold.py` | Scaffold CI config and pipeline scripts into a new sync repo |
| `models.py` | Pydantic data models |
| `claude_paths.py` | Resolve Claude Code paths (memory dir, project slug) |

The pipeline source is under `pipeline/`:

| File | Purpose |
|------|---------|
| `synthesize.py` | Main CI entry point |
| `models.py` | Pydantic models (subset of client models) |
| `requirements.txt` | `anthropic`, `pydantic` |
| `prompts/synthesize_claude_md.txt` | Merge prompt template |
| `prompts/synthesize_critic.txt` | Hallucination-check prompt template |
