mirror of
https://github.com/tradecatlabs/vibe-coding-cn.git
synced 2026-08-19 13:58:05 +00:00
refactor: 重构目录结构以支持 i18n
创建 'i18n' 目录以存放多语言内容。将所有现有的中文内容(文档、提示词、技能、README)移动到 'i18n/zh/' 中。添加了新的根 README 作为语言入口,并为英文('en')翻译创建了占位符结构。
This commit is contained in:
@@ -1,181 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ==================== Help ====================
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
create-skill.sh <skill-name> [--minimal|--full] [--output <dir>] [--force]
|
||||
|
||||
Notes:
|
||||
- <skill-name> MUST be lowercase, start with a letter, and only contain letters, digits, and hyphens
|
||||
- Default mode: --full
|
||||
- Default output: current directory (creates ./<skill-name>/)
|
||||
|
||||
Examples:
|
||||
./skills/claude-skills/scripts/create-skill.sh postgresql --full --output skills
|
||||
./skills/claude-skills/scripts/create-skill.sh my-api --minimal
|
||||
EOF
|
||||
}
|
||||
|
||||
die() {
|
||||
echo "Error: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ==================== Arg Parsing ====================
|
||||
|
||||
skill_name=""
|
||||
mode="full"
|
||||
output_dir="."
|
||||
force=0
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
--minimal)
|
||||
mode="minimal"
|
||||
shift
|
||||
;;
|
||||
--full)
|
||||
mode="full"
|
||||
shift
|
||||
;;
|
||||
-o|--output)
|
||||
[[ $# -ge 2 ]] || die "--output requires a directory argument"
|
||||
output_dir="$2"
|
||||
shift 2
|
||||
;;
|
||||
-f|--force)
|
||||
force=1
|
||||
shift
|
||||
;;
|
||||
--)
|
||||
shift
|
||||
break
|
||||
;;
|
||||
-*)
|
||||
die "Unknown argument: $1 (use --help)"
|
||||
;;
|
||||
*)
|
||||
if [[ -z "$skill_name" ]]; then
|
||||
skill_name="$1"
|
||||
shift
|
||||
else
|
||||
die "Extra argument: $1 (only one <skill-name> is allowed)"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -n "$skill_name" ]] || { usage; exit 1; }
|
||||
|
||||
if [[ ! "$skill_name" =~ ^[a-z][a-z0-9-]*$ ]]; then
|
||||
die "skill-name must be lowercase, start with a letter, and only contain letters/digits/hyphens (e.g. my-skill-name)"
|
||||
fi
|
||||
|
||||
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
assets_dir="${script_dir}/../assets"
|
||||
|
||||
template_path=""
|
||||
case "$mode" in
|
||||
minimal) template_path="${assets_dir}/template-minimal.md" ;;
|
||||
full) template_path="${assets_dir}/template-complete.md" ;;
|
||||
*) die "Internal error: unknown mode=$mode" ;;
|
||||
esac
|
||||
|
||||
[[ -f "$template_path" ]] || die "Template not found: $template_path"
|
||||
|
||||
mkdir -p "$output_dir"
|
||||
|
||||
target_dir="${output_dir%/}/${skill_name}"
|
||||
|
||||
if [[ -e "$target_dir" && "$force" -ne 1 ]]; then
|
||||
die "Target already exists: $target_dir (use --force to overwrite)"
|
||||
fi
|
||||
|
||||
mkdir -p "$target_dir"/{assets,scripts,references}
|
||||
|
||||
# ==================== Write Files ====================
|
||||
|
||||
render_template() {
|
||||
local src="$1"
|
||||
local dest="$2"
|
||||
sed "s/{{skill_name}}/${skill_name}/g" "$src" > "$dest"
|
||||
}
|
||||
|
||||
render_template "$template_path" "$target_dir/SKILL.md"
|
||||
|
||||
cat > "$target_dir/references/index.md" <<EOF
|
||||
# ${skill_name} Reference Index
|
||||
|
||||
## Quick Links
|
||||
|
||||
- Getting started: \`getting_started.md\`
|
||||
- API/CLI/config: \`api.md\` (if applicable)
|
||||
- Examples: \`examples.md\`
|
||||
- Troubleshooting: \`troubleshooting.md\`
|
||||
|
||||
## Notes
|
||||
|
||||
- Put long-form content here: excerpts, evidence links, edge cases, FAQ
|
||||
- Keep \`SKILL.md\` Quick Reference short and directly usable
|
||||
EOF
|
||||
|
||||
if [[ "$mode" == "full" ]]; then
|
||||
cat > "$target_dir/references/getting_started.md" <<'EOF'
|
||||
# Getting Started & Vocabulary
|
||||
|
||||
## Goals
|
||||
|
||||
- Define the 10 most important terms in this domain
|
||||
- Provide the shortest path from zero to working
|
||||
EOF
|
||||
|
||||
cat > "$target_dir/references/api.md" <<'EOF'
|
||||
# API / CLI / Config Reference (If Applicable)
|
||||
|
||||
## Suggested Structure
|
||||
|
||||
- Organize by use case, not alphabetically
|
||||
- Key parameters: defaults, boundaries, common misuse
|
||||
- Common errors: message -> cause -> fix steps
|
||||
EOF
|
||||
|
||||
cat > "$target_dir/references/examples.md" <<'EOF'
|
||||
# Long Examples
|
||||
|
||||
Put examples longer than ~20 lines here, split by use case:
|
||||
|
||||
- Use case 1: ...
|
||||
- Use case 2: ...
|
||||
EOF
|
||||
|
||||
cat > "$target_dir/references/troubleshooting.md" <<'EOF'
|
||||
# Troubleshooting & Edge Cases
|
||||
|
||||
Write as: symptom -> likely causes -> diagnosis -> fix.
|
||||
EOF
|
||||
fi
|
||||
|
||||
# ==================== Summary ====================
|
||||
|
||||
echo ""
|
||||
echo "OK: Skill generated: $target_dir/"
|
||||
echo ""
|
||||
echo "Layout:"
|
||||
echo " $target_dir/"
|
||||
echo " |-- SKILL.md"
|
||||
echo " |-- assets/"
|
||||
echo " |-- scripts/"
|
||||
echo " \\-- references/"
|
||||
echo " \\-- index.md"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1) Edit $target_dir/SKILL.md (triggers/boundaries/quick reference/examples)"
|
||||
echo " 2) Put long-form docs into $target_dir/references/ and update index.md"
|
||||
@@ -1,212 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
validate-skill.sh <skill-dir> [--strict]
|
||||
|
||||
What it does:
|
||||
- Validates SKILL.md YAML frontmatter (name/description)
|
||||
- Performs lightweight structural checks
|
||||
- In --strict mode, enforces the recommended section layout
|
||||
|
||||
Examples:
|
||||
./skills/claude-skills/scripts/validate-skill.sh skills/postgresql
|
||||
./skills/claude-skills/scripts/validate-skill.sh skills/my-skill --strict
|
||||
EOF
|
||||
}
|
||||
|
||||
die() {
|
||||
echo "Error: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
warn() {
|
||||
echo "Warning: $*" >&2
|
||||
}
|
||||
|
||||
strict=0
|
||||
skill_dir=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
--strict)
|
||||
strict=1
|
||||
shift
|
||||
;;
|
||||
--)
|
||||
shift
|
||||
break
|
||||
;;
|
||||
-*)
|
||||
die "Unknown argument: $1 (use --help)"
|
||||
;;
|
||||
*)
|
||||
if [[ -z "$skill_dir" ]]; then
|
||||
skill_dir="$1"
|
||||
shift
|
||||
else
|
||||
die "Extra argument: $1 (only one <skill-dir> is allowed)"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -n "$skill_dir" ]] || { usage; exit 1; }
|
||||
[[ -d "$skill_dir" ]] || die "Not a directory: $skill_dir"
|
||||
|
||||
skill_md="$skill_dir/SKILL.md"
|
||||
[[ -f "$skill_md" ]] || die "Missing SKILL.md: $skill_md"
|
||||
|
||||
base_name="$(basename -- "${skill_dir%/}")"
|
||||
|
||||
# -------------------- Parse YAML frontmatter --------------------
|
||||
|
||||
frontmatter=""
|
||||
if frontmatter="$(
|
||||
awk '
|
||||
BEGIN { in_fm=0; closed=0 }
|
||||
NR==1 {
|
||||
if ($0 != "---") exit 2
|
||||
in_fm=1
|
||||
next
|
||||
}
|
||||
in_fm==1 {
|
||||
if ($0 == "---") { closed=1; exit 0 }
|
||||
print
|
||||
next
|
||||
}
|
||||
END {
|
||||
if (closed == 0) exit 3
|
||||
}
|
||||
' "$skill_md"
|
||||
)"; then
|
||||
:
|
||||
else
|
||||
rc=$?
|
||||
case "$rc" in
|
||||
2) die "SKILL.md must start with YAML frontmatter (--- as the first line)" ;;
|
||||
3) die "YAML frontmatter is not closed (missing ---)" ;;
|
||||
*) die "Failed to parse YAML frontmatter (awk exit=$rc)" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
name="$(
|
||||
printf "%s\n" "$frontmatter" | awk -F: '
|
||||
tolower($1) ~ /^name$/ {
|
||||
sub(/^[^:]*:[[:space:]]*/, "", $0)
|
||||
gsub(/[[:space:]]+$/, "", $0)
|
||||
print
|
||||
exit
|
||||
}
|
||||
'
|
||||
)"
|
||||
|
||||
description="$(
|
||||
printf "%s\n" "$frontmatter" | awk -F: '
|
||||
tolower($1) ~ /^description$/ {
|
||||
sub(/^[^:]*:[[:space:]]*/, "", $0)
|
||||
gsub(/[[:space:]]+$/, "", $0)
|
||||
print
|
||||
exit
|
||||
}
|
||||
'
|
||||
)"
|
||||
|
||||
[[ -n "$name" ]] || die "Missing frontmatter field: name"
|
||||
[[ -n "$description" ]] || die "Missing frontmatter field: description"
|
||||
|
||||
if [[ ! "$name" =~ ^[a-z][a-z0-9-]*$ ]]; then
|
||||
die "Invalid name: '$name' (expected ^[a-z][a-z0-9-]*$)"
|
||||
fi
|
||||
|
||||
if [[ "$strict" -eq 1 && "$name" != "$base_name" ]]; then
|
||||
die "Strict mode: frontmatter name ('$name') must match directory name ('$base_name')"
|
||||
fi
|
||||
|
||||
# -------------------- Strip fenced code blocks for section checks --------------------
|
||||
|
||||
filtered_md="$(mktemp)"
|
||||
trap 'rm -f "$filtered_md"' EXIT
|
||||
|
||||
awk '
|
||||
BEGIN { in_fence=0 }
|
||||
/^[[:space:]]*```/ { in_fence = !in_fence; next }
|
||||
in_fence==0 { print }
|
||||
' "$skill_md" > "$filtered_md"
|
||||
|
||||
# -------------------- Structural checks --------------------
|
||||
|
||||
required_h2=(
|
||||
"When to Use This Skill"
|
||||
"Not For / Boundaries"
|
||||
"Quick Reference"
|
||||
"Examples"
|
||||
"References"
|
||||
"Maintenance"
|
||||
)
|
||||
|
||||
for title in "${required_h2[@]}"; do
|
||||
if ! grep -Eq "^##[[:space:]]+${title}([[:space:]]*)$" "$filtered_md"; then
|
||||
if [[ "$strict" -eq 1 ]]; then
|
||||
die "Strict mode: missing required section heading: '## ${title}'"
|
||||
fi
|
||||
warn "Missing recommended section heading: '## ${title}'"
|
||||
fi
|
||||
done
|
||||
|
||||
# references/index.md presence (only enforced in strict mode when references/ exists)
|
||||
if [[ -d "$skill_dir/references" && "$strict" -eq 1 && ! -f "$skill_dir/references/index.md" ]]; then
|
||||
die "Strict mode: references/ exists but references/index.md is missing"
|
||||
fi
|
||||
|
||||
# -------------------- Heuristics: Quick Reference size --------------------
|
||||
|
||||
quick_start="$(awk 'match($0, /^##[[:space:]]+Quick Reference([[:space:]]*)$/){print NR; exit}' "$filtered_md" || true)"
|
||||
if [[ -n "$quick_start" ]]; then
|
||||
quick_end="$(awk -v s="$quick_start" 'NR>s && match($0, /^##[[:space:]]+/){print NR; exit}' "$filtered_md" || true)"
|
||||
total_lines="$(wc -l < "$filtered_md" | tr -d ' ')"
|
||||
if [[ -z "$quick_end" ]]; then
|
||||
quick_end="$((total_lines + 1))"
|
||||
fi
|
||||
quick_len="$((quick_end - quick_start - 1))"
|
||||
if [[ "$quick_len" -gt 250 ]]; then
|
||||
if [[ "$strict" -eq 1 ]]; then
|
||||
die "Strict mode: Quick Reference section is too long (${quick_len} lines). Move long-form text into references/."
|
||||
fi
|
||||
warn "Quick Reference section is large (${quick_len} lines). Consider moving long-form text into references/."
|
||||
fi
|
||||
fi
|
||||
|
||||
# -------------------- Heuristics: Examples count --------------------
|
||||
|
||||
examples_start="$(awk 'match($0, /^##[[:space:]]+Examples([[:space:]]*)$/){print NR; exit}' "$filtered_md" || true)"
|
||||
if [[ -n "$examples_start" ]]; then
|
||||
examples_end="$(awk -v s="$examples_start" 'NR>s && match($0, /^##[[:space:]]+/){print NR; exit}' "$filtered_md" || true)"
|
||||
total_lines="$(wc -l < "$filtered_md" | tr -d ' ')"
|
||||
if [[ -z "$examples_end" ]]; then
|
||||
examples_end="$((total_lines + 1))"
|
||||
fi
|
||||
|
||||
example_count="$(
|
||||
awk -v s="$examples_start" -v e="$examples_end" '
|
||||
NR>s && NR<e && match($0, /^###[[:space:]]+Example([[:space:]]|$)/) { c++ }
|
||||
END { print c+0 }
|
||||
' "$filtered_md"
|
||||
)"
|
||||
|
||||
if [[ "$example_count" -lt 3 ]]; then
|
||||
if [[ "$strict" -eq 1 ]]; then
|
||||
die "Strict mode: expected >= 3 examples (found ${example_count})."
|
||||
fi
|
||||
warn "Recommended: >= 3 examples (found ${example_count})."
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "OK: $skill_dir"
|
||||
Reference in New Issue
Block a user