tests — utils
tests — utils
This document provides an overview of the core utility modules within the src/utils directory. These modules offer foundational, reusable functionalities that support various aspects of the application, from data management and validation to error handling and user interaction.
The documentation for each utility is derived from its comprehensive test suite, highlighting its purpose, key components, and how its API is intended to be used.
Core Utility Modules
1. Cache (src/utils/cache.ts)
The Cache module provides a simple, in-memory key-value store with optional Time-To-Live (TTL) functionality. It's designed for caching frequently accessed data to improve performance.
Purpose: To store and retrieve data efficiently, with support for automatic expiration and on-demand computation of missing values.
Key Components:
CacheClass: A generic class that implements the caching logic.createCacheKey(...args: (string | number | boolean | null | undefined)[]): string: A helper function to generate a consistent string key from multiple input values, useful for complex cache keys.
API & Functionality:
constructor(defaultTtlMs?: number): Initializes a new cache instance. An optionaldefaultTtlMscan be provided for entries without a custom TTL.set(key: string, value: T, ttlMs?: number): Stores avalueunder akey. An optionalttlMscan override the cache's default TTL for this specific entry.get(key: string): T | undefined: Retrieves the value associated withkey. Returnsundefinedif the key does not exist or the entry has expired.has(key: string): boolean: Checks if akeyexists and is not expired in the cache.delete(key: string): boolean: Removes an entry from the cache. Returnstrueif the entry was found and deleted,falseotherwise.clear(): Removes all entries from the cache.size: number: A getter that returns the current number of non-expired entries in the cache.cleanup(): Manually removes all expired entries from the cache. This is typically handled implicitly bygetandsetoperations but can be useful for explicit memory management.getOrCompute(key: string, computeFn: () => Promise: Asynchronously retrieves a value. If the value is not in the cache or has expired,, ttlMs?: number): Promise computeFnis called to generate it, and the result is stored before being returned.getOrComputeSync(key: string, computeFn: () => T, ttlMs?: number): T: Synchronous version ofgetOrCompute.
Usage Example:
import { Cache, createCacheKey } from 39;./src/utils/cache39;;
const myCache = new Cache<string>(5000); class="hl-cmt">// Default TTL of 5 seconds
class="hl-cmt">// Basic operations
myCache.set(39;user:139;, 39;Alice39;);
console.log(myCache.get(39;user:139;)); class="hl-cmt">// 39;Alice39;
class="hl-cmt">// Custom TTL
myCache.set(39;temp_data39;, 39;some_value39;, 100); class="hl-cmt">// Expires in 100ms
class="hl-cmt">// Asynchronous computation
async function fetchUserData(userId: string) {
return myCache.getOrCompute(`user_data:${userId}`, async () => {
console.log(`Fetching data for ${userId}...`);
await new Promise(resolve => setTimeout(resolve, 100)); class="hl-cmt">// Simulate API call
return `Data for ${userId}`;
});
}
(async () => {
console.log(await fetchUserData(39;239;)); class="hl-cmt">// Fetches and caches
console.log(await fetchUserData(39;239;)); class="hl-cmt">// Returns from cache, no computation
})();
class="hl-cmt">// Creating complex keys
const key = createCacheKey(39;report39;, 2023, 39;Q439;, true); class="hl-cmt">// "report:2023:Q4:true"
2. Confirmation Service (src/utils/confirmation-service.ts)
The ConfirmationService manages user confirmation states for sensitive operations and provides a dry-run mode for previewing actions without execution.
Purpose: To centralize the logic for user confirmations, manage a dry-run mode, and log actions performed in dry-run. It acts as a single point of control for interactive operations.
Key Components:
ConfirmationServiceClass: A singleton class that manages confirmation states and dry-run logs. It extendsEventEmitterto allow other parts of the application to subscribe to confirmation events.
API & Functionality:
static getInstance(): ConfirmationService: Returns the singleton instance of the service.dispose(): Cleans up the service, typically by clearing any pending states and event listeners.isDryRunMode(): boolean: Checks if the service is currently in dry-run mode.setDryRunMode(enabled: boolean): Enables or disables dry-run mode.getDryRunLog(): string[]: Returns an array of strings representing actions logged during dry-run mode.clearDryRunLog(): Clears the dry-run log.formatDryRunLog(): string: Returns a formatted string representation of the dry-run log.isPending(): boolean: Checks if there is an operation currently awaiting user confirmation.resetSession(): Resets the current confirmation session, clearing any pending states or "don't ask again" flags.getSessionFlags(): SessionFlags: Returns an object containing the current session flags (e.g.,fileOperations,bashCommands,allOperations) which indicate user-granted permissions for certain types of actions.setSessionFlag(flag: keyof SessionFlags, value: boolean): Sets a specific session flag.confirmOperation(confirmed: boolean, dontAskAgain?: boolean): Responds to a pending confirmation request.confirmedindicates user's decision,dontAskAgainupdates session flags.rejectOperation(feedback?: string): Rejects a pending operation, optionally with feedback.on(event: string, listener: (...args: any[]) => void): Inherited fromEventEmitter, allows listening for service events (e.g.,confirmationRequired,operationConfirmed,operationRejected).emit(event: string, ...args: any[]): Inherited fromEventEmitter, used internally to emit events.
Usage Example:
import { ConfirmationService } from 39;./src/utils/confirmation-service39;;
const service = ConfirmationService.getInstance();
service.on(39;confirmationRequired39;, (message, type) => {
console.log(`Confirmation needed for ${type}: ${message}`);
class="hl-cmt">// In a real UI, this would prompt the user
service.confirmOperation(true, true); class="hl-cmt">// Auto-confirm for example
});
service.setDryRunMode(true);
class="hl-cmt">// Simulate an operation that would normally require confirmation
class="hl-cmt">// In dry-run mode, it would log instead of prompting
class="hl-cmt">// service.requestConfirmation(39;Delete sensitive file?39;, 39;fileOperations39;);
class="hl-cmt">// console.log(service.getDryRunLog());
service.setDryRunMode(false);
service.setSessionFlag(39;fileOperations39;, true); class="hl-cmt">// User agreed to all file ops
class="hl-cmt">// Now, file operations might proceed without explicit prompt
3. Disposable System (src/utils/disposable.ts)
The Disposable system provides a robust mechanism for managing resources that need explicit cleanup, ensuring they are disposed of correctly, especially in a Last-In, First-Out (LIFO) order.
Purpose:
To prevent resource leaks by centralizing the registration and disposal of objects that implement the Disposable interface. It's particularly useful for managing subscriptions, file handles, or other resources that require explicit release.
Key Components:
DisposableInterface: Defines a singledispose()method that objects must implement for cleanup.DisposableManagerClass: Manages a collection ofDisposableobjects, calling theirdispose()methods whendisposeAll()is invoked.getDisposableManager(): DisposableManager: Returns the singleton instance of the globalDisposableManager.registerDisposable(disposable: Disposable): A global helper function to register aDisposablewith the global manager.unregisterDisposable(disposable: Disposable): A global helper function to unregister aDisposable.disposeAll(): Promise: A global helper function to trigger disposal of all registeredDisposableobjects.
API & Functionality:
DisposableManager Class:
register(disposable: Disposable): Adds aDisposableobject to the manager.unregister(disposable: Disposable): Removes aDisposableobject from the manager.disposeAll(): Promise: Iterates through all registered disposables in reverse order of registration (LIFO) and calls theirdispose()method. It handles both synchronous and asynchronousdisposemethods and continues even if some disposals throw errors.getCount(): number: Returns the number of currently registered disposables.isDisposed(): boolean: Indicates if the manager itself has been disposed (i.e.,disposeAllhas been called).reset(): Clears all registered disposables and resets the disposed state. Primarily used for testing.
Global Helper Functions:
getDisposableManager(): Accesses the global singletonDisposableManager.registerDisposable(disposable: Disposable): Convenience wrapper forgetDisposableManager().register(disposable).unregisterDisposable(disposable: Disposable): Convenience wrapper forgetDisposableManager().unregister(disposable).disposeAll(): Convenience wrapper forgetDisposableManager().disposeAll().
Usage Example:
import { registerDisposable, disposeAll, Disposable } from 39;./src/utils/disposable39;;
class MyResource implements Disposable {
private id: number;
constructor(id: number) {
this.id = id;
console.log(`Resource ${this.id} created.`);
registerDisposable(this); class="hl-cmt">// Register itself for automatic cleanup
}
async dispose() {
console.log(`Disposing Resource ${this.id}...`);
await new Promise(resolve => setTimeout(resolve, 10)); class="hl-cmt">// Simulate async cleanup
console.log(`Resource ${this.id} disposed.`);
}
}
function setupApplication() {
new MyResource(1);
new MyResource(2);
class="hl-cmt">// ... more resources
}
async function shutdownApplication() {
console.log(39;Shutting down application, disposing resources...39;);
await disposeAll(); class="hl-cmt">// This will call dispose on MyResource(2) then MyResource(1)
console.log(39;Application shutdown complete.39;);
}
setupApplication();
shutdownApplication();
4. Error Utilities (src/utils/errors.ts)
This module defines a set of custom error classes for specific application scenarios and provides utility functions for robust error handling, including retries and timeouts for asynchronous operations.
Purpose: To provide a structured and categorized error system, making error identification and handling more consistent. It also offers practical utilities for managing common asynchronous failure patterns.
Key Components:
Custom Error Classes (all extend CodeBuddyError):
CodeBuddyError: The base class for all custom application errors. Includescodeanddetailsproperties for structured error information.toJSON(): Serializes the error to a plain object for logging or API responses.APIError: For errors originating from API calls, includesstatusCodeandresponse.FileError: For file system related errors, includesfilePathandoperation.FileNotFoundError: A specificFileErrorfor when a file is not found.TimeoutError: For operations that exceed a time limit, includestimeoutMs.InvalidCommandError: For attempts to execute blocked or invalid commands, includescommand.ValidationError: For input validation failures, includesfieldandvalue.
Utility Functions:
isCodeBuddyError(error: unknown): error is CodeBuddyError: Type guard to check if an error is an instance ofCodeBuddyErroror one of its subclasses.getErrorMessage(error: unknown): string: Extracts a user-friendly message from various error types (Error objects, strings, or unknown values).withTimeout: Wraps a promise, rejecting with a(promise: Promise , timeoutMs: number, message?: string): Promise TimeoutErrorif the promise does not resolve withintimeoutMs.withRetry: Retries an asynchronous function(fn: () => Promise , options?: RetryOptions): Promise fna specified number of times with exponential backoff, useful for transient failures.
Usage Example:
import {
CodeBuddyError,
APIError,
FileNotFoundError,
withTimeout,
withRetry,
getErrorMessage,
} from 39;./src/utils/errors39;;
class="hl-cmt">// Custom Error Usage
try {
throw new APIError(39;Failed to fetch user data39;, 500, { reason: 39;server_error39; });
} catch (e) {
if (e instanceof APIError) {
console.error(`API Error (${e.statusCode}): ${e.message} - Details: ${JSON.stringify(e.response)}`);
}
}
class="hl-cmt">// withTimeout Usage
async function fetchDataWithTimeout() {
const slowPromise = new Promise(resolve => setTimeout(() => resolve(39;data39;), 500));
try {
const result = await withTimeout(slowPromise, 100, 39;Data fetch took too long!39;);
console.log(result);
} catch (e) {
console.error(getErrorMessage(e)); class="hl-cmt">// "Data fetch took too long!"
}
}
fetchDataWithTimeout();
class="hl-cmt">// withRetry Usage
let attemptCount = 0;
async function flakyOperation() {
attemptCount++;
console.log(`Attempt ${attemptCount} to perform flaky operation...`);
if (attemptCount < 3) {
throw new Error(39;Transient network issue39;);
}
return 39;Operation successful!39;;
}
(async () => {
try {
const result = await withRetry(flakyOperation, { maxRetries: 2, initialDelay: 50 });
console.log(result); class="hl-cmt">// "Operation successful!" after 3 attempts
} catch (e) {
console.error(`Failed after retries: ${getErrorMessage(e)}`);
}
})();
5. Exit Codes (src/utils/exit-codes.ts)
This module defines a standardized set of exit codes for the application and provides utilities to manage the application's exit behavior, including mapping errors to appropriate exit codes.
Purpose: To ensure consistent and meaningful exit codes, allowing external scripts or CI/CD pipelines to interpret the application's termination status reliably. It also centralizes fatal error handling.
Key Components:
EXIT_CODESObject: A collection of numeric constants representing standard exit codes (e.g.,SUCCESS,GENERAL_ERROR,API_ERROR,FILE_NOT_FOUND).
API & Functionality:
EXIT_CODES:SUCCESS = 0GENERAL_ERROR = 1INVALID_USAGE = 2API_ERRORAUTHENTICATION_ERRORTIMEOUTFILE_NOT_FOUNDPERMISSION_DENIEDNETWORK_ERRORUSER_CANCELLED = 130(standard for Ctrl+C)- ... and other specific error codes.
getExitCodeDescription(code: number): string: Returns a human-readable description for a givenEXIT_CODESvalue.exitWithCode(code: number, message?: string): Terminates the application with the specifiedcode. If amessageis provided, it's logged toconsole.logfor success codes orconsole.errorfor error codes.errorToExitCode(error: Error | unknown): number: Attempts to map a givenErrorobject (or unknown value) to the most appropriateEXIT_CODESvalue based on its message or type.handleFatalError(error: Error | unknown): A high-level function to process a fatal error. It logs the error, determines the appropriate exit code usingerrorToExitCode, and then callsexitWithCodeto terminate the application.
Usage Example:
import {
EXIT_CODES,
exitWithCode,
errorToExitCode,
handleFatalError,
} from 39;./src/utils/exit-codes39;;
import { APIError, FileNotFoundError } from 39;./src/utils/errors39;; class="hl-cmt">// Assuming custom errors
function performOperation(shouldFail: boolean, errorType?: string) {
try {
if (shouldFail) {
if (errorType === 39;api39;) {
throw new APIError(39;Service unavailable39;, 503);
} else if (errorType === 39;file39;) {
throw new FileNotFoundError(39;/non/existent/file.txt39;);
} else if (errorType === 39;auth39;) {
throw new Error(39;Invalid API key39;); class="hl-cmt">// errorToExitCode will map this
} else {
throw new Error(39;An unexpected error occurred.39;);
}
}
exitWithCode(EXIT_CODES.SUCCESS, 39;Operation completed successfully.39;);
} catch (e) {
handleFatalError(e); class="hl-cmt">// This will log the error and exit with the mapped code
}
}
class="hl-cmt">// Example calls (these would typically terminate the process)
class="hl-cmt">// performOperation(false); // Exits with SUCCESS
class="hl-cmt">// performOperation(true, 39;api39;); // Exits with API_ERROR
class="hl-cmt">// performOperation(true, 39;file39;); // Exits with FILE_NOT_FOUND
class="hl-cmt">// performOperation(true, 39;auth39;); // Exits with AUTHENTICATION_ERROR
class="hl-cmt">// performOperation(true); // Exits with GENERAL_ERROR
6. Glob Matcher (src/utils/glob-matcher.ts)
This module provides utilities for matching strings against glob patterns, commonly used for filtering lists of items like files, tools, or configurations.
Purpose: To offer flexible pattern matching capabilities, similar to shell globs, for filtering and selection logic within the application.
Key Components:
ToolFilterConfigInterface: Defines the structure for tool filtering, includingenabledToolsanddisabledToolsarrays of glob patterns.
API & Functionality:
globToRegex(glob: string): RegExp: Converts a glob pattern string into a regular expression object. Supports(any non-/),*(any character including/),?(single character),[abc](character sets), and{a,b}(brace expansion).matchGlob(text: string, pattern: string): boolean: Checks if atextstring matches a singlepattern.matchAnyGlob(text: string, patterns: string[]): boolean: Returnstrueiftextmatches any of the providedpatterns.matchAllGlobs(text: string, patterns: string[]): boolean: Returnstrueiftextmatches all of the providedpatterns.filterByGlob: Filters an array of(items: T[], patterns: string[], accessor?: (item: T) => string): T[] items, returning only those that match any of thepatterns. An optionalaccessorfunction can be provided to extract the string to be matched from each item.excludeByGlob: Filters an array of(items: T[], patterns: string[], accessor?: (item: T) => string): T[] items, returning only those that do not match any of thepatterns.filterTools(tools: string[], config: ToolFilterConfig): string[]: Filters a list of tool names based on anenabledTools(whitelist) anddisabledTools(blacklist) configuration. Blacklist takes precedence.isToolEnabled(toolName: string, config: ToolFilterConfig): boolean: Checks if a single tool is enabled according to the providedToolFilterConfig.
Usage Example:
import {
matchGlob,
filterByGlob,
filterTools,
isToolEnabled,
} from 39;./src/utils/glob-matcher39;;
class="hl-cmt">// Basic matching
console.log(matchGlob(39;my_file.txt39;, 39;*.txt39;)); class="hl-cmt">// true
console.log(matchGlob(39;src/components/button.ts39;, 39;src/**39;)); class="hl-cmt">// true
class="hl-cmt">// Filtering arrays
const files = [39;index.js39;, 39;src/main.ts39;, 39;src/utils/helper.js39;, 39;test/test.js39;];
const jsFiles = filterByGlob(files, [39;*.js39;]);
console.log(jsFiles); class="hl-cmt">// [39;index.js39;, 39;src/utils/helper.js39;, 39;test/test.js39;]
class="hl-cmt">// Tool filtering
const allTools = [39;bash39;, 39;git39;, 39;npm39;, 39;web_search39;, 39;view_file39;, 39;mcp__filesystem__read39;];
const toolConfig = {
enabledTools: [39;*39;, 39;mcp__*39;], class="hl-cmt">// Enable all, but then disable specific ones
disabledTools: [39;web_*39;, 39;mcp__filesystem__read39;],
};
const availableTools = filterTools(allTools, toolConfig);
console.log(availableTools); class="hl-cmt">// [39;bash39;, 39;git39;, 39;npm39;, 39;view_file39;]
console.log(isToolEnabled(39;bash39;, toolConfig)); class="hl-cmt">// true
console.log(isToolEnabled(39;web_search39;, toolConfig)); class="hl-cmt">// false
console.log(isToolEnabled(39;mcp__filesystem__read39;, toolConfig)); class="hl-cmt">// false
7. Input Validator (src/utils/input-validator.ts)
This module provides a comprehensive set of functions for validating various input types and structures, returning detailed validation results or throwing assertion errors.
Purpose: To ensure data integrity and correctness by providing a consistent and extensible way to validate user input, configuration values, or API payloads.
Key Components:
ValidationResultType:{ valid: true, value: T } | { valid: false, error: string }. Allvalidate*functions return this type.- Numerous
validate*Functions: Each designed for a specific type or format (e.g.,validateString,validateNumber,validateArray,validateObject,validateUrl,validateEmail,validateSchema). assert*Functions: Convenience functions that throw an error if validation fails, useful when a valid input is strictly expected.
API & Functionality (Highlights):
Basic Type Validators:
validateString(value: unknown, options?: StringValidationOptions): Validates ifvalueis a non-empty string. Options includeallowEmpty,fieldName,customError.validateStringLength(value: string, min: number, max: number, options?: ValidationOptions): Validates string length.validatePattern(value: string, pattern: RegExp, options?: PatternValidationOptions): Validates string against a regex pattern.validateNumber(value: unknown, options?: ValidationOptions): Validates ifvalueis a number or a numeric string.validateNumberRange(value: number, min: number, max: number, options?: ValidationOptions): Validates if a number is within a specified range.validatePositiveInteger(value: unknown, options?: ValidationOptions): Validates ifvalueis a positive integer.validateBoolean(value: unknown, options?: ValidationOptions): Validates ifvalueis a boolean or can be parsed as one (e.g., "true", "false", 1, 0).validateArray(value: unknown, options?: ArrayValidationOptions): Validates ifvalueis an array. Options includeminLength,maxLength.validateObject(value: unknown, options?: ValidationOptions): Validates ifvalueis a plain object.validateChoice: Validates if(value: unknown, choices: readonly T[], options?: ValidationOptions) valueis one of the predefinedchoices.
Specialized Format Validators:
validateUrl(value: unknown, options?: UrlValidationOptions): Validates ifvalueis a valid URL. Options forprotocols.validateEmail(value: unknown, options?: ValidationOptions): Validates ifvalueis a valid email address.validateFilePath(value: unknown, options?: FilePathValidationOptions): Validates ifvalueis a valid file path. Options formustBeAbsolute.
Composite Validators:
validateOptional: Makes any validator optional. If(value: unknown, validator: (val: unknown, opts?: any) => ValidationResult , options?: ValidationOptions) valueisundefinedornull, it's considered valid and returnsundefined. Otherwise, it applies thevalidator.validateWithDefault: Provides a(value: unknown, defaultValue: T, validator: (val: unknown, opts?: any) => ValidationResult , options?: ValidationOptions) defaultValueifvalueisundefinedornull. Otherwise, it applies thevalidatortovalue.
Schema Validation:
validateSchema: Validates an object against a defined schema. Each schema property specifies a>(data: unknown, schema: SchemaDefinition , options?: SchemaValidationOptions) validator,requiredstatus, and an optionaldefaultvalue.strictmode can reject unknown fields.
Assertion Helpers:
assertValid: Throws a(result: ValidationResult , context?: string): T ValidationErrorifresult.validisfalse, otherwise returnsresult.value.assertString(value: unknown, options?: StringValidationOptions): string: CombinesvalidateStringandassertValid.assertNumber(value: unknown, options?: ValidationOptions): number: CombinesvalidateNumberandassertValid.
Usage Example:
import {
validateString,
validatePositiveInteger,
validateEmail,
validateSchema,
assertValid,
ValidationError,
} from 39;./src/utils/input-validator39;;
class="hl-cmt">// Basic validation
const nameResult = validateString(39;Alice39;, { fieldName: 39;User Name39; });
if (nameResult.valid) {
console.log(`Name: ${nameResult.value}`);
}
const ageResult = validatePositiveInteger(30);
if (!ageResult.valid) {
console.error(`Age error: ${ageResult.error}`);
}
class="hl-cmt">// Schema validation
const userSchema = {
username: { validator: validateString, required: true, fieldName: 39;Username39; },
age: { validator: validatePositiveInteger, required: true, fieldName: 39;Age39; },
email: { validator: validateEmail, required: false },
role: { validator: validateString, default: 39;guest39; },
};
const userData = {
username: 39;john_doe39;,
age: 25,
email: 39;john@example.com39;,
};
try {
const validatedUser = assertValid(validateSchema(userData, userSchema));
console.log(39;Validated User:39;, validatedUser);
class="hl-cmt">// Output: { username: 39;john_doe39;, age: 25, email: 39;john@example.com39;, role: 39;guest39; }
class="hl-cmt">// Example with missing required field
const invalidUserData = { username: 39;jane_doe39; };
assertValid(validateSchema(invalidUserData, userSchema));
} catch (e) {
if (e instanceof ValidationError) {
console.error(`Validation Error: ${e.message}`); class="hl-cmt">// Contains 39;age is required39;
}
}
8. LRU Cache (src/utils/lru-cache.ts)
This module provides a robust Least Recently Used (LRU) cache implementation with optional Time-To-Live (TTL) expiration, detailed statistics, and factory functions for common cache configurations.
Purpose: To efficiently manage a fixed-size cache, automatically evicting the least recently used items when capacity is reached, and supporting time-based expiration for cached data.
Key Components:
LRUCacheClass: The primary LRU cache implementation. It extendsEventEmitterto emit cache-related events.LRUMapClass: A specializedLRUCachethat behaves more like a standardMapbut with LRU eviction and optional key serialization.CACHE_SIZESObject: Constants for common cache size configurations (e.g.,SMALL,MEDIUM,LARGE,XLARGE,CHECKPOINT,CHUNK_STORE,MEMORY,ANALYSIS).CACHE_TTLObject: Constants for common TTL durations (e.g.,SHORT,MEDIUM,LONG,HOUR,DAY).- Factory Functions:
createCheckpointCache,createChunkStoreCache,createMemoryCache,createAnalysisCacheprovide pre-configuredLRUCacheinstances.
API & Functionality:
LRUCache Class:
constructor(options: LRUCacheOptions: Initializes the cache with) maxSize, optionalttlMs, andonEvictcallback.set(key: K, value: V, ttlMs?: number): Adds or updates an entry. IfmaxSizeis exceeded, the LRU entry is evicted. Emitssetevent.get(key: K): V | undefined: Retrieves a value. Marks the entry as most recently used. Returnsundefinedif not found or expired. Tracks hits/misses.has(key: K): boolean: Checks for the existence of a non-expired key.delete(key: K): boolean: Removes an entry. Emitsdeleteevent.clear(): Empties the cache. Emitsclearevent.size: number: Getter for the current number of non-expired entries.keys(): K[]: Returns an array of all non-expired keys.values(): V[]: Returns an array of all non-expired values.entries(): [K, V][]: Returns an array of all non-expired key-value pairs.forEach(callback: (value: V, key: K, cache: this) => void, thisArg?: any): Iterates over non-expired entries.[Symbol.iterator](): Makes the cache iterable (e.g.,for (const [key, value] of cache)).toObject(): Record: Converts the cache to a plain JavaScript object (keys are stringified).fromObject(obj: Record: Populates the cache from a plain object or Map.| Map ) getStats(): CacheStats: Returns an object with cache statistics (hits, misses, hitRate, evictions, size, maxSize).resetStats(): Resets hit/miss/eviction counters.prune(): number: Manually removes all expired entries. Returns the number of entries pruned.setMaxSize(newSize: number): Adjusts the maximum size of the cache, potentially triggering evictions.dispose(): Clears the cache and removes all event listeners.- Events: Emits
set,delete,evict,clearevents.
LRUMap Class:
- Extends
LRUCacheand provides akeyToStringoption in its constructor for custom key serialization, allowing complex objects to be used as keys.
Factory Functions:
createCheckpointCache: Creates an() LRUCachewithCACHE_SIZES.CHECKPOINT.createChunkStoreCache: Creates an() LRUCachewithCACHE_SIZES.CHUNK_STORE.createMemoryCache: Creates an() LRUCachewithCACHE_SIZES.MEMORY.createAnalysisCache: Creates an() LRUCachewithCACHE_SIZES.ANALYSIS.
Usage Example:
import { LRUCache, CACHE_SIZES, CACHE_TTL, createMemoryCache } from 39;./src/utils/lru-cache39;;
class="hl-cmt">// Create a cache with a specific size and default TTL
const userCache = new LRUCache<string, { name: string }>({
maxSize: CACHE_SIZES.MEDIUM, class="hl-cmt">// 500 entries
ttlMs: CACHE_TTL.HOUR, class="hl-cmt">// 1 hour
onEvict: (key, value) => console.log(`Evicted user: ${value.name} (${key})`),
});
userCache.set(39;user:139;, { name: 39;Alice39; });
userCache.set(39;user:239;, { name: 39;Bob39; });
console.log(userCache.get(39;user:139;)); class="hl-cmt">// { name: 39;Alice39; }
console.log(userCache.getStats()); class="hl-cmt">// { hits: 1, misses: 0, ... }
class="hl-cmt">// Accessing 39;user:139; makes it most recently used
userCache.get(39;user:139;);
class="hl-cmt">// Fill up and evict
for (let i = 3; i <= CACHE_SIZES.MEDIUM + 1; i++) {
userCache.set(`user:${i}`, { name: `User ${i}` });
}
class="hl-cmt">// 39;user:239; should be evicted as it was LRU before 39;user:139; was accessed
console.log(userCache.has(39;user:239;)); class="hl-cmt">// false
class="hl-cmt">// Using a factory function
const analysisCache = createMemoryCache<string>();
analysisCache.set(39;report:summary39;, 39;...39;);
9. Model Utilities (src/utils/model-utils.ts)
This module provides a centralized way to manage and query information about the various AI models supported by the application, including their providers, capabilities, and default selections.
Purpose: To abstract away the details of different AI models and providers, offering a consistent interface for model selection, validation, and information retrieval.
Key Components:
- A hidden internal registry of supported models and their properties (max tokens, provider, etc.).
API & Functionality:
isSupportedModel(modelName: string): boolean: Checks if a givenmodelNameis recognized and supported by the application.getModelInfo(modelName: string): ModelInfo: Returns an object containing detailed information about a model, such asisSupported,maxTokens, andprovider. Provides default values for unsupported models.validateModel(modelName: string, strict: boolean): void: Validates amodelName. Instrictmode, it throws aValidationErrorif the model is not supported. In non-strict mode, it only checks for non-empty strings.getDefaultModel(provider: ModelProvider): string: Returns the default model name for a givenModelProvider(e.g., 'xai', 'anthropic', 'openai', 'google', 'lmstudio').getSupportedModels(): string[]: Returns an array of all currently supported model names.getModelsByProvider(provider: ModelProvider): string[]: Returns an array of model names associated with a specificModelProvider.suggestModel(partialName: string): string[]: Provides a list of supported model names that match or partially match thepartialName(case-insensitive).formatModelInfo(modelName: string): string: Returns a human-readable, formatted string containing the details of a given model.
Usage Example:
import {
isSupportedModel,
getModelInfo,
validateModel,
getDefaultModel,
getSupportedModels,
getModelsByProvider,
suggestModel,
formatModelInfo,
} from 39;./src/utils/model-utils39;;
import { ValidationError } from 39;./src/utils/errors39;;
class="hl-cmt">// Check model support
console.log(isSupportedModel(39;grok-4-latest39;)); class="hl-cmt">// true
console.log(isSupportedModel(39;unknown-model39;)); class="hl-cmt">// false
class="hl-cmt">// Get model information
const grokInfo = getModelInfo(39;grok-4-latest39;);
console.log(`Grok Max Tokens: ${grokInfo.maxTokens}, Provider: ${grokInfo.provider}`);
class="hl-cmt">// Validate model
try {
validateModel(39;claude-opus-4-639;, true); class="hl-cmt">// OK in strict mode
validateModel(39;non-existent-model39;, true); class="hl-cmt">// Throws ValidationError
} catch (e) {
if (e instanceof ValidationError) {
console.error(`Model validation failed: ${e.message}`);
}
}
class="hl-cmt">// Get defaults and lists
console.log(`Default OpenAI model: ${getDefaultModel(39;openai39;)}`);
console.log(`All supported models: ${getSupportedModels().join(39;, 39;)}`);
console.log(`Google models: ${getModelsByProvider(39;google39;).join(39;, 39;)}`);
class="hl-cmt">// Suggest models
console.log(`Suggestions for 39;grok39;: ${suggestModel(39;grok39;).join(39;, 39;)}`);
class="hl-cmt">// Format info
console.log(formatModelInfo(39;gpt-4o39;));
10. Path Validator (src/utils/path-validator.ts)
The PathValidator module provides robust security checks for file paths, ensuring that operations are confined to allowed directories and preventing path traversal vulnerabilities.
Purpose: To safeguard against malicious path inputs by validating that all file system operations occur within a defined base directory or explicitly allowed paths, and to resolve paths safely.
Key Components:
PathValidatorClass: The core class responsible for path validation logic.getPathValidator(): PathValidator: Returns the singleton instance of the globalPathValidator.initializePathValidator(options?: PathValidatorOptions): PathValidator: Initializes or re-initializes the globalPathValidatorwith specific options.validatePath(inputPath: string, options?: PathValidationOptions): PathValidationResult: A global helper to validate a single path using the global validator.isPathSafe(inputPath: string, options?: PathValidationOptions): boolean: A global helper to quickly check if a path is safe.
API & Functionality:
PathValidator Class:
constructor(options?: PathValidatorOptions): Initializes the validator. Options includebaseDirectory(defaults toprocess.cwd()),checkSymlinks(defaults totrue),additionalAllowedPaths, andallowOutsideBase.setBaseDirectory(dir: string): Updates the base directory for validation.getBaseDirectory(): string: Returns the currently configured base directory.validate(inputPath: string, options?: PathValidationOptions): PathValidationResult: The primary validation method. It normalizes the path, checks for emptiness, ensures it's within thebaseDirectory(oradditionalAllowedPaths), and optionally resolves symlinks to their real paths to prevent escapes. Returns aPathValidationResultobject ({ valid: boolean, resolved?: string, error?: string }).validateMany(paths: string[], options?: PathValidationOptions): MultiPathValidationResult: Validates an array of paths, collecting all results and errors.isSafe(inputPath: string, options?: PathValidationOptions): boolean: A convenience method that returnstrueifvalidatereturnsvalid: true,falseotherwise.resolveOrThrow(inputPath: string, options?: PathValidationOptions): string: Validates a path and returns its resolved absolute path if valid. Throws an error if validation fails.
Global Helper Functions:
getPathValidator(): Accesses the global singletonPathValidator.initializePathValidator(options?: PathValidatorOptions): Sets up the globalPathValidatorinstance. This should typically be called once at application startup.validatePath(inputPath: string, options?: PathValidationOptions): Uses the global validator to validate a path.isPathSafe(inputPath: string, options?: PathValidationOptions): Uses the global validator for a quick safety check.
Usage Example:
import * as path from 39;path39;;
import * as os from 39;os39;;
import {
initializePathValidator,
getPathValidator,
validatePath,
isPathSafe,
} from 39;./src/utils/path-validator39;;
class="hl-cmt">// Initialize the global validator with the current working directory as base
initializePathValidator({ baseDirectory: process.cwd() });
const validator = getPathValidator();
const safePath = path.join(process.cwd(), 39;my_project39;, 39;file.txt39;);
const unsafePath = 39;/etc/passwd39;;
const traversalPath = path.join(process.cwd(), 39;..39;, 39;sibling_dir39;, 39;secret.txt39;);
class="hl-cmt">// Using the instance
console.log(`Is "${safePath}" safe? ${validator.isSafe(safePath)}`); class="hl-cmt">// true
console.log(`Is "${unsafePath}" safe? ${validator.isSafe(unsafePath)}`); class="hl-cmt">// false
const result = validator.validate(traversalPath);
if (!result.valid) {
console.error(`Validation error for "${traversalPath}": ${result.error}`);
}
class="hl-cmt">// Using global helpers
const globalSafeResult = validatePath(safePath);
if (globalSafeResult.valid) {
console.log(`Global check: "${globalSafeResult.resolved}" is valid.`);
}
class="hl-cmt">// Example with additional allowed paths
initializePathValidator({
baseDirectory: process.cwd(),
additionalAllowedPaths: [os.tmpdir()],
});
const tmpFile = path.join(os.tmpdir(), 39;temp_log.txt39;);
console.log(`Is "${tmpFile}" safe with additional allowed path? ${isPathSafe(tmpFile)}`); class="hl-cmt">// true
11. Semantic Truncation (src/utils/head-tail-truncation.ts)
This module provides intelligent text truncation capabilities, designed to shorten long outputs while preserving critical information such as error messages, JSON structure, or custom patterns.
Purpose: To make verbose text outputs more manageable for display or processing, without losing essential context that might be buried in the middle of the text.
Key Components:
TruncationResultType:{ output: string, truncated: boolean, omittedLines: number }.SemanticTruncationOptionsInterface: Configures truncation behavior (head/tail lines, preserve errors/JSON/custom patterns).
API & Functionality:
needsTruncation(text: string, maxLines?: number): boolean: Checks if the giventextexceeds amaxLinesthreshold (defaults to 200) and thus requires truncation.headTailTruncate(text: string, options?: { headLines?: number; tailLines?: number }): TruncationResult: A basic truncation function that keeps a specified number of lines from the beginning (headLines) and end (tailLines) of the text, inserting an omission message in between.semanticTruncate(text: string, options?: SemanticTruncationOptions): TruncationResult: The main intelligent truncation function. It combines head/tail truncation with logic to preserve:- Error lines (if
preserveErrorsis true). - JSON structure (if
preserveJsonis true, attempts to keep opening/closing braces and array/object structures). - Lines matching custom regular expressions (if
preservePatternsis provided).
It inserts a message indicating the number of omitted lines and any preserved important lines.
Usage Example:
import {
semanticTruncate,
needsTruncation,
} from 39;./src/utils/head-tail-truncation39;;
const longLog = `Line 1: Start of log
Line 2: Some normal output
... (many lines) ...
Line 100: Error: Failed to connect to database
Line 101: at connectDB (/app/src/db.ts:20:15)
Line 102: at main (/app/src/index.ts:5:3)
... (many more lines) ...
Line 200: End of log`;
const jsonOutput = `{
"data": [
{"id": 1, "value": "A"},
{"id": 2, "value": "B"},
class="hl-cmt">// ... 100s of entries ...
{"id": 999, "value": "Y"},
{"id": 1000, "value": "Z"}
],
"metadata": {
"count": 1000,
"status": "complete"
}
}`;
class="hl-cmt">// Check if truncation is needed
console.log(`Needs truncation? ${needsTruncation(longLog, 50)}`); class="hl-cmt">// true
class="hl-cmt">// Semantic truncation preserving errors
const truncatedLog = semanticTruncate(longLog, {
headLines: 5,
tailLines: 5,
preserveErrors: true,
});
console.log(39;--- Truncated Log (Errors Preserved) ---39;);
console.log(truncatedLog.output);
console.log(`Omitted lines: ${truncatedLog.omittedLines}`);
class="hl-cmt">// Semantic truncation preserving JSON structure
const truncatedJson = semanticTruncate(jsonOutput, {
headLines: 5,
tailLines: 5,
preserveJson: true,
});
console.log(39;\n--- Truncated JSON (Structure Preserved) ---39;);
console.log(truncatedJson.output);
class="hl-cmt">// Custom patterns
const customLog = `Header line 1
Important: User ID 123 processed.
Some other line.
Another line.
Important: Transaction ABC completed.
Footer line 1`;
const truncatedCustom = semanticTruncate(customLog, {
headLines: 1,
tailLines: 1,
preservePatterns: [/Important:/],
});
console.log(39;\n--- Truncated Custom (Patterns Preserved) ---39;);
console.log(truncatedCustom.output);
12. Settings Manager (src/utils/settings-manager.ts)
The SettingsManager module provides a centralized and hierarchical system for managing application settings, including user-specific and project-specific configurations. It handles persistence to JSON files and provides access to various configuration values.
Purpose: To offer a consistent and robust way to store, retrieve, and update application settings, prioritizing project-level configurations over user-level defaults.
Key Components:
SettingsManagerClass: A singleton class that encapsulates all settings management logic.
API & Functionality:
static getSettingsManager(): SettingsManager: Returns the singleton instance of the service.loadUserSettings(): UserSettings: Loads user-specific settings from a configuration file (e.g.,~/.codebuddy/settings.json) and merges them with default values.saveUserSettings(settings: UserSettings): Persists the providedUserSettingsto the user's configuration file.updateUserSetting: Updates a single user setting and saves the changes.(key: K, value: UserSettings[K]) getUserSetting: Retrieves a specific user setting.(key: K): UserSettings[K] loadProjectSettings(): ProjectSettings: Loads project-specific settings from a configuration file (e.g.,.codebuddy/config.jsonin the current project directory) and merges them with default values.saveProjectSettings(settings: ProjectSettings): Persists the providedProjectSettingsto the project's configuration file.updateProjectSetting: Updates a single project setting and saves the changes.(key: K, value: ProjectSettings[K]) getProjectSetting: Retrieves a specific project setting.(key: K): ProjectSettings[K] | undefined getCurrentModel(): string: Determines the active AI model, prioritizing project settings over user settings, and falling back to a hardcoded default.setCurrentModel(model: string): Sets the current model, typically by updating the user settings.getAvailableModels(): string[]: Returns a list of all AI models available for use, usually by delegating tomodel-utils.getApiKey(): string | undefined: Retrieves the API key, typically from environment variables or user settings.getBaseURL(): string: Retrieves the base URL for API calls, prioritizing project settings, then user settings, then a default.
Settings Hierarchy:
The SettingsManager follows a clear hierarchy for resolving settings:
- Project Settings: Settings defined in the project's
.codebuddy/config.jsontake highest precedence. - User Settings: Settings defined in the user's global
~/.codebuddy/settings.jsoncome next. - Application Defaults: Hardcoded default values are used if no other setting is found.
Usage Example:
import { getSettingsManager } from 39;./src/utils/settings-manager39;;
const settingsManager = getSettingsManager();
class="hl-cmt">// Get the current active model
const currentModel = settingsManager.getCurrentModel();
console.log(`Current AI Model: ${currentModel}`);
class="hl-cmt">// Update a user setting
settingsManager.updateUserSetting(39;defaultModel39;, 39;grok-3-latest39;);
console.log(`Updated default model to: ${settingsManager.getUserSetting(39;defaultModel39;)}`);
class="hl-cmt">// Get the API key (might be undefined if not set)
const apiKey = settingsManager.getApiKey();
if (apiKey) {
console.log(39;API Key is set.39;);
} else {
console.log(39;API Key is not set.39;);
}
class="hl-cmt">// Load and inspect project settings
const projectSettings = settingsManager.loadProjectSettings();
console.log(39;Project Settings:39;, projectSettings);
class="hl-cmt">// Example of how settings are resolved (conceptual, as mocks prevent direct testing here)
class="hl-cmt">// If projectSettings.model is 39;gpt-439;, it will override userSettings.defaultModel
class="hl-cmt">// If projectSettings.model is undefined, userSettings.defaultModel will be used
class="hl-cmt">// If both are undefined, a hardcoded default will be used.
13. Text Utilities (src/utils/text-utils.ts)
This module provides a collection of basic text manipulation functions, primarily focused on cursor-based editing operations within a string.
Purpose: To offer fundamental building blocks for implementing text editor-like functionalities, such as deleting characters/words and moving the cursor.
Key Components:
- A set of pure functions that take a string and a cursor position, returning a new string and the updated cursor position.
API & Functionality:
All functions return an object { text: string, position: number }.
deleteCharBefore(text: string, position: number): Deletes the character immediately before theposition.deleteCharAfter(text: string, position: number): Deletes the character immediately after theposition.deleteWordBefore(text: string, position: number): Deletes the word (and any preceding whitespace) before theposition.deleteWordAfter(text: string, position: number): Deletes the word (and any trailing whitespace) after theposition.insertText(text: string, position: number, newText: string): InsertsnewTextat the specifiedposition.moveToLineStart(text: string, position: number): Moves thepositionto the beginning of the current line.moveToLineEnd(text: string, position: number): Moves thepositionto the end of the current line.moveToPreviousWord(text: string, position: number): Moves thepositionto the beginning of the previous word.moveToNextWord(text: string, position: number): Moves thepositionto the beginning of the next word.
Usage Example:
import {
deleteCharBefore,
deleteWordAfter,
insertText,
moveToLineStart,
moveToNextWord,
} from 39;./src/utils/text-utils39;;
let currentText = 39;hello world39;;
let cursor = currentText.length; class="hl-cmt">// At the end
class="hl-cmt">// Delete character before
let result = deleteCharBefore(currentText, cursor);
currentText = result.text; class="hl-cmt">// 39;hello worl39;
cursor = result.position; class="hl-cmt">// 10
class="hl-cmt">// Insert text
result = insertText(currentText, cursor, 39;d!39;);
currentText = result.text; class="hl-cmt">// 39;hello world!39;
cursor = result.position; class="hl-cmt">// 12
class="hl-cmt">// Move to start of next word and delete it
currentText = 39;first second third39;;
cursor = 0;
result = moveToNextWord(currentText, cursor); class="hl-cmt">// cursor moves to 39; second third39;
cursor = result.position; class="hl-cmt">// 6
result = deleteWordAfter(currentText, cursor); class="hl-cmt">// deletes 39;second 39;
currentText = result.text; class="hl-cmt">// 39;first third39;
cursor = result.position; class="hl-cmt">// 6 (still at start of 39;third39;)
class="hl-cmt">// Move to line start
currentText = 39;Line 1\n Line 2 part 1\nLine 339;;
cursor = 18; class="hl-cmt">// In "part 1" of Line 2
cursor = moveToLineStart(currentText, cursor); class="hl-cmt">// Moves to start of " Line 2 part 1"
console.log(`Cursor at: ${cursor}`); class="hl-cmt">// 7 (index of 39; 39;)
14. Update Notifier (src/utils/update-notifier.ts)
The UpdateNotifier module provides functionality to check for new versions of the application and notify the user when an update is available.
Purpose: To keep users informed about new releases, encouraging them to update to the latest version for new features, bug fixes, and security improvements.
Key Components:
UpdateNotifierClass: Manages the update checking process and notification formatting.compareVersions(a: string, b: string): -1 | 0 | 1: A utility function for semantically comparing two version strings.
API & Functionality:
compareVersions(a: string, b: string): -1 | 0 | 1:- Returns
0if versionsaandbare equal. - Returns
-1if versionais older than versionb. - Returns
1if versionais newer than versionb. - Handles
vprefixes (e.g.,v1.0.0is equal to1.0.0). UpdateNotifierClass:constructor(config?: UpdateNotifierConfig): Initializes the notifier.configcan specifyenabled(defaulttrue) andcheckIntervalHours(default24).check(): Promise: Asynchronously checks for updates. It respects thecheckIntervalHoursto avoid frequent checks. ReturnsUpdateInfoif an update is available,nullotherwise.getUpdateInfo(): UpdateInfo | null: Returns the last known update information without performing a new check.formatNotification(): string | null: Returns a formatted string message suitable for display if an update is available, otherwisenull.
Usage Example:
import { UpdateNotifier, compareVersions } from 39;./src/utils/update-notifier39;;
class="hl-cmt">// Version comparison
console.log(`1.0.0 vs 1.0.1: ${compareVersions(39;1.0.039;, 39;1.0.139;)}`); class="hl-cmt">// -1
console.log(`v2.0.0 vs 2.0.0: ${compareVersions(39;v2.0.039;, 39;2.0.039;)}`); class="hl-cmt">// 0
class="hl-cmt">// Initialize notifier
const notifier = new UpdateNotifier({
enabled: true,
checkIntervalHours: 1, class="hl-cmt">// Check every hour
});
async function checkForUpdatesAndNotify() {
console.log(39;Checking for updates...39;);
const updateInfo = await notifier.check();
if (updateInfo && updateInfo.updateAvailable) {
const notificationMessage = notifier.formatNotification();
if (notificationMessage) {
console.log(39;\n--- UPDATE AVAILABLE ---39;);
console.log(notificationMessage);
console.log(39;------------------------\n39;);
}
} else {
console.log(39;No updates available or check skipped.39;);
}
}
class="hl-cmt">// In a real application, this would run periodically or on startup
class="hl-cmt">// checkForUpdatesAndNotify();