feat(playbook): add no_backup deploy controls

remove obsolete main_language placeholders and language summary template text

limit generated .agents guidance to configured langs and normalize AGENT_RULES trailing newlines

document install_skills no_backup behavior and refresh tests for deployment links and sync flows
This commit is contained in:
csh 2026-04-24 10:06:03 +08:00
parent 8609d59d4a
commit a2e3cb07de
12 changed files with 138 additions and 90 deletions

View File

@ -175,6 +175,8 @@ TSL 相关问题直接查阅 `rulesets/tsl/index.md` 与 `docs/tsl/`。
**安装与使用**:详见 `SKILLS.md` **安装与使用**:详见 `SKILLS.md`
如果你通过 `[install_skills]` 更新已经安装过的 skill默认会先把旧目录备份为 `*.bak.<timestamp>`;如果你明确希望“删除旧版本后直接重装”,可在 `playbook.toml``[install_skills]` 下设置 `no_backup = true`
## 在其他项目中使用本 Playbook ## 在其他项目中使用本 Playbook
由于本仓库需要内部权限访问,其他项目**不能仅用外链引用**;推荐把 Playbook 规范部署到项目内,并用统一入口执行。 由于本仓库需要内部权限访问,其他项目**不能仅用外链引用**;推荐把 Playbook 规范部署到项目内,并用统一入口执行。

View File

@ -75,10 +75,23 @@ skills = ["style-cleanup", "commit-message"]
[install_skills] [install_skills]
mode = "all" mode = "all"
agents_home = "./.agents" agents_home = "./.agents"
# no_backup = true
``` ```
> 注意Codex 默认从 `~/.agents/skills` 加载 skills使用本地安装时需要确保 Codex 能发现该路径。 > 注意Codex 默认从 `~/.agents/skills` 加载 skills使用本地安装时需要确保 Codex 能发现该路径。
`[install_skills]` 默认会先把已存在的 skill 目录重命名为 `*.bak.<timestamp>`,再复制新版本,便于手动回退。
如果你希望安装过程不保留备份,而是“先删除旧目录,再复制新目录”,可显式设置:
```toml
[install_skills]
mode = "all"
agents_home = "~/.agents"
no_backup = true
```
`no_backup = true` 适合 CI 或你已经用 Git 管理变更、只想要确定性覆盖安装的场景。
如果你的项目已经把本 Playbook 部署到项目内(无论来自 `git subtree`,还是外部 clone 后部署到自定义根目录),则在目标项目里执行: 如果你的项目已经把本 Playbook 部署到项目内(无论来自 `git subtree`,还是外部 clone 后部署到自定义根目录),则在目标项目里执行:
```bash ```bash

View File

@ -1,6 +1,6 @@
# TSL函数 # TSL函数
> 本文档从 [docs/tsl/syntax_book/function/index.md](../../docs/tsl/syntax_book/function/index.md) 拆分而来 > 本文档对应的正式检索入口见 [docs/tsl/reference/catalog/index.md](../../docs/tsl/reference/catalog/index.md)
TSL函数包含数学、系统、基础、图形等通用函数适用于各种TSL脚本开发场景。 TSL函数包含数学、系统、基础、图形等通用函数适用于各种TSL脚本开发场景。
@ -80,4 +80,4 @@ TSL函数包含数学、系统、基础、图形等通用函数适用于各
--- ---
**返回**: [docs/tsl/syntax_book/function/index.md](../../docs/tsl/syntax_book/function/index.md) **返回**: [docs/tsl/reference/catalog/index.md](../../docs/tsl/reference/catalog/index.md)

View File

@ -15,7 +15,6 @@
# force = false # 可选:覆盖已有文件 # force = false # 可选:覆盖已有文件
# no_backup = false # 可选:跳过备份 # no_backup = false # 可选:跳过备份
# date = "2026-04-22" # 可选:替换 {{DATE}} # date = "2026-04-22" # 可选:替换 {{DATE}}
# main_language = "tsl" # 可选:覆盖 {{MAIN_LANGUAGE}}
[sync_memory_bank] [sync_memory_bank]
# 同步 memory-bank/(配置节存在即启用) # 同步 memory-bank/(配置节存在即启用)
@ -25,7 +24,6 @@
# force = false # 可选:覆盖已有文件(会先备份) # force = false # 可选:覆盖已有文件(会先备份)
# no_backup = false # 可选:跳过备份 # no_backup = false # 可选:跳过备份
# date = "2026-04-22" # 可选:替换 {{DATE}} # date = "2026-04-22" # 可选:替换 {{DATE}}
# main_language = "tsl" # 可选:覆盖 {{MAIN_LANGUAGE}}
[sync_prompts] [sync_prompts]
# 同步 docs/prompts/(配置节存在即启用) # 同步 docs/prompts/(配置节存在即启用)
@ -33,7 +31,6 @@
# force = false # 可选:覆盖已有文件(会先备份) # force = false # 可选:覆盖已有文件(会先备份)
# no_backup = false # 可选:跳过备份 # no_backup = false # 可选:跳过备份
# date = "2026-04-22" # 可选:替换 {{DATE}} # date = "2026-04-22" # 可选:替换 {{DATE}}
# main_language = "tsl" # 可选:覆盖 {{MAIN_LANGUAGE}}
[sync_standards] [sync_standards]
# langs = ["tsl", "cpp", "typescript"] # 必填:要同步的语言 # langs = ["tsl", "cpp", "typescript"] # 必填:要同步的语言
@ -44,6 +41,7 @@
# mode = "list" # list|all # mode = "list" # list|all
# skills = ["brainstorming"] # mode=list 时必填 # skills = ["brainstorming"] # mode=list 时必填
# agents_home = "~/.agents" # 可选:默认 ~/.agents # agents_home = "~/.agents" # 可选:默认 ~/.agents
# no_backup = false # 可选:跳过备份,直接删除旧 skill 后重装
[format_md] [format_md]
# tool = "prettier" # 仅支持 prettier # tool = "prettier" # 仅支持 prettier

View File

@ -320,29 +320,7 @@ def resolve_docs_prefix(context: dict) -> str:
return join_deploy_subpath(resolve_deploy_root(context), "docs") return join_deploy_subpath(resolve_deploy_root(context), "docs")
def resolve_main_language(config: dict, context: dict) -> str: def resolve_playbook_scripts(context: dict) -> str:
raw = config.get("main_language")
if raw is not None and str(raw).strip():
return str(raw).strip()
full_config = context.get("config", {})
if isinstance(full_config, dict):
sync_conf = full_config.get("sync_standards")
if isinstance(sync_conf, dict):
langs_raw = sync_conf.get("langs")
if langs_raw is not None:
try:
langs = normalize_langs(langs_raw)
except ValueError:
langs = []
if langs:
return langs[0]
return "tsl"
def resolve_playbook_scripts(project_root: Path, context: dict) -> str:
_ = project_root
return join_deploy_subpath(resolve_deploy_root(context), "scripts") return join_deploy_subpath(resolve_deploy_root(context), "scripts")
@ -541,14 +519,11 @@ def replace_placeholders(
text: str, text: str,
project_name: str | None, project_name: str | None,
date_value: str, date_value: str,
main_language: str | None,
playbook_scripts: str | None, playbook_scripts: str | None,
) -> str: ) -> str:
result = text.replace("{{DATE}}", date_value) result = text.replace("{{DATE}}", date_value)
if project_name: if project_name:
result = result.replace("{{PROJECT_NAME}}", project_name) result = result.replace("{{PROJECT_NAME}}", project_name)
if main_language:
result = result.replace("{{MAIN_LANGUAGE}}", main_language)
if playbook_scripts: if playbook_scripts:
result = result.replace("{{PLAYBOOK_SCRIPTS}}", playbook_scripts) result = result.replace("{{PLAYBOOK_SCRIPTS}}", playbook_scripts)
return result return result
@ -563,41 +538,16 @@ def backup_path(path: Path, no_backup: bool) -> None:
log(f"Backed up: {path} -> {backup}") log(f"Backed up: {path} -> {backup}")
def rename_template_files(root: Path) -> None:
for template in root.rglob("*.template.md"):
target = template.with_name(template.name.replace(".template.md", ".md"))
template.rename(target)
def replace_placeholders_in_dir(
root: Path,
project_name: str | None,
date_value: str,
main_language: str | None,
playbook_scripts: str | None,
) -> None:
for file_path in root.rglob("*.md"):
text = file_path.read_text(encoding="utf-8")
updated = replace_placeholders(
text, project_name, date_value, main_language, playbook_scripts
)
if updated != text:
file_path.write_text(updated, encoding="utf-8")
def replace_placeholders_in_file( def replace_placeholders_in_file(
file_path: Path, file_path: Path,
project_name: str | None, project_name: str | None,
date_value: str, date_value: str,
main_language: str | None,
playbook_scripts: str | None, playbook_scripts: str | None,
) -> None: ) -> None:
if file_path.suffix != ".md": if file_path.suffix != ".md":
return return
text = file_path.read_text(encoding="utf-8") text = file_path.read_text(encoding="utf-8")
updated = replace_placeholders( updated = replace_placeholders(text, project_name, date_value, playbook_scripts)
text, project_name, date_value, main_language, playbook_scripts
)
if updated != text: if updated != text:
file_path.write_text(updated, encoding="utf-8") file_path.write_text(updated, encoding="utf-8")
@ -616,7 +566,6 @@ def sync_directory(
target_dir: Path, target_dir: Path,
project_name: str | None, project_name: str | None,
date_value: str, date_value: str,
main_language: str | None,
playbook_scripts: str | None, playbook_scripts: str | None,
force: bool, force: bool,
no_backup: bool, no_backup: bool,
@ -637,7 +586,6 @@ def sync_directory(
target_file, target_file,
project_name, project_name,
date_value, date_value,
main_language,
playbook_scripts, playbook_scripts,
) )
@ -665,12 +613,11 @@ def update_agents_section(
end_marker: str, end_marker: str,
project_name: str | None, project_name: str | None,
date_value: str, date_value: str,
main_language: str | None,
playbook_scripts: str | None, playbook_scripts: str | None,
) -> None: ) -> None:
template_text = template_path.read_text(encoding="utf-8") template_text = template_path.read_text(encoding="utf-8")
template_text = replace_placeholders( template_text = replace_placeholders(
template_text, project_name, date_value, main_language, playbook_scripts template_text, project_name, date_value, playbook_scripts
) )
block = extract_block_lines(template_text, start_marker, end_marker) block = extract_block_lines(template_text, start_marker, end_marker)
if not block: if not block:
@ -746,8 +693,7 @@ def sync_agents_template(context: dict) -> int:
return 0 return 0
project_name = resolve_project_name(context) project_name = resolve_project_name(context)
main_language = resolve_main_language({}, context) playbook_scripts = resolve_playbook_scripts(context)
playbook_scripts = resolve_playbook_scripts(project_root, context)
date_value = resolve_template_date(context) date_value = resolve_template_date(context)
agents_dst = project_root / "AGENTS.md" agents_dst = project_root / "AGENTS.md"
@ -773,7 +719,6 @@ def sync_agents_template(context: dict) -> int:
end_marker, end_marker,
project_name, project_name,
date_value, date_value,
main_language,
playbook_scripts, playbook_scripts,
) )
return 0 return 0
@ -805,17 +750,14 @@ def sync_rules_action(config: dict, context: dict) -> int:
return 0 return 0
project_name = resolve_project_name(context) project_name = resolve_project_name(context)
main_language = resolve_main_language(config, context) playbook_scripts = resolve_playbook_scripts(context)
playbook_scripts = resolve_playbook_scripts(project_root, context)
date_value = config.get("date") or datetime.now().strftime("%Y-%m-%d") date_value = config.get("date") or datetime.now().strftime("%Y-%m-%d")
no_backup = bool(config.get("no_backup", False)) no_backup = bool(config.get("no_backup", False))
backup_path(rules_dst, no_backup) backup_path(rules_dst, no_backup)
text = rules_src.read_text(encoding="utf-8") text = rules_src.read_text(encoding="utf-8")
text = replace_placeholders( text = replace_placeholders(text, project_name, date_value, playbook_scripts)
text, project_name, date_value, main_language, playbook_scripts rules_dst.write_text(text.rstrip("\n") + "\n", encoding="utf-8")
)
rules_dst.write_text(text + "\n", encoding="utf-8")
log("Synced: AGENT_RULES.md") log("Synced: AGENT_RULES.md")
return 0 return 0
@ -833,8 +775,7 @@ def sync_memory_bank_action(config: dict, context: dict) -> int:
return 2 return 2
project_name = config.get("project_name") project_name = config.get("project_name")
main_language = resolve_main_language(config, context) playbook_scripts = resolve_playbook_scripts(context)
playbook_scripts = resolve_playbook_scripts(project_root, context)
date_value = config.get("date") or datetime.now().strftime("%Y-%m-%d") date_value = config.get("date") or datetime.now().strftime("%Y-%m-%d")
force = bool(config.get("force", False)) force = bool(config.get("force", False))
no_backup = bool(config.get("no_backup", False)) no_backup = bool(config.get("no_backup", False))
@ -846,7 +787,6 @@ def sync_memory_bank_action(config: dict, context: dict) -> int:
memory_dst, memory_dst,
project_name, project_name,
date_value, date_value,
main_language,
playbook_scripts, playbook_scripts,
force, force,
no_backup, no_backup,
@ -868,8 +808,7 @@ def sync_prompts_action(config: dict, context: dict) -> int:
return 2 return 2
project_name = resolve_project_name(context) project_name = resolve_project_name(context)
main_language = resolve_main_language(config, context) playbook_scripts = resolve_playbook_scripts(context)
playbook_scripts = resolve_playbook_scripts(project_root, context)
date_value = config.get("date") or datetime.now().strftime("%Y-%m-%d") date_value = config.get("date") or datetime.now().strftime("%Y-%m-%d")
force = bool(config.get("force", False)) force = bool(config.get("force", False))
no_backup = bool(config.get("no_backup", False)) no_backup = bool(config.get("no_backup", False))
@ -882,7 +821,6 @@ def sync_prompts_action(config: dict, context: dict) -> int:
prompts_dst, prompts_dst,
project_name, project_name,
date_value, date_value,
main_language,
playbook_scripts, playbook_scripts,
force, force,
no_backup, no_backup,
@ -945,6 +883,13 @@ def update_agents_block(agents_md: Path, block_lines: list[str]) -> None:
def create_agents_index(agents_root: Path, langs: list[str], docs_prefix: str | None) -> None: def create_agents_index(agents_root: Path, langs: list[str], docs_prefix: str | None) -> None:
agents_index = agents_root / "index.md" agents_index = agents_root / "index.md"
lang_descriptions = {
"tsl": "TSL 相关规则集(由 playbook 同步;适用于 `.tsl`/`.tsf`",
"cpp": "C++ 相关规则集(由 playbook 同步;适用于 C++23/Modules",
"python": "Python 相关规则集(由 playbook 同步)",
"typescript": "TypeScript/JavaScript 相关规则集(由 playbook 同步)",
"markdown": "Markdown 相关规则集(仅代码格式化)",
}
lines = [ lines = [
"# .agents多语言", "# .agents多语言",
"", "",
@ -952,11 +897,11 @@ def create_agents_index(agents_root: Path, langs: list[str], docs_prefix: str |
"", "",
"建议约定:", "建议约定:",
"", "",
"- `.agents/tsl/`TSL 相关规则集(由 playbook 同步;适用于 `.tsl`/`.tsf`", ]
"- `.agents/cpp/`C++ 相关规则集(由 playbook 同步;适用于 C++23/Modules", for lang in langs:
"- `.agents/python/`Python 相关规则集(由 playbook 同步)", description = lang_descriptions.get(lang, "相关规则集(由 playbook 同步)")
"- `.agents/typescript/`TypeScript/JavaScript 相关规则集(由 playbook 同步)", lines.append(f"- `.agents/{lang}/`{description}")
"- `.agents/markdown/`Markdown 相关规则集(仅代码格式化)", lines += [
"", "",
"规则发生冲突时,建议以“更靠近代码的目录规则更具体”为准。", "规则发生冲突时,建议以“更靠近代码的目录规则更具体”为准。",
"", "",
@ -1220,6 +1165,7 @@ def install_skills_action(config: dict, context: dict) -> int:
return 2 return 2
timestamp = datetime.now().strftime("%Y%m%d%H%M%S") timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
no_backup = bool(config.get("no_backup", False))
for name in skills: for name in skills:
src = skills_src_root / name src = skills_src_root / name
if not src.is_dir(): if not src.is_dir():
@ -1227,9 +1173,12 @@ def install_skills_action(config: dict, context: dict) -> int:
return 2 return 2
dst = skills_dst_root / name dst = skills_dst_root / name
if dst.exists(): if dst.exists():
backup = skills_dst_root / f"{name}.bak.{timestamp}" if no_backup:
dst.rename(backup) rmtree(dst)
log(f"Backed up existing skill: {name} -> {backup.name}") else:
backup = skills_dst_root / f"{name}.bak.{timestamp}"
dst.rename(backup)
log(f"Backed up existing skill: {name} -> {backup.name}")
copytree(src, dst) copytree(src, dst)
rewrite_skill_docs_links(dst, resolve_docs_prefix(context)) rewrite_skill_docs_links(dst, resolve_docs_prefix(context))
log(f"Installed: {name}") log(f"Installed: {name}")

View File

@ -9,7 +9,7 @@
请以 `.agents/` 下的规则为准: 请以 `.agents/` 下的规则为准:
- 入口:`.agents/index.md` - 入口:`.agents/index.md`
- 语言规则:`.agents/{{MAIN_LANGUAGE}}/index.md` - 语言规则:`.agents/index.md` 与对应语言子目录
<!-- playbook:agents:end --> <!-- playbook:agents:end -->
<!-- playbook:templates:start --> <!-- playbook:templates:start -->

View File

@ -186,12 +186,10 @@ project/
| `{{PROJECT_NAME}}` | 项目名称 | ✅ 可选 | | `{{PROJECT_NAME}}` | 项目名称 | ✅ 可选 |
| `{{PROJECT_GOAL}}` | 项目目标 | ❌ 手动 | | `{{PROJECT_GOAL}}` | 项目目标 | ❌ 手动 |
| `{{PROJECT_DESCRIPTION}}` | 项目描述 | ❌ 手动 | | `{{PROJECT_DESCRIPTION}}` | 项目描述 | ❌ 手动 |
| `{{MAIN_LANGUAGE}}` | 主语言 | ✅ 可选 |
| `{{PLAYBOOK_SCRIPTS}}` | 脚本路径 | ✅ 是 | | `{{PLAYBOOK_SCRIPTS}}` | 脚本路径 | ✅ 是 |
| 其他 `{{...}}` | 项目特定内容 | ❌ 手动 | | 其他 `{{...}}` | 项目特定内容 | ❌ 手动 |
`{{PROJECT_NAME}}` 可通过 `sync_memory_bank.project_name` 自动替换;未配置时保持原样。 `{{PROJECT_NAME}}` 可通过 `sync_memory_bank.project_name` 自动替换;未配置时保持原样。
`{{MAIN_LANGUAGE}}` 可通过 `sync_standards.langs[0]` 自动替换;未配置时默认 `tsl`
`{{PLAYBOOK_SCRIPTS}}` 自动替换为 Playbook 脚本路径(默认 `docs/standards/playbook/scripts`,也可按项目配置改成 `custom/playbook/scripts` 等)。 `{{PLAYBOOK_SCRIPTS}}` 自动替换为 Playbook 脚本路径(默认 `docs/standards/playbook/scripts`,也可按项目配置改成 `custom/playbook/scripts` 等)。
## 模板说明 ## 模板说明

View File

@ -11,8 +11,6 @@
<!-- 【必填】 --> <!-- 【必填】 -->
**主语言**{{MAIN_LANGUAGE}}
**文件类型**{{FILE_TYPES}} **文件类型**{{FILE_TYPES}}
## 项目结构 ## 项目结构

View File

@ -237,6 +237,29 @@ no_backup = true
self.assertIn("`.agents/tsl/index.md`", agents_index) self.assertIn("`.agents/tsl/index.md`", agents_index)
self.assertIn("`.agents/cpp/index.md`", agents_index) self.assertIn("`.agents/cpp/index.md`", agents_index)
def test_sync_standards_agents_index_only_lists_configured_langs(self):
with tempfile.TemporaryDirectory() as tmp_dir:
root = Path(tmp_dir)
config_body = f"""
[playbook]
project_root = "{tmp_dir}"
deploy_root = "{CUSTOM_DEPLOY_ROOT}"
[sync_standards]
langs = ["tsl", "markdown"]
"""
config_path = write_config(root, "playbook.toml", config_body)
result = run_cli("-config", str(config_path))
self.assertEqual(result.returncode, 0, msg=result.stdout + result.stderr)
agents_index = (root / ".agents" / "index.md").read_text(encoding="utf-8")
self.assertIn("`.agents/tsl/`TSL 相关规则集", agents_index)
self.assertIn("`.agents/markdown/`Markdown 相关规则集", agents_index)
self.assertNotIn("`.agents/cpp/`", agents_index)
self.assertNotIn("`.agents/python/`", agents_index)
self.assertNotIn("`.agents/typescript/`", agents_index)
def test_sync_standards_agents_block_has_blank_lines(self): def test_sync_standards_agents_block_has_blank_lines(self):
with tempfile.TemporaryDirectory() as tmp_dir: with tempfile.TemporaryDirectory() as tmp_dir:
config_body = f""" config_body = f"""

View File

@ -74,6 +74,36 @@ no_backup = true
git_backups = list(root.glob(".gitattributes.bak.*")) git_backups = list(root.glob(".gitattributes.bak.*"))
self.assertEqual(git_backups, []) self.assertEqual(git_backups, [])
def test_install_skills_no_backup_replaces_existing_skill_without_backup(self):
with tempfile.TemporaryDirectory() as tmp_dir:
root = Path(tmp_dir)
skills_root = root / "agents" / "skills"
existing = skills_root / "brainstorming"
existing.mkdir(parents=True)
(existing / "stale.txt").write_text("old", encoding="utf-8")
config_body = f"""
[playbook]
project_root = "{tmp_dir}"
deploy_root = "{DEFAULT_DEPLOY_ROOT}"
[install_skills]
agents_home = "{root / 'agents'}"
mode = "list"
skills = ["brainstorming"]
no_backup = true
"""
config_path = root / "playbook.toml"
config_path.write_text(config_body, encoding="utf-8")
result = run_cli("-config", str(config_path))
self.assertEqual(result.returncode, 0, msg=result.stderr)
backups = list(skills_root.glob("brainstorming.bak.*"))
self.assertEqual(backups, [])
self.assertFalse((existing / "stale.txt").exists())
self.assertTrue((existing / "SKILL.md").is_file())
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@ -27,7 +27,29 @@ def run_script(script_path: Path, *args, cwd: Path | None = None):
class SyncTemplatesPlaceholdersTests(unittest.TestCase): class SyncTemplatesPlaceholdersTests(unittest.TestCase):
def test_main_language_placeholder_replaced(self): def test_templates_no_longer_expose_main_language_placeholder(self):
example_text = (ROOT / "playbook.toml.example").read_text(encoding="utf-8")
self.assertNotIn("main_language", example_text)
templates_readme = (ROOT / "templates" / "README.md").read_text(
encoding="utf-8"
)
self.assertNotIn("{{MAIN_LANGUAGE}}", templates_readme)
self.assertNotIn("{{LANGUAGE_1}}", templates_readme)
agents_template = (ROOT / "templates" / "AGENTS.template.md").read_text(
encoding="utf-8"
)
self.assertNotIn("{{MAIN_LANGUAGE}}", agents_template)
tech_stack_template = (
ROOT / "templates" / "memory-bank" / "tech-stack.template.md"
).read_text(encoding="utf-8")
self.assertNotIn("{{MAIN_LANGUAGE}}", tech_stack_template)
self.assertNotIn("{{LANGUAGE_1}}", tech_stack_template)
self.assertNotIn("**主要语言**", tech_stack_template)
def test_sync_templates_replaces_playbook_scripts_without_main_language_support(self):
with tempfile.TemporaryDirectory() as tmp_dir: with tempfile.TemporaryDirectory() as tmp_dir:
config_body = f""" config_body = f"""
[playbook] [playbook]
@ -36,6 +58,8 @@ deploy_root = "{DEFAULT_DEPLOY_ROOT}"
[sync_rules] [sync_rules]
[sync_memory_bank]
[sync_standards] [sync_standards]
langs = [\"cpp\", \"tsl\"] langs = [\"cpp\", \"tsl\"]
""" """
@ -50,6 +74,12 @@ langs = [\"cpp\", \"tsl\"]
self.assertIn(".agents/cpp/index.md", text) self.assertIn(".agents/cpp/index.md", text)
self.assertNotIn("{{MAIN_LANGUAGE}}", text) self.assertNotIn("{{MAIN_LANGUAGE}}", text)
tech_stack = Path(tmp_dir) / "memory-bank" / "tech-stack.md"
tech_stack_text = tech_stack.read_text(encoding="utf-8")
self.assertNotIn("{{LANGUAGE_1}}", tech_stack_text)
self.assertNotIn("{{MAIN_LANGUAGE}}", tech_stack_text)
self.assertNotIn("**主要语言**", tech_stack_text)
rules_md = Path(tmp_dir) / "AGENT_RULES.md" rules_md = Path(tmp_dir) / "AGENT_RULES.md"
rules_text = rules_md.read_text(encoding="utf-8") rules_text = rules_md.read_text(encoding="utf-8")
self.assertIn("docs/standards/playbook/scripts/main_loop.py claim", rules_text) self.assertIn("docs/standards/playbook/scripts/main_loop.py claim", rules_text)
@ -57,6 +87,7 @@ langs = [\"cpp\", \"tsl\"]
self.assertIn("不得直接使用 `$executing-plans`", rules_text) self.assertIn("不得直接使用 `$executing-plans`", rules_text)
self.assertIn("不得直接使用 `$subagent-driven-development`", rules_text) self.assertIn("不得直接使用 `$subagent-driven-development`", rules_text)
self.assertNotIn("{{PLAYBOOK_SCRIPTS}}", rules_text) self.assertNotIn("{{PLAYBOOK_SCRIPTS}}", rules_text)
self.assertFalse(rules_text.endswith("\n\n"))
def test_sync_standards_rewrites_typescript_docs_prefix_for_vendored_playbook(self): def test_sync_standards_rewrites_typescript_docs_prefix_for_vendored_playbook(self):
with tempfile.TemporaryDirectory() as tmp_dir: with tempfile.TemporaryDirectory() as tmp_dir:

View File

@ -8,6 +8,7 @@ PLAYBOOK_EXAMPLE = ROOT / "playbook.toml.example"
SKILLS_DOC = ROOT / "SKILLS.md" SKILLS_DOC = ROOT / "SKILLS.md"
TEMPLATES_CI_README = ROOT / "templates" / "ci" / "README.md" TEMPLATES_CI_README = ROOT / "templates" / "ci" / "README.md"
REMOVED_TSL_GUIDE = ROOT / "codex" / "skills" / "tsl-guide" REMOVED_TSL_GUIDE = ROOT / "codex" / "skills" / "tsl-guide"
REFERENCE_CATALOG_SOURCE_INDEX = ROOT / "data" / "tsl_reference_catalog_source" / "index.md"
class TslEntrypointsConsistencyTests(unittest.TestCase): class TslEntrypointsConsistencyTests(unittest.TestCase):
@ -51,6 +52,11 @@ class TslEntrypointsConsistencyTests(unittest.TestCase):
self.assertNotIn("tsl-guide", SKILLS_DOC.read_text(encoding="utf-8")) self.assertNotIn("tsl-guide", SKILLS_DOC.read_text(encoding="utf-8"))
self.assertNotIn("$tsl-guide", RULESET_TSL.read_text(encoding="utf-8")) self.assertNotIn("$tsl-guide", RULESET_TSL.read_text(encoding="utf-8"))
def test_reference_catalog_source_index_uses_canonical_reference_entrypoint(self):
text = REFERENCE_CATALOG_SOURCE_INDEX.read_text(encoding="utf-8")
self.assertIn("docs/tsl/reference/catalog/index.md", text)
self.assertNotIn("docs/tsl/syntax_book/function/index.md", text)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()