mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-08-17 12:45:42 +00:00
Oversized AGENTS.md files are no longer silently truncated. The full content is injected, and a warning is shown in the TUI status bar and the web UI when the combined AGENTS.md size exceeds the recommended 32 KB. A generic session-warnings API backs this so future warning types can be added without changing the API surface.
204 lines
8.3 KiB
TypeScript
204 lines
8.3 KiB
TypeScript
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'pathe';
|
|
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import { loadAgentsMd, prepareSystemPromptContext } from '../../src/profile/context';
|
|
import { testKaos } from '../fixtures/test-kaos';
|
|
|
|
let homeDir: string;
|
|
let workDir: string;
|
|
let extraDirs: string[];
|
|
|
|
beforeEach(async () => {
|
|
homeDir = await mkdtemp(join(tmpdir(), 'kimi-agents-home-'));
|
|
workDir = await mkdtemp(join(tmpdir(), 'kimi-agents-work-'));
|
|
extraDirs = [];
|
|
vi.spyOn(testKaos, 'gethome').mockReturnValue(homeDir);
|
|
vi.spyOn(testKaos, 'getcwd').mockReturnValue(workDir);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
vi.restoreAllMocks();
|
|
await rm(homeDir, { recursive: true, force: true });
|
|
await rm(workDir, { recursive: true, force: true });
|
|
await Promise.all(extraDirs.map((dir) => rm(dir, { recursive: true, force: true })));
|
|
});
|
|
|
|
describe('loadAgentsMd user-level discovery', () => {
|
|
it('loads user-level branded and generic files before project-level', async () => {
|
|
await mkdir(join(homeDir, '.kimi-code'), { recursive: true });
|
|
await writeFile(join(homeDir, '.kimi-code', 'AGENTS.md'), 'user branded', 'utf-8');
|
|
await mkdir(join(homeDir, '.agents'), { recursive: true });
|
|
await writeFile(join(homeDir, '.agents', 'AGENTS.md'), 'user generic', 'utf-8');
|
|
await writeFile(join(workDir, 'AGENTS.md'), 'project instructions', 'utf-8');
|
|
|
|
const result = await loadAgentsMd(testKaos);
|
|
|
|
expect(result).toContain('user branded');
|
|
expect(result).toContain('user generic');
|
|
expect(result).toContain('project instructions');
|
|
expect(result.indexOf('user branded')).toBeLessThan(result.indexOf('user generic'));
|
|
expect(result.indexOf('user generic')).toBeLessThan(result.indexOf('project instructions'));
|
|
});
|
|
|
|
it('loads generic user-level .agents/AGENTS.md', async () => {
|
|
await mkdir(join(homeDir, '.agents'), { recursive: true });
|
|
await writeFile(join(homeDir, '.agents', 'AGENTS.md'), 'dot-agents generic', 'utf-8');
|
|
|
|
const result = await loadAgentsMd(testKaos);
|
|
|
|
expect(result).toContain('dot-agents generic');
|
|
});
|
|
|
|
it('falls back to project-level only when no user-level files exist', async () => {
|
|
await writeFile(join(workDir, 'AGENTS.md'), 'project only', 'utf-8');
|
|
|
|
const result = await loadAgentsMd(testKaos);
|
|
|
|
expect(result).toContain('project only');
|
|
expect(result).not.toContain(homeDir);
|
|
});
|
|
|
|
it('does not load the same file twice when the work dir is the home dir', async () => {
|
|
vi.spyOn(testKaos, 'getcwd').mockReturnValue(homeDir);
|
|
await mkdir(join(homeDir, '.kimi-code'), { recursive: true });
|
|
await writeFile(join(homeDir, '.kimi-code', 'AGENTS.md'), 'home branded', 'utf-8');
|
|
|
|
const result = await loadAgentsMd(testKaos);
|
|
|
|
expect(result.split('home branded').length - 1).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe('loadAgentsMd brand home (KIMI_CODE_HOME)', () => {
|
|
let brandHome: string;
|
|
|
|
beforeEach(async () => {
|
|
brandHome = await mkdtemp(join(tmpdir(), 'kimi-agents-brand-'));
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await rm(brandHome, { recursive: true, force: true });
|
|
});
|
|
|
|
it('loads the branded AGENTS.md from the brand home and generic from the real home', async () => {
|
|
await writeFile(join(brandHome, 'AGENTS.md'), 'brand home instructions', 'utf-8');
|
|
await mkdir(join(homeDir, '.agents'), { recursive: true });
|
|
await writeFile(join(homeDir, '.agents', 'AGENTS.md'), 'real home generic', 'utf-8');
|
|
|
|
const result = await loadAgentsMd(testKaos, brandHome);
|
|
|
|
expect(result).toContain('brand home instructions');
|
|
expect(result).toContain('real home generic');
|
|
});
|
|
|
|
it('ignores the real-home .kimi-code/AGENTS.md when the brand home is elsewhere', async () => {
|
|
await writeFile(join(brandHome, 'AGENTS.md'), 'brand wins', 'utf-8');
|
|
await mkdir(join(homeDir, '.kimi-code'), { recursive: true });
|
|
await writeFile(join(homeDir, '.kimi-code', 'AGENTS.md'), 'stale real-home brand', 'utf-8');
|
|
|
|
const result = await loadAgentsMd(testKaos, brandHome);
|
|
|
|
expect(result).toContain('brand wins');
|
|
expect(result).not.toContain('stale real-home brand');
|
|
});
|
|
|
|
it('falls back to the real-home .kimi-code/AGENTS.md when no brand home is given', async () => {
|
|
await mkdir(join(homeDir, '.kimi-code'), { recursive: true });
|
|
await writeFile(join(homeDir, '.kimi-code', 'AGENTS.md'), 'fallback branded', 'utf-8');
|
|
|
|
const result = await loadAgentsMd(testKaos);
|
|
|
|
expect(result).toContain('fallback branded');
|
|
});
|
|
});
|
|
|
|
describe('loadAgentsMd oversized content', () => {
|
|
it('keeps the full content when AGENTS.md exceeds the recommended size', async () => {
|
|
const largeContent = 'x'.repeat(40 * 1024);
|
|
await writeFile(join(workDir, 'AGENTS.md'), largeContent, 'utf-8');
|
|
|
|
const result = await loadAgentsMd(testKaos);
|
|
|
|
expect(result).toContain(largeContent);
|
|
expect(result).not.toContain('truncated or omitted');
|
|
});
|
|
});
|
|
|
|
describe('prepareSystemPromptContext AGENTS.md size warning', () => {
|
|
it('returns agentsMdWarning and keeps full content when oversized', async () => {
|
|
const brandHome = await mkdtemp(join(tmpdir(), 'kimi-agents-brand-'));
|
|
extraDirs.push(brandHome);
|
|
const largeContent = 'x'.repeat(40 * 1024);
|
|
await writeFile(join(workDir, 'AGENTS.md'), largeContent, 'utf-8');
|
|
|
|
const result = await prepareSystemPromptContext(testKaos, brandHome);
|
|
|
|
expect(result.agentsMd).toContain(largeContent);
|
|
expect(result.agentsMdWarning).toBeDefined();
|
|
expect(result.agentsMdWarning).toContain('exceeds the recommended');
|
|
});
|
|
|
|
it('does not return agentsMdWarning when within the recommended size', async () => {
|
|
const brandHome = await mkdtemp(join(tmpdir(), 'kimi-agents-brand-'));
|
|
extraDirs.push(brandHome);
|
|
await writeFile(join(workDir, 'AGENTS.md'), 'small instructions', 'utf-8');
|
|
|
|
const result = await prepareSystemPromptContext(testKaos, brandHome);
|
|
|
|
expect(result.agentsMdWarning).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('prepareSystemPromptContext additional directories', () => {
|
|
it('includes additional directory listings without loading their AGENTS.md', async () => {
|
|
const brandHome = await mkdtemp(join(tmpdir(), 'kimi-agents-empty-brand-'));
|
|
extraDirs.push(brandHome);
|
|
const extraDir = await mkdtemp(join(tmpdir(), 'kimi-agents-extra-'));
|
|
extraDirs.push(extraDir);
|
|
|
|
await writeFile(join(workDir, 'AGENTS.md'), 'repo project instructions', 'utf-8');
|
|
await writeFile(join(extraDir, 'AGENTS.md'), 'extra project instructions', 'utf-8');
|
|
await writeFile(join(extraDir, 'extra-file.txt'), 'extra listing entry', 'utf-8');
|
|
|
|
const result = await prepareSystemPromptContext(testKaos, brandHome, {
|
|
additionalDirs: [extraDir],
|
|
});
|
|
|
|
const agentsMd = result.agentsMd ?? '';
|
|
|
|
expect(result.cwdListing).toBeTypeOf('string');
|
|
expect(result.additionalDirsInfo).toContain(`### ${extraDir}`);
|
|
expect(result.additionalDirsInfo).toContain('extra-file.txt');
|
|
expect(agentsMd).toContain('repo project instructions');
|
|
expect(agentsMd).not.toContain('extra project instructions');
|
|
expect(agentsMd.split('<!-- From:').length - 1).toBe(1);
|
|
});
|
|
|
|
it('loads user-level AGENTS.md once and skips additional directory AGENTS.md', async () => {
|
|
const brandHome = await mkdtemp(join(tmpdir(), 'kimi-agents-empty-brand-'));
|
|
extraDirs.push(brandHome);
|
|
const extraDirA = await mkdtemp(join(tmpdir(), 'kimi-agents-extra-a-'));
|
|
const extraDirB = await mkdtemp(join(tmpdir(), 'kimi-agents-extra-b-'));
|
|
extraDirs.push(extraDirA, extraDirB);
|
|
|
|
await mkdir(join(homeDir, '.agents'), { recursive: true });
|
|
await writeFile(join(homeDir, '.agents', 'AGENTS.md'), 'shared user instructions', 'utf-8');
|
|
await writeFile(join(extraDirA, 'AGENTS.md'), 'extra A instructions', 'utf-8');
|
|
await writeFile(join(extraDirB, 'AGENTS.md'), 'extra B instructions', 'utf-8');
|
|
|
|
const result = await prepareSystemPromptContext(testKaos, brandHome, {
|
|
additionalDirs: [extraDirA, extraDirB],
|
|
});
|
|
|
|
const agentsMd = result.agentsMd ?? '';
|
|
|
|
expect(result.additionalDirsInfo).toContain(`### ${extraDirA}`);
|
|
expect(result.additionalDirsInfo).toContain(`### ${extraDirB}`);
|
|
expect(agentsMd.split('shared user instructions').length - 1).toBe(1);
|
|
expect(agentsMd).not.toContain('extra A instructions');
|
|
expect(agentsMd).not.toContain('extra B instructions');
|
|
});
|
|
});
|