Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 33 additions & 26 deletions .agents/skills/agent-customization/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,46 @@
---
name: agent-customization
description: LLM-powered injection of project context into installed agent templates via `aspens customize agents`
triggers:
files:
- src/commands/customize.js
- src/prompts/customize-agents.md
keywords:
- customize
- agents
- subagent
- agent customization
---

## Activation

This skill triggers when editing agent-customization files:
- `src/commands/customize.js`
- `src/prompts/customize-agents.md`
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.

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/`.
## Business rules / invariants
- **Claude-only feature.** Throws `CliError` for 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.md` is 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.

## Key Files
- `src/commands/customize.js` — Main command: finds agents, gathers context, calls Claude, writes results
- `src/prompts/customize-agents.md` — System prompt telling Claude how to customize agents
- `src/lib/runner.js` — `runClaude()`, `loadPrompt()`, `parseFileOutput()` shared across commands
- `src/lib/skill-writer.js` — `writeSkillFiles()` writes parsed output to disk
- `src/lib/timeout.js` — `resolveTimeout()` for timeout handling (default 300s)
## 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). Then `maybeInjectBaseSkill()` post-processes each returned file to add `skills: [base]` into the frontmatter — this keeps agents valid even when installed via `aspens add agent` without a prior `doc init`.
- **`--reset` semantics:** without `--reset`, agents that already declare `skills:` are left alone; with `--reset`, any existing `skills:` line is overwritten to `skills: [base]`. Used to roll out v0.8 upgrades to previously-customized agents.
- **`## Project context` block 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/`, extracts `name:` via regex, falls back to filename if missing.
- **Default timeout 300s** via `resolveTimeout(options.timeout, 300)`; `ASPENS_TIMEOUT` env var honored with warning on invalid value.

## Key Concepts
- **Claude-only feature:** Customize command reads `.aspens.json` and throws `CliError` if repo is configured for Codex-only (`targets: ['codex']`). Codex CLI has no agent concept.
- **Context gathering:** `gatherProjectContext()` reads AGENTS.md (truncated at 3000 chars), all `.claude/skills/**/*.md` in full, and lists `.claude/guidelines/` paths without reading their contents.
- **Agent discovery:** `findAgents()` recursively walks `.claude/agents/`, reads `.md` files, extracts `name:` via regex — falls back to filename if no frontmatter match.
- **Read-only tools:** Claude is invoked with `allowedTools: ['Read', 'Glob', 'Grep']` and no maxTokens cap (unlike doc-init which sets per-call limits).
- **Output parsing:** Claude returns `<file path="...">content</file>` XML tags, parsed by `parseFileOutput()`. Only `.claude/` paths are allowed.
## Critical files (purpose, not inventory)
- `src/commands/customize.js` — orchestrator: preflight, agent discovery, context gathering, per-agent Claude calls, post-LLM `skills: [base]` injection, write.
- `src/prompts/customize-agents.md` — system prompt; enforces frontmatter + `## Project context` preservation and bans file-inventory / hub-ranking output.

## Critical Rules
- **Claude-only** — throws `CliError` for Codex-only repos. Checks `readConfig(repoPath)` for target config.
- **Read-only tools only** — Claude agents never get write tools. All output goes through `parseFileOutput()` → `writeSkillFiles()`.
- **Context truncation** — AGENTS.md is capped at 3000 chars to avoid blowing up prompt size. Skills are read in full.
- **Path safety** — `parseFileOutput()` only allows writes to `.claude/` prefixed paths. Customized agents stay in `.claude/agents/`.
- **Dry-run support** — `--dry-run` flag previews output without writing. Confirmation prompt shown before writes.
- **Model override** — `--model` flag passed through to `runClaude()` for model selection.
- **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-04-02
**Last Updated:** 2026-05-11
20 changes: 2 additions & 18 deletions .agents/skills/architecture/references/code-map.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,6 @@
# Code Map

## Key Files

**Hub files (most depended-on):**
- `src/lib/runner.js` - 9 dependents
- `src/lib/target.js` - 9 dependents
- `src/lib/errors.js` - 8 dependents
- `src/lib/scanner.js` - 8 dependents
- `src/lib/skill-writer.js` - 7 dependents

**Domain clusters:**

| Domain | Files | Top entries |
|--------|-------|-------------|
| src | 45 | `src/commands/doc-init.js`, `src/lib/runner.js`, `src/lib/target.js` |

**High-churn hotspots:**
- `src/commands/doc-init.js` - 35 changes
- `src/commands/doc-sync.js` - 21 changes
- `src/lib/runner.js` - 17 changes
- **src**: `src/lib/target.js`, `src/lib/errors.js`, `src/lib/runner.js`, `src/lib/scanner.js`, `src/lib/skill-reader.js`
- **tests**: `tests/agent-templates-project-context.test.js`, `tests/hook-runtime.test.js`, `tests/no-guidelines-refs.test.js`, `tests/save-tokens-prompt-guard.test.js`

31 changes: 18 additions & 13 deletions .agents/skills/base/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
---
name: base
description: Core conventions, tech stack, and project structure for aspens
---

## Activation

This is a **base skill** that always loads when working in this repository.

triggers:
alwaysActivate: true
---

You are working in **aspens** — a CLI that keeps coding-agent context accurate as your codebase changes. Scans repos, generates project-specific instructions and skills for Claude Code and Codex CLI, and keeps them fresh.

## Tech Stack
Node.js (ESM) | Commander | Vitest | es-module-lexer | @clack/prompts | picocolors
Node.js 20+ (ESM) | Commander | Vitest | es-module-lexer | @clack/prompts | picocolors

## Commands
- `npm test` — Run vitest suite
Expand All @@ -25,27 +21,33 @@ Node.js (ESM) | Commander | Vitest | es-module-lexer | @clack/prompts | picocolo
- `aspens add <type> [name]` — Install templates (agents, commands, hooks)
- `aspens customize agents` — Inject project context into installed agents
- `aspens save-tokens [path]` — Install token-saving session settings (`--recommended` for no-prompt install, `--remove` to uninstall)
- **Debug:** `ASPENS_DEBUG=1` dumps raw stream events to `$TMPDIR/aspens-debug-{stream,codex-stream}.json`
- **Env knob:** `ASPENS_TIMEOUT` (seconds) overrides default LLM timeout when `--timeout` not passed

## Architecture
CLI entry (`bin/cli.js`) → command handlers (`src/commands/`) → lib modules (`src/lib/`)

- `src/lib/scanner.js` — Deterministic repo scanner (languages, frameworks, domains, structure)
- `src/lib/graph-builder.js` — Static import analysis via es-module-lexer (hub files, clusters, priority)
- `src/lib/graph-persistence.js` — Graph serialization, subgraph extraction, code-map + index generation
- `src/lib/runner.js` — Claude/Codex CLI wrapper (`runClaude` for stream-json, `runCodex` for Codex JSONL)
- `src/lib/runner.js` — Claude/Codex CLI wrapper (`runClaude` for stream-json, `runCodex` for Codex JSONL); also hosts `loadPrompt` (partial substitution) and `parseFileOutput`/`validateSkillFiles`
- `src/lib/context-builder.js` — Assembles repo files into prompt-friendly context
- `src/lib/skill-writer.js` — Writes skill files and directory-scoped files, generates skill-rules.json, merges settings
- `src/lib/skill-reader.js` — Parses skill files, frontmatter, activation patterns, keywords
- `src/lib/skill-reader.js` — Parses skill files, frontmatter, `triggers:` blocks, legacy activation patterns, keywords
- `src/lib/diff-classifier.js` — Maps changed files to affected skills for doc-sync
- `src/lib/diff-helpers.js` — Targeted file diffs and prioritized diff truncation for doc-sync
- `src/lib/git-helpers.js` — Git repo detection, git root resolution, diff retrieval, log formatting
- `src/lib/git-hook.js` — Post-commit git hook installation/removal for auto doc-sync (monorepo-aware)
- `src/lib/impact.js` — Context health analysis: domain coverage, hub surfacing, drift detection, hook health, save-tokens health, usefulness summary, value comparison, opportunities
- `src/lib/save-tokens.js` — Save-tokens config defaults, settings builders, gitignore/readme generators
- `src/lib/timeout.js` — Timeout resolution (`--timeout` flag > `ASPENS_TIMEOUT` env > default)
- `src/lib/errors.js` — `CliError` class (structured errors caught by CLI top-level handler)
- `src/lib/target.js` — Target definitions (claude/codex), config persistence (`.aspens.json`) with `saveTokens` feature config
- `src/lib/target.js` — Target definitions (claude/codex), config persistence (`.aspens.json`) with `saveTokens` feature config; `getAllowedPaths` for multi-target sanitization
- `src/lib/target-transform.js` — Transforms Claude-format output to other target formats
- `src/lib/backend.js` — Backend detection and resolution (which CLI generates content)
- `src/lib/path-resolver.js` / `src/lib/source-exts.js` — Source-file extension and path resolution helpers shared by scanner/graph
- `src/lib/parsers/` — Language-specific import parsers (TypeScript, Python)
- `src/lib/frameworks/` — Framework-specific detectors (e.g. Next.js)
- `src/prompts/` — Prompt templates with `{{partial}}` and `{{variable}}` substitution
- `src/templates/` — Bundled agents, commands, hooks, and settings for `aspens add` / `doc init` / `save-tokens`

Expand All @@ -54,12 +56,15 @@ CLI entry (`bin/cli.js`) → command handlers (`src/commands/`) → lib modules
- **es-module-lexer WASM** — must `await init` before calling `parse()` in graph-builder
- **Claude CLI execution** — `runClaude()` spawns `claude -p` with stream-json; always use `--verbose` flag with stream-json
- **Codex CLI execution** — `runCodex()` spawns `codex exec --json --sandbox read-only --ask-for-approval never --ephemeral`; returns `{ text, usage }` matching `runClaude` interface
- **Path sanitization** — `parseFileOutput()` restricts writes to `.claude/` and `AGENTS.md` by default; accepts `allowedPaths` override for multi-target
- **Stdin with backpressure** — `runClaude`/`runCodex` pipe prompts via stdin and respect `drain` when `write()` returns false; never rewrite to use args (shell length limits)
- **Path sanitization** — `parseFileOutput()` restricts writes to `.claude/` and `AGENTS.md` by default; accepts `allowedPaths` override for multi-target via `getAllowedPaths(targets)`
- **Read-only LLM tools** — customize-style commands pass `allowedTools: ['Read', 'Glob', 'Grep']`; never broaden without review
- **Prompt partials** — `{{name}}` in prompt files resolves to `src/prompts/partials/name.md` first, then falls back to template variables
- **Target/Backend distinction** — Target = output format/location; Backend = which LLM CLI generates content. Config persisted in `.aspens.json`
- **Target/Backend distinction** — Target = output format/location; Backend = which LLM CLI generates content. Config persisted in `.aspens.json`. Customize is Claude-only (`CliError` if `targets: ['codex']`)
- **Scanner is deterministic** — no LLM calls; pure filesystem analysis
- **CliError pattern** — command handlers throw `CliError` instead of calling `process.exit()`; caught at top level in `bin/cli.js`
- **Monorepo support** — `getGitRoot()` resolves the actual git root; hooks, sync, and impact scope to the subdirectory project path
- **Verify before claiming** — Never state something is configured/running/done without confirming in-session

## Structure
- `bin/` — CLI entry point (commander setup, CliError handler)
Expand All @@ -70,4 +75,4 @@ CLI entry (`bin/cli.js`) → command handlers (`src/commands/`) → lib modules
- `tests/` — Vitest test files

---
**Last Updated:** 2026-04-09
**Last Updated:** 2026-05-11
43 changes: 23 additions & 20 deletions .agents/skills/claude-runner/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
---
name: claude-runner
description: Claude/Codex CLI execution layer — prompt loading, stream-json parsing, file output extraction, path sanitization, skill file writing, and skill rule generation
---

## Activation

This skill triggers when editing claude-runner files:
- `src/lib/runner.js`
- `src/lib/skill-writer.js`
- `src/lib/skill-reader.js`
- `src/lib/timeout.js`
- `src/prompts/**/*.md`
- `tests/*extract*`, `tests/*parse*`, `tests/*prompt*`, `tests/*skill-writer*`, `tests/*skill-mapper*`, `tests/*timeout*`

triggers:
files:
- src/lib/runner.js
- src/lib/timeout.js
- src/lib/skill-writer.js
- src/lib/skill-reader.js
- src/prompts/**/*.md
keywords:
- runClaude
- runCodex
- runLLM
- stream-json
- codex exec
- parseFileOutput
- sanitizePath
- loadPrompt
- writeSkillFiles
- skill-rules
- mergeSettings
- resolveTimeout
---

You are working on the **CLI execution layer** — the bridge between assembled prompts and the `claude -p` / `codex exec` CLIs, plus skill file I/O.

## Key Files
- `src/lib/runner.js` — `runClaude()`, `runCodex()`, `runLLM()`, `loadPrompt()`, `parseFileOutput()`, `validateSkillFiles()`, `extractResultFromStream()` (exported); `extractResultFromCodexStream()`, `normalizeCodexItemType()`, `collectCodexText()`, `handleStreamEvent()`, `sanitizePath()`, `getCodexExecCapabilities()` (internal)
- `src/lib/skill-writer.js` — `writeSkillFiles()`, `writeTransformedFiles()`, `extractRulesFromSkills()`, `generateDomainPatterns()`, `mergeSettings()`
- `src/lib/skill-reader.js` — `findSkillFiles()`, `parseFrontmatter()`, `parseActivationPatterns()`, `parseKeywords()`, `fileMatchesActivation()`, `getActivationBlock()`, `GENERIC_PATH_SEGMENTS`
- `src/lib/timeout.js` — `resolveTimeout()` — priority: `--timeout` flag > `ASPENS_TIMEOUT` env var > caller fallback
- `src/prompts/` — Markdown prompt templates; `partials/` subdir holds `skill-format.md`, `guideline-format.md`, `examples.md`

## Key Concepts
- **Stream-JSON protocol (Claude):** `runClaude()` always passes `--verbose --output-format stream-json`. Output is NDJSON: `type: 'result'` has final text + usage; `type: 'assistant'` has text/tool_use blocks; `type: 'user'` has tool_result blocks.
- **JSONL protocol (Codex):** `runCodex()` spawns `codex exec --json --sandbox read-only --ephemeral`. The `--ask-for-approval never` flag is **conditionally included** based on capability detection (see below). Prompt is passed via **stdin** (`'-'` placeholder arg) to avoid shell arg length limits. Stdin write happens **after** event handlers are attached so fast failures are captured. Events: `item.completed`/`item.updated` with normalized types.
Expand All @@ -36,6 +37,7 @@ You are working on the **CLI execution layer** — the bridge between assembled
- **Validation:** `validateSkillFiles()` checks for truncation (XML tag collisions), missing frontmatter, missing sections, bad file path references.
- **Skill rules generation:** `extractRulesFromSkills()` reads all skills via `skill-reader.js`, produces `skill-rules.json` (v2.0) with file patterns, keywords, and intent patterns.
- **Domain patterns:** `generateDomainPatterns()` converts file patterns to bash `detect_skill_domain()` function using `BEGIN/END` markers.
- **Trigger parsing precedence:** `parseTriggersFrontmatter(content)` returns `{ filePatterns, keywords, alwaysActivate }` parsed from a `triggers:` block in YAML frontmatter (supports block lists, inline arrays, and `alwaysActivate: true` for the base skill); returns `null` when no `triggers:` key exists. `parseActivationPatterns` and `parseKeywords` prefer this frontmatter when present and fall back to legacy `## Activation` / `Keywords:` line parsing for older skills.
- **Settings merge:** `mergeSettings()` merges aspens hook config into existing `settings.json`. Detects aspens-managed hooks by `ASPENS_HOOK_MARKERS` (`skill-activation-prompt`, `graph-context-prompt`, `post-tool-use-tracker`, `save-tokens-statusline`, `save-tokens-prompt-guard`, `save-tokens-precompact`). Also handles `statusLine` merging — replaces existing statusLine only if the current one is aspens-managed (detected by `isAspensHook`), preserving user-custom statusLine configs. After merging hooks, `dedupeAspensHookEntries()` removes duplicate aspens-managed entries per event type.
- **Directory-scoped writes:** `writeTransformedFiles()` handles files outside `.claude/` (e.g., `src/billing/AGENTS.md`) with explicit path allowlist — only `AGENTS.md`, `AGENTS.md` exact files and `.claude/`, `.agents/`, `.codex/` prefixes are permitted.
- **`findSkillFiles` matching:** Only matches the exact `skillFilename` (e.g., `skill.md` or `SKILL.md`), not arbitrary `.md` files in the skills directory.
Expand All @@ -47,8 +49,9 @@ You are working on the **CLI execution layer** — the bridge between assembled
- **Path sanitization is non-negotiable** — `sanitizePath()` blocks `..` traversal, absolute paths, and any path not in the allowed set.
- **Prompt partials resolve before variables** — `{{skill-format}}` resolves to `partials/skill-format.md` first. If no file, falls through to variable substitution.
- **Timeout resolution:** `resolveTimeout(flagValue, fallbackSeconds)` — `--timeout` flag wins, then `ASPENS_TIMEOUT` env, then caller-provided fallback. Size-based defaults (small: 120s, medium: 300s, large: 600s, very-large: 900s) are set by command handlers, not runner.
- **Disk writes are sanitized** — `writeSkillFiles` and `writeTransformedFiles` pass every payload through `sanitizePublishedContent` so forbidden blocks (`## Activation`, `## Key Files`, hub/cluster/hotspot tables outside `code-map.md`) cannot leak to disk even if an earlier stage missed them.
- **`mergeSettings` preserves non-aspens hooks and statusLine** — identifies aspens hooks by `ASPENS_HOOK_MARKERS` (now includes save-tokens markers), replaces matching entries, preserves everything else. StatusLine only replaced if current one is aspens-managed. Post-merge deduplication ensures no duplicate aspens entries accumulate.
- **Debug mode:** Set `ASPENS_DEBUG=1` to dump raw stream-json to `$TMPDIR/aspens-debug-stream.json` (Claude) or `$TMPDIR/aspens-debug-codex-stream.json` (Codex). Codex also logs exit code and output length to stderr.

---
**Last Updated:** 2026-04-10
**Last Updated:** 2026-05-11
Loading
Loading