/ src / cron / sonnet-overseer.js
sonnet-overseer.js
  1  /**
  2   * Sonnet Overseer — DEPRECATED as standalone LLM cron job.
  3   *
  4   * LLM analysis is now handled by the Claude orchestrator:
  5   *   scripts/claude-orchestrator.sh --type oversee
  6   *
  7   * This file is retained only because src/cron.js references runSonnetOverseer().
  8   * The cron registration in cron.js should be removed — this stub logs a warning
  9   * and exits cleanly so nothing breaks in the meantime.
 10   *
 11   * Data collection functions are kept here as they may be used by other modules.
 12   */
 13  
 14  import { getAll } from '../utils/db.js';
 15  import { execSync } from 'child_process';
 16  import { appendFileSync, readFileSync, existsSync } from 'fs';
 17  import { join, dirname } from 'path';
 18  import { fileURLToPath } from 'url';
 19  import '../utils/load-env.js';
 20  
 21  const __filename = fileURLToPath(import.meta.url);
 22  const __dirname = dirname(__filename);
 23  const PROJECT_ROOT = join(__dirname, '../..');
 24  
 25  const LOG_FILE = '/tmp/sonnet-overseer.log';
 26  const STATUS_FILE = join(PROJECT_ROOT, 'logs/pipeline-status.txt');
 27  
 28  function log(msg) {
 29    const ts = new Date().toISOString();
 30    const line = `[${ts}] [SonnetOverseer] ${msg}`;
 31    console.log(line);
 32    try {
 33      appendFileSync(LOG_FILE, `${line}\n`);
 34    } catch {
 35      /* ignore */
 36    }
 37  }
 38  
 39  function statusLog(section) {
 40    try {
 41      appendFileSync(STATUS_FILE, `${section}\n`);
 42    } catch {
 43      /* ignore */
 44    }
 45  }
 46  
 47  // ─── Data collection (kept for potential direct use) ─────────────────────────
 48  
 49  export function collectServiceStatus() {
 50    const services = ['333method-pipeline', 'mmo-cron.timer', '333method-dashboard'];
 51    const results = {};
 52    for (const svc of services) {
 53      try {
 54        const status = execSync(`systemctl --user is-active ${svc}`, {
 55          encoding: 'utf8',
 56          timeout: 8000,
 57        }).trim();
 58        results[svc] = status;
 59      } catch (err) {
 60        results[svc] = (err.stdout || '').trim() || 'inactive';
 61      }
 62    }
 63    return results;
 64  }
 65  
 66  export function collectRecentErrors() {
 67    const today = new Date().toISOString().slice(0, 10);
 68    const logFile = join(PROJECT_ROOT, `logs/pipeline-${today}.log`);
 69    if (!existsSync(logFile)) return [];
 70  
 71    try {
 72      const cutoff = new Date(Date.now() - 30 * 60 * 1000);
 73      const raw = readFileSync(logFile, 'utf8');
 74      const lines = raw.split('\n');
 75      const errors = [];
 76      const seen = new Map();
 77  
 78      for (const line of lines.slice(-400)) {
 79        if (!line.includes('[ERROR]') && !line.includes('[WARN]')) continue;
 80  
 81        const tsMatch = line.match(/^(\[[\d\-T:.+Z]+\])/);
 82        if (tsMatch) {
 83          try {
 84            const lineTs = new Date(tsMatch[1].slice(1, -1));
 85            if (lineTs < cutoff) continue;
 86          } catch {
 87            /* keep */
 88          }
 89        }
 90  
 91        const trimmed = line.slice(0, 200);
 92        const key = trimmed.slice(0, 80);
 93        const count = (seen.get(key) || 0) + 1;
 94        seen.set(key, count);
 95        if (count === 1) errors.push(trimmed);
 96        else if (count <= 3) errors[errors.length - 1] = `${trimmed} (×${count})`;
 97      }
 98  
 99      return errors.slice(-30);
100    } catch {
101      return [];
102    }
103  }
104  
105  // ─── Main (stub — LLM work moved to orchestrator) ────────────────────────────
106  
107  export async function runSonnetOverseer() {
108    log(
109      'NOTICE: runSonnetOverseer() is deprecated — LLM analysis now runs via claude-orchestrator.sh --type oversee. Remove this cron job from cron.js.'
110    );
111    statusLog(
112      `[${new Date().toISOString()}] [SonnetOverseer] NOTICE: Cron stub — LLM analysis delegated to orchestrator`
113    );
114  
115    // Log a basic data snapshot so the status file still has useful info
116    try {
117      const services = collectServiceStatus();
118      const errors = collectRecentErrors();
119      const dist = await getAll(
120        `SELECT status, COUNT(*) as n FROM sites GROUP BY status ORDER BY n DESC LIMIT 5`
121      );
122      statusLog(
123        `Services: ${JSON.stringify(services)} | Errors(30m): ${errors.length} | Sites: ${dist.map(r => `${r.status}=${r.n}`).join(', ')}`
124      );
125    } catch {
126      /* non-fatal — deprecated stub */
127    }
128  
129    return { summary: 'Delegated to orchestrator', severity: 'ok', actions_taken: 0 };
130  }
131  
132  // Allow direct invocation (prints deprecation notice)
133  if (process.argv[1] === fileURLToPath(import.meta.url)) {
134    runSonnetOverseer()
135      .then(result => {
136        console.log('\nResult:', JSON.stringify(result, null, 2));
137        process.exit(0);
138      })
139      .catch(err => {
140        console.error('Fatal:', err);
141        process.exit(1);
142      });
143  }