feat(observer): coverage + registration-integrity controller (C5)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-05-19 10:38:25 +03:00
parent 35231d8b96
commit d080198220
3 changed files with 136 additions and 1 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
# Brain Status (auto-generated)
Last updated: 2026-05-19T07:31:46.792Z
Last updated: 2026-05-19T07:35:07.872Z
| Контролёр | Состояние | Детали |
|---|---|---|
+90
View File
@@ -0,0 +1,90 @@
#!/usr/bin/env node
/**
* C5 observer-coverage-checker (brain governance, observer factor-analysis
* spec §5.2). Warn-only — always exits 0. Two checks:
* 1. Coverage — recent git commits but 0 observer episodes this month.
* 2. Registration integrity — observer Stop-hook present in
* .claude/settings.json and .git/hooks/post-commit installed.
* Findings are surfaced in docs/observer/STATUS.md (C4 generator); this
* controller never blocks a commit.
*
* Security Guidance #40: git is invoked via execFileSync (argument array,
* no shell) — no exec/execSync.
*/
import { readFileSync, existsSync } from 'fs';
import { join } from 'path';
import { execFileSync } from 'child_process';
const RECENT_WINDOW = '14 days ago';
/** @returns {{ok: boolean, detail: string}} */
export function checkCoverage(episodeCount, recentCommitCount) {
if (recentCommitCount > 0 && episodeCount === 0) {
return {
ok: false,
detail: `${recentCommitCount} commit(s) in the last 2 weeks but 0 observer episodes this month`,
};
}
return { ok: true, detail: `${episodeCount} episode(s), ${recentCommitCount} recent commit(s)` };
}
/** @returns {{ok: boolean, detail: string}} */
export function checkRegistration(settingsJson, postCommitExists) {
const problems = [];
const stopHooks = (((settingsJson || {}).hooks || {}).Stop) || [];
const hasObserverStop = stopHooks.some((entry) =>
((entry && entry.hooks) || []).some((h) => String((h && h.command) || '').includes('observer-stop-hook'))
);
if (!hasObserverStop) {
problems.push('observer-stop-hook NOT registered in .claude/settings.json Stop hook');
}
if (!postCommitExists) {
problems.push('.git/hooks/post-commit not installed (run: npx lefthook install --force)');
}
return {
ok: problems.length === 0,
detail: problems.length ? problems.join('; ') : 'Stop-hook + post-commit OK',
};
}
function countEpisodes(root) {
const month = new Date().toISOString().slice(0, 7);
const file = join(root, 'docs', 'observer', `episodes-${month}.jsonl`);
if (!existsSync(file)) return 0;
return readFileSync(file, 'utf-8').trim().split('\n').filter(Boolean).length;
}
function countRecentCommits(root) {
try {
const out = execFileSync('git', ['log', `--since=${RECENT_WINDOW}`, '--oneline'], {
cwd: root,
encoding: 'utf-8',
stdio: ['ignore', 'pipe', 'ignore'],
});
return out.trim() ? out.trim().split('\n').length : 0;
} catch {
return 0;
}
}
export function runCoverageChecker(root = process.cwd()) {
const coverage = checkCoverage(countEpisodes(root), countRecentCommits(root));
let settings = {};
try {
settings = JSON.parse(readFileSync(join(root, '.claude', 'settings.json'), 'utf-8'));
} catch {
settings = {};
}
const registration = checkRegistration(settings, existsSync(join(root, '.git', 'hooks', 'post-commit')));
return { coverage, registration };
}
if (process.argv[1] && process.argv[1].replace(/\\/g, '/').endsWith('/observer-coverage-checker.mjs')) {
const { coverage, registration } = runCoverageChecker();
if (!coverage.ok) console.warn(`[observer-coverage-checker] WARN — coverage: ${coverage.detail}`);
if (!registration.ok) console.warn(`[observer-coverage-checker] WARN — registration: ${registration.detail}`);
if (coverage.ok && registration.ok) {
console.log(`[observer-coverage-checker] OK — ${coverage.detail}; ${registration.detail}`);
}
process.exit(0); // warn-only — never blocks a commit
}
+45
View File
@@ -0,0 +1,45 @@
import { describe, it, expect } from 'vitest';
import { checkCoverage, checkRegistration } from './observer-coverage-checker.mjs';
describe('checkCoverage', () => {
it('flags recent commits but zero episodes', () => {
const r = checkCoverage(0, 7);
expect(r.ok).toBe(false);
expect(r.detail).toContain('0 observer episodes');
});
it('is ok when episodes exist', () => {
expect(checkCoverage(5, 7).ok).toBe(true);
});
it('is ok when there is no recent git activity', () => {
expect(checkCoverage(0, 0).ok).toBe(true);
});
});
describe('checkRegistration', () => {
const goodSettings = {
hooks: { Stop: [{ hooks: [{ type: 'command', command: 'node tools/observer-stop-hook.mjs' }] }] },
};
it('is ok when the Stop-hook is registered and post-commit exists', () => {
const r = checkRegistration(goodSettings, true);
expect(r.ok).toBe(true);
});
it('flags a missing Stop-hook registration', () => {
const r = checkRegistration({ hooks: { Stop: [] } }, true);
expect(r.ok).toBe(false);
expect(r.detail).toContain('observer-stop-hook NOT registered');
});
it('flags a missing post-commit hook', () => {
const r = checkRegistration(goodSettings, false);
expect(r.ok).toBe(false);
expect(r.detail).toContain('post-commit');
});
it('handles an empty settings object', () => {
expect(checkRegistration({}, false).ok).toBe(false);
});
});