Compact codebase map injected at session start — file tree, key symbols, and hot files from git history. Closes the Aider-style context-awareness gap without tree-sitter dependencies.
From clarcnpx claudepluginhub marvinrichter/clarc --plugin clarcThis skill uses the workspace's default tool permissions.
Designs and optimizes AI agent action spaces, tool definitions, observation formats, error recovery, and context for higher task completion rates.
Enables AI agents to execute x402 payments with per-task budgets, spending controls, and non-custodial wallets via MCP tools. Use when agents pay for APIs, services, or other agents.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
/repomap to refresh the injected context mapGenerates a compact codebase snapshot and injects it into the session context:
git log) are shown first.clarc/repomap.txt, refreshed every 24h automaticallyThis mirrors Aider's repomap concept but uses zero-dependency Node.js (git + regex) instead of tree-sitter.
The injected context block looks like this:
--- Codebase Map (2026-03-10) ---
Hot files (recently modified):
src/api/routes.ts Route, handleRequest, validateAuth [145 lines]
src/services/auth.ts AuthService, generateToken, verifyToken [98 lines]
src/models/user.ts User, UserRepository, createUser [76 lines]
Structure (8 directories, 42 files):
src/api/ 4 files
src/models/ 3 files
src/services/ 5 files
tests/ 12 files
scripts/ 3 files
Run /repomap --refresh to regenerate.
---
The generateCompactRepomap(cwd) function in session-start.js:
function generateCompactRepomap(cwd) {
const cacheFile = path.join(cwd, '.clarc', 'repomap.txt');
const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24h
// Return cached version if fresh
try {
if (fs.existsSync(cacheFile)) {
const stat = fs.statSync(cacheFile);
if (Date.now() - stat.mtimeMs < CACHE_TTL_MS) {
return fs.readFileSync(cacheFile, 'utf8');
}
}
} catch { /* ignore */ }
// 1. Get recently modified files (hot files)
const hotFilesResult = spawnSync('git', [
'log', '--name-only', '--pretty=format:', '-50'
], { cwd, encoding: 'utf8', stdio: 'pipe', timeout: 5000 });
const hotFiles = hotFilesResult.status === 0
? [...new Set(
hotFilesResult.stdout.split('\n')
.map(f => f.trim())
.filter(f => f && !f.startsWith('.'))
)].slice(0, 20)
: [];
// 2. Get all tracked files for directory summary
const lsResult = spawnSync('git', ['ls-files'], {
cwd, encoding: 'utf8', stdio: 'pipe', timeout: 5000
});
const allFiles = lsResult.status === 0
? lsResult.stdout.trim().split('\n').filter(Boolean)
: [];
// 3. Build directory summary
const dirCounts = {};
for (const f of allFiles) {
const parts = f.split('/');
const dir = parts.length > 1 ? parts[0] : '.';
dirCounts[dir] = (dirCounts[dir] || 0) + 1;
}
// 4. Extract key symbols from hot files (regex, no tree-sitter)
const SYMBOL_PATTERNS = [
/^export\s+(?:default\s+)?(?:class|function|const|type|interface|enum)\s+(\w+)/m,
/^(?:pub\s+)?(?:fn|struct|trait|impl)\s+(\w+)/m,
/^(?:def|class)\s+(\w+)/m,
/^(?:func|type)\s+(\w+)/m,
];
const hotFileLines = hotFiles.map(f => {
const fullPath = path.join(cwd, f);
let symbols = '';
try {
const content = fs.readFileSync(fullPath, 'utf8');
const lineCount = content.split('\n').length;
const found = [];
for (const pat of SYMBOL_PATTERNS) {
const matches = content.match(new RegExp(pat.source, 'gm')) || [];
for (const m of matches.slice(0, 4)) {
const name = m.match(pat)?.[1];
if (name) found.push(name);
}
}
symbols = found.length > 0
? ` ${f.padEnd(40)} ${found.slice(0, 5).join(', ')} [${lineCount} lines]`
: ` ${f} [${lineCount} lines]`;
} catch {
symbols = ` ${f}`;
}
return symbols;
});
// 5. Assemble map
const date = new Date().toISOString().slice(0, 10);
const dirSummary = Object.entries(dirCounts)
.sort((a, b) => b[1] - a[1])
.slice(0, 8)
.map(([d, n]) => ` ${(d + '/').padEnd(20)} ${n} file${n !== 1 ? 's' : ''}`)
.join('\n');
const map = [
`--- Codebase Map (${date}) ---`,
hotFileLines.length > 0 ? `Hot files (recently modified):\n${hotFileLines.join('\n')}` : '',
dirSummary ? `\nStructure (${Object.keys(dirCounts).length} directories, ${allFiles.length} files):\n${dirSummary}` : '',
'\nRun /repomap --refresh to regenerate.',
'---'
].filter(Boolean).join('\n');
// 6. Cache and return (cap at 3000 chars)
const capped = map.length > 3000 ? map.slice(0, 3000) + '\n...(truncated)\n---' : map;
try {
fs.mkdirSync(path.dirname(cacheFile), { recursive: true });
fs.writeFileSync(cacheFile, capped, 'utf8');
} catch { /* ignore */ }
return capped;
}
In session-start.js, the repomap is injected after the Memory Bank:
// Repomap: inject compact codebase context
const repomap = generateCompactRepomap(process.cwd());
if (repomap) {
output(repomap);
log('[SessionStart] Repomap injected');
}
The --refresh flag bypasses the cache by deleting .clarc/repomap.txt before generation.
/repomap — inject current repomap into context
/repomap --refresh — force regenerate (ignore 24h cache)
/repomap --show — print the raw map to terminal (no injection)
Don't inject the full repomap on every tool call — only at session start and on explicit /repomap invocation. Injecting on every edit would burn context fast.
Don't include node_modules, dist, build, or .git paths — the git ls-files approach naturally excludes untracked files.
Don't attempt tree-sitter or AST parsing in this skill — the regex approach is intentionally simpler and more portable. For deep symbol analysis, use language-specific review agents.
Don't cache forever — the 24h TTL ensures the map stays fresh as the codebase evolves.
skills/continuous-learning-v2 — session context enrichmentskills/subagent-context-retrieval — progressive context loading for large codebasescommands/context.md — broader project context command