Six defects, each one a place where two files that an author reads in the same
sitting told them different things — or where the trim dropped a rule and nothing
noticed because no gate covers prose.
**"Use proactively" contradicted itself across the pair.** All three agent
templates said to add it where the runtime should delegate unprompted, while
`agent-audit`'s `KyberforgeCopilot.ProactivePhrase` rule grades it a hard FAIL in
any `*.agent.md` — which is the Copilot half of every project/user pair *and* the
vendor-neutral plugin-scope file, since that compiles to a real Copilot agent
downstream. Following the template produced a file the repo's own gate rejects.
The phrase is now permitted in exactly one place, the Claude Code `.md`, and
`references/contract.md` carries the per-file table plus the consequence authors
ask about next: a pair whose CC half has it and whose Copilot half does not is
correct, because `agent-audit` checks that both halves describe the same job, not
that they match word for word.
**The output-schema rule contradicted itself inside one file.** `contract.md`
said any content only one branch reaches moves to `references/`, and then offered
an "Output format template" body pattern with no qualification. Stated once now,
so it is not re-litigated: an output schema stays in the body only when every flow
produces it and it is roughly 50 words or less. No third option.
**Gotchas tiers disagreed with the script.** `validate.sh` emits the entry count
through `suggest()` and exits 0, while `skill-author` and `skill-audit` both
called more than five entries a FAIL. Whether a given gotcha earns its place is
judgment, so the prose moves to the script's tier rather than the reverse. The
paraphrase rule stays a FAIL and is explicitly marked as the auditor's call — no
script detects it.
**The dispatch exemplar was cited at the wrong number.** `apm-workflow`'s body is
421 words; 554 is its whole-file count. Both `contract.md` and `body-discipline.md`
cited 554 while describing a body budget, so an author calibrating against the
exemplar overshot by ~30% — the exact whole-file/body-only conflation those two
sections exist to warn against, reproduced inside the warning.
**"Error handling" came back as a required body element.** It was one of four and
is the one that gets dropped, and dropping it is not neutral: an agent handed
malformed input with no instruction invents a recovery, and a subagent's invented
recovery is invisible to its caller until the output is wrong. Restored in
`agent-audit`'s rubric as a SUGGESTION, in `agent-author`'s contract and both
scope checklists as a required element, and as an `## Errors` section in all three
templates.
**`skill-author` Step 4 gains the one check the audit misses.** An empty body
reports `PASS SKILL.md body word count 0` — a word gate cannot tell "concise"
from "absent". Step 4 now hand-checks for a non-empty section, and its commit
verification is conditioned on actually being inside a git worktree, which a skill
under `~/.claude/skills/` is not.
Also here: absolute repo paths removed from `skill-author`'s SKILL.md and
contract.md in favour of naming the skill (`zoom-out`'s description is quoted
inline instead of pointed at), the boundary-target universe documented to match
the resolver, a two-hops-from-SKILL.md limit on reference chains, and
`new-agent.sh`'s next-steps output naming the description budget and the
deliberate absence of an agent body gate.
Refs: ADR-0020
Skill name+description pairs are preloaded into every session, costing
~6,200 tokens across 39 skills before any skill is invoked. The authoring
rules mandated that growth: skill-author:104 and description-quality.md:21
both required padding, while skill-author:102 (the deflating rule) had no
FAIL condition behind it.
Gates (blocking, no baseline file):
- description 250 chars SUGGESTION / 400 FAIL, measured on the folded
YAML value
- body-only 600 words SUGGESTION / 900 FAIL, independent of the unchanged
whole-file 2770-word / 500-line spec backstop
- every boundary-clause routing target must resolve to a real skill or
agent; catches skill-improve, neuledge-context and gitea-labels
- agents take the description gates but deliberately no body gate; a test
pins that absence
Vale: DescriptionOpener widened to ^This\b, new CompositionNote rule
banning architecture notes from descriptions. 10 hits, 0 false positives.
Kyberforge's own four skills retrofitted: descriptions 3,364 -> 938 chars
(-72%), bodies 8,306 -> 2,487 words (-70%), all via the apm-workflow
dispatch pattern. Fixes the skill-improve dangling route and the
agent-author misroute to manual review.
Also fixes a pre-existing false positive where any line-initial 'read '
was flagged as interactive input, which had already caused two scripts to
be rewritten around it.
Refs: ADR-0020
Output of scripts/sync-plugin-content.sh --all against this round's .apm/
source edits. No file here is hand-edited.
Carries agent-author's scaffolder and its contract doc into the flat mirror.
No compiled manifest changed: nothing in this round touched apm.yml, so apm
pack and sync-marketplace-mirror.sh both produced byte-identical output.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X7GvKuJfy2WrdBmUttV4DT
Output of scripts/sync-plugin-content.sh --all against this round's .apm/
source edits. No file here is hand-edited.
Carries the agent-author and agent-audit documentation changes into the flat
mirrors. No compiled manifest changed: nothing in this round touched apm.yml,
so apm pack and sync-marketplace-mirror.sh both produced byte-identical output.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X7GvKuJfy2WrdBmUttV4DT
Claude Code's (and Copilot's) native plugin installer has zero awareness of
.apm/ nesting -- it convention-scans only flat skills/, agents/, commands/,
hooks.json at each plugin's root. Confirmed via strings on the installed
claude binary and live installs of git@holocron/gitea@holocron/kyberforge@
holocron, all reporting Skills(0) Agents(0) Hooks(0) post ADR-0015's apm
conversion. Root cause (apm_cli/core/plugin_manifest.py): apm's plugin.json
compiler deliberately strips skills/agents/commands keys, assuming the host
already auto-discovers those convention directories -- it has no model of
.apm/ being host-visible at all. Separately, apm's own bundle exporter
(apm_cli/bundle/plugin_exporter.py, behind `apm pack --format plugin`)
implements the correct .apm/ -> flat mapping, but only ever targeted
build/<name>-<version>/, a path nothing in marketplace.json's source: points
at.
scripts/sync-plugin-content.sh wraps that bundle exporter and copies its
agents/, skills/, commands/, instructions/, extensions/, and merged
hooks.json back into each plugin's own root as a second tracked
compiled-output category -- same governance status as
.claude-plugin/plugin.json: generated from .apm/, never hand-edited. tests/
subdirectories are excluded from the mirror (dev fixtures, not host-visible
runtime content; several hardcode a relative repo-root walk-up sized for the
.apm/-nested depth, which breaks when duplicated one level shallower).
Applied for real across all 6 plugins and verified two ways: `claude plugin
validate --strict` passes on every real plugin directory, and a live
`claude --plugin-dir <path> -p "list skills/agents"` behavioral test
confirms content is now actually discovered.
Also, from the same issue #90 review round:
- scripts/check-manifests.sh pointed at each plugin's root-level plugin.json
(checking skills/hooks/mcpServers/agents pointer fields) -- that file was a
stale near-duplicate of .claude-plugin/plugin.json nothing else read or
wrote, now deleted across all 6 plugins. check-manifests.sh is rewritten to
validate .claude-plugin/plugin.json instead, and drops the pointer-field
checks entirely (nothing to check -- those fields are correctly absent by
design). Content-presence drift is now check-plugin-content-sync's job, a
new pre-push hook wired in .pre-commit-config.yaml.
docs/adr/0017 records the root cause and decision in full, including two
rejected alternatives (patching plugin.json's path fields directly -- apm's
compiler strips them on every run; pointing marketplace.json at apm pack's
build/ output -- a version-suffixed non-source directory nothing can install
from without an extra build step). ADR-0015 and CONTEXT.md are updated to
point at it.
Refs: #90
Why:
ADR-0015 established that Microsoft APM (apm.yml + .apm/) should replace
this repo's hand-authored plugin.json/marketplace.json model, with those
files becoming compiled output of `apm pack` instead of files edited by
hand via the (now-retired) plugin-author/marketplace-author skills.
Issue #90 was the deferred execution of that decision, gated on #88
(apm tooling) and #89 (apm-native agent-author/skill-author routing).
Implementation notes:
- All six plugins (bin, core, git, gitea, kyberforge, lint) now carry
apm.yml + .apm/{skills,agents,hooks} as their authoring source. Skills
moved with a plain git mv (content-identical across targets). Agents
were re-authored, not moved: per ADR-0016, .apm/agents/*.agent.md
compiles verbatim to both Claude and Copilot, so plugin-scope agents
now carry only name/description/model/source_keys -- no tools: field,
no Claude-only knobs (isolation, maxTurns, effort, memory,
permissionMode).
- Root apm.yml registers all 7 marketplace packages (6 local plus
mattpocock-skills as a remote entry) under versioning: per_package,
matching this repo's existing independent-plugin-versioning practice.
- .claude-plugin/marketplace.json and every plugin's plugin.json are now
apm-pack-compiled output, verified against the prior hand-maintained
content: same names/descriptions/versions/licenses/authors, only
cosmetic serialization differences (JSON key order, owner email vs.
url, Unicode escaping).
- plugin-author and marketplace-author are retired now that apm-based
authoring fully replaces their job; kyberforge bumped 1.3.1 -> 1.4.0
for that removal, and the root marketplace catalog bumped
0.3.1 -> 0.3.2 to match, per the version-bump convention now
documented in apm-workflow's reference docs instead of a dedicated
script (apm has no native version-bump automation).
- Fixed hardcoded pre-.apm/ path assumptions across
.pre-commit-config.yaml, .pre-commit-hooks.yaml,
scripts/check-scope-walkup-sync.sh, scripts/sync-vale-styles.sh,
scripts/check-vale-style-sync.sh, six plugins' root plugin.json
(stale skills/hooks/agents pointer fields that check-manifests.sh
validates), and several tests/*.bats and tests/*.sh fixtures --
including a bats REPO_ROOT relative-path depth bug (10 files, one
extra .apm/ directory level to walk up) and a vale probe-path
isolation regression introduced mid-fix.
- Corrected empirically-wrong assumptions surfaced this session in
apm-workflow/apm-install's own reference docs: `apm marketplace
package add` does not accept local paths (only owner/repo remote
shorthand -- local packages are registered by editing apm.yml's
marketplace.packages[] directly); `apm compile` is a consumer-side
AGENTS.md/CLAUDE.md generator, not the plugin.json producer, and
hard-fails on skill/agent-only packages without --clean; `apm plugin
init <name>` nests a stray subdirectory when run with a positional
name arg from inside a same-named directory; no native Copilot
marketplace output profile exists; .mcp.json is merged into the
compiled plugin.json content-aware and target-scoped, with no
dependencies.mcp entry needed for simple passthrough; pipx is the
correct pip fallback on externally-managed Python environments.
- Renamed agent-author's copilot.agent.md template asset to
copilot.agent.md.template so apm compile's recursive *.agent.md glob
stops misparsing the placeholder template as a real agent primitive.
Impact:
plugin.json and marketplace.json are compiled artifacts from here on --
editing them by hand is no longer the workflow; edit apm.yml/.apm/ and
run apm pack. CONTEXT.md's Plugin/Plugin marketplace glossary entries
reflect this. ADR-0001 is marked superseded, ADR-0006 moot, and
ADR-0010 updated for the new .apm/agents/ path (project/user scope
unaffected, per ADR-0016). Full local verification: claude plugin
validate --strict on all 6 plugins, apm audit --ci, apm marketplace
check, check-manifests.sh, and the full test suite (165/165 bats,
13/13 shell scripts) all pass clean.
Fixes: #90
Refs: #88, #89
ADR: 0015
ADR: 0016
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub96PyaSRD9BHPktotj1pC
field-inventory.md's apm-agent-allowlist and validate.sh's runtime
check already included source_keys as a 4th allowed field, and the
apm-agent.md template already instructed authors to add it for
provenance tracking — but SKILL.md (x2), README.md, ADR-0016, and
deployment-modes.md still described the allowlist as name/description/
model, "nothing else". The template itself even contradicted its own
source_keys guidance with a header claiming "ONLY the three fields
below — full stop" directly above it.
Updates all six locations to document source_keys as the intentional
4th field, resolving the contradiction.
Plugin scope now authors a single vendor-neutral .apm/agents/<name>.agent.md
file (name/description/model/body only) instead of a Claude Code + Copilot
CLI pair, per ADR-0016: apm compile has no per-target field integrator, so
tools: and all Claude-only fields (isolation/maxTurns/effort/memory/
permissionMode) are omitted entirely rather than shipping a value that's
guaranteed wrong on one harness. Scope detection switches from a flat
plugin.json check to the same apm.yml+type: walk-up agent-audit's
validate.sh already uses. Project scope and user scope are unaffected —
both keep the existing dual-file pair model.
Refs: #89
Corrects three FAIL findings from the skill-audit run:
- Scope detection table in SKILL.md and references/scripts.md now lists all
four plugin-marker variants the script actually checks (plugin.json,
.claude-plugin/plugin.json, .plugin/plugin.json, .github/plugin/plugin.json)
- assets/README.md copilot.agent.md description was wrong about field set;
replaced with accurate CLI-format description noting excluded cloud/IDE fields
and the Copilot tool aliases actually used.
Also applies the SUGGESTION: moves the conditional reference
(`If the destination is a plugin directory, read references/deployment-modes.md`)
out of the ## Gotchas section body and into ## Route as a standalone line,
immediately after ## Gotchas closes.
INFO findings (source_keys frontmatter) were already present in both
references/README.md and references/scripts.md — no change needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0147vXtL5sP6vorDdqXGJJU9
## Why
skill-author explicitly excludes agent definition files ("Do not use to author
agent definition files"). No factory skill existed to create or improve the
.md / .agent.md files that define Claude Code subagents and Copilot CLI agents
in a plugin, project, or user scope. This fills that gap.
## Implementation Notes
Single-root scaffold convention: new-agent.sh <name> <root> derives both
provider file paths from the root by convention — plugin scope (plugin.json
present) writes both files into <root>/agents/; non-plugin scope writes
.claude/agents/<name>.md and .github/agents/<name>.agent.md. This keeps
input minimal while always generating both provider files. See ADR-0015.
Routing is file-level (not directory-level like skill-author): neither file
exists → create flow; at least one exists → improve flow; scaffold is a
file-by-file no-op so retries are safe.
No companion agent-audit skill — inline validation in the close step covers
the simpler agent field contract. agent-audit is tracked as a follow-on.
## Impact
Closes the skill-author gap for agent definitions. Follow-ons tracked in
Gitea #11: agent-audit skill and --copilot-dest override flag for non-standard
Copilot project paths.
---
ADR: docs/adr/0015-agent-author-dual-provider-scaffold.md
Refs: #10
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>