✨ 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:
parent
8609d59d4a
commit
a2e3cb07de
|
|
@ -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 规范部署到项目内,并用统一入口执行。
|
||||||
|
|
|
||||||
13
SKILLS.md
13
SKILLS.md
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,6 +1173,9 @@ 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():
|
||||||
|
if no_backup:
|
||||||
|
rmtree(dst)
|
||||||
|
else:
|
||||||
backup = skills_dst_root / f"{name}.bak.{timestamp}"
|
backup = skills_dst_root / f"{name}.bak.{timestamp}"
|
||||||
dst.rename(backup)
|
dst.rename(backup)
|
||||||
log(f"Backed up existing skill: {name} -> {backup.name}")
|
log(f"Backed up existing skill: {name} -> {backup.name}")
|
||||||
|
|
|
||||||
|
|
@ -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 -->
|
||||||
|
|
|
||||||
|
|
@ -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` 等)。
|
||||||
|
|
||||||
## 模板说明
|
## 模板说明
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,6 @@
|
||||||
|
|
||||||
<!-- 【必填】 -->
|
<!-- 【必填】 -->
|
||||||
|
|
||||||
**主语言**:{{MAIN_LANGUAGE}}
|
|
||||||
|
|
||||||
**文件类型**:{{FILE_TYPES}}
|
**文件类型**:{{FILE_TYPES}}
|
||||||
|
|
||||||
## 项目结构
|
## 项目结构
|
||||||
|
|
|
||||||
|
|
@ -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"""
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue