You are working on agent customization — the feature that reads a project's skills and AGENTS.md, then uses Claude CLI to inject project-specific context into generic agent files in .claude/agents/.
Domain purpose
aspens customize agents makes generic, bundled agent templates project-aware. It pulls the repo's skills + AGENTS.md as ground truth and asks Claude to add a tech-stack line, 3-5 project conventions, and real commands into each agent — without touching the agent's core logic.
Business rules / invariants
- Claude-only feature. Throws
CliErrorfor Codex-only repos (config.targets === ['codex']). Codex CLI has no agent concept. - Base skill is required. Pre-flight throws
CliError("Run 'aspens doc init' first — base skill is required for agent context.")if.agents/skills/base/SKILL.mdis missing. - Skills (
.claude/skills/**) are the single source of truth for project context. The prompt must not invent other context directories. - Read-only tools only. Claude is invoked with
allowedTools: ['Read', 'Glob', 'Grep']— no edits/writes from the LLM itself. - Output paths restricted to
.claude/.parseFileOutput()rejects anything else;writeSkillFiles(..., { force: true })does the actual write.
Non-obvious behaviors
- Frontmatter preservation is split across LLM + code. The prompt instructs Claude to preserve YAML frontmatter verbatim (including NOT adding a
skills:line). ThenmaybeInjectBaseSkill()post-processes each returned file to addskills: [base]into the frontmatter — this keeps agents valid even when installed viaaspens add agentwithout a priordoc init. --resetsemantics: without--reset, agents that already declareskills:are left alone; with--reset, any existingskills:line is overwritten toskills: [base]. Used to roll out v0.8 upgrades to previously-customized agents.## Project contextblock is verbatim-preserved by the prompt — it carries conditional read instructions for code-map / domain skills.- AGENTS.md is truncated at 3000 chars in
gatherProjectContext(); skills are passed in full. - Agent discovery:
findAgents()recursively walks.claude/agents/, extractsname:via regex, falls back to filename if missing. - Default timeout 300s via
resolveTimeout(options.timeout, 300);ASPENS_TIMEOUTenv var honored with warning on invalid value.
Critical files (purpose, not inventory)
src/commands/customize.js— orchestrator: preflight, agent discovery, context gathering, per-agent Claude calls, post-LLMskills: [base]injection, write.src/prompts/customize-agents.md— system prompt; enforces frontmatter +## Project contextpreservation and bans file-inventory / hub-ranking output.
Critical Rules
- Never let the LLM emit a
skills:line — the prompt forbids it and the code adds it. If you change one, change both. - Never weaken path sanitization — only
.claude/paths may be written. - Never duplicate file-inventory or hub-ranking output in customized agents — the graph hook supplies that dynamically.
- Do not bypass the base-skill preflight — agents without base context regress to generic behavior.
Last Updated: 2026-05-11