fix(context): AGENTS.md 注入仅取根目录,子目录文件只通知相对路径
- _load_agents_md_content 由 rglob 递归改为只读根目录 AGENTS.md - 新增一级子目录扫描:仅通知相对路径(最多 20 个、30s 超时兜底),不注入内容 - 跳过通用非源码目录(.git/node_modules/.venv/dist 等),不针对本项目特化 - 注入模板新增修改时间与子目录通知占位符
This commit is contained in:
parent
cedef87dab
commit
aac374b6f4
@ -88,6 +88,21 @@ logger = setup_logger(__name__)
|
||||
DISABLE_LENGTH_CHECK = True
|
||||
|
||||
|
||||
def _build_sub_agents_md_notice(sub_paths: List[str], total: int) -> str:
|
||||
"""构建子目录 AGENTS.md 路径通知文案(只列相对路径,不注入内容)。无子目录文件时返回空串。"""
|
||||
if not sub_paths:
|
||||
return ""
|
||||
lines = ["另外,工作区以下子目录也存在 AGENTS.md 规范文件:", ""]
|
||||
for p in sub_paths:
|
||||
lines.append(f"- `{p}`")
|
||||
if total > len(sub_paths):
|
||||
lines.append("")
|
||||
lines.append(f"...共 {total} 个")
|
||||
lines.append("")
|
||||
lines.append("这些子目录规范仅适用于其所在目录范围。若你的操作涉及上述目录,请先自行读取对应 AGENTS.md 了解局部规范后再动手,不得将子目录规范当作全局规范套用。")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
class MessagesMixin:
|
||||
"""MainTerminalContextMixin messages 能力 mixin。"""
|
||||
|
||||
@ -244,10 +259,18 @@ class MessagesMixin:
|
||||
agents_md_content = self._load_agents_md_content()
|
||||
if not agents_md_content:
|
||||
return ""
|
||||
# 子目录 AGENTS.md 只通知相对路径,不注入内容
|
||||
sub_paths, sub_total = self._load_sub_agents_md_paths()
|
||||
sub_notice = _build_sub_agents_md_notice(sub_paths, sub_total)
|
||||
agents_md_template = self.load_prompt("agents_md_inject").strip()
|
||||
if agents_md_template and "{{AGENTS_MD_CONTENT}}" in agents_md_template:
|
||||
return agents_md_template.replace("{{AGENTS_MD_CONTENT}}", agents_md_content)
|
||||
return f"【AGENTS.md 项目规范】\n\n{agents_md_content}\n\n---\n请注意:以上规范来自工作区根目录的 AGENTS.md 文件,若有冲突请以 AGENTS.md 为准。"
|
||||
text = agents_md_template.replace("{{AGENTS_MD_CONTENT}}", agents_md_content)
|
||||
text = text.replace("{{AGENTS_MD_UPDATED_AT}}", self._load_agents_md_updated_at())
|
||||
text = text.replace("{{SUB_AGENTS_MD_NOTICE}}", sub_notice)
|
||||
return text
|
||||
# 模板缺失/无占位符时的兜底格式
|
||||
text = f"【AGENTS.md 项目规范】{self._load_agents_md_updated_at()}\n\n{agents_md_content}\n\n---\n请注意:以上规范来自工作区根目录的 AGENTS.md 文件,若有冲突请以 AGENTS.md 为准。"
|
||||
return f"{text}\n\n{sub_notice}" if sub_notice else text
|
||||
|
||||
# AGENTS.md 注入开关可能变化,只在开启时生成并缓存,避免关闭时把空字符串冻住
|
||||
agents_md_inject_enabled = (
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import asyncio
|
||||
import json
|
||||
import time
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Set
|
||||
|
||||
@ -86,35 +88,72 @@ from config.model_profiles import (
|
||||
logger = setup_logger(__name__)
|
||||
DISABLE_LENGTH_CHECK = True
|
||||
|
||||
# 扫描一级子目录 AGENTS.md 时跳过的通用非源码目录(版本控制/依赖/构建产物/工具数据等,不针对本项目特化)
|
||||
_AGENTS_MD_SKIP_DIRS = {
|
||||
".git", ".hg", ".svn",
|
||||
"node_modules", "bower_components",
|
||||
".venv", "venv", "__pycache__",
|
||||
"dist", "build", ".next", ".nuxt", ".output",
|
||||
".astrion", ".claude", ".cursor", ".idea", ".vscode",
|
||||
}
|
||||
|
||||
|
||||
class PromptMixin:
|
||||
"""MainTerminalContextMixin prompt 能力 mixin。"""
|
||||
|
||||
def _load_agents_md_content(self) -> Optional[str]:
|
||||
"""加载工作区根目录的 AGENTS.md 文件内容。
|
||||
|
||||
如果存在多个 AGENTS.md 文件,返回最新更新的那一个。
|
||||
"""
|
||||
"""加载工作区根目录的 AGENTS.md 文件内容(仅根目录,子目录的不注入)。"""
|
||||
try:
|
||||
project_path = Path(self.project_path)
|
||||
if not project_path.exists():
|
||||
root_file = project_path / "AGENTS.md"
|
||||
if not root_file.is_file():
|
||||
return None
|
||||
|
||||
# 查找所有 AGENTS.md 文件
|
||||
agents_md_files = list(project_path.rglob("AGENTS.md"))
|
||||
if not agents_md_files:
|
||||
return None
|
||||
|
||||
# 找到最新更新的文件
|
||||
latest_file = max(agents_md_files, key=lambda p: p.stat().st_mtime)
|
||||
|
||||
# 读取文件内容
|
||||
content = latest_file.read_text(encoding='utf-8')
|
||||
content = root_file.read_text(encoding='utf-8')
|
||||
return content.strip() if content else None
|
||||
except Exception as exc:
|
||||
logger.warning(f"[AGENTS.md] 读取失败: {exc}")
|
||||
return None
|
||||
|
||||
def _load_agents_md_updated_at(self) -> str:
|
||||
"""返回根目录 AGENTS.md 的最后修改时间文案(如「(最后修改:2026-08-12 17:54)」,失败返回空串)。"""
|
||||
try:
|
||||
root_file = Path(self.project_path) / "AGENTS.md"
|
||||
if not root_file.is_file():
|
||||
return ""
|
||||
mtime = root_file.stat().st_mtime
|
||||
return f"(最后修改:{datetime.fromtimestamp(mtime).strftime('%Y-%m-%d %H:%M')})"
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def _load_sub_agents_md_paths(self, max_notice: int = 20, timeout_seconds: float = 30.0) -> tuple:
|
||||
"""扫描工作区一级子目录中的 AGENTS.md,返回 (相对路径列表, 实际总数)。
|
||||
|
||||
- 仅扫描根目录与一级子目录,不递归;跳过通用非源码目录(_AGENTS_MD_SKIP_DIRS)。
|
||||
- 列表最多 max_notice 个(超出截断,总数仍在第二项返回)。
|
||||
- 扫描超过 timeout_seconds 秒时停止,返回已扫描到的结果。
|
||||
"""
|
||||
found: List[str] = []
|
||||
total = 0
|
||||
try:
|
||||
project_path = Path(self.project_path)
|
||||
if not project_path.is_dir():
|
||||
return found, total
|
||||
start = time.monotonic()
|
||||
for sub in project_path.iterdir():
|
||||
if time.monotonic() - start >= timeout_seconds:
|
||||
logger.warning(f"[AGENTS.md] 子目录扫描超过 {timeout_seconds}s,返回已扫描到的 {len(found)} 个")
|
||||
break
|
||||
if not sub.is_dir() or sub.name in _AGENTS_MD_SKIP_DIRS:
|
||||
continue
|
||||
candidate = sub / "AGENTS.md"
|
||||
if candidate.is_file():
|
||||
total += 1
|
||||
if len(found) < max_notice:
|
||||
found.append(candidate.relative_to(project_path).as_posix())
|
||||
except Exception as exc:
|
||||
logger.warning(f"[AGENTS.md] 子目录扫描失败: {exc}")
|
||||
return found, total
|
||||
|
||||
def load_prompt(self, name: str) -> str:
|
||||
"""加载提示模板"""
|
||||
prompt_file = Path(PROMPTS_DIR) / f"{name}.txt"
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
【AGENTS.md 项目规范】
|
||||
|
||||
以下是工作区根目录 AGENTS.md 文件的内容,请在回答时参考这些项目规范:
|
||||
以下是工作区根目录 AGENTS.md 文件的内容{{AGENTS_MD_UPDATED_AT}},请在回答时参考这些项目规范:
|
||||
|
||||
{{AGENTS_MD_CONTENT}}
|
||||
|
||||
---
|
||||
注意:以上规范来自工作区根目录的 AGENTS.md 文件,若与未来代码冲突,以实际代码为准并及时更新 AGENTS.md。
|
||||
|
||||
{{SUB_AGENTS_MD_NOTICE}}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user