The 'living' half of the v5.7 living knowledge base. Same hybrid split as the
optimization lens (Chunk 2b): a deterministic, byte-stable, unit-tested core +
a web/judgment command shell.
- scanners/lib/knowledge-refresh.mjs: pure assessFreshness(register,
{referenceDate, staleAfterDays=90}) — age-based fresh/stale classification of
source.verified; referenceDate injected (never reads the clock) → fully
deterministic. 15 tests.
- scanners/knowledge-refresh-cli.mjs: -cli (NOT an orchestrated scanner →
scanner count stays 15, suite byte-stable). Read-only — never writes the
register, never hits the network. --reference-date/--stale-after/--dry-run,
exit 0/1/3. 8 tests.
- commands/knowledge-refresh.md (opus): CLI stale-report → re-verify each stale
entry by re-reading its source.url → poll CC changelog + Anthropic blog →
apply ONLY human-approved writes, then re-validate the register. No unverified
claim is ever auto-written (Verifiseringsplikt). Web-driven → not byte-stable.
No new agent, no new orchestrated scanner. Docs/badges: commands 19→20,
tests 1068→1091 (20 lib + 32 scanner test files). self-audit A/A, readmeCheck passed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
96 lines
4 KiB
JavaScript
96 lines
4 KiB
JavaScript
import { describe, it } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { resolve, join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { execFileSync } from 'node:child_process';
|
|
import { readFileSync, mkdtempSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
|
|
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
const CLI = resolve(__dirname, '../../scanners/knowledge-refresh-cli.mjs');
|
|
|
|
// The bundled register's seed entries are all verified 2026-06-20, so a reference
|
|
// date of 2026-06-21 makes every entry exactly 1 day old — deterministic regardless
|
|
// of the real clock. We exploit that to exercise both the fresh and stale branches.
|
|
const REF = '2026-06-21';
|
|
|
|
function runCli(extraArgs) {
|
|
try {
|
|
const stdout = execFileSync('node', [CLI, ...extraArgs], { encoding: 'utf-8', timeout: 15000 });
|
|
return { status: 0, stdout };
|
|
} catch (err) {
|
|
return { status: err.status, stdout: err.stdout || '', stderr: err.stderr || '' };
|
|
}
|
|
}
|
|
|
|
describe('knowledge-refresh-cli — exit codes', () => {
|
|
it('exits 0 when every entry is fresh', () => {
|
|
const { status, stdout } = runCli(['--reference-date', REF, '--stale-after', '90']);
|
|
assert.equal(status, 0);
|
|
const out = JSON.parse(stdout);
|
|
assert.equal(out.counts.stale, 0);
|
|
});
|
|
|
|
it('exits 1 (advisory) when one or more entries are stale', () => {
|
|
// stale-after 0 → anything verified before the reference date is stale.
|
|
const { status, stdout } = runCli(['--reference-date', REF, '--stale-after', '0']);
|
|
assert.equal(status, 1);
|
|
const out = JSON.parse(stdout);
|
|
assert.ok(out.counts.stale > 0);
|
|
assert.equal(out.counts.fresh, 0);
|
|
});
|
|
|
|
it('exits 3 on an invalid --stale-after', () => {
|
|
assert.equal(runCli(['--stale-after', 'abc']).status, 3);
|
|
assert.equal(runCli(['--stale-after', '-5']).status, 3);
|
|
});
|
|
|
|
it('exits 3 on a malformed --reference-date', () => {
|
|
assert.equal(runCli(['--reference-date', '20-06-2026']).status, 3);
|
|
});
|
|
});
|
|
|
|
describe('knowledge-refresh-cli — payload shape', () => {
|
|
it('emits the expected keys + a consistent count triple', () => {
|
|
const { stdout } = runCli(['--reference-date', REF, '--stale-after', '90']);
|
|
const out = JSON.parse(stdout);
|
|
assert.equal(out.status, 'ok');
|
|
assert.match(out.registerPath, /knowledge\/best-practices\.json$/);
|
|
assert.equal(out.referenceDate, '2026-06-21');
|
|
assert.equal(out.staleAfterDays, 90);
|
|
assert.ok(Array.isArray(out.stale));
|
|
assert.ok(Array.isArray(out.fresh));
|
|
assert.equal(out.counts.total, out.counts.stale + out.counts.fresh);
|
|
assert.ok(out.counts.total > 0, 'bundled register is non-empty');
|
|
});
|
|
|
|
it('is always dryRun:true regardless of the flag, and echoes the request', () => {
|
|
const without = JSON.parse(runCli(['--reference-date', REF]).stdout);
|
|
assert.equal(without.dryRun, true);
|
|
assert.equal(without.requestedDryRun, false);
|
|
const withFlag = JSON.parse(runCli(['--reference-date', REF, '--dry-run']).stdout);
|
|
assert.equal(withFlag.dryRun, true);
|
|
assert.equal(withFlag.requestedDryRun, true);
|
|
});
|
|
|
|
it('stale entries carry provenance (id, verified, ageDays, url, claim)', () => {
|
|
const out = JSON.parse(runCli(['--reference-date', REF, '--stale-after', '0']).stdout);
|
|
const s = out.stale[0];
|
|
assert.match(s.id, /^BP-[A-Z]+-\d{3}$/);
|
|
assert.match(s.verified, /^\d{4}-\d{2}-\d{2}$/);
|
|
assert.equal(s.ageDays, 1);
|
|
assert.ok(s.url && s.claim);
|
|
});
|
|
});
|
|
|
|
describe('knowledge-refresh-cli — --output-file', () => {
|
|
it('writes JSON to the file and not to stdout', () => {
|
|
const dir = mkdtempSync(join(tmpdir(), 'kr-cli-'));
|
|
const out = join(dir, 'refresh.json');
|
|
const { stdout } = runCli(['--reference-date', REF, '--stale-after', '90', '--output-file', out]);
|
|
assert.equal(stdout.trim(), '', 'stdout is silent when --output-file is given');
|
|
const written = JSON.parse(readFileSync(out, 'utf-8'));
|
|
assert.equal(written.status, 'ok');
|
|
assert.equal(written.counts.stale, 0);
|
|
});
|
|
});
|