refactor: fold ack kit into skill

This commit is contained in:
2026-07-30 11:54:02 +08:00
parent e308d6a04c
commit 076d87e303
32 changed files with 592 additions and 619 deletions
+23 -43
View File
@@ -4,7 +4,6 @@ from __future__ import annotations
import argparse
import re
import shutil
import subprocess
import sys
from datetime import datetime
@@ -18,7 +17,6 @@ from skiff.paths import (
DRAFTS_DIR,
EXTERNALS_DIR,
CONFIG_FILE,
KITS_DIR,
SKILLS_DIR,
SKILLS_HOME,
TEMPLATE_DIR,
@@ -83,7 +81,7 @@ def _project_root(explicit: str | None = None) -> Path:
return find_repo_root() or Path.cwd()
def _render_kit_template(source: Path, destination: Path, values: dict[str, str]) -> None:
def _render_template(source: Path, destination: Path, values: dict[str, str]) -> None:
content = source.read_text(encoding="utf-8")
for placeholder, value in values.items():
content = content.replace(placeholder, value)
@@ -1132,66 +1130,55 @@ def cmd_doctor(args: argparse.Namespace) -> None:
sys.exit(1)
def cmd_kit_init(args: argparse.Namespace) -> None:
"""将 owned kit 初始化到目标项目。"""
def cmd_init(args: argparse.Namespace) -> None:
"""使用 builtin skill 自带的模板初始化目标项目状态。"""
ensure_skills_home()
kit_source = KITS_DIR / args.name
if not kit_source.is_dir():
available = sorted(path.name for path in KITS_DIR.iterdir() if path.is_dir()) if KITS_DIR.is_dir() else []
suffix = f";可用 kit: {', '.join(available)}" if available else ""
raise SystemExit(f"kit 不存在: {args.name}{suffix}")
skill_source = SKILLS_DIR / args.name
if not (skill_source / "SKILL.md").is_file():
raise SystemExit(f"builtin skill 不存在: {args.name}")
project = _project_root(args.project)
if not project.is_dir():
raise SystemExit(f"项目目录不存在: {project}")
destination = project / "docs" / args.name
kit_target = destination / "kit"
project_file = destination / "project.md"
tasks_file = destination / "tasks.yaml"
managed_targets = (kit_target, project_file, tasks_file)
managed_targets = (project_file, tasks_file)
existing = [path for path in managed_targets if path.exists() or path.is_symlink()]
if existing:
paths = ", ".join(str(path.relative_to(project)) for path in existing)
raise SystemExit(f"拒绝覆盖已有路径: {paths}")
project_template = kit_source / "templates" / "project.template.md"
tasks_template = kit_source / "templates" / "tasks.template.yaml"
project_template = skill_source / "templates" / "project.template.md"
tasks_template = skill_source / "templates" / "tasks.template.yaml"
missing = [path for path in (project_template, tasks_template) if not path.is_file()]
if missing:
paths = ", ".join(str(path.relative_to(SKILLS_HOME)) for path in missing)
raise SystemExit(f"kit 缺少初始化模板: {paths}")
raise SystemExit(f"skill 缺少初始化模板: {paths}")
destination.mkdir(parents=True, exist_ok=True)
if args.copy:
shutil.copytree(kit_source, kit_target)
mode = "copy"
else:
kit_target.symlink_to(kit_source.resolve(), target_is_directory=True)
mode = "symlink"
version_file = kit_source / "VERSION"
kit_version = version_file.read_text(encoding="utf-8").strip() if version_file.is_file() else "unknown"
version_file = skill_source / "VERSION"
ack_version = version_file.read_text(encoding="utf-8").strip() if version_file.is_file() else "unknown"
now = datetime.now().astimezone().isoformat(timespec="seconds")
values = {
"<project_name>": project.name,
"<repo_path>": str(project),
"<dev_worktree>": str(project),
"<overlay_file_path>": f"docs/{args.name}/project.md",
"<kit_version>": kit_version,
"<接入时的 ack 版本,见 kit 根 VERSION>": kit_version,
"<ack_version>": ack_version,
"<接入时的 ack skill 版本>": ack_version,
"<YYYY-MM-DDTHH:mm:ss+TZ>": now,
}
_render_kit_template(project_template, project_file, values)
_render_kit_template(tasks_template, tasks_file, values)
_render_template(project_template, project_file, values)
_render_template(tasks_template, tasks_file, values)
validator = kit_target / "scripts" / "validate_tasks.py"
validator = skill_source / "scripts" / "validate_tasks.py"
if validator.is_file():
subprocess.run([sys.executable, str(validator), str(tasks_file)], check=True)
_print(f"✓ kit 初始化完成: {args.name}")
_print(f"✓ skill 项目状态初始化完成: {args.name}")
_print(f" 项目: {project}")
_print(f" 模式: {mode}")
_print(f" kit: {kit_target}")
_print(f" 覆盖层: {project_file}")
_print(f" 任务板: {tasks_file}")
_print("下一步: 填写 project.md 中的项目命令、路径权限和 Base URL")
@@ -1363,23 +1350,16 @@ def build_parser() -> argparse.ArgumentParser:
p_finalize.add_argument("name", help="skill 名称")
p_finalize.set_defaults(func=cmd_finalize)
p_init = sub.add_parser("init", help="使用 skill 模板初始化项目状态")
p_init.add_argument("name", help="skill 名称")
p_init.add_argument("--project", help="项目根目录(默认自动检测或当前目录)")
p_init.set_defaults(func=cmd_init)
p_doctor = sub.add_parser("doctor", help="软链健康检查")
p_doctor.add_argument("-a", "--agent", dest="agents", nargs="+", action="append")
p_doctor.add_argument("--fix", action="store_true", help="自动修复可修复的软链")
p_doctor.set_defaults(func=cmd_doctor)
p_kit = sub.add_parser("kit", help="管理项目规范包")
kit_sub = p_kit.add_subparsers(dest="kit_command", required=True)
p_kit_init = kit_sub.add_parser("init", help="在项目中初始化 kit")
p_kit_init.add_argument("name", help="kit 名称")
p_kit_init.add_argument("--project", help="项目根目录(默认自动检测或当前目录)")
p_kit_init.add_argument(
"--copy",
action="store_true",
help="复制 kit,而不是创建指向 SSOT 的软链接",
)
p_kit_init.set_defaults(func=cmd_kit_init)
return parser