import { describe, test, expect, beforeAll, afterAll } from 'bun:test'; import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; import { execFileSync } from 'child_process'; const ROOT = path.resolve(import.meta.dir, '..'); const BIN = path.join(ROOT, 'bin', 'gstack-learnings-search'); const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-search-test-')); const tmpCwd = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-search-cwd-')); // gstack-slug derives slug from git remote (none here) → falls back to basename of cwd. const slug = path.basename(tmpCwd).replace(/[^a-zA-Z0-9._-]/g, ''); const projDir = path.join(tmpHome, 'projects', slug); function run(args: string[]): string { return execFileSync(BIN, args, { env: { ...process.env, GSTACK_HOME: tmpHome }, cwd: tmpCwd, encoding: 'utf-8', }); } beforeAll(() => { fs.mkdirSync(projDir, { recursive: true }); const entries = [ { ts: '2026-05-01T00:00:00Z', skill: 'test', type: 'pattern', key: 'foo-pattern', insight: 'A foo-related insight', confidence: 8, source: 'observed', files: [] }, { ts: '2026-05-02T00:00:00Z', skill: 'test', type: 'pitfall', key: 'bar-pitfall', insight: 'A bar-related insight', confidence: 8, source: 'observed', files: [] }, { ts: '2026-05-03T00:00:00Z', skill: 'test', type: 'pattern', key: 'baz-pattern', insight: 'A baz-related insight', confidence: 8, source: 'observed', files: [] }, ]; fs.writeFileSync(path.join(projDir, 'learnings.jsonl'), entries.map(e => JSON.stringify(e)).join('\n') + '\n'); }); afterAll(() => { fs.rmSync(tmpHome, { recursive: true, force: true }); fs.rmSync(tmpCwd, { recursive: true, force: true }); }); describe('gstack-learnings-search token-OR query semantics', () => { test('multi-token query returns entries matching ANY token', () => { const out = run(['--query', 'foo bar']); expect(out).toContain('foo-pattern'); expect(out).toContain('bar-pitfall'); expect(out).not.toContain('baz-pattern'); }); test('single-token query returns only entries matching that token', () => { const out = run(['--query', 'foo']); expect(out).toContain('foo-pattern'); expect(out).not.toContain('bar-pitfall'); expect(out).not.toContain('baz-pattern'); }); test('no --query flag returns all entries (backwards-compat)', () => { const out = run(['--limit', '10']); expect(out).toContain('foo-pattern'); expect(out).toContain('bar-pitfall'); expect(out).toContain('baz-pattern'); }); });