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 20+ (ESM) | Commander | Vitest | es-module-lexer | @clack/prompts | picocolors
Commands
npm test— Run vitest suitenpm start/node bin/cli.js— Run CLIaspens scan [path]— Deterministic repo analysis (no LLM)aspens doc init [path]— Generate skills + hooks + AGENTS.md (--target claude|codex|all,--recommendedfor full recommended setup)aspens doc impact [path]— Show freshness, coverage, and drift of generated context (--applyfor auto-repair,--backend/--model/--timeout/--verbosefor LLM interpretation)aspens doc sync [path]— Incremental skill updates from git diffsaspens doc graph [path]— Rebuild import graph cache (.claude/graph.json)aspens add <type> [name]— Install templates (agents, commands, hooks)aspens customize agents— Inject project context into installed agentsaspens save-tokens [path]— Install token-saving session settings (--recommendedfor no-prompt install,--removeto uninstall)- Debug:
ASPENS_DEBUG=1dumps raw stream events to$TMPDIR/aspens-debug-{stream,codex-stream}.json - Env knob:
ASPENS_TIMEOUT(seconds) overrides default LLM timeout when--timeoutnot 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 generationsrc/lib/runner.js— Claude/Codex CLI wrapper (runClaudefor stream-json,runCodexfor Codex JSONL); also hostsloadPrompt(partial substitution) andparseFileOutput/validateSkillFilessrc/lib/context-builder.js— Assembles repo files into prompt-friendly contextsrc/lib/skill-writer.js— Writes skill files and directory-scoped files, generates skill-rules.json, merges settingssrc/lib/skill-reader.js— Parses skill files, frontmatter,triggers:blocks, legacy activation patterns, keywordssrc/lib/diff-classifier.js— Maps changed files to affected skills for doc-syncsrc/lib/diff-helpers.js— Targeted file diffs and prioritized diff truncation for doc-syncsrc/lib/git-helpers.js— Git repo detection, git root resolution, diff retrieval, log formattingsrc/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, opportunitiessrc/lib/save-tokens.js— Save-tokens config defaults, settings builders, gitignore/readme generatorssrc/lib/timeout.js— Timeout resolution (--timeoutflag >ASPENS_TIMEOUTenv > default)src/lib/errors.js—CliErrorclass (structured errors caught by CLI top-level handler)src/lib/target.js— Target definitions (claude/codex), config persistence (.aspens.json) withsaveTokensfeature config;getAllowedPathsfor multi-target sanitizationsrc/lib/target-transform.js— Transforms Claude-format output to other target formatssrc/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/graphsrc/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}}substitutionsrc/templates/— Bundled agents, commands, hooks, and settings foraspens add/doc init/save-tokens
Critical Conventions
- Pure ESM —
"type": "module"throughout; useimport/export, neverrequire() - es-module-lexer WASM — must
await initbefore callingparse()in graph-builder - Claude CLI execution —
runClaude()spawnsclaude -pwith stream-json; always use--verboseflag with stream-json - Codex CLI execution —
runCodex()spawnscodex exec --json --sandbox read-only --ask-for-approval never --ephemeral; returns{ text, usage }matchingrunClaudeinterface - Stdin with backpressure —
runClaude/runCodexpipe prompts via stdin and respectdrainwhenwrite()returns false; never rewrite to use args (shell length limits) - Path sanitization —
parseFileOutput()restricts writes to.claude/andAGENTS.mdby default; acceptsallowedPathsoverride for multi-target viagetAllowedPaths(targets) - Read-only LLM tools — customize-style commands pass
allowedTools: ['Read', 'Glob', 'Grep']; never broaden without review - Prompt partials —
{{name}}in prompt files resolves tosrc/prompts/partials/name.mdfirst, then falls back to template variables - Target/Backend distinction — Target = output format/location; Backend = which LLM CLI generates content. Config persisted in
.aspens.json. Customize is Claude-only (CliErroriftargets: ['codex']) - Scanner is deterministic — no LLM calls; pure filesystem analysis
- CliError pattern — command handlers throw
CliErrorinstead of callingprocess.exit(); caught at top level inbin/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)src/commands/— Command handlers (scan, doc-init, doc-impact, doc-sync, doc-graph, add, customize, save-tokens)src/lib/— Core library modulessrc/prompts/— Prompt templates + partialssrc/templates/— Installable agents, commands, hooks, settingstests/— Vitest test files
Last Updated: 2026-05-11