refactor: agents and skills move to a client-neutral source that renders per client
`.claude/agents/` was the source of truth, which made every role Claude-Code shaped. Adding a second client meant rewriting each role in that client's syntax and maintaining both copies — the drift this scaffold exists to prevent, one layer up. Roles and skills now live under `.agents/` and render into each registered client. `.claude/agents/`, `.claude/skills/` and `.codex/agents/` are generated; `scripts/sync-agent-integrations.py --check` fails on drift and belongs in CI. The role metadata is portable rather than vendor-named: `reasoning_tier` (deep/balanced/fast/vision), `capabilities`, `mutation`, `invocation`, and an optional `preload_skills`. A client manifest maps those to native syntax and must declare what it cannot express — `codex.yaml` declares `tier_policy: unsupported` and its adapters say so in the file, rather than the tier silently evaporating and leaving the repository to believe it was enforced. The port is behaviour-preserving where it should be and a fix where it should not. Every instruction body is byte-identical — the whole diff to `.claude/agents/` is 18 added lines and zero deletions. What changed is frontmatter that was missing: - four agents (`code-reviewer`, `tdd-guardian`, `dependency-audit`, `pr-creator`) declared no `tools:` and therefore inherited the ENTIRE tool pool, so three review-only agents could edit and write the code they were reviewing. All eight now declare capabilities explicitly. - the six read-only roles gain a non-editing permission mode, so the constraint is enforced by the client rather than by the prompt asking nicely. - `mutation` is now explicit, which records the two roles that genuinely need to write: `pr-creator` (external-write — it pushes a branch and opens a PR) and `dependency-audit` (workspace-write — package managers rewrite lockfiles). `pr-creator` keeps `shell` because opening a PR needs it, but it is now the only agent here with a write mutation and a declared reason for it, instead of one of four with unlimited access by omission.
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
# Agent integration sources
|
||||
|
||||
This directory is the client-neutral source of truth for agent behaviour. No
|
||||
agent client owns the roles, the skills, or the capability policy. Client
|
||||
directories (`.claude/`, `.codex/`) contain only the native files their runtimes
|
||||
require, and those files are **generated**.
|
||||
|
||||
## Layout
|
||||
|
||||
| Path | Purpose |
|
||||
|---|---|
|
||||
| `roles/*.md` | Portable role instructions plus reasoning, capability, mutation and reachability metadata. |
|
||||
| `skills/**` | Agent Skills-standard content, mirrored verbatim into each client that declares a skills path. |
|
||||
| `clients/*.yaml` | Declarative mappings from portable metadata to one client's native syntax. |
|
||||
|
||||
Regenerate and verify every registered client:
|
||||
|
||||
```bash
|
||||
python3 scripts/sync-agent-integrations.py
|
||||
python3 scripts/sync-agent-integrations.py --check
|
||||
```
|
||||
|
||||
`--check` exits non-zero when a generated adapter has drifted, so it belongs in
|
||||
CI. Generated files name their source and must never be edited directly — the
|
||||
next sync reverts the edit.
|
||||
|
||||
To add a client, add a manifest under `clients/`. A client using an existing
|
||||
native format needs no new role content; a genuinely new format adds one renderer
|
||||
to the sync script.
|
||||
|
||||
## Portable metadata
|
||||
|
||||
**`reasoning_tier`** — `deep`, `balanced`, `fast`, `vision`, `inherit`. Describes
|
||||
intent, never a vendor's model name; each client maps it. Assign by rule rather
|
||||
than taste:
|
||||
|
||||
- work whose output gates something — a merge, a release, a deployment — runs
|
||||
**`deep`** (or `vision`, which maps to the same frontier model and additionally
|
||||
asserts the client can read images);
|
||||
- roles applying a written standard carefully run **`balanced`**;
|
||||
- mechanical transformation with no judgment to get wrong runs **`fast`**;
|
||||
- prefer not to use **`inherit`**: it resolves to whatever model invoked the role,
|
||||
so the depth of a verdict comes to depend on its caller.
|
||||
|
||||
**`capabilities`** — `read`, `search`, `list`, `shell`, `write`. Mapped to each
|
||||
client's tool names. Declaring them is not optional: before this layer existed,
|
||||
four agents here declared no tools and therefore inherited the entire pool,
|
||||
including the ability to edit files they were only meant to review.
|
||||
|
||||
**`mutation`** — `read-only`, `workspace-write`, `external-write`. Adapters must
|
||||
fail safe when translating it. `read-only` roles are additionally pinned to a
|
||||
non-editing permission mode where the client has one.
|
||||
|
||||
**`invocation`** — `manual`, `hook`, `plan-time`, or `matrix` where a repository
|
||||
routes reviewers by changed path. Says how a role is actually reached, so a role
|
||||
nobody can invoke is visible as a defect rather than looking like coverage.
|
||||
|
||||
**`preload_skills`** *(optional)* — skills the role should launch with already in
|
||||
context, for a reviewer that grades against a standard a skill defines. Mapped per
|
||||
client: a manifest declaring `preload_support: false` cannot honour it, so the
|
||||
generated adapter names the skills and states the standard is **not** in context.
|
||||
A declaration that silently evaporates between this layer and the client is worse
|
||||
than one never made, because the repository goes on believing it took effect.
|
||||
|
||||
## Where a rule belongs
|
||||
|
||||
A rule belongs at the highest layer that can hold it:
|
||||
|
||||
1. formatters, linters and the test suite enforce anything machine-checkable;
|
||||
2. `CLAUDE.md` holds always-on rules;
|
||||
3. `skills/` hold focused procedures that apply those rules;
|
||||
4. `roles/` supply the independent judgment tools cannot provide.
|
||||
|
||||
A role that restates what a linter already enforces adds tokens and no coverage.
|
||||
Reference in New Issue
Block a user