fix: harden skill template contracts and scaffolding
All checks were successful
技能自动化发布 / release (push) Successful in 4s
All checks were successful
技能自动化发布 / release (push) Successful in 4s
Align scaffold slug replacement, Action manifest strict boundaries, metadata prune API, field permission validation, and task_logs readonly semantics with documented template contracts. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -22,7 +22,10 @@ python tools/scaffold_skill.py --slug my-new-skill --destination /path/to/my-new
|
||||
- 复制模板到 `-Destination`(目录末段须与 `-Slug` 一致)
|
||||
- **排除**:`.git`、`.pytest_cache`、`__pycache__`、`.env`、`.env.local`、`*.pyc`
|
||||
- 删除目标中的 `.openclaw-skill-template` 标记
|
||||
- 将文本文件中的 `your-skill-slug` 替换为新 slug(含 `assets/actions.json`、`SKILL.md`、`constants.py` 等)
|
||||
- 将文本文件中的模板占位 slug 替换为新 slug:
|
||||
- `your-skill-slug` → 新技能 kebab-case slug(如 `scrape-demo-records`)
|
||||
- `your_skill_slug` → 连字符替换为下划线(如 `scrape_demo_records`,用于 `LOG_LOGGER_NAME` 等)
|
||||
- 覆盖 `assets/actions.json`、`SKILL.md`、`scripts/util/constants.py` 等生成文件;文档中的路径示例可保留占位,但生成后的技能文件不得残留占位符
|
||||
- **不**自动修改 `actions.json` 中的 CLI 业务命令;技能作者自行增删 action
|
||||
- **不**自动 `git init`;打印后续 Git 与测试命令
|
||||
|
||||
|
||||
@@ -71,7 +71,8 @@ if ($targetExists) {
|
||||
|
||||
$excludeDirs = @(".git", ".pytest_cache", "__pycache__")
|
||||
$excludeFiles = @(".env", ".env.local")
|
||||
$templatePlaceholderSlug = "your-skill-slug"
|
||||
$templatePlaceholderSlugKebab = "your-skill-slug"
|
||||
$templatePlaceholderSlugUnderscore = "your_skill_slug"
|
||||
$textFileSuffixes = @(".md", ".py", ".json", ".yaml", ".yml", ".txt", ".ini", ".ps1", ".sample")
|
||||
|
||||
function Copy-TemplateTree {
|
||||
@@ -104,6 +105,7 @@ function Replace-TemplatePlaceholders {
|
||||
[string]$Root,
|
||||
[string]$Slug
|
||||
)
|
||||
$underscoreSlug = $Slug.Replace("-", "_")
|
||||
Get-ChildItem -LiteralPath $Root -Recurse -File -Force | ForEach-Object {
|
||||
$name = $_.Name
|
||||
if ($excludeFiles -contains $name) { return }
|
||||
@@ -115,8 +117,12 @@ function Replace-TemplatePlaceholders {
|
||||
catch {
|
||||
return
|
||||
}
|
||||
if ($text -notlike "*$templatePlaceholderSlug*") { return }
|
||||
$updated = $text.Replace($templatePlaceholderSlug, $Slug)
|
||||
if (
|
||||
$text -notlike "*$templatePlaceholderSlugKebab*" -and
|
||||
$text -notlike "*$templatePlaceholderSlugUnderscore*"
|
||||
) { return }
|
||||
$updated = $text.Replace($templatePlaceholderSlugKebab, $Slug)
|
||||
$updated = $updated.Replace($templatePlaceholderSlugUnderscore, $underscoreSlug)
|
||||
[System.IO.File]::WriteAllText($_.FullName, $updated)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ from pathlib import Path
|
||||
EXCLUDE_DIR_NAMES = {".git", ".pytest_cache", "__pycache__"}
|
||||
EXCLUDE_FILE_NAMES = {".env", ".env.local"}
|
||||
KEBAB_SLUG = re.compile(r"^[a-z0-9]+(-[a-z0-9]+)*$")
|
||||
TEMPLATE_PLACEHOLDER_SLUG = "your-skill-slug"
|
||||
TEMPLATE_PLACEHOLDER_SLUG_KEBAB = "your-skill-slug"
|
||||
TEMPLATE_PLACEHOLDER_SLUG_UNDERSCORE = "your_skill_slug"
|
||||
TEXT_FILE_SUFFIXES = {
|
||||
".md",
|
||||
".py",
|
||||
@@ -46,8 +47,13 @@ def _should_replace_placeholders(path: Path) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _slug_to_underscore(slug: str) -> str:
|
||||
return slug.replace("-", "_")
|
||||
|
||||
|
||||
def _replace_template_placeholders(target: Path, slug: str) -> None:
|
||||
"""将模板占位 slug 替换为新技能 slug(不修改 action 业务命令结构)。"""
|
||||
underscore_slug = _slug_to_underscore(slug)
|
||||
for path in target.rglob("*"):
|
||||
if not path.is_file():
|
||||
continue
|
||||
@@ -59,9 +65,14 @@ def _replace_template_placeholders(target: Path, slug: str) -> None:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
if TEMPLATE_PLACEHOLDER_SLUG not in text:
|
||||
if (
|
||||
TEMPLATE_PLACEHOLDER_SLUG_KEBAB not in text
|
||||
and TEMPLATE_PLACEHOLDER_SLUG_UNDERSCORE not in text
|
||||
):
|
||||
continue
|
||||
path.write_text(text.replace(TEMPLATE_PLACEHOLDER_SLUG, slug), encoding="utf-8")
|
||||
updated = text.replace(TEMPLATE_PLACEHOLDER_SLUG_KEBAB, slug)
|
||||
updated = updated.replace(TEMPLATE_PLACEHOLDER_SLUG_UNDERSCORE, underscore_slug)
|
||||
path.write_text(updated, encoding="utf-8")
|
||||
|
||||
|
||||
def _print_next_steps(target: Path) -> None:
|
||||
|
||||
Reference in New Issue
Block a user