| name | caching-strategy |
| description | Cache expensive operations to avoid redundant work across workflow phases. Caches project docs (15min TTL), npm info (60min), grep results (30min), token counts (until file modified), web searches (15min). Auto-triggers when detecting repeated reads of same files or repeated API calls. Saves 20-40% execution time. |
The caching-strategy skill eliminates redundant work by intelligently caching expensive operations across workflow phases, reducing execution time by 20-40%.
Repeated work wastes time and resources:
- Reading docs/project/api-strategy.md 5 times in /plan phase (5× file I/O)
- Searching codebase for "user" pattern 3 times (3× grep execution)
- Fetching npm package info for same package repeatedly (3× network calls)
- Counting tokens in spec.md every phase (5× token calculation)
- Web searching "React hooks best practices" multiple times (3× API calls)
This skill implements smart caching with:
- File read cache: Cache file contents until file modified (mtime check)
- Search result cache: Cache grep/glob results for 30 minutes
- Network request cache: Cache npm/web API calls for 15-60 minutes
- Computed value cache: Cache expensive calculations until inputs change
- Automatic invalidation: TTL expiration + file modification detection
The result: 20-40% faster workflow execution with zero behavior changes (transparent caching).
<quick_start>
<cacheable_operations>
High-value caching targets (biggest time savings):
-
Project documentation reads (15min TTL):
docs/project/api-strategy.md
docs/project/system-architecture.md
docs/project/tech-stack.md
- Read once per phase, not 5× per phase
-
Codebase searches (30min TTL):
- Grep:
"user" in **/*.ts → Cache results
- Glob:
**/components/**/*.tsx → Cache file list
- Repeated in anti-duplication, implementation, review
-
Package registry queries (60min TTL):
- npm info for package versions
- Dependency metadata
- Rarely changes during single workflow
-
Web searches (15min TTL):
- Documentation lookups
- Error message searches
- Best practice research
-
Token counts (until file modified):
- spec.md token count
- plan.md token count
- Recompute only when file changes
</cacheable_operations>
<basic_workflow>
Before caching:
Phase 1 (/plan):
- Read api-strategy.md (250ms)
- Read tech-stack.md (200ms)
- Read api-strategy.md again (250ms) ← Redundant
- Grep "user" in codebase (3s)
Total: 3.7s
After caching:
Phase 1 (/plan):
- Read api-strategy.md (250ms) → Cache
- Read tech-stack.md (200ms) → Cache
- Read api-strategy.md (from cache: 5ms) ← Cached!
- Grep "user" (3s) → Cache
Total: 3.45s saved 250ms (6.7%)
Across multiple phases:
/plan: Read api-strategy.md (250ms) → Cache
/tasks: Read api-strategy.md (from cache: 5ms) ← Saved 245ms
/impl: Read api-strategy.md (from cache: 5ms) ← Saved 245ms
/opt: Read api-strategy.md (from cache: 5ms) ← Saved 245ms
Total saved: 735ms on single file across 4 phases
</basic_workflow>
<immediate_value>
Typical /feature workflow (7 phases):
Without caching:
Phase reads:
- api-strategy.md: 7 reads × 250ms = 1.75s
- tech-stack.md: 5 reads × 200ms = 1s
- spec.md: 10 reads × 150ms = 1.5s
- Grep "user": 3 searches × 3s = 9s
- npm info react: 2 calls × 500ms = 1s
Total redundant work: 14.25s
With caching:
Phase reads:
- api-strategy.md: 1 read (250ms) + 6 cache hits (30ms) = 280ms
- tech-stack.md: 1 read (200ms) + 4 cache hits (20ms) = 220ms
- spec.md: 1 read (150ms) + 9 cache hits (45ms) = 195ms
- Grep "user": 1 search (3s) + 2 cache hits (10ms) = 3.01s
- npm info react: 1 call (500ms) + 1 cache hit (5ms) = 505ms
Total with caching: 4.21s
Time saved: 14.25s - 4.21s = 10.04s (70% reduction)
Savings scale with workflow length:
- Single phase: 5-10% faster
- Full /feature (7 phases): 20-30% faster
- /epic (20+ phases): 30-40% faster
</immediate_value>
</quick_start>
**Detect cacheable operation**
Identify operations that are:
- Idempotent: Same input → Same output
- Expensive: Takes >100ms
- Repeated: Called 2+ times
- Predictable: Output doesn't change rapidly
Cacheable:
- File reads (same file, unchanged content)
- Codebase searches (same pattern, unchanged code)
- API calls (package info, docs, rarely changes)
- Expensive computations (token counts, parsing)
Not cacheable:
- User input (unpredictable)
- Current time/date (changes constantly)
- Random values
- System state (memory, CPU)
- Database queries (data changes frequently)
**Generate cache key**
Create unique key for each cacheable operation:
File reads:
Cache key: `file:${absolutePath}`
Example: "file:/project/docs/api-strategy.md"
Grep searches:
Cache key: `grep:${pattern}:${path}:${options}`
Example: "grep:user:**/*.ts:case-insensitive"
Glob patterns:
Cache key: `glob:${pattern}:${cwd}`
Example: "glob:**/components/**/*.tsx:/project"
npm queries:
Cache key: `npm:${operation}:${package}`
Example: "npm:info:react"
Web searches:
Cache key: `web:${query}:${engine}`
Example: "web:React hooks best practices:google"
Token counts:
Cache key: `tokens:${filePath}:${mtime}`
Example: "tokens:/project/spec.md:1704067200"
See references/cache-key-strategies.md for comprehensive patterns.
**Check cache before executing**
Before expensive operation:
function readFile(path: string): string {
const cacheKey = `file:${path}`;
const cached = cache.get(cacheKey);
if (cached && !isExpired(cached) && !isFileModified(path, cached.mtime)) {
logger.debug('Cache HIT', { key: cacheKey });
return cached.value;
}
logger.debug('Cache MISS', { key: cacheKey });
const content = fs.readFileSync(path, 'utf-8');
const mtime = fs.statSync(path).mtimeMs;
cache.set(cacheKey, {
value: content,
mtime: mtime,
cachedAt: Date.now(),
ttl: 15 * 60 * 1000
});
return content;
}
Cache check logic:
- Generate cache key
- Look up in cache
- If found AND not expired AND input unchanged → Return cached value
- If not found OR expired OR input changed → Execute operation, cache result
**Set appropriate TTL**
Different operations have different freshness requirements:
Immutable (cache indefinitely):
- npm package versions (once published, never changes)
- Historical git commits
- Published documentation versions
Stable (60min TTL):
- npm package metadata (latest version)
- Project documentation (rarely changes during workflow)
- Codebase structure (files/directories)
Dynamic (15min TTL):
- Web search results
- API documentation (may update)
- Error message searches
File-based (cache until modified):
- File reads → Check mtime
- Token counts → Recompute if file changed
- Parsed AST → Recompute if source changed
Session-based (cache for entire workflow):
- User preferences
- Environment variables
- Project configuration
TTL guidelines:
- Too short: Cache miss overhead negates benefits
- Too long: Stale data causes incorrect results
- Sweet spot: Long enough to avoid repeated work, short enough to stay fresh
**Invalidate on changes**
Automatically invalidate cache when inputs change:
File modification:
function isCacheValid(cacheEntry, filePath) {
const currentMtime = fs.statSync(filePath).mtimeMs;
return cacheEntry.mtime === currentMtime;
}
if (!isCacheValid(cached, filePath)) {
cache.delete(cacheKey);
}
TTL expiration:
function isExpired(cacheEntry) {
const age = Date.now() - cacheEntry.cachedAt;
return age > cacheEntry.ttl;
}
Manual invalidation:
onFileSave((filePath) => {
cache.invalidatePattern(`file:${filePath}*`);
cache.invalidatePattern(`grep:*`);
});
onBranchChange(() => {
cache.clear();
});
Dependency invalidation:
onFileChange('spec.md', () => {
cache.delete('tokens:spec.md');
});
**Monitor cache effectiveness**
Track metrics to optimize caching strategy:
Hit rate:
Hit rate = Cache hits / (Cache hits + Cache misses)
Good: >60% hit rate
Great: >80% hit rate
Excellent: >90% hit rate
Time savings:
Time saved = Σ(Cache hit time - Original operation time)
Example:
- 10 file reads from cache (50ms) vs disk (250ms)
- Saved: 10 × (250ms - 50ms) = 2000ms (2 seconds)
Cache size:
Monitor memory usage
- Target: <50MB cache size
- Evict oldest entries if exceeds limit (LRU eviction)
Metrics to log:
{
cacheHits: 145,
cacheMisses: 23,
hitRate: 0.863,
timeSaved: 12450,
cacheSize: 34.2,
topKeys: [
{ key: 'file:api-strategy.md', hits: 24 },
{ key: 'grep:user:**/*.ts', hits: 12 }
]
}
See references/cache-monitoring.md for dashboard setup.
<cache_types>
<file_read_cache>
When to use: Reading same file multiple times in workflow
Implementation:
const fileCache = new Map();
function readFileCached(path: string): string {
const cacheKey = `file:${path}`;
const stat = fs.statSync(path);
const currentMtime = stat.mtimeMs;
const cached = fileCache.get(cacheKey);
if (cached && cached.mtime === currentMtime) {
return cached.content;
}
const content = fs.readFileSync(path, 'utf-8');
fileCache.set(cacheKey, {
content,
mtime: currentMtime,
size: stat.size
});
return content;
}
Use cases:
- Project docs (api-strategy.md, tech-stack.md)
- Spec files (spec.md, plan.md, tasks.md)
- Configuration files (package.json, tsconfig.json)
Invalidation: File mtime changes
Expected hit rate: 70-90% (files read multiple times per phase)
</file_read_cache>
<search_result_cache>
When to use: Repeated grep/glob searches
Implementation:
const searchCache = new Map();
const SEARCH_TTL = 30 * 60 * 1000;
function grepCached(pattern: string, path: string, options: any): string[] {
const cacheKey = `grep:${pattern}:${path}:${JSON.stringify(options)}`;
const cached = searchCache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < SEARCH_TTL) {
return cached.results;
}
const results = execGrep(pattern, path, options);
searchCache.set(cacheKey, {
results,
timestamp: Date.now()
});
return results;
}
Use cases:
- Anti-duplication searches (same pattern multiple times)
- Dependency analysis (finding imports/exports)
- Code review (finding patterns across codebase)
Invalidation: 30min TTL or file modifications in search path
Expected hit rate: 40-60% (searches often repeated in same phase)
</search_result_cache>
<network_request_cache>
When to use: API calls with stable results
Implementation:
const networkCache = new Map();
async function npmInfoCached(packageName: string): Promise<any> {
const cacheKey = `npm:info:${packageName}`;
const NPM_TTL = 60 * 60 * 1000;
const cached = networkCache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < NPM_TTL) {
return cached.data;
}
const data = await execCommand(`npm info ${packageName} --json`);
networkCache.set(cacheKey, {
data,
timestamp: Date.now()
});
return data;
}
Use cases:
- npm package info
- Web documentation fetches
- GitHub API calls (repo info, release data)
Invalidation: 15-60min TTL (depends on data volatility)
Expected hit rate: 50-70% (packages queried multiple times)
</network_request_cache>
<computed_value_cache>
When to use: Expensive calculations with same inputs
Implementation:
const computeCache = new Map();
function countTokensCached(filePath: string): number {
const stat = fs.statSync(filePath);
const cacheKey = `tokens:${filePath}:${stat.mtimeMs}`;
const cached = computeCache.get(cacheKey);
if (cached) {
return cached.count;
}
const content = fs.readFileSync(filePath, 'utf-8');
const count = tokenizer.count(content);
computeCache.set(cacheKey, { count });
return count;
}
Use cases:
- Token counting
- Code parsing/AST generation
- Complexity analysis
- Checksum calculation
Invalidation: Input file mtime changes
Expected hit rate: 60-80% (same files analyzed repeatedly)
</computed_value_cache>
<web_search_cache>
When to use: Web searches for documentation/errors
Implementation:
const webCache = new Map();
const WEB_TTL = 15 * 60 * 1000;
async function webSearchCached(query: string): Promise<any> {
const cacheKey = `web:${query}`;
const cached = webCache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < WEB_TTL) {
return cached.results;
}
const results = await performWebSearch(query);
webCache.set(cacheKey, {
results,
timestamp: Date.now()
});
return results;
}
Use cases:
- Documentation lookups
- Error message searches
- Best practice research
- Library usage examples
Invalidation: 15min TTL
Expected hit rate: 30-50% (searches often unique, but some repeated)
</web_search_cache>
</cache_types>
<auto_trigger_conditions>
<when_to_cache>
Operation characteristics:
- Takes >100ms to execute
- Called 2+ times with same inputs
- Idempotent (same input → same output)
- Results don't change rapidly
Detection patterns:
Repeated file reads:
Phase activity log:
- Read api-strategy.md
- Read tech-stack.md
- Read api-strategy.md ← DUPLICATE (trigger caching)
Repeated searches:
Search history:
- Grep "user" in **/*.ts
- ... (other work)
- Grep "user" in **/*.ts ← DUPLICATE (trigger caching)
Repeated API calls:
Network calls:
- npm info react
- ... (other work)
- npm info react ← DUPLICATE (trigger caching)
Expensive computations:
Computation log:
- Count tokens in spec.md (took 350ms)
- ... (other work)
- Count tokens in spec.md ← EXPENSIVE + DUPLICATE (trigger caching)
</when_to_cache>
<when_not_to_cache>
Operation characteristics:
- Non-idempotent (random, time-based, stateful)
- Fast (<50ms execution time)
- Called once (no repetition)
- Results change frequently
Examples:
Don't cache:
- User input (unpredictable)
Date.now() (always changes)
- Random values (
Math.random())
- Database queries (data changes)
- File writes (side effects)
- Environment variables modified by user
Don't cache (too fast):
- Simple string operations
- Array lookups
- Map/Set operations
- Variable assignments
</when_not_to_cache>
<proactive_caching>
Pre-cache common operations:
At workflow start (/feature):
async function precacheCommonDocs() {
await Promise.all([
readFileCached('docs/project/api-strategy.md'),
readFileCached('docs/project/tech-stack.md'),
readFileCached('docs/project/system-architecture.md')
]);
}
Prefetch based on workflow phase:
async function prefetchForPlanPhase() {
await Promise.all([
readFileCached('docs/project/api-strategy.md'),
readFileCached('docs/project/data-architecture.md'),
grepCached('interface.*Props', '**/*.tsx', {})
]);
}
Warm cache from previous phase:
function warmCacheForTasks() {
readFileCached('specs/NNN-slug/spec.md');
readFileCached('specs/NNN-slug/plan.md');
}
</proactive_caching>
</auto_trigger_conditions>
**Scenario**: /plan phase reads api-strategy.md 5 times
Without caching:
const apiStrategy1 = readFile('docs/project/api-strategy.md');
const apiStrategy2 = readFile('docs/project/api-strategy.md');
const apiStrategy3 = readFile('docs/project/api-strategy.md');
const apiStrategy4 = readFile('docs/project/api-strategy.md');
const apiStrategy5 = readFile('docs/project/api-strategy.md');
Total time: 1250ms (1.25 seconds)
With caching:
const apiStrategy1 = readFileCached('docs/project/api-strategy.md');
const apiStrategy2 = readFileCached('docs/project/api-strategy.md');
const apiStrategy3 = readFileCached('docs/project/api-strategy.md');
const apiStrategy4 = readFileCached('docs/project/api-strategy.md');
const apiStrategy5 = readFileCached('docs/project/api-strategy.md');
Total time: 270ms
Time saved: 980ms (78% reduction)
Cache invalidation:
fs.writeFileSync('docs/project/api-strategy.md', newContent);
const apiStrategy6 = readFileCached('docs/project/api-strategy.md');
**Scenario**: Anti-duplication searches for "user" pattern 3 times
Without caching:
const results1 = grep('"user"', '**/*.ts');
const results2 = grep('"user"', '**/*.ts');
const results3 = grep('"user"', '**/*.ts');
Total time: 9000ms (9 seconds)
With caching:
const results1 = grepCached('"user"', '**/*.ts');
const results2 = grepCached('"user"', '**/*.ts');
const results3 = grepCached('"user"', '**/*.ts');
Total time: 3020ms
Time saved: 5980ms (66% reduction)
Cache invalidation:
fs.writeFileSync('src/services/UserService.ts', content);
cache.invalidatePattern('grep:*');
const results4 = grepCached('"user"', '**/*.ts');
**Scenario**: Dependency curator checks react package 3 times
Without caching:
const info1 = await execCommand('npm info react --json');
const info2 = await execCommand('npm info react --json');
const info3 = await execCommand('npm info react --json');
Total time: 1500ms
With caching:
const info1 = await npmInfoCached('react');
const info2 = await npmInfoCached('react');
const info3 = await npmInfoCached('react');
Total time: 510ms
Time saved: 990ms (66% reduction)
TTL expiration:
const info4 = await npmInfoCached('react');
**Scenario**: Token budget checks across 4 phases
Without caching:
const tokens1 = countTokens('specs/001/spec.md');
const tokens2 = countTokens('specs/001/spec.md');
const tokens3 = countTokens('specs/001/spec.md');
const tokens4 = countTokens('specs/001/spec.md');
Total time: 1400ms (1.4 seconds)
With caching:
const tokens1 = countTokensCached('specs/001/spec.md');
const tokens2 = countTokensCached('specs/001/spec.md');
const tokens3 = countTokensCached('specs/001/spec.md');
const tokens4 = countTokensCached('specs/001/spec.md');
Total time: 365ms
Time saved: 1035ms (74% reduction)
Cache invalidation on file modification:
fs.writeFileSync('specs/001/spec.md', updatedContent);
const tokens5 = countTokensCached('specs/001/spec.md');
<anti_patterns>
<anti_pattern name="caching-non-idempotent-operations">
Problem: Caching operations that change on each call
Bad approach:
const timestamp = cacheable(() => Date.now());
Correct approach:
const timestamp = Date.now();
Rule: Only cache idempotent operations (same input → same output).
</anti_pattern>
<anti_pattern name="overly-aggressive-caching">
Problem: Caching everything, including fast operations
Bad approach:
const uppercased = cacheable((str) => str.toUpperCase());
Correct approach:
const uppercased = str.toUpperCase();
Rule: Only cache operations taking >100ms.
</anti_pattern>
<anti_pattern name="stale-cache-serving">
Problem: Serving stale data because TTL too long
Bad approach:
const FILE_TTL = 24 * 60 * 60 * 1000;
Correct approach:
function isCacheValid(cached, filePath) {
const currentMtime = fs.statSync(filePath).mtimeMs;
return cached.mtime === currentMtime;
}
Rule: For file-based caching, check mtime. For network caching, use appropriate TTL (15-60min).
</anti_pattern>
<anti_pattern name="unbounded-cache-growth">
Problem: Cache grows without limit, exhausts memory
Bad approach:
cache.set(key, value);
Correct approach:
const cache = new LRU({ max: 500, maxSize: 50 * 1024 * 1024 });
Rule: Set cache size limits and use LRU eviction.
</anti_pattern>
<anti_pattern name="ignoring-cache-invalidation">
Problem: Not invalidating cache when inputs change
Bad approach:
fs.writeFileSync('api-strategy.md', newContent);
const cached = readFileCached('api-strategy.md');
Correct approach:
fs.writeFileSync('api-strategy.md', newContent);
cache.invalidate('file:api-strategy.md');
const fresh = readFileCached('api-strategy.md');
Rule: Invalidate cache when inputs change (file mtime, TTL expiration, manual invalidation).
</anti_pattern>
</anti_patterns>
Caching strategy successfully applied when:
- Hit rate: >60% cache hit rate (most operations served from cache)
- Time savings: 20-40% reduction in workflow execution time
- Zero staleness: No stale data served (proper invalidation)
- Memory usage: Cache size <50MB (efficient memory use)
- Transparent: Behavior identical to non-cached version (correctness maintained)
- Measurable: Cache metrics logged (hits, misses, time saved)
</success_indicators>
Track caching effectiveness:
Hit rate metrics:
{
totalRequests: 200,
cacheHits: 145,
cacheMisses: 55,
hitRate: 0.725
}
Time savings:
{
totalTimeWithCache: 12.3,
totalTimeWithoutCache: 18.7,
timeSaved: 6.4,
timeSavedPercent: 34.2
}
Cache size:
{
entries: 347,
sizeBytes: 42_534_912,
sizeMB: 40.6
}
Top cached operations:
{
topKeys: [
{ key: 'file:api-strategy.md', hits: 24, timeSaved: 5800 },
{ key: 'grep:user:**/*.ts', hits: 12, timeSaved: 35800 },
{ key: 'npm:info:react', hits: 8, timeSaved: 3960 }
]
}
<validation_checklist>
Before deploying caching:
<reference_guides>
For deeper topics, see reference files:
Cache key strategies: references/cache-key-strategies.md
- Generating unique cache keys
- Handling complex inputs
- Collision avoidance
Cache monitoring: references/cache-monitoring.md
- Setting up metrics dashboard
- Analyzing cache effectiveness
- Optimizing hit rates
Implementation patterns: references/implementation-patterns.md
- In-memory cache (Map, LRU)
- Persistent cache (Redis, file-based)
- Distributed cache (multi-process)
</reference_guides>
<success_criteria>
The caching-strategy skill is successfully applied when:
- Auto-detection: Repeated operations automatically trigger caching
- Appropriate caching: Only expensive (>100ms), idempotent, repeated operations cached
- Correct TTLs: File-based (mtime check), network (15-60min), computed (until input changes)
- Proper invalidation: Cache invalidated when inputs change (file mtime, TTL, manual)
- High hit rate: >60% of cacheable operations served from cache
- Significant savings: 20-40% reduction in workflow execution time
- Memory efficient: Cache size <50MB, LRU eviction when needed
- Transparent behavior: Cached operations produce identical results to non-cached
- Measurable impact: Metrics show time saved, hit rates, cache effectiveness
- Zero staleness: No stale data served (all invalidation triggers working)
</success_criteria>