| 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. |
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).
Project documentation reads (15min TTL):
docs/project/api-strategy.mddocs/project/system-architecture.mddocs/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
- Grep:
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
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
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
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)
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.
Before expensive operation:
function readFile(path: string): string {
const cacheKey = `file:${path}`;
// Check cache
const cached = cache.get(cacheKey);
if (cached && !isExpired(cached) && !isFileModified(path, cached.mtime)) {
logger.debug('Cache HIT', { key: cacheKey });
return cached.value;
}
// Cache MISS - execute operation
logger.debug('Cache MISS', { key: cacheKey });
const content = fs.readFileSync(path, 'utf-8');
const mtime = fs.statSync(path).mtimeMs;
// Store in cache
cache.set(cacheKey, {
value: content,
mtime: mtime,
cachedAt: Date.now(),
ttl: 15 * 60 * 1000 // 15 minutes
});
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
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
Automatically invalidate cache when inputs change:
File modification:
function isCacheValid(cacheEntry, filePath) {
const currentMtime = fs.statSync(filePath).mtimeMs;
return cacheEntry.mtime === currentMtime;
}
// Before returning cached file content
if (!isCacheValid(cached, filePath)) {
// File modified - invalidate cache
cache.delete(cacheKey);
// Re-read file
}
TTL expiration:
function isExpired(cacheEntry) {
const age = Date.now() - cacheEntry.cachedAt;
return age > cacheEntry.ttl;
}
Manual invalidation:
// When user saves file
onFileSave((filePath) => {
cache.invalidatePattern(`file:${filePath}*`);
cache.invalidatePattern(`grep:*`); // File change may affect search results
});
// When switching git branch
onBranchChange(() => {
cache.clear(); // Full invalidation
});
Dependency invalidation:
// If spec.md changes, invalidate token count
onFileChange('spec.md', () => {
cache.delete('tokens:spec.md');
});
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, // 86.3%
timeSaved: 12450, // 12.45 seconds
cacheSize: 34.2, // MB
topKeys: [
{ key: 'file:api-strategy.md', hits: 24 },
{ key: 'grep:user:**/*.ts', hits: 12 }
]
}
See references/cache-monitoring.md for dashboard setup.
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; // Cache HIT
}
// Cache MISS
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)
Implementation:
const searchCache = new Map();
const SEARCH_TTL = 30 * 60 * 1000; // 30 minutes
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; // Cache HIT
}
// Cache MISS
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)
Implementation:
const networkCache = new Map();
async function npmInfoCached(packageName: string): Promise<any> {
const cacheKey = `npm:info:${packageName}`;
const NPM_TTL = 60 * 60 * 1000; // 60 minutes
const cached = networkCache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < NPM_TTL) {
return cached.data; // Cache HIT
}
// Cache MISS
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)
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; // Cache HIT
}
// Cache MISS
const content = fs.readFileSync(filePath, 'utf-8');
const count = tokenizer.count(content); // Expensive operation
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)
Implementation:
const webCache = new Map();
const WEB_TTL = 15 * 60 * 1000; // 15 minutes
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; // Cache HIT
}
// Cache MISS
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)
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)
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
At workflow start (/feature):
async function precacheCommonDocs() {
// Pre-load project docs (will be needed in /plan, /tasks, /implement)
await Promise.all([
readFileCached('docs/project/api-strategy.md'),
readFileCached('docs/project/tech-stack.md'),
readFileCached('docs/project/system-architecture.md')
]);
// Now cached for all phases
}
Prefetch based on workflow phase:
// When entering /plan phase
async function prefetchForPlanPhase() {
// Plan phase always needs these
await Promise.all([
readFileCached('docs/project/api-strategy.md'),
readFileCached('docs/project/data-architecture.md'),
grepCached('interface.*Props', '**/*.tsx', {}) // Common search
]);
}
Warm cache from previous phase:
// /tasks phase uses output from /plan phase
function warmCacheForTasks() {
// spec.md and plan.md already read in /plan - should be cached
readFileCached('specs/NNN-slug/spec.md');
readFileCached('specs/NNN-slug/plan.md');
}
Without caching:
// /plan phase workflow
const apiStrategy1 = readFile('docs/project/api-strategy.md'); // 250ms (disk read)
// ... analyze versioning strategy
const apiStrategy2 = readFile('docs/project/api-strategy.md'); // 250ms (disk read again)
// ... check deprecation policy
const apiStrategy3 = readFile('docs/project/api-strategy.md'); // 250ms (disk read again)
// ... validate breaking change rules
const apiStrategy4 = readFile('docs/project/api-strategy.md'); // 250ms (disk read again)
// ... generate plan section
const apiStrategy5 = readFile('docs/project/api-strategy.md'); // 250ms (disk read again)
// ... final validation
Total time: 1250ms (1.25 seconds)
With caching:
// First read: Cache MISS (read from disk)
const apiStrategy1 = readFileCached('docs/project/api-strategy.md'); // 250ms
// Store in cache with mtime
// Subsequent reads: Cache HIT (read from memory)
const apiStrategy2 = readFileCached('docs/project/api-strategy.md'); // 5ms
const apiStrategy3 = readFileCached('docs/project/api-strategy.md'); // 5ms
const apiStrategy4 = readFileCached('docs/project/api-strategy.md'); // 5ms
const apiStrategy5 = readFileCached('docs/project/api-strategy.md'); // 5ms
Total time: 270ms
Time saved: 980ms (78% reduction)
Cache invalidation:
// If user edits api-strategy.md during /plan phase
fs.writeFileSync('docs/project/api-strategy.md', newContent);
// Next read detects mtime change
const apiStrategy6 = readFileCached('docs/project/api-strategy.md');
// mtime changed → Cache MISS → Re-read from disk (250ms)
// Update cache with new content
Without caching:
// Task 1: Create user endpoint
const results1 = grep('"user"', '**/*.ts'); // 3000ms (scan entire codebase)
// Task 2: Create user service
const results2 = grep('"user"', '**/*.ts'); // 3000ms (scan again)
// Task 3: Create user model
const results3 = grep('"user"', '**/*.ts'); // 3000ms (scan again)
Total time: 9000ms (9 seconds)
With caching:
// Task 1: Cache MISS (execute grep)
const results1 = grepCached('"user"', '**/*.ts'); // 3000ms
// Store results in cache (30min TTL)
// Task 2: Cache HIT (return cached results)
const results2 = grepCached('"user"', '**/*.ts'); // 10ms
// Task 3: Cache HIT (return cached results)
const results3 = grepCached('"user"', '**/*.ts'); // 10ms
Total time: 3020ms
Time saved: 5980ms (66% reduction)
Cache invalidation:
// If new file created with "user" in it
fs.writeFileSync('src/services/UserService.ts', content);
// Search cache invalidated (codebase changed)
cache.invalidatePattern('grep:*');
// Next grep: Cache MISS (re-execute)
const results4 = grepCached('"user"', '**/*.ts'); // 3000ms
// Picks up new UserService.ts file
Without caching:
// Check current version
const info1 = await execCommand('npm info react --json'); // 500ms (network call)
// Check for vulnerabilities
const info2 = await execCommand('npm info react --json'); // 500ms (network call again)
// Check peer dependencies
const info3 = await execCommand('npm info react --json'); // 500ms (network call again)
Total time: 1500ms
With caching:
// First call: Cache MISS (network call)
const info1 = await npmInfoCached('react'); // 500ms
// Store in cache (60min TTL)
// Subsequent calls: Cache HIT (return cached data)
const info2 = await npmInfoCached('react'); // 5ms
const info3 = await npmInfoCached('react'); // 5ms
Total time: 510ms
Time saved: 990ms (66% reduction)
TTL expiration:
// After 60 minutes (TTL expired)
const info4 = await npmInfoCached('react'); // 500ms (re-fetch)
// Update cache with latest package info
Without caching:
// /plan phase: Check token budget
const tokens1 = countTokens('specs/001/spec.md'); // 350ms (tokenize entire file)
// /tasks phase: Check token budget
const tokens2 = countTokens('specs/001/spec.md'); // 350ms (tokenize again)
// /implement phase: Check token budget
const tokens3 = countTokens('specs/001/spec.md'); // 350ms (tokenize again)
// /optimize phase: Check token budget
const tokens4 = countTokens('specs/001/spec.md'); // 350ms (tokenize again)
Total time: 1400ms (1.4 seconds)
With caching:
// /plan phase: Cache MISS (compute tokens)
const tokens1 = countTokensCached('specs/001/spec.md'); // 350ms
// Cache key includes mtime: "tokens:spec.md:1704067200"
// /tasks phase: Cache HIT (spec.md unchanged)
const tokens2 = countTokensCached('specs/001/spec.md'); // 5ms
// /implement phase: Cache HIT (spec.md unchanged)
const tokens3 = countTokensCached('specs/001/spec.md'); // 5ms
// /optimize phase: Cache HIT (spec.md unchanged)
const tokens4 = countTokensCached('specs/001/spec.md'); // 5ms
Total time: 365ms
Time saved: 1035ms (74% reduction)
Cache invalidation on file modification:
// User updates spec.md during /implement
fs.writeFileSync('specs/001/spec.md', updatedContent);
// mtime changes: 1704067200 → 1704067500
// Next token count: Cache key mismatch
const tokens5 = countTokensCached('specs/001/spec.md');
// New key: "tokens:spec.md:1704067500" (not in cache)
// Cache MISS → Recompute (350ms)
Bad approach:
// Caching timestamp (always changes)
const timestamp = cacheable(() => Date.now()); // WRONG
Correct approach:
// Don't cache non-idempotent operations
const timestamp = Date.now(); // No caching
Rule: Only cache idempotent operations (same input → same output).
Bad approach:
// Caching simple string operations
const uppercased = cacheable((str) => str.toUpperCase()); // WRONG (too fast)
Correct approach:
// Don't cache operations faster than cache overhead
const uppercased = str.toUpperCase(); // Direct call
Rule: Only cache operations taking >100ms.
Bad approach:
// Caching file content for 24 hours
const FILE_TTL = 24 * 60 * 60 * 1000; // WRONG (file may change)
Correct approach:
// Check file mtime instead of long TTL
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).
Bad approach:
// No size limit or eviction policy
cache.set(key, value); // Keeps growing forever
Correct approach:
// LRU cache with size limit
const cache = new LRU({ max: 500, maxSize: 50 * 1024 * 1024 }); // 500 entries, 50MB max
Rule: Set cache size limits and use LRU eviction.
Bad approach:
// File changes but cache not invalidated
fs.writeFileSync('api-strategy.md', newContent);
const cached = readFileCached('api-strategy.md'); // Returns old content!
Correct approach:
// Invalidate on file write
fs.writeFileSync('api-strategy.md', newContent);
cache.invalidate('file:api-strategy.md');
const fresh = readFileCached('api-strategy.md'); // Re-reads new content
Rule: Invalidate cache when inputs change (file mtime, TTL expiration, manual invalidation).
- 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)
Hit rate metrics:
{
totalRequests: 200,
cacheHits: 145,
cacheMisses: 55,
hitRate: 0.725 // 72.5%
}
Time savings:
{
totalTimeWithCache: 12.3, // seconds
totalTimeWithoutCache: 18.7, // seconds (estimated)
timeSaved: 6.4, // seconds (34% reduction)
timeSavedPercent: 34.2
}
Cache size:
{
entries: 347,
sizeBytes: 42_534_912, // 42MB
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 }
]
}
- Operations are idempotent (same input → same output)
- TTLs appropriate for data volatility
- Invalidation triggers configured (file mtime, manual)
- Cache size limits set (prevent memory exhaustion)
- Metrics collection enabled (hit rate, time saved)
- Behavior identical to non-cached (correctness verified)
- Hit rate >60% (confirms caching is effective)
- Time savings >20% (confirms performance benefit)
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)
- 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)