playbook/tests/test_sync_templates_placeho...

362 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SCRIPT = ROOT / "scripts" / "playbook.py"
DEFAULT_DEPLOY_ROOT = "docs/standards/playbook"
def run_cli(*args):
return subprocess.run(
[sys.executable, str(SCRIPT), *args],
capture_output=True,
text=True,
)
def run_script(script_path: Path, *args, cwd: Path | None = None):
return subprocess.run(
[sys.executable, str(script_path), *args],
capture_output=True,
text=True,
cwd=str(cwd) if cwd else None,
)
class SyncTemplatesPlaceholdersTests(unittest.TestCase):
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)
self.assertNotIn("docs/workflows/", templates_readme)
self.assertNotIn("templates/workflows/", templates_readme)
self.assertIn("docs/superpowers/", templates_readme)
self.assertIn("docs/prompts/README.md", templates_readme)
self.assertIn("完整主链只在 `AGENT_RULES.template.md` 定义", templates_readme)
agents_template = (ROOT / "templates" / "AGENTS.template.md").read_text(
encoding="utf-8"
)
self.assertNotIn("{{MAIN_LANGUAGE}}", agents_template)
self.assertIn("AGENT_RULES.md", agents_template)
self.assertIn("docs/prompts/README.md", agents_template)
tech_context_template = (
ROOT / "templates" / "memory-bank" / "tech-context.template.md"
).read_text(encoding="utf-8")
self.assertNotIn("{{MAIN_LANGUAGE}}", tech_context_template)
self.assertNotIn("{{LANGUAGE_1}}", tech_context_template)
self.assertNotIn("**主要语言**", tech_context_template)
self.assertIn("## 不可假设项", tech_context_template)
project_brief_template = (
ROOT / "templates" / "memory-bank" / "project-brief.template.md"
).read_text(encoding="utf-8")
self.assertIn("## 成功定义", project_brief_template)
progress_template = (
ROOT / "templates" / "memory-bank" / "progress.template.md"
).read_text(encoding="utf-8")
self.assertIn("短期状态快照,不是 changelog", progress_template)
self.assertIn("`Recent Changes` 只保留最近 3-5 条", progress_template)
self.assertIn("整理/替换旧摘要,不做无限追加", progress_template)
active_context_template = (
ROOT / "templates" / "memory-bank" / "active-context.template.md"
).read_text(encoding="utf-8")
self.assertIn("短期上下文快照,不是长期日志", active_context_template)
self.assertIn("`Recent Changes` 只保留最近 3-5 条", active_context_template)
self.assertIn(
"`Touched Files` 只保留当前 Plan / 下一轮仍相关的文件",
active_context_template,
)
self.assertIn("整理/替换旧上下文,不做无限追加", active_context_template)
update_memory_template = (
ROOT / "templates" / "prompts" / "coding" / "update-memory.template.md"
).read_text(encoding="utf-8")
self.assertIn("workflow-state", update_memory_template)
self.assertIn("plan-status", update_memory_template)
self.assertIn("## Updated Files", update_memory_template)
self.assertIn("## New Context", update_memory_template)
self.assertIn("## Outstanding Risks", update_memory_template)
self.assertIn("AGENT_RULES.local.md", update_memory_template)
self.assertIn("短期状态快照", update_memory_template)
self.assertIn(
"不要把 `Recent Changes` 当作无限追加日志", update_memory_template
)
self.assertIn("短期上下文快照", update_memory_template)
self.assertIn(
"`Touched Files` 只保留当前 Plan / 下一轮仍相关的文件",
update_memory_template,
)
close_task_template = (
ROOT / "templates" / "prompts" / "coding" / "close-task.template.md"
).read_text(encoding="utf-8")
self.assertIn("main_loop.py finish", close_task_template)
self.assertIn("如本轮来自 `main_loop.py claim`", close_task_template)
self.assertIn("workflow-state.phase", close_task_template)
self.assertIn("未归档不得声明 Plan 完成", close_task_template)
self.assertIn("按项目归档机制只归档", close_task_template)
self.assertIn("状态已写回,交付未完成", close_task_template)
self.assertIn("## Completed", close_task_template)
self.assertIn("## Not Completed", close_task_template)
self.assertIn("## Verification", close_task_template)
self.assertIn("## Risks", close_task_template)
self.assertIn("## Next Steps", close_task_template)
verify_change_template = (
ROOT / "templates" / "prompts" / "coding" / "verify-change.template.md"
).read_text(encoding="utf-8")
self.assertIn("如本轮来自 `main_loop.py claim`", verify_change_template)
self.assertIn("workflow-state.phase", verify_change_template)
self.assertIn("plan-status", verify_change_template)
self.assertIn("验证通过不等于 Plan 完成", verify_change_template)
self.assertIn(
"当前 Plan 相关差异未归档/提交时,不得声明 Plan 完成",
verify_change_template,
)
self.assertIn("## Validated", verify_change_template)
self.assertIn("## Evidence", verify_change_template)
self.assertIn("## Not Validated", verify_change_template)
self.assertIn("## Risks", verify_change_template)
clarify_template = (
ROOT / "templates" / "prompts" / "coding" / "clarify.template.md"
).read_text(encoding="utf-8")
self.assertNotIn("Vibe-coding", clarify_template)
self.assertIn("## Current Understanding", clarify_template)
self.assertIn("## Open Question", clarify_template)
self.assertIn("## Recommended Default", clarify_template)
code_review_template = (
ROOT / "templates" / "prompts" / "coding" / "code-review.template.md"
).read_text(encoding="utf-8")
self.assertIn("## Findings", code_review_template)
self.assertIn("## Open Questions", code_review_template)
self.assertIn("## Residual Risk", code_review_template)
prompts_readme = (ROOT / "templates" / "prompts" / "README.md").read_text(
encoding="utf-8"
)
self.assertIn("AGENT_RULES.md", prompts_readme)
self.assertIn("docs/superpowers/", prompts_readme)
self.assertIn("不是流程权威", prompts_readme)
self.assertNotIn("playbook.py -record-spec", prompts_readme)
self.assertNotIn("playbook.py -record-plan", prompts_readme)
self.assertNotIn("using-superpowers", prompts_readme)
self.assertNotIn("main_loop.py claim", prompts_readme)
self.assertNotIn("docs/workflows/", prompts_readme)
self.assertIn("设计与计划产物", prompts_readme)
agent_behavior_template = (
ROOT / "templates" / "prompts" / "system" / "agent-behavior.template.md"
).read_text(encoding="utf-8")
self.assertIn("AGENT_RULES.md", agent_behavior_template)
self.assertIn("AGENT_RULES.local.md", agent_behavior_template)
self.assertIn("docs/superpowers/specs/", agent_behavior_template)
self.assertIn("docs/superpowers/plans/", agent_behavior_template)
self.assertNotIn("using-superpowers", agent_behavior_template)
self.assertNotIn("main_loop.py claim", agent_behavior_template)
self.assertNotIn("playbook.py -record-spec", agent_behavior_template)
self.assertNotIn("playbook.py -record-plan", agent_behavior_template)
self.assertIn(
"项目上下文与执行状态写入 `memory-bank/`", agent_behavior_template
)
rules_template = (ROOT / "templates" / "AGENT_RULES.template.md").read_text(
encoding="utf-8"
)
self.assertIn("唯一流程约束中心", rules_template)
self.assertIn("唯一设计与计划产物中心", rules_template)
self.assertIn("memory-bank/progress.md", rules_template)
self.assertIn("已有 `in-progress` 优先恢复", rules_template)
self.assertIn("按 Plan 文件顺序选择第一个可执行 Plan", rules_template)
self.assertIn(
"归档指按当前项目约定创建可回溯的交付记录",
rules_template,
)
self.assertIn("如项目使用 Git", rules_template)
self.assertIn("Git 提交commit", rules_template)
self.assertIn("不是仅更新 memory 或回复摘要", rules_template)
self.assertIn(
"`main_loop.py finish -status done` 只负责写回机器状态",
rules_template,
)
self.assertIn(
"Plan `done` 后必须完成当前 Plan 变更的归档/提交", rules_template
)
self.assertIn("Plan 范围是归档/提交边界", rules_template)
self.assertIn(
"不得由 `main_loop.py finish` 自动执行提交或变更归档",
rules_template,
)
self.assertIn("当前 Plan 文件", rules_template)
self.assertIn("`memory-bank/progress.md`", rules_template)
self.assertIn("必要 memory 更新", rules_template)
self.assertIn("允许存在其他 session 的未归档改动", rules_template)
self.assertIn("当前 Plan 无遗留差异", rules_template)
self.assertIn("状态已写回,交付未完成", rules_template)
self.assertNotIn("git status", rules_template)
self.assertNotIn("git commit", rules_template)
self.assertNotIn("git worktree", rules_template)
self.assertNotIn(".gitattributes", rules_template)
self.assertIn("`progress.md` 上半部分是短期状态快照", rules_template)
self.assertIn("`active-context.md` 是短期上下文快照", rules_template)
def test_sync_templates_replaces_playbook_scripts_without_main_language_support(
self,
):
with tempfile.TemporaryDirectory() as tmp_dir:
config_body = f"""
[playbook]
project_root = \"{tmp_dir}\"
deploy_root = "{DEFAULT_DEPLOY_ROOT}"
[sync_rules]
[sync_memory_bank]
[sync_standards]
langs = [\"cpp\", \"tsl\"]
"""
config_path = Path(tmp_dir) / "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)
agents_md = Path(tmp_dir) / "AGENTS.md"
text = agents_md.read_text(encoding="utf-8")
self.assertIn(".agents/cpp/index.md", text)
self.assertNotIn("{{MAIN_LANGUAGE}}", text)
tech_context = Path(tmp_dir) / "memory-bank" / "tech-context.md"
tech_context_text = tech_context.read_text(encoding="utf-8")
self.assertNotIn("{{LANGUAGE_1}}", tech_context_text)
self.assertNotIn("{{MAIN_LANGUAGE}}", tech_context_text)
self.assertNotIn("**主要语言**", tech_context_text)
rules_md = Path(tmp_dir) / "AGENT_RULES.md"
rules_text = rules_md.read_text(encoding="utf-8")
self.assertIn(
"docs/standards/playbook/scripts/main_loop.py claim",
rules_text,
)
self.assertIn("docs/superpowers/plans", rules_text)
self.assertNotIn("plan_progress.py", rules_text)
self.assertIn("记录 `phase=planning` 与 `spec=<path>`", rules_text)
self.assertIn(
"记录 `plan=<path>`、`executor=executing-plans`、",
rules_text,
)
self.assertIn("未领取 Plan 前,不得直接进入 `$executing-plans`", rules_text)
self.assertIn("默认执行使用 `$executing-plans`", rules_text)
self.assertIn("不是默认执行器", 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):
with tempfile.TemporaryDirectory() as tmp_dir:
root = Path(tmp_dir)
vendor_config = root / "vendor.toml"
vendor_config.write_text(
f"""
[playbook]
project_root = "{tmp_dir}"
deploy_root = "{DEFAULT_DEPLOY_ROOT}"
[vendor]
langs = ["typescript"]
""",
encoding="utf-8",
)
vendor_result = run_cli("-config", str(vendor_config))
self.assertEqual(vendor_result.returncode, 0, msg=vendor_result.stderr)
sync_config = root / "sync.toml"
sync_config.write_text(
f"""
[playbook]
project_root = "{tmp_dir}"
deploy_root = "{DEFAULT_DEPLOY_ROOT}"
[sync_standards]
langs = ["typescript"]
""",
encoding="utf-8",
)
vendored_script = (
root / "docs" / "standards" / "playbook" / "scripts" / "playbook.py"
)
sync_result = run_script(
vendored_script, "-config", str(sync_config), cwd=root
)
self.assertEqual(sync_result.returncode, 0, msg=sync_result.stderr)
agents_index = root / ".agents" / "typescript" / "index.md"
text = agents_index.read_text(encoding="utf-8")
self.assertIn("`docs/standards/playbook/docs/typescript/", text)
self.assertNotIn("`docs/typescript/", text)
def test_sync_memory_bank_includes_active_context_and_human_readable_progress(self):
with tempfile.TemporaryDirectory() as tmp_dir:
config_body = f"""
[playbook]
project_root = "{tmp_dir}"
deploy_root = "{DEFAULT_DEPLOY_ROOT}"
[sync_rules]
[sync_memory_bank]
project_name = "MyProject"
"""
config_path = Path(tmp_dir) / "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)
active_context = Path(tmp_dir) / "memory-bank" / "active-context.md"
self.assertTrue(active_context.is_file())
progress = Path(tmp_dir) / "memory-bank" / "progress.md"
progress_text = progress.read_text(encoding="utf-8")
self.assertIn("## Current Focus", progress_text)
self.assertIn("## 状态块示例", progress_text)
self.assertIn("phase: planning", progress_text)
self.assertIn("executor: executing-plans", progress_text)
self.assertIn("<!-- workflow-state:start -->", progress_text)
self.assertIn("<!-- workflow-state:end -->", progress_text)
self.assertIn("## Plan Status", progress_text)
self.assertIn("<!-- plan-status:start -->", progress_text)
self.assertIn("<!-- plan-status:end -->", progress_text)
system_patterns = Path(tmp_dir) / "memory-bank" / "system-patterns.md"
system_patterns_text = system_patterns.read_text(encoding="utf-8")
self.assertIn("# 系统模式与约束", system_patterns_text)
self.assertIn("## 核心不变量", system_patterns_text)
agents_md = Path(tmp_dir) / "AGENTS.md"
agents_text = agents_md.read_text(encoding="utf-8")
self.assertIn("memory-bank/active-context.md", agents_text)
rules_md = Path(tmp_dir) / "AGENT_RULES.md"
rules_text = rules_md.read_text(encoding="utf-8")
self.assertIn("memory-bank/active-context.md", rules_text)
if __name__ == "__main__":
unittest.main()