rolesync 1.0.0rc1__tar.gz
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.
- rolesync-1.0.0rc1/LICENSE +21 -0
- rolesync-1.0.0rc1/PKG-INFO +141 -0
- rolesync-1.0.0rc1/README.md +118 -0
- rolesync-1.0.0rc1/pyproject.toml +43 -0
- rolesync-1.0.0rc1/setup.cfg +4 -0
- rolesync-1.0.0rc1/src/rolesync/__init__.py +4 -0
- rolesync-1.0.0rc1/src/rolesync/__main__.py +3 -0
- rolesync-1.0.0rc1/src/rolesync/cli.py +211 -0
- rolesync-1.0.0rc1/src/rolesync/core.py +546 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/AGENTS.md +7 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/CLAUDE.md +1 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/catalog.json +342 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/common.md +1 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/policy.json +55 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/references/SOURCES.md +36 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/references/evaluation.md +33 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/references/git-pr-lifecycle.md +52 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/references/handoff-contract.md +70 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/references/routing-and-budgets.md +56 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/references/runtime-and-security.md +53 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/roles/advanced-coder.md +9 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/roles/architect.md +9 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/roles/ci-investigator.md +9 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/roles/deep-rescue.md +9 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/roles/documentation-updater.md +9 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/roles/general-coder.md +9 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/roles/issue-filer.md +9 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/roles/orchestrator.md +15 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/roles/pr-manager.md +9 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/roles/reader.md +7 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/roles/reviewer.md +11 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/roles/test-maintainer.md +9 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/skills/example-architecture/SKILL.md +12 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/skills/example-ci/SKILL.md +12 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/skills/example-context/SKILL.md +12 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/skills/example-document/SKILL.md +12 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/skills/example-escalate/SKILL.md +14 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/skills/example-implement/SKILL.md +14 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/skills/example-issue/SKILL.md +14 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/skills/example-orchestrate/SKILL.md +20 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/skills/example-pr/SKILL.md +16 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/skills/example-review/SKILL.md +23 -0
- rolesync-1.0.0rc1/src/rolesync/presets/github-workflow/skills/example-test/SKILL.md +14 -0
- rolesync-1.0.0rc1/src/rolesync/presets/minimal/AGENTS.md +5 -0
- rolesync-1.0.0rc1/src/rolesync/presets/minimal/CLAUDE.md +1 -0
- rolesync-1.0.0rc1/src/rolesync/presets/minimal/catalog.json +34 -0
- rolesync-1.0.0rc1/src/rolesync/presets/minimal/common.md +1 -0
- rolesync-1.0.0rc1/src/rolesync/presets/minimal/policy.json +6 -0
- rolesync-1.0.0rc1/src/rolesync/presets/minimal/roles/coder.md +5 -0
- rolesync-1.0.0rc1/src/rolesync/presets/minimal/roles/orchestrator.md +5 -0
- rolesync-1.0.0rc1/src/rolesync/presets/minimal/roles/reviewer.md +5 -0
- rolesync-1.0.0rc1/src/rolesync/presets/minimal/skills/loom-implement/SKILL.md +8 -0
- rolesync-1.0.0rc1/src/rolesync/presets/minimal/skills/loom-orchestrate/SKILL.md +8 -0
- rolesync-1.0.0rc1/src/rolesync/presets/minimal/skills/loom-review/SKILL.md +8 -0
- rolesync-1.0.0rc1/src/rolesync.egg-info/PKG-INFO +141 -0
- rolesync-1.0.0rc1/src/rolesync.egg-info/SOURCES.txt +59 -0
- rolesync-1.0.0rc1/src/rolesync.egg-info/dependency_links.txt +1 -0
- rolesync-1.0.0rc1/src/rolesync.egg-info/entry_points.txt +2 -0
- rolesync-1.0.0rc1/src/rolesync.egg-info/top_level.txt +1 -0
- rolesync-1.0.0rc1/tests/test_cli.py +81 -0
- rolesync-1.0.0rc1/tests/test_core.py +188 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Snarebox LLC
|
|
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,141 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rolesync
|
|
3
|
+
Version: 1.0.0rc1
|
|
4
|
+
Summary: Generate native Claude Code and Codex agent definitions from one canonical catalog.
|
|
5
|
+
Author: Brian Grubba
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ceponatia/rolesync
|
|
8
|
+
Project-URL: Repository, https://github.com/ceponatia/rolesync
|
|
9
|
+
Project-URL: Issues, https://github.com/ceponatia/rolesync/issues
|
|
10
|
+
Keywords: ai,agents,claude,codex,developer-tools
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# RoleSync
|
|
25
|
+
|
|
26
|
+
RoleSync keeps one canonical set of agent roles and skills under `.agents/` and renders native definitions for both Claude Code and Codex. Generated files are tracked by checksum so accidental hand-edits are detected rather than silently overwritten.
|
|
27
|
+
|
|
28
|
+
The project is intentionally a generator, not an agent runtime: it does not run a scheduler, broker credentials, enforce GitHub permissions, or provide durable background orchestration.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
Python 3.11+ is required. Claude Code and Codex are optional runtime dependencies and are installed separately.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Recommended isolated CLI install after the package is published
|
|
36
|
+
uv tool install rolesync
|
|
37
|
+
|
|
38
|
+
# Alternative
|
|
39
|
+
pipx install rolesync
|
|
40
|
+
|
|
41
|
+
# One-off execution
|
|
42
|
+
uvx rolesync --version
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Until a PyPI release exists, install from a tagged/reviewed source checkout instead of assuming the registry name is live.
|
|
46
|
+
|
|
47
|
+
## Quick start
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
rolesync init . --preset minimal --platform both
|
|
51
|
+
rolesync check
|
|
52
|
+
rolesync doctor
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`init` refuses to replace an existing `.agents/` directory. Add `--install-root-guidance` if you want RoleSync to append a small managed block to root `AGENTS.md` and, for Claude projects, `CLAUDE.md`. Existing files are appended to rather than replaced.
|
|
56
|
+
|
|
57
|
+
Built-in presets:
|
|
58
|
+
|
|
59
|
+
- `minimal` — three generic roles (orchestrator, coder, reviewer) with no mandatory GitHub planning workflow.
|
|
60
|
+
- `github-workflow` — the richer example catalog shipped by this repository for issue → implementation → review → PR work. It is an opinionated starting point, not a universal policy.
|
|
61
|
+
|
|
62
|
+
Choose `--platform claude`, `--platform codex`, or `--platform both`. The selection is stored in `.agents/loom.json`.
|
|
63
|
+
|
|
64
|
+
## Commands
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
rolesync init [ROOT] [--preset minimal|github-workflow] [--platform both|claude|codex]
|
|
68
|
+
rolesync sync [--root ROOT]
|
|
69
|
+
rolesync check [--root ROOT]
|
|
70
|
+
rolesync doctor [--root ROOT]
|
|
71
|
+
rolesync --version
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- `init` installs user-owned canonical sources from a built-in preset, then renders native output.
|
|
75
|
+
- `sync` validates the catalog and reconciles generated files.
|
|
76
|
+
- `check` performs the same validation/drift calculation without writing.
|
|
77
|
+
- `doctor` validates configuration and reports whether the selected Claude/Codex CLIs are currently on `PATH`; it does not launch paid/provider sessions.
|
|
78
|
+
|
|
79
|
+
The legacy source-tree command remains available:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
python scripts/sync_agents.py
|
|
83
|
+
python scripts/sync_agents.py --check
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Canonical project layout
|
|
87
|
+
|
|
88
|
+
```text
|
|
89
|
+
.agents/
|
|
90
|
+
catalog.json
|
|
91
|
+
common.md
|
|
92
|
+
loom.json optional RoleSync project settings
|
|
93
|
+
policy.json optional workflow policy; informational to the generator
|
|
94
|
+
roles/<role>.md
|
|
95
|
+
skills/<skill>/SKILL.md
|
|
96
|
+
references/*.md optional supporting guidance
|
|
97
|
+
generated-manifest.json generated ownership/checksum record
|
|
98
|
+
|
|
99
|
+
.claude/agents/*.md generated when Claude is enabled
|
|
100
|
+
.claude/skills/** generated byte-for-byte mirrors of canonical skill resources
|
|
101
|
+
.codex/agents/*.toml generated when Codex is enabled
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Only paths recorded in the validated generated manifest are owned by RoleSync. Unrelated files under `.claude/` and `.codex/` remain untouched.
|
|
105
|
+
|
|
106
|
+
## Safety and ownership
|
|
107
|
+
|
|
108
|
+
RoleSync treats repository paths and the generated manifest as untrusted input. Managed output paths must stay inside `.claude/agents/`, `.claude/skills/`, or `.codex/agents/`; traversal and escaping symlink paths are rejected. Stale entries receive the same containment validation before deletion.
|
|
109
|
+
|
|
110
|
+
Synchronization is staged and guarded by a cross-process lock. Before applying a multi-file update, RoleSync stores recovery copies and a transaction journal. A later `sync` restores an interrupted transaction before generating new output.
|
|
111
|
+
|
|
112
|
+
Canonical skill directories may contain binary resources; they are mirrored byte-for-byte for Claude. Common transient files are ignored, while likely secret files such as `.env`, private keys, and PKCS#12 bundles are rejected rather than copied. This is a guardrail, not a replacement for repository secret scanning.
|
|
113
|
+
|
|
114
|
+
## Defining agents
|
|
115
|
+
|
|
116
|
+
Each catalog role supplies an internal ID, lowercase-kebab-case generated name, description, role source under `.agents/roles/`, a skill under `.agents/skills/`, and provider-specific native settings. RoleSync validates required types and boundaries but intentionally does not hard-code every provider model or effort value; provider availability changes and must be verified in the installed runtime. Optional roles can set `"enabled": false`; disabled roles remain in canonical sources but are omitted from native discovery output until enabled.
|
|
117
|
+
|
|
118
|
+
`policy.json`, role `activation` notes, and prose permissions do not become enforcement just because they are rendered. Filesystem sandboxing, connector credentials, repository protections, and runtime settings remain the actual security boundaries.
|
|
119
|
+
|
|
120
|
+
## Development and release checks
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
python -m unittest discover -s scripts/tests -v
|
|
124
|
+
python -m unittest discover -s tests -v
|
|
125
|
+
python scripts/sync_agents.py --check
|
|
126
|
+
python -m build
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
CI runs the test suites on Linux, Windows, and macOS using the minimum supported Python and the newest explicitly supported Python. It also installs the built wheel into a clean environment and exercises the CLI outside the source tree.
|
|
130
|
+
|
|
131
|
+
Releases are built by GitHub Actions and the publish workflow is configured for PyPI Trusted Publishing through the protected `pypi` environment. Configure the matching Trusted Publisher on PyPI before publishing the first release.
|
|
132
|
+
|
|
133
|
+
## Example workflow catalog
|
|
134
|
+
|
|
135
|
+
The repository's own `.agents/` catalog remains a worked example of a larger GitHub-centered team. Model routing is a starting hypothesis rather than a benchmark. The generator does not turn this example into a daemon or promise that a runtime will enforce prose-only limits.
|
|
136
|
+
|
|
137
|
+
## License
|
|
138
|
+
|
|
139
|
+
MIT — see [LICENSE](LICENSE).
|
|
140
|
+
|
|
141
|
+
Built by Brian Grubba ([@ceponatia](https://github.com/ceponatia)) at Snarebox LLC.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# RoleSync
|
|
2
|
+
|
|
3
|
+
RoleSync keeps one canonical set of agent roles and skills under `.agents/` and renders native definitions for both Claude Code and Codex. Generated files are tracked by checksum so accidental hand-edits are detected rather than silently overwritten.
|
|
4
|
+
|
|
5
|
+
The project is intentionally a generator, not an agent runtime: it does not run a scheduler, broker credentials, enforce GitHub permissions, or provide durable background orchestration.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
Python 3.11+ is required. Claude Code and Codex are optional runtime dependencies and are installed separately.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Recommended isolated CLI install after the package is published
|
|
13
|
+
uv tool install rolesync
|
|
14
|
+
|
|
15
|
+
# Alternative
|
|
16
|
+
pipx install rolesync
|
|
17
|
+
|
|
18
|
+
# One-off execution
|
|
19
|
+
uvx rolesync --version
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Until a PyPI release exists, install from a tagged/reviewed source checkout instead of assuming the registry name is live.
|
|
23
|
+
|
|
24
|
+
## Quick start
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
rolesync init . --preset minimal --platform both
|
|
28
|
+
rolesync check
|
|
29
|
+
rolesync doctor
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`init` refuses to replace an existing `.agents/` directory. Add `--install-root-guidance` if you want RoleSync to append a small managed block to root `AGENTS.md` and, for Claude projects, `CLAUDE.md`. Existing files are appended to rather than replaced.
|
|
33
|
+
|
|
34
|
+
Built-in presets:
|
|
35
|
+
|
|
36
|
+
- `minimal` — three generic roles (orchestrator, coder, reviewer) with no mandatory GitHub planning workflow.
|
|
37
|
+
- `github-workflow` — the richer example catalog shipped by this repository for issue → implementation → review → PR work. It is an opinionated starting point, not a universal policy.
|
|
38
|
+
|
|
39
|
+
Choose `--platform claude`, `--platform codex`, or `--platform both`. The selection is stored in `.agents/loom.json`.
|
|
40
|
+
|
|
41
|
+
## Commands
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
rolesync init [ROOT] [--preset minimal|github-workflow] [--platform both|claude|codex]
|
|
45
|
+
rolesync sync [--root ROOT]
|
|
46
|
+
rolesync check [--root ROOT]
|
|
47
|
+
rolesync doctor [--root ROOT]
|
|
48
|
+
rolesync --version
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- `init` installs user-owned canonical sources from a built-in preset, then renders native output.
|
|
52
|
+
- `sync` validates the catalog and reconciles generated files.
|
|
53
|
+
- `check` performs the same validation/drift calculation without writing.
|
|
54
|
+
- `doctor` validates configuration and reports whether the selected Claude/Codex CLIs are currently on `PATH`; it does not launch paid/provider sessions.
|
|
55
|
+
|
|
56
|
+
The legacy source-tree command remains available:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
python scripts/sync_agents.py
|
|
60
|
+
python scripts/sync_agents.py --check
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Canonical project layout
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
.agents/
|
|
67
|
+
catalog.json
|
|
68
|
+
common.md
|
|
69
|
+
loom.json optional RoleSync project settings
|
|
70
|
+
policy.json optional workflow policy; informational to the generator
|
|
71
|
+
roles/<role>.md
|
|
72
|
+
skills/<skill>/SKILL.md
|
|
73
|
+
references/*.md optional supporting guidance
|
|
74
|
+
generated-manifest.json generated ownership/checksum record
|
|
75
|
+
|
|
76
|
+
.claude/agents/*.md generated when Claude is enabled
|
|
77
|
+
.claude/skills/** generated byte-for-byte mirrors of canonical skill resources
|
|
78
|
+
.codex/agents/*.toml generated when Codex is enabled
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Only paths recorded in the validated generated manifest are owned by RoleSync. Unrelated files under `.claude/` and `.codex/` remain untouched.
|
|
82
|
+
|
|
83
|
+
## Safety and ownership
|
|
84
|
+
|
|
85
|
+
RoleSync treats repository paths and the generated manifest as untrusted input. Managed output paths must stay inside `.claude/agents/`, `.claude/skills/`, or `.codex/agents/`; traversal and escaping symlink paths are rejected. Stale entries receive the same containment validation before deletion.
|
|
86
|
+
|
|
87
|
+
Synchronization is staged and guarded by a cross-process lock. Before applying a multi-file update, RoleSync stores recovery copies and a transaction journal. A later `sync` restores an interrupted transaction before generating new output.
|
|
88
|
+
|
|
89
|
+
Canonical skill directories may contain binary resources; they are mirrored byte-for-byte for Claude. Common transient files are ignored, while likely secret files such as `.env`, private keys, and PKCS#12 bundles are rejected rather than copied. This is a guardrail, not a replacement for repository secret scanning.
|
|
90
|
+
|
|
91
|
+
## Defining agents
|
|
92
|
+
|
|
93
|
+
Each catalog role supplies an internal ID, lowercase-kebab-case generated name, description, role source under `.agents/roles/`, a skill under `.agents/skills/`, and provider-specific native settings. RoleSync validates required types and boundaries but intentionally does not hard-code every provider model or effort value; provider availability changes and must be verified in the installed runtime. Optional roles can set `"enabled": false`; disabled roles remain in canonical sources but are omitted from native discovery output until enabled.
|
|
94
|
+
|
|
95
|
+
`policy.json`, role `activation` notes, and prose permissions do not become enforcement just because they are rendered. Filesystem sandboxing, connector credentials, repository protections, and runtime settings remain the actual security boundaries.
|
|
96
|
+
|
|
97
|
+
## Development and release checks
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
python -m unittest discover -s scripts/tests -v
|
|
101
|
+
python -m unittest discover -s tests -v
|
|
102
|
+
python scripts/sync_agents.py --check
|
|
103
|
+
python -m build
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
CI runs the test suites on Linux, Windows, and macOS using the minimum supported Python and the newest explicitly supported Python. It also installs the built wheel into a clean environment and exercises the CLI outside the source tree.
|
|
107
|
+
|
|
108
|
+
Releases are built by GitHub Actions and the publish workflow is configured for PyPI Trusted Publishing through the protected `pypi` environment. Configure the matching Trusted Publisher on PyPI before publishing the first release.
|
|
109
|
+
|
|
110
|
+
## Example workflow catalog
|
|
111
|
+
|
|
112
|
+
The repository's own `.agents/` catalog remains a worked example of a larger GitHub-centered team. Model routing is a starting hypothesis rather than a benchmark. The generator does not turn this example into a daemon or promise that a runtime will enforce prose-only limits.
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
MIT — see [LICENSE](LICENSE).
|
|
117
|
+
|
|
118
|
+
Built by Brian Grubba ([@ceponatia](https://github.com/ceponatia)) at Snarebox LLC.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "rolesync"
|
|
7
|
+
version = "1.0.0rc1"
|
|
8
|
+
description = "Generate native Claude Code and Codex agent definitions from one canonical catalog."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{name = "Brian Grubba"}]
|
|
14
|
+
keywords = ["ai", "agents", "claude", "codex", "developer-tools"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Environment :: Console",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Programming Language :: Python :: 3.14",
|
|
23
|
+
"Topic :: Software Development :: Code Generators",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://github.com/ceponatia/rolesync"
|
|
28
|
+
Repository = "https://github.com/ceponatia/rolesync"
|
|
29
|
+
Issues = "https://github.com/ceponatia/rolesync/issues"
|
|
30
|
+
|
|
31
|
+
[project.scripts]
|
|
32
|
+
rolesync = "rolesync.cli:main"
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.packages.find]
|
|
35
|
+
where = ["src"]
|
|
36
|
+
|
|
37
|
+
[tool.setuptools.package-data]
|
|
38
|
+
rolesync = [
|
|
39
|
+
"presets/*/*",
|
|
40
|
+
"presets/*/*/*",
|
|
41
|
+
"presets/*/*/*/*",
|
|
42
|
+
"presets/*/*/*/*/*",
|
|
43
|
+
]
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import shutil
|
|
6
|
+
import sys
|
|
7
|
+
from importlib import metadata, resources
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from .core import RoleSyncError, CONFIG, render, sync, validate_project
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def version() -> str:
|
|
14
|
+
try:
|
|
15
|
+
return metadata.version("rolesync")
|
|
16
|
+
except metadata.PackageNotFoundError:
|
|
17
|
+
return "1.0.0rc1"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _preset_root(name: str):
|
|
21
|
+
base = resources.files("rolesync").joinpath("presets", name)
|
|
22
|
+
if not base.is_dir():
|
|
23
|
+
raise RoleSyncError(f"Unknown preset: {name}")
|
|
24
|
+
return base
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _copy_resource_tree(source, destination: Path) -> None:
|
|
28
|
+
destination.mkdir(parents=True, exist_ok=True)
|
|
29
|
+
for child in source.iterdir():
|
|
30
|
+
target = destination / child.name
|
|
31
|
+
if child.is_dir():
|
|
32
|
+
_copy_resource_tree(child, target)
|
|
33
|
+
else:
|
|
34
|
+
if target.exists():
|
|
35
|
+
raise RoleSyncError(f"Refusing to overwrite existing file during init: {target}")
|
|
36
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
37
|
+
target.write_bytes(child.read_bytes())
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _append_managed_block(path: Path, body: str) -> bool:
|
|
41
|
+
start = "<!-- rolesync:start -->"
|
|
42
|
+
end = "<!-- rolesync:end -->"
|
|
43
|
+
if path.exists():
|
|
44
|
+
text = path.read_text(encoding="utf-8")
|
|
45
|
+
if start in text or end in text:
|
|
46
|
+
raise RoleSyncError(f"{path.name} already contains a rolesync managed block")
|
|
47
|
+
prefix = text.rstrip() + "\n\n" if text.strip() else ""
|
|
48
|
+
else:
|
|
49
|
+
prefix = ""
|
|
50
|
+
path.write_text(prefix + start + "\n" + body.rstrip() + "\n" + end + "\n", encoding="utf-8", newline="\n")
|
|
51
|
+
return True
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _prune_empty_subdirs(path: Path) -> None:
|
|
55
|
+
if not path.is_dir():
|
|
56
|
+
return
|
|
57
|
+
for child in sorted(path.iterdir()):
|
|
58
|
+
if child.is_dir():
|
|
59
|
+
_prune_empty_subdirs(child)
|
|
60
|
+
try:
|
|
61
|
+
child.rmdir()
|
|
62
|
+
except OSError:
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def init_project(root: Path, preset: str, platform: str, install_root_guidance: bool = False) -> None:
|
|
67
|
+
root = root.resolve()
|
|
68
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
69
|
+
agents = root / ".agents"
|
|
70
|
+
if agents.exists():
|
|
71
|
+
raise RoleSyncError(f"Refusing to initialize over existing {agents}; adopt it manually or run sync/check instead")
|
|
72
|
+
source = _preset_root(preset)
|
|
73
|
+
managed_dirs = (root / ".codex" / "agents", root / ".claude" / "agents", root / ".claude" / "skills")
|
|
74
|
+
preexisting_dirs = {d for d in managed_dirs if d.exists()}
|
|
75
|
+
output_paths: list[Path] = []
|
|
76
|
+
preexisting_outputs: set[Path] = set()
|
|
77
|
+
guidance_writes: list[tuple[Path, bytes | None]] = []
|
|
78
|
+
try:
|
|
79
|
+
_copy_resource_tree(source, agents)
|
|
80
|
+
platforms = ["claude", "codex"] if platform == "both" else [platform]
|
|
81
|
+
(root / CONFIG).write_text(
|
|
82
|
+
json.dumps({"schema_version": 1, "preset": preset, "platforms": platforms}, indent=2) + "\n",
|
|
83
|
+
encoding="utf-8",
|
|
84
|
+
newline="\n",
|
|
85
|
+
)
|
|
86
|
+
output_paths = [root / rel for rel in render(root)]
|
|
87
|
+
preexisting_outputs = {p for p in output_paths if p.exists()}
|
|
88
|
+
sync(root)
|
|
89
|
+
if install_root_guidance:
|
|
90
|
+
guidance = [(
|
|
91
|
+
root / "AGENTS.md",
|
|
92
|
+
"Agent definitions are maintained under `.agents/`. Follow `.agents/AGENTS.md` for agent-system maintenance. Do not hand-edit generated files under `.claude/agents/`, `.claude/skills/`, or `.codex/agents/`.",
|
|
93
|
+
)]
|
|
94
|
+
if "claude" in platforms:
|
|
95
|
+
guidance.append((root / "CLAUDE.md", "@AGENTS.md"))
|
|
96
|
+
for path, body in guidance:
|
|
97
|
+
original = path.read_bytes() if path.exists() else None
|
|
98
|
+
_append_managed_block(path, body)
|
|
99
|
+
guidance_writes.append((path, original))
|
|
100
|
+
except BaseException:
|
|
101
|
+
for path, original in guidance_writes:
|
|
102
|
+
if original is None:
|
|
103
|
+
path.unlink(missing_ok=True)
|
|
104
|
+
else:
|
|
105
|
+
path.write_bytes(original)
|
|
106
|
+
if agents.exists():
|
|
107
|
+
shutil.rmtree(agents, ignore_errors=True)
|
|
108
|
+
for path in output_paths:
|
|
109
|
+
if path not in preexisting_outputs and path.exists():
|
|
110
|
+
path.unlink(missing_ok=True)
|
|
111
|
+
for managed_dir in managed_dirs:
|
|
112
|
+
if managed_dir.exists():
|
|
113
|
+
_prune_empty_subdirs(managed_dir)
|
|
114
|
+
if managed_dir not in preexisting_dirs:
|
|
115
|
+
try:
|
|
116
|
+
managed_dir.rmdir()
|
|
117
|
+
except OSError:
|
|
118
|
+
pass
|
|
119
|
+
raise
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _sync_command(root: Path, check: bool) -> int:
|
|
123
|
+
try:
|
|
124
|
+
problems = sync(root, check=check)
|
|
125
|
+
except (OSError, UnicodeError, RoleSyncError, KeyError, TypeError) as exc:
|
|
126
|
+
print(f"rolesync: {exc}", file=sys.stderr)
|
|
127
|
+
return 2
|
|
128
|
+
if check and problems:
|
|
129
|
+
print("Generated files differ:", file=sys.stderr)
|
|
130
|
+
for problem in problems:
|
|
131
|
+
print(problem, file=sys.stderr)
|
|
132
|
+
return 1
|
|
133
|
+
print("Generated files are current." if check else "Native agent files and mirrored skills generated.")
|
|
134
|
+
return 0
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _doctor(root: Path) -> int:
|
|
138
|
+
root = root.resolve()
|
|
139
|
+
issues = validate_project(root)
|
|
140
|
+
if issues:
|
|
141
|
+
print(f"Project: {root}")
|
|
142
|
+
for issue in issues:
|
|
143
|
+
print(f"ERROR: {issue}")
|
|
144
|
+
return 1
|
|
145
|
+
drift = sync(root, check=True)
|
|
146
|
+
config_path = root / CONFIG
|
|
147
|
+
platforms = ["claude", "codex"]
|
|
148
|
+
if config_path.is_file():
|
|
149
|
+
config = json.loads(config_path.read_text(encoding="utf-8"))
|
|
150
|
+
platforms = config.get("platforms", platforms)
|
|
151
|
+
print(f"Project: {root}")
|
|
152
|
+
print("Configuration: valid")
|
|
153
|
+
print("Generated output: " + ("current" if not drift else "drifted"))
|
|
154
|
+
for platform in platforms:
|
|
155
|
+
executable = "claude" if platform == "claude" else "codex"
|
|
156
|
+
found = shutil.which(executable)
|
|
157
|
+
print(f"{platform}: {'found at ' + found if found else 'CLI not found on PATH (generation still works)'}")
|
|
158
|
+
if drift:
|
|
159
|
+
print("Drift:")
|
|
160
|
+
for item in drift:
|
|
161
|
+
print(f" {item}")
|
|
162
|
+
return 1
|
|
163
|
+
return 0
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
167
|
+
parser = argparse.ArgumentParser(prog="rolesync", description="Generate native Claude Code and Codex agent definitions from one canonical catalog.")
|
|
168
|
+
parser.add_argument("--version", action="version", version=f"rolesync {version()}")
|
|
169
|
+
sub = parser.add_subparsers(dest="command")
|
|
170
|
+
|
|
171
|
+
init = sub.add_parser("init", help="Initialize a project from a built-in preset")
|
|
172
|
+
init.add_argument("root", nargs="?", type=Path, default=Path.cwd())
|
|
173
|
+
init.add_argument("--preset", choices=["minimal", "github-workflow"], default="minimal")
|
|
174
|
+
init.add_argument("--platform", choices=["both", "claude", "codex"], default="both")
|
|
175
|
+
init.add_argument("--install-root-guidance", action="store_true", help="Append a small managed guidance block to root AGENTS.md and CLAUDE.md")
|
|
176
|
+
|
|
177
|
+
for name, help_text in (("sync", "Validate and render native agent files"), ("check", "Report drift without writing"), ("doctor", "Validate setup and local runtime availability")):
|
|
178
|
+
cmd = sub.add_parser(name, help=help_text)
|
|
179
|
+
cmd.add_argument("--root", type=Path, default=Path.cwd())
|
|
180
|
+
return parser
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def main(argv: list[str] | None = None) -> int:
|
|
184
|
+
parser = build_parser()
|
|
185
|
+
args = parser.parse_args(argv)
|
|
186
|
+
if not args.command:
|
|
187
|
+
parser.print_help()
|
|
188
|
+
return 0
|
|
189
|
+
try:
|
|
190
|
+
if args.command == "init":
|
|
191
|
+
init_project(args.root, args.preset, args.platform, args.install_root_guidance)
|
|
192
|
+
print(f"Initialized rolesync in {args.root.resolve()}")
|
|
193
|
+
return 0
|
|
194
|
+
if args.command == "sync":
|
|
195
|
+
return _sync_command(args.root, check=False)
|
|
196
|
+
if args.command == "check":
|
|
197
|
+
return _sync_command(args.root, check=True)
|
|
198
|
+
if args.command == "doctor":
|
|
199
|
+
return _doctor(args.root)
|
|
200
|
+
except (OSError, UnicodeError, RoleSyncError, KeyError, TypeError, json.JSONDecodeError) as exc:
|
|
201
|
+
print(f"rolesync: {exc}", file=sys.stderr)
|
|
202
|
+
return 2
|
|
203
|
+
return 2
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def legacy_sync_main(default_root: Path) -> int:
|
|
207
|
+
parser = argparse.ArgumentParser(description="Render native Codex/Claude agents and mirror portable skills.")
|
|
208
|
+
parser.add_argument("--root", type=Path, default=default_root)
|
|
209
|
+
parser.add_argument("--check", action="store_true", help="Report missing/stale/drifted outputs without writing")
|
|
210
|
+
args = parser.parse_args()
|
|
211
|
+
return _sync_command(args.root, check=args.check)
|