feat(brain): add fix hints to /brain for actionable degraded state
Adds buildSplitBrainFixHints() to split-brain-status.ts — maps each degraded component to the exact just command that repairs it: - missing artifact → just setup --step skills-memory - outdated artifact → just setup --step skills-memory - skills DB down → just setup-db - runtime lookup → just setup --step skills-init /brain now shows issues followed by → fix hints when degraded, turning the command from a diagnostic into an operator runbook. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --- Build: pass | Tests: pass — Tests 2067 passed (2067)
This commit is contained in:
parent
ca3a02283e
commit
2ee217f42b
4 changed files with 96 additions and 0 deletions
|
|
@ -3,6 +3,7 @@ import { describe, expect, it } from 'vitest';
|
|||
import {
|
||||
collectSplitBrainIssues,
|
||||
deriveSplitBrainReadiness,
|
||||
buildSplitBrainFixHints,
|
||||
type SplitBrainStatus,
|
||||
} from './split-brain-status.js';
|
||||
|
||||
|
|
@ -82,3 +83,29 @@ describe('collectSplitBrainIssues', () => {
|
|||
expect(issues).toEqual(['built-in knowledge artifact is incomplete']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildSplitBrainFixHints', () => {
|
||||
it('returns no hints for a ready status', () => {
|
||||
expect(buildSplitBrainFixHints(readyStatus())).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns skills-memory hint for missing artifact', () => {
|
||||
const hints = buildSplitBrainFixHints({ ...readyStatus(), skillsArtifact: 'missing' });
|
||||
expect(hints).toContain('Import knowledge artifact: just setup --step skills-memory');
|
||||
});
|
||||
|
||||
it('returns skills-memory hint for outdated artifact', () => {
|
||||
const hints = buildSplitBrainFixHints({ ...readyStatus(), skillsArtifact: 'incomplete' });
|
||||
expect(hints).toContain('Refresh knowledge artifact: just setup --step skills-memory');
|
||||
});
|
||||
|
||||
it('returns db hint for unavailable skills db', () => {
|
||||
const hints = buildSplitBrainFixHints({ ...readyStatus(), skillsDb: 'missing' });
|
||||
expect(hints).toContain('Fix skills DB: check PostgreSQL, then run: just setup-db');
|
||||
});
|
||||
|
||||
it('returns skills-init hint for missing runtime lookup', () => {
|
||||
const hints = buildSplitBrainFixHints({ ...readyStatus(), skillsRuntimeLookup: 'missing' });
|
||||
expect(hints).toContain('Restore runtime lookup: just setup --step skills-init');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -221,3 +221,30 @@ export function collectSplitBrainIssues(status: SplitBrainStatus): string[] {
|
|||
|
||||
return issues;
|
||||
}
|
||||
|
||||
// Returns operator-facing fix hints for each degraded component.
|
||||
// Kept separate from collectSplitBrainIssues so startup report stays concise
|
||||
// while /brain can show the full remediation path.
|
||||
export function buildSplitBrainFixHints(status: SplitBrainStatus): string[] {
|
||||
const hints: string[] = [];
|
||||
|
||||
if (status.skillsDb !== 'available') {
|
||||
hints.push('Fix skills DB: check PostgreSQL, then run: just setup-db');
|
||||
}
|
||||
if (status.memoryDb !== 'available') {
|
||||
hints.push('Fix memory DB: check PostgreSQL, then run: just setup-db');
|
||||
}
|
||||
if (status.skillsArtifact !== 'ready') {
|
||||
if (status.skillsArtifact === 'missing') {
|
||||
hints.push('Import knowledge artifact: just setup --step skills-memory');
|
||||
} else {
|
||||
// outdated or incomplete
|
||||
hints.push('Refresh knowledge artifact: just setup --step skills-memory');
|
||||
}
|
||||
}
|
||||
if (status.skillsRuntimeLookup !== 'present') {
|
||||
hints.push('Restore runtime lookup: just setup --step skills-init');
|
||||
}
|
||||
|
||||
return hints;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ vi.mock('./split-brain-status.js', () => ({
|
|||
})),
|
||||
collectSplitBrainIssues: vi.fn(() => []),
|
||||
deriveSplitBrainReadiness: vi.fn(() => 'ready'),
|
||||
buildSplitBrainFixHints: vi.fn(() => []),
|
||||
}));
|
||||
|
||||
afterEach(() => {
|
||||
|
|
@ -74,4 +75,37 @@ describe('/brain command', () => {
|
|||
expect(replies[0]?.text).not.toContain('(vv0.7.0)');
|
||||
expect(replies[0]?.opts).toEqual({ parse_mode: 'HTML' });
|
||||
});
|
||||
|
||||
it('shows fix hints when degraded', async () => {
|
||||
const { collectSplitBrainIssues, deriveSplitBrainReadiness, buildSplitBrainFixHints } =
|
||||
await import('./split-brain-status.js');
|
||||
vi.mocked(deriveSplitBrainReadiness).mockReturnValue('degraded');
|
||||
vi.mocked(collectSplitBrainIssues).mockReturnValue([
|
||||
'built-in knowledge artifact is outdated (loaded: v0.6.0, expected: v0.7.0)',
|
||||
]);
|
||||
vi.mocked(buildSplitBrainFixHints).mockReturnValue([
|
||||
'Refresh knowledge artifact: just setup --step skills-memory',
|
||||
]);
|
||||
|
||||
process.env.TELEGRAM_ADMIN_IDS = '123';
|
||||
process.env.TELEGRAM_OPS_CHAT_ID = 'tg:999';
|
||||
|
||||
const replies: Array<{ text: string; opts?: unknown }> = [];
|
||||
const ctxArg = {
|
||||
from: { id: 123 },
|
||||
chat: { id: 999 },
|
||||
reply: async (text: string, opts?: unknown) => {
|
||||
replies.push({ text, opts });
|
||||
},
|
||||
};
|
||||
|
||||
const { handleBrainCommand } = await import('./telegram-commands.js');
|
||||
await handleBrainCommand(ctxArg, 'tg:999');
|
||||
|
||||
expect(replies).toHaveLength(1);
|
||||
const text = replies[0]?.text ?? '';
|
||||
expect(text).toContain('⚠ Brain: <b>degraded</b>');
|
||||
expect(text).toContain('⚠ built-in knowledge artifact is outdated');
|
||||
expect(text).toContain('→ Refresh knowledge artifact: just setup --step skills-memory');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ import {
|
|||
collectSplitBrainStatus,
|
||||
collectSplitBrainIssues,
|
||||
deriveSplitBrainReadiness,
|
||||
buildSplitBrainFixHints,
|
||||
} from './split-brain-status.js';
|
||||
import {
|
||||
buildTestReport,
|
||||
|
|
@ -783,6 +784,13 @@ export async function handleBrainCommand(
|
|||
for (const issue of issues) {
|
||||
lines.push(`⚠ ${issue}`);
|
||||
}
|
||||
const hints = buildSplitBrainFixHints(status);
|
||||
if (hints.length > 0) {
|
||||
lines.push('');
|
||||
for (const hint of hints) {
|
||||
lines.push(`→ ${hint}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await ctxArg.reply(lines.join('\n'), { parse_mode: 'HTML' });
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue