Files
brain/tools/observer-of-observer.mjs
T

52 lines
2.0 KiB
JavaScript

#!/usr/bin/env node
import { readFileSync, writeFileSync, existsSync } from 'fs';
const COUNTER_PATH = 'docs/observer/.read-counter.json';
const SELF_PRUNE_WEEKS = 54;
export function weeksSince(isoTimestamp, now = new Date()) {
const past = new Date(isoTimestamp);
const ms = now - past;
return Math.floor(ms / (1000 * 60 * 60 * 24 * 7));
}
export function isStale(counter, thresholdWeeks = SELF_PRUNE_WEEKS, now = new Date()) {
if (!counter || !counter.last_read_at) return true;
return weeksSince(counter.last_read_at, now) >= thresholdWeeks;
}
export function recordRead(counterPath = COUNTER_PATH, now = new Date()) {
let counter = { last_read_at: now.toISOString(), read_count_last_period: 0, period_start: now.toISOString() };
if (existsSync(counterPath)) {
try {
counter = JSON.parse(readFileSync(counterPath, 'utf-8'));
} catch {}
}
counter.last_read_at = now.toISOString();
counter.read_count_last_period = (counter.read_count_last_period || 0) + 1;
writeFileSync(counterPath, JSON.stringify(counter, null, 2) + '\n');
return counter;
}
if (process.argv[1] && process.argv[1].replace(/\\/g, '/').endsWith('/observer-of-observer.mjs')) {
const mode = process.argv[2] || 'check';
if (mode === 'record') {
recordRead();
console.log(`[observer-of-observer] recorded read`);
process.exit(0);
}
if (!existsSync(COUNTER_PATH)) {
console.warn(`[observer-of-observer] WARN — counter file missing: ${COUNTER_PATH}`);
process.exit(0);
}
const counter = JSON.parse(readFileSync(COUNTER_PATH, 'utf-8'));
if (isStale(counter)) {
console.warn(`[observer-of-observer] WARN — observer infrastructure not read for ≥${SELF_PRUNE_WEEKS} weeks.`);
console.warn(` last_read_at: ${counter.last_read_at}`);
console.warn(` Consider self-pruning: archive docs/observer/ and remove Stop-hook.`);
process.exit(0);
}
console.log(`[observer-of-observer] OK — last read ${weeksSince(counter.last_read_at)} week(s) ago`);
process.exit(0);
}