♻️ refactor(skills): rename repo skills source dir
move the repository skills source from `codex/skills` to `skills`. update vendor/install_skills paths, thirdparty sync pipeline, docs, and regression tests, including deployment-route e2e coverage.
This commit is contained in:
parent
f049dfbe10
commit
2c5050de09
|
|
@ -36,7 +36,7 @@ for entry in data["sources"]:
|
||||||
PY
|
PY
|
||||||
}
|
}
|
||||||
|
|
||||||
render_codex_skill() {
|
render_skill() {
|
||||||
local snapshot_root="$1"
|
local snapshot_root="$1"
|
||||||
local output_dir="$2"
|
local output_dir="$2"
|
||||||
local platform_config_rel="$3"
|
local platform_config_rel="$3"
|
||||||
|
|
@ -101,8 +101,8 @@ PY
|
||||||
|
|
||||||
tracked_skill_exists() {
|
tracked_skill_exists() {
|
||||||
local name="$1"
|
local name="$1"
|
||||||
# conflict only if a first-party SKILL.md exists directly under codex/skills/<name>/
|
# conflict only if a first-party SKILL.md exists directly under skills/<name>/
|
||||||
[ -f "codex/skills/$name/SKILL.md" ]
|
[ -f "skills/$name/SKILL.md" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -122,7 +122,7 @@ trap cleanup EXIT
|
||||||
|
|
||||||
git checkout -B "$TARGET_BRANCH" "origin/$TARGET_BRANCH"
|
git checkout -B "$TARGET_BRANCH" "origin/$TARGET_BRANCH"
|
||||||
|
|
||||||
mkdir -p "codex/skills/thirdparty/.sources"
|
mkdir -p "skills/thirdparty/.sources"
|
||||||
|
|
||||||
sources_file="$tmp_dir/sources.tsv"
|
sources_file="$tmp_dir/sources.tsv"
|
||||||
if ! emit_sources_tsv > "$sources_file"; then
|
if ! emit_sources_tsv > "$sources_file"; then
|
||||||
|
|
@ -136,7 +136,7 @@ while IFS=$'\x1f' read -r source_id snapshot_dir sync_mode source_list skills_su
|
||||||
if [ -f "$source_list" ]; then
|
if [ -f "$source_list" ]; then
|
||||||
while IFS= read -r name; do
|
while IFS= read -r name; do
|
||||||
[ -n "$name" ] || continue
|
[ -n "$name" ] || continue
|
||||||
rm -rf "codex/skills/$name"
|
rm -rf "skills/$name"
|
||||||
done < "$source_list"
|
done < "$source_list"
|
||||||
fi
|
fi
|
||||||
done < "$sources_file"
|
done < "$sources_file"
|
||||||
|
|
@ -169,13 +169,13 @@ while IFS=$'\x1f' read -r source_id snapshot_dir sync_mode source_list skills_su
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -rf "codex/skills/thirdparty/$name"
|
rm -rf "skills/thirdparty/$name"
|
||||||
cp -R "$dir" "codex/skills/thirdparty/$name"
|
cp -R "$dir" "skills/thirdparty/$name"
|
||||||
names+=("$name")
|
names+=("$name")
|
||||||
owners["$name"]="$source_id"
|
owners["$name"]="$source_id"
|
||||||
done
|
done
|
||||||
;;
|
;;
|
||||||
render_codex_skill)
|
render_skill)
|
||||||
name="$output_name"
|
name="$output_name"
|
||||||
if [ -n "${owners[$name]:-}" ] && [ "${owners[$name]}" != "$source_id" ]; then
|
if [ -n "${owners[$name]:-}" ] && [ "${owners[$name]}" != "$source_id" ]; then
|
||||||
echo "ERROR: duplicate third-party skill name: $name" >&2
|
echo "ERROR: duplicate third-party skill name: $name" >&2
|
||||||
|
|
@ -186,7 +186,7 @@ while IFS=$'\x1f' read -r source_id snapshot_dir sync_mode source_list skills_su
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
render_codex_skill "$snapshot_root" "codex/skills/thirdparty/$name" "$platform_config" "$template_root" "$data_dir" "$scripts_dir"
|
render_skill "$snapshot_root" "skills/thirdparty/$name" "$platform_config" "$template_root" "$data_dir" "$scripts_dir"
|
||||||
names+=("$name")
|
names+=("$name")
|
||||||
owners["$name"]="$source_id"
|
owners["$name"]="$source_id"
|
||||||
;;
|
;;
|
||||||
|
|
@ -199,7 +199,7 @@ while IFS=$'\x1f' read -r source_id snapshot_dir sync_mode source_list skills_su
|
||||||
printf "%s\n" "${names[@]}" | sort > "$source_list"
|
printf "%s\n" "${names[@]}" | sort > "$source_list"
|
||||||
done < "$sources_file"
|
done < "$sources_file"
|
||||||
|
|
||||||
git add codex/skills
|
git add skills
|
||||||
|
|
||||||
if git diff --cached --quiet; then
|
if git diff --cached --quiet; then
|
||||||
echo "No third-party skills to sync."
|
echo "No third-party skills to sync."
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
"upstream_ref": "main",
|
"upstream_ref": "main",
|
||||||
"snapshot_dir": "superpowers",
|
"snapshot_dir": "superpowers",
|
||||||
"sync_mode": "copy_skill_dirs",
|
"sync_mode": "copy_skill_dirs",
|
||||||
"source_list": "codex/skills/thirdparty/.sources/superpowers.list",
|
"source_list": "skills/thirdparty/.sources/superpowers.list",
|
||||||
"skills_subdir": "skills",
|
"skills_subdir": "skills",
|
||||||
"remove_paths": ["skills/ui-ux-pro-max"]
|
"remove_paths": ["skills/ui-ux-pro-max"]
|
||||||
},
|
},
|
||||||
|
|
@ -15,8 +15,8 @@
|
||||||
"upstream_repo": "https://github.com/nextlevelbuilder/ui-ux-pro-max-skill.git",
|
"upstream_repo": "https://github.com/nextlevelbuilder/ui-ux-pro-max-skill.git",
|
||||||
"upstream_ref": "main",
|
"upstream_ref": "main",
|
||||||
"snapshot_dir": "ui-ux-pro-max",
|
"snapshot_dir": "ui-ux-pro-max",
|
||||||
"sync_mode": "render_codex_skill",
|
"sync_mode": "render_skill",
|
||||||
"source_list": "codex/skills/thirdparty/.sources/ui-ux-pro-max.list",
|
"source_list": "skills/thirdparty/.sources/ui-ux-pro-max.list",
|
||||||
"output_name": "ui-ux-pro-max",
|
"output_name": "ui-ux-pro-max",
|
||||||
"platform_config": "src/ui-ux-pro-max/templates/platforms/codex.json",
|
"platform_config": "src/ui-ux-pro-max/templates/platforms/codex.json",
|
||||||
"template_root": "src/ui-ux-pro-max/templates",
|
"template_root": "src/ui-ux-pro-max/templates",
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
"upstream_ref": "main",
|
"upstream_ref": "main",
|
||||||
"snapshot_dir": "andrej-karpathy-skills",
|
"snapshot_dir": "andrej-karpathy-skills",
|
||||||
"sync_mode": "copy_skill_dirs",
|
"sync_mode": "copy_skill_dirs",
|
||||||
"source_list": "codex/skills/thirdparty/.sources/andrej-karpathy-skills.list",
|
"source_list": "skills/thirdparty/.sources/andrej-karpathy-skills.list",
|
||||||
"skills_subdir": "skills"
|
"skills_subdir": "skills"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
node_modules
|
node_modules
|
||||||
tmp
|
tmp
|
||||||
reports
|
reports
|
||||||
codex/skills/thirdparty
|
skills/thirdparty
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
codex/skills/**
|
skills/**
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ Layer 1: rulesets/ (≤50 行/语言,模板源)
|
||||||
├─ 核心约束与安全红线
|
├─ 核心约束与安全红线
|
||||||
└─ 指向 Skills 和 docs
|
└─ 指向 Skills 和 docs
|
||||||
|
|
||||||
Layer 2: codex/skills/ (按需加载,$skill-name 触发)
|
Layer 2: skills/ (按需加载,$skill-name 触发)
|
||||||
├─ commit-message: 提交信息规范
|
├─ commit-message: 提交信息规范
|
||||||
├─ style-cleanup: 代码风格整理
|
├─ style-cleanup: 代码风格整理
|
||||||
├─ bulk-refactor-workflow: 批量重构流程
|
├─ bulk-refactor-workflow: 批量重构流程
|
||||||
|
|
@ -158,7 +158,7 @@ Layer 3: docs/ (权威静态文档)
|
||||||
|
|
||||||
## SKILLS(Codex CLI / Claude Code)
|
## SKILLS(Codex CLI / Claude Code)
|
||||||
|
|
||||||
本仓库内置一组 AI agent skills(见 `codex/skills/`),支持 Codex CLI 和 Claude Code,用于按需加载的工作流与知识库。
|
本仓库内置一组 AI agent skills(见 `skills/`),支持 Codex CLI 和 Claude Code,用于按需加载的工作流与知识库。
|
||||||
|
|
||||||
TSL 相关问题直接查阅 `rulesets/tsl/index.md` 与 `docs/tsl/`。
|
TSL 相关问题直接查阅 `rulesets/tsl/index.md` 与 `docs/tsl/`。
|
||||||
|
|
||||||
|
|
|
||||||
10
SKILLS.md
10
SKILLS.md
|
|
@ -10,7 +10,7 @@
|
||||||
| Codex CLI | `~/.agents/skills/` |
|
| Codex CLI | `~/.agents/skills/` |
|
||||||
| Claude Code | `~/.claude/skills/` |
|
| Claude Code | `~/.claude/skills/` |
|
||||||
|
|
||||||
> 提示:本仓库将 skills 以可分发的形式放在 `codex/skills/`,并提供脚本一键安装到对应平台目录。
|
> 提示:本仓库将 skills 以可分发的形式放在 `skills/`,并提供脚本一键安装到对应平台目录。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -40,7 +40,7 @@ skills = true
|
||||||
本 Playbook 以“可分发、可安装”的方式提供 skills:
|
本 Playbook 以“可分发、可安装”的方式提供 skills:
|
||||||
|
|
||||||
```txt
|
```txt
|
||||||
codex/skills/
|
skills/
|
||||||
<skill-name>/ # 本仓库自维护
|
<skill-name>/ # 本仓库自维护
|
||||||
SKILL.md
|
SKILL.md
|
||||||
references/ # 可选:拆分参考文档
|
references/ # 可选:拆分参考文档
|
||||||
|
|
@ -164,7 +164,7 @@ python <deploy_root>/scripts/playbook.py -config playbook.toml
|
||||||
|
|
||||||
## 8. 本 Playbook 原生 skills
|
## 8. 本 Playbook 原生 skills
|
||||||
|
|
||||||
位于 `codex/skills/`(Playbook 自维护部分),当前共 3 个。
|
位于 `skills/`(Playbook 自维护部分),当前共 3 个。
|
||||||
第三方 skills 来源见第 9 节。
|
第三方 skills 来源见第 9 节。
|
||||||
|
|
||||||
### 语言特定 Skills
|
### 语言特定 Skills
|
||||||
|
|
@ -181,7 +181,7 @@ python <deploy_root>/scripts/playbook.py -config playbook.toml
|
||||||
|
|
||||||
## 9. Third-party Skills
|
## 9. Third-party Skills
|
||||||
|
|
||||||
来源:`codex/skills/thirdparty/.sources/`(第三方来源清单目录)。
|
来源:`skills/thirdparty/.sources/`(第三方来源清单目录)。
|
||||||
|
|
||||||
- `superpowers.list`
|
- `superpowers.list`
|
||||||
- `ui-ux-pro-max.list`
|
- `ui-ux-pro-max.list`
|
||||||
|
|
@ -190,7 +190,7 @@ python <deploy_root>/scripts/playbook.py -config playbook.toml
|
||||||
部署链路:
|
部署链路:
|
||||||
|
|
||||||
- `thirdparty/skill` 分支保存上游快照 `andrej-karpathy-skills/`
|
- `thirdparty/skill` 分支保存上游快照 `andrej-karpathy-skills/`
|
||||||
- 自动同步后,`skills/karpathy-guidelines/` 会落到 `codex/skills/karpathy-guidelines/`
|
- 自动同步后,`karpathy-guidelines/` 会落到仓库内 `skills/thirdparty/karpathy-guidelines/`
|
||||||
- 运行 `[install_skills]` 时,再复制到目标平台 skills 目录(`~/.agents/skills/` 或 `~/.claude/skills/`)
|
- 运行 `[install_skills]` 时,再复制到目标平台 skills 目录(`~/.agents/skills/` 或 `~/.claude/skills/`)
|
||||||
- 该 skill 本身不依赖 Playbook 文档路径重写,也不需要像 `ui-ux-pro-max` 那样额外渲染
|
- 该 skill 本身不依赖 Playbook 文档路径重写,也不需要像 `ui-ux-pro-max` 那样额外渲染
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -477,7 +477,7 @@ def vendor_action(config: dict, context: dict) -> int:
|
||||||
copy2(gitattributes_src, dest_prefix / ".gitattributes")
|
copy2(gitattributes_src, dest_prefix / ".gitattributes")
|
||||||
|
|
||||||
copytree(PLAYBOOK_ROOT / "scripts", dest_prefix / "scripts")
|
copytree(PLAYBOOK_ROOT / "scripts", dest_prefix / "scripts")
|
||||||
copytree(PLAYBOOK_ROOT / "codex", dest_prefix / "codex")
|
copytree(PLAYBOOK_ROOT / "skills", dest_prefix / "skills")
|
||||||
copy2(PLAYBOOK_ROOT / "SKILLS.md", dest_prefix / "SKILLS.md")
|
copy2(PLAYBOOK_ROOT / "SKILLS.md", dest_prefix / "SKILLS.md")
|
||||||
|
|
||||||
common_docs = PLAYBOOK_ROOT / "docs/common"
|
common_docs = PLAYBOOK_ROOT / "docs/common"
|
||||||
|
|
@ -1253,7 +1253,7 @@ def install_skills_action(config: dict, context: dict) -> int:
|
||||||
if not agents_home.is_absolute():
|
if not agents_home.is_absolute():
|
||||||
agents_home = (context["project_root"] / agents_home).resolve()
|
agents_home = (context["project_root"] / agents_home).resolve()
|
||||||
|
|
||||||
skills_src_root = PLAYBOOK_ROOT / "codex/skills"
|
skills_src_root = PLAYBOOK_ROOT / "skills"
|
||||||
skills_thirdparty_root = skills_src_root / "thirdparty"
|
skills_thirdparty_root = skills_src_root / "thirdparty"
|
||||||
if not skills_src_root.is_dir():
|
if not skills_src_root.is_dir():
|
||||||
print(f"ERROR: skills source not found: {skills_src_root}", file=sys.stderr)
|
print(f"ERROR: skills source not found: {skills_src_root}", file=sys.stderr)
|
||||||
|
|
|
||||||
|
Can't render this file because it contains an unexpected character in line 10 and column 56.
|
|
Can't render this file because it contains an unexpected character in line 13 and column 56.
|
|
Can't render this file because it is too large.
|
|
Can't render this file because it contains an unexpected character in line 2 and column 134.
|
|
Can't render this file because it contains an unexpected character in line 2 and column 209.
|
|
Can't render this file because it contains an unexpected character in line 6 and column 94.
|
|
Can't render this file because it contains an unexpected character in line 8 and column 193.
|
|
Can't render this file because it contains an unexpected character in line 4 and column 188.
|
|
|
@ -325,7 +325,7 @@ python scripts/playbook.py -config playbook.toml
|
||||||
```text
|
```text
|
||||||
playbook/
|
playbook/
|
||||||
├── rulesets/ # 语言级硬规则 → 部署到 .agents/
|
├── rulesets/ # 语言级硬规则 → 部署到 .agents/
|
||||||
├── codex/skills/ # 按需加载的技能(thirdparty/ 为第三方同步)
|
├── skills/ # 按需加载的技能(thirdparty/ 为第三方同步)
|
||||||
├── docs/ # 权威静态文档
|
├── docs/ # 权威静态文档
|
||||||
├── templates/ # 本目录:项目架构模板 → 部署到 memory-bank/ 等
|
├── templates/ # 本目录:项目架构模板 → 部署到 memory-bank/ 等
|
||||||
└── scripts/
|
└── scripts/
|
||||||
|
|
|
||||||
|
|
@ -397,6 +397,9 @@ skills = ["brainstorming"]
|
||||||
manifest_src = ROOT / ".gitea" / "ci" / "thirdparty_skills.json"
|
manifest_src = ROOT / ".gitea" / "ci" / "thirdparty_skills.json"
|
||||||
manifest_dst = repo / ".gitea" / "ci" / "thirdparty_skills.json"
|
manifest_dst = repo / ".gitea" / "ci" / "thirdparty_skills.json"
|
||||||
manifest_dst.write_text(manifest_src.read_text(encoding="utf-8"), encoding="utf-8")
|
manifest_dst.write_text(manifest_src.read_text(encoding="utf-8"), encoding="utf-8")
|
||||||
|
sync_src = ROOT / ".gitea" / "ci" / "sync_thirdparty_skills.sh"
|
||||||
|
sync_dst = repo / ".gitea" / "ci" / "sync_thirdparty_skills.sh"
|
||||||
|
sync_dst.write_text(sync_src.read_text(encoding="utf-8"), encoding="utf-8")
|
||||||
|
|
||||||
sync_result = subprocess.run(
|
sync_result = subprocess.run(
|
||||||
["bash", ".gitea/ci/sync_thirdparty_skills.sh"],
|
["bash", ".gitea/ci/sync_thirdparty_skills.sh"],
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,185 @@
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
DEFAULT_DEPLOY_ROOT = Path("docs/standards/playbook")
|
||||||
|
CUSTOM_DEPLOY_ROOT = Path("custom/playbook")
|
||||||
|
REPO_COPY_IGNORE = shutil.ignore_patterns(
|
||||||
|
".git",
|
||||||
|
".venv",
|
||||||
|
"node_modules",
|
||||||
|
"__pycache__",
|
||||||
|
".pytest_cache",
|
||||||
|
".mypy_cache",
|
||||||
|
".ruff_cache",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def run_script(
|
||||||
|
script: Path, *args: str, cwd: Path | None = None
|
||||||
|
) -> subprocess.CompletedProcess[str]:
|
||||||
|
return subprocess.run(
|
||||||
|
[sys.executable, str(script), *args],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
cwd=cwd,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def write_config(root: Path, name: str, body: str) -> Path:
|
||||||
|
config_path = root / name
|
||||||
|
config_path.write_text(body.strip() + "\n", encoding="utf-8")
|
||||||
|
return config_path
|
||||||
|
|
||||||
|
|
||||||
|
def copy_repo(target: Path) -> Path:
|
||||||
|
shutil.copytree(ROOT, target, ignore=REPO_COPY_IGNORE)
|
||||||
|
return target
|
||||||
|
|
||||||
|
|
||||||
|
class DeploymentRoutesE2ETests(unittest.TestCase):
|
||||||
|
def assert_core_project_files(self, project_root: Path) -> None:
|
||||||
|
expected = [
|
||||||
|
"AGENTS.md",
|
||||||
|
"AGENT_RULES.md",
|
||||||
|
"AGENT_RULES.local.md",
|
||||||
|
"memory-bank/progress.md",
|
||||||
|
"docs/prompts/system/agent-behavior.md",
|
||||||
|
".agents/index.md",
|
||||||
|
".agents/tsl/index.md",
|
||||||
|
".agents/markdown/index.md",
|
||||||
|
".gitattributes",
|
||||||
|
]
|
||||||
|
for rel in expected:
|
||||||
|
with self.subTest(path=rel):
|
||||||
|
self.assertTrue((project_root / rel).exists(), f"missing: {rel}")
|
||||||
|
|
||||||
|
def assert_docs_prefix(self, project_root: Path, docs_prefix: str) -> None:
|
||||||
|
agents_index = (project_root / ".agents" / "index.md").read_text(encoding="utf-8")
|
||||||
|
self.assertIn("`.agents/tsl/index.md`", agents_index)
|
||||||
|
self.assertIn("`.agents/markdown/index.md`", agents_index)
|
||||||
|
self.assertIn(f"- {docs_prefix}", agents_index)
|
||||||
|
|
||||||
|
tsl_index = (project_root / ".agents" / "tsl" / "index.md").read_text(
|
||||||
|
encoding="utf-8"
|
||||||
|
)
|
||||||
|
self.assertIn(f"`{docs_prefix}/tsl/index.md`", tsl_index)
|
||||||
|
self.assertNotIn("`docs/tsl/index.md`", tsl_index)
|
||||||
|
|
||||||
|
def test_subtree_style_deployment_syncs_project_files(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||||
|
tmp_root = Path(tmp_dir)
|
||||||
|
project_root = tmp_root / "project"
|
||||||
|
playbook_root = project_root / DEFAULT_DEPLOY_ROOT
|
||||||
|
project_root.mkdir()
|
||||||
|
copy_repo(playbook_root)
|
||||||
|
|
||||||
|
config_path = write_config(
|
||||||
|
project_root,
|
||||||
|
"playbook.toml",
|
||||||
|
"""
|
||||||
|
[playbook]
|
||||||
|
project_root = "."
|
||||||
|
|
||||||
|
[sync_rules]
|
||||||
|
no_backup = true
|
||||||
|
|
||||||
|
[sync_memory_bank]
|
||||||
|
project_name = "Demo"
|
||||||
|
|
||||||
|
[sync_prompts]
|
||||||
|
no_backup = true
|
||||||
|
|
||||||
|
[sync_standards]
|
||||||
|
langs = ["tsl", "markdown"]
|
||||||
|
no_backup = true
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
result = run_script(
|
||||||
|
playbook_root / "scripts" / "playbook.py",
|
||||||
|
"-config",
|
||||||
|
str(config_path),
|
||||||
|
cwd=project_root,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(result.returncode, 0, msg=result.stdout + result.stderr)
|
||||||
|
self.assert_core_project_files(project_root)
|
||||||
|
self.assert_docs_prefix(project_root, "docs/standards/playbook/docs")
|
||||||
|
|
||||||
|
claude_md = project_root / "CLAUDE.md"
|
||||||
|
self.assertTrue(claude_md.is_file())
|
||||||
|
text = claude_md.read_text(encoding="utf-8")
|
||||||
|
self.assertIn("@AGENTS.md", text)
|
||||||
|
self.assertIn("@AGENT_RULES.md", text)
|
||||||
|
self.assertIn("<!-- playbook:claude:start -->", text)
|
||||||
|
|
||||||
|
def test_external_clone_deployment_vendors_snapshot_and_updates_claude(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||||
|
tmp_root = Path(tmp_dir)
|
||||||
|
external_clone = tmp_root / "playbook"
|
||||||
|
project_root = tmp_root / "project"
|
||||||
|
copy_repo(external_clone)
|
||||||
|
project_root.mkdir()
|
||||||
|
|
||||||
|
claude_md = project_root / ".claude" / "CLAUDE.md"
|
||||||
|
claude_md.parent.mkdir()
|
||||||
|
claude_md.write_text("# Existing Claude\n\nKeep this.\n", encoding="utf-8")
|
||||||
|
|
||||||
|
config_path = write_config(
|
||||||
|
project_root,
|
||||||
|
"playbook.toml",
|
||||||
|
f"""
|
||||||
|
[playbook]
|
||||||
|
project_root = "."
|
||||||
|
deploy_root = "{CUSTOM_DEPLOY_ROOT.as_posix()}"
|
||||||
|
|
||||||
|
[vendor]
|
||||||
|
langs = ["tsl", "markdown"]
|
||||||
|
|
||||||
|
[sync_rules]
|
||||||
|
no_backup = true
|
||||||
|
|
||||||
|
[sync_memory_bank]
|
||||||
|
project_name = "Demo"
|
||||||
|
|
||||||
|
[sync_prompts]
|
||||||
|
no_backup = true
|
||||||
|
|
||||||
|
[sync_standards]
|
||||||
|
langs = ["tsl", "markdown"]
|
||||||
|
no_backup = true
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
result = run_script(
|
||||||
|
external_clone / "scripts" / "playbook.py",
|
||||||
|
"-config",
|
||||||
|
str(config_path),
|
||||||
|
cwd=project_root,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(result.returncode, 0, msg=result.stdout + result.stderr)
|
||||||
|
|
||||||
|
snapshot_root = project_root / CUSTOM_DEPLOY_ROOT
|
||||||
|
self.assertTrue((snapshot_root / "SOURCE.md").is_file())
|
||||||
|
self.assertTrue((snapshot_root / "scripts" / "playbook.py").is_file())
|
||||||
|
self.assertTrue((snapshot_root / "templates" / "README.md").is_file())
|
||||||
|
|
||||||
|
self.assert_core_project_files(project_root)
|
||||||
|
self.assert_docs_prefix(project_root, "custom/playbook/docs")
|
||||||
|
|
||||||
|
text = claude_md.read_text(encoding="utf-8")
|
||||||
|
self.assertIn("Keep this.", text)
|
||||||
|
self.assertIn("@../AGENTS.md", text)
|
||||||
|
self.assertIn("@../AGENT_RULES.md", text)
|
||||||
|
self.assertEqual(text.count("<!-- playbook:claude:start -->"), 1)
|
||||||
|
self.assertFalse((project_root / "CLAUDE.md").exists())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
|
|
@ -14,9 +14,9 @@ LEGACY_WORKFLOW = ROOT / ".gitea" / "workflows" / "update-thirdparty-superpowers
|
||||||
UPDATE_SCRIPT = ROOT / ".gitea" / "ci" / "update_thirdparty_skills.sh"
|
UPDATE_SCRIPT = ROOT / ".gitea" / "ci" / "update_thirdparty_skills.sh"
|
||||||
SYNC_SCRIPT = ROOT / ".gitea" / "ci" / "sync_thirdparty_skills.sh"
|
SYNC_SCRIPT = ROOT / ".gitea" / "ci" / "sync_thirdparty_skills.sh"
|
||||||
SKILLS_MD = ROOT / "SKILLS.md"
|
SKILLS_MD = ROOT / "SKILLS.md"
|
||||||
SUPERPOWERS_LIST = ROOT / "codex" / "skills" / "thirdparty" / ".sources" / "superpowers.list"
|
SUPERPOWERS_LIST = ROOT / "skills" / "thirdparty" / ".sources" / "superpowers.list"
|
||||||
UI_UX_PRO_MAX_LIST = ROOT / "codex" / "skills" / "thirdparty" / ".sources" / "ui-ux-pro-max.list"
|
UI_UX_PRO_MAX_LIST = ROOT / "skills" / "thirdparty" / ".sources" / "ui-ux-pro-max.list"
|
||||||
UI_UX_PRO_MAX_DIR = ROOT / "codex" / "skills" / "thirdparty" / "ui-ux-pro-max"
|
UI_UX_PRO_MAX_DIR = ROOT / "skills" / "thirdparty" / "ui-ux-pro-max"
|
||||||
|
|
||||||
|
|
||||||
def load_manifest() -> dict:
|
def load_manifest() -> dict:
|
||||||
|
|
@ -58,13 +58,13 @@ class ThirdpartySkillsPipelineTests(unittest.TestCase):
|
||||||
self.assertEqual(karpathy["snapshot_dir"], "andrej-karpathy-skills")
|
self.assertEqual(karpathy["snapshot_dir"], "andrej-karpathy-skills")
|
||||||
self.assertEqual(karpathy["skills_subdir"], "skills")
|
self.assertEqual(karpathy["skills_subdir"], "skills")
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
karpathy["source_list"], "codex/skills/thirdparty/.sources/andrej-karpathy-skills.list"
|
karpathy["source_list"], "skills/thirdparty/.sources/andrej-karpathy-skills.list"
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_ui_ux_pro_max_uses_render_codex_skill_sync_mode(self):
|
def test_ui_ux_pro_max_uses_render_skill_sync_mode(self):
|
||||||
data = load_manifest()
|
data = load_manifest()
|
||||||
ui_skill = next(item for item in data["sources"] if item["id"] == "ui-ux-pro-max")
|
ui_skill = next(item for item in data["sources"] if item["id"] == "ui-ux-pro-max")
|
||||||
self.assertEqual(ui_skill["sync_mode"], "render_codex_skill")
|
self.assertEqual(ui_skill["sync_mode"], "render_skill")
|
||||||
self.assertEqual(ui_skill["snapshot_dir"], "ui-ux-pro-max")
|
self.assertEqual(ui_skill["snapshot_dir"], "ui-ux-pro-max")
|
||||||
|
|
||||||
def test_superpowers_manifest_prunes_non_superpowers_paths(self):
|
def test_superpowers_manifest_prunes_non_superpowers_paths(self):
|
||||||
|
|
@ -105,7 +105,7 @@ class ThirdpartySkillsPipelineTests(unittest.TestCase):
|
||||||
def test_skills_doc_points_to_generic_thirdparty_sources(self):
|
def test_skills_doc_points_to_generic_thirdparty_sources(self):
|
||||||
text = SKILLS_MD.read_text(encoding="utf-8")
|
text = SKILLS_MD.read_text(encoding="utf-8")
|
||||||
self.assertIn("## 9. Third-party Skills", text)
|
self.assertIn("## 9. Third-party Skills", text)
|
||||||
self.assertIn("来源:`codex/skills/thirdparty/.sources/`(第三方来源清单目录)。", text)
|
self.assertIn("来源:`skills/thirdparty/.sources/`(第三方来源清单目录)。", text)
|
||||||
self.assertNotIn("Third-party Skills (superpowers)", text)
|
self.assertNotIn("Third-party Skills (superpowers)", text)
|
||||||
|
|
||||||
def test_superpowers_and_ui_ux_pro_max_source_lists_exist(self):
|
def test_superpowers_and_ui_ux_pro_max_source_lists_exist(self):
|
||||||
|
|
@ -160,6 +160,7 @@ class ThirdpartySkillsPipelineTests(unittest.TestCase):
|
||||||
self.assertEqual(set_remote.returncode, 0, msg=set_remote.stderr)
|
self.assertEqual(set_remote.returncode, 0, msg=set_remote.stderr)
|
||||||
|
|
||||||
shutil.copy2(MANIFEST, work / ".gitea" / "ci" / "thirdparty_skills.json")
|
shutil.copy2(MANIFEST, work / ".gitea" / "ci" / "thirdparty_skills.json")
|
||||||
|
shutil.copy2(SYNC_SCRIPT, work / ".gitea" / "ci" / "sync_thirdparty_skills.sh")
|
||||||
|
|
||||||
sync_result = run_command("bash", ".gitea/ci/sync_thirdparty_skills.sh", cwd=work)
|
sync_result = run_command("bash", ".gitea/ci/sync_thirdparty_skills.sh", cwd=work)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
|
@ -169,10 +170,10 @@ class ThirdpartySkillsPipelineTests(unittest.TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
generated_list = (
|
generated_list = (
|
||||||
work / "codex" / "skills" / "thirdparty" / ".sources" / "andrej-karpathy-skills.list"
|
work / "skills" / "thirdparty" / ".sources" / "andrej-karpathy-skills.list"
|
||||||
)
|
)
|
||||||
generated_skill = (
|
generated_skill = (
|
||||||
work / "codex" / "skills" / "thirdparty" / "karpathy-guidelines" / "SKILL.md"
|
work / "skills" / "thirdparty" / "karpathy-guidelines" / "SKILL.md"
|
||||||
)
|
)
|
||||||
self.assertTrue(generated_list.is_file())
|
self.assertTrue(generated_list.is_file())
|
||||||
self.assertTrue(generated_skill.is_file())
|
self.assertTrue(generated_skill.is_file())
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ README = ROOT / "README.md"
|
||||||
PLAYBOOK_EXAMPLE = ROOT / "playbook.toml.example"
|
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"
|
TEMPLATES_README = ROOT / "templates" / "README.md"
|
||||||
|
REMOVED_TSL_GUIDE = ROOT / "skills" / "tsl-guide"
|
||||||
|
|
||||||
class TslEntrypointsConsistencyTests(unittest.TestCase):
|
class TslEntrypointsConsistencyTests(unittest.TestCase):
|
||||||
def test_ruleset_lists_canonical_tsl_layers(self):
|
def test_ruleset_lists_canonical_tsl_layers(self):
|
||||||
|
|
@ -44,6 +45,19 @@ class TslEntrypointsConsistencyTests(unittest.TestCase):
|
||||||
self.assertNotIn("vendoring", SKILLS_DOC.read_text(encoding="utf-8"))
|
self.assertNotIn("vendoring", SKILLS_DOC.read_text(encoding="utf-8"))
|
||||||
self.assertNotIn("vendoring", TEMPLATES_CI_README.read_text(encoding="utf-8"))
|
self.assertNotIn("vendoring", TEMPLATES_CI_README.read_text(encoding="utf-8"))
|
||||||
|
|
||||||
|
def test_repo_docs_use_platform_neutral_skills_source_dir(self):
|
||||||
|
readme_text = README.read_text(encoding="utf-8")
|
||||||
|
skills_text = SKILLS_DOC.read_text(encoding="utf-8")
|
||||||
|
templates_text = TEMPLATES_README.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
self.assertIn("`skills/`", readme_text)
|
||||||
|
self.assertIn("`skills/`", skills_text)
|
||||||
|
self.assertIn("├── skills/", templates_text)
|
||||||
|
|
||||||
|
self.assertNotIn("`codex/skills/`", readme_text)
|
||||||
|
self.assertNotIn("`codex/skills/`", skills_text)
|
||||||
|
self.assertNotIn("├── codex/skills/", templates_text)
|
||||||
|
|
||||||
def test_repo_no_longer_ships_tsl_guide_skill(self):
|
def test_repo_no_longer_ships_tsl_guide_skill(self):
|
||||||
self.assertFalse(REMOVED_TSL_GUIDE.exists())
|
self.assertFalse(REMOVED_TSL_GUIDE.exists())
|
||||||
self.assertNotIn("tsl-guide", README.read_text(encoding="utf-8"))
|
self.assertNotIn("tsl-guide", README.read_text(encoding="utf-8"))
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,8 @@ langs = ["tsl"]
|
||||||
self.assertTrue((snapshot / "templates/README.md").is_file())
|
self.assertTrue((snapshot / "templates/README.md").is_file())
|
||||||
self.assertTrue((snapshot / "templates/memory-bank").is_dir())
|
self.assertTrue((snapshot / "templates/memory-bank").is_dir())
|
||||||
self.assertTrue((snapshot / "templates/prompts").is_dir())
|
self.assertTrue((snapshot / "templates/prompts").is_dir())
|
||||||
|
self.assertTrue((snapshot / "skills").is_dir())
|
||||||
|
self.assertFalse((snapshot / "codex").exists())
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue