Back to skills
SkillHub ClubShip Full StackFull Stack

context-optimizer

Advanced context management with auto-compaction and dynamic context optimization for DeepSeek's 64k context window. Features intelligent compaction (merging, summarizing, extracting), query-aware relevance scoring, and hierarchical memory system with context archive. Logs optimization events to chat.

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
3,131
Hot score
99
Updated
March 20, 2026
Overall rating
C4.5
Composite score
4.5
Best-practice grade
C62.8

Install command

npx @skill-hub/cli install openclaw-skills-context-optimizer

Repository

openclaw/skills

Skill path: skills/ad2546/context-optimizer

Advanced context management with auto-compaction and dynamic context optimization for DeepSeek's 64k context window. Features intelligent compaction (merging, summarizing, extracting), query-aware relevance scoring, and hierarchical memory system with context archive. Logs optimization events to chat.

Open repository

Best for

Primary workflow: Ship Full Stack.

Technical facets: Full Stack.

Target audience: everyone.

License: Unknown.

Original source

Catalog source: SkillHub Club.

Repository owner: openclaw.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install context-optimizer into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/openclaw/skills before adding context-optimizer to shared team environments
  • Use context-optimizer for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: context-optimizer
description: Advanced context management with auto-compaction and dynamic context optimization for DeepSeek's 64k context window. Features intelligent compaction (merging, summarizing, extracting), query-aware relevance scoring, and hierarchical memory system with context archive. Logs optimization events to chat.
homepage: https://github.com/clawdbot/clawdbot
metadata:
  clawdbot:
    emoji: "🧠"
    requires:
      bins: []
      npm: ["tiktoken", "@xenova/transformers"]
    install:
      - id: npm
        kind: npm
        label: Install Context Pruner dependencies
        command: "cd ~/.clawdbot/skills/context-pruner && npm install"
---

# Context Pruner

Advanced context management optimized for DeepSeek's 64k context window. Provides intelligent pruning, compression, and token optimization to prevent context overflow while preserving important information.

## Key Features

- **DeepSeek-optimized**: Specifically tuned for 64k context window
- **Adaptive pruning**: Multiple strategies based on context usage
- **Semantic deduplication**: Removes redundant information
- **Priority-aware**: Preserves high-value messages
- **Token-efficient**: Minimizes token overhead
- **Real-time monitoring**: Continuous context health tracking

## Quick Start

### Auto-compaction with dynamic context:
```javascript
import { createContextPruner } from './lib/index.js';

const pruner = createContextPruner({
  contextLimit: 64000, // DeepSeek's limit
  autoCompact: true,    // Enable automatic compaction
  dynamicContext: true, // Enable dynamic relevance-based context
  strategies: ['semantic', 'temporal', 'extractive', 'adaptive'],
  queryAwareCompaction: true, // Compact based on current query relevance
});

await pruner.initialize();

// Process messages with auto-compaction and dynamic context
const processed = await pruner.processMessages(messages, currentQuery);

// Get context health status
const status = pruner.getStatus();
console.log(`Context health: ${status.health}, Relevance scores: ${status.relevanceScores}`);

// Manual compaction when needed
const compacted = await pruner.autoCompact(messages, currentQuery);
```

### Archive Retrieval (Hierarchical Memory):
```javascript
// When something isn't in current context, search archive
const archiveResult = await pruner.retrieveFromArchive('query about previous conversation', {
  maxContextTokens: 1000,
  minRelevance: 0.4,
});

if (archiveResult.found) {
  // Add relevant snippets to current context
  const archiveContext = archiveResult.snippets.join('\n\n');
  // Use archiveContext in your prompt
  console.log(`Found ${archiveResult.sources.length} relevant sources`);
  console.log(`Retrieved ${archiveResult.totalTokens} tokens from archive`);
}
```

## Auto-Compaction Strategies

1. **Semantic Compaction**: Merges similar messages instead of removing them
2. **Temporal Compaction**: Summarizes older conversations by time windows  
3. **Extractive Compaction**: Extracts key information from verbose messages
4. **Adaptive Compaction**: Chooses best strategy based on message characteristics
5. **Dynamic Context**: Filters messages based on relevance to current query

## Dynamic Context Management

- **Query-aware Relevance**: Scores messages based on similarity to current query
- **Relevance Decay**: Relevance scores decay over time for older conversations
- **Adaptive Filtering**: Automatically filters low-relevance messages
- **Priority Integration**: Combines message priority with semantic relevance

## Hierarchical Memory System

The context archive provides a RAM vs Storage approach:

- **Current Context (RAM)**: Limited (64k tokens), fast access, auto-compacted
- **Archive (Storage)**: Larger (100MB), slower but searchable
- **Smart Retrieval**: When information isn't in current context, efficiently search archive
- **Selective Loading**: Extract only relevant snippets, not entire documents
- **Automatic Storage**: Compacted content automatically stored in archive

## Configuration

```javascript
{
  contextLimit: 64000, // DeepSeek's context window
  autoCompact: true, // Enable automatic compaction
  compactThreshold: 0.75, // Start compacting at 75% usage
  aggressiveCompactThreshold: 0.9, // Aggressive compaction at 90%
  
  dynamicContext: true, // Enable dynamic context management
  relevanceDecay: 0.95, // Relevance decays 5% per time step
  minRelevanceScore: 0.3, // Minimum relevance to keep
  queryAwareCompaction: true, // Compact based on current query relevance
  
  strategies: ['semantic', 'temporal', 'extractive', 'adaptive'],
  preserveRecent: 10, // Always keep last N messages
  preserveSystem: true, // Always keep system messages
  minSimilarity: 0.85, // Semantic similarity threshold
  
  // Archive settings
  enableArchive: true, // Enable hierarchical memory system
  archivePath: './context-archive',
  archiveSearchLimit: 10,
  archiveMaxSize: 100 * 1024 * 1024, // 100MB
  archiveIndexing: true,
  
  // Chat logging
  logToChat: true, // Log optimization events to chat
  chatLogLevel: 'brief', // 'brief', 'detailed', or 'none'
  chatLogFormat: '📊 {action}: {details}', // Format for chat messages
  
  // Performance
  batchSize: 5, // Messages to process in batch
  maxCompactionRatio: 0.5, // Maximum 50% compaction in one pass
}
```

## Chat Logging

The context optimizer can log events directly to chat:

```javascript
// Example chat log messages:
// 📊 Context optimized: Compacted 15 messages → 8 (47% reduction)
// 📊 Archive search: Found 3 relevant snippets (42% similarity)
// 📊 Dynamic context: Filtered 12 low-relevance messages

// Configure logging:
const pruner = createContextPruner({
  logToChat: true,
  chatLogLevel: 'brief', // Options: 'brief', 'detailed', 'none'
  chatLogFormat: '📊 {action}: {details}',
  
  // Custom log handler (optional)
  onLog: (level, message, data) => {
    if (level === 'info' && data.action === 'compaction') {
      // Send to chat
      console.log(`🧠 Context optimized: ${message}`);
    }
  }
});
```

## Integration with Clawdbot

Add to your Clawdbot config:

```yaml
skills:
  context-pruner:
    enabled: true
    config:
      contextLimit: 64000
      autoPrune: true
```

The pruner will automatically monitor context usage and apply appropriate pruning strategies to stay within DeepSeek's 64k limit.

---

## Referenced Files

> The following files are referenced in this skill and included for context.

### lib/index.js

```javascript
/**
 * Context Pruner - Advanced context management optimized for DeepSeek's 64k context window
 * 
 * Features:
 * - DeepSeek-optimized token counting
 * - Multiple pruning strategies
 * - Semantic deduplication
 * - Adaptive compression based on context usage
 * - Real-time monitoring and health tracking
 * - Hierarchical memory system with context archive
 */

import { get_encoding } from 'tiktoken';
import { pipeline } from '@xenova/transformers';
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';

// Import chat logger from parent directory
import ContextOptimizerLogger from '../chat-logger.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const DEFAULT_CONFIG = {
  // DeepSeek specific
  contextLimit: 64000,
  model: 'deepseek-chat',
  
  // Auto-compaction settings
  autoCompact: true,
  compactThreshold: 0.75,      // Start compacting at 75% usage
  aggressiveCompactThreshold: 0.9, // Aggressive compaction at 90%
  
  // Dynamic context settings
  dynamicContext: true,
  relevanceDecay: 0.95,        // Relevance decays 5% per time step
  minRelevanceScore: 0.3,      // Minimum relevance to keep
  queryAwareCompaction: true,  // Compact based on current query relevance
  
  // Compaction strategies
  strategies: ['semantic', 'temporal', 'extractive', 'adaptive'],
  preserveRecent: 10,          // Always keep last N messages
  preserveSystem: true,        // Always keep system messages
  preserveHighPriority: 8,     // Priority threshold for preservation
  
  // Semantic settings
  minSimilarity: 0.85,
  summarizer: null,
  
  // Tokenizer
  encoding: 'cl100k_base', // DeepSeek uses cl100k_base
  
  // Archive settings
  enableArchive: true,
  archivePath: './context-archive',
  archiveSearchLimit: 10,
  archiveMaxSize: 100 * 1024 * 1024, // 100MB
  archiveCompression: true,
  archiveIndexing: true,
  
  // Performance
  batchSize: 5,               // Messages to process in batch
  maxCompactionRatio: 0.5,    // Maximum 50% compaction in one pass
  
  // Chat logging
  logToChat: true,            // Log optimization events to chat
  chatLogLevel: 'brief',      // 'brief', 'detailed', or 'none'
  chatLogFormat: '🧠 {action}: {details}', // Format for chat messages
  chatLogger: null,           // Custom logger instance
};

/**
 * Context Archive - Hierarchical memory system for storing and retrieving pruned context
 * 
 * Like RAM vs Storage:
 * - Current context = RAM (limited, fast access)
 * - Archive = Storage (larger, slower access)
 * - Efficient search when something isn't in current context
 * - Extract only important parts to bring into RAM
 */
class ContextArchive {
  constructor(config) {
    this.config = config;
    this.archivePath = path.resolve(config.archivePath);
    this.indexPath = path.join(this.archivePath, 'index.json');
    this.embedder = null;
    this.index = {
      entries: [],
      totalSize: 0,
      lastUpdated: null,
      stats: {
        searches: 0,
        hits: 0,
        misses: 0,
        bytesStored: 0,
      }
    };
  }

  async initialize() {
    // Create archive directory if it doesn't exist
    await fs.mkdir(this.archivePath, { recursive: true });
    
    // Load existing index
    try {
      const indexData = await fs.readFile(this.indexPath, 'utf-8');
      this.index = JSON.parse(indexData);
    } catch (error) {
      // Create new index if doesn't exist
      await this.saveIndex();
    }
    
    // Initialize embedding model for semantic search
    if (this.config.archiveIndexing) {
      try {
        const { pipeline } = await import('@xenova/transformers');
        this.embedder = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2');
      } catch (error) {
        console.warn('Failed to load embedding model for archive, semantic search disabled:', error.message);
        this.embedder = null;
      }
    }
    
    return this;
  }

  /**
   * Store pruned content in archive
   */
  async store(content, metadata = {}) {
    const entryId = `entry_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
    const entryPath = path.join(this.archivePath, `${entryId}.json`);
    
    const tokens = metadata.tokens || this.estimateTokens(content);
    const category = metadata.category || 'general';
    const priority = metadata.priority || 5;
    
    console.log(`[Archive] Storing entry: ${entryId}`);
    console.log(`[Archive] Content: "${content.substring(0, 80)}${content.length > 80 ? '...' : ''}"`);
    console.log(`[Archive] Metadata: ${tokens} tokens, category: ${category}, priority: ${priority}`);
    
    const entry = {
      id: entryId,
      content,
      metadata: {
        ...metadata,
        timestamp: Date.now(),
        tokens,
        priority,
        category,
      },
      embedding: null,
      keywords: this.extractKeywords(content),
    };
    
    // Generate embedding if available
    if (this.embedder && this.config.archiveIndexing) {
      try {
        console.log(`[Archive] Generating embedding for entry`);
        entry.embedding = await this.embed(content);
      } catch (error) {
        console.warn('[Archive] Failed to generate embedding for archive entry:', error.message);
      }
    }
    
    // Save entry to file
    await fs.writeFile(entryPath, JSON.stringify(entry, null, 2));
    
    // Update index
    this.index.entries.push({
      id: entryId,
      path: entryPath,
      size: Buffer.byteLength(JSON.stringify(entry)),
      metadata: entry.metadata,
      keywords: entry.keywords,
    });
    
    this.index.totalSize += this.index.entries[this.index.entries.length - 1].size;
    this.index.lastUpdated = Date.now();
    
    // Check if we need to clean up old entries
    await this.cleanupIfNeeded();
    
    // Save updated index
    await this.saveIndex();
    
    this.index.stats.bytesStored += Buffer.byteLength(JSON.stringify(entry));
    console.log(`[Archive] Entry stored successfully: ${entryId}`);
    return entryId;
  }

  /**
   * Search archive for relevant content
   */
  async search(query, options = {}) {
    const {
      limit = this.config.archiveSearchLimit,
      minScore = 0.3,
      categories = [],
      timeRange = null,
    } = options;

    this.index.stats.searches++;
    
    // Extract query keywords
    const queryKeywords = this.extractKeywords(query);
    let queryEmbedding = null;
    
    if (this.embedder) {
      try {
        queryEmbedding = await this.embed(query);
      } catch (error) {
        console.warn('Failed to generate query embedding:', error.message);
      }
    }
    
    const results = [];
    
    for (const entry of this.index.entries) {
      // Load entry data
      let entryData;
      try {
        const entryContent = await fs.readFile(entry.path, 'utf-8');
        entryData = JSON.parse(entryContent);
      } catch (error) {
        console.warn(`Failed to load archive entry ${entry.id}:`, error.message);
        continue;
      }
      
      // Apply filters
      if (categories.length > 0 && !categories.includes(entryData.metadata.category)) {
        continue;
      }
      
      if (timeRange) {
        const age = Date.now() - entryData.metadata.timestamp;
        if (age > timeRange) continue;
      }
      
      // Calculate score
      let score = 0;
      
      // Keyword matching
      const keywordMatches = queryKeywords.filter(kw => 
        entryData.keywords.some(ekw => ekw.toLowerCase().includes(kw.toLowerCase()) ||
                                      kw.toLowerCase().includes(ekw.toLowerCase()))
      ).length;
      
      score += (keywordMatches / Math.max(queryKeywords.length, 1)) * 0.4;
      
      // Semantic similarity if embeddings available
      if (queryEmbedding && entryData.embedding) {
        const similarity = this.cosineSimilarity(queryEmbedding, entryData.embedding);
        score += similarity * 0.6;
      }
      
      if (score >= minScore) {
        results.push({
          id: entryData.id,
          content: entryData.content,
          metadata: entryData.metadata,
          score,
          keywords: entryData.keywords,
        });
      }
    }
    
    // Sort by score (descending)
    results.sort((a, b) => b.score - a.score);
    
    // Limit results
    const limitedResults = results.slice(0, limit);
    
    if (limitedResults.length > 0) {
      this.index.stats.hits++;
    } else {
      this.index.stats.misses++;
    }
    
    return limitedResults;
  }

  /**
   * Extract relevant snippets from archive content
   */
  extractRelevantSnippets(content, query, maxTokens = 500) {
    const sentences = content.match(/[^.!?]+[.!?]+/g) || [content];
    const queryKeywords = this.extractKeywords(query);
    
    const scoredSentences = sentences.map((sentence, index) => {
      let score = 0;
      
      // Keyword matching
      const keywordMatches = queryKeywords.filter(kw => 
        sentence.toLowerCase().includes(kw.toLowerCase())
      ).length;
      
      score += (keywordMatches / Math.max(queryKeywords.length, 1)) * 0.7;
      
      // Position bonus (recent sentences are more relevant)
      score += (1 - index / sentences.length) * 0.3;
      
      return {
        sentence: sentence.trim(),
        score,
        tokens: this.estimateTokens(sentence),
      };
    });
    
    // Sort by score
    scoredSentences.sort((a, b) => b.score - a.score);
    
    // Collect sentences until token limit
    const snippets = [];
    let totalTokens = 0;
    
    for (const { sentence, tokens } of scoredSentences) {
      if (totalTokens + tokens <= maxTokens) {
        snippets.push(sentence);
        totalTokens += tokens;
      } else {
        break;
      }
    }
    
    return {
      snippets,
      totalTokens,
      coverage: snippets.length / sentences.length,
    };
  }

  /**
   * Retrieve and extract relevant information from archive
   */
  async retrieveRelevantInfo(query, options = {}) {
    const {
      maxContextTokens = 1000,
      minRelevance = 0.4,
    } = options;
    
    // Search archive
    const searchResults = await this.search(query, {
      limit: 5,
      minScore: minRelevance,
    });
    
    if (searchResults.length === 0) {
      return {
        found: false,
        snippets: [],
        totalTokens: 0,
        sources: [],
      };
    }
    
    // Extract relevant snippets from each result
    const allSnippets = [];
    const sources = [];
    let totalTokens = 0;
    
    for (const result of searchResults) {
      if (totalTokens >= maxContextTokens) break;
      
      const extraction = this.extractRelevantSnippets(
        result.content,
        query,
        maxContextTokens - totalTokens
      );
      
      if (extraction.snippets.length > 0) {
        allSnippets.push(...extraction.snippets);
        totalTokens += extraction.totalTokens;
        sources.push({
          id: result.id,
          score: result.score,
          metadata: result.metadata,
          snippetsCount: extraction.snippets.length,
          coverage: extraction.coverage,
        });
      }
    }
    
    return {
      found: true,
      snippets: allSnippets,
      totalTokens,
      sources,
      searchStats: {
        totalSearched: searchResults.length,
        totalHits: sources.length,
      },
    };
  }

  /**
   * Cleanup old entries if archive exceeds max size
   */
  async cleanupIfNeeded() {
    if (this.index.totalSize <= this.config.archiveMaxSize) {
      return;
    }
    
    console.log(`Archive size ${this.index.totalSize} exceeds limit ${this.config.archiveMaxSize}, cleaning up...`);
    
    // Sort entries by priority and age (lower priority and older first)
    this.index.entries.sort((a, b) => {
      const priorityDiff = (a.metadata.priority || 5) - (b.metadata.priority || 5);
      if (priorityDiff !== 0) return priorityDiff;
      return a.metadata.timestamp - b.metadata.timestamp;
    });
    
    let removedSize = 0;
    const toRemove = [];
    
    while (this.index.totalSize - removedSize > this.config.archiveMaxSize * 0.9 && this.index.entries.length > 0) {
      const entry = this.index.entries.shift();
      toRemove.push(entry);
      removedSize += entry.size;
    }
    
    // Remove files
    for (const entry of toRemove) {
      try {
        await fs.unlink(entry.path);
      } catch (error) {
        console.warn(`Failed to remove archive entry ${entry.id}:`, error.message);
      }
    }
    
    this.index.totalSize -= removedSize;
    await this.saveIndex();
    
    console.log(`Removed ${toRemove.length} entries, freed ${removedSize} bytes`);
  }

  /**
   * Get archive statistics
   */
  getStats() {
    return {
      ...this.index.stats,
      totalEntries: this.index.entries.length,
      totalSize: this.index.totalSize,
      maxSize: this.config.archiveMaxSize,
      usagePercentage: (this.index.totalSize / this.config.archiveMaxSize) * 100,
      lastUpdated: this.index.lastUpdated,
    };
  }

  /**
   * Clear entire archive
   */
  async clear() {
    // Remove all entry files
    for (const entry of this.index.entries) {
      try {
        await fs.unlink(entry.path);
      } catch (error) {
        // Ignore errors
      }
    }
    
    // Reset index
    this.index = {
      entries: [],
      totalSize: 0,
      lastUpdated: Date.now(),
      stats: {
        searches: 0,
        hits: 0,
        misses: 0,
        bytesStored: 0,
      }
    };
    
    await this.saveIndex();
  }

  // Helper methods
  async embed(text) {
    if (!this.embedder) return null;
    
    try {
      const result = await this.embedder(text, { pooling: 'mean', normalize: true });
      return Array.from(result.data);
    } catch (error) {
      console.warn('Embedding failed:', error.message);
      return null;
    }
  }

  cosineSimilarity(a, b) {
    if (!a || !b || a.length !== b.length) return 0;
    
    let dot = 0, normA = 0, normB = 0;
    for (let i = 0; i < a.length; i++) {
      dot += a[i] * b[i];
      normA += a[i] * a[i];
      normB += b[i] * b[i];
    }
    
    return dot / (Math.sqrt(normA) * Math.sqrt(normB));
  }

  extractKeywords(text, maxKeywords = 10) {
    // Simple keyword extraction: remove common words, take most frequent
    const commonWords = new Set([
      'the', 'be', 'to', 'of', 'and', 'a', 'in', 'that', 'have', 'i',
      'it', 'for', 'not', 'on', 'with', 'he', 'as', 'you', 'do', 'at',
      'this', 'but', 'his', 'by', 'from', 'they', 'we', 'say', 'her', 'she',
      'or', 'an', 'will', 'my', 'one', 'all', 'would', 'there', 'their', 'what',
      'so', 'up', 'out', 'if', 'about', 'who', 'get', 'which', 'go', 'me',
      'when', 'make', 'can', 'like', 'time', 'no', 'just', 'him', 'know', 'take',
      'people', 'into', 'year', 'your', 'good', 'some', 'could', 'them', 'see',
      'other', 'than', 'then', 'now', 'look', 'only', 'come', 'its', 'over',
      'think', 'also', 'back', 'after', 'use', 'two', 'how', 'our', 'work',
      'first', 'well', 'way', 'even', 'new', 'want', 'because', 'any', 'these',
      'give', 'day', 'most', 'us'
    ]);
    
    const words = text.toLowerCase()
      .replace(/[^\w\s]/g, ' ')
      .split(/\s+/)
      .filter(word => word.length > 3 && !commonWords.has(word));
    
    // Count frequency
    const freq = {};
    words.forEach(word => {
      freq[word] = (freq[word] || 0) + 1;
    });
    
    // Get top keywords
    return Object.entries(freq)
      .sort((a, b) => b[1] - a[1])
      .slice(0, maxKeywords)
      .map(([word]) => word);
  }

  estimateTokens(text) {
    // Simple estimation: 4 chars ≈ 1 token for English
    return Math.ceil((text || '').length / 4);
  }

  async saveIndex() {
    await fs.writeFile(this.indexPath, JSON.stringify(this.index, null, 2));
  }
}

export class ContextOptimizer {
  constructor(options = {}) {
    this.config = { ...DEFAULT_CONFIG, ...options };
    this.encoder = get_encoding(this.config.encoding);
    this.embedder = null;
    this.archive = null;
    this.messageHistory = [];
    this.tokenUsage = 0;
    this.relevanceScores = new Map(); // Track relevance of each message
    this.lastQuery = null;
    this.lastQueryTime = 0;
    
    // Initialize chat logger
    this.logger = this.config.chatLogger || new ContextOptimizerLogger({
      logToChat: this.config.logToChat,
      chatLogLevel: this.config.chatLogLevel,
      chatLogFormat: this.config.chatLogFormat,
    });
    
    this.stats = {
      totalCompacted: 0,
      totalTokensSaved: 0,
      archiveStores: 0,
      archiveRetrievals: 0,
      archiveAutoRetrievals: 0,
      compactions: 0,
      dynamicAdjustments: 0,
      relevanceUpdates: 0,
      chatLogs: 0,
    };
  }

  async initialize() {
    // Initialize embedding model for semantic deduplication
    try {
      this.embedder = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2');
    } catch (error) {
      console.warn('Failed to load embedding model, semantic pruning disabled:', error.message);
      this.embedder = null;
    }
    
    // Initialize archive if enabled
    if (this.config.enableArchive) {
      this.archive = new ContextArchive(this.config);
      await this.archive.initialize();
    }
    
    return this;
  }

  // ---------------------------------------------------------------------------
  // Core Processing
  // ---------------------------------------------------------------------------

  /**
   * Process messages with automatic pruning based on context usage
   */
  async processMessages(messages) {
    if (!Array.isArray(messages)) {
      throw new Error('messages must be an array');
    }

    // Calculate current token usage
    const currentTokens = this.countTokensForMessages(messages);
    this.tokenUsage = currentTokens;
    
    // Check if pruning is needed
    const usageRatio = currentTokens / this.config.contextLimit;
    const status = this.getHealthStatus(usageRatio);
    
    let processedMessages = [...messages];
    
    // Apply pruning based on usage
    if (this.config.autoPrune) {
      if (status === 'EMERGENCY') {
        processedMessages = await this.emergencyPrune(processedMessages);
      } else if (status === 'PRUNE') {
        processedMessages = await this.prune(processedMessages, {
          targetTokens: this.config.contextLimit * 0.7, // Target 70% usage
          strategy: 'aggressive'
        });
      } else if (status === 'WARNING') {
        processedMessages = await this.prune(processedMessages, {
          targetTokens: this.config.contextLimit * 0.6, // Target 60% usage
          strategy: 'conservative'
        });
      }
    }
    
    // Update message history for temporal analysis
    this.updateMessageHistory(processedMessages);
    
    return processedMessages;
  }

  /**
   * Auto-compact messages based on current context usage
   */
  async autoCompact(messages, currentQuery = null) {
    const currentTokens = this.countTokensForMessages(messages);
    const usageRatio = currentTokens / this.config.contextLimit;
    
    // Check if compaction is needed
    if (usageRatio < this.config.compactThreshold) {
      return messages; // No compaction needed
    }
    
    // Update relevance scores based on current query
    if (currentQuery && this.config.queryAwareCompaction) {
      await this.updateRelevanceScores(messages, currentQuery);
      this.lastQuery = currentQuery;
      this.lastQueryTime = Date.now();
    }
    
    // Determine compaction intensity
    let compactionRatio;
    let strategy;
    
    if (usageRatio >= this.config.aggressiveCompactThreshold) {
      compactionRatio = 0.7; // Aggressive: target 30% of current size
      strategy = 'aggressive';
    } else {
      compactionRatio = 0.5; // Normal: target 50% of current size
      strategy = 'balanced';
    }
    
    const targetTokens = Math.min(
      currentTokens * compactionRatio,
      this.config.contextLimit * this.config.compactThreshold
    );
    
    console.log(`[Auto-Compact] Current: ${currentTokens} tokens, Target: ${targetTokens} tokens, Strategy: ${strategy}`);
    
    // Apply adaptive compaction
    const compacted = await this.adaptiveCompact(messages, targetTokens, strategy, currentQuery);
    
    // Log to chat
    const beforeCount = messages.length;
    const afterCount = compacted.length;
    const reduction = Math.round((1 - afterCount / beforeCount) * 100);
    const tokensSaved = currentTokens - this.countTokensForMessages(compacted);
    
    this.logger.logCompaction(beforeCount, afterCount, strategy);
    this.stats.chatLogs++;
    
    this.stats.compactions++;
    this.stats.totalCompacted += (beforeCount - afterCount);
    this.stats.totalTokensSaved += tokensSaved;
    
    return compacted;
  }

  /**
   * Adaptive compaction that chooses best strategy based on content
   */
  async adaptiveCompact(messages, targetTokens, strategy = 'balanced', currentQuery = null) {
    const currentTokens = this.countTokensForMessages(messages);
    const tokensToSave = currentTokens - targetTokens;
    
    if (tokensToSave <= 0) return messages;
    
    let compactedMessages = [...messages];
    const removedMessages = [];
    let tokensSaved = 0;
    
    // Analyze message characteristics to choose best strategy
    const analysis = this.analyzeMessages(messages);
    
    // Apply strategies based on analysis
    const strategies = this.selectStrategies(analysis, currentQuery);
    
    for (const strategyName of strategies) {
      if (tokensSaved >= tokensToSave) break;
      
      const remainingTokensToSave = tokensToSave - tokensSaved;
      
      switch (strategyName) {
        case 'semantic':
          const semanticResult = await this.applySemanticCompaction(compactedMessages, remainingTokensToSave, strategy, currentQuery);
          compactedMessages = semanticResult.compacted;
          removedMessages.push(...semanticResult.removed);
          tokensSaved += semanticResult.tokensSaved;
          break;
          
        case 'temporal':
          const temporalResult = this.applyTemporalCompaction(compactedMessages, remainingTokensToSave, strategy, currentQuery);
          compactedMessages = temporalResult.compacted;
          removedMessages.push(...temporalResult.removed);
          tokensSaved += temporalResult.tokensSaved;
          break;
          
        case 'extractive':
          const extractiveResult = await this.applyExtractiveCompaction(compactedMessages, remainingTokensToSave, strategy, currentQuery);
          compactedMessages = extractiveResult.compacted;
          removedMessages.push(...extractiveResult.removed);
          tokensSaved += extractiveResult.tokensSaved;
          break;
          
        case 'adaptive':
          const adaptiveResult = await this.applyAdaptiveCompaction(compactedMessages, remainingTokensToSave, strategy, currentQuery);
          compactedMessages = adaptiveResult.compacted;
          removedMessages.push(...adaptiveResult.removed);
          tokensSaved += adaptiveResult.tokensSaved;
          break;
      }
    }
    
    // Store removed messages in archive
    if (this.archive && removedMessages.length > 0) {
      await this.storeRemovedInArchive(removedMessages, strategy);
    }
    
    return compactedMessages;
  }

  /**
   * Process messages with auto-compaction
   */
  async processMessages(messages, currentQuery = null) {
    await this.initialize();
    
    const startTime = Date.now();
    const initialTokens = this.countTokensForMessages(messages);
    const initialCount = messages.length;
    
    console.log(`[Context-Optimizer] Starting processing: ${initialCount} messages, ${initialTokens} tokens`);
    if (currentQuery) {
      console.log(`[Context-Optimizer] Current query: "${currentQuery.substring(0, 50)}${currentQuery.length > 50 ? '...' : ''}"`);
    }
    
    // Update message history
    this.messageHistory.push({
      timestamp: Date.now(),
      count: messages.length,
      tokens: initialTokens,
    });
    
    // Keep only recent history
    if (this.messageHistory.length > 100) {
      this.messageHistory = this.messageHistory.slice(-100);
    }
    
    // Update token usage
    this.tokenUsage = initialTokens;
    
    // Apply auto-compaction if enabled
    if (this.config.autoCompact) {
      console.log(`[Context-Optimizer] Auto-compaction enabled, threshold: ${this.config.compactThreshold}`);
      messages = await this.autoCompact(messages, currentQuery);
      this.tokenUsage = this.countTokensForMessages(messages);
    }
    
    // Apply dynamic context adjustment if enabled
    if (this.config.dynamicContext) {
      console.log(`[Context-Optimizer] Dynamic context adjustment enabled`);
      messages = await this.adjustDynamicContext(messages, currentQuery);
      this.tokenUsage = this.countTokensForMessages(messages);
    }
    
    // Smart Archive Retrieval: Check if we should fetch relevant content from archive
    if (this.config.enableArchive && currentQuery) {
      console.log(`[Context-Optimizer] Smart archive retrieval enabled`);
      messages = await this.smartArchiveRetrieval(messages, currentQuery);
      this.tokenUsage = this.countTokensForMessages(messages);
    }
    
    const finalCount = messages.length;
    const finalTokens = this.tokenUsage;
    const tokensSaved = initialTokens - finalTokens;
    const timeTaken = Date.now() - startTime;
    
    console.log(`[Context-Optimizer] Processing complete: ${finalCount} messages, ${finalTokens} tokens (saved ${tokensSaved} tokens, ${timeTaken}ms)`);
    console.log(`[Context-Optimizer] Stats: ${this.stats.totalCompacted} compacted, ${this.stats.archiveRetrievals} archive retrievals`);
    
    return messages;
  }

  /**
   * Emergency pruning for critical situations
   */
  async emergencyPrune(messages) {
    console.warn('Applying emergency pruning - context near limit!');
    
    // Keep only essential messages
    const essential = messages.filter(msg => {
      // Always keep system messages
      if (this.config.preserveSystem && msg.role === 'system') return true;
      
      // Keep high priority messages
      if (msg.priority && msg.priority >= this.config.preserveHighPriority) return true;
      
      // Keep recent messages (last N)
      const recentIndex = messages.length - this.config.preserveRecent;
      const index = messages.indexOf(msg);
      return index >= recentIndex;
    });
    
    // If still too large, apply aggressive compression
    const tokens = this.countTokensForMessages(essential);
    if (tokens > this.config.contextLimit * 0.8) {
      return await this.applyExtractiveCompression(essential, tokens * 0.5, 'aggressive');
    }
    
    this.stats.totalPruned += (messages.length - essential.length);
    this.stats.prunes++;
    
    return essential;
  }

  // ---------------------------------------------------------------------------
  // Pruning Strategies
  // ---------------------------------------------------------------------------

  /**
   * Semantic compaction: merge similar messages instead of removing
   */
  async applySemanticCompaction(messages, targetTokensToSave, strategy = 'balanced', currentQuery = null) {
    if (!this.embedder || messages.length < 3) {
      return { compacted: messages, removed: [], tokensSaved: 0 };
    }
    
    const embeddings = await Promise.all(
      messages.map(async (msg, index) => ({
        index,
        embedding: await this.embed(msg.content),
        message: msg,
        relevance: this.relevanceScores.get(index) || 0.5,
      }))
    );
    
    // Find similar message pairs
    const similarPairs = [];
    for (let i = 0; i < embeddings.length; i++) {
      for (let j = i + 1; j < embeddings.length; j++) {
        const similarity = this.cosineSimilarity(
          embeddings[i].embedding,
          embeddings[j].embedding
        );
        
        if (similarity > this.config.minSimilarity) {
          // Calculate merge score based on similarity and relevance
          const mergeScore = similarity * 0.6 + 
                           (Math.max(embeddings[i].relevance, embeddings[j].relevance) * 0.4);
          
          similarPairs.push({
            i, j, similarity, mergeScore,
            tokensI: this.countTokens(embeddings[i].message.content),
            tokensJ: this.countTokens(embeddings[j].message.content),
            relevanceI: embeddings[i].relevance,
            relevanceJ: embeddings[j].relevance,
          });
        }
      }
    }
    
    // Sort by merge score (highest first)
    similarPairs.sort((a, b) => b.mergeScore - a.mergeScore);
    
    const mergedIndices = new Set();
    const compactedMessages = [];
    const removedMessages = [];
    let tokensSaved = 0;
    
    for (const pair of similarPairs) {
      if (tokensSaved >= targetTokensToSave) break;
      if (mergedIndices.has(pair.i) || mergedIndices.has(pair.j)) continue;
      
      // Merge the two messages
      const message1 = embeddings[pair.i].message;
      const message2 = embeddings[pair.j].message;
      
      // Create merged message
      const mergedContent = await this.mergeSimilarMessages(message1, message2, strategy);
      const mergedTokens = this.countTokens(mergedContent);
      const originalTokens = pair.tokensI + pair.tokensJ;
      const savedTokens = originalTokens - mergedTokens;
      
      if (savedTokens > 0) {
        // Add merged message
        compactedMessages.push({
          role: message1.role === message2.role ? message1.role : 'system',
          content: mergedContent,
          priority: Math.max(message1.priority || 5, message2.priority || 5),
          merged: true,
          originalCount: 2,
          mergedFrom: [pair.i, pair.j],
        });
        
        // Track removed original messages
        removedMessages.push({
          message: message1,
          reason: 'semantic_merge',
          similarity: pair.similarity,
          mergedInto: compactedMessages.length - 1,
        });
        
        removedMessages.push({
          message: message2,
          reason: 'semantic_merge',
          similarity: pair.similarity,
          mergedInto: compactedMessages.length - 1,
        });
        
        mergedIndices.add(pair.i);
        mergedIndices.add(pair.j);
        tokensSaved += savedTokens;
      }
    }
    
    // Add unmerged messages
    for (let i = 0; i < messages.length; i++) {
      if (!mergedIndices.has(i)) {
        compactedMessages.push(messages[i]);
      }
    }
    
    // Sort by original order (approximate)
    compactedMessages.sort((a, b) => {
      const aIndex = a.mergedFrom ? Math.min(...a.mergedFrom) : messages.findIndex(m => m === a);
      const bIndex = b.mergedFrom ? Math.min(...b.mergedFrom) : messages.findIndex(m => m === b);
      return aIndex - bIndex;
    });
    
    return { compacted: compactedMessages, removed: removedMessages, tokensSaved };
  }

  /**
   * Semantic pruning: remove semantically similar messages (original, kept for compatibility)
   */
  async applySemanticPruning(messages, targetTokensToRemove, strategy) {
    const result = await this.applySemanticPruningWithArchive(messages, targetTokensToRemove, strategy, false);
    return result.pruned;
  }

  /**
   * Temporal compaction: summarize older messages instead of removing
   */
  applyTemporalCompaction(messages, targetTokensToSave, strategy = 'balanced', currentQuery = null) {
    // Skip if we need to preserve recent messages
    const preserveCount = Math.min(this.config.preserveRecent, messages.length);
    const recentMessages = messages.slice(-preserveCount);
    const olderMessages = messages.slice(0, -preserveCount);
    
    if (olderMessages.length === 0) {
      return { compacted: messages, removed: [], tokensSaved: 0 };
    }
    
    // Group older messages by time windows
    const timeWindows = this.groupByTimeWindows(olderMessages);
    const compactedOlder = [];
    const removedMessages = [];
    let tokensSaved = 0;
    
    for (const window of timeWindows) {
      if (tokensSaved >= targetTokensToSave) {
        compactedOlder.push(...window.messages);
        continue;
      }
      
      const windowTokens = this.countTokensForMessages(window.messages);
      
      if (window.messages.length >= 3 && windowTokens > 300) {
        // Summarize this time window
        const summary = this.summarizeTimeWindow(window.messages, strategy);
        const summaryTokens = this.countTokens(summary);
        const savedTokens = windowTokens - summaryTokens;
        
        if (savedTokens > 0 && savedTokens <= targetTokensToSave - tokensSaved) {
          compactedOlder.push({
            role: 'system',
            content: `[Time window ${window.startTime.toLocaleTimeString()} - ${window.endTime.toLocaleTimeString()}]: ${summary}`,
            priority: 6,
            compressed: true,
            originalCount: window.messages.length,
            timeWindow: true,
          });
          
          // Track removed messages
          removedMessages.push(...window.messages.map((msg, index) => ({
            message: msg,
            reason: 'temporal_summary',
            timeWindow: window.label,
            summary: summary,
          })));
          
          tokensSaved += savedTokens;
        } else {
          compactedOlder.push(...window.messages);
        }
      } else {
        compactedOlder.push(...window.messages);
      }
    }
    
    return {
      compacted: [...compactedOlder, ...recentMessages],
      removed: removedMessages,
      tokensSaved,
    };
  }

  /**
   * Temporal pruning: remove older messages first (original, kept for compatibility)
   */
  applyTemporalPruning(messages, targetTokensToRemove, strategy) {
    const result = this.applyTemporalPruningWithArchive(messages, targetTokensToRemove, strategy, false);
    this.stats.totalTokensSaved += (this.countTokensForMessages(messages) - this.countTokensForMessages(result.pruned));
    this.stats.totalPruned += (messages.length - result.pruned.length);
    return result.pruned;
  }

  /**
   * Extractive compaction: extract key information from verbose messages
   */
  async applyExtractiveCompaction(messages, targetTokensToSave, strategy = 'balanced', currentQuery = null) {
    // Identify verbose messages that can be compacted
    const verboseMessages = messages.filter((msg, index) => {
      const tokens = this.countTokens(msg.content);
      const relevance = this.relevanceScores.get(index) || 0.5;
      
      // Messages that are long but not highly relevant to current query
      return tokens > 100 && relevance < 0.7 && msg.priority < 8;
    });
    
    if (verboseMessages.length === 0) {
      return { compacted: messages, removed: [], tokensSaved: 0 };
    }
    
    // Sort by length (longest first) and low relevance
    verboseMessages.sort((a, b) => {
      const aTokens = this.countTokens(a.content);
      const bTokens = this.countTokens(b.content);
      const aIndex = messages.indexOf(a);
      const bIndex = messages.indexOf(b);
      const aRelevance = this.relevanceScores.get(aIndex) || 0.5;
      const bRelevance = this.relevanceScores.get(bIndex) || 0.5;
      
      // Prioritize long, low-relevance messages
      return (bTokens * (1 - bRelevance)) - (aTokens * (1 - aRelevance));
    });
    
    const compactedMessages = [...messages];
    const removedMessages = [];
    let tokensSaved = 0;
    
    for (const verboseMsg of verboseMessages) {
      if (tokensSaved >= targetTokensToSave) break;
      
      const originalIndex = messages.indexOf(verboseMsg);
      if (originalIndex === -1) continue;
      
      const originalTokens = this.countTokens(verboseMsg.content);
      
      // Extract key information
      const extracted = this.extractKeyInformation(verboseMsg.content, currentQuery, strategy);
      const extractedTokens = this.countTokens(extracted);
      const savedTokens = originalTokens - extractedTokens;
      
      if (savedTokens > 0 && savedTokens <= targetTokensToSave - tokensSaved) {
        // Replace verbose message with extracted version
        compactedMessages[originalIndex] = {
          ...verboseMsg,
          content: `[Extracted key points]: ${extracted}`,
          extracted: true,
          originalLength: originalTokens,
        };
        
        removedMessages.push({
          message: verboseMsg,
          reason: 'extractive_compaction',
          extraction: extracted,
          savedTokens,
        });
        
        tokensSaved += savedTokens;
      }
    }
    
    return { compacted: compactedMessages, removed: removedMessages, tokensSaved };
  }

  /**
   * Extractive compression: summarize groups of messages (original, kept for compatibility)
   */
  async applyExtractiveCompression(messages, targetTokensToRemove, strategy) {
    const result = await this.applyExtractiveCompressionWithArchive(messages, targetTokensToRemove, strategy, false);
    this.stats.totalTokensSaved += (this.countTokensForMessages(messages) - this.countTokensForMessages(result.pruned));
    return result.pruned;
  }

  // ---------------------------------------------------------------------------
  // Helper Methods
  // ---------------------------------------------------------------------------

  groupMessagesForCompression(messages) {
    const groups = [];
    let currentGroup = { role: null, messages: [], startIndex: 0 };
    
    for (let i = 0; i < messages.length; i++) {
      const msg = messages[i];
      
      // Start new group if:
      // 1. Role changes
      // 2. Group is getting too large (> 10 messages)
      // 3. Large gap in timestamps (if available)
      if (currentGroup.role !== msg.role || 
          currentGroup.messages.length >= 10 ||
          (msg.timestamp && currentGroup.messages.length > 0 && 
           msg.timestamp - currentGroup.messages[currentGroup.messages.length - 1].timestamp > 300000)) { // 5 minutes
        
        if (currentGroup.messages.length > 0) {
          groups.push({ ...currentGroup });
        }
        
        currentGroup = {
          role: msg.role,
          messages: [msg],
          startIndex: i,
        };
      } else {
        currentGroup.messages.push(msg);
      }
    }
    
    // Add the last group
    if (currentGroup.messages.length > 0) {
      groups.push(currentGroup);
    }
    
    return groups;
  }

  async summarizeGroup(messages, strategy) {
    if (this.config.summarizer) {
      try {
        const combined = messages.map(m => `[${m.role}]: ${m.content}`).join('\n\n');
        return await this.config.summarizer(combined);
      } catch (error) {
        console.warn('Summarizer failed:', error.message);
      }
    }
    
    // Fallback extractive summarization
    return this.extractiveSummarize(messages, strategy);
  }

  /**
   * Adaptive compaction: choose best approach based on message characteristics
   */
  async applyAdaptiveCompaction(messages, targetTokensToSave, strategy = 'balanced', currentQuery = null) {
    // Analyze message characteristics
    const analysis = this.analyzeMessages(messages);
    
    let compactedMessages = [...messages];
    const removedMessages = [];
    let tokensSaved = 0;
    
    // Always try at least one technique based on analysis
    let triedAny = false;
    
    // Try semantic compaction if there's any duplication
    if (analysis.duplicateScore > 0.1 || messages.length > 5) {
      triedAny = true;
      const semanticResult = await this.applySemanticCompaction(
        compactedMessages, 
        targetTokensToSave - tokensSaved, 
        strategy, 
        currentQuery
      );
      compactedMessages = semanticResult.compacted;
      removedMessages.push(...semanticResult.removed);
      tokensSaved += semanticResult.tokensSaved;
    }
    
    // Try extractive compaction if messages are verbose or we haven't saved enough
    if (tokensSaved < targetTokensToSave && (analysis.verboseScore > 0.2 || analysis.avgTokens > 50)) {
      triedAny = true;
      const extractiveResult = await this.applyExtractiveCompaction(
        compactedMessages,
        targetTokensToSave - tokensSaved,
        strategy,
        currentQuery
      );
      compactedMessages = extractiveResult.compacted;
      removedMessages.push(...extractiveResult.removed);
      tokensSaved += extractiveResult.tokensSaved;
    }
    
    // Try temporal compaction as last resort if we have timestamps
    if (tokensSaved < targetTokensToSave && analysis.temporalSpread > 0.1) {
      triedAny = true;
      const temporalResult = this.applyTemporalCompaction(
        compactedMessages,
        targetTokensToSave - tokensSaved,
        strategy,
        currentQuery
      );
      compactedMessages = temporalResult.compacted;
      removedMessages.push(...temporalResult.removed);
      tokensSaved += temporalResult.tokensSaved;
    }
    
    // If nothing worked and we still need to save tokens, use aggressive extractive on longest messages
    if (tokensSaved < targetTokensToSave && !triedAny && messages.length > 3) {
      // Find the longest messages
      const messagesWithTokens = messages.map((msg, index) => ({
        msg,
        index,
        tokens: this.countTokens(msg.content),
      }));
      
      messagesWithTokens.sort((a, b) => b.tokens - a.tokens);
      
      // Try to compact the longest messages
      for (const { msg, index, tokens } of messagesWithTokens) {
        if (tokensSaved >= targetTokensToSave) break;
        if (tokens < 30) continue; // Skip very short messages
        
        const extracted = this.extractKeyInformation(msg.content, currentQuery, 'aggressive');
        const extractedTokens = this.countTokens(extracted);
        const savedTokens = tokens - extractedTokens;
        
        if (savedTokens > 0) {
          compactedMessages[index] = {
            ...msg,
            content: `[Compressed]: ${extracted}`,
            extracted: true,
            originalLength: tokens,
          };
          
          removedMessages.push({
            message: msg,
            reason: 'adaptive_fallback',
            extraction: extracted,
            savedTokens,
          });
          
          tokensSaved += savedTokens;
        }
      }
    }
    
    return { compacted: compactedMessages, removed: removedMessages, tokensSaved };
  }

  /**
   * Dynamic context adjustment based on relevance
   */
  async adjustDynamicContext(messages, currentQuery = null) {
    if (messages.length === 0) return messages;
    
    // Update relevance scores
    if (currentQuery) {
      await this.updateRelevanceScores(messages, currentQuery);
    }
    
    // Apply relevance decay over time
    this.applyRelevanceDecay();
    
    // Filter messages below minimum relevance
    const relevantMessages = messages.filter((_, index) => {
      const relevance = this.relevanceScores.get(index) || 0.5;
      return relevance >= this.config.minRelevanceScore;
    });
    
    if (relevantMessages.length < messages.length) {
      const filtered = messages.length - relevantMessages.length;
      const total = messages.length;
      
      // Calculate average relevance of filtered messages
      let avgRelevance = 0;
      let filteredCount = 0;
      for (let i = 0; i < messages.length; i++) {
        if (!relevantMessages.includes(messages[i])) {
          const relevance = this.relevanceScores.get(i) || 0.5;
          avgRelevance += relevance;
          filteredCount++;
        }
      }
      avgRelevance = filteredCount > 0 ? avgRelevance / filteredCount : 0;
      
      // Log to chat
      this.logger.logDynamicContext(filtered, total, avgRelevance);
      this.stats.chatLogs++;
      
      this.stats.dynamicAdjustments++;
      console.log(`[Dynamic Context] Filtered ${filtered} low-relevance messages`);
    }
    
    return relevantMessages;
  }

  /**
   * Update relevance scores based on current query
   */
  async updateRelevanceScores(messages, currentQuery) {
    if (!this.embedder || !currentQuery) return;
    
    try {
      const queryEmbedding = await this.embed(currentQuery);
      
      for (let i = 0; i < messages.length; i++) {
        const msg = messages[i];
        const msgEmbedding = await this.embed(msg.content);
        const similarity = this.cosineSimilarity(queryEmbedding, msgEmbedding);
        
        // Combine similarity with message priority
        const priorityFactor = (msg.priority || 5) / 10;
        const relevance = (similarity * 0.7) + (priorityFactor * 0.3);
        
        this.relevanceScores.set(i, relevance);
      }
      
      this.stats.relevanceUpdates++;
    } catch (error) {
      console.warn('Failed to update relevance scores:', error.message);
    }
  }

  /**
   * Apply relevance decay over time
   */
  applyRelevanceDecay() {
    const now = Date.now();
    const timeSinceLastQuery = now - this.lastQueryTime;
    
    if (timeSinceLastQuery > 5 * 60 * 1000) { // 5 minutes
      for (const [index, relevance] of this.relevanceScores.entries()) {
        const decayed = relevance * Math.pow(this.config.relevanceDecay, timeSinceLastQuery / (60 * 1000));
        this.relevanceScores.set(index, decayed);
      }
    }
  }

  /**
   * Smart Archive Retrieval: Automatically fetch relevant content from archive
   * when current query suggests missing context
   */
  async smartArchiveRetrieval(messages, currentQuery) {
    if (!this.config.enableArchive || !this.archive || !currentQuery) {
      return messages;
    }
    
    // Check if we have space for archive content
    const currentTokens = this.countTokensForMessages(messages);
    const availableTokens = this.config.contextLimit - currentTokens;
    
    // Only retrieve if we have reasonable space (at least 10% of context limit)
    if (availableTokens < this.config.contextLimit * 0.1) {
      return messages;
    }
    
    // Calculate query complexity to decide if archive search is needed
    const queryTokens = this.countTokens(currentQuery);
    const queryComplexity = queryTokens > 20; // Longer queries are more likely to need context
    
    // Check if current messages already contain relevant information
    let hasRelevantContext = false;
    if (this.embedder) {
      try {
        const queryEmbedding = await this.embed(currentQuery);
        for (const msg of messages) {
          const msgEmbedding = await this.embed(msg.content);
          const similarity = this.cosineSimilarity(queryEmbedding, msgEmbedding);
            if (similarity > 0.8) {
            hasRelevantContext = true;
            break;
          }
        }
      } catch (error) {
        console.warn('Failed to check context relevance:', error.message);
      }
    }
    
    // If query is complex AND we don't have highly relevant context, search archive
    if (queryComplexity && !hasRelevantContext) {
      
      const archiveResult = await this.retrieveFromArchive(currentQuery, {
        maxContextTokens: Math.min(availableTokens * 0.5, 2000), // Use up to half available space
        minRelevance: 0.6, // High relevance threshold
        limit: 5, // Limit to top 5 snippets
      });
      
      if (archiveResult.found && archiveResult.snippets.length > 0) {
        // Deduplicate snippets (simple content-based deduplication)
        const uniqueSnippets = [];
        const seenContents = new Set();
        
        for (const snippet of archiveResult.snippets) {
          const normalized = snippet.trim().toLowerCase().substring(0, 100);
          if (!seenContents.has(normalized)) {
            seenContents.add(normalized);
            uniqueSnippets.push(snippet);
          }
        }
        
        // Limit to top 3 unique snippets
        const topSnippets = uniqueSnippets.slice(0, 3);
        
        if (topSnippets.length > 0) {
          console.log(`[Smart Archive] Retrieved ${topSnippets.length} relevant snippets (${archiveResult.totalTokens} tokens)`);
          
          // Log to chat
          const avgSimilarity = archiveResult.relevanceScores ? 
            archiveResult.relevanceScores.reduce((a, b) => a + b, 0) / archiveResult.relevanceScores.length : 0.5;
          this.logger.logArchiveSearch(currentQuery, topSnippets.length, avgSimilarity);
          this.stats.chatLogs++;
          
          // Add archive snippets as system messages at the beginning
          const archiveMessages = topSnippets.map((snippet, index) => ({
            role: 'system',
            content: `[Archived Context]: ${snippet}`,
            priority: 7, // High priority for retrieved context
            fromArchive: true,
            archiveRelevance: archiveResult.relevanceScores?.[index] || 0.5,
          }));
          
          // Combine archive messages with current messages
          const combinedMessages = [...archiveMessages, ...messages];
          const combinedTokens = this.countTokensForMessages(combinedMessages);
          
          // If combined exceeds limit, compact again
          if (combinedTokens > this.config.contextLimit * 0.9) {
            console.log(`[Smart Archive] Combined context (${combinedTokens} tokens) exceeds 90% limit, re-compacting...`);
            const compacted = await this.autoCompact(combinedMessages, currentQuery);
            return compacted;
          }
          
          this.stats.archiveAutoRetrievals++;
          return combinedMessages;
        }
      }
    }
    
    return messages;
  }

  /**
   * Analyze message characteristics
   */
  analyzeMessages(messages) {
    if (messages.length === 0) {
      return {
        duplicateScore: 0,
        verboseScore: 0,
        temporalSpread: 0,
        prioritySpread: 0,
      };
    }
    
    // Calculate duplicate score (based on message lengths and similarities)
    const avgLength = messages.reduce((sum, msg) => sum + msg.content.length, 0) / messages.length;
    const lengthVariance = messages.reduce((sum, msg) => {
      const diff = msg.content.length - avgLength;
      return sum + (diff * diff);
    }, 0) / messages.length;
    
    // Calculate verbose score (percentage of long messages)
    const longMessages = messages.filter(msg => this.countTokens(msg.content) > 150).length;
    const verboseScore = longMessages / messages.length;
    
    // Calculate temporal spread (if timestamps available)
    let temporalSpread = 0;
    if (messages[0].timestamp && messages[messages.length - 1].timestamp) {
      const timeSpan = messages[messages.length - 1].timestamp - messages[0].timestamp;
      temporalSpread = Math.min(timeSpan / (60 * 60 * 1000), 1); // Normalize to 0-1 over 1 hour
    }
    
    // Calculate priority spread
    const priorities = messages.map(msg => msg.priority || 5);
    const minPriority = Math.min(...priorities);
    const maxPriority = Math.max(...priorities);
    const prioritySpread = (maxPriority - minPriority) / 10; // Normalize to 0-1
    
    return {
      duplicateScore: Math.min(lengthVariance / (avgLength * avgLength), 1),
      verboseScore,
      temporalSpread,
      prioritySpread,
      avgTokens: messages.reduce((sum, msg) => sum + this.countTokens(msg.content), 0) / messages.length,
    };
  }

  /**
   * Select strategies based on message analysis
   */
  selectStrategies(analysis, currentQuery = null) {
    const strategies = [];
    
    // Always try semantic if we have enough messages
    if (analysis.duplicateScore > 0.1 || analysis.avgTokens > 30) {
      strategies.push('semantic');
    }
    
    // Try extractive if messages are verbose
    if (analysis.verboseScore > 0.2 || analysis.avgTokens > 50) {
      strategies.push('extractive');
    }
    
    // Try temporal if we have time data
    if (analysis.temporalSpread > 0.2) {
      strategies.push('temporal');
    }
    
    // Always include adaptive as fallback
    strategies.push('adaptive');
    
    return strategies;
  }

  /**
   * Store removed messages in archive
   */
  async storeRemovedInArchive(removedMessages, strategy) {
    if (!this.archive || removedMessages.length === 0) return;
    
    for (const removed of removedMessages) {
      const { message, reason, ...context } = removed;
      
      // Prepare metadata for archive
      const metadata = {
        role: message.role,
        priority: message.priority || 5,
        tokens: this.countTokens(message.content),
        compactedAt: Date.now(),
        compactionReason: reason,
        compactionStrategy: strategy,
        compactionContext: context,
        category: this.determineCategory(message.content),
      };
      
      // Store in archive
      try {
        await this.archive.store(message.content, metadata);
        this.stats.archiveStores++;
      } catch (error) {
        console.warn('Failed to store message in archive:', error.message);
      }
    }
  }

  /**
   * Retrieve relevant information from archive when not in current context
   */
  async retrieveFromArchive(query, options = {}) {
    if (!this.archive) {
      return {
        found: false,
        snippets: [],
        totalTokens: 0,
        sources: [],
        error: 'Archive not enabled',
      };
    }
    
    const result = await this.archive.retrieveRelevantInfo(query, options);
    
    if (result.found) {
      this.stats.archiveRetrievals++;
    }
    
    return result;
  }

  /**
   * Merge similar messages
   */
  async mergeSimilarMessages(message1, message2, strategy) {
    // Simple merging: combine key points
    const content1 = message1.content;
    const content2 = message2.content;
    
    if (strategy === 'aggressive') {
      // Take the longer/more detailed message
      return content1.length > content2.length ? content1 : content2;
    } else {
      // Combine key points from both
      const sentences1 = content1.match(/[^.!?]+[.!?]+/g) || [content1];
      const sentences2 = content2.match(/[^.!?]+[.!?]+/g) || [content2];
      
      // Take unique sentences (simple deduplication)
      const uniqueSentences = new Set([...sentences1, ...sentences2]);
      return Array.from(uniqueSentences).slice(0, 5).join(' ');
    }
  }

  /**
   * Group messages by time windows
   */
  groupByTimeWindows(messages) {
    if (messages.length === 0) return [];
    
    const windows = [];
    const windowSize = 5 * 60 * 1000; // 5 minutes
    
    let currentWindow = {
      messages: [],
      startTime: new Date(messages[0].timestamp || Date.now()),
      endTime: new Date(messages[0].timestamp || Date.now()),
      label: '',
    };
    
    for (const msg of messages) {
      const msgTime = new Date(msg.timestamp || Date.now());
      
      if (msgTime - currentWindow.startTime > windowSize) {
        // Close current window
        currentWindow.label = `${currentWindow.startTime.toLocaleTimeString()} - ${currentWindow.endTime.toLocaleTimeString()}`;
        windows.push(currentWindow);
        
        // Start new window
        currentWindow = {
          messages: [msg],
          startTime: msgTime,
          endTime: msgTime,
          label: '',
        };
      } else {
        currentWindow.messages.push(msg);
        currentWindow.endTime = msgTime;
      }
    }
    
    // Add last window
    if (currentWindow.messages.length > 0) {
      currentWindow.label = `${currentWindow.startTime.toLocaleTimeString()} - ${currentWindow.endTime.toLocaleTimeString()}`;
      windows.push(currentWindow);
    }
    
    return windows;
  }

  /**
   * Summarize a time window
   */
  summarizeTimeWindow(messages, strategy) {
    const allContent = messages.map(m => m.content).join(' ');
    return this.extractiveSummarize([{ content: allContent }], strategy);
  }

  /**
   * Extract key information from verbose content
   */
  extractKeyInformation(content, currentQuery = null, strategy = 'balanced') {
    const sentences = content.match(/[^.!?]+[.!?]+/g) || [content];
    
    // Score sentences based on query relevance and importance markers
    const scoredSentences = sentences.map((sentence, index) => {
      let score = 0;
      
      // Length bonus (medium-length sentences are often most informative)
      const lengthBonus = Math.min(sentence.length / 100, 1) * 0.3;
      
      // Position bonus (first and last sentences are often important)
      const positionBonus = (index === 0 || index === sentences.length - 1) ? 0.2 : 0;
      
      // Importance markers
      const hasImportantMarker = /(important|key|critical|essential|must|should)/i.test(sentence);
      const markerBonus = hasImportantMarker ? 0.3 : 0;
      
      // Query relevance (if query provided)
      let queryBonus = 0;
      if (currentQuery) {
        const queryWords = currentQuery.toLowerCase().split(/\s+/);
        const matchingWords = queryWords.filter(word => 
          sentence.toLowerCase().includes(word)
        ).length;
        queryBonus = (matchingWords / Math.max(queryWords.length, 1)) * 0.2;
      }
      
      score = lengthBonus + positionBonus + markerBonus + queryBonus;
      
      return { sentence: sentence.trim(), score };
    });
    
    // Sort by score
    scoredSentences.sort((a, b) => b.score - a.score);
    
    // Select top sentences based on strategy
    let topCount;
    switch (strategy) {
      case 'aggressive': topCount = 2; break;
      case 'conservative': topCount = 4; break;
      default: topCount = 3; break;
    }
    
    const topSentences = scoredSentences
      .slice(0, Math.min(topCount, scoredSentences.length))
      .filter(s => s.score > 0.2); // Only include sentences with meaningful score
    
    return topSentences.map(s => s.sentence).join(' ');
  }

  /**
   * Determine category for archive entry
   */
  determineCategory(content) {
    const contentLower = content.toLowerCase();
    
    if (contentLower.includes('code') || contentLower.includes('function') || contentLower.includes('import')) {
      return 'code';
    } else if (contentLower.includes('error') || contentLower.includes('failed') || contentLower.includes('issue')) {
      return 'error';
    } else if (contentLower.includes('todo') || contentLower.includes('task') || contentLower.includes('remind')) {
      return 'todo';
    } else if (contentLower.includes('question') || contentLower.includes('ask') || contentLower.includes('what') || contentLower.includes('how')) {
      return 'question';
    } else if (contentLower.includes('prefer') || contentLower.includes('like') || contentLower.includes('want')) {
      return 'preference';
    } else if (contentLower.includes('decision') || contentLower.includes('choose') || contentLower.includes('select')) {
      return 'decision';
    } else {
      return 'general';
    }
  }

  extractiveSummarize(messages, strategy) {
    // Simple extractive summarization: take key sentences
    const allContent = messages.map(m => m.content).join(' ');
    const sentences = allContent.match(/[^.!?]+[.!?]+/g) || [allContent];
    
    // Score sentences (simple heuristic: length and position)
    const scoredSentences = sentences.map((sentence, index) => ({
      sentence,
      score: sentence.length / 100 + (1 - index / sentences.length) * 0.5,
    }));
    
    // Sort by score and take top 3-5
    scoredSentences.sort((a, b) => b.score - a.score);
    const topCount = strategy === 'aggressive' ? 2 : strategy === 'conservative' ? 5 : 3;
    const topSentences = scoredSentences.slice(0, Math.min(topCount, scoredSentences.length));
    
    return topSentences.map(s => s.sentence.trim()).join(' ');
  }

  async embed(text) {
    if (!this.embedder) return null;
    
    try {
      const result = await this.embedder(text, { pooling: 'mean', normalize: true });
      return Array.from(result.data);
    } catch (error) {
      console.warn('Embedding failed:', error.message);
      return null;
    }
  }

  cosineSimilarity(a, b) {
    if (!a || !b || a.length !== b.length) return 0;
    
    let dot = 0, normA = 0, normB = 0;
    for (let i = 0; i < a.length; i++) {
      dot += a[i] * b[i];
      normA += a[i] * a[i];
      normB += b[i] * b[i];
    }
    
    return dot / (Math.sqrt(normA) * Math.sqrt(normB));
  }

  countTokens(text) {
    return this.encoder.encode(text).length;
  }

  countTokensForMessages(messages) {
    return messages.reduce((total, msg) => total + this.countTokens(msg.content || ''), 0);
  }

  getHealthStatus(usageRatio) {
    if (usageRatio >= this.config.aggressiveCompactThreshold) return 'AGGRESSIVE_COMPACT';
    if (usageRatio >= this.config.compactThreshold) return 'COMPACT';
    return 'HEALTHY';
  }

  updateMessageHistory(messages) {
    // Keep track of recent message patterns for better pruning decisions
    this.messageHistory.push({
      timestamp: Date.now(),
      count: messages.length,
      tokens: this.countTokensForMessages(messages),
      compressed: messages.some(m => m.compressed),
    });
    
    // Keep only last 100 entries
    if (this.messageHistory.length > 100) {
      this.messageHistory = this.messageHistory.slice(-100);
    }
  }

  // ---------------------------------------------------------------------------
  // Status and Monitoring
  // ---------------------------------------------------------------------------

  getStatus() {
    const usageRatio = this.tokenUsage / this.config.contextLimit;
    const health = this.getHealthStatus(usageRatio);
    
    const status = {
      health,
      tokens: {
        used: this.tokenUsage,
        limit: this.config.contextLimit,
        remaining: this.config.contextLimit - this.tokenUsage,
        percentage: Math.round(usageRatio * 100),
      },
      messages: this.messageHistory.length > 0 ? this.messageHistory[this.messageHistory.length - 1].count : 0,
      relevanceScores: this.relevanceScores.size,
      stats: { ...this.stats },
      config: {
        strategies: this.config.strategies,
        autoCompact: this.config.autoCompact,
        dynamicContext: this.config.dynamicContext,
        thresholds: {
          compact: this.config.compactThreshold,
          aggressiveCompact: this.config.aggressiveCompactThreshold,
        },
      },
    };
    
    // Add relevance distribution if available
    if (this.relevanceScores.size > 0) {
      const scores = Array.from(this.relevanceScores.values());
      status.relevanceStats = {
        avg: scores.reduce((a, b) => a + b, 0) / scores.length,
        min: Math.min(...scores),
        max: Math.max(...scores),
        lowRelevance: scores.filter(s => s < this.config.minRelevanceScore).length,
      };
    }
    
    // Add archive stats if enabled
    if (this.archive) {
      status.archive = {
        enabled: true,
        stats: this.archive.getStats(),
      };
    } else {
      status.archive = {
        enabled: false,
      };
    }
    
    return status;
  }

  getStats() {
    return { ...this.stats };
  }

  resetStats() {
    this.stats = {
      totalCompacted: 0,
      totalTokensSaved: 0,
      archiveStores: 0,
      archiveRetrievals: 0,
      archiveAutoRetrievals: 0,
      compactions: 0,
      dynamicAdjustments: 0,
      relevanceUpdates: 0,
    };
  }

  // ---------------------------------------------------------------------------
  // Cleanup
  // ---------------------------------------------------------------------------

  destroy() {
    try {
      this.encoder.free();
    } catch (error) {
      // Ignore
    }
    this.embedder = null;
    
    // Clean up archive if exists
    if (this.archive) {
      // Archive cleanup happens automatically
    }
  }
}

// Factory function
export function createContextPruner(options = {}) {
  return new ContextOptimizer(options);
}

// Alias for the new name
export function createContextOptimizer(options = {}) {
  return new ContextOptimizer(options);
}

// Singleton instance
let _instance = null;

export function getContextPruner(options = {}) {
  if (!_instance) {
    _instance = new ContextOptimizer(options);
  }
  return _instance;
}

// Auto-compaction functions
export async function autoCompactMessages(pruner, messages, currentQuery = null) {
  return await pruner.autoCompact(messages, currentQuery);
}

export async function adaptiveCompactMessages(pruner, messages, targetTokens, strategy = 'balanced', currentQuery = null) {
  return await pruner.adaptiveCompact(messages, targetTokens, strategy, currentQuery);
}

export async function adjustDynamicContext(pruner, messages, currentQuery = null) {
  return await pruner.adjustDynamicContext(messages, currentQuery);
}

// Archive-specific functions
export function getArchiveStats(pruner) {
  return pruner.archive ? pruner.archive.getStats() : null;
}

export async function searchArchive(pruner, query, options = {}) {
  if (!pruner.archive) {
    throw new Error('Archive not enabled');
  }
  return await pruner.archive.search(query, options);
}

export async function retrieveFromArchive(pruner, query, options = {}) {
  if (!pruner.archive) {
    throw new Error('Archive not enabled');
  }
  return await pruner.retrieveFromArchive(query, options);
}

export default ContextOptimizer;
```



---

## Skill Companion Files

> Additional files collected from the skill directory layout.

### README.md

```markdown
# Context Pruner

Advanced context management optimized for DeepSeek's 64k context window. Provides intelligent pruning, compression, and token optimization to prevent context overflow while preserving important information.

## Features

- **DeepSeek-optimized**: Specifically tuned for 64k context window
- **Multiple pruning strategies**: Semantic, temporal, and extractive compression
- **Adaptive pruning**: Different strategies based on context usage levels
- **Priority-aware**: Preserves high-priority and system messages
- **Real-time monitoring**: Continuous context health tracking
- **Token-efficient**: Minimizes token overhead from pruning operations

## Installation

```bash
# Install dependencies
npm install

# Or install globally for CLI use
npm install -g .
```

## Quick Start

```javascript
import { createContextPruner } from './lib/index.js';

const pruner = createContextPruner({
  contextLimit: 64000, // DeepSeek's limit
  autoPrune: true,
  strategies: ['semantic', 'temporal', 'extractive'],
});

await pruner.initialize();

// Process messages with automatic pruning
const messages = [
  { role: 'user', content: 'Hello!', priority: 5 },
  { role: 'assistant', content: 'Hi there!', priority: 5 },
  // ... more messages
];

const processed = await pruner.processMessages(messages);

// Get status
const status = pruner.getStatus();
console.log(`Health: ${status.health}`);
console.log(`Tokens: ${status.tokens.used}/${status.tokens.limit}`);
```

## CLI Usage

```bash
# Run tests
node scripts/cli.js test

# Show status
node scripts/cli.js status

# Prune a JSON file
node scripts/cli.js prune input.json output.json

# Show statistics
node scripts/cli.js stats
```

## Pruning Strategies

### 1. Semantic Pruning
Removes semantically similar messages using embeddings. Useful for eliminating redundant information.

### 2. Temporal Pruning
Removes older messages first, preserving recent conversation. Configurable preservation of recent messages.

### 3. Extractive Compression
Summarizes groups of messages using extractive summarization. Preserves key information while reducing token count.

## Configuration Options

| Option | Default | Description |
|--------|---------|-------------|
| `contextLimit` | 64000 | DeepSeek's context window size |
| `model` | 'deepseek-chat' | Model-specific optimizations |
| `warningThreshold` | 0.7 | Warn at 70% usage |
| `pruneThreshold` | 0.8 | Start pruning at 80% usage |
| `emergencyThreshold` | 0.95 | Aggressive pruning at 95% usage |
| `strategies` | ['semantic', 'temporal', 'extractive'] | Pruning strategies to use |
| `autoPrune` | true | Enable automatic pruning |
| `preserveRecent` | 10 | Always keep last N messages |
| `preserveSystem` | true | Always keep system messages |
| `preserveHighPriority` | 8 | Priority threshold for preservation |
| `minSimilarity` | 0.85 | Semantic deduplication threshold |
| `summarizer` | null | Optional LLM summarizer function |

## Integration with Clawdbot

### As a Skill

1. Copy the `context-pruner` folder to your Clawdbot skills directory
2. Add to your Clawdbot config:

```yaml
skills:
  context-pruner:
    enabled: true
    config:
      contextLimit: 64000
      autoPrune: true
      strategies: ['semantic', 'temporal', 'extractive']
```

### Direct Integration

```javascript
import { ClawdbotContextManager } from './examples/clawdbot-integration.js';

const contextManager = new ClawdbotContextManager();
await contextManager.initialize();

// Add messages
await contextManager.addMessage('user', 'Hello!', 6);

// Get pruned context
const context = await contextManager.getContext();

// Check status
const status = contextManager.getStatus();
```

## Health Status

The pruner monitors context usage and reports health status:

- **HEALTHY**: Below 70% usage
- **WARNING**: 70-80% usage (mild pruning may occur)
- **PRUNE**: 80-95% usage (active pruning)
- **EMERGENCY**: Above 95% usage (aggressive pruning)

## Performance

- **Token counting**: Uses tiktoken for accurate token estimation
- **Embeddings**: Uses Xenova/transformers for local semantic analysis
- **Memory**: Lightweight, with configurable caching
- **Speed**: Optimized for real-time conversation processing

## Testing

```bash
# Run the test suite
npm test

# Or directly
node lib/index.test.js
```

## License

MIT
```

### _meta.json

```json
{
  "owner": "ad2546",
  "slug": "context-optimizer",
  "displayName": "Context Optimizer",
  "latest": {
    "version": "1.0.0",
    "publishedAt": 1769831499415,
    "commit": "https://github.com/clawdbot/skills/commit/49d87e1370896bdbcc9d650f847d128282c7cb53"
  },
  "history": []
}

```

### scripts/cli.js

```javascript
#!/usr/bin/env node

/**
 * CLI for Context Pruner
 */

import { createContextPruner } from '../lib/index.js';
import { readFileSync, writeFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';

const __dirname = dirname(fileURLToPath(import.meta.url));

async function main() {
  const args = process.argv.slice(2);
  const command = args[0];
  
  if (!command || command === 'help') {
    showHelp();
    return;
  }
  
  const pruner = createContextPruner({
    contextLimit: 64000,
    autoPrune: true,
    strategies: ['semantic', 'temporal', 'extractive'],
  });
  
  await pruner.initialize();
  
  switch (command) {
    case 'test':
      await runTest();
      break;
      
    case 'status':
      showStatus(pruner);
      break;
      
    case 'prune':
      await pruneFile(args[1], args[2], pruner);
      break;
      
    case 'stats':
      showStats(pruner);
      break;
      
    default:
      console.error(`Unknown command: ${command}`);
      showHelp();
      process.exit(1);
  }
  
  pruner.destroy();
}

function showHelp() {
  console.log(`
Context Pruner CLI - Advanced context management for DeepSeek

Usage:
  node cli.js <command> [options]

Commands:
  test        Run basic tests
  status      Show current pruner status
  prune <input> <output>  Prune a JSON file with messages
  stats       Show pruning statistics
  help        Show this help message

Examples:
  node cli.js test
  node cli.js status
  node cli.js prune messages.json pruned.json
  `);
}

async function runTest() {
  console.log('Running Context Pruner tests...\n');
  
  // Import and run the test
  const testModule = await import('../lib/index.test.js');
  await testModule.testContextPruner();
}

function showStatus(pruner) {
  const status = pruner.getStatus();
  console.log('Context Pruner Status:');
  console.log('=====================\n');
  
  console.log(`Health: ${status.health}`);
  console.log(`Tokens: ${status.tokens.used}/${status.tokens.limit} (${status.tokens.percentage}%)`);
  console.log(`Remaining: ${status.tokens.remaining} tokens`);
  console.log(`Messages in history: ${status.messages}`);
  
  console.log('\nConfiguration:');
  console.log(`  Strategies: ${status.config.strategies.join(', ')}`);
  console.log(`  Auto-prune: ${status.config.autoPrune ? 'Enabled' : 'Disabled'}`);
  console.log(`  Thresholds: Warning=${status.config.thresholds.warning*100}%, ` +
              `Prune=${status.config.thresholds.prune*100}%, ` +
              `Emergency=${status.config.thresholds.emergency*100}%`);
  
  console.log('\nStatistics:');
  console.log(`  Total pruned: ${status.stats.totalPruned} messages`);
  console.log(`  Total compressed: ${status.stats.totalCompressed} messages`);
  console.log(`  Total tokens saved: ${status.stats.totalTokensSaved}`);
  console.log(`  Prune operations: ${status.stats.prunes}`);
  console.log(`  Compression operations: ${status.stats.compressions}`);
}

async function pruneFile(inputPath, outputPath, pruner) {
  if (!inputPath || !outputPath) {
    console.error('Error: Both input and output paths are required');
    console.log('Usage: node cli.js prune <input.json> <output.json>');
    process.exit(1);
  }
  
  try {
    // Read input file
    const inputData = JSON.parse(readFileSync(inputPath, 'utf8'));
    
    if (!Array.isArray(inputData.messages) && !Array.isArray(inputData)) {
      console.error('Error: Input file must contain a "messages" array or be an array of messages');
      process.exit(1);
    }
    
    const messages = Array.isArray(inputData.messages) ? inputData.messages : inputData;
    
    console.log(`Processing ${messages.length} messages...`);
    console.log(`Initial tokens: ${pruner.countTokensForMessages(messages)}`);
    
    // Process messages
    const processed = await pruner.processMessages(messages);
    
    console.log(`After pruning: ${processed.length} messages`);
    console.log(`Final tokens: ${pruner.countTokensForMessages(processed)}`);
    
    // Save output
    const outputData = Array.isArray(inputData.messages) 
      ? { ...inputData, messages: processed }
      : processed;
    
    writeFileSync(outputPath, JSON.stringify(outputData, null, 2));
    
    console.log(`\nSaved pruned messages to: ${outputPath}`);
    showStatus(pruner);
    
  } catch (error) {
    console.error('Error:', error.message);
    process.exit(1);
  }
}

function showStats(pruner) {
  const stats = pruner.getStats();
  console.log('Context Pruner Statistics:');
  console.log('==========================\n');
  
  console.log(`Total messages pruned: ${stats.totalPruned}`);
  console.log(`Total messages compressed: ${stats.totalCompressed}`);
  console.log(`Total tokens saved: ${stats.totalTokensSaved}`);
  console.log(`Prune operations: ${stats.prunes}`);
  console.log(`Compression operations: ${stats.compressions}`);
  
  if (stats.prunes > 0) {
    const avgPrunedPerOp = stats.totalPruned / stats.prunes;
    const avgTokensSavedPerOp = stats.totalTokensSaved / stats.prunes;
    console.log(`\nAverages per prune operation:`);
    console.log(`  Messages pruned: ${avgPrunedPerOp.toFixed(1)}`);
    console.log(`  Tokens saved: ${avgTokensSavedPerOp.toFixed(0)}`);
  }
}

// Run CLI
main().catch(console.error);
```

context-optimizer | SkillHub