mirror of
https://github.com/ruvnet/RuView
synced 2026-07-30 18:41:42 +00:00
a34bfc246e
Add a read-only guidance CLI/MCP surface with reviewed capability maturity, repository citations, validation commands, limitations, and shared-brain evidence. Bump @ruvnet/ruview to 0.3.1 and add release-tarball smoke coverage.
122 lines
5.0 KiB
JavaScript
122 lines
5.0 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { mkdtempSync, rmSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { getGuidance, GUIDANCE_TOPICS, listGuidanceTopics } from '../src/guidance.js';
|
|
import { runTool } from '../src/tools.js';
|
|
|
|
const REPO_ROOT = fileURLToPath(new URL('../../..', import.meta.url));
|
|
|
|
test('guidance topics are stable, unique, and described', () => {
|
|
assert.equal(new Set(GUIDANCE_TOPICS).size, GUIDANCE_TOPICS.length);
|
|
assert.ok(GUIDANCE_TOPICS.includes('homecore'));
|
|
assert.deepEqual(
|
|
listGuidanceTopics().map(({ topic }) => topic),
|
|
GUIDANCE_TOPICS,
|
|
);
|
|
for (const item of listGuidanceTopics()) assert.ok(item.summary.length > 20);
|
|
});
|
|
|
|
test('overview returns a source-cited capability map and verifies local paths', () => {
|
|
const result = getGuidance({}, { repoRoot: REPO_ROOT });
|
|
assert.equal(result.ok, true, JSON.stringify(result.sourceCheck));
|
|
assert.equal(result.topic, 'overview');
|
|
assert.ok(result.capabilities.length >= 10);
|
|
assert.equal(result.sourceCheck.mode, 'local-checkout');
|
|
assert.equal(result.sourceCheck.verified, true);
|
|
assert.deepEqual(result.sourceCheck.missing, []);
|
|
assert.ok(result.entryPoints.includes('v2/Cargo.toml'));
|
|
assert.ok(result.recommendedCommands.length > 0);
|
|
for (const capability of result.capabilities) {
|
|
assert.match(capability.id, /^[a-z0-9][a-z0-9-]+$/);
|
|
assert.ok(capability.summary);
|
|
assert.ok(capability.status);
|
|
assert.ok(capability.evidence);
|
|
assert.ok(capability.sources.length > 0);
|
|
assert.ok(capability.validation.length > 0);
|
|
assert.ok(capability.limitations.length > 0);
|
|
for (const source of capability.sources) {
|
|
assert.ok(!source.startsWith('/'));
|
|
assert.ok(!source.includes('..'));
|
|
}
|
|
}
|
|
result.capabilities[0].sources[0] = 'mutated';
|
|
assert.notEqual(getGuidance({}, { repoRoot: REPO_ROOT }).capabilities[0].sources[0], 'mutated');
|
|
});
|
|
|
|
test('homecore guidance exposes requested capabilities and honest boundaries', () => {
|
|
const result = getGuidance({ topic: 'homecore' }, { repoRoot: REPO_ROOT });
|
|
const ids = new Set(result.capabilities.map(({ id }) => id));
|
|
for (const id of [
|
|
'homecore-runtime-restore',
|
|
'homecore-plugins',
|
|
'homecore-ha-api',
|
|
'homecore-hap',
|
|
'homecore-migration',
|
|
'homecore-voice',
|
|
]) {
|
|
assert.ok(ids.has(id), `missing ${id}`);
|
|
}
|
|
assert.equal(result.capabilities.find(({ id }) => id === 'homecore-plugins').status, 'feature-gated');
|
|
assert.equal(result.capabilities.find(({ id }) => id === 'homecore-voice').status, 'provider-required');
|
|
assert.match(
|
|
result.capabilities.find(({ id }) => id === 'homecore-ha-api').limitations.join(' '),
|
|
/not parity/i,
|
|
);
|
|
});
|
|
|
|
test('query ranks the matching capability and searches reviewed knowledge', () => {
|
|
const result = getGuidance(
|
|
{ topic: 'homecore', query: 'Wasmtime plugin', limit: 3 },
|
|
{ repoRoot: REPO_ROOT },
|
|
);
|
|
assert.equal(result.ok, true);
|
|
assert.equal(result.capabilities[0].id, 'homecore-plugins');
|
|
assert.ok(result.capabilities.length <= 3);
|
|
assert.ok(Array.isArray(result.relatedKnowledge));
|
|
for (const record of result.relatedKnowledge) {
|
|
assert.match(record.citation, /:\d+$/);
|
|
assert.equal(record.reviewed, true);
|
|
}
|
|
const shared = getGuidance({ query: 'guidance' }, { repoRoot: REPO_ROOT });
|
|
assert.ok(shared.relatedKnowledge.some(({ id }) => id === 'guidance-entrypoint'));
|
|
});
|
|
|
|
test('packaged guidance is explicit when no checkout is available', () => {
|
|
const result = getGuidance({ topic: 'architecture', limit: 1 });
|
|
assert.equal(result.ok, true);
|
|
assert.equal(result.sourceCheck.mode, 'packaged-catalog');
|
|
assert.equal(result.sourceCheck.verified, false);
|
|
assert.match(result.sourceCheck.note, /not checked/i);
|
|
});
|
|
|
|
test('local source drift fails closed', () => {
|
|
const empty = mkdtempSync(join(tmpdir(), 'ruview-guidance-'));
|
|
try {
|
|
const result = getGuidance({ topic: 'homecore', limit: 1 }, { repoRoot: empty });
|
|
assert.equal(result.ok, false);
|
|
assert.equal(result.sourceCheck.verified, false);
|
|
assert.ok(result.sourceCheck.missing.length > 0);
|
|
} finally {
|
|
rmSync(empty, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('guidance direct API and MCP schema reject malformed input', async () => {
|
|
assert.throws(() => getGuidance([]), /input must be an object/);
|
|
assert.throws(() => getGuidance({ query: {} }), /query must be a string/);
|
|
assert.throws(() => getGuidance({}, { repoRoot: 7 }), /repoRoot/);
|
|
assert.throws(() => getGuidance({ topic: 'unknown' }), /unsupported guidance topic/);
|
|
assert.throws(() => getGuidance({ query: 'x' }), /2\.\.500/);
|
|
assert.throws(() => getGuidance({ limit: 21 }), /between 1 and 20/);
|
|
|
|
const bad = await runTool('ruview_guidance', { topic: 'homecore', injected: true });
|
|
assert.equal(bad.ok, false);
|
|
assert.equal(bad.reason, 'invalid_arguments');
|
|
const short = await runTool('ruview_guidance', { query: 'x' });
|
|
assert.equal(short.ok, false);
|
|
assert.equal(short.reason, 'invalid_arguments');
|
|
});
|