mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
Knip-driven cleanup. All changes verified with tsc --noEmit, lint, and
build.
Removed (zero consumers):
- VERIFICATION_PROMPT constant + its barrel re-export
- createInitialOrchestratorPersistState factory
- transcriptWatcher singleton export
- createAnsiPatternFull / createAnsiPatternSimple factories
- TimerInfo interface + unused AiCheckResult/AiPlanCheckResult imports
in respawn-controller.ts
- 35 unused Zod z.infer \`*Input\` types in schemas.ts
- Dead re-exports: SessionMode from session.ts, AuthSessionRecord from
web/ports/index.ts, EnhancedPlanTask/CheckpointReview from
ralph-tracker.ts, 7 unused entries in utils/index.ts
- 14 event/config interfaces that lived only as JSDoc hints (no TS type
position usage): Session/Respawn/RalphLoop/RalphTracker/
SessionManager/SessionAutoOps/Subagent/TaskQueue/TaskTracker/
TranscriptWatcher/Image/OrchestratorLoop Events + RespawnPreset +
SessionOutput
Narrowed to module scope (kept but no longer exported):
- buildPermissionArgs in session-cli-builder.ts
- 28 type/interface declarations used only within their own file:
Ai{Idle,Plan}Check{Config,State}, BashToolParser{Events,Config},
FileStream/CreateStream{Options,Result}, PlanSubagentEvent,
SubagentCallback, RalphLoopConfig, RalphLoop{Events,Options},
ActiveTimerInfo, DetectionStatus, ActionLogEntry, AutoOpsCallbacks,
TunnelStatus, Timer/LRUMap/StaleExpirationMap Options, AuthState,
SessionListenerDeps, SseStreamManagerDeps, and 8 more
Docs: CLAUDE.md advice for global-regex `lastIndex` now points to the
remaining `execPattern()` helper instead of the deleted factories.
Knip delta: unused files 42→0, unused exports 161→16, unused types 92→0.
The 16 remaining exports are a mobile-test helper toolkit intentionally
kept for upcoming tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
321 lines
8.4 KiB
TypeScript
321 lines
8.4 KiB
TypeScript
/**
|
|
* @fileoverview Centralized resource cleanup manager.
|
|
*
|
|
* Tracks timers, intervals, watchers, and other resources that need explicit
|
|
* cleanup. Provides a unified dispose() method that safely cleans up all
|
|
* registered resources, even on partial failures.
|
|
*
|
|
* @module utils/cleanup-manager
|
|
*/
|
|
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
import type { Disposable, CleanupRegistration, CleanupResourceType } from '../types.js';
|
|
|
|
/**
|
|
* Options for setTimeout/setInterval with automatic cleanup.
|
|
*/
|
|
interface TimerOptions {
|
|
/** Human-readable description for debugging */
|
|
description?: string;
|
|
}
|
|
|
|
/**
|
|
* Centralized manager for tracking and disposing resources.
|
|
*
|
|
* Implements the Disposable interface for hierarchical cleanup.
|
|
* All registered resources are cleaned up when dispose() is called.
|
|
*
|
|
* Features:
|
|
* - Tracks timers, intervals, watchers, and custom cleanup functions
|
|
* - isStopped guard to prevent callbacks firing after disposal
|
|
* - Safe disposal that continues even if individual cleanups fail
|
|
* - Debug logging for resource tracking
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* class MyService implements Disposable {
|
|
* private cleanup = new CleanupManager();
|
|
*
|
|
* start() {
|
|
* // Timer automatically cleared on dispose
|
|
* this.cleanup.setTimeout(() => {
|
|
* if (this.cleanup.isStopped) return; // Guard
|
|
* this.doWork();
|
|
* }, 5000, { description: 'work timer' });
|
|
* }
|
|
*
|
|
* dispose() {
|
|
* this.cleanup.dispose();
|
|
* }
|
|
*
|
|
* get isDisposed() { return this.cleanup.isDisposed; }
|
|
* }
|
|
* ```
|
|
*/
|
|
export class CleanupManager implements Disposable {
|
|
private registrations = new Map<string, CleanupRegistration>();
|
|
private _isDisposed = false;
|
|
private readonly debugMode: boolean;
|
|
|
|
/**
|
|
* Creates a new CleanupManager.
|
|
*
|
|
* @param debug - Enable debug logging for resource tracking
|
|
*/
|
|
constructor(debug = false) {
|
|
this.debugMode = debug;
|
|
}
|
|
|
|
/**
|
|
* Whether this manager has been disposed.
|
|
* Check this before executing callbacks to prevent zombie operations.
|
|
*/
|
|
get isDisposed(): boolean {
|
|
return this._isDisposed;
|
|
}
|
|
|
|
/**
|
|
* Alias for isDisposed - check before executing async callbacks.
|
|
*/
|
|
get isStopped(): boolean {
|
|
return this._isDisposed;
|
|
}
|
|
|
|
/**
|
|
* Number of currently registered resources.
|
|
*/
|
|
get resourceCount(): number {
|
|
return this.registrations.size;
|
|
}
|
|
|
|
/**
|
|
* Get counts by resource type for metrics/debugging.
|
|
*/
|
|
get resourceCounts(): Record<CleanupResourceType, number> {
|
|
const counts: Record<CleanupResourceType, number> = {
|
|
timer: 0,
|
|
interval: 0,
|
|
watcher: 0,
|
|
listener: 0,
|
|
stream: 0,
|
|
};
|
|
|
|
for (const reg of this.registrations.values()) {
|
|
counts[reg.type]++;
|
|
}
|
|
|
|
return counts;
|
|
}
|
|
|
|
/**
|
|
* Schedule a timeout that will be automatically cleared on dispose.
|
|
*
|
|
* @param callback - Function to call when timeout fires
|
|
* @param delay - Delay in milliseconds
|
|
* @param options - Optional configuration
|
|
* @returns Timer ID for manual clearing if needed
|
|
*/
|
|
setTimeout(callback: () => void, delay: number, options?: TimerOptions): string {
|
|
const id = uuidv4();
|
|
const timeoutId = setTimeout(() => {
|
|
// Remove registration when timer fires naturally
|
|
this.registrations.delete(id);
|
|
// Don't execute if already stopped
|
|
if (this._isDisposed) return;
|
|
callback();
|
|
}, delay);
|
|
|
|
this.register({
|
|
id,
|
|
type: 'timer',
|
|
description: options?.description || `setTimeout(${delay}ms)`,
|
|
cleanup: () => clearTimeout(timeoutId),
|
|
registeredAt: Date.now(),
|
|
});
|
|
|
|
return id;
|
|
}
|
|
|
|
/**
|
|
* Schedule an interval that will be automatically cleared on dispose.
|
|
*
|
|
* @param callback - Function to call on each interval
|
|
* @param delay - Interval in milliseconds
|
|
* @param options - Optional configuration
|
|
* @returns Interval ID for manual clearing if needed
|
|
*/
|
|
setInterval(callback: () => void, delay: number, options?: TimerOptions): string {
|
|
const id = uuidv4();
|
|
const intervalId = setInterval(() => {
|
|
// Don't execute if stopped
|
|
if (this._isDisposed) return;
|
|
callback();
|
|
}, delay);
|
|
|
|
this.register({
|
|
id,
|
|
type: 'interval',
|
|
description: options?.description || `setInterval(${delay}ms)`,
|
|
cleanup: () => clearInterval(intervalId),
|
|
registeredAt: Date.now(),
|
|
});
|
|
|
|
return id;
|
|
}
|
|
|
|
/**
|
|
* Register a custom cleanup function.
|
|
*
|
|
* @param type - Type of resource for categorization
|
|
* @param cleanup - Function to call on dispose
|
|
* @param description - Human-readable description
|
|
* @returns Registration ID for manual removal if needed
|
|
*/
|
|
registerCleanup(type: CleanupResourceType, cleanup: () => void, description: string): string {
|
|
const id = uuidv4();
|
|
this.register({
|
|
id,
|
|
type,
|
|
description,
|
|
cleanup,
|
|
registeredAt: Date.now(),
|
|
});
|
|
return id;
|
|
}
|
|
|
|
/**
|
|
* Register a file system watcher for cleanup.
|
|
*
|
|
* @param watcher - Object with close() method (FSWatcher, chokidar, etc.)
|
|
* @param description - Human-readable description
|
|
* @returns Registration ID
|
|
*/
|
|
registerWatcher(watcher: { close: () => void }, description: string): string {
|
|
return this.registerCleanup('watcher', () => watcher.close(), description);
|
|
}
|
|
|
|
/**
|
|
* Register an event listener for cleanup.
|
|
*
|
|
* @param emitter - Object with removeListener/off method
|
|
* @param event - Event name
|
|
* @param listener - Listener function
|
|
* @param description - Human-readable description
|
|
* @returns Registration ID
|
|
*/
|
|
registerListener<
|
|
T extends {
|
|
removeListener?: (event: string, listener: () => void) => void;
|
|
off?: (event: string, listener: () => void) => void;
|
|
},
|
|
>(emitter: T, event: string, listener: () => void, description: string): string {
|
|
return this.registerCleanup(
|
|
'listener',
|
|
() => {
|
|
if (emitter.removeListener) {
|
|
emitter.removeListener(event, listener);
|
|
} else if (emitter.off) {
|
|
emitter.off(event, listener);
|
|
}
|
|
},
|
|
description
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Register a stream for cleanup.
|
|
*
|
|
* @param stream - Object with destroy() or close() method
|
|
* @param description - Human-readable description
|
|
* @returns Registration ID
|
|
*/
|
|
registerStream(stream: { destroy?: () => void; close?: () => void }, description: string): string {
|
|
return this.registerCleanup(
|
|
'stream',
|
|
() => {
|
|
if (stream.destroy) {
|
|
stream.destroy();
|
|
} else if (stream.close) {
|
|
stream.close();
|
|
}
|
|
},
|
|
description
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Manually remove a registered resource.
|
|
*
|
|
* @param id - Registration ID returned from register methods
|
|
* @returns True if resource was found and removed
|
|
*/
|
|
unregister(id: string): boolean {
|
|
const reg = this.registrations.get(id);
|
|
if (!reg) return false;
|
|
|
|
try {
|
|
reg.cleanup();
|
|
} catch (err) {
|
|
this.debug(`Error cleaning up ${reg.type} "${reg.description}": ${err}`);
|
|
}
|
|
|
|
this.registrations.delete(id);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Dispose all registered resources.
|
|
* Safe to call multiple times (idempotent).
|
|
* Continues cleanup even if individual resources fail.
|
|
*/
|
|
dispose(): void {
|
|
if (this._isDisposed) return;
|
|
this._isDisposed = true;
|
|
|
|
const errors: Array<{ description: string; error: unknown }> = [];
|
|
|
|
for (const reg of this.registrations.values()) {
|
|
try {
|
|
reg.cleanup();
|
|
this.debug(`Cleaned up ${reg.type}: ${reg.description}`);
|
|
} catch (err) {
|
|
errors.push({ description: reg.description, error: err });
|
|
this.debug(`Error cleaning up ${reg.type} "${reg.description}": ${err}`);
|
|
}
|
|
}
|
|
|
|
this.registrations.clear();
|
|
|
|
if (errors.length > 0) {
|
|
console.error(
|
|
`[CleanupManager] ${errors.length} errors during disposal:`,
|
|
errors.map((e) => e.description).join(', ')
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all current registrations for debugging.
|
|
*/
|
|
getRegistrations(): CleanupRegistration[] {
|
|
return Array.from(this.registrations.values());
|
|
}
|
|
|
|
/**
|
|
* Internal: Register a cleanup entry.
|
|
*/
|
|
private register(reg: CleanupRegistration): void {
|
|
this.registrations.set(reg.id, reg);
|
|
this.debug(`Registered ${reg.type}: ${reg.description}`);
|
|
}
|
|
|
|
/**
|
|
* Internal: Debug logging.
|
|
*/
|
|
private debug(message: string): void {
|
|
if (this.debugMode) {
|
|
console.log(`[CleanupManager] ${message}`);
|
|
}
|
|
}
|
|
}
|