moving
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled

This commit is contained in:
Oleg Maslov
2026-09-02 10:10:29 +02:00
commit 0c3e2ead3b
3841 changed files with 970576 additions and 0 deletions

View File

@@ -0,0 +1,149 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import {
parseHygieneVerdict,
judgeSkillHygiene,
runSkillHygieneScan,
loadActiveSkills,
type SkillForHygiene,
} from '../src/skill-hygiene.js';
import {
loadSkillHygiene,
demoteSkillToDraft,
isSkillDraft,
type SkillHygieneVerdict,
} from '../src/skill-hygiene-store.js';
import type { JudgeLLMCall } from '../src/judge.js';
let home: string;
beforeEach(() => {
home = fs.mkdtempSync(path.join(os.tmpdir(), 'waggle-hygiene-'));
});
afterEach(() => {
fs.rmSync(home, { recursive: true, force: true });
});
/** A judge that returns a canned verdict per target skill name (parsed from the prompt). */
function fakeJudge(byName: Record<string, { verdict: SkillHygieneVerdict; reason: string } | string>): JudgeLLMCall {
return async (prompt: string) => {
const m = prompt.match(/TARGET SKILL:\s*(.+)/);
const name = m ? m[1].trim() : '';
const canned = byName[name];
if (canned === undefined) return JSON.stringify({ verdict: 'necessary', reason: 'default' });
if (typeof canned === 'string') return canned; // raw (for unparseable cases)
return JSON.stringify(canned);
};
}
const skill = (name: string, content = 'do a specific thing'): SkillForHygiene => ({ name, content });
describe('parseHygieneVerdict', () => {
it('parses a clean JSON verdict', () => {
expect(parseHygieneVerdict('{"verdict":"redundant","reason":"dup of X"}')).toEqual({
verdict: 'redundant', reason: 'dup of X',
});
});
it('parses a fenced JSON verdict', () => {
expect(parseHygieneVerdict('```json\n{"verdict":"generic","reason":"too vague"}\n```')?.verdict).toBe('generic');
});
it('rejects an invalid verdict value', () => {
expect(parseHygieneVerdict('{"verdict":"delete","reason":"x"}')).toBeNull();
});
it('returns null on non-JSON', () => {
expect(parseHygieneVerdict('I think this is fine, keep it.')).toBeNull();
expect(parseHygieneVerdict('')).toBeNull();
});
});
describe('judgeSkillHygiene (fail-safe)', () => {
it('keeps active (necessary, parsed=false) when the LLM throws', async () => {
const throwing: JudgeLLMCall = async () => { throw new Error('boom'); };
const j = await judgeSkillHygiene(throwing, skill('a'), []);
expect(j.verdict).toBe('necessary');
expect(j.parsed).toBe(false);
});
it('keeps active (necessary, parsed=false) when the response is unparseable', async () => {
const garbage: JudgeLLMCall = async () => 'no json here';
const j = await judgeSkillHygiene(garbage, skill('a'), []);
expect(j.verdict).toBe('necessary');
expect(j.parsed).toBe(false);
});
it('returns a parsed verdict when the LLM answers cleanly', async () => {
const j = await judgeSkillHygiene(fakeJudge({ a: { verdict: 'redundant', reason: 'dup' } }), skill('a'), []);
expect(j).toMatchObject({ verdict: 'redundant', reason: 'dup', parsed: true });
});
});
describe('runSkillHygieneScan', () => {
it('demotes a redundant skill to draft via the sidecar — and never deletes/edits', async () => {
const skills = [skill('keep-me'), skill('dupe')];
const judge = fakeJudge({
'keep-me': { verdict: 'necessary', reason: 'unique' },
'dupe': { verdict: 'redundant', reason: 'overlaps keep-me' },
});
const report = await runSkillHygieneScan(home, skills, judge);
expect(report.scanned).toBe(2);
expect(report.demoted).toEqual(['dupe']);
expect(report.kept).toEqual(['keep-me']);
// sidecar written; status draft + reason
const idx = loadSkillHygiene(home);
expect(idx['dupe']).toMatchObject({ status: 'draft', verdict: 'redundant', reason: 'overlaps keep-me' });
expect(idx['dupe'].judgedAt).toBeTypeOf('string');
expect(idx['keep-me']).toBeUndefined(); // necessary skills are not written
// never deletes/edits anything on disk — only the sidecar file exists
expect(fs.existsSync(path.join(home, 'skill-hygiene.json'))).toBe(true);
});
it('dryRun reports demotions but writes nothing', async () => {
const report = await runSkillHygieneScan(
home,
[skill('dupe')],
fakeJudge({ dupe: { verdict: 'generic', reason: 'vague' } }),
{ dryRun: true },
);
expect(report.dryRun).toBe(true);
expect(report.demoted).toEqual(['dupe']);
expect(isSkillDraft(home, 'dupe')).toBe(false); // not persisted
expect(fs.existsSync(path.join(home, 'skill-hygiene.json'))).toBe(false);
});
it('skips skills already demoted to draft (no re-judge)', async () => {
demoteSkillToDraft(home, 'already', 'redundant', 'prior');
let judged = 0;
const counting: JudgeLLMCall = async (p) => { judged++; return await fakeJudge({})(p); };
const report = await runSkillHygieneScan(home, [skill('already'), skill('fresh')], counting);
expect(report.scanned).toBe(1); // only 'fresh' is judged
expect(judged).toBe(1);
expect(report.judged.map(j => j.name)).toEqual(['fresh']);
});
it('uses an injectable clock for judgedAt', async () => {
const fixed = new Date('2026-06-28T00:00:00.000Z');
await runSkillHygieneScan(home, [skill('dupe')], fakeJudge({ dupe: { verdict: 'redundant', reason: 'x' } }), {
now: () => fixed,
});
expect(loadSkillHygiene(home)['dupe'].judgedAt).toBe(fixed.toISOString());
});
});
describe('loadActiveSkills', () => {
it('excludes draft skills from the active set but keeps them on disk', () => {
const skillsDir = path.join(home, 'skills');
fs.mkdirSync(skillsDir, { recursive: true });
fs.writeFileSync(path.join(skillsDir, 'alpha.md'), '# Alpha\nbody', 'utf-8');
fs.writeFileSync(path.join(skillsDir, 'beta.md'), '# Beta\nbody', 'utf-8');
demoteSkillToDraft(home, 'beta', 'generic', 'too vague');
const active = loadActiveSkills(home).map(s => s.name).sort();
expect(active).toEqual(['alpha']);
// beta.md is untouched on disk — demotion is reversible
expect(fs.existsSync(path.join(skillsDir, 'beta.md'))).toBe(true);
});
});