feat: create tm-core and apps/cli (#1093)

- add typescript
- add npm workspaces
This commit is contained in:
Ralph Khreish
2025-09-01 21:44:43 +02:00
parent a7ad4c8e92
commit 0f3ab00f26
162 changed files with 22235 additions and 706 deletions

View File

@@ -0,0 +1,423 @@
/**
* @fileoverview AI Provider interface definitions for the tm-core package
* This file defines the contract for all AI provider implementations
*/
/**
* Options for AI completion requests
*/
export interface AIOptions {
/** Temperature for response randomness (0.0 to 1.0) */
temperature?: number;
/** Maximum number of tokens to generate */
maxTokens?: number;
/** Whether to use streaming responses */
stream?: boolean;
/** Top-p sampling parameter (0.0 to 1.0) */
topP?: number;
/** Frequency penalty to reduce repetition (-2.0 to 2.0) */
frequencyPenalty?: number;
/** Presence penalty to encourage new topics (-2.0 to 2.0) */
presencePenalty?: number;
/** Stop sequences to halt generation */
stop?: string | string[];
/** Custom system prompt override */
systemPrompt?: string;
/** Request timeout in milliseconds */
timeout?: number;
/** Number of retry attempts on failure */
retries?: number;
}
/**
* Response from AI completion request
*/
export interface AIResponse {
/** Generated text content */
content: string;
/** Token count for the request */
inputTokens: number;
/** Token count for the response */
outputTokens: number;
/** Total tokens used */
totalTokens: number;
/** Cost in USD (if available) */
cost?: number;
/** Model used for generation */
model: string;
/** Provider name */
provider: string;
/** Response timestamp */
timestamp: string;
/** Request duration in milliseconds */
duration: number;
/** Whether the response was cached */
cached?: boolean;
/** Finish reason (completed, length, stop, etc.) */
finishReason?: string;
}
/**
* AI model information
*/
export interface AIModel {
/** Model identifier */
id: string;
/** Human-readable model name */
name: string;
/** Model description */
description?: string;
/** Maximum context length in tokens */
contextLength: number;
/** Input cost per 1K tokens in USD */
inputCostPer1K?: number;
/** Output cost per 1K tokens in USD */
outputCostPer1K?: number;
/** Whether the model supports function calling */
supportsFunctions?: boolean;
/** Whether the model supports vision/image inputs */
supportsVision?: boolean;
/** Whether the model supports streaming */
supportsStreaming?: boolean;
}
/**
* Provider capabilities and metadata
*/
export interface ProviderInfo {
/** Provider name */
name: string;
/** Provider display name */
displayName: string;
/** Provider description */
description?: string;
/** Base API URL */
baseUrl?: string;
/** Available models */
models: AIModel[];
/** Default model ID */
defaultModel: string;
/** Whether the provider requires an API key */
requiresApiKey: boolean;
/** Supported features */
features: {
streaming?: boolean;
functions?: boolean;
vision?: boolean;
embeddings?: boolean;
};
}
/**
* Interface for AI provider implementations
* All AI providers must implement this interface
*/
export interface IAIProvider {
/**
* Generate a text completion from a prompt
* @param prompt - Input prompt text
* @param options - Optional generation parameters
* @returns Promise that resolves to AI response
*/
generateCompletion(prompt: string, options?: AIOptions): Promise<AIResponse>;
/**
* Generate a streaming completion (if supported)
* @param prompt - Input prompt text
* @param options - Optional generation parameters
* @returns AsyncIterator of response chunks
*/
generateStreamingCompletion(
prompt: string,
options?: AIOptions
): AsyncIterator<Partial<AIResponse>>;
/**
* Calculate token count for given text
* @param text - Text to count tokens for
* @param model - Optional model to use for counting
* @returns Number of tokens
*/
calculateTokens(text: string, model?: string): number;
/**
* Get the provider name
* @returns Provider name string
*/
getName(): string;
/**
* Get current model being used
* @returns Current model ID
*/
getModel(): string;
/**
* Set the model to use for requests
* @param model - Model ID to use
*/
setModel(model: string): void;
/**
* Get the default model for this provider
* @returns Default model ID
*/
getDefaultModel(): string;
/**
* Check if the provider is available and configured
* @returns Promise that resolves to availability status
*/
isAvailable(): Promise<boolean>;
/**
* Get provider information and capabilities
* @returns Provider information object
*/
getProviderInfo(): ProviderInfo;
/**
* Get available models for this provider
* @returns Array of available models
*/
getAvailableModels(): AIModel[];
/**
* Validate API key or credentials
* @returns Promise that resolves to validation status
*/
validateCredentials(): Promise<boolean>;
/**
* Get usage statistics if available
* @returns Promise that resolves to usage stats or null
*/
getUsageStats(): Promise<ProviderUsageStats | null>;
/**
* Initialize the provider (set up connections, validate config, etc.)
* @returns Promise that resolves when initialization is complete
*/
initialize(): Promise<void>;
/**
* Clean up and close provider connections
* @returns Promise that resolves when cleanup is complete
*/
close(): Promise<void>;
}
/**
* Usage statistics for a provider
*/
export interface ProviderUsageStats {
/** Total requests made */
totalRequests: number;
/** Total tokens consumed */
totalTokens: number;
/** Total cost in USD */
totalCost: number;
/** Requests today */
requestsToday: number;
/** Tokens used today */
tokensToday: number;
/** Cost today */
costToday: number;
/** Average response time in milliseconds */
averageResponseTime: number;
/** Success rate (0.0 to 1.0) */
successRate: number;
/** Last request timestamp */
lastRequestAt?: string;
/** Rate limit information if available */
rateLimits?: {
requestsPerMinute: number;
tokensPerMinute: number;
requestsRemaining: number;
tokensRemaining: number;
resetTime: string;
};
}
/**
* Configuration for AI provider instances
*/
export interface AIProviderConfig {
/** API key for the provider */
apiKey: string;
/** Base URL override */
baseUrl?: string;
/** Default model to use */
model?: string;
/** Default generation options */
defaultOptions?: AIOptions;
/** Request timeout in milliseconds */
timeout?: number;
/** Maximum retry attempts */
maxRetries?: number;
/** Custom headers to include in requests */
headers?: Record<string, string>;
/** Enable request/response logging */
enableLogging?: boolean;
/** Enable usage tracking */
enableUsageTracking?: boolean;
}
/**
* Abstract base class for AI provider implementations
* Provides common functionality and enforces the interface
*/
export abstract class BaseAIProvider implements IAIProvider {
protected config: AIProviderConfig;
protected currentModel: string;
protected usageStats: ProviderUsageStats | null = null;
constructor(config: AIProviderConfig) {
this.config = config;
this.currentModel = config.model || this.getDefaultModel();
if (config.enableUsageTracking) {
this.initializeUsageTracking();
}
}
// Abstract methods that must be implemented by concrete classes
abstract generateCompletion(
prompt: string,
options?: AIOptions
): Promise<AIResponse>;
abstract generateStreamingCompletion(
prompt: string,
options?: AIOptions
): AsyncIterator<Partial<AIResponse>>;
abstract calculateTokens(text: string, model?: string): number;
abstract getName(): string;
abstract getDefaultModel(): string;
abstract isAvailable(): Promise<boolean>;
abstract getProviderInfo(): ProviderInfo;
abstract validateCredentials(): Promise<boolean>;
abstract initialize(): Promise<void>;
abstract close(): Promise<void>;
// Implemented methods with common functionality
getModel(): string {
return this.currentModel;
}
setModel(model: string): void {
const availableModels = this.getAvailableModels();
const modelExists = availableModels.some((m) => m.id === model);
if (!modelExists) {
throw new Error(
`Model "${model}" is not available for provider "${this.getName()}"`
);
}
this.currentModel = model;
}
getAvailableModels(): AIModel[] {
return this.getProviderInfo().models;
}
async getUsageStats(): Promise<ProviderUsageStats | null> {
return this.usageStats;
}
/**
* Initialize usage tracking
*/
protected initializeUsageTracking(): void {
this.usageStats = {
totalRequests: 0,
totalTokens: 0,
totalCost: 0,
requestsToday: 0,
tokensToday: 0,
costToday: 0,
averageResponseTime: 0,
successRate: 1.0
};
}
/**
* Update usage statistics after a request
* @param response - AI response to record
* @param duration - Request duration in milliseconds
* @param success - Whether the request was successful
*/
protected updateUsageStats(
response: AIResponse,
duration: number,
success: boolean
): void {
if (!this.usageStats) return;
this.usageStats.totalRequests++;
this.usageStats.totalTokens += response.totalTokens;
if (response.cost) {
this.usageStats.totalCost += response.cost;
}
// Update daily stats (simplified - would need proper date tracking)
this.usageStats.requestsToday++;
this.usageStats.tokensToday += response.totalTokens;
if (response.cost) {
this.usageStats.costToday += response.cost;
}
// Update average response time
const totalTime =
this.usageStats.averageResponseTime * (this.usageStats.totalRequests - 1);
this.usageStats.averageResponseTime =
(totalTime + duration) / this.usageStats.totalRequests;
// Update success rate
const successCount = Math.floor(
this.usageStats.successRate * (this.usageStats.totalRequests - 1)
);
const newSuccessCount = successCount + (success ? 1 : 0);
this.usageStats.successRate =
newSuccessCount / this.usageStats.totalRequests;
this.usageStats.lastRequestAt = new Date().toISOString();
}
/**
* Merge user options with default options
* @param userOptions - User-provided options
* @returns Merged options object
*/
protected mergeOptions(userOptions?: AIOptions): AIOptions {
return {
temperature: 0.7,
maxTokens: 2000,
stream: false,
topP: 1.0,
frequencyPenalty: 0.0,
presencePenalty: 0.0,
timeout: 30000,
retries: 3,
...this.config.defaultOptions,
...userOptions
};
}
/**
* Validate prompt input
* @param prompt - Prompt to validate
* @throws Error if prompt is invalid
*/
protected validatePrompt(prompt: string): void {
if (!prompt || typeof prompt !== 'string') {
throw new Error('Prompt must be a non-empty string');
}
if (prompt.trim().length === 0) {
throw new Error('Prompt cannot be empty or only whitespace');
}
}
}

View File

@@ -0,0 +1,413 @@
/**
* @fileoverview Configuration interface definitions for the tm-core package
* This file defines the contract for configuration management
*/
import type { TaskComplexity, TaskPriority } from '../types/index';
/**
* Model configuration for different AI roles
*/
export interface ModelConfig {
/** Primary model for task generation and updates */
main: string;
/** Research model for enhanced task analysis (optional) */
research?: string;
/** Fallback model when primary fails */
fallback: string;
}
/**
* AI provider configuration
*/
export interface ProviderConfig {
/** Provider name (e.g., 'anthropic', 'openai', 'perplexity') */
name: string;
/** API key for the provider */
apiKey?: string;
/** Base URL override */
baseUrl?: string;
/** Custom configuration options */
options?: Record<string, unknown>;
/** Whether this provider is enabled */
enabled?: boolean;
}
/**
* Task generation and management settings
*/
export interface TaskSettings {
/** Default priority for new tasks */
defaultPriority: TaskPriority;
/** Default complexity for analysis */
defaultComplexity: TaskComplexity;
/** Maximum number of subtasks per task */
maxSubtasks: number;
/** Maximum number of concurrent tasks */
maxConcurrentTasks: number;
/** Enable automatic task ID generation */
autoGenerateIds: boolean;
/** Task ID prefix (e.g., 'TASK-', 'TM-') */
taskIdPrefix?: string;
/** Enable task dependency validation */
validateDependencies: boolean;
/** Enable automatic timestamps */
enableTimestamps: boolean;
/** Enable effort tracking */
enableEffortTracking: boolean;
}
/**
* Tag and context management settings
*/
export interface TagSettings {
/** Enable tag-based task organization */
enableTags: boolean;
/** Default tag for new tasks */
defaultTag: string;
/** Maximum number of tags per task */
maxTagsPerTask: number;
/** Enable automatic tag creation from Git branches */
autoCreateFromBranch: boolean;
/** Tag naming convention (kebab-case, camelCase, snake_case) */
tagNamingConvention: 'kebab-case' | 'camelCase' | 'snake_case';
}
/**
* Storage and persistence settings
*/
export interface StorageSettings {
/** Storage backend type */
type: 'file' | 'api';
/** Base path for file storage */
basePath?: string;
/** API endpoint for API storage (Hamster integration) */
apiEndpoint?: string;
/** Access token for API authentication */
apiAccessToken?: string;
/** Enable automatic backups */
enableBackup: boolean;
/** Maximum number of backups to retain */
maxBackups: number;
/** Enable compression for storage */
enableCompression: boolean;
/** File encoding for text files */
encoding: BufferEncoding;
/** Enable atomic file operations */
atomicOperations: boolean;
}
/**
* Retry and resilience settings
*/
export interface RetrySettings {
/** Number of retry attempts for failed operations */
retryAttempts: number;
/** Base delay between retries in milliseconds */
retryDelay: number;
/** Maximum delay between retries in milliseconds */
maxRetryDelay: number;
/** Exponential backoff multiplier */
backoffMultiplier: number;
/** Request timeout in milliseconds */
requestTimeout: number;
/** Enable retry for network errors */
retryOnNetworkError: boolean;
/** Enable retry for rate limit errors */
retryOnRateLimit: boolean;
}
/**
* Logging and debugging settings
*/
export interface LoggingSettings {
/** Enable logging */
enabled: boolean;
/** Log level (error, warn, info, debug) */
level: 'error' | 'warn' | 'info' | 'debug';
/** Log file path (optional) */
filePath?: string;
/** Enable request/response logging */
logRequests: boolean;
/** Enable performance metrics logging */
logPerformance: boolean;
/** Enable error stack traces */
logStackTraces: boolean;
/** Maximum log file size in MB */
maxFileSize: number;
/** Maximum number of log files to retain */
maxFiles: number;
}
/**
* Security and validation settings
*/
export interface SecuritySettings {
/** Enable API key validation */
validateApiKeys: boolean;
/** Enable request rate limiting */
enableRateLimit: boolean;
/** Maximum requests per minute */
maxRequestsPerMinute: number;
/** Enable input sanitization */
sanitizeInputs: boolean;
/** Maximum prompt length in characters */
maxPromptLength: number;
/** Allowed file extensions for imports */
allowedFileExtensions: string[];
/** Enable CORS protection */
enableCors: boolean;
}
/**
* Main configuration interface for Task Master core
*/
export interface IConfiguration {
/** Project root path */
projectPath: string;
/** Current AI provider name */
aiProvider: string;
/** API keys for different providers */
apiKeys: Record<string, string>;
/** Model configuration for different roles */
models: ModelConfig;
/** Provider configurations */
providers: Record<string, ProviderConfig>;
/** Task management settings */
tasks: TaskSettings;
/** Tag and context settings */
tags: TagSettings;
/** Storage configuration */
storage: StorageSettings;
/** Retry and resilience settings */
retry: RetrySettings;
/** Logging configuration */
logging: LoggingSettings;
/** Security settings */
security: SecuritySettings;
/** Custom user-defined settings */
custom?: Record<string, unknown>;
/** Configuration version for migration purposes */
version: string;
/** Last updated timestamp */
lastUpdated: string;
}
/**
* Partial configuration for updates (all fields optional)
*/
export type PartialConfiguration = Partial<IConfiguration>;
/**
* Configuration validation result
*/
export interface ConfigValidationResult {
/** Whether the configuration is valid */
isValid: boolean;
/** Array of error messages */
errors: string[];
/** Array of warning messages */
warnings: string[];
/** Suggested fixes */
suggestions?: string[];
}
/**
* Environment variable configuration mapping
*/
export interface EnvironmentConfig {
/** Mapping of environment variables to config paths */
variables: Record<string, string>;
/** Prefix for environment variables */
prefix: string;
/** Whether to override existing config with env vars */
override: boolean;
}
/**
* Configuration schema definition for validation
*/
export interface ConfigSchema {
/** Schema for the main configuration */
properties: Record<string, ConfigProperty>;
/** Required properties */
required: string[];
/** Additional properties allowed */
additionalProperties: boolean;
}
/**
* Configuration property schema
*/
export interface ConfigProperty {
/** Property type */
type: 'string' | 'number' | 'boolean' | 'object' | 'array';
/** Property description */
description?: string;
/** Default value */
default?: unknown;
/** Allowed values for enums */
enum?: unknown[];
/** Minimum value (for numbers) */
minimum?: number;
/** Maximum value (for numbers) */
maximum?: number;
/** Pattern for string validation */
pattern?: string;
/** Nested properties (for objects) */
properties?: Record<string, ConfigProperty>;
/** Array item type (for arrays) */
items?: ConfigProperty;
/** Whether the property is required */
required?: boolean;
}
/**
* Default configuration factory
*/
export interface IConfigurationFactory {
/**
* Create a default configuration
* @param projectPath - Project root path
* @returns Default configuration object
*/
createDefault(projectPath: string): IConfiguration;
/**
* Merge configurations with precedence
* @param base - Base configuration
* @param override - Override configuration
* @returns Merged configuration
*/
merge(base: IConfiguration, override: PartialConfiguration): IConfiguration;
/**
* Validate configuration against schema
* @param config - Configuration to validate
* @returns Validation result
*/
validate(config: IConfiguration): ConfigValidationResult;
/**
* Load configuration from environment variables
* @param envConfig - Environment variable mapping
* @returns Partial configuration from environment
*/
loadFromEnvironment(envConfig: EnvironmentConfig): PartialConfiguration;
/**
* Get configuration schema
* @returns Configuration schema definition
*/
getSchema(): ConfigSchema;
}
/**
* Configuration manager interface
*/
export interface IConfigurationManager {
/**
* Load configuration from file or create default
* @param configPath - Path to configuration file
* @returns Promise that resolves to configuration
*/
load(configPath?: string): Promise<IConfiguration>;
/**
* Save configuration to file
* @param config - Configuration to save
* @param configPath - Optional path override
* @returns Promise that resolves when save is complete
*/
save(config: IConfiguration, configPath?: string): Promise<void>;
/**
* Update configuration with partial changes
* @param updates - Partial configuration updates
* @returns Promise that resolves to updated configuration
*/
update(updates: PartialConfiguration): Promise<IConfiguration>;
/**
* Get current configuration
* @returns Current configuration object
*/
getConfig(): IConfiguration;
/**
* Watch for configuration changes
* @param callback - Function to call when config changes
* @returns Function to stop watching
*/
watch(callback: (config: IConfiguration) => void): () => void;
/**
* Validate current configuration
* @returns Validation result
*/
validate(): ConfigValidationResult;
/**
* Reset configuration to defaults
* @returns Promise that resolves when reset is complete
*/
reset(): Promise<void>;
}
/**
* Constants for default configuration values
*/
export const DEFAULT_CONFIG_VALUES = {
MODELS: {
MAIN: 'claude-3-5-sonnet-20241022',
FALLBACK: 'gpt-4o-mini'
},
TASKS: {
DEFAULT_PRIORITY: 'medium' as TaskPriority,
DEFAULT_COMPLEXITY: 'moderate' as TaskComplexity,
MAX_SUBTASKS: 20,
MAX_CONCURRENT: 5,
TASK_ID_PREFIX: 'TASK-'
},
TAGS: {
DEFAULT_TAG: 'master',
MAX_TAGS_PER_TASK: 10,
NAMING_CONVENTION: 'kebab-case' as const
},
STORAGE: {
TYPE: 'file' as const,
ENCODING: 'utf8' as BufferEncoding,
MAX_BACKUPS: 5
},
RETRY: {
ATTEMPTS: 3,
DELAY: 1000,
MAX_DELAY: 30000,
BACKOFF_MULTIPLIER: 2,
TIMEOUT: 30000
},
LOGGING: {
LEVEL: 'info' as const,
MAX_FILE_SIZE: 10,
MAX_FILES: 5
},
SECURITY: {
MAX_REQUESTS_PER_MINUTE: 60,
MAX_PROMPT_LENGTH: 100000,
ALLOWED_EXTENSIONS: ['.txt', '.md', '.json']
},
VERSION: '1.0.0'
} as const;

View File

@@ -0,0 +1,16 @@
/**
* @fileoverview Interface definitions index for the tm-core package
* This file exports all interface definitions from their respective modules
*/
// Storage interfaces
export type * from './storage.interface';
export * from './storage.interface';
// AI Provider interfaces
export type * from './ai-provider.interface';
export * from './ai-provider.interface';
// Configuration interfaces
export type * from './configuration.interface';
export * from './configuration.interface';

View File

@@ -0,0 +1,238 @@
/**
* @fileoverview Storage interface definitions for the tm-core package
* This file defines the contract for all storage implementations
*/
import type { Task, TaskMetadata } from '../types/index';
/**
* Interface for storage operations on tasks
* All storage implementations must implement this interface
*/
export interface IStorage {
/**
* Load all tasks from storage, optionally filtered by tag
* @param tag - Optional tag to filter tasks by
* @returns Promise that resolves to an array of tasks
*/
loadTasks(tag?: string): Promise<Task[]>;
/**
* Save tasks to storage, replacing existing tasks
* @param tasks - Array of tasks to save
* @param tag - Optional tag context for the tasks
* @returns Promise that resolves when save is complete
*/
saveTasks(tasks: Task[], tag?: string): Promise<void>;
/**
* Append new tasks to existing storage without replacing
* @param tasks - Array of tasks to append
* @param tag - Optional tag context for the tasks
* @returns Promise that resolves when append is complete
*/
appendTasks(tasks: Task[], tag?: string): Promise<void>;
/**
* Update a specific task by ID
* @param taskId - ID of the task to update
* @param updates - Partial task object with fields to update
* @param tag - Optional tag context for the task
* @returns Promise that resolves when update is complete
*/
updateTask(
taskId: string,
updates: Partial<Task>,
tag?: string
): Promise<void>;
/**
* Delete a task by ID
* @param taskId - ID of the task to delete
* @param tag - Optional tag context for the task
* @returns Promise that resolves when deletion is complete
*/
deleteTask(taskId: string, tag?: string): Promise<void>;
/**
* Check if tasks exist in storage for the given tag
* @param tag - Optional tag to check existence for
* @returns Promise that resolves to boolean indicating existence
*/
exists(tag?: string): Promise<boolean>;
/**
* Load metadata about the task collection
* @param tag - Optional tag to get metadata for
* @returns Promise that resolves to task metadata
*/
loadMetadata(tag?: string): Promise<TaskMetadata | null>;
/**
* Save metadata about the task collection
* @param metadata - Metadata object to save
* @param tag - Optional tag context for the metadata
* @returns Promise that resolves when save is complete
*/
saveMetadata(metadata: TaskMetadata, tag?: string): Promise<void>;
/**
* Get all available tags in storage
* @returns Promise that resolves to array of available tags
*/
getAllTags(): Promise<string[]>;
/**
* Delete all tasks and metadata for a specific tag
* @param tag - Tag to delete
* @returns Promise that resolves when deletion is complete
*/
deleteTag(tag: string): Promise<void>;
/**
* Rename a tag (move all tasks from old tag to new tag)
* @param oldTag - Current tag name
* @param newTag - New tag name
* @returns Promise that resolves when rename is complete
*/
renameTag(oldTag: string, newTag: string): Promise<void>;
/**
* Copy all tasks from one tag to another
* @param sourceTag - Source tag to copy from
* @param targetTag - Target tag to copy to
* @returns Promise that resolves when copy is complete
*/
copyTag(sourceTag: string, targetTag: string): Promise<void>;
/**
* Initialize storage (create necessary directories, files, etc.)
* @returns Promise that resolves when initialization is complete
*/
initialize(): Promise<void>;
/**
* Clean up and close storage connections
* @returns Promise that resolves when cleanup is complete
*/
close(): Promise<void>;
/**
* Get storage statistics (file sizes, task counts, etc.)
* @returns Promise that resolves to storage statistics
*/
getStats(): Promise<StorageStats>;
}
/**
* Storage statistics interface
*/
export interface StorageStats {
/** Total number of tasks across all tags */
totalTasks: number;
/** Total number of tags */
totalTags: number;
/** Storage size in bytes */
storageSize: number;
/** Last modified timestamp */
lastModified: string;
/** Available tags with task counts */
tagStats: Array<{
tag: string;
taskCount: number;
lastModified: string;
}>;
}
/**
* Configuration options for storage implementations
*/
export interface StorageConfig {
/** Base path for storage */
basePath: string;
/** Enable backup creation */
enableBackup?: boolean;
/** Maximum number of backups to keep */
maxBackups?: number;
/** Enable compression for storage */
enableCompression?: boolean;
/** File encoding (default: utf8) */
encoding?: BufferEncoding;
/** Enable atomic writes */
atomicWrites?: boolean;
}
/**
* Base abstract class for storage implementations
* Provides common functionality and enforces the interface
*/
export abstract class BaseStorage implements IStorage {
protected config: StorageConfig;
constructor(config: StorageConfig) {
this.config = config;
}
// Abstract methods that must be implemented by concrete classes
abstract loadTasks(tag?: string): Promise<Task[]>;
abstract saveTasks(tasks: Task[], tag?: string): Promise<void>;
abstract appendTasks(tasks: Task[], tag?: string): Promise<void>;
abstract updateTask(
taskId: string,
updates: Partial<Task>,
tag?: string
): Promise<void>;
abstract deleteTask(taskId: string, tag?: string): Promise<void>;
abstract exists(tag?: string): Promise<boolean>;
abstract loadMetadata(tag?: string): Promise<TaskMetadata | null>;
abstract saveMetadata(metadata: TaskMetadata, tag?: string): Promise<void>;
abstract getAllTags(): Promise<string[]>;
abstract deleteTag(tag: string): Promise<void>;
abstract renameTag(oldTag: string, newTag: string): Promise<void>;
abstract copyTag(sourceTag: string, targetTag: string): Promise<void>;
abstract initialize(): Promise<void>;
abstract close(): Promise<void>;
abstract getStats(): Promise<StorageStats>;
/**
* Utility method to generate backup filename
* @param originalPath - Original file path
* @returns Backup file path with timestamp
*/
protected generateBackupPath(originalPath: string): string {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const parts = originalPath.split('.');
const extension = parts.pop();
const baseName = parts.join('.');
return `${baseName}.backup.${timestamp}.${extension}`;
}
/**
* Utility method to validate task data before storage operations
* @param task - Task to validate
* @throws Error if task is invalid
*/
protected validateTask(task: Task): void {
if (!task.id) {
throw new Error('Task ID is required');
}
if (!task.title) {
throw new Error('Task title is required');
}
if (!task.description) {
throw new Error('Task description is required');
}
if (!task.status) {
throw new Error('Task status is required');
}
}
/**
* Utility method to sanitize tag names for file system safety
* @param tag - Tag name to sanitize
* @returns Sanitized tag name
*/
protected sanitizeTag(tag: string): string {
return tag.replace(/[^a-zA-Z0-9-_]/g, '-').toLowerCase();
}
}