AGENTS.md · diff
git:20260903.f199530 to git:20260911.37b054b
12 added, 0 removed. Audit A to A.
# AGENTS.md
This file provides guidance to OpenAI Codex when working in this repository.
## What this is
A package manager for AI coding-agent skills—npm/Homebrew, but the install target is multiple
heterogeneous coding agents (Claude Code, Codex) rather than one runtime. That single fact drives
the whole architecture.
Deep background lives in [ARCHITECTURE.md](ARCHITECTURE.md) (how the code is shaped and why) and
[DESIGN.md](DESIGN.md) (the artefacts: manifest, registry protocol, adapter contract, CLI UX).
Read both before any structural change.
## Commands
```bash
npm run verify # everything CI runs; run before claiming completion
npm run build # tsc --build across all 7 packages (project references)
npm run test:only # tests without rebuilding
npm test # build + tests
npm run lint # eslint
npm run format # prettier --write
npm run check:boundaries # architecture dependency rule
npm run registry:build # regenerate registry/skills.yaml
```
Run the CLI from source:
```bash
npm run agent-skills -- install java-performance --dry-run
node packages/cli/bin/agent-skills.mjs --help
```
Run a single test file or test:
```bash
node --test packages/core/test/resolver.test.ts
node --test --test-name-pattern="detects a cycle" packages/core/test/resolver.test.ts
```
Tests use `node:test` with no test framework and run TypeScript sources directly via Node's native
type stripping. `test:only` skips the build; cross-package imports (`@jvm-expert/core`) still
resolve to `dist`, so build first after changing another package.
## TypeScript constraints
`erasableSyntaxOnly` is enabled because Node executes the sources directly in tests:
- Do not use `enum`; use a `const` object with `as const` plus a union type. See
`domain/errors.ts`.
- Do not use parameter properties. Declare the field and assign it in the constructor body.
- Do not use `namespace` or non-declare class-field initialization tricks.
- Relative imports carry the `.ts` extension (`./errors.ts`, not `./errors.js`).
`rewriteRelativeImportExtensions` emits `.js` at build time.
## Architecture
Seven packages follow one dependency rule:
```text
cli ──────► core ◄────── registry
│ ▲ ▲
│ installer adapter-claude
│ adapter-codex
└──────► node ─────────────┘
```
| Package | Owns | May import |
| ---------------------------------- | -------------------------------------------------------------------- | --------------------- |
| `core` | Domain model, application services, all port interfaces | nothing that does I/O |
| `node` | The only place `node:fs`, `child_process`, `fetch`, and `tar` appear | core + built-ins |
| `registry` | Local/Git/HTTP drivers + precedence federation | core |
| `installer` | Atomic install, path safety, archive extraction | core |
| `adapter-claude` / `adapter-codex` | One agent's layout + detection | core (+ `node:path`) |
| `cli` | Commander, rendering, composition root | everything |
`npm run check:boundaries` enforces this in CI. `core` may not import `node:fs` or `node:path`;
the domain carries its own POSIX path helper (`domain/posix-path.ts`) so behavior is identical on
every OS. When adding a package, register it in `scripts/check-boundaries.mjs` or it is silently
unchecked.
### Invariants
1. **Only `AtomicInstaller` writes into an agent's skill directory.** `AgentAdapter` deliberately
has no `install()` or `uninstall()`. Adapters describe the target (`locationFor`) and project
the package (`layoutFor`, a pure function returning a plan). Every write goes through one
hardened commit path so atomicity, rollback, ownership tracking, and path-traversal defense
exist once.
2. **No agent-specific logic outside `packages/adapter-*`.** If code outside those packages needs
`if (agent === 'codex')`, reconsider the design. Per-agent presentation metadata belongs in
manifest `agentOverrides`; each adapter allowlists its keys through `overrideKeys`.
3. **Path safety has exactly one implementation:** `core/domain/path-safety.ts`. Both validation
and archive extraction wrap it. Do not create a second implementation.
### Where things live
- Ports: `packages/core/src/ports/`. They are intentionally small; adding a method is a design
decision.
- Application services: `packages/core/src/application/`. They own ordering and policy and do no
I/O themselves.
- Composition root: `packages/cli/src/container.ts`. This is the only file that constructs
concrete classes. Register a new adapter with one `.register(...)` call here.
- Test doubles: `@jvm-expert/core/testing` (`InMemoryFileSystem`, `FakeRegistry`, `buildPackage`,
etc.). They are exported so out-of-tree adapters can use them. Core's tests cannot import
`@jvm-expert/registry`, so use `core/test/helpers/federation-double.ts` there.
## Protect real agent configuration
Never allow a test or manual installation run to touch the user's real agent configuration.
Point all relevant locations at an isolated temporary directory:
```bash
export AGENT_SKILLS_HOME="$SANDBOX/.agent-skills"
export CLAUDE_CONFIG_DIR="$SANDBOX/.claude"
export CODEX_HOME="$SANDBOX/.codex"
export HOME="$SANDBOX" USERPROFILE="$SANDBOX"
```
On PowerShell, set the equivalent process-scoped environment variables. Use task-specific variable
names rather than overwriting shell variables globally, and restore inherited values after a
manual run if the shell persists.
Set `HOME`/`USERPROFILE` in the isolated child process as well as the three overrides: project-root
discovery walks upward from the working directory and must know where "home" is. Otherwise a run
from a temporary directory beneath the real home can resolve project scope to the user's home and
write into actual agent configuration.
`packages/cli/test/cli.test.ts` handles this correctly. Reuse its `cli()` helper; it also clears
`PATH` so a real `claude` or `codex` binary cannot affect detection.
## Agent conventions are verified facts
| Agent | Kind | Global | Project |
| ----------- | -------- | ---------------------------------------------------- | ----------------------------- |
| Claude Code | skill | `$CLAUDE_CONFIG_DIR/skills` → `~/.claude/skills` | `<project>/.claude/skills` |
| Claude Code | command | `$CLAUDE_CONFIG_DIR/commands/<name>.md` | `<project>/.claude/commands` |
| Claude Code | workflow | `$CLAUDE_CONFIG_DIR/workflows/<name>.js` | `<project>/.claude/workflows` |
| Codex | skill | `$CODEX_HOME/skills` → `~/.codex/skills` | `<project>/.agents/skills` |
| Codex | command | none; `locationFor` returns `undefined` (unverified) | none |
| Codex | workflow | none; `locationFor` returns `undefined` (unverified) | none |
A package declares `kind` in `skill.yaml`: `skill` (default), `command`, or `workflow`. Its
entrypoint is respectively `SKILL.md`, `COMMAND.md`, or `WORKFLOW.js`. A skill installs as a
directory; commands and workflows install as a single file named after the package.
`AgentLocation.shape` carries that distinction, and every shape commits through the same rename in
`AtomicInstaller`.
A workflow's identity comes from `export const meta = { ... }`, read by the literal parser in
`domain/js-literal.ts` and never executed. `parseEntrypoint` normalizes all forms to one
`SkillDocument`; validation, search, and `info` therefore do not branch on kind. Claude Code's
workflow determinism rules (`Date.now()`, `Math.random()`, and `new Date()` are forbidden) belong
in the Claude adapter.
Codex's global path was extracted from the `codex` binary. Documentation that cites
`~/.agents/skills` describes the repository convention, not the user-global location. Do not
"fix" this. Skill roots are configurable per agent through `agents.<id>.globalRoot` and
`projectRoot`; these settings name the skills root itself.
When adding an agent, verify behavior against the real binary and follow
[docs/adding-an-agent.md](docs/adding-an-agent.md).
## Generated files and versions
`registry/skills.yaml` is generated by `scripts/build-registry-index.mjs` and intentionally listed
in `.prettierignore`; its formatting belongs to the repository's YAML serializer.
Package integrity hashes file contents, so every edit under `skills/` changes integrity, including
format-only changes. After touching `skills/`:
```bash
npm run registry:build
```
Also bump the skill's `version` whenever its contents change. Published versions are immutable:
without a bump, lockfiles pinned to the old hash fail verification and existing installations do
not receive the update. `npm run check:versions`, included in `verify`, enforces this against the
last committed `registry/skills.yaml`.
`registry:build` also checks cross-package constraints: dependency ranges must resolve against
published index versions, and the `SKILL.md` description must equal the manifest description.
Only the manifest description ships.
## Public contracts
Treat these as breaking-change boundaries:
- `ASK_*` error codes in `core/src/domain/errors.ts` appear in JSON output and
[docs/errors.md](docs/errors.md). Never change an existing code's meaning; new codes may be
added.
- Exit codes distinguish usage (2), validation (3), resolution (4), security (5), and no-agent
(6). Do not collapse them to 1.
- `schemaVersion` applies to manifests, indexes, and lockfiles. Additive fields remain optional
under the current version; bump it only for a genuinely breaking change.
## Repository conventions
- Errors: throw `AgentSkillsError` with a code, `details`, and concrete command `hints`.
Translate infrastructure errors at the port boundary; higher layers must not interpret
`ENOENT`.
- Validation: return every discoverable problem through `IssueCollector`; throw only when the
input cannot be loaded at all.
- Commits: use Conventional Commits, for example `feat(installer): ...`. See
[CONTRIBUTING.md](CONTRIBUTING.md).
- Security-relevant changes require an adversarial test (a hostile fixture), not only a happy-path
test. Follow `packages/installer/test/security.test.ts`.
## Codex working rules
+ ### Parallel skill reviews
+
+ When the user requests reviews of multiple skills, delegate one skill per agent using
+ the `skill-reviewer` custom agent in `.codex/agents/skill-reviewer.toml` and follow
+ [the parallel review workflow](docs/parallel-skill-reviews.md). Each reviewer must read
+ and execute [the complete review prompt](docs/skill-review-prompt.md).
+ The coordinator alone regenerates the index and runs the final `npm run verify` after
+ all reviewers have stopped writing; this is the parallel-run exception to those steps
+ in the individual prompt. Configuration requests alone do not start catalog reviews.
+
+ ### General rules
+
- Inspect the relevant implementation, tests, and package-level documentation before editing.
- Keep changes scoped to the user's request. Preserve unrelated user changes and avoid broad
formatting or cleanup.
- Use `rg`/`rg --files` for repository searches and `apply_patch` for hand edits.
- Do not edit generated `registry/skills.yaml` by hand.
- Run the narrowest useful checks while developing and `npm run verify` before claiming the work
is complete. If a check cannot run, report that explicitly rather than implying it passed.
- Read command output, not only exit status: a successful test command can still execute zero
tests or skip relevant work.
- Never weaken or remove a test merely to make a build pass.
- Do not commit, publish, install globally, or modify real agent configuration unless the user
explicitly requests it.