create-ai-chat-context-experimental
Version:
Phase 2: TypeScript rewrite - AI Chat Context & Memory System with conversation extraction and AICF format support (powered by aicf-core v2.1.0).
257 lines • 7.92 kB
JavaScript
/**
* This file is part of create-ai-chat-context-experimental.
* Licensed under the GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later).
* See LICENSE file for details.
*/
/**
* Watcher Manager
* Phase 3.3: Watcher Integration - October 2025
*
* Manages daemon mode, process lifecycle, and background watcher operations
* Integrated with aicf-core for AICF format logging
*/
import { writeFileSync, readFileSync, existsSync, unlinkSync } from 'fs';
import chalk from 'chalk';
import { AICFWriter } from 'aicf-core';
/**
* Watcher Manager for daemon mode and process lifecycle management
* Integrates with aicf-core for AICF format logging
*/
export class WatcherManager {
pidFile;
logFile;
aicfLogFile;
verbose;
pid;
startTime = null;
processedCount = 0;
errorCount = 0;
aicfWriter;
constructor(options = {}) {
this.pidFile = options.pidFile || '.watcher.pid';
this.logFile = options.logFile || '.watcher.log';
this.aicfLogFile = options.aicfLogFile || '.aicf/.watcher-events.aicf';
this.verbose = options.verbose || false;
this.pid = process.pid;
this.aicfWriter = new AICFWriter('.aicf');
}
/**
* Initialize watcher (write PID file, setup signal handlers)
*/
initialize() {
try {
// Write PID file
writeFileSync(this.pidFile, String(this.pid), 'utf-8');
// Record start time
this.startTime = new Date();
// Setup signal handlers
this.setupSignalHandlers();
if (this.verbose) {
console.log(chalk.green(`✅ Watcher initialized (PID: ${this.pid})`));
}
return { ok: true };
}
catch (error) {
return {
ok: false,
error: error instanceof Error ? error.message : String(error),
};
}
}
/**
* Cleanup watcher (remove PID file, cleanup resources)
*/
cleanup() {
try {
// Remove PID file
if (existsSync(this.pidFile)) {
unlinkSync(this.pidFile);
}
if (this.verbose) {
console.log(chalk.green('✅ Watcher cleaned up'));
}
return { ok: true };
}
catch (error) {
return {
ok: false,
error: error instanceof Error ? error.message : String(error),
};
}
}
/**
* Get watcher status
*/
getStatus() {
const isRunning = this.isProcessRunning();
const uptime = this.startTime ? Date.now() - this.startTime.getTime() : undefined;
return {
isRunning,
pid: this.pid,
startTime: this.startTime || undefined,
uptime,
processedCount: this.processedCount,
errorCount: this.errorCount,
};
}
/**
* Check if process is running
*/
isProcessRunning() {
if (!existsSync(this.pidFile)) {
return false;
}
try {
const pidContent = readFileSync(this.pidFile, 'utf-8').trim();
const storedPid = parseInt(pidContent, 10);
return storedPid === this.pid;
}
catch {
return false;
}
}
/**
* Record successful checkpoint processing
*/
async recordSuccess() {
this.processedCount++;
await this.logEvent('success', `Processed checkpoint (total: ${this.processedCount})`);
}
/**
* Record checkpoint processing error
*/
async recordError(error) {
this.errorCount++;
await this.logEvent('error', `Processing error: ${error} (total errors: ${this.errorCount})`);
}
/**
* Log event to file (both plain text and AICF)
*/
async logEvent(level, message) {
try {
const timestamp = new Date().toISOString();
const logEntry = `[${timestamp}] [${level.toUpperCase()}] ${message}\n`;
// Append to plain text log file
if (existsSync(this.logFile)) {
const existing = readFileSync(this.logFile, 'utf-8');
writeFileSync(this.logFile, existing + logEntry, 'utf-8');
}
else {
writeFileSync(this.logFile, logEntry, 'utf-8');
}
// Also write to AICF log file using aicf-core
await this.logEventAsAICF(level, message, timestamp);
if (this.verbose) {
const colorMap = {
info: chalk.blue,
success: chalk.green,
error: chalk.red,
warning: chalk.yellow,
};
const colorFn = colorMap[level];
console.log(colorFn(`[${level.toUpperCase()}] ${message}`));
}
}
catch (error) {
console.error(chalk.red('Failed to write log:'), error);
}
}
/**
* Log event to AICF file using aicf-core
*/
async logEventAsAICF(level, message, timestamp) {
try {
// Determine event type from message
let eventType = 'watcher_event';
if (message.includes('checkpoint'))
eventType = 'checkpoint_processed';
if (message.includes('error'))
eventType = 'error_occurred';
if (message.includes('Received'))
eventType = 'signal_received';
// Use aicf-core's AICFWriter to append event
const aicfEntry = `|timestamp=${timestamp}|level=${level}|event=${eventType}|message=${message}`;
await this.aicfWriter.appendLine('.watcher-events.aicf', aicfEntry);
}
catch (error) {
// Silently fail for AICF logging to not disrupt main logging
if (this.verbose) {
console.error(chalk.yellow('Warning: Failed to write AICF log:'), error);
}
}
}
/**
* Setup signal handlers for graceful shutdown
*/
setupSignalHandlers() {
const signals = ['SIGINT', 'SIGTERM', 'SIGHUP'];
for (const signal of signals) {
process.on(signal, async () => {
await this.logEvent('info', `Received ${signal}, shutting down gracefully...`);
this.cleanup();
process.exit(0);
});
}
}
/**
* Get log file content
*/
getLogContent() {
try {
if (!existsSync(this.logFile)) {
return { ok: true, content: '' };
}
const content = readFileSync(this.logFile, 'utf-8');
return { ok: true, content };
}
catch (error) {
return {
ok: false,
error: error instanceof Error ? error.message : String(error),
};
}
}
/**
* Clear log file
*/
async clearLog() {
try {
if (existsSync(this.logFile)) {
unlinkSync(this.logFile);
}
await this.logEvent('info', 'Log file cleared');
return { ok: true };
}
catch (error) {
return {
ok: false,
error: error instanceof Error ? error.message : String(error),
};
}
}
/**
* Get PID file path
*/
getPidFilePath() {
return this.pidFile;
}
/**
* Get log file path
*/
getLogFilePath() {
return this.logFile;
}
/**
* Get AICF log file path
*/
getAICFLogFilePath() {
return this.aicfLogFile;
}
/**
* Set verbose mode
*/
setVerbose(verbose) {
this.verbose = verbose;
}
}
//# sourceMappingURL=WatcherManager.js.map