diff --git a/plugins/kyberforge/skills/agent-author/README.md b/plugins/kyberforge/skills/agent-author/README.md index a3e80fa..f21bfb0 100644 --- a/plugins/kyberforge/skills/agent-author/README.md +++ b/plugins/kyberforge/skills/agent-author/README.md @@ -4,7 +4,7 @@ Creates and improves agent definition files for Claude Code and GitHub Copilot C ## What it does -Scaffolds and fills in agent definition files (`.md` for Claude Code, `.agent.md` for Copilot CLI) at plugin, project, or user scope. Always generates both provider files in one pass. Also applies improvement signals — grill output, inline feedback, session context — to existing agent files. Bumps the plugin version in both manifests after every change (minor for new agents, patch for improvements). +Scaffolds and fills in agent definition files at plugin/APM, project, or user scope. Project and user scope always generate a Claude Code + Copilot CLI file pair (`.md` + `.agent.md`) in one pass. Plugin/APM scope generates a single vendor-neutral `.apm/agents/.agent.md` file instead — no separate Claude Code / Copilot split, since `apm compile` has no per-target field integrator (see ADR-0016). Also applies improvement signals — grill output, inline feedback, session context — to existing agent files. Bumps the version after every change: the resolved package's `apm.yml` at plugin/APM scope (minor for new agents, patch for improvements); project/user scope has no manifest to bump. ## Before you start @@ -21,7 +21,7 @@ Have ready: the agent's name (kebab-case), the root directory (plugin root, proj bash scripts/new-agent.sh # Examples: -bash scripts/new-agent.sh code-reviewer plugins/kyberforge/ +bash scripts/new-agent.sh code-reviewer packages/my-package/ # plugin/APM scope if packages/my-package/apm.yml has a type: field bash scripts/new-agent.sh deploy-assistant . bash scripts/new-agent.sh security-reviewer ~ ``` @@ -31,12 +31,13 @@ bash scripts/new-agent.sh security-reviewer ~ | File | Purpose | |------|---------| | `SKILL.md` | Skill instructions for agents | -| `scripts/new-agent.sh` | Scaffolds Claude Code and Copilot CLI agent files from templates | -| `references/deployment-modes.md` | Plugin vs project vs user scope: restrictions, scoped identifiers, path conventions | +| `scripts/new-agent.sh` | Scaffolds agent definition file(s) from templates — a single `.apm/agents/.agent.md` at plugin/APM scope, or a Claude Code + Copilot CLI pair at project/user scope | +| `references/deployment-modes.md` | Plugin/APM vs project vs user scope: restrictions, scoped identifiers, path conventions | | `references/scripts.md` | Conventions for new-agent.sh and any future scripts: contract, template variables, file placement, error messages | | `references/sources.md` | Research provenance — sources that informed this skill | -| `assets/templates/claude-code.md` | Annotated Claude Code agent definition template | -| `assets/templates/copilot.agent.md` | Annotated Copilot CLI agent definition template | +| `assets/templates/claude-code.md` | Annotated Claude Code agent definition template (project/user scope) | +| `assets/templates/copilot.agent.md` | Annotated Copilot CLI agent definition template (project/user scope) | +| `assets/templates/apm-agent.md` | Annotated vendor-neutral APM agent definition template (plugin/APM scope) | | `tests/new-agent.bats` | bats tests for `scripts/new-agent.sh` | | `assets/README.md` | Directory meta-documentation for assets/ | | `references/README.md` | Directory meta-documentation for references/ | diff --git a/plugins/kyberforge/skills/agent-author/SKILL.md b/plugins/kyberforge/skills/agent-author/SKILL.md index 96045f1..3ba89ee 100644 --- a/plugins/kyberforge/skills/agent-author/SKILL.md +++ b/plugins/kyberforge/skills/agent-author/SKILL.md @@ -3,14 +3,16 @@ name: agent-author description: > Use when the user wants to create a new agent definition file from scratch ("write an agent for X", "build a subagent that does Y", "create an agent - definition for Z"), or improve an existing one. Handles both Claude Code and - GitHub Copilot CLI agent formats, at plugin, project, and user scope — always - generating both provider files in one pass. Also use when the user provides - inline feedback about an agent's behavior and wants it applied, or when a - grill session has produced findings the user wants acted on — even if they - don't say "improve" explicitly. Do not use for read-only review — examine - agent files manually or run a grill session to generate improvement signals. - Do not use to author skills — use /skill-author instead. + definition for Z"), or improve an existing one. Handles agent definitions at + plugin/APM, project, and user scope. Project and user scope always generate + a Claude Code (`.md`) + Copilot CLI (`.agent.md`) file pair in one pass; + plugin/APM scope generates a single vendor-neutral `.apm/agents/.agent.md` + file instead (no per-target Claude Code / Copilot split). Also use when the + user provides inline feedback about an agent's behavior and wants it applied, + or when a grill session has produced findings the user wants acted on — even + if they don't say "improve" explicitly. Do not use for read-only review — + examine agent files manually or run a grill session to generate improvement + signals. Do not use to author skills — use /skill-author instead. allowed-tools: Bash Read Write Edit metadata: category: factory @@ -26,18 +28,19 @@ metadata: ## Gotchas -- If the agent lives inside a plugin (its path contains a `plugin.json`), bump the plugin version after every change — in both `plugin.json` and `.claude-plugin/plugin.json` in the same edit pass. Convention: new agent → minor bump; improvement or fix → patch bump. Plugin consumers compare this version to detect updates; skipping it makes the change invisible. -- Plugin agents silently ignore `hooks`, `mcpServers`, and `permissionMode` — these fields have no effect and produce no warning. They only work in `.claude/agents/` or `~/.claude/agents/`. +- At plugin/APM scope, bump the resolved package's `apm.yml` `version` after every change — minor for a new agent, patch for a fix. Consumers compare this version to detect updates; skipping it hides the change. +- At plugin/APM scope, `tools` and all Claude-only fields (`isolation`, `maxTurns`, `effort`, `memory`, `permissionMode`, `disallowedTools`, `skills`, `color`, `initialPrompt`, `background`, `hooks`, `mcpServers`) are omitted entirely, not merely restricted (ADR-0016: `apm compile` copies frontmatter verbatim to both harnesses with no per-target integrator, so a harness-specific value is wrong on at least one). Only project/user scope supports these fields. +- An `apm.yml` with no top-level `type:` field is a marketplace-only manifest, not a package root — the walk-up skips it and keeps going. - `AskUserQuestion`, `EnterPlanMode`, `ExitPlanMode`, `ScheduleWakeup`, and `WaitForMcpServers` are never available to any subagent regardless of the `tools` field. Exception: `ExitPlanMode` is available when the parent session runs in `permissionMode: plan`. - Duplicate `name` values in the same scope: Claude Code silently discards one without warning. Always verify uniqueness before shipping. -- Plugin agents in subdirectories get scoped identifiers (`plugin:folder:name`). Keep agents flat in `agents/` to avoid this. -- Copilot CLI agent files **must** use the `.agent.md` extension. A plain `.md` file is not picked up by Copilot. -- Copilot has no `permissionMode`, `maxTurns`, `isolation`, or `memory` fields — do not include them in the Copilot file. +- Plugin agents in subdirectories get scoped identifiers (`plugin:folder:name`) — keep agents flat in `agents/` to avoid this. Applies to project/user-scope Claude Code agents only. +- Copilot CLI agent files **must** use the `.agent.md` extension — a plain `.md` file isn't picked up. The plugin/APM-scope single file also ends in `.agent.md` by convention, but it's vendor-neutral, not Copilot-only — it compiles to Claude Code too. +- Copilot has no `permissionMode`, `maxTurns`, `isolation`, or `memory` fields — do not include them in project/user-scope Copilot files. - `model` resolution order for Claude Code: `CLAUDE_CODE_SUBAGENT_MODEL` env var → per-invocation parameter → frontmatter `model` → main session model. The frontmatter value is a low-priority default, not a guarantee. ## Route -If the destination is a plugin directory, read `references/deployment-modes.md`. +If the destination resolves to plugin/APM scope (scope detection in Step 1 finds a `type:`-bearing `apm.yml` at or above the root), read `references/deployment-modes.md`. Determine which flow before touching the filesystem: @@ -53,7 +56,7 @@ Signals: grill session output, inline user feedback, session context describing Before touching the filesystem, confirm you have: - [ ] Agent name (kebab-case, e.g. `code-reviewer`) -- [ ] Root directory (plugin root, project root, or `~` for user scope) +- [ ] Root directory (a path inside a package for plugin/APM scope, project root, or `~` for user scope) - [ ] Agent purpose — one sentence describing the task this agent handles - [ ] Trigger condition — when should the runtime delegate to this agent? @@ -71,19 +74,25 @@ bash scripts/new-agent.sh Examples: ```bash -bash scripts/new-agent.sh code-reviewer plugins/kyberforge/ +bash scripts/new-agent.sh code-reviewer packages/my-package/ # plugin/APM scope if packages/my-package/apm.yml has a type: field bash scripts/new-agent.sh deploy-assistant . bash scripts/new-agent.sh security-reviewer ~ ``` -**Scope detection (script handles this automatically):** -- Root contains `plugin.json`, `.claude-plugin/plugin.json`, `.plugin/plugin.json`, or `.github/plugin/plugin.json` → plugin scope → creates `/agents/.md` + `/agents/.agent.md` -- Root is a project directory (no plugin marker) → creates `/.claude/agents/.md` + `/.github/agents/.agent.md` -- Root is `~` → creates `~/.claude/agents/.md` + `~/.copilot/agents/.agent.md` +**Scope detection (script handles this automatically).** The script walks up from `` for a package boundary — same shape `agent-audit`'s `validate.sh` uses: +- Nearest ancestor `apm.yml` with a top-level `type:` field (`instructions`/`skill`/`hybrid`/`prompts`) → **plugin/APM scope** → `/.apm/agents/.agent.md` (single vendor-neutral file). A `type:`-less `apm.yml` is marketplace-only — skipped, walk continues upward. +- No such `apm.yml`, `` is a project directory → **project scope** (unchanged) → `/.claude/agents/.md` + `/.github/agents/.agent.md` +- `` is exactly `~` (checked directly, no walk-up) → **user scope** (unchanged) → `~/.claude/agents/.md` + `~/.copilot/agents/.agent.md` + +A bare `plugin.json` with no `apm.yml` no longer signals plugin scope — that path is fully replaced, not dual-mode; it falls through to project scope. The script is file-by-file no-op — it skips any file that already exists. -### Step 2 — Fill in the Claude Code agent file (`.md`) +### Step 2 — Fill in the agent file(s) + +**At plugin/APM scope**, there is exactly one file: `/.apm/agents/.agent.md`. Frontmatter carries ONLY `name`, `description`, and optionally `model` — never `tools` or the other Claude-only fields listed in Gotchas (ADR-0016). Fill in `name`, `description`, `model`, and the system prompt body per the guidance below; the rest of this step's field-by-field guidance (tools, maxTurns, effort, memory, isolation, disallowedTools, skills, color, initialPrompt, background) is project/user scope only. Skip Step 3 and go to Step 4. + +**At project/user scope**, continue below to fill in both provider files — this step covers the Claude Code file (`.md`); Step 3 covers the Copilot file. Open the scaffolded Claude Code file. Replace every `FILL IN:` placeholder. **Remove all template documentation comments from the YAML frontmatter after filling in required fields** — these are marked with `` and must be deleted before shipping. @@ -95,9 +104,9 @@ Open the scaffolded Claude Code file. Replace every `FILL IN:` placeholder. **Re - Specific about the triggering condition and expertise domain - Under 300 characters preferred -**`tools`** — restrict to what the agent actually needs. Omit to inherit all tools. Use `Agent(type1,type2)` to limit which subagent types this agent can spawn; omit `Agent` entirely to prevent spawning. +**`tools`** (project/user scope only — never at plugin/APM scope) — restrict to what the agent actually needs. Omit to inherit all tools. Use `Agent(type1,type2)` to limit which subagent types this agent can spawn; omit `Agent` entirely to prevent spawning. -**Optional fields worth considering:** +**Optional fields worth considering (project/user scope only — never at plugin/APM scope):** - `model`: set when this agent needs a different capability tier (`haiku` for fast tasks, `opus` for deep reasoning) - `maxTurns`: set a cap to prevent runaway agents on bounded tasks - `effort`: set to `low` for single-lookup tasks, `high` or above for deep reasoning or multi-file analysis — overrides session effort level; omit to inherit @@ -109,7 +118,7 @@ Open the scaffolded Claude Code file. Replace every `FILL IN:` placeholder. **Re - `initialPrompt`: auto-submitted as the first turn when this agent activates as the main session thread; only set when this agent is intended for main-thread activation - `background`: set `true` to force background execution -**`source_keys`** — top-level list of research source slugs that informed this agent. Add only when research sources were used (i.e. entries with `` `extracted` `` status are in context from a prior `/research` session). Each slug must match an H2 heading in `sources.md` (plugin root). Omit entirely when no research was used. +**`source_keys`** — top-level list of research source slugs that informed this agent. Add only when research sources were used (i.e. entries with `` `extracted` `` status are in context from a prior `/research` session). Each slug must match an H2 heading in `sources.md` — see Step 4 for where that file lives (plugin/APM scope only). Omit entirely when no research was used. ```yaml source_keys: @@ -121,12 +130,14 @@ source_keys: - Cover: inputs expected, process steps, output format, error handling - One job per agent -### Step 3 — Fill in the Copilot agent file +### Step 3 — Fill in the Copilot agent file (project/user scope only) + +Skip this step entirely at plugin/APM scope — there is no separate Copilot file there. The single `.apm/agents/.agent.md` file from Step 2 already compiles to both Claude Code and Copilot CLI via `apm compile`. **Two distinct Copilot agent formats** exist, with different paths and field sets. Choose one based on the deployment target: **CLI format** (default — what the scaffold creates): -- Path: `.github/agents/.agent.md` (project) or `/agents/.agent.md` (plugin) +- Path: `.github/agents/.agent.md` (project) or `~/.copilot/agents/.agent.md` (user) - Extension: **must be `.agent.md`** - Supported fields: `name` (required), `description` (required), `tools` (optional) - `tools` uses Copilot aliases: `execute` (shell), `read`, `edit`, `search`, `agent`, `web` @@ -145,14 +156,14 @@ source_keys: The system prompt body should match the Claude Code version — the agent's task definition is the same across providers. -### Step 4 — Populate or delete `sources.md` (plugin scope only) +### Step 4 — Populate or delete `sources.md` (plugin/APM scope only) -Skip this step at project and user scope. The file lives at the plugin root, not inside `agents/` — `claude plugin validate --strict` auto-discovers every `.md` under `agents/` as an agent requiring frontmatter, and a flat provenance file would fail that check (see ADR-0010). +Skip at project/user scope. The file lives at the package root (alongside `apm.yml`), not inside `.apm/agents/` — otherwise tooling that scans that directory for agent definitions would treat it as an agent needing frontmatter (ADR-0010). If a research `sources.md` is present in the conversation context: 1. Filter to entries with `` `extracted` `` status only. -2. For each entry, identify which agent files in the pair it contributed to. -3. Write `sources.md` at the plugin root using the format below. Paths in `Contributing files:` are relative to the plugin root. +2. For each entry, identify which agent file it contributed to. +3. Write `sources.md` at the package root using the format below. Paths in `Contributing files:` are relative to the package root. ```markdown # Sources @@ -162,11 +173,11 @@ If a research `sources.md` is present in the conversation context: - **URL:** - **Research doc:** - **Description:** -- **Contributing files:** agents/.md, agents/.agent.md +- **Contributing files:** .apm/agents/.agent.md - **Status:** `extracted` ``` -Each slug must match an H2 heading, and each slug must also appear in the `source_keys` list of every file listed under `Contributing files:`. +Each slug must match an H2 heading, and each slug must also appear in the `source_keys` list of the file listed under `Contributing files:`. If no research sources are in context, delete `sources.md`. @@ -174,15 +185,22 @@ If no research sources are in context, delete `sources.md`. Run this checklist before invoking the audit: -**Claude Code file (`.md`):** +**Plugin/APM scope — single file (`.agent.md`):** - [ ] `name` field present, kebab-case, unique in scope - [ ] `description` field present and action-first -- [ ] If plugin scope: no `hooks`, `mcpServers`, or `permissionMode` (silently ignored at plugin scope) +- [ ] Frontmatter contains ONLY `name`, `description`, and optionally `model` (plus `source_keys` if research-sourced) — no `tools`, `isolation`, `maxTurns`, `effort`, `memory`, `permissionMode`, `disallowedTools`, `skills`, `color`, `initialPrompt`, `background`, `hooks`, or `mcpServers` - [ ] System prompt body present and non-empty - [ ] No `FILL IN:` placeholders remain - [ ] No `` template comments remain in frontmatter -**Copilot CLI file (`.agent.md`):** +**Project/user scope — Claude Code file (`.md`):** +- [ ] `name` field present, kebab-case, unique in scope +- [ ] `description` field present and action-first +- [ ] System prompt body present and non-empty +- [ ] No `FILL IN:` placeholders remain +- [ ] No `` template comments remain in frontmatter + +**Project/user scope — Copilot CLI file (`.agent.md`):** - [ ] File extension is `.agent.md` (not `.md`) - [ ] `name` field matches the filename stem (e.g. `name: my-agent` in `my-agent.agent.md`) - [ ] `description` field present @@ -191,9 +209,9 @@ Run this checklist before invoking the audit: - [ ] Body does not exceed 30,000 characters - [ ] No `` template comments remain in frontmatter -If the destination is inside a plugin directory, apply a **minor bump** to the `version` field in both `plugin.json` and `.claude-plugin/plugin.json` at the plugin root in the same edit pass (e.g. `1.0.4` → `1.1.0`). +At plugin/APM scope, apply a **minor bump** to the resolved package's `apm.yml` `version` (single manifest, e.g. `1.0.4` → `1.1.0`). -Invoke the `kyberforge:agent-audit` skill directly on the created files to confirm the pair is valid before closing. +Invoke `kyberforge:agent-audit` on the created file(s) before closing — validates the pair at project/user scope, the single file at plugin/APM scope. **Commit verification.** Capture `git log --oneline -1` before Step 1 and keep it. Once the audit is clean, run `git add` and `git commit` for the new agent files — do not stop at staging. Then run `git log --oneline -1` again and confirm the hash changed from the one you captured at the start. A non-empty `git diff --stat` is not sufficient proof of completion: staged-but-uncommitted work isn't part of any commit and can be silently lost if the working tree is cleaned up before a commit lands. Only report the agent as done once the hash has actually changed. @@ -209,7 +227,7 @@ Verify `kyberforge:agent-audit` is available — it ships with the kyberforge pl Capture `git log --oneline -1` now, before making any edits — Step 5 needs it to verify a real commit landed. -**Partial state** — if one provider file exists but the other does not, scaffold the missing file first (run `bash scripts/new-agent.sh ` — the file-by-file no-op means only the missing file is created), then continue with the improve flow on both files. +**Partial state (project/user scope only)** — if one provider file exists but not the other, scaffold the missing one (`bash scripts/new-agent.sh `, file-by-file no-op) then continue. Doesn't apply at plugin/APM scope — single file, no partial-pair state. ### Step 2 — Gather and group signals @@ -230,7 +248,7 @@ Before editing, state which root causes were identified, what evidence supports ### Step 4 — Apply changes -Edit any file the signals point to. Generalize the fix — find the underlying gap, not the specific example that failed. For every sentence you add, ask: "Would the agent get this wrong without it?" A shorter, focused definition consistently outperforms an exhaustive one. For Copilot files, verify no Claude Code-only fields are introduced. +Edit any file the signals point to. Generalize the fix — find the underlying gap, not the specific example that failed. For every sentence you add, ask: "Would the agent get this wrong without it?" A shorter, focused definition consistently outperforms an exhaustive one. For Copilot files, verify no Claude Code-only fields are introduced. For a plugin/APM-scope single file, verify no field beyond `name`, `description`, `model`, and `source_keys` is introduced. If the edit adds or removes research-sourced content, update `source_keys` in the edited file(s) and the corresponding entry in `sources.md` per Create flow's Step 4. @@ -238,8 +256,8 @@ If the edit adds or removes research-sourced content, update `source_keys` in th Re-run the validation checklist from the create flow's Step 5 on any edited file. -If the agent lives inside a plugin directory, apply a **patch bump** to the `version` field in both `plugin.json` and `.claude-plugin/plugin.json` at the plugin root in the same edit pass (e.g. `1.0.4` → `1.0.5`). +At plugin/APM scope, apply a **patch bump** to the resolved package's `apm.yml` `version` (e.g. `1.0.4` → `1.0.5`). -Invoke the `kyberforge:agent-audit` skill directly on the edited files to confirm no regressions before closing. +Invoke `kyberforge:agent-audit` on the edited file(s) to confirm no regressions — the pair at project/user scope, the single file at plugin/APM scope. **Commit verification.** Capture `git log --oneline -1` at the start of Step 1 and keep it. Once the audit is clean, run `git add` and `git commit` for the changed files — do not stop at staging. Then run `git log --oneline -1` again and confirm the hash changed from the one you captured at the start. A non-empty `git diff --stat` is not sufficient proof of completion: staged-but-uncommitted work isn't part of any commit and can be silently lost if the working tree is cleaned up before a commit lands. Only report the improvement as done once the hash has actually changed. diff --git a/plugins/kyberforge/skills/agent-author/assets/README.md b/plugins/kyberforge/skills/agent-author/assets/README.md index abdd42e..f910298 100644 --- a/plugins/kyberforge/skills/agent-author/assets/README.md +++ b/plugins/kyberforge/skills/agent-author/assets/README.md @@ -4,5 +4,6 @@ Annotated agent definition templates copied by `scripts/new-agent.sh` when scaffolding a new agent. -- **`claude-code.md`** — Claude Code agent definition template. Includes all supported frontmatter fields (required and optional) with inline guidance comments and `FILL IN:` placeholders. Notes which fields are silently ignored for plugin agents. -- **`copilot.agent.md`** — Copilot CLI agent definition template (CLI format). Excludes cloud/IDE-only fields (`target`, `user-invocable`, `disable-model-invocation`, `mcp-servers`) and Claude Code-only fields. Uses Copilot tool aliases (`execute`, `read`, `edit`, `search`, `agent`, `web`). +- **`claude-code.md`** — Claude Code agent definition template (project/user scope). Includes all supported frontmatter fields (required and optional) with inline guidance comments and `FILL IN:` placeholders. +- **`copilot.agent.md`** — Copilot CLI agent definition template (CLI format, project/user scope). Excludes cloud/IDE-only fields (`target`, `user-invocable`, `disable-model-invocation`, `mcp-servers`) and Claude Code-only fields. Uses Copilot tool aliases (`execute`, `read`, `edit`, `search`, `agent`, `web`). +- **`apm-agent.md`** — Vendor-neutral APM agent definition template (plugin/APM scope). Only `name`, `description`, and optional `model` in frontmatter — no `tools` and no Claude-only fields, since `apm compile` copies frontmatter verbatim to both the Claude Code and Copilot CLI targets with no per-target integrator (ADR-0016). diff --git a/plugins/kyberforge/skills/agent-author/assets/templates/apm-agent.md b/plugins/kyberforge/skills/agent-author/assets/templates/apm-agent.md new file mode 100644 index 0000000..599c9e5 --- /dev/null +++ b/plugins/kyberforge/skills/agent-author/assets/templates/apm-agent.md @@ -0,0 +1,53 @@ +--- + + +name: AGENT_NAME + + +description: FILL IN: Action-first description of what this agent does and when to invoke it. + + + + + +--- + +FILL IN: System prompt body. Write as a direct role instruction. + +You are a FILL IN: role description. When invoked, FILL IN: primary action. + +## Inputs + +FILL IN: What inputs does this agent expect? (files, context, parameters) + +## Process + +FILL IN: Steps the agent takes. Be specific about ordering if it matters. + +## Output + +FILL IN: What does the agent produce? Format, location, structure. diff --git a/plugins/kyberforge/skills/agent-author/references/deployment-modes.md b/plugins/kyberforge/skills/agent-author/references/deployment-modes.md index c93a753..826d2d0 100644 --- a/plugins/kyberforge/skills/agent-author/references/deployment-modes.md +++ b/plugins/kyberforge/skills/agent-author/references/deployment-modes.md @@ -18,22 +18,15 @@ Agent definitions deploy at three scopes and behave differently at each. The sco |---|---|---|---| | User | `~/.claude/agents/` | `~/.copilot/agents/` | All sessions for this user | | Project | `.claude/agents/` | `.github/agents/` or `.copilot/agents/` | This repo only | -| Plugin | `/agents/` | `/agents/` | Sessions with the plugin installed | +| Plugin/APM | `/.apm/agents/.agent.md` — single vendor-neutral file, `apm compile` emits it to both targets | *(same file)* | Sessions with the plugin/package installed | When the same agent `name` appears at multiple scopes, **user scope wins over project scope wins over plugin scope** in Claude Code. In Copilot CLI, repo-level agents override enterprise and org-level; home-directory (user) agents override repo-level on name collision. ## Plugin scope restrictions -Plugin agents (agents shipped inside a plugin's `agents/` directory) have fewer capabilities than project or user agents: +Plugin/APM agents (`.apm/agents/.agent.md`) carry only `name`, `description`, and optionally `model` in frontmatter — full stop (see ADR-0016). `apm compile` copies this frontmatter verbatim to both the Claude Code and Copilot CLI compile targets with no per-target integrator: Claude's `tools:` (space-separated string) and Copilot's `tools:` (alias list) are incompatible vocabularies, and Claude-only fields have no Copilot equivalent, so any harness-specific value is guaranteed wrong on at least one target. -**Claude Code — silently ignored for plugin agents:** -- `hooks` — per-agent event handlers; copy the agent to `.claude/agents/` to use these -- `mcpServers` — inline MCP server definitions; not available in plugin context -- `permissionMode` — silently ignored; the parent session's permission mode applies - -**Copilot CLI — unsupported or ignored:** -- `mcp-servers` — cloud agents only; ignored in VS Code and most IDEs -- `metadata` — key-value annotations; ignored in VS Code +This makes the old "silently ignored at plugin scope" framing moot. It's not that `hooks`, `mcpServers`, `permissionMode`, `tools`, `isolation`, `maxTurns`, `effort`, `memory`, `disallowedTools`, `skills`, `color`, `initialPrompt`, or `background` are merely ignored at this scope — they are never written to the file at all. Copy the agent to `.claude/agents/` (project scope) or `~/.claude/agents/` (user scope) to use any of them. ## Scoped identifiers (Claude Code plugin agents only) @@ -59,6 +52,6 @@ Agents at project or user scope are read directly from disk; cache isolation doe |---|---|---| | User | `~/.copilot/agents/.agent.md` | Home directory | | Project | `.github/agents/.agent.md` | Standard; also `.copilot/agents/` | -| Plugin | `/agents/.agent.md` | Inside plugin root | +| Plugin/APM | `/.apm/agents/.agent.md` | Not a Copilot-only file — this is the single vendor-neutral source `apm compile` reads for the Copilot CLI target | -The `.agent.md` extension is **mandatory** — Copilot CLI does not pick up plain `.md` files in the `agents/` directory. +The `.agent.md` extension is **mandatory** for real Copilot CLI files (project/user scope) — Copilot CLI does not pick up plain `.md` files in the `agents/` directory. The plugin/APM source file also uses `.agent.md` by convention, since it compiles to Copilot CLI too, but it is not itself a Copilot file. diff --git a/plugins/kyberforge/skills/agent-author/references/scripts.md b/plugins/kyberforge/skills/agent-author/references/scripts.md index 337085d..6a4966c 100644 --- a/plugins/kyberforge/skills/agent-author/references/scripts.md +++ b/plugins/kyberforge/skills/agent-author/references/scripts.md @@ -24,7 +24,7 @@ Do not add additional substitution tokens unless you update both the template fi ## File placement -The script creates files at paths determined by scope detection (plugin / project / user). Scope is detected from the presence of `plugin.json`, `.claude-plugin/plugin.json`, `.plugin/plugin.json`, or `.github/plugin/plugin.json` in the root directory. If scope detection logic changes, update the `new-agent.sh` usage comment and `SKILL.md` Step 1 scope detection table in the same pass. +The script creates files at paths determined by scope detection (plugin/APM / project / user). Scope is resolved by walking up from the root directory: a `type:`-bearing `apm.yml` at or above the root marks the package root (plugin/APM scope, single file); an `apm.yml` without a `type:` field is a marketplace-only manifest and is skipped, the walk continues upward. If no such `apm.yml` is found, the root resolving to exactly `$HOME` is user scope; anything else is project scope. If scope detection logic changes, update the `new-agent.sh` usage comment and `SKILL.md` Step 1 scope detection description in the same pass. ## Error messages diff --git a/plugins/kyberforge/skills/agent-author/scripts/README.md b/plugins/kyberforge/skills/agent-author/scripts/README.md index 619ef9b..32d891a 100644 --- a/plugins/kyberforge/skills/agent-author/scripts/README.md +++ b/plugins/kyberforge/skills/agent-author/scripts/README.md @@ -2,12 +2,12 @@ ## new-agent.sh -Scaffolds agent definition files for both Claude Code and GitHub Copilot CLI from a single root directory input. +Scaffolds agent definition file(s) from a single root directory input. ``` Usage: new-agent.sh ``` -Detects scope from the root: `plugin.json` present → plugin scope (both files in `/agents/`); `~` → user scope (`~/.claude/agents/` + `~/.copilot/agents/`); otherwise project scope (`.claude/agents/` + `.github/agents/`). Each file is a no-op if it already exists. See `--help` for full usage. +Resolves scope by walking up from ``: a `type:`-bearing `apm.yml` found at or above `` → plugin/APM scope (single file at `/.apm/agents/.agent.md`; an `apm.yml` without `type:` is a marketplace-only manifest and is skipped); `` exactly `~` → user scope (`~/.claude/agents/` + `~/.copilot/agents/`); otherwise project scope (`/.claude/agents/` + `/.github/agents/`). Each file is a no-op if it already exists. See `--help` for full usage. Tests: `tests/new-agent.bats` (requires `bats-support` and `bats-assert`). diff --git a/plugins/kyberforge/skills/agent-author/scripts/new-agent.sh b/plugins/kyberforge/skills/agent-author/scripts/new-agent.sh index 6e85595..89016ce 100755 --- a/plugins/kyberforge/skills/agent-author/scripts/new-agent.sh +++ b/plugins/kyberforge/skills/agent-author/scripts/new-agent.sh @@ -9,19 +9,29 @@ usage() { cat < -Scaffold agent definition files for Claude Code and GitHub Copilot CLI. +Scaffold agent definition file(s) for Claude Code, GitHub Copilot CLI, and/or +vendor-neutral APM packages. Arguments: agent-name Kebab-case agent identifier (e.g. code-reviewer, deploy-assistant). - root Root directory — determines scope: - plugin scope : root contains plugin.json - → creates /agents/.md - → creates /agents/.agent.md - → creates /sources.md (if absent) - project scope : root is a project directory (no plugin.json) + root Starting directory — scope is resolved by walking up from here: + plugin/APM scope : nearest ancestor (at/above root) whose apm.yml + has a top-level type: field (instructions, + skill, hybrid, or prompts) — an apm.yml + without type: is a marketplace-only manifest + and is skipped, the walk continues upward + → creates /.apm/agents/.agent.md + (single vendor-neutral file — no tools, + isolation, maxTurns, effort, memory, or + permissionMode; apm compile has no per-target + field integrator, see ADR-0016) + → creates /sources.md (if absent) + project scope : no type:-bearing apm.yml found; root is a + project directory → creates /.claude/agents/.md → creates /.github/agents/.agent.md - user scope : root is ~ (home directory) + user scope : root is exactly ~ (home directory; checked + directly, no walk-up) → creates ~/.claude/agents/.md → creates ~/.copilot/agents/.agent.md @@ -71,22 +81,71 @@ if [[ ! -d "$ROOT" ]]; then echo "Error: root directory '$ROOT' does not exist." >&2 exit 1 fi +ROOT="$(cd "$ROOT" && pwd)" -# Detect scope -if [[ -f "$ROOT/plugin.json" || -f "$ROOT/.claude-plugin/plugin.json" || -f "$ROOT/.plugin/plugin.json" || -f "$ROOT/.github/plugin/plugin.json" ]]; then - SCOPE="plugin" -elif [[ "$ROOT" == "$HOME" ]]; then - SCOPE="user" -else - SCOPE="project" -fi +# --- Walk-up package-root detection --- +# +# Mirrors agent-audit's validate.sh scope walk-up, with apm.yml + type: swapped +# in for the old plugin.json marker. Starting at ROOT, walk upward: +# - an apm.yml with a top-level `type:` field marks an APM package root +# (plugin/APM scope) — stop and return it. +# - an apm.yml with no `type:` field is a marketplace-only manifest — skip +# it, keep walking up. +# - a .git directory marks the project-scope boundary — stop. +# - filesystem root reached with neither found — boundary-reached. +find_package_root() { + local current="$1" + while true; do + if [[ -f "$current/apm.yml" ]] && grep -qE '^type:[[:space:]]*(instructions|skill|hybrid|prompts)([[:space:]]|$)' "$current/apm.yml"; then + echo "plugin" + echo "$current" + return + fi + if [[ -d "$current/.git" ]]; then + echo "project" + echo "$current" + return + fi + local parent + parent="$(dirname "$current")" + if [[ "$parent" == "$current" ]]; then + echo "boundary-reached" + echo "$current" + return + fi + current="$parent" + done +} + +WALK_RESULT="$(find_package_root "$ROOT")" +WALK_KIND="$(echo "$WALK_RESULT" | sed -n '1p')" +WALK_ROOT="$(echo "$WALK_RESULT" | sed -n '2p')" + +PACKAGE_ROOT="" +case "$WALK_KIND" in + plugin) + SCOPE="plugin" + PACKAGE_ROOT="$WALK_ROOT" + ;; + project) + SCOPE="project" + ;; + boundary-reached) + if [[ "$ROOT" == "$HOME" ]]; then + SCOPE="user" + else + # Default fallback, same as the pre-walk-up script: no plugin/APM + # marker and not exactly $HOME means project scope. + SCOPE="project" + fi + ;; +esac # Determine file destinations case "$SCOPE" in plugin) - CC_DIR="$ROOT/agents" - CP_DIR="$ROOT/agents" - SOURCES_DIR="$ROOT" + APM_DIR="$PACKAGE_ROOT/.apm/agents" + SOURCES_DIR="$PACKAGE_ROOT" ;; project) CC_DIR="$ROOT/.claude/agents" @@ -100,33 +159,47 @@ case "$SCOPE" in ;; esac -CC_FILE="$CC_DIR/$AGENT_NAME.md" -CP_FILE="$CP_DIR/$AGENT_NAME.agent.md" - -# Create directories -mkdir -p "$CC_DIR" -mkdir -p "$CP_DIR" - -# Copy Claude Code template (no-op if exists) created_any=false -if [[ -f "$CC_FILE" ]]; then - echo "Skipping '$CC_FILE' — already exists." >&2 + +if [[ "$SCOPE" == "plugin" ]]; then + APM_FILE="$APM_DIR/$AGENT_NAME.agent.md" + + mkdir -p "$APM_DIR" + + if [[ -f "$APM_FILE" ]]; then + echo "Skipping '$APM_FILE' — already exists." >&2 + else + sed "s/AGENT_NAME/$AGENT_NAME/g" "$TEMPLATES_DIR/apm-agent.md" > "$APM_FILE" + echo "Created: $APM_FILE" >&2 + created_any=true + fi else - sed "s/AGENT_NAME/$AGENT_NAME/g" "$TEMPLATES_DIR/claude-code.md" > "$CC_FILE" - echo "Created: $CC_FILE" >&2 - created_any=true + CC_FILE="$CC_DIR/$AGENT_NAME.md" + CP_FILE="$CP_DIR/$AGENT_NAME.agent.md" + + mkdir -p "$CC_DIR" + mkdir -p "$CP_DIR" + + # Copy Claude Code template (no-op if exists) + if [[ -f "$CC_FILE" ]]; then + echo "Skipping '$CC_FILE' — already exists." >&2 + else + sed "s/AGENT_NAME/$AGENT_NAME/g" "$TEMPLATES_DIR/claude-code.md" > "$CC_FILE" + echo "Created: $CC_FILE" >&2 + created_any=true + fi + + # Copy Copilot template (no-op if exists) + if [[ -f "$CP_FILE" ]]; then + echo "Skipping '$CP_FILE' — already exists." >&2 + else + sed "s/AGENT_NAME/$AGENT_NAME/g" "$TEMPLATES_DIR/copilot.agent.md" > "$CP_FILE" + echo "Created: $CP_FILE" >&2 + created_any=true + fi fi -# Copy Copilot template (no-op if exists) -if [[ -f "$CP_FILE" ]]; then - echo "Skipping '$CP_FILE' — already exists." >&2 -else - sed "s/AGENT_NAME/$AGENT_NAME/g" "$TEMPLATES_DIR/copilot.agent.md" > "$CP_FILE" - echo "Created: $CP_FILE" >&2 - created_any=true -fi - -# Create sources.md at plugin scope (no-op if exists) +# Create sources.md at plugin/APM package root (no-op if exists) if [[ -n "$SOURCES_DIR" ]]; then SOURCES_FILE="$SOURCES_DIR/sources.md" if [[ -f "$SOURCES_FILE" ]]; then @@ -135,7 +208,7 @@ if [[ -n "$SOURCES_DIR" ]]; then cat > "$SOURCES_FILE" <<'SOURCES' # Sources - @@ -143,7 +216,7 @@ if [[ -n "$SOURCES_DIR" ]]; then - **URL:** - **Research doc:** - **Description:** -- **Contributing files:** agents/.md, agents/.agent.md +- **Contributing files:** .apm/agents/.agent.md - **Status:** `extracted` --> SOURCES echo "Created: $SOURCES_FILE" >&2 @@ -158,12 +231,13 @@ else echo "Scope: $SCOPE" >&2 echo "" >&2 echo "Next steps:" >&2 - echo " 1. Fill in $CC_FILE — replace all FILL IN: placeholders" >&2 - echo " 2. Fill in $CP_FILE — replace all FILL IN: placeholders" >&2 - if [[ -n "$SOURCES_DIR" ]]; then - echo " 3. Populate $SOURCES_DIR/sources.md with research sources, or delete it" >&2 - echo " 4. Validate: check required fields (name, description, system prompt) in both files" >&2 + if [[ "$SCOPE" == "plugin" ]]; then + echo " 1. Fill in $APM_FILE — replace all FILL IN: placeholders (name, description, model, body only)" >&2 + echo " 2. Populate $SOURCES_DIR/sources.md with research sources, or delete it" >&2 + echo " 3. Validate: check required fields (name, description, system prompt) in the file" >&2 else + echo " 1. Fill in $CC_FILE — replace all FILL IN: placeholders" >&2 + echo " 2. Fill in $CP_FILE — replace all FILL IN: placeholders" >&2 echo " 3. Validate: check required fields (name, description, system prompt) in both files" >&2 fi fi diff --git a/plugins/kyberforge/skills/agent-author/tests/new-agent.bats b/plugins/kyberforge/skills/agent-author/tests/new-agent.bats index ed193b9..634cc4c 100644 --- a/plugins/kyberforge/skills/agent-author/tests/new-agent.bats +++ b/plugins/kyberforge/skills/agent-author/tests/new-agent.bats @@ -24,62 +24,129 @@ teardown() { } # --------------------------------------------------------------------------- -# Plugin scope (plugin.json present at root) +# Plugin/APM scope (type:-bearing apm.yml at root) # --------------------------------------------------------------------------- -@test "plugin scope: creates both agent files in agents/" { - touch "$ROOT/plugin.json" +@test "plugin/APM scope: creates single agent file in .apm/agents/" { + printf 'name: my-package\ntype: skill\n' > "$ROOT/apm.yml" run bash "$SCRIPT" my-agent "$ROOT" assert_success - assert [ -f "$ROOT/agents/my-agent.md" ] - assert [ -f "$ROOT/agents/my-agent.agent.md" ] + assert [ -f "$ROOT/.apm/agents/my-agent.agent.md" ] } -@test "plugin scope: creates agents/ directory if missing" { - touch "$ROOT/plugin.json" +@test "plugin/APM scope: does not create the old dual-file pair" { + printf 'name: my-package\ntype: skill\n' > "$ROOT/apm.yml" run bash "$SCRIPT" my-agent "$ROOT" assert_success - assert [ -d "$ROOT/agents" ] + assert [ ! -f "$ROOT/agents/my-agent.md" ] + assert [ ! -f "$ROOT/agents/my-agent.agent.md" ] } -@test "plugin scope: creates sources.md at plugin root" { - touch "$ROOT/plugin.json" +@test "plugin/APM scope: creates .apm/agents/ directory if missing" { + printf 'name: my-package\ntype: skill\n' > "$ROOT/apm.yml" + run bash "$SCRIPT" my-agent "$ROOT" + assert_success + assert [ -d "$ROOT/.apm/agents" ] +} + +@test "plugin/APM scope: creates sources.md at package root" { + printf 'name: my-package\ntype: skill\n' > "$ROOT/apm.yml" run bash "$SCRIPT" my-agent "$ROOT" assert_success assert [ -f "$ROOT/sources.md" ] } -@test "plugin scope: no-op if claude code file already exists" { - touch "$ROOT/plugin.json" - mkdir -p "$ROOT/agents" - echo "existing" > "$ROOT/agents/my-agent.md" +@test "plugin/APM scope: no-op if agent file already exists" { + printf 'name: my-package\ntype: skill\n' > "$ROOT/apm.yml" + mkdir -p "$ROOT/.apm/agents" + echo "existing" > "$ROOT/.apm/agents/my-agent.agent.md" run bash "$SCRIPT" my-agent "$ROOT" assert_success - run grep "existing" "$ROOT/agents/my-agent.md" + run grep "existing" "$ROOT/.apm/agents/my-agent.agent.md" assert_success } -@test "plugin scope: no-op if copilot file already exists" { - touch "$ROOT/plugin.json" - mkdir -p "$ROOT/agents" - echo "existing" > "$ROOT/agents/my-agent.agent.md" - run bash "$SCRIPT" my-agent "$ROOT" - assert_success - run grep "existing" "$ROOT/agents/my-agent.agent.md" +@test "plugin/APM scope: frontmatter has no tools/isolation/maxTurns/effort/memory/permissionMode keys" { + printf 'name: my-package\ntype: skill\n' > "$ROOT/apm.yml" + bash "$SCRIPT" my-agent "$ROOT" + file="$ROOT/.apm/agents/my-agent.agent.md" + fm="$(sed -n '/^---$/,/^---$/p' "$file")" + # Column-0 key lines only — comment bodies in the template are indented, + # so this anchor naturally excludes commented-out example fields. + run grep -E '^(tools|isolation|maxTurns|effort|memory|permissionMode|disallowedTools|skills|color|initialPrompt|background|hooks|mcpServers):' <<< "$fm" + assert_failure +} + +@test "plugin/APM scope: frontmatter carries only name, description, model, source_keys fields" { + printf 'name: my-package\ntype: skill\n' > "$ROOT/apm.yml" + bash "$SCRIPT" my-agent "$ROOT" + file="$ROOT/.apm/agents/my-agent.agent.md" + fm="$(sed -n '/^---$/,/^---$/p' "$file")" + keys="$(grep -oE '^[a-zA-Z][a-zA-Z0-9_-]*:' <<< "$fm" | sed 's/:$//' | sort -u)" + for key in $keys; do + if [[ "$key" != "name" && "$key" != "description" && "$key" != "model" && "$key" != "source_keys" ]]; then + fail "unexpected frontmatter key: $key" + fi + done +} + +@test "plugin/APM scope: sources.md contributing-files template mentions the single-file path" { + printf 'name: my-package\ntype: skill\n' > "$ROOT/apm.yml" + bash "$SCRIPT" my-agent "$ROOT" + run grep ".apm/agents/.agent.md" "$ROOT/sources.md" assert_success } -@test "plugin scope: still creates missing file when one already exists" { - touch "$ROOT/plugin.json" - mkdir -p "$ROOT/agents" - echo "existing" > "$ROOT/agents/my-agent.md" +@test "plugin/APM scope: template AGENT_NAME substituted" { + printf 'name: my-package\ntype: skill\n' > "$ROOT/apm.yml" + bash "$SCRIPT" my-agent "$ROOT" + run grep "my-agent" "$ROOT/.apm/agents/my-agent.agent.md" + assert_success +} + +@test "plugin/APM scope: walk-up finds apm.yml at an ancestor directory, not just root arg" { + printf 'name: my-package\ntype: skill\n' > "$ROOT/apm.yml" + mkdir -p "$ROOT/nested/subdir" + run bash "$SCRIPT" my-agent "$ROOT/nested/subdir" + assert_success + assert [ -f "$ROOT/.apm/agents/my-agent.agent.md" ] +} + +@test "plugin/APM scope: apm.yml without type: is skipped (marketplace-only manifest)" { + printf 'name: my-marketplace\n' > "$ROOT/apm.yml" + mkdir -p "$ROOT/.git" run bash "$SCRIPT" my-agent "$ROOT" assert_success - assert [ -f "$ROOT/agents/my-agent.agent.md" ] + assert [ ! -f "$ROOT/.apm/agents/my-agent.agent.md" ] + assert [ -f "$ROOT/.claude/agents/my-agent.md" ] +} + +@test "plugin/APM scope: type:-less apm.yml at leaf falls through to a type:-bearing apm.yml higher up" { + printf 'name: outer-package\ntype: skill\n' > "$ROOT/apm.yml" + mkdir -p "$ROOT/inner" + printf 'name: inner-marketplace\n' > "$ROOT/inner/apm.yml" + run bash "$SCRIPT" my-agent "$ROOT/inner" + assert_success + assert [ -f "$ROOT/.apm/agents/my-agent.agent.md" ] } # --------------------------------------------------------------------------- -# Non-plugin scope (no plugin.json) +# Old plugin.json marker is no longer recognized (full switch, no dual-mode) +# --------------------------------------------------------------------------- + +@test "bare plugin.json (no apm.yml) is no longer detected as plugin scope — falls through to project scope" { + touch "$ROOT/plugin.json" + run bash "$SCRIPT" my-agent "$ROOT" + assert_success + assert [ ! -f "$ROOT/agents/my-agent.md" ] + assert [ ! -f "$ROOT/agents/my-agent.agent.md" ] + assert [ ! -f "$ROOT/.apm/agents/my-agent.agent.md" ] + assert [ -f "$ROOT/.claude/agents/my-agent.md" ] + assert [ -f "$ROOT/.github/agents/my-agent.agent.md" ] +} + +# --------------------------------------------------------------------------- +# Non-plugin (project) scope # --------------------------------------------------------------------------- @test "non-plugin scope: creates claude code file in .claude/agents/" { @@ -122,6 +189,28 @@ teardown() { assert_success } +@test "project scope: detected via .git present above root, output still relative to root arg" { + mkdir -p "$ROOT/repo/.git" + mkdir -p "$ROOT/repo/pkg" + run bash "$SCRIPT" my-agent "$ROOT/repo/pkg" + assert_success + assert [ -f "$ROOT/repo/pkg/.claude/agents/my-agent.md" ] + assert [ -f "$ROOT/repo/pkg/.github/agents/my-agent.agent.md" ] +} + +# --------------------------------------------------------------------------- +# User scope +# --------------------------------------------------------------------------- + +@test "user scope: root exactly \$HOME creates files under ~/.claude and ~/.copilot" { + FAKE_HOME="$(mktemp -d)" + run env HOME="$FAKE_HOME" bash "$SCRIPT" my-agent "~" + assert_success + assert [ -f "$FAKE_HOME/.claude/agents/my-agent.md" ] + assert [ -f "$FAKE_HOME/.copilot/agents/my-agent.agent.md" ] + rm -rf "$FAKE_HOME" +} + # --------------------------------------------------------------------------- # Name validation # --------------------------------------------------------------------------- @@ -164,21 +253,3 @@ teardown() { run bash "$SCRIPT" my-agent "/nonexistent/path" assert_failure } - -# --------------------------------------------------------------------------- -# Template content -# --------------------------------------------------------------------------- - -@test "plugin scope: claude code template contains AGENT_NAME substituted" { - touch "$ROOT/plugin.json" - bash "$SCRIPT" my-agent "$ROOT" - run grep "my-agent" "$ROOT/agents/my-agent.md" - assert_success -} - -@test "plugin scope: copilot template contains AGENT_NAME substituted" { - touch "$ROOT/plugin.json" - bash "$SCRIPT" my-agent "$ROOT" - run grep "my-agent" "$ROOT/agents/my-agent.agent.md" - assert_success -}