mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-31 04:07:52 +00:00
Master landed significant work since this branch was cut (v0.15.x → v0.16.x →
v0.17.0 gbrain dream + runCycle → v0.18.0 multi-source brains → v0.18.1 RLS
hardening). Bumped this branch's version from the claimed 0.18.0 to 0.19.0
because master already owns 0.18.x.
Conflicts resolved:
- VERSION: 0.19.0 (was 0.18.0 on HEAD vs 0.18.1 on master)
- package.json: 0.19.0, kept all 11 eval-facing exports, merged master's
typescript devDep + postinstall script + test script (typecheck added)
- src/core/types.ts: union of both PageType additions. Master had added
`meeting | note`; this branch added `email | slack | calendar-event`
for inbox/chat/calendar ingest. Final enum carries all five.
- CHANGELOG.md: renumbered the BrainBench-extraction entry to 0.19.0 and
placed it above master's 0.18.1 RLS entry. Tweaked copy ("In v0.17 it
lived inside this repo" → "Previously it lived inside this repo") to
stop implying a specific version that never shipped.
- CLAUDE.md: adjusted "BrainBench in a sibling repo" heading from
(v0.18+) → (v0.19+).
- docs/benchmarks/2026-04-18-minions-vs-openclaw-production.md:
resolved modify-vs-delete conflict in favor of delete (the extraction).
- scripts/llms-config.ts: dropped the docs/benchmarks/ entry (directory
no longer exists here; lives in gbrain-evals).
- llms.txt / llms-full.txt: regenerated after the config change.
- bun.lock: accepted master's (master already dropped pdf-parse as a
drive-by; aligned with our removal).
Tests: 2094 pass, 236 skip, 18 fail. Spot-checked failures — build-llms,
dream, orphans tests all pass in isolation. Failures reproduce only under
full-suite parallel load and are pre-existing master flakiness (matches the
graph-quality flake noted in the earlier summary). Not merge-introduced.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
162 lines
6.5 KiB
TypeScript
162 lines
6.5 KiB
TypeScript
import { describe, test, expect } from 'bun:test';
|
|
import { spawnSync } from 'node:child_process';
|
|
import { join } from 'node:path';
|
|
import { parseGlobalFlags, cliOptsToProgressOptions, DEFAULT_CLI_OPTIONS, setCliOptions, getCliOptions, _resetCliOptionsForTest } from '../src/core/cli-options.ts';
|
|
|
|
describe('parseGlobalFlags', () => {
|
|
test('empty argv → defaults, empty rest', () => {
|
|
const r = parseGlobalFlags([]);
|
|
expect(r.cliOpts).toEqual(DEFAULT_CLI_OPTIONS);
|
|
expect(r.rest).toEqual([]);
|
|
});
|
|
|
|
test('strips --quiet from argv and sets quiet=true', () => {
|
|
// Per-command handlers that historically parsed their own --quiet
|
|
// (skillpack-check) now read the resolved CliOptions singleton via
|
|
// getCliOptions() — see src/core/cli-options.ts.
|
|
const r = parseGlobalFlags(['--quiet', 'doctor', '--fast']);
|
|
expect(r.cliOpts.quiet).toBe(true);
|
|
expect(r.cliOpts.progressJson).toBe(false);
|
|
expect(r.rest).toEqual(['doctor', '--fast']);
|
|
});
|
|
|
|
test('strips --progress-json from argv', () => {
|
|
const r = parseGlobalFlags(['--progress-json', 'doctor']);
|
|
expect(r.cliOpts.progressJson).toBe(true);
|
|
expect(r.rest).toEqual(['doctor']);
|
|
});
|
|
|
|
test('--progress-interval=500 form', () => {
|
|
const r = parseGlobalFlags(['--progress-interval=500', 'embed']);
|
|
expect(r.cliOpts.progressInterval).toBe(500);
|
|
expect(r.rest).toEqual(['embed']);
|
|
});
|
|
|
|
test('--progress-interval 500 space-separated form', () => {
|
|
const r = parseGlobalFlags(['--progress-interval', '500', 'embed']);
|
|
expect(r.cliOpts.progressInterval).toBe(500);
|
|
expect(r.rest).toEqual(['embed']);
|
|
});
|
|
|
|
test('global flag interleaved mid-argv still stripped', () => {
|
|
const r = parseGlobalFlags(['doctor', '--progress-json', '--fast']);
|
|
expect(r.cliOpts.progressJson).toBe(true);
|
|
expect(r.rest).toEqual(['doctor', '--fast']);
|
|
});
|
|
|
|
test('invalid --progress-interval value passes through (per-command parser can handle it)', () => {
|
|
const r = parseGlobalFlags(['--progress-interval=abc', 'doctor']);
|
|
// Unparseable value → leave the flag in rest, default interval kept.
|
|
expect(r.cliOpts.progressInterval).toBe(DEFAULT_CLI_OPTIONS.progressInterval);
|
|
expect(r.rest).toEqual(['--progress-interval=abc', 'doctor']);
|
|
});
|
|
|
|
test('negative --progress-interval rejected', () => {
|
|
const r = parseGlobalFlags(['--progress-interval=-1', 'doctor']);
|
|
expect(r.cliOpts.progressInterval).toBe(DEFAULT_CLI_OPTIONS.progressInterval);
|
|
expect(r.rest).toContain('--progress-interval=-1');
|
|
});
|
|
|
|
test('unknown flags pass through unchanged', () => {
|
|
const r = parseGlobalFlags(['doctor', '--fast', '--json', '--foo=bar']);
|
|
expect(r.rest).toEqual(['doctor', '--fast', '--json', '--foo=bar']);
|
|
expect(r.cliOpts).toEqual(DEFAULT_CLI_OPTIONS);
|
|
});
|
|
|
|
test('all global flags combined', () => {
|
|
const r = parseGlobalFlags(['--quiet', '--progress-json', '--progress-interval=250', 'sync']);
|
|
expect(r.cliOpts).toEqual({ quiet: true, progressJson: true, progressInterval: 250 });
|
|
expect(r.rest).toEqual(['sync']);
|
|
});
|
|
});
|
|
|
|
describe('getCliOptions / setCliOptions singleton', () => {
|
|
test('defaults when never set', () => {
|
|
_resetCliOptionsForTest();
|
|
expect(getCliOptions()).toEqual(DEFAULT_CLI_OPTIONS);
|
|
});
|
|
|
|
test('setCliOptions applies + getCliOptions returns a copy', () => {
|
|
_resetCliOptionsForTest();
|
|
setCliOptions({ quiet: false, progressJson: true, progressInterval: 250 });
|
|
expect(getCliOptions().progressJson).toBe(true);
|
|
expect(getCliOptions().progressInterval).toBe(250);
|
|
});
|
|
});
|
|
|
|
describe('cli.ts global-flag stripping (integration)', () => {
|
|
const CLI = join(import.meta.dir, '..', 'src', 'cli.ts');
|
|
|
|
test('gbrain --progress-json --version works (global flag stripped before dispatch)', () => {
|
|
const res = spawnSync('bun', [CLI, '--progress-json', '--version'], {
|
|
encoding: 'utf-8',
|
|
env: { ...process.env, NO_COLOR: '1' },
|
|
});
|
|
expect(res.status).toBe(0);
|
|
expect(res.stdout).toContain('gbrain ');
|
|
});
|
|
|
|
test('gbrain --quiet --progress-interval=500 version works (flags interleaved, all stripped)', () => {
|
|
const res = spawnSync('bun', [CLI, '--quiet', '--progress-interval=500', 'version'], {
|
|
encoding: 'utf-8',
|
|
env: { ...process.env, NO_COLOR: '1' },
|
|
});
|
|
expect(res.status).toBe(0);
|
|
expect(res.stdout).toContain('gbrain ');
|
|
});
|
|
});
|
|
|
|
describe('CLI integration: progress streams to the right channel', () => {
|
|
const CLI = join(import.meta.dir, '..', 'src', 'cli.ts');
|
|
|
|
test('gbrain --progress-json --version emits only the version on stdout', () => {
|
|
// `version` is a single-shot command that goes through the main()
|
|
// dispatch path. We want to confirm --progress-json doesn't force
|
|
// stray progress onto stdout for commands that don't use a reporter.
|
|
const res = spawnSync('bun', [CLI, '--progress-json', '--version'], {
|
|
encoding: 'utf-8',
|
|
env: { ...process.env, NO_COLOR: '1' },
|
|
});
|
|
expect(res.status).toBe(0);
|
|
expect(res.stdout.trim()).toMatch(/^gbrain /);
|
|
// No JSON progress object should end up on stdout.
|
|
expect(res.stdout).not.toContain('"event":"start"');
|
|
});
|
|
|
|
test('gbrain --quiet skillpack-check returns exit code with no stdout', () => {
|
|
// Regression guard for the flag-collision that skillpack-check hit
|
|
// when --quiet briefly passed through argv. Now it reads the singleton.
|
|
const res = spawnSync('bun', [CLI, '--quiet', 'skillpack-check'], {
|
|
encoding: 'utf-8',
|
|
env: { ...process.env, NO_COLOR: '1' },
|
|
});
|
|
// Exit may be 0 or 1 depending on whether a brain is configured;
|
|
// what matters is stdout stays empty.
|
|
expect(res.stdout).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('cliOptsToProgressOptions', () => {
|
|
test('--quiet → quiet mode', () => {
|
|
const opts = cliOptsToProgressOptions({ quiet: true, progressJson: false, progressInterval: 1000 });
|
|
expect(opts.mode).toBe('quiet');
|
|
});
|
|
|
|
test('--progress-json → json mode with interval', () => {
|
|
const opts = cliOptsToProgressOptions({ quiet: false, progressJson: true, progressInterval: 500 });
|
|
expect(opts.mode).toBe('json');
|
|
expect(opts.minIntervalMs).toBe(500);
|
|
});
|
|
|
|
test('defaults → auto mode', () => {
|
|
const opts = cliOptsToProgressOptions(DEFAULT_CLI_OPTIONS);
|
|
expect(opts.mode).toBe('auto');
|
|
expect(opts.minIntervalMs).toBe(1000);
|
|
});
|
|
|
|
test('quiet takes priority over progressJson', () => {
|
|
const opts = cliOptsToProgressOptions({ quiet: true, progressJson: true, progressInterval: 1000 });
|
|
expect(opts.mode).toBe('quiet');
|
|
});
|
|
});
|