agentsbridge 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ """agentsbridge - Bridge AGENTS.md to every AI coding tool's rules file."""
2
+
3
+ __version__ = "0.1.0"
agentsbridge/cli.py ADDED
@@ -0,0 +1,174 @@
1
+ """agentsbridge CLI - bridge AGENTS.md to every AI coding tool's rules file."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ from . import __version__, core
10
+
11
+
12
+ def build_parser() -> argparse.ArgumentParser:
13
+ parser = argparse.ArgumentParser(
14
+ prog="agentsbridge",
15
+ description=(
16
+ "Bridge AGENTS.md to every AI coding tool's rules file. "
17
+ "Maintain one source of truth; generate CLAUDE.md, "
18
+ "copilot-instructions.md, Cursor rules, and more."
19
+ ),
20
+ )
21
+ parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
22
+ sub = parser.add_subparsers(dest="command")
23
+
24
+ p_sync = sub.add_parser("sync", help="generate all target rules files from AGENTS.md")
25
+ p_sync.add_argument(
26
+ "-t",
27
+ "--targets",
28
+ default="",
29
+ help="comma-separated subset of targets (default: all)",
30
+ )
31
+ p_sync.add_argument(
32
+ "--force", action="store_true", help="overwrite files not generated by agentsbridge"
33
+ )
34
+ p_sync.add_argument(
35
+ "--dry-run", action="store_true", help="show what would change without writing"
36
+ )
37
+
38
+ p_check = sub.add_parser(
39
+ "check", help="exit non-zero if any generated file drifted from AGENTS.md (for CI)"
40
+ )
41
+ p_check.add_argument(
42
+ "-t",
43
+ "--targets",
44
+ default="",
45
+ help="comma-separated subset of targets (default: all)",
46
+ )
47
+
48
+ p_import = sub.add_parser(
49
+ "import", help="seed AGENTS.md from an existing rules file (CLAUDE.md, .cursorrules, ...)"
50
+ )
51
+ p_import.add_argument("--out", default="AGENTS.md", help="output path (default: AGENTS.md)")
52
+
53
+ p_init = sub.add_parser("init", help="create a starter AGENTS.md")
54
+ p_init.add_argument("--force", action="store_true", help="overwrite existing AGENTS.md")
55
+
56
+ return parser
57
+
58
+
59
+ def _parse_targets(value: str) -> list[str] | None:
60
+ value = value.strip()
61
+ if not value:
62
+ return None
63
+ return [v.strip() for v in value.split(",") if v.strip()]
64
+
65
+
66
+ def _root() -> Path:
67
+ return Path.cwd()
68
+
69
+
70
+ def cmd_sync(args: argparse.Namespace) -> int:
71
+ root = core.walk_up(_root())
72
+ try:
73
+ results = core.sync(
74
+ root, _parse_targets(args.targets), force=args.force, dry_run=args.dry_run
75
+ )
76
+ except FileNotFoundError as e:
77
+ print(f"error: {e}", file=sys.stderr)
78
+ return 1
79
+ except ValueError as e:
80
+ print(f"error: {e}", file=sys.stderr)
81
+ return 2
82
+
83
+ label = "would create/update" if args.dry_run else "wrote"
84
+ for r in results:
85
+ icon = {
86
+ "created": "+",
87
+ "updated": "~",
88
+ "unchanged": "=",
89
+ "skipped": "!",
90
+ "missing": "?",
91
+ }.get(r.action, " ")
92
+ detail = f" ({r.detail})" if r.detail else ""
93
+ print(f" {icon} {r.action:<9} {r.path}{detail}")
94
+ created = sum(1 for r in results if r.action in ("created", "updated"))
95
+ skipped = sum(1 for r in results if r.action == "skipped")
96
+ print(
97
+ f"\n{label} {created} file(s)"
98
+ + (f", skipped {skipped}" if skipped else "")
99
+ + (" (dry run)" if args.dry_run else "")
100
+ )
101
+ return 0
102
+
103
+
104
+ def cmd_check(args: argparse.Namespace) -> int:
105
+ root = core.walk_up(_root())
106
+ try:
107
+ clean, results = core.check(root, _parse_targets(args.targets))
108
+ except FileNotFoundError as e:
109
+ print(f"error: {e}", file=sys.stderr)
110
+ return 1
111
+ except ValueError as e:
112
+ print(f"error: {e}", file=sys.stderr)
113
+ return 2
114
+
115
+ for r in results:
116
+ icon = {"ok": "=", "drifted": "!", "missing": "?", "skipped": "-"}.get(r.action, " ")
117
+ detail = f" ({r.detail})" if r.detail else ""
118
+ print(f" {icon} {r.action:<9} {r.path}{detail}")
119
+ if clean:
120
+ print("\nall target files are up to date")
121
+ return 0
122
+ print("\ndrift detected: run `agentsbridge sync` to fix", file=sys.stderr)
123
+ return 1
124
+
125
+
126
+ def cmd_import(args: argparse.Namespace) -> int:
127
+ root = _root()
128
+ try:
129
+ body, src = core.import_source(root)
130
+ except FileNotFoundError as e:
131
+ print(f"error: {e}", file=sys.stderr)
132
+ return 1
133
+
134
+ out_path = root / args.out
135
+ if out_path.exists():
136
+ print(f"error: {out_path} already exists. Delete it first or use --out.", file=sys.stderr)
137
+ return 1
138
+ out_path.write_text(body.rstrip() + "\n", encoding="utf-8")
139
+ print(f"created {out_path} from {src.name}")
140
+ print("review it, then run `agentsbridge sync` to propagate.")
141
+ return 0
142
+
143
+
144
+ def cmd_init(args: argparse.Namespace) -> int:
145
+ root = _root()
146
+ if (root / core.SOURCE_FILENAME).exists() and not args.force:
147
+ print("AGENTS.md already exists (use --force to overwrite)")
148
+ return 0
149
+ r = core.ensure_agents_md(root, dry_run=False)
150
+ if r.action == "created":
151
+ print(f"created {r.path}")
152
+ print("edit it, then run `agentsbridge sync`")
153
+ return 0
154
+
155
+
156
+ COMMANDS = {
157
+ "sync": cmd_sync,
158
+ "check": cmd_check,
159
+ "import": cmd_import,
160
+ "init": cmd_init,
161
+ }
162
+
163
+
164
+ def main(argv: list[str] | None = None) -> int:
165
+ parser = build_parser()
166
+ args = parser.parse_args(argv)
167
+ if not args.command:
168
+ parser.print_help()
169
+ return 0
170
+ return COMMANDS[args.command](args)
171
+
172
+
173
+ if __name__ == "__main__":
174
+ sys.exit(main())
agentsbridge/core.py ADDED
@@ -0,0 +1,204 @@
1
+ """Core logic: parse AGENTS.md, render targets, sync/check/import/init."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+
9
+ from .targets import GENERATED_MARKER, TARGETS, Target, render
10
+
11
+ SOURCE_FILENAME = "AGENTS.md"
12
+
13
+ # Files agentsbridge knows how to import from, in priority order.
14
+ IMPORT_SOURCES = [
15
+ "CLAUDE.md",
16
+ ".github/copilot-instructions.md",
17
+ ".cursorrules",
18
+ ".clinerules",
19
+ ".windsurfrules",
20
+ "CONVENTIONS.md",
21
+ ]
22
+
23
+
24
+ @dataclass
25
+ class SyncResult:
26
+ target: str
27
+ path: Path
28
+ action: str # created | updated | skipped | unchanged
29
+ detail: str = ""
30
+
31
+
32
+ def find_source(root: Path) -> Path | None:
33
+ p = root / SOURCE_FILENAME
34
+ return p if p.is_file() else None
35
+
36
+
37
+ def read_source(root: Path) -> str:
38
+ p = find_source(root)
39
+ if p is None:
40
+ raise FileNotFoundError(
41
+ f"No {SOURCE_FILENAME} found in {root}. Run `agentsbridge init` first."
42
+ )
43
+ return strip_generated_marker(p.read_text(encoding="utf-8"))
44
+
45
+
46
+ def strip_generated_marker(text: str) -> str:
47
+ """Remove our own marker so re-importing a generated file stays clean."""
48
+ lines = text.splitlines()
49
+ out = [ln for ln in lines if GENERATED_MARKER not in ln]
50
+ return "\n".join(out).strip()
51
+
52
+
53
+ def is_generated(path: Path) -> bool:
54
+ try:
55
+ return GENERATED_MARKER in path.read_text(encoding="utf-8", errors="replace")[:500]
56
+ except OSError:
57
+ return False
58
+
59
+
60
+ def sync(
61
+ root: Path,
62
+ selected: list[str] | None = None,
63
+ force: bool = False,
64
+ dry_run: bool = False,
65
+ ) -> list[SyncResult]:
66
+ """Generate target files from AGENTS.md."""
67
+ body = read_source(root)
68
+ targets = resolve_targets(selected)
69
+ results: list[SyncResult] = []
70
+
71
+ for target in targets:
72
+ path = root / target.path
73
+ content = render(target, body)
74
+
75
+ if path.exists():
76
+ if not force and not is_generated(path):
77
+ results.append(
78
+ SyncResult(
79
+ target.name,
80
+ path,
81
+ "skipped",
82
+ "file exists and was not generated by agentsbridge (use --force to overwrite)",
83
+ )
84
+ )
85
+ continue
86
+ if path.read_text(encoding="utf-8", errors="replace") == content:
87
+ results.append(SyncResult(target.name, path, "unchanged"))
88
+ continue
89
+ action = "updated"
90
+ else:
91
+ action = "created"
92
+
93
+ if not dry_run:
94
+ path.parent.mkdir(parents=True, exist_ok=True)
95
+ path.write_text(content, encoding="utf-8")
96
+ results.append(SyncResult(target.name, path, action))
97
+
98
+ return results
99
+
100
+
101
+ def check(root: Path, selected: list[str] | None = None) -> tuple[bool, list[SyncResult]]:
102
+ """Return (clean, results). clean=False means at least one target drifted."""
103
+ body = read_source(root)
104
+ targets = resolve_targets(selected)
105
+ results: list[SyncResult] = []
106
+ clean = True
107
+
108
+ for target in targets:
109
+ path = root / target.path
110
+ content = render(target, body)
111
+
112
+ if not path.exists():
113
+ clean = False
114
+ results.append(SyncResult(target.name, path, "missing"))
115
+ continue
116
+ if not is_generated(path):
117
+ # Hand-written file: we never claim drift on files we don't own.
118
+ results.append(SyncResult(target.name, path, "skipped", "not generated"))
119
+ continue
120
+ if path.read_text(encoding="utf-8", errors="replace") != content:
121
+ clean = False
122
+ results.append(SyncResult(target.name, path, "drifted"))
123
+ else:
124
+ results.append(SyncResult(target.name, path, "ok"))
125
+
126
+ return clean, results
127
+
128
+
129
+ def resolve_targets(selected: list[str] | None) -> list[Target]:
130
+ if not selected:
131
+ return list(TARGETS.values())
132
+ unknown = [s for s in selected if s not in TARGETS]
133
+ if unknown:
134
+ raise ValueError(
135
+ f"Unknown target(s): {', '.join(unknown)}. Available: {', '.join(sorted(TARGETS))}"
136
+ )
137
+ # preserve user order, dedupe
138
+ seen: set[str] = set()
139
+ out = []
140
+ for s in selected:
141
+ if s not in seen:
142
+ seen.add(s)
143
+ out.append(TARGETS[s])
144
+ return out
145
+
146
+
147
+ def import_source(root: Path) -> tuple[str, Path]:
148
+ """Find an existing rules file and return (body, path) to seed AGENTS.md."""
149
+ for name in IMPORT_SOURCES:
150
+ p = root / name
151
+ if p.is_file():
152
+ text = p.read_text(encoding="utf-8", errors="replace")
153
+ return strip_generated_marker(text), p
154
+ raise FileNotFoundError(
155
+ "No importable rules file found. Looked for: " + ", ".join(IMPORT_SOURCES)
156
+ )
157
+
158
+
159
+ def init_template() -> str:
160
+ return """# Coding Agent Rules
161
+
162
+ <!-- Edit this file. Run `agentsbridge sync` to propagate changes to
163
+ CLAUDE.md, .github/copilot-instructions.md, Cursor, Windsurf, Cline,
164
+ Aider, and more. Tools that read AGENTS.md natively (Codex CLI,
165
+ Gemini CLI, Jules, Amp, Zed) need no bridging. -->
166
+
167
+ ## General
168
+
169
+ - Write clear, idiomatic code; prefer simplicity over cleverness.
170
+ - Keep functions small and focused; extract helpers when logic repeats.
171
+
172
+ ## Testing
173
+
174
+ - Add or update tests for every behavior change.
175
+ - Run the test suite before committing.
176
+
177
+ ## Git
178
+
179
+ - Commit messages: imperative mood, under 72 characters.
180
+ - One logical change per commit.
181
+ """
182
+
183
+
184
+ def ensure_agents_md(root: Path, template: str | None = None, dry_run: bool = False) -> SyncResult:
185
+ path = root / SOURCE_FILENAME
186
+ if path.exists():
187
+ return SyncResult("agents", path, "unchanged", "AGENTS.md already exists")
188
+ content = template if template is not None else init_template()
189
+ if not dry_run:
190
+ path.write_text(content, encoding="utf-8")
191
+ return SyncResult("agents", path, "created")
192
+
193
+
194
+ def walk_up(start: Path) -> Path:
195
+ """Walk up from start to find the nearest directory containing AGENTS.md."""
196
+ cur = start.resolve()
197
+ for p in [cur, *cur.parents]:
198
+ if (p / SOURCE_FILENAME).is_file():
199
+ return p
200
+ return start.resolve()
201
+
202
+
203
+ def is_quiet() -> bool:
204
+ return os.environ.get("AGENTSBRIDGE_QUIET") == "1"
@@ -0,0 +1,117 @@
1
+ """Target definitions: how AGENTS.md content maps to each tool's rules file.
2
+
3
+ A target is a callable-free dataclass describing:
4
+ - name: identifier used on the CLI (--targets claude,copilot)
5
+ - path: file path relative to the project root
6
+ - style: how the body should be rendered (raw markdown, mdc frontmatter, ...)
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass, field
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class Target:
16
+ name: str
17
+ path: str
18
+ style: str = "markdown" # markdown | mdc
19
+ description: str = ""
20
+ # Some tools nest rules in subdirectories; record parent dirs to create.
21
+ extra_dirs: tuple[str, ...] = field(default_factory=tuple)
22
+
23
+
24
+ TARGETS: dict[str, Target] = {
25
+ t.name: t
26
+ for t in [
27
+ Target(
28
+ name="claude",
29
+ path="CLAUDE.md",
30
+ style="markdown",
31
+ description="Claude Code (project memory)",
32
+ ),
33
+ Target(
34
+ name="copilot",
35
+ path=".github/copilot-instructions.md",
36
+ style="markdown",
37
+ description="GitHub Copilot",
38
+ ),
39
+ Target(
40
+ name="cursor",
41
+ path=".cursor/rules/agentsbridge.mdc",
42
+ style="mdc",
43
+ description="Cursor (rules dir)",
44
+ ),
45
+ Target(
46
+ name="windsurf",
47
+ path=".windsurfrules",
48
+ style="markdown",
49
+ description="Windsurf",
50
+ ),
51
+ Target(
52
+ name="cline",
53
+ path=".clinerules",
54
+ style="markdown",
55
+ description="Cline",
56
+ ),
57
+ Target(
58
+ name="aider",
59
+ path="CONVENTIONS.md",
60
+ style="markdown",
61
+ description="Aider",
62
+ ),
63
+ ]
64
+ }
65
+
66
+ # Tools that read AGENTS.md natively - no bridging needed, documented only.
67
+ NATIVE_TOOLS = [
68
+ "Codex CLI",
69
+ "Gemini CLI",
70
+ "Jules",
71
+ "Amp",
72
+ "Zed",
73
+ "Cursor (newer versions)",
74
+ "Factory Droids",
75
+ "opencode",
76
+ ]
77
+
78
+ GENERATED_MARKER = "<!-- generated by agentsbridge; do not edit -->"
79
+
80
+
81
+ def render(target: Target, body: str) -> str:
82
+ """Render the AGENTS.md body for a specific target style."""
83
+ if target.style == "mdc":
84
+ return _render_mdc(body)
85
+ return _render_markdown(body)
86
+
87
+
88
+ def _render_markdown(body: str) -> str:
89
+ lines = [
90
+ GENERATED_MARKER,
91
+ "",
92
+ "> Generated from `AGENTS.md` by [agentsbridge](https://github.com/DavidStarYu/agentsbridge).",
93
+ "> Run `agentsbridge sync` after editing `AGENTS.md`. Do not edit this file directly.",
94
+ "",
95
+ body.rstrip(),
96
+ "",
97
+ ]
98
+ return "\n".join(lines)
99
+
100
+
101
+ def _render_mdc(body: str) -> str:
102
+ lines = [
103
+ "---",
104
+ "description: Coding agent rules generated from AGENTS.md by agentsbridge",
105
+ "globs: **/*",
106
+ "alwaysApply: true",
107
+ "---",
108
+ "",
109
+ GENERATED_MARKER,
110
+ "",
111
+ "> Generated from `AGENTS.md` by [agentsbridge](https://github.com/DavidStarYu/agentsbridge).",
112
+ "> Run `agentsbridge sync` after editing `AGENTS.md`. Do not edit this file directly.",
113
+ "",
114
+ body.rstrip(),
115
+ "",
116
+ ]
117
+ return "\n".join(lines)
@@ -0,0 +1,194 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentsbridge
3
+ Version: 0.1.0
4
+ Summary: Bridge AGENTS.md to every AI coding tool's rules file - one source of truth for Claude Code, Copilot, Cursor, Windsurf, Cline, and Aider.
5
+ Author: agentsbridge contributors
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/DavidStarYu/agentsbridge
8
+ Project-URL: Repository, https://github.com/DavidStarYu/agentsbridge
9
+ Project-URL: Changelog, https://github.com/DavidStarYu/agentsbridge/blob/main/CHANGELOG.md
10
+ Keywords: agents-md,claude,claude-code,cursor,copilot,windsurf,cline,aider,ai,rules,cli,developer-tools
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development
23
+ Classifier: Topic :: Utilities
24
+ Requires-Python: >=3.9
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Dynamic: license-file
28
+
29
+ <div align="center">
30
+
31
+ <img src="assets/banner.svg" alt="agentsbridge" width="640">
32
+
33
+ **One `AGENTS.md`. Every AI coding tool. Zero drift.**
34
+
35
+ [![CI](https://github.com/DavidStarYu/agentsbridge/actions/workflows/ci.yml/badge.svg)](https://github.com/DavidStarYu/agentsbridge/actions/workflows/ci.yml)
36
+ [![PyPI](https://img.shields.io/pypi/v/agentsbridge)](https://pypi.org/project/agentsbridge/)
37
+ [![Python](https://img.shields.io/pypi/pyversions/agentsbridge)](https://pypi.org/project/agentsbridge/)
38
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
39
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
40
+
41
+ [Bridge](#why) · [Quick start](#quick-start) · [Supported tools](#supported-tools) · [CI drift check](#ci-drift-check) · [FAQ](#faq)
42
+
43
+ </div>
44
+
45
+ ---
46
+
47
+ ## Why
48
+
49
+ Your team uses Claude Code. Your teammate uses Cursor. Someone just switched
50
+ to Codex CLI. And suddenly your coding standards live in **five different
51
+ files** that silently drift apart:
52
+
53
+ ```text
54
+ AGENTS.md ← Codex, Gemini CLI, Jules, Amp, Zed read this
55
+ CLAUDE.md ← Claude Code reads this (not AGENTS.md)
56
+ .github/copilot-instructions.md ← GitHub Copilot reads this (not AGENTS.md)
57
+ .cursor/rules/*.mdc ← Cursor reads this (not AGENTS.md, older versions)
58
+ .windsurfrules / .clinerules ← Windsurf / Cline read these
59
+ CONVENTIONS.md ← Aider reads this
60
+ ```
61
+
62
+ `agentsbridge` fixes this with one command. You maintain **one** `AGENTS.md`
63
+ (the emerging industry standard) — it generates and keeps every tool's file
64
+ in sync. Edit once, sync everywhere, and let CI catch anything that drifts.
65
+
66
+ ## Quick start
67
+
68
+ ```bash
69
+ pipx install agentsbridge # or: pip install agentsbridge / uv tool install agentsbridge
70
+ ```
71
+
72
+ **Already have rules somewhere?** Import them:
73
+
74
+ ```bash
75
+ agentsbridge import # seeds AGENTS.md from CLAUDE.md, .cursorrules, etc.
76
+ ```
77
+
78
+ **Starting fresh?**
79
+
80
+ ```bash
81
+ agentsbridge init # creates a starter AGENTS.md
82
+ ```
83
+
84
+ Then — the only command you'll ever need again:
85
+
86
+ ```bash
87
+ agentsbridge sync
88
+ ```
89
+
90
+ ```text
91
+ + created CLAUDE.md
92
+ + created .github/copilot-instructions.md
93
+ + created .cursor/rules/agentsbridge.mdc
94
+ + created .windsurfrules
95
+ + created .clinerules
96
+ + created CONVENTIONS.md
97
+
98
+ wrote 6 file(s)
99
+ ```
100
+
101
+ Edit `AGENTS.md`, run `agentsbridge sync`, commit. That's the whole workflow.
102
+
103
+ ## Supported tools
104
+
105
+ | Tool | File generated | Notes |
106
+ |---|---|---|
107
+ | **Claude Code** | `CLAUDE.md` | |
108
+ | **GitHub Copilot** | `.github/copilot-instructions.md` | |
109
+ | **Cursor** | `.cursor/rules/agentsbridge.mdc` | with `alwaysApply` frontmatter |
110
+ | **Windsurf** | `.windsurfrules` | |
111
+ | **Cline** | `.clinerules` | |
112
+ | **Aider** | `CONVENTIONS.md` | |
113
+ | **Codex CLI, Gemini CLI, Jules, Amp, Zed, opencode** | — | read `AGENTS.md` natively, nothing to generate |
114
+
115
+ Missing a tool? [Open an issue](https://github.com/DavidStarYu/agentsbridge/issues) —
116
+ adding a target is ~10 lines.
117
+
118
+ ## Safety
119
+
120
+ Generated files are marked:
121
+
122
+ ```markdown
123
+ <!-- generated by agentsbridge; do not edit -->
124
+ ```
125
+
126
+ If a target file already exists **without** that marker (i.e. you wrote it by
127
+ hand), `sync` skips it instead of clobbering your work. Use `--force` to
128
+ adopt it deliberately.
129
+
130
+ ```bash
131
+ agentsbridge sync --force # adopt existing files
132
+ agentsbridge sync --dry-run # preview changes
133
+ agentsbridge sync -t claude,copilot # subset of targets
134
+ ```
135
+
136
+ ## CI drift check
137
+
138
+ The whole point: rules that drift are rules nobody follows. Add the check to
139
+ your workflow and CI fails whenever someone edits a generated file — or
140
+ forgets to re-sync after editing `AGENTS.md`:
141
+
142
+ ```yaml
143
+ # .github/workflows/ci.yml
144
+ name: rules
145
+ on: [push, pull_request]
146
+ jobs:
147
+ agentsbridge:
148
+ runs-on: ubuntu-latest
149
+ steps:
150
+ - uses: actions/checkout@v4
151
+ - uses: DavidStarYu/agentsbridge/action@main
152
+ ```
153
+
154
+ Or invoke the CLI directly: `agentsbridge check` (exit code 1 on drift).
155
+
156
+ This repository runs the same check on itself — [dogfooding](.github/workflows/rules.yml).
157
+
158
+ ## FAQ
159
+
160
+ **Why AGENTS.md as the source, not my own config?**
161
+ AGENTS.md is the emerging standard (adopted by OpenAI Codex, Gemini CLI, Jules,
162
+ Amp, Zed and others — see [agents.md](https://agents.md)). Tools that don't
163
+ read it yet are exactly the ones this bridge targets. You keep zero
164
+ agentsbridge-specific config: the source file is a standard.
165
+
166
+ **Cursor already reads AGENTS.md, why generate an .mdc?**
167
+ Newer Cursor versions read `AGENTS.md` directly; many teams run versions that
168
+ don't, or want explicit rule scoping. The generated `.mdc` is harmless if
169
+ redundant — and you can exclude it: `agentsbridge sync -t claude,copilot,...`.
170
+
171
+ **Does it send my code anywhere?**
172
+ No. No network calls, no telemetry, no API keys. It's a template engine over
173
+ one markdown file. [Zero dependencies](pyproject.toml).
174
+
175
+ **How is this different from X?**
176
+ rulesync (Node.js) covers more features — MCP servers, subagents, commands —
177
+ at the cost of a bigger footprint and a `.rulesync/` directory convention.
178
+ agentsbridge does one thing: keep rule *files* in sync, in pure Python with
179
+ zero dependencies. If you live in `pipx`/`uv` and want the simple thing, this
180
+ is for you.
181
+
182
+ ## Development
183
+
184
+ ```bash
185
+ git clone https://github.com/DavidStarYu/agentsbridge
186
+ cd agentsbridge
187
+ pip install -e . pytest ruff
188
+ pytest # 36 tests
189
+ ruff check .
190
+ ```
191
+
192
+ ## License
193
+
194
+ [MIT](LICENSE)
@@ -0,0 +1,10 @@
1
+ agentsbridge/__init__.py,sha256=Xkjv3FNyx666xFTvwEPOhJHhHlis0Krzlc4h_ggN7oM,99
2
+ agentsbridge/cli.py,sha256=O6UTKHWGFtwFgxmwkyV8eNH3knFv68y8KeLyGxUCFxU,5341
3
+ agentsbridge/core.py,sha256=t_UG2814vP2x6IZOF6ATU_by_X3nMuxdvSkcwwVZJKw,6235
4
+ agentsbridge/targets.py,sha256=PnYwSjz7FNA7iKRp1AGhQ508puJ9Pi12_VwFP0ALB8g,3172
5
+ agentsbridge-0.1.0.dist-info/licenses/LICENSE,sha256=iRxJZoPltXN8yg2qdGpddFfFkyoijlDgaCy_Q3B3pqg,1082
6
+ agentsbridge-0.1.0.dist-info/METADATA,sha256=rTx2k0sPMFQSH6ZTtE-TATqCMQJErC8e5DKFTNSnmTA,6834
7
+ agentsbridge-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
8
+ agentsbridge-0.1.0.dist-info/entry_points.txt,sha256=eBXAYoARN5l9hW82X2GpYCu-aamwpgVTWDE7x9IiDu0,55
9
+ agentsbridge-0.1.0.dist-info/top_level.txt,sha256=uHtHVPF3WL52X35o7sWaOYHMsiO-C2t-xPrqLk1p7k8,13
10
+ agentsbridge-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ agentsbridge = agentsbridge.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 agentsbridge contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ agentsbridge