import { describe, it, expect, vi, afterEach } from 'vitest'; import { isAgentRegistryMarkdown, parseAgentsMd } from '../agents.js'; describe('isAgentRegistryMarkdown', () => { it('rejects Cursor navigation AGENTS.md at repo root', () => { expect( isAgentRegistryMarkdown('# Agent navigation\n\n## Doc map\n'), ).toBe(false); }); it('accepts the global data/AGENTS.md registry shape', () => { expect(isAgentRegistryMarkdown('# Agents\n\n## Code Reviewer\n---\n')).toBe(true); }); }); describe('parseAgentsMd', () => { it('does not emit errors for navigation sections when file is skipped upstream', () => { // When isAgentRegistryMarkdown returns false, getAgentsForProject never calls this. // Sanity: a nav-shaped file would produce six "missing fence" errors if parsed. const nav = `# Agent navigation ## Doc map | Need | Read | |------|------| ## Task routing Start here `; const r = parseAgentsMd(nav); expect(r.agents).toHaveLength(0); expect(r.errors.length).toBeGreaterThan(0); }); }); // v2.6 sampling-streamjson-tokens (#11): per-agent llama.cpp sampler extensions. describe('parseAgentsMd: v2.6 sampling knobs', () => { afterEach(() => { vi.restoreAllMocks(); }); const withFrontmatter = (lines: string) => `# Agents ## Sampler --- temperature: 0.6 ${lines} tools: [view_file] description: test --- You sample. `; it('parses top_n_sigma and the dry_* family from frontmatter', () => { const md = withFrontmatter( [ 'top_n_sigma: 1.5', 'dry_multiplier: 0.8', 'dry_base: 1.75', 'dry_allowed_length: 2', 'dry_penalty_last_n: -1', ].join('\n'), ); const { agents, errors } = parseAgentsMd(md); expect(errors).toHaveLength(0); expect(agents).toHaveLength(1); const a = agents[0]!; expect(a.top_n_sigma).toBe(1.5); expect(a.dry_multiplier).toBe(0.8); expect(a.dry_base).toBe(1.75); expect(a.dry_allowed_length).toBe(2); expect(a.dry_penalty_last_n).toBe(-1); }); it('defaults the new sampler fields to null when omitted', () => { const { agents } = parseAgentsMd(withFrontmatter('top_p: 0.95')); const a = agents[0]!; expect(a.top_n_sigma).toBeNull(); expect(a.dry_multiplier).toBeNull(); expect(a.dry_base).toBeNull(); expect(a.dry_allowed_length).toBeNull(); expect(a.dry_penalty_last_n).toBeNull(); }); it('warns (does not error) on out-of-range top_n_sigma / dry_* values', () => { const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); const md = withFrontmatter( [ 'top_n_sigma: -1', 'dry_multiplier: -0.5', 'dry_base: -2', 'dry_allowed_length: -3', 'dry_penalty_last_n: -5', ].join('\n'), ); const { agents, errors } = parseAgentsMd(md); expect(errors).toHaveLength(0); expect(agents).toHaveLength(1); // Mirrors top_k/min_p: out-of-range still stored, with a warning. expect(warn).toHaveBeenCalled(); const warnings = warn.mock.calls.map((c) => String(c[0])).join('\n'); expect(warnings).toContain('top_n_sigma'); expect(warnings).toContain('dry_multiplier'); expect(warnings).toContain('dry_base'); expect(warnings).toContain('dry_allowed_length'); expect(warnings).toContain('dry_penalty_last_n'); }); it('errors on non-numeric / non-integer sampler values', () => { const md = withFrontmatter( ['top_n_sigma: high', 'dry_allowed_length: 2.5'].join('\n'), ); const { errors } = parseAgentsMd(md); const joined = errors.map((e) => e.reason).join('\n'); expect(joined).toContain('top_n_sigma must be a number'); expect(joined).toContain('dry_allowed_length must be an integer'); }); });