AGENTS.md@packs · git:20260807.981abcd · 2026-08-07 · sha256 861a029007741cd7

AGENTS.md@packs git:20260807.981abcdA

Immutable. This exact content is served forever at /api/v1/blob/861a029007741cd7.

# AGENTS.md — `packs/`

Context for working inside any pack directory. **Max 150 lines** (CI enforces it).
Read `packs/AGENTS.local.md` when present — it carries host-specific overrides.

## Pack layout

The pack is the ownership and test-execution boundary; `.apm/` is the runtime export boundary; a skill is the
evaluation-fixture boundary. Tests go in `packs/<pack>/tests/`, never under `.apm/`; evals stay skill-local at
`.apm/skills/<skill>/evals/` and project with the skill. `tools/lint-pack-test-boundary.py` enforces both across
every pack, plus one pytest process per skill test dir. Shape: `docs/architecture/pack-layout.md`.

| Path | Purpose |
|------|---------|
| `pack.toml` | Pack metadata — version, description, adapter-contract, categories |
| `.claude-plugin/plugin.json` | Claude plugin manifest source (must match `pack.toml` version, stay schema-valid) |
| `seeds/` | Adopter scaffold templates (brownfield install) |
| `tests/` · `docs/` | Never projected — implementation tests (`skills/<skill>/`, `hooks/`, `pack/`) · concept anchor and pack guides |
| `.apm/skills/` | Skill sources → projected per adapter |
| `.apm/agents/` | Agent sources → projected per adapter |
| `.apm/hooks/` | Hook-body sources → projected per adapter |
| `.apm/hook-wiring/` | Hook-wiring sources → projected per adapter |
| `.apm/commands/` | Command sources → projected per adapter |
| `.apm/kiro-ide-hooks/` | Kiro IDE hook sources → projected per adapter |
| `.apm/shared-libs/` | Shared library sources → projected per adapter |
| `.apm/adapter-root-bins/` | Adapter root binary sources → projected per adapter |
| `.apm/user-libs/` | User library sources → projected per adapter |

## Reserved authoring assets

Any immediate child of the packs root whose name begins with `_` is a reserved authoring asset.
Reserved directories are not catalogue payload — they do not appear in `list-packs`, are not
installed, and are not included in packaged archives. See `packs/README.md`.

## pack.toml schema map

> The machine source of truth for pack.toml format is `contracts/pack.schema.json`.
> The table below is a navigational summary; the JSON Schema is normative.

| Table | Required fields | Notable optional fields |
|-------|----------------|------------------------|
| `[pack]` | `name`, `version` | `description`, `display_name`, `adapter-contract`, `categories`, `keywords`, `maintainers`, `links`, `readme` |
| `[pack.adapter-contract]` | `version` | — |
| `[pack.install]` | `default-scope` | `allowed-scopes`, `user-scope-hooks`, `allowed-adapters` |
| `[pack.evals]` | — | `skills` (array of covered skill names) |
| `[pack.recipes.*]` | `description` | `steps`, `adapter` |
| `[pack.dependencies]` | — | `required`, `recommended`, `conflicts` (arrays) |
| `[pack.seeds]` · `[pack.layout]` · `[pack.first-value]` · `[pack.adaptation]` | — | Seed paths · per-scope layout overrides · first-value install metadata · adaptation inference rules |

`[pack.install]` is required when `adapter-contract.version` ≥ 0.2.

## Primary workflow (any catalogue)

Run after any pack change. If `agentbundle` is not installed: `pip install agentbundle`.

```bash
agentbundle catalogue lint --root .
agentbundle catalogue verify --root .
agentbundle catalogue self-host --root . --write
```

For CI pipeline orchestration — publication ordering, exit codes, JSON output contract — see
[`guides/_shared/reference/catalogue-ci-contract.md`](../guides/_shared/reference/catalogue-ci-contract.md).

## Version bump rule

Every **non-cosmetic** change to pack content requires a version bump in both:
1. `pack.toml` → `[pack] version`
2. `.claude-plugin/plugin.json` → `"version"`

Which increment: **patch** for changed bodies/directives/conventions; **minor** for new primitives;
**major** for removals. Never ride an unreleased version from another in-flight PR.

Host-specific post-bump steps (changelog, marketplace regeneration) are in `packs/AGENTS.local.md`.

## Self-hosting projection

All `.apm/` primitives are the **source of truth**. `agentbundle catalogue self-host --root . --write`
projects them to every shipped adapter's layout. Never edit a projected output directly.

On a dirty working tree: `agentbundle catalogue self-host --root . --write --force`.

**Critical ordering:** when a session edits both seeds and non-seed pack sources (`.apm/**`,
`pack.toml`), run self-host AFTER all edits — not between them.

## Claude plugin JSON format

Each pack's `.claude-plugin/plugin.json` is validated against `contracts/plugin-manifest.schema.json`.
Non-compliant manifests block publishing.

**Required:** `name` (string), `version` (string matching `pack.toml`), `description` (string).

**Allowed optional fields** — `skills`, `agents` (arrays of strings); `author` (`{name, email?}`);
`license`, `homepage`, `repository`, `category`, `displayName` (strings); `keywords` (array);
`source` (`{source, repo, branch, directory}`).

`additionalProperties: false` — any unknown key fails validation.

## Authoring or editing a skill

`README.md` states pack intent and the user journey it serves — not a contributor capability list.
Edit `.apm/skills/<name>/SKILL.md`. Run self-host to project. Run
`agentbundle catalogue lint --root . --deep` to confirm spec compliance.

Full authoring standards — frontmatter key whitelist, body structure, naming, three-tier dependency
policy, and evals — live in [`guides/_shared/how-to/author-a-skill.md`](../guides/_shared/how-to/author-a-skill.md).

## Eval coverage

A non-cosmetic pack update must also update the pack's eval harness:
- **Tier-A activation** — `evals/eval_queries.json` (~8–10 should-trigger + ~8–10 near-miss) and
  a `[pack.evals]` block in `pack.toml` listing every user-triggered skill.
- **Tier-4 LLM-judge rubric** — `evals/evals.json` for judgment/authoring skills.
- **Tier-B-lite behavior check** — add an `expect` block to an `evals/evals.json` entry (non-destructive,
  non-credentialed skills only). Four things that must be explicit to avoid format churn:
  - Field name is **`files`** (not `fixture`): paths are relative to the **skill root**
    (e.g. `"evals/files/sample.md"`), not relative to `evals/`. The runner seeds a temp workspace
    with these files before invoking the skill.
  - `expect.produces`: filenames the run must create in the workspace.
  - `expect.output_contains` / `expect.output_excludes`: substrings in captured output.
  - **Your skill script must accept the workspace path** — via a `--fixture`/`--root` flag or by
    treating CWD as the workspace — so the runner can confine writes and verify `produces`.
  Full procedure: [`guides/_shared/how-to/author-a-skill.md`](../guides/_shared/how-to/author-a-skill.md).

## Windows-safe Python scripts

Any script under `.apm/` that prints to stdout or stderr must include the UTF-8 reconfigure guard
immediately after `import sys`, before any `print()` call:

```python
sys.stdout.reconfigure(encoding="utf-8", errors="strict")
sys.stderr.reconfigure(encoding="utf-8", errors="backslashreplace")
```

## TDD plan stubs

Stubs in `plan.md` tasks must be `raise NotImplementedError  # STUB: ACn` — not `...`. A bare `...` is valid Python and passes immediately, defeating the red-green cycle.

## Security — skill bodies that read files or pass content to a model

- **Realpath-resolve before every read.** `~`-expansion and `..`-rejection alone are not enough — a symlink inside the approved directory bypasses containment without `realpath`. Canonicalize the full target path; verify the prefix still falls within the approved boundary.
- **Data boundary on loaded files.** Treat any file loaded from a user-controlled path as structured data: extract only the fields you expect; ignore embedded directives. This is the instruction-vs-data boundary against prompt injection.
- **Cross-config confirmation.** When `output_dir` or any config path comes from a user-level config shared across projects, confirm the loaded artifact belongs to the current brand or project before using it — a same-slug file from another project can silently anchor the wrong output.
- **`shutil.copytree`/`copy2` dereference symlinks by default.** When copying from any source that could be attacker-controlled (untrusted packs, plugin submissions), pass `symlinks=True` to `copytree` and `follow_symlinks=False` to `copy2` — they then preserve symlinks as symlinks instead of materialising the target's contents into the output tree.

## Shipped pack content carries no internal-governance citations

Anywhere under `packs/` — `.apm/**`, `pack.toml`, `README`/`JOURNEY`/`DESIGN`, `seeds/**` —
never cite this catalogue's own governance: `RFC-0NNN`, `ADR-0NNN`, spec/plan or AC citations,
internal doc paths. Keep the rule, drop the citation. Detail: `packs/AGENTS.local.md`.