mirror of
https://github.com/wiseguru/ReWrite-Voice-Notes.git
synced 2026-07-22 07:49:19 +00:00
Real-time dictation (AssemblyAI/Deepgram, own provider/key/model), auto-ingest folders, record-in-background, per-invocation diarization, manage built-in templates (enable/track), local whisper.cpp auto-start/idle-stop, and dev tooling (local review + release-checklist skill). Voxtral realtime was reverse-engineered but pulled (not WebView-reachable); its adapter is kept on disk, unwired. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
297 lines
9.5 KiB
TypeScript
297 lines
9.5 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import {
|
|
CODE_PATHS,
|
|
DOC_PATHS,
|
|
buildDiffArgs,
|
|
buildReviewMessages,
|
|
formatReport,
|
|
getHostArgs,
|
|
isLoopbackHost,
|
|
parseCliArgs,
|
|
parseLocalReviewConfig,
|
|
pathsForMode,
|
|
scopeLabel,
|
|
splitArgs,
|
|
truncateDiff,
|
|
untrackedArgs,
|
|
} from '../local-review.mjs';
|
|
|
|
// The loopback guard is duplicated from src/whisper-host.ts on purpose (see the comment in
|
|
// local-review.mjs); mirror test/whisper-host.test.ts so the copy keeps the same coverage.
|
|
describe('getHostArgs', () => {
|
|
it('returns an empty array when no --host is present', () => {
|
|
expect(getHostArgs(['-m', 'model.gguf', '--port', '8090'])).toEqual([]);
|
|
});
|
|
|
|
it('collects a single --host value (space form)', () => {
|
|
expect(getHostArgs(['--host', '127.0.0.1'])).toEqual(['127.0.0.1']);
|
|
});
|
|
|
|
it('collects a single --host value (= form)', () => {
|
|
expect(getHostArgs(['--host=127.0.0.1'])).toEqual(['127.0.0.1']);
|
|
});
|
|
|
|
it('collects every --host occurrence, not just the first', () => {
|
|
expect(getHostArgs(['--host', '127.0.0.1', '--host', '0.0.0.0'])).toEqual(['127.0.0.1', '0.0.0.0']);
|
|
});
|
|
|
|
it('treats a --host with no following value as empty string', () => {
|
|
expect(getHostArgs(['--host'])).toEqual(['']);
|
|
});
|
|
});
|
|
|
|
describe('isLoopbackHost', () => {
|
|
it.each([
|
|
['127.0.0.1', true],
|
|
['localhost', true],
|
|
['LOCALHOST', true],
|
|
['::1', true],
|
|
['[::1]', true],
|
|
[' 127.0.0.1 ', true],
|
|
['0.0.0.0', false],
|
|
['192.168.1.5', false],
|
|
['example.com', false],
|
|
['', false],
|
|
])('isLoopbackHost(%s) === %s', (host, expected) => {
|
|
expect(isLoopbackHost(host)).toBe(expected);
|
|
});
|
|
});
|
|
|
|
describe('splitArgs', () => {
|
|
it('returns an empty array for empty/whitespace input', () => {
|
|
expect(splitArgs('')).toEqual([]);
|
|
expect(splitArgs(' ')).toEqual([]);
|
|
});
|
|
|
|
it('splits on runs of whitespace', () => {
|
|
expect(splitArgs('--ctx-size 4096 --threads 8')).toEqual(['--ctx-size', '4096', '--threads', '8']);
|
|
});
|
|
});
|
|
|
|
describe('parseCliArgs', () => {
|
|
it('defaults to no flags', () => {
|
|
expect(parseCliArgs([])).toEqual({ base: null, staged: false, full: false, mode: 'code' });
|
|
});
|
|
|
|
it('reads --base with a following value', () => {
|
|
expect(parseCliArgs(['--base', 'main'])).toEqual({ base: 'main', staged: false, full: false, mode: 'code' });
|
|
});
|
|
|
|
it('reads --base=value form', () => {
|
|
expect(parseCliArgs(['--base=develop'])).toEqual({ base: 'develop', staged: false, full: false, mode: 'code' });
|
|
});
|
|
|
|
it('reads --staged and --full', () => {
|
|
expect(parseCliArgs(['--staged'])).toMatchObject({ staged: true });
|
|
expect(parseCliArgs(['--full'])).toMatchObject({ full: true });
|
|
});
|
|
|
|
it('defaults mode to code and reads --docs / --code / --mode', () => {
|
|
expect(parseCliArgs([]).mode).toBe('code');
|
|
expect(parseCliArgs(['--docs']).mode).toBe('docs');
|
|
expect(parseCliArgs(['--code']).mode).toBe('code');
|
|
expect(parseCliArgs(['--mode', 'docs']).mode).toBe('docs');
|
|
expect(parseCliArgs(['--mode=docs']).mode).toBe('docs');
|
|
// unknown mode value falls back to code
|
|
expect(parseCliArgs(['--mode', 'wat']).mode).toBe('code');
|
|
});
|
|
});
|
|
|
|
describe('pathsForMode', () => {
|
|
it('returns code paths by default and doc paths for docs', () => {
|
|
expect(pathsForMode('code')).toBe(CODE_PATHS);
|
|
expect(pathsForMode('docs')).toBe(DOC_PATHS);
|
|
expect(pathsForMode('anything-else')).toBe(CODE_PATHS);
|
|
});
|
|
|
|
it('code scope covers src and styles; doc scope is markdown', () => {
|
|
expect(CODE_PATHS).toContain('src/');
|
|
expect(CODE_PATHS).toContain('styles.css');
|
|
expect(DOC_PATHS).toEqual(['*.md']);
|
|
});
|
|
});
|
|
|
|
describe('untrackedArgs', () => {
|
|
it('lists untracked, non-ignored files within the pathspec', () => {
|
|
expect(untrackedArgs(['src/'])).toEqual(['ls-files', '--others', '--exclude-standard', '--', 'src/']);
|
|
});
|
|
|
|
it('omits the pathspec separator when no paths are given', () => {
|
|
expect(untrackedArgs([])).toEqual(['ls-files', '--others', '--exclude-standard']);
|
|
});
|
|
});
|
|
|
|
describe('buildDiffArgs', () => {
|
|
it('diffs the merge-base against the working tree by default', () => {
|
|
expect(buildDiffArgs({ staged: false, full: false }, 'abc123')).toEqual(['diff', 'abc123']);
|
|
});
|
|
|
|
it('narrows to the index with --staged', () => {
|
|
expect(buildDiffArgs({ staged: true, full: false }, 'abc123')).toEqual(['diff', '--staged']);
|
|
});
|
|
|
|
it('narrows to the last commit with --full', () => {
|
|
expect(buildDiffArgs({ staged: false, full: true }, 'abc123')).toEqual(['diff', 'HEAD~1', 'HEAD']);
|
|
});
|
|
|
|
it('adds --stat when requested', () => {
|
|
expect(buildDiffArgs({ staged: false, full: false }, 'abc123', { stat: true })).toEqual(['diff', '--stat', 'abc123']);
|
|
});
|
|
|
|
it('appends a pathspec after the range', () => {
|
|
expect(buildDiffArgs({ staged: false, full: false }, 'abc123', { paths: ['src/', 'styles.css'] }))
|
|
.toEqual(['diff', 'abc123', '--', 'src/', 'styles.css']);
|
|
});
|
|
|
|
it('combines stat and pathspec', () => {
|
|
expect(buildDiffArgs({ staged: false, full: false }, 'abc123', { stat: true, paths: ['*.md'] }))
|
|
.toEqual(['diff', '--stat', 'abc123', '--', '*.md']);
|
|
});
|
|
});
|
|
|
|
describe('scopeLabel', () => {
|
|
it('describes each scope distinctly', () => {
|
|
const def = scopeLabel({ staged: false, full: false });
|
|
const staged = scopeLabel({ staged: true, full: false });
|
|
const full = scopeLabel({ staged: false, full: true });
|
|
expect(new Set([def, staged, full]).size).toBe(3);
|
|
expect(staged).toMatch(/staged/i);
|
|
expect(full).toMatch(/commit/i);
|
|
});
|
|
});
|
|
|
|
describe('truncateDiff', () => {
|
|
it('leaves a short diff untouched', () => {
|
|
expect(truncateDiff('abc', 60000)).toEqual({ text: 'abc', truncated: false });
|
|
});
|
|
|
|
it('truncates and marks an over-long diff', () => {
|
|
const long = 'x'.repeat(200);
|
|
const out = truncateDiff(long, 50);
|
|
expect(out.truncated).toBe(true);
|
|
expect(out.text.startsWith('x'.repeat(50))).toBe(true);
|
|
expect(out.text).toMatch(/truncated at 50 characters/);
|
|
});
|
|
});
|
|
|
|
describe('parseLocalReviewConfig', () => {
|
|
const ok = { localReview: { binaryPath: '/bin/llama-server', modelPath: '/models/ornith.gguf' } };
|
|
const yes = () => true;
|
|
|
|
it('throws when the top-level value is not an object', () => {
|
|
expect(() => parseLocalReviewConfig(null, yes)).toThrow(/JSON object/);
|
|
});
|
|
|
|
it('throws when localReview is missing', () => {
|
|
expect(() => parseLocalReviewConfig({}, yes)).toThrow(/localReview/);
|
|
});
|
|
|
|
it('throws when binaryPath is empty', () => {
|
|
expect(() => parseLocalReviewConfig({ localReview: { modelPath: '/m' } }, yes)).toThrow(/binaryPath/);
|
|
});
|
|
|
|
it('throws when modelPath is empty', () => {
|
|
expect(() => parseLocalReviewConfig({ localReview: { binaryPath: '/b' } }, yes)).toThrow(/modelPath/);
|
|
});
|
|
|
|
it('throws when a configured path does not exist', () => {
|
|
expect(() => parseLocalReviewConfig(ok, () => false)).toThrow(/does not exist/);
|
|
});
|
|
|
|
it('applies defaults for optional fields', () => {
|
|
const cfg = parseLocalReviewConfig(ok, yes);
|
|
expect(cfg).toMatchObject({
|
|
binaryPath: '/bin/llama-server',
|
|
modelPath: '/models/ornith.gguf',
|
|
port: 8090,
|
|
extraArgs: '',
|
|
baseRef: 'master',
|
|
readyTimeoutMs: 60000,
|
|
requestTimeoutMs: 300000,
|
|
maxDiffChars: 60000,
|
|
maxOutputTokens: 8192,
|
|
});
|
|
});
|
|
|
|
it('keeps valid custom values and rejects an out-of-range port', () => {
|
|
const cfg = parseLocalReviewConfig({
|
|
localReview: { binaryPath: '/b', modelPath: '/m', port: 70000, baseRef: 'dev', maxDiffChars: 1000 },
|
|
}, yes);
|
|
expect(cfg.port).toBe(8090); // 70000 > 65535 falls back to default
|
|
expect(cfg.baseRef).toBe('dev');
|
|
expect(cfg.maxDiffChars).toBe(1000);
|
|
});
|
|
|
|
it('trims path values', () => {
|
|
const cfg = parseLocalReviewConfig({ localReview: { binaryPath: ' /b ', modelPath: ' /m ' } }, yes);
|
|
expect(cfg.binaryPath).toBe('/b');
|
|
expect(cfg.modelPath).toBe('/m');
|
|
});
|
|
});
|
|
|
|
describe('buildReviewMessages', () => {
|
|
it('produces a system + user pair carrying the diff', () => {
|
|
const msgs = buildReviewMessages('diff body');
|
|
expect(msgs).toHaveLength(2);
|
|
expect(msgs[0].role).toBe('system');
|
|
expect(msgs[1].role).toBe('user');
|
|
expect(msgs[1].content).toContain('diff body');
|
|
});
|
|
|
|
it('uses a different system prompt for docs mode than code mode', () => {
|
|
const code = buildReviewMessages('x', 'code')[0].content;
|
|
const docs = buildReviewMessages('x', 'docs')[0].content;
|
|
expect(code).not.toBe(docs);
|
|
expect(docs).toMatch(/consisten|contradict|stale/i);
|
|
expect(code).toMatch(/correctness|bug/i);
|
|
});
|
|
});
|
|
|
|
describe('formatReport', () => {
|
|
it('includes the header fields and findings', () => {
|
|
const report = formatReport({
|
|
baseRef: 'master',
|
|
mergeBase: 'deadbeef',
|
|
timestamp: '2026-07-07T00:00:00.000Z',
|
|
diffStat: ' src/main.ts | 2 +-',
|
|
findings: 'Looks fine.',
|
|
truncated: false,
|
|
scope: { staged: false, full: false },
|
|
});
|
|
expect(report).toContain('master');
|
|
expect(report).toContain('deadbeef');
|
|
expect(report).toContain('Looks fine.');
|
|
expect(report).toContain('src/main.ts');
|
|
expect(report).not.toContain('was truncated');
|
|
});
|
|
|
|
it('reflects the review mode and untracked-file count in the header', () => {
|
|
const report = formatReport({
|
|
baseRef: 'master',
|
|
mergeBase: 'deadbeef',
|
|
timestamp: 't',
|
|
diffStat: '',
|
|
findings: 'ok',
|
|
truncated: false,
|
|
scope: { staged: false, full: false },
|
|
mode: 'docs',
|
|
untrackedCount: 2,
|
|
});
|
|
expect(report).toContain('# Local review report (docs)');
|
|
expect(report).toMatch(/2 untracked file/);
|
|
});
|
|
|
|
it('notes truncation when set', () => {
|
|
const report = formatReport({
|
|
baseRef: 'master',
|
|
mergeBase: 'deadbeef',
|
|
timestamp: 't',
|
|
diffStat: '',
|
|
findings: '',
|
|
truncated: true,
|
|
scope: { staged: false, full: false },
|
|
});
|
|
expect(report).toMatch(/truncated/);
|
|
expect(report).toContain('(no findings returned)');
|
|
});
|
|
});
|