Skip to content

Sync Claude and Codex skills & subagents#157

Merged
Miyamura80 merged 3 commits intomainfrom
feat/sync-agent-config
Apr 15, 2026
Merged

Sync Claude and Codex skills & subagents#157
Miyamura80 merged 3 commits intomainfrom
feat/sync-agent-config

Conversation

@Miyamura80
Copy link
Copy Markdown
Owner

Summary

Ports the dual-tool sync setup from edison-watch#636, adapted for this repo.

  • Single-source shared skills at .agents/skills/<name>/SKILL.md — Codex auto-discovers them; Claude sees them via symlinks under .claude/skills/.
  • Subagents authored as .claude/agents/*.md; make sync-agent-config regenerates .codex/agents/*.toml with Claude-only frontmatter (tools, model, color) preserved as TOML comments.
  • manage-agent-config meta-skill + .claude/rules/codex-claude-sync.md document the layout and shared-frontmatter rules for future contributors.
  • prek pre-commit hook added to the existing local hooks block in prek.toml.

Nuances relative to edison-watch#636

1. Target name is sync-agent-config (matches the upstream target after its rename; this repo had no conflict so the choice is consistent with the current upstream name).

2. Pre-commit hook uses a content-based --check, not a porcelain compare. scripts/sync_agent_config.py always writes changes but exits non-zero in --check mode when any were made — same pattern as ruff --fix --exit-non-zero-on-fix. make sync-agent-config retains exit-0 semantics for manual invocations.

3. Repo-specific adaptations:

  • Added [tool.ty.src] exclude = ["scripts/sync_agent_config.py"] since the script relies on PEP 723 inline deps.
  • Added tomli-w to project dependencies so deptry is satisfied (the script imports it at runtime).
  • Hook added as an entry in the existing local hooks array in prek.toml rather than a new [[repos]] block, matching this repo's style.
  • .gitignore unchanged.

4. CRLF-safe body extraction. parse_md uses re.sub(r"^(?:\r?\n)+", "", ...) instead of lstrip("\n") so CRLF files don't leave a leading \r\n in the generated TOML (which would cause --check to flap between Unix and Windows developers).

Test plan

  • Clone fresh, run prek install, verify the hook fires on commit.
  • Add a shared skill with a forbidden key (e.g. allowed-tools:) → make sync-agent-config errors with a clear message.
  • Add .claude/agents/foo.mdmake sync-agent-config generates .codex/agents/foo.toml.
  • Delete the .mdmake sync-agent-config prunes the orphan .toml.
  • Hand-edit a generated .toml (simulate drift), commit → hook exits 1 and tells you to re-stage.

🤖 Generated with Claude Code

Ports the sync-agent-config setup so shared skills live at
`.agents/skills/<name>/SKILL.md` with a `.claude/skills/` symlink,
and Claude subagents at `.claude/agents/<name>.md` generate matching
`.codex/agents/<name>.toml`. Adds `make sync-agent-config`, a prek
hook using `--check` mode for drift detection, and a meta-skill +
rule doc explaining the layout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Implement dual-tool Claude/Codex skill and subagent sync

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Implements dual-tool Claude/Codex skill and subagent synchronization
• Shared skills at .agents/skills/ auto-discovered by both tools via symlinks
• Claude subagents (.claude/agents/*.md) generate Codex TOML configs automatically
• Pre-commit hook validates sync state; make sync-agent-config regenerates artifacts
• Adds meta-skill and documentation for contributor guidance on shared layout
Diagram
flowchart LR
  shared[".agents/skills/SKILL.md<br/>(shared source)"]
  claude_link[".claude/skills/<br/>(symlink)"]
  claude_agent[".claude/agents/*.md<br/>(source of truth)"]
  codex_agent[".codex/agents/*.toml<br/>(generated)"]
  script["sync_agent_config.py<br/>(converter)"]
  
  shared -- "symlink" --> claude_link
  claude_agent -- "render_toml()" --> script
  script -- "write" --> codex_agent
  script -- "create" --> claude_link
Loading

Grey Divider

File Changes

1. scripts/sync_agent_config.py ✨ Enhancement +226/-0

Dual-tool skill and subagent synchronization converter

• New 226-line Python script with PEP 723 inline dependencies (pyyaml, tomli_w)
• Validates shared skills for Claude-only features (forbidden keys, $ARGUMENTS, shell preprocessing)
• Creates/maintains symlinks from .claude/skills/ to .agents/skills/
• Regenerates .codex/agents/*.toml from .claude/agents/*.md with CRLF-safe parsing
• Supports --check mode for pre-commit hook drift detection; auto-prunes orphaned files

scripts/sync_agent_config.py


2. .agents/skills/manage-agent-config/SKILL.md 📝 Documentation +51/-0

Meta-skill for managing Claude/Codex skill layout

• New meta-skill documenting the dual-tool layout and decision tree
• Explains when to create shared vs Claude-only skills
• Clarifies subagent source-of-truth workflow and frontmatter rules
• Provides quick reference for renaming/deleting and post-change steps

.agents/skills/manage-agent-config/SKILL.md


3. .claude/rules/codex-claude-sync.md 📝 Documentation +77/-0

Comprehensive sync rules and layout documentation

• New 77-line rule document detailing shared-frontmatter constraints
• Specifies safe keys (name, description, plain markdown) vs Claude-only features
• Documents subagent conversion rules and format differences between tools
• Lists what should not be synced (rules, commands, separate docs)
• Includes tooling reference and contributor checklist

.claude/rules/codex-claude-sync.md


View more (4)
4. .claude/skills/manage-agent-config ⚙️ Configuration changes +1/-0

Symlink to shared manage-agent-config skill

• New symlink pointing to ../../.agents/skills/manage-agent-config
• Makes the meta-skill discoverable by Claude via standard skill symlink pattern

.claude/skills/manage-agent-config


5. Makefile ⚙️ Configuration changes +4/-0

Add sync-agent-config make target

• New sync-agent-config target invoking uv run scripts/sync_agent_config.py
• Provides manual entry point for developers to regenerate artifacts
• Marked as .PHONY with help text describing dual-tool sync purpose

Makefile


6. prek.toml ⚙️ Configuration changes +1/-0

Add pre-commit hook for sync validation

• New pre-commit hook entry in existing local repos block
• Runs sync_agent_config.py --check to detect and block commits with drift
• Fails commit if sync script produces changes, forcing re-staging

prek.toml


7. pyproject.toml Dependencies +4/-0

Add tomli-w dependency and type-check exclusion

• Added tomli-w>=1.0 to project dependencies for TOML generation
• Added [tool.ty.src] section excluding scripts/sync_agent_config.py from type checking
• Accommodates PEP 723 inline dependencies in standalone script

pyproject.toml


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Apr 15, 2026

Code Review by Qodo

🐞 Bugs (1)   📘 Rule violations (1)   📎 Requirement gaps (0)
🐞\ ≡ Correctness (1)
📘\ ✧ Quality (1)

Grey Divider


Action required

1. PEP723 requires-python is 3.11📘 §
Description
The new scripts/sync_agent_config.py declares requires-python = ">=3.11", which conflicts with
the project requirement to target Python 3.12+. This can cause the script to run under an
unsupported interpreter version and violate version-policy consistency.
Code

scripts/sync_agent_config.py[R1-4]

+# /// script
+# requires-python = ">=3.11"
+# dependencies = ["pyyaml>=6.0", "tomli_w>=1.0"]
+# ///
Evidence
PR Compliance ID 315730 requires all Python version declarations to target 3.12+. The script’s
inline metadata explicitly sets a lower bound of 3.11.

Rule 315730: Project must target Python 3.12+
scripts/sync_agent_config.py[1-4]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new PEP 723 inline script metadata declares `requires-python = ">=3.11"`, which conflicts with the repo-wide Python 3.12+ requirement.
## Issue Context
The project targets Python 3.12+; all version declarations must be consistent.
## Fix Focus Areas
- scripts/sync_agent_config.py[1-4]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. manage-agent-config directory not snake_case 📘
Description
A new directory path under .agents/skills/ uses hyphens (manage-agent-config) instead of
snake_case. This violates the repository requirement that directories and identifiers follow
snake_case naming.
Code

.agents/skills/manage-agent-config/SKILL.md[R1-4]

+---
+name: manage-agent-config
+description: Use whenever creating, editing, renaming, or deleting any file under .claude/skills/, .claude/agents/, .agents/skills/, or .codex/agents/. Teaches the dual-tool Claude/Codex layout and reminds to run `make sync-agent-config`.
+---
Evidence
PR Compliance ID 315732 forbids hyphens/uppercase in new directories/files and requires snake_case.
The newly added skill lives under .agents/skills/manage-agent-config/ and its frontmatter name
also uses hyphens.

Rule 315732: Use snake_case for functions, files, and directories
.agents/skills/manage-agent-config/SKILL.md[1-4]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A newly added skill directory uses a hyphenated name (`manage-agent-config`), which violates the snake_case requirement for directories.
## Issue Context
This directory is created in `.agents/skills/` and referenced elsewhere via symlink and docs; renaming must update those references.
## Fix Focus Areas
- .agents/skills/manage-agent-config/SKILL.md[1-4]
- .claude/skills/manage-agent-config[1-1]
- .claude/rules/codex-claude-sync.md[15-21]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Frontmatter type can crash🐞
Description
parse_md() returns yaml.safe_load() output without enforcing it is a mapping, but later code assumes
dict methods (get/keys/items). If any skill/agent frontmatter parses to a truthy non-dict (e.g.,
list/string/int), the sync script will crash with AttributeError and block the pre-commit hook.
Code

scripts/sync_agent_config.py[R48-68]

+def parse_md(path: Path) -> tuple[dict, str]:
+    text = path.read_text()
+    m = FRONTMATTER_RE.match(text)
+    if not m:
+        raise SystemExit(f"{path}: missing YAML frontmatter")
+    meta = yaml.safe_load(m.group(1)) or {}
+    return meta, re.sub(r"^(?:\r?\n)+", "", m.group(2))
+
+
+def render_toml(meta: dict, body: str, source: Path | None = None) -> str:
+    if not meta.get("name"):
+        where = f"{source}: " if source else ""
+        raise SystemExit(f"{where}missing `name` in frontmatter")
+    data = {
+        "name": str(meta["name"]),
+        "description": str(meta.get("description") or ""),
+        "developer_instructions": body.rstrip() + "\n",
+    }
+    out = tomli_w.dumps(data, multiline_strings=True)
+    extras = {k: v for k, v in meta.items() if k in CLAUDE_ONLY_KEYS}
+    if extras:
Evidence
parse_md() returns meta directly from yaml.safe_load(), and downstream code assumes meta is
dict-like (calls .keys() and .items() and .get()), so a non-mapping YAML frontmatter will
raise at runtime and abort the sync run.

scripts/sync_agent_config.py[48-55]
scripts/sync_agent_config.py[57-72]
scripts/sync_agent_config.py[102-125]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`parse_md()` accepts any YAML value as `meta`, but the rest of the script assumes `meta` is a dict. If YAML frontmatter is malformed (e.g., a list), the script crashes with `AttributeError`, which is especially disruptive because it runs in pre-commit.
### Issue Context
This affects both shared skills validation and agent TOML generation.
### Fix Focus Areas
- scripts/sync_agent_config.py[48-55]
- scripts/sync_agent_config.py[57-72]
- scripts/sync_agent_config.py[102-125]
### Suggested change
After `yaml.safe_load(...)`, enforce `isinstance(meta, dict)` and error with a clear message (include file path and actual type). Optionally also validate keys are strings to avoid surprising TOML comments/metadata handling.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
4. Symlink errors block commits🐞
Description
_materialize_symlink() calls Path.symlink_to() without handling failures, so environments that
cannot create symlinks will hard-fail. Because prek is configured to always run this script, those
developers will be unable to commit at all.
Code

scripts/sync_agent_config.py[R138-151]

+def _materialize_symlink(name: str) -> str | None:
+    link = CLAUDE_SKILLS / name
+    target = Path("..") / ".." / ".agents" / "skills" / name
+    if link.is_symlink():
+        if os.path.normpath(os.readlink(link)) == os.path.normpath(str(target)):
+            return None
+        link.unlink()
+    elif link.exists():
+        raise SystemExit(
+            f"ERROR: name collision - .claude/skills/{name} is a real directory (Claude-only skill) "
+            f"but .agents/skills/{name} also exists (shared skill). Resolve by removing one of them."
+        )
+    link.symlink_to(target)
+    return f"symlinked {link.relative_to(REPO)}"
Evidence
The script unconditionally creates symlinks via link.symlink_to(target) with no fallback or error
handling. The prek hook runs this script on every commit (always_run = true), so a symlink failure
aborts the hook and blocks committing.

scripts/sync_agent_config.py[138-151]
prek.toml[8-19]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Creating `.claude/skills/<name>` symlinks can fail (e.g., Windows without symlink privileges, restricted FS). Today this raises and aborts the sync, which then blocks commits due to the always-run pre-commit hook.
### Issue Context
The repo intends to be dual-tool and mentions cross-platform concerns (CRLF), so a hard failure here is workflow-breaking for some environments.
### Fix Focus Areas
- scripts/sync_agent_config.py[138-151]
- scripts/sync_agent_config.py[154-179]
- prek.toml[8-19]
### Suggested change
Wrap `link.symlink_to(target)` in a try/except catching `OSError`/`NotImplementedError` and exit with a targeted error message explaining how to enable symlinks. Optionally add a flag/env var to skip symlink management (or implement a fallback like copying/creating a small marker file) so the rest of the sync can still run.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

5. Shared-skill $10 not flagged 🐞
Description
Shared-skill validation only detects positional substitutions $1..$9, so multi-digit forms like $10
are not caught. This undermines the repo’s rule that $1, $2, ... substitutions are forbidden in
shared skills.
Code

scripts/sync_agent_config.py[R37-42]

+SHARED_SKILL_FORBIDDEN_BODY_PATTERNS = [
+    (re.compile(r"\$ARGUMENTS\b"), "$ARGUMENTS substitution"),
+    (re.compile(r"\$[1-9]\b"), "positional arg substitution ($1, $2, ...)"),
+    (re.compile(r"\$\{CLAUDE_[A-Z_]+\}"), "${CLAUDE_*} interpolation"),
+    (re.compile(r"!`[^`]+`"), "!`cmd` shell preprocessing"),
+]
Evidence
The validator uses regex \$[1-9]\b, which cannot match $10/$11 etc., while the repo rule
explicitly forbids $1, $2, ... substitutions in shared skills (ellipsis implies more than just
1–9).

scripts/sync_agent_config.py[37-42]
.claude/rules/codex-claude-sync.md[34-39]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The shared-skill validator intends to block positional substitutions like `$1`, `$2`, ... but currently only matches single-digit forms ($1–$9). Multi-digit forms (e.g., `$10`) are missed.
### Issue Context
This is a validation correctness gap; the docs forbid `$1`, `$2`, ... in shared skills.
### Fix Focus Areas
- scripts/sync_agent_config.py[37-42]
### Suggested change
Update the regex to match multi-digit positional args, e.g. `re.compile(r"\$(?:[1-9]\d*)\b")`, and keep the existing label/message.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment thread scripts/sync_agent_config.py
Comment thread .agents/skills/manage-agent-config/SKILL.md
Comment thread scripts/sync_agent_config.py
Comment thread scripts/sync_agent_config.py
Miyamura80 and others added 2 commits April 15, 2026 20:07
AI writing CI check rejects U+2014.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Bump `requires-python` in PEP 723 metadata from 3.11 to 3.12 so it
  matches the repo-wide Python 3.12 requirement.
- Enforce `isinstance(meta, dict)` after `yaml.safe_load` so malformed
  frontmatter yields a clear error instead of AttributeError blocking
  the pre-commit hook.
- Wrap `Path.symlink_to` in try/except for OSError/NotImplementedError
  so environments without symlink privileges (e.g. Windows without
  Developer Mode) get an actionable message instead of a raw traceback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Miyamura80 Miyamura80 merged commit 12de83a into main Apr 15, 2026
10 checks passed
@github-actions github-actions bot deleted the feat/sync-agent-config branch April 15, 2026 19:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant