88 lines
3.4 KiB
JavaScript
88 lines
3.4 KiB
JavaScript
#!/usr/bin/env node
|
|
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
import { join } from 'path';
|
|
import { execFileSync } from 'child_process';
|
|
import { runCoverageChecker } from './observer-coverage-checker.mjs';
|
|
|
|
function iconFor(status) {
|
|
return { ok: '✅', warn: '⚠️', fail: '🔴' }[status] || '⚪';
|
|
}
|
|
|
|
export function renderStatus(inputs) {
|
|
const { now, c1, c2, c3, c5, observer } = inputs;
|
|
return `# Brain Status (auto-generated)
|
|
|
|
Last updated: ${now}
|
|
|
|
| Контролёр | Состояние | Детали |
|
|
|---|---|---|
|
|
| C1 L1-watcher | ${iconFor(c1.status)} | ${c1.detail || '—'} |
|
|
| C2 Cross-ref consistency | ${iconFor(c2.status)} | ${c2.detail || '—'} |
|
|
| C3 Observer-of-observer | ${iconFor(c3.status)} | ${c3.detail || '—'} |
|
|
| C4 Сигнальный статус | ✅ | This file (self-reference) |
|
|
| C5 Observer-coverage | ${iconFor(c5.status)} | ${c5.detail || '—'} |
|
|
|
|
## Метрики (информационные, не алерты)
|
|
|
|
- Observer evidence: ${observer.episodeCount} episodes this month, ${observer.observerErrors} observer_error markers, ${observer.piiMatches} PII matches before filter
|
|
- Использование узлов: см. \`/brain-retro\` (раз в спринт). **Неиспользованные узлы — не проблема** (capability-readiness; см. memory \`feedback_brain_unused_tools_not_problem\` — outside-repo memory store).
|
|
|
|
## Алерт-индикаторы
|
|
|
|
✅ — норма ・ ⚠️ — внимание ・ 🔴 — действие требуется ・ ⚪ — не запускалось
|
|
`;
|
|
}
|
|
|
|
function runControllerNode(scriptArgs) {
|
|
try {
|
|
const out = execFileSync('node', scriptArgs, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
return { status: 'ok', detail: out.trim().split('\n').pop() };
|
|
} catch (err) {
|
|
return { status: 'fail', detail: (err.stderr || err.message || '').trim().split('\n').pop() };
|
|
}
|
|
}
|
|
|
|
function countEpisodes() {
|
|
const dir = 'docs/observer';
|
|
if (!existsSync(dir)) return 0;
|
|
const month = new Date().toISOString().slice(0, 7);
|
|
const file = join(dir, `episodes-${month}.jsonl`);
|
|
if (!existsSync(file)) return 0;
|
|
return readFileSync(file, 'utf-8').trim().split('\n').filter(Boolean).length;
|
|
}
|
|
|
|
function countObserverErrors() {
|
|
const dir = 'docs/observer';
|
|
if (!existsSync(dir)) return 0;
|
|
const month = new Date().toISOString().slice(0, 7);
|
|
const file = join(dir, `episodes-${month}.jsonl`);
|
|
if (!existsSync(file)) return 0;
|
|
return readFileSync(file, 'utf-8')
|
|
.trim()
|
|
.split('\n')
|
|
.filter((l) => l.includes('"observer_error":true')).length;
|
|
}
|
|
|
|
if (process.argv[1] && process.argv[1].replace(/\\/g, '/').endsWith('/status-md-generator.mjs')) {
|
|
const cov = runCoverageChecker();
|
|
const c5ok = cov.coverage.ok && cov.registration.ok;
|
|
const inputs = {
|
|
now: new Date().toISOString(),
|
|
c1: runControllerNode(['tools/l1-watcher.mjs']),
|
|
c2: runControllerNode(['tools/cross-ref-checker.mjs']),
|
|
c3: runControllerNode(['tools/observer-of-observer.mjs', 'check']),
|
|
c5: {
|
|
status: c5ok ? 'ok' : 'warn',
|
|
detail: [cov.coverage.detail, cov.registration.detail].join(' · '),
|
|
},
|
|
observer: {
|
|
episodeCount: countEpisodes(),
|
|
observerErrors: countObserverErrors(),
|
|
piiMatches: 0,
|
|
},
|
|
};
|
|
const md = renderStatus(inputs);
|
|
writeFileSync('docs/observer/STATUS.md', md);
|
|
console.log(`[status-md-generator] OK — wrote docs/observer/STATUS.md`);
|
|
}
|