/** * State Manager for OpenCode Diff Plugin * * Manages pending file changes in a queue structure with line-level state tracking. * Provides storage and retrieval for intercepted file operations. */ import { ParsedDiff } from './diff-engine.js'; /** * Line state types for granular change tracking */ export type LineState = 'pending' | 'accepted' | 'rejected'; /** * Aggregate state for a hunk or file */ export type AggregateState = 'pending' | 'accepted' | 'rejected' | 'mixed'; /** * Line identifier structure for tracking state per line */ export interface LineId { hunkIndex: number; lineIndex: number; } /** * Represents a single pending file change with line-level state tracking */ export declare class PendingChange { /** Unique identifier for this change */ id: string; /** The tool that triggered this change ('write' or 'edit') */ tool: string; /** Absolute path to the file being modified */ filePath: string; /** Original content of the file (empty string for new files) */ oldContent: string; /** Proposed new content for the file */ newContent: string; /** Session ID that initiated this change */ sessionID: string; /** Tool call ID that triggered this change */ callID: string; /** Timestamp when the change was intercepted */ timestamp: number; /** Parsed diff information */ parsedDiff?: ParsedDiff; /** Line-level state tracking: key is "hunkIndex:lineIndex" */ private lineStates; constructor(data: { id: string; tool: string; filePath: string; oldContent: string; newContent: string; sessionID: string; callID: string; timestamp: number; parsedDiff?: ParsedDiff; }); /** * Initialize line states to 'pending' for all changed lines */ private initializeLineStates; /** * Create a unique key for a line */ private makeLineKey; /** * Parse a line key back to indices */ private parseLineKey; /** * Set the state of a specific line * @param lineId - The line identifier (hunkIndex:lineIndex string or LineId object) * @param state - The new state for the line * @returns True if the line was found and state was set, false otherwise */ setLineState(lineId: string | LineId, state: LineState): boolean; /** * Get the state of a specific line * @param lineId - The line identifier (hunkIndex:lineIndex string or LineId object) * @returns The line state, or undefined if not found */ getLineState(lineId: string | LineId): LineState | undefined; /** * Get all line states * @returns Map of line keys to their states */ getAllLineStates(): Map; /** * Get the number of accepted lines * @returns Count of lines with 'accepted' state */ getAcceptedCount(): number; /** * Get the number of rejected lines * @returns Count of lines with 'rejected' state */ getRejectedCount(): number; /** * Get the number of pending lines * @returns Count of lines with 'pending' state */ getPendingCount(): number; /** * Count lines by state */ private countLinesByState; /** * Get the total number of tracked lines * @returns Total count of tracked lines */ getTotalLineCount(): number; /** * Get the aggregate state for a specific hunk * @param hunkIndex - The index of the hunk * @returns The aggregate state ('pending', 'accepted', 'rejected', or 'mixed') */ getHunkState(hunkIndex: number): AggregateState; /** * Get the aggregate state for the entire file * @returns The aggregate state ('pending', 'accepted', 'rejected', or 'mixed') */ getFileState(): AggregateState; /** * Aggregate an array of line states into a single state */ private aggregateStates; /** * Reconstruct the final content based on accepted lines * Applies accepted changes to the original content * @returns The reconstructed content string */ reconstructContent(): string; /** * Accept all lines in the change */ acceptAll(): void; /** * Reject all lines in the change */ rejectAll(): void; /** * Reset all lines to pending state */ resetAll(): void; /** * Accept all lines in a specific hunk * @param hunkIndex - The index of the hunk */ acceptHunk(hunkIndex: number): void; /** * Reject all lines in a specific hunk * @param hunkIndex - The index of the hunk */ rejectHunk(hunkIndex: number): void; /** * Check if all lines have been decided (no pending) * @returns True if there are no pending lines */ isFullyDecided(): boolean; /** * Check if the change can be applied (has accepted lines or is fully decided) * @returns True if the change can be applied */ canApply(): boolean; /** * Get statistics about the change * @returns Object with counts of accepted, rejected, pending, and total lines */ getStats(): { accepted: number; rejected: number; pending: number; total: number; percentageAccepted: number; percentageRejected: number; percentagePending: number; }; /** * Create a plain object representation for serialization * @returns Plain object with all data */ toJSON(): object; /** * Create a PendingChange instance from a plain object * @param data - Plain object with PendingChange data * @returns New PendingChange instance */ static fromJSON(data: any): PendingChange; } /** * Options for ChangeQueue constructor */ export interface ChangeQueueOptions { /** Enable file-based persistence for the queue */ enablePersistence?: boolean; /** Path to the state file (required if enablePersistence is true) */ statePath?: string; /** Session ID for state file tracking (will use default if not provided) */ sessionID?: string; } /** * Simple in-memory change queue using Map for O(1) lookups * Works with PendingChange class instances * * Optional file-based persistence can be enabled via constructor options. * When enabled, the queue will: * - Load persisted state on initialization * - Write state to file on mutations (debounced) * - Watch for external changes and reflect them in the queue */ export declare class ChangeQueue { private changes; private fileIndex; private stateSync; private persistenceEnabled; /** Promise that resolves when initialization is complete (for tests) */ ready: Promise; /** * Create a new ChangeQueue instance * @param options Optional configuration for persistence */ constructor(options?: ChangeQueueOptions); /** * Load persisted state from file * Called automatically on initialization when persistence is enabled */ loadState(): Promise; /** * Set up file watcher for external changes */ private setupWatcher; /** * Persist current state to file * Called automatically on mutations when persistence is enabled */ private persistState; /** * Add a new pending change to the queue * @param change The pending change to add (PendingChange instance or plain object) */ add(change: PendingChange | object): void; /** * Get a pending change by its ID * @param id The change ID * @returns The pending change or undefined if not found */ get(id: string): PendingChange | undefined; /** * Get a pending change by file path * @param filePath The file path * @returns The pending change or undefined if not found */ getByFilePath(filePath: string): PendingChange | undefined; /** * Check if a change exists for a given file path * @param filePath The file path to check * @returns True if a pending change exists for this file */ hasFile(filePath: string): boolean; /** * Remove a pending change from the queue * @param id The change ID to remove * @returns True if the change was found and removed */ remove(id: string): boolean; /** * Remove a pending change by file path * @param filePath The file path to remove * @returns True if the change was found and removed */ removeByFilePath(filePath: string): boolean; /** * Get all pending changes * @returns Array of all pending changes */ getAll(): PendingChange[]; /** * Get all changes sorted by timestamp (oldest first) * @returns Array of pending changes sorted by timestamp */ getAllSorted(): PendingChange[]; /** * Get changes filtered by state * @param state The aggregate state to filter by * @returns Array of changes with the specified aggregate state */ getByState(state: AggregateState): PendingChange[]; /** * Get the count of pending changes * @returns Number of pending changes */ size(): number; /** * Clear all pending changes */ clear(): void; /** * Update an existing change in the queue * @param id The change ID to update * @param updates Partial PendingChange data to update * @returns True if the change was found and updated */ update(id: string, updates: Partial): boolean; /** * Stop watching the state file and clean up resources * Call this when the queue is no longer needed */ destroy(): void; /** * Get queue statistics * @returns Object with counts of changes by state */ getStats(): { total: number; pending: number; accepted: number; rejected: number; mixed: number; }; /** * Generate a unique change ID * @returns A unique identifier string */ static generateId(): string; } /** * Global change queue instance */ export declare const changeQueue: ChangeQueue; /** * Error thrown when a tool execution is intercepted * This signals to OpenCode that the plugin has handled the operation */ export declare class InterceptedError extends Error { readonly changeId: string; readonly filePath: string; constructor(message: string, changeId: string, filePath: string); } /** * Utility functions for state management */ export declare namespace StateUtils { /** * Check if a line state allows the change to be applied * @param state The line state * @returns True if the change should be applied */ function isApplied(state: LineState): boolean; /** * Check if a line state rejects the change * @param state The line state * @returns True if the change should be rejected */ function isRejected(state: LineState): boolean; /** * Merge two aggregate states * @param a First aggregate state * @param b Second aggregate state * @returns The merged aggregate state */ function mergeAggregateStates(a: AggregateState, b: AggregateState): AggregateState; } //# sourceMappingURL=state-manager.d.ts.map