f6a1b3d09f
Auto-generated блок с разбивкой % дисциплины по типам задач, router-step distribution + suspicious-флаг, boundaries-applied rate. Backward-compat: блок опускается, если discipline не передан. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
153 lines
6.2 KiB
JavaScript
153 lines
6.2 KiB
JavaScript
import { describe, it, expect } from 'vitest';
|
|
import { renderStatus } from './status-md-generator.mjs';
|
|
|
|
const baseInputs = (overrides = {}) => ({
|
|
now: '2026-05-19T10:00:00+03:00',
|
|
c1: { status: 'ok', detail: 'no drift' },
|
|
c2: { status: 'ok', detail: '0 version drift' },
|
|
c3: { status: 'ok', detail: 'last read today' },
|
|
c5: { status: 'ok', detail: 'coverage OK · registration OK' },
|
|
c6: { status: 'ok', detail: '14 chains in sync' },
|
|
observer: { episodeCount: 12, observerErrors: 0, piiMatches: 0 },
|
|
missed: { totalMissed: 0, byNode: {}, byClassification: {} },
|
|
...overrides,
|
|
});
|
|
|
|
describe('renderStatus', () => {
|
|
it('renders all 5 controllers + metrics', () => {
|
|
const md = renderStatus(baseInputs());
|
|
expect(md).toContain('# Brain Status');
|
|
expect(md).toContain('| C1 L1-watcher | ✅');
|
|
expect(md).toContain('| C2 Cross-ref consistency | ✅');
|
|
expect(md).toContain('| C3 Observer-of-observer | ✅');
|
|
expect(md).toContain('| C4 Сигнальный статус | ✅');
|
|
expect(md).toContain('| C5 Observer-coverage | ✅');
|
|
expect(md).toContain('12 episodes');
|
|
});
|
|
|
|
it('includes a C6 chain-map row', () => {
|
|
const md = renderStatus(baseInputs());
|
|
expect(md).toContain('| C6 Chain map sync | ✅');
|
|
});
|
|
|
|
it('shows a warn status for the coverage controller', () => {
|
|
const md = renderStatus(baseInputs({ c5: { status: 'warn', detail: '3 commits, 0 episodes' } }));
|
|
expect(md).toContain('| C5 Observer-coverage | ⚠️');
|
|
});
|
|
|
|
it('shows the observer_error count in the metrics block', () => {
|
|
const md = renderStatus(baseInputs({ observer: { episodeCount: 4, observerErrors: 2, piiMatches: 0 } }));
|
|
expect(md).toContain('2 observer_error markers');
|
|
});
|
|
|
|
it('shows a red status for failing controllers', () => {
|
|
const md = renderStatus(baseInputs({ c1: { status: 'fail', detail: '2 plugins not formalized' } }));
|
|
expect(md).toContain('| C1 L1-watcher | 🔴');
|
|
});
|
|
|
|
it('mentions the conditional capability-readiness behavioral rule (§16.4 v1.36)', () => {
|
|
const md = renderStatus(baseInputs());
|
|
expect(md).toContain('Неиспользованные узлы — не алерт');
|
|
expect(md).toContain('если профильной задачи не было');
|
|
expect(md).toContain('feedback_brain_unused_tools_not_problem');
|
|
});
|
|
|
|
it('shows piiMatches > 0 when counter file has data (Task 3)', () => {
|
|
const md = renderStatus(baseInputs({ observer: { episodeCount: 24, observerErrors: 0, piiMatches: 7 } }));
|
|
expect(md).toMatch(/7 PII matches before filter/);
|
|
});
|
|
});
|
|
|
|
describe('renderStatus — last /brain-retro (Task 10)', () => {
|
|
it('shows last /brain-retro days-ago when counter has data', () => {
|
|
const md = renderStatus(baseInputs({ lastRetroDaysAgo: 3 }));
|
|
expect(md).toMatch(/Last \/brain-retro:\s*3 day\(s\) ago/);
|
|
});
|
|
it('shows "never" when lastRetroDaysAgo is null', () => {
|
|
const md = renderStatus(baseInputs({ lastRetroDaysAgo: null }));
|
|
expect(md).toMatch(/Last \/brain-retro:\s*never/);
|
|
});
|
|
it('shows "never" when lastRetroDaysAgo is undefined', () => {
|
|
const md = renderStatus(baseInputs());
|
|
expect(md).toMatch(/Last \/brain-retro:\s*never/);
|
|
});
|
|
});
|
|
|
|
describe('renderStatus — v1 episodes count surface (Task 18)', () => {
|
|
it('shows v1 count when present', () => {
|
|
const md = renderStatus(baseInputs({ observer: { episodeCount: 22, observerErrors: 0, piiMatches: 0, v1Episodes: 5 } }));
|
|
expect(md).toMatch(/Legacy v1 episodes \(not in factor analysis\):\s*5/);
|
|
});
|
|
it('shows 0 when v1Episodes undefined', () => {
|
|
const md = renderStatus(baseInputs());
|
|
expect(md).toMatch(/Legacy v1 episodes \(not in factor analysis\):\s*0/);
|
|
});
|
|
});
|
|
|
|
describe('renderStatus — missed activations (Task 7, Pravila §16.4 v1.36)', () => {
|
|
it('renders missed_activations: 0 when there are no misses', () => {
|
|
const md = renderStatus(baseInputs());
|
|
expect(md).toContain('missed_activations: 0');
|
|
});
|
|
|
|
it('renders missed_activations: N when misses occur', () => {
|
|
const md = renderStatus(baseInputs({
|
|
missed: { totalMissed: 3, byNode: { '#11': 2, '#12': 1 }, byClassification: { refactor: 3 } },
|
|
}));
|
|
expect(md).toContain('missed_activations: 3');
|
|
});
|
|
|
|
it('keeps C5 ✅ when controller is ok and no misses', () => {
|
|
const md = renderStatus(baseInputs());
|
|
expect(md).toContain('| C5 Observer-coverage | ✅');
|
|
});
|
|
|
|
it('honors the c5 status override (warn) regardless of missed count', () => {
|
|
const md = renderStatus(baseInputs({
|
|
c5: { status: 'warn', detail: '16 missed activation(s)' },
|
|
}));
|
|
expect(md).toContain('| C5 Observer-coverage | ⚠️');
|
|
});
|
|
});
|
|
|
|
describe('renderStatus — discipline block (stage 2)', () => {
|
|
const baseInputs = {
|
|
now: '2026-05-24T10:00:00Z',
|
|
c1: { status: 'ok', detail: 'OK' },
|
|
c2: { status: 'ok', detail: 'OK' },
|
|
c3: { status: 'ok', detail: 'OK' },
|
|
c5: { status: 'ok', detail: 'OK' },
|
|
c6: { status: 'ok', detail: 'OK' },
|
|
observer: { episodeCount: 10, observerErrors: 0, piiMatches: 0, v1Episodes: 0 },
|
|
missed: { totalMissed: 0, byNode: {}, byClassification: {} },
|
|
lastRetroDaysAgo: 0,
|
|
};
|
|
|
|
it('renders discipline table when discipline data is provided', () => {
|
|
const md = renderStatus({
|
|
...baseInputs,
|
|
discipline: {
|
|
byClassification: {
|
|
feature: { episodes: 5, withTriggerMatch: 0, viaSkill: 0, pctTriggerMatch: 0, pctViaSkill: 0 },
|
|
bugfix: { episodes: 6, withTriggerMatch: 2, viaSkill: 2, pctTriggerMatch: 0.333, pctViaSkill: 0.333 },
|
|
},
|
|
routerStep: { distribution: { '1': 10, '3': 1 }, total: 11, suspicious: true },
|
|
boundariesRate: { total: 11, withBoundaries: 3, rate: 0.273, byPathType: {} },
|
|
},
|
|
});
|
|
expect(md).toMatch(/## Метрики дисциплины/);
|
|
expect(md).toMatch(/feature/);
|
|
expect(md).toMatch(/bugfix/);
|
|
expect(md).toMatch(/33\.3%/);
|
|
expect(md).toMatch(/router step distribution/i);
|
|
expect(md).toMatch(/⚠️.*suspicious/i);
|
|
expect(md).toMatch(/boundaries applied/i);
|
|
expect(md).toMatch(/27\.3%/);
|
|
});
|
|
|
|
it('omits the discipline block when discipline is absent (backward compat)', () => {
|
|
const md = renderStatus(baseInputs);
|
|
expect(md).not.toMatch(/## Метрики дисциплины/);
|
|
});
|
|
});
|