Skip to content
This repository was archived by the owner on Sep 23, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 29 additions & 4 deletions src/kimi_cli/soul/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,44 @@ class BuiltinSystemPromptArgs:
"""The content of AGENTS.md."""


async def load_agents_md(work_dir: KaosPath) -> str | None:
async def _load_agents_md_from_dir(dir_path: KaosPath) -> str | None:
"""Load AGENTS.md from a specific directory."""
paths = [
work_dir / "AGENTS.md",
work_dir / "agents.md",
dir_path / "AGENTS.md",
dir_path / "agents.md",
]
for path in paths:
if await path.is_file():
logger.info("Loaded agents.md: {path}", path=path)
return (await path.read_text()).strip()
logger.info("No AGENTS.md found in {work_dir}", work_dir=work_dir)
return None


async def load_agents_md(work_dir: KaosPath) -> str | None:
"""Load AGENTS.md from work_dir and global ~/.kimi directory.

Both project-level and global AGENTS.md are loaded and merged.
Project-level content comes first, followed by global content.
"""
global_kimi_dir = KaosPath(str(Path.home() / ".kimi"))

project_content, global_content = await asyncio.gather(
_load_agents_md_from_dir(work_dir),
_load_agents_md_from_dir(global_kimi_dir),
)

if project_content and global_content:
logger.info("Merging project and global AGENTS.md")
return f"{project_content}\n\n{global_content}"
elif project_content:
return project_content
elif global_content:
return global_content
else:
logger.info("No AGENTS.md found in {work_dir} or ~/.kimi", work_dir=work_dir)
return None


@dataclass(frozen=True, slots=True, kw_only=True)
class Runtime:
"""Agent runtime."""
Expand Down
61 changes: 55 additions & 6 deletions tests/test_load_agents_md.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,85 @@
from __future__ import annotations

from unittest.mock import patch

import pytest
from kaos.path import KaosPath

from kimi_cli.soul.agent import load_agents_md


@pytest.mark.asyncio
async def test_load_agents_md_found(temp_work_dir: KaosPath):
async def test_load_agents_md_found(temp_work_dir: KaosPath, tmp_path):
"""Test loading AGENTS.md when it exists."""
agents_md = temp_work_dir / "AGENTS.md"
await agents_md.write_text("Test agents content")

content = await load_agents_md(temp_work_dir)
with patch("kimi_cli.soul.agent.Path.home", return_value=tmp_path):
content = await load_agents_md(temp_work_dir)

assert content == "Test agents content"


@pytest.mark.asyncio
async def test_load_agents_md_not_found(temp_work_dir: KaosPath):
async def test_load_agents_md_not_found(temp_work_dir: KaosPath, tmp_path):
"""Test loading AGENTS.md when it doesn't exist."""
content = await load_agents_md(temp_work_dir)
with patch("kimi_cli.soul.agent.Path.home", return_value=tmp_path):
content = await load_agents_md(temp_work_dir)

assert content is None


@pytest.mark.asyncio
async def test_load_agents_md_lowercase(temp_work_dir: KaosPath):
async def test_load_agents_md_lowercase(temp_work_dir: KaosPath, tmp_path):
"""Test loading agents.md (lowercase)."""
agents_md = temp_work_dir / "agents.md"
await agents_md.write_text("Lowercase agents content")

content = await load_agents_md(temp_work_dir)
with patch("kimi_cli.soul.agent.Path.home", return_value=tmp_path):
content = await load_agents_md(temp_work_dir)

assert content == "Lowercase agents content"


@pytest.mark.asyncio
async def test_load_agents_md_global_only(temp_work_dir: KaosPath, tmp_path):
"""Test loading global AGENTS.md when project-level doesn't exist."""
global_kimi_dir = tmp_path / ".kimi"
global_kimi_dir.mkdir()
(global_kimi_dir / "AGENTS.md").write_text("Global agents content")

with patch("kimi_cli.soul.agent.Path.home", return_value=tmp_path):
content = await load_agents_md(temp_work_dir)

assert content == "Global agents content"


@pytest.mark.asyncio
async def test_load_agents_md_merge_project_and_global(temp_work_dir: KaosPath, tmp_path):
"""Test merging project-level and global AGENTS.md."""
# Create project-level AGENTS.md
agents_md = temp_work_dir / "AGENTS.md"
await agents_md.write_text("Project agents content")

# Create global AGENTS.md
global_kimi_dir = tmp_path / ".kimi"
global_kimi_dir.mkdir()
(global_kimi_dir / "AGENTS.md").write_text("Global agents content")

with patch("kimi_cli.soul.agent.Path.home", return_value=tmp_path):
content = await load_agents_md(temp_work_dir)

assert content == "Project agents content\n\nGlobal agents content"


@pytest.mark.asyncio
async def test_load_agents_md_global_lowercase(temp_work_dir: KaosPath, tmp_path):
"""Test loading global agents.md (lowercase)."""
global_kimi_dir = tmp_path / ".kimi"
global_kimi_dir.mkdir()
(global_kimi_dir / "agents.md").write_text("Global lowercase content")

with patch("kimi_cli.soul.agent.Path.home", return_value=tmp_path):
content = await load_agents_md(temp_work_dir)

assert content == "Global lowercase content"