Metadata-Version: 2.4
Name: symlegion
Version: 0.3.1
Summary: Keep AI instruction files in sync using symlinks
Author: Martin Mose Facondini
License-Expression: MIT
Project-URL: Homepage, https://github.com/jmb-yolo-mode/symlegion
Project-URL: Repository, https://github.com/jmb-yolo-mode/symlegion
Project-URL: Issues, https://github.com/jmb-yolo-mode/symlegion/issues
Keywords: ai,cli,instructions,symlink,tooling
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Software Development :: Version Control
Classifier: Topic :: Utilities
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: PyYAML>=6.0
Dynamic: license-file

# Symlegion

Keep your AI instruction files in sync with **zero magic** — just symlinks.

> Credit: **Symlegion** is a Python port of the original [`agentlink`](https://github.com/martinmose/agentlink) tool by Martin Mose Hansen.

Different tools want different files at project root: `AGENTS.md` (OpenAI/Codex, OpenCode), `CLAUDE.md` (Claude Code), `GEMINI.md`, etc. There's no standard, and I'm not waiting for one. **Symlegion** solves the basic need: keep your **personal** instruction files (in `~`) and your **project** instruction files in sync **without generators**. Edit one, they all reflect it.

Creating instruction files is easy with `/init` commands, but keeping them up to date is the hard part — and expensive too. Good instruction files are often crucial and make a huge difference when using agentic tools. Since they're so important, these files are typically generated with expensive models. Why pay repeatedly to regenerate similar content across different tools?

**Future-proof by design:** We don't know what tomorrow brings in the AI tooling space, but symlegion is ready. New tool expects `.newtool/ai-config.md`? Just add it to your config. Complex nested structure like `workspace/ai/tools/newframework/instructions.md`? No problem. Symlegion automatically creates the directories and symlinks without any code changes needed.

> Scope: **instruction files only**. No MCP `.mcp.json` or chain configs. Simple on purpose.

---

## Why Symlegion?

- **One real file, many aliases** — pick a *source* (`CLAUDE.md` or `AGENTS.md` or whatever), symlink the rest.
- **No codegen** — no templates, no transforms, no surprise diffs.
- **Project + global** — works in repos *and* under `~/.config/…`.
- **Idempotent** — re-run safely; it fixes broken/misdirected links.
- **Portable** — works on macOS and Linux.
- **Future-ready** — handles any directory structure, automatically creates paths. Tomorrow's AI tool? Just add its path.

---

## How it works

You tell Symlegion which file or folder is the **source**, and which other paths should **link** to it. Symlegion creates/fixes symlinks accordingly.

- `mode: direct` is the original behavior and the default.
- `mode: recursive` searches multiple roots for matching projects and creates links inside each match.

```yaml
# .symlegion.yaml (in project root)
- source: CLAUDE.md
  links:
    - AGENTS.md                          # OpenCode, Codex
    - .github/copilot-instructions.md    # GitHub Copilot
    - .cursorrules                       # Cursor AI
    - GEMINI.md                          # Gemini CLI
- source: prompts/shared
  links:
    - .ai/prompts                        # Folder symlink
```

Result:
```
./CLAUDE.md                           # real file you edit
./AGENTS.md                           -> CLAUDE.md          (symlink)
./.github/copilot-instructions.md     -> ../CLAUDE.md       (symlink)
./.cursorrules                        -> CLAUDE.md          (symlink)
./GEMINI.md                           -> CLAUDE.md          (symlink)
./.ai/prompts                         -> ../prompts/shared  (symlink dir)
```

Global mode (in HOME) is the same idea:

```yaml
# ~/.config/symlegion/config.yaml
- source: ~/.config/claude/CLAUDE.md
  links:
    - ~/.config/opencode/AGENTS.md
    - ~/.config/some-tool/INSTRUCTIONS.md
```

---

## Install

```bash
uv tool install symlegion
```

Or run without installing:

```bash
uv run symlegion.py --help
```

---

## Usage

### Getting started

```bash
# Initialize in your project
symlegion init

# Edit the created .symlegion.yaml to match your needs
# Create your source file (e.g., CLAUDE.md)

# Sync to create symlinks
symlegion sync
```

`init` creates a starter `.symlegion.yaml` with both `direct` and `recursive` examples.

The starter config assumes OpenCode as the source of truth and includes mappings for:

- rules: `AGENTS.md` -> `CLAUDE.md`, `.claude/CLAUDE.md`, `.goosehints`.
- commands/prompts/recipes: `.opencode/commands/` -> `.claude/commands/`, `.pi/prompts/`, `.goose/recipes/`.

### Commands

```bash
symlegion init
symlegion sync
symlegion check
symlegion clean
symlegion doctor
```

### Command behavior

- `init` creates a starter YAML file. By default it writes `.symlegion.yaml` in the current directory. With `--config`, it writes to the provided path.
- `sync` validates each source and creates or repairs the configured links.
- `check` reports source and link status without changing anything.
- `clean` removes only links managed by the selected config file. It never removes source files or folders.
- `doctor` checks symlink support, binary availability, and default config locations.

### Helpful flags

```bash
symlegion sync --dry-run
symlegion sync --force
symlegion --verbose sync
symlegion --config ~/somewhere/project.yaml sync
```

- `--config` chooses a specific YAML file.
- `--dry-run` previews changes without writing anything.
- `--force` allows Symlegion to replace wrong targets, replace existing non-symlink paths during sync, and accept a symlink as the source.
- `--verbose` prints each source and link as it is processed.

### Without init (auto-config)

```bash
symlegion sync
```

What it does:

- If `--config` is provided, Symlegion uses that file.
- Otherwise it reads `.symlegion.yaml` in the current directory.
- It creates or fixes symlinks listed under each group so they point to that group's `source`.

If there's **no** `.symlegion.yaml` in CWD:
- Falls back to `~/.config/symlegion/config.yaml` (global).
- If missing, it **auto-creates** a sane default and tells you.

### Config file path resolution

- `symlegion sync` / `check` / `clean`:
	- use `--config <file>` when provided.
	- otherwise use `.symlegion.yaml` in the current working directory.
	- otherwise fall back to `~/.config/symlegion/config.yaml`.
- `symlegion init`:
	- creates `.symlegion.yaml` in the current working directory by default.
	- creates the file passed through `--config` when provided.

---

## Config

### Project config (recommended)

Place a single file at repo root:

`.symlegion.yaml`
```yaml
- source: CLAUDE.md
  links:
    - AGENTS.md
    - OPENCODE.md
- source: prompts/shared
  links:
    - .ai/prompts
```

Notes:
- Omitting `mode` is the same as `mode: direct`.
- Each `source` can be a real file or a real folder, but not a symlink unless you use `--force`.
- A config file is a YAML list of groups. Each group has one `source`, one or more `links`, and optional recursive fields.

### Direct mode

`direct` is the default mode and matches the original Symlegion behavior.

```yaml
- mode: direct
  source: CLAUDE.md
  links:
    - AGENTS.md
    - OPENCODE.md
```

- `source` can be absolute or relative.
- Relative `source` and `links` are resolved from the folder that contains the YAML config file.
- Absolute `source` values stay absolute.
- `~` expands to `$HOME`.
- Direct mode is best when one repo or one config file explicitly manages known paths.

### Recursive mode

Use `recursive` when you want Symlegion to scan one or more parent folders, find matching projects, and create the same symlink layout inside each one.

```yaml
- mode: recursive
  source: .opencode/commands/
  links:
    - .claude/commands/
    - .pi/prompts/
  search:
    - ~/koofr/workspace/
    - ~/code/
  depth: 3
```

- In recursive mode, `source` and every entry in `links` must be relative paths.
- `search` entries may be absolute or relative.
- Relative `search` entries are resolved from the folder that contains the YAML config file.
- `~` in `search` entries expands to `$HOME`.
- If `depth` is omitted, Symlegion uses `5`.
- Symlegion walks each search root down to `depth` levels, looking for `source` in each candidate folder.
- When it finds a match, it creates every link in `links` relative to that matched folder root.
- If one or more search paths do not exist, Symlegion prints a warning and continues.
- Recursive mode is best when one config file manages many repos that share the same layout.

### How recursive matching works

Given this config:

```yaml
- mode: recursive
  source: .opencode/commands/
  links:
    - .claude/commands/
    - .pi/prompts/
    - .goose/recipes/
  search:
    - ~/code/
    - ../workspace/
  depth: 3
```

- Symlegion scans every folder under each `search` root until depth `3`.
- When it finds a folder containing `.opencode/commands/`, that folder becomes the matched project root.
- It then creates:
	- `.claude/commands/` -> `.opencode/commands/`.
	- `.pi/prompts/` -> `.opencode/commands/`.
	- `.goose/recipes/` -> `.opencode/commands/`.
- If `~/code/client-a/project-x` matches, all links are created inside `~/code/client-a/project-x`.

### Selected config examples

Starter config generated by `init`:

```yaml
- mode: direct
  source: AGENTS.md
  links:
    - CLAUDE.md
    - .claude/CLAUDE.md
    - .goosehints

- mode: direct
  source: .opencode/commands/
  links:
    - .claude/commands/
    - .pi/prompts/
    - .goose/recipes/

# - mode: recursive
#   source: .opencode/commands/
#   links:
#     - .claude/commands/
#     - .pi/prompts/
#     - .goose/recipes/
#   search:
#     - ~/koofr/workspace/
#     - ~/code/
#   depth: 3
```

### Global config

`~/.config/symlegion/config.yaml`
```yaml
- source: ~/.config/claude/CLAUDE.md
  links:
    - ~/.config/opencode/AGENTS.md
```

---

## Platform notes

- **macOS + Linux**: standard POSIX symlinks (`ln -s`) — works the same.
- **Git**: symlinks are stored as links (not file copies). That's fine; teams who dislike that can add them to `.gitignore`.

### Gitignore patterns

Since symlegion creates multiple instruction files but only one is the real source, you can gitignore all AI instruction files except your chosen source:

```gitignore
# Ignore all AI instruction files
AGENTS.md
CLAUDE.md  
GEMINI.md
OPENCODE.md
.cursorrules
.github/copilot-instructions.md

# But track your chosen source file (example: tracking CLAUDE.md)
!CLAUDE.md
```

This keeps your repository clean while ensuring your source file is version controlled. Symlegion will create the source file if it doesn't exist when running `sync`.
- **Editors/IDEs**: most follow symlinks transparently.

---

## FAQ

**Why not templates or generators?**  
Because 90% of the time the files **should be identical**. When they're not, this tool isn't the right fit (or add a second source and stop linking that one).

**What if my source differs per project?**  
Perfect—put a `.symlegion.yaml` in each repo and choose the source you actually edit there.

**Can the source be `AGENTS.md` instead of `CLAUDE.md`?**  
Yes. The source is *whatever you want to edit*. The others link to it.

**What happens when a new AI tool comes out?**  
Just add its expected path to your config. If "SuperCoder AI" expects `.supercoder/prompts/main.md`, add that path and run `symlegion sync`. Directories are created automatically, and the symlink points to your chosen file or folder source. Zero code changes, zero updates needed.

**MCP / `.mcp.json`?**  
Out of scope. Formats differ between tools; symlinking a single JSON to multiple consumers usually doesn't make sense.
