playbook/scripts/sync_templates.sh

370 lines
10 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env sh
set -eu
# Sync project templates to target project.
# - Copies templates/memory-bank/ -> <project-root>/memory-bank/
# - Copies templates/prompts/ -> <project-root>/docs/prompts/
# - Copies templates/AGENTS.template.md -> <project-root>/AGENTS.md
# - Copies templates/AGENT_RULES.template.md -> <project-root>/AGENT_RULES.md
# Existing targets are backed up before overwrite.
#
# Usage:
# sh scripts/sync_templates.sh # sync to current git root
# sh scripts/sync_templates.sh -project-root /path/to/project
# sh scripts/sync_templates.sh -project-root /path/to/project -project-name "MyProject" -date "2026-01-20"
#
# Options:
# -project-root PATH Target project root (default: git root)
# -project-name NAME Replace {{PROJECT_NAME}} placeholder
# -date DATE Replace {{DATE}} placeholder (default: today)
# -no-backup Skip backup of existing files
# -force Overwrite without prompting
# -full Append full framework (规则优先级 + 新会话开始时) to existing AGENTS.md
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)"
SRC="$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd -P)"
# Defaults
PROJECT_NAME=""
SYNC_DATE="$(date +%Y-%m-%d 2>/dev/null || echo "{{DATE}}")"
NO_BACKUP=0
FORCE=0
FULL=0
PROJECT_ROOT=""
# Parse arguments
while [ $# -gt 0 ]; do
case "$1" in
-project-root)
if [ $# -lt 2 ] || [ -z "${2:-}" ]; then
echo "ERROR: -project-root requires a path." >&2
exit 1
fi
PROJECT_ROOT="$2"
shift 2
;;
-project-name)
if [ $# -lt 2 ] || [ -z "${2:-}" ]; then
echo "ERROR: -project-name requires a value." >&2
exit 1
fi
PROJECT_NAME="$2"
shift 2
;;
-date)
if [ $# -lt 2 ] || [ -z "${2:-}" ]; then
echo "ERROR: -date requires a value." >&2
exit 1
fi
SYNC_DATE="$2"
shift 2
;;
-no-backup)
NO_BACKUP=1
shift
;;
-force)
FORCE=1
shift
;;
-full)
FULL=1
shift
;;
-h|-help)
cat <<'EOF'
Usage:
sh scripts/sync_templates.sh [options]
sh scripts/sync_templates.sh -project-root /path/to/project
Options:
-project-root PATH Target project root (default: git root)
-project-name NAME Replace {{PROJECT_NAME}} placeholder
-date DATE Replace {{DATE}} placeholder (default: today)
-no-backup Skip backup of existing files
-force Overwrite without prompting
-full Append full framework (规则优先级 + 新会话开始时) to existing AGENTS.md
-h, -help Show this help
Examples:
sh scripts/sync_templates.sh
sh scripts/sync_templates.sh -project-root /path/to/project
sh scripts/sync_templates.sh -project-root /path/to/project -full
EOF
exit 0
;;
-*)
echo "ERROR: Unknown option: $1" >&2
exit 1
;;
*)
echo "ERROR: positional args are not supported; use -project-root." >&2
exit 1
;;
esac
done
# Determine project root
if [ -z "$PROJECT_ROOT" ]; then
PROJECT_ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || pwd)"
fi
PROJECT_ROOT="$(CDPATH= cd -- "$PROJECT_ROOT" && pwd -P)"
# Source directories
TEMPLATES_DIR="$SRC/templates"
MEMORY_BANK_SRC="$TEMPLATES_DIR/memory-bank"
PROMPTS_SRC="$TEMPLATES_DIR/prompts"
AGENTS_SRC="$TEMPLATES_DIR/AGENTS.template.md"
AGENT_RULES_SRC="$TEMPLATES_DIR/AGENT_RULES.template.md"
# Check source exists
if [ ! -d "$TEMPLATES_DIR" ]; then
echo "ERROR: Templates directory not found: $TEMPLATES_DIR" >&2
exit 1
fi
# Skip if source equals destination (running from playbook repo itself)
if [ "$SRC" = "$PROJECT_ROOT" ]; then
echo "Skip: playbook root equals project root."
echo "Done."
exit 0
fi
timestamp="$(date +%Y%m%d%H%M%S 2>/dev/null || echo bak)"
# Function: backup file/directory
backup_if_exists() {
target="$1"
if [ -e "$target" ] && [ "$NO_BACKUP" -eq 0 ]; then
backup="${target}.bak.$timestamp"
mv "$target" "$backup"
echo "Backed up: $(basename "$target") -> $(basename "$backup")"
fi
}
escape_sed_replacement() {
printf '%s' "$1" | sed 's/[&/|\\]/\\&/g'
}
# Function: replace placeholders in file
replace_placeholders() {
file="$1"
[ -f "$file" ] || return 0
tmp="$(mktemp 2>/dev/null || echo "$file.tmp.$timestamp")"
date_repl="$(escape_sed_replacement "$SYNC_DATE")"
if [ -n "$PROJECT_NAME" ]; then
project_repl="$(escape_sed_replacement "$PROJECT_NAME")"
sed -e "s/{{PROJECT_NAME}}/$project_repl/g" -e "s/{{DATE}}/$date_repl/g" "$file" > "$tmp"
else
sed -e "s/{{DATE}}/$date_repl/g" "$file" > "$tmp"
fi
mv "$tmp" "$file"
}
# Function: replace placeholders in directory
replace_placeholders_dir() {
dir="$1"
[ -d "$dir" ] || return 0
find "$dir" -type f -name '*.md' -print | while IFS= read -r file; do
replace_placeholders "$file"
done
}
echo "Syncing templates to: $PROJECT_ROOT"
echo ""
# 1. Sync memory-bank/
if [ -d "$MEMORY_BANK_SRC" ]; then
MEMORY_BANK_DST="$PROJECT_ROOT/memory-bank"
if [ -e "$MEMORY_BANK_DST" ] && [ "$FORCE" -eq 0 ]; then
echo "memory-bank/ already exists. Use -force to overwrite."
else
backup_if_exists "$MEMORY_BANK_DST"
mkdir -p "$MEMORY_BANK_DST"
cp -R "$MEMORY_BANK_SRC"/* "$MEMORY_BANK_DST/" 2>/dev/null || true
# Rename .template.md to .md
for f in "$MEMORY_BANK_DST"/*.template.md; do
[ -f "$f" ] || continue
newname="$(echo "$f" | sed 's/\.template\.md$/.md/')"
mv "$f" "$newname"
done
replace_placeholders_dir "$MEMORY_BANK_DST"
echo "Synced: memory-bank/"
fi
else
echo "Skip: memory-bank/ templates not found"
fi
# 2. Sync docs/prompts/
if [ -d "$PROMPTS_SRC" ]; then
PROMPTS_DST="$PROJECT_ROOT/docs/prompts"
if [ -e "$PROMPTS_DST" ] && [ "$FORCE" -eq 0 ]; then
echo "docs/prompts/ already exists. Use -force to overwrite."
else
backup_if_exists "$PROMPTS_DST"
mkdir -p "$PROJECT_ROOT/docs"
mkdir -p "$PROMPTS_DST"
cp -R "$PROMPTS_SRC"/* "$PROMPTS_DST/" 2>/dev/null || true
# Rename .template.md to .md (recursive)
find "$PROMPTS_DST" -type f -name '*.template.md' -print | while IFS= read -r f; do
newname="$(echo "$f" | sed 's/\.template\.md$/.md/')"
mv "$f" "$newname"
done
replace_placeholders_dir "$PROMPTS_DST"
echo "Synced: docs/prompts/"
fi
else
echo "Skip: prompts/ templates not found"
fi
# 3. Sync AGENTS.md
# Choose markers based on -full flag
if [ "$FULL" -eq 1 ]; then
MARKER_START="<!-- playbook:framework:start -->"
MARKER_END="<!-- playbook:framework:end -->"
SECTION_NAME="framework"
else
MARKER_START="<!-- playbook:templates:start -->"
MARKER_END="<!-- playbook:templates:end -->"
SECTION_NAME="templates"
fi
if [ -f "$AGENTS_SRC" ]; then
AGENTS_DST="$PROJECT_ROOT/AGENTS.md"
if [ ! -e "$AGENTS_DST" ]; then
# AGENTS.md doesn't exist: create from full template
cp "$AGENTS_SRC" "$AGENTS_DST"
replace_placeholders "$AGENTS_DST"
echo "Created: AGENTS.md"
else
# AGENTS.md exists: update or append section
# Extract snippet from template
snippet_content="$(awk -v start="$MARKER_START" -v end="$MARKER_END" '
$0 ~ start { found=1 }
found { print }
$0 ~ end { found=0 }
' "$AGENTS_SRC")"
if [ -z "$snippet_content" ]; then
echo "Skip: markers not found in template"
elif grep -q "$MARKER_START" "$AGENTS_DST"; then
# Has markers: replace content between markers in place
snippet_tmp="$(mktemp 2>/dev/null || echo "$AGENTS_DST.snippet.$timestamp")"
printf "%s\n" "$snippet_content" > "$snippet_tmp"
tmp="$(mktemp 2>/dev/null || echo "$AGENTS_DST.tmp.$timestamp")"
awk -v start="$MARKER_START" -v end="$MARKER_END" -v snippet="$snippet_tmp" '
BEGIN {
while ((getline line < snippet) > 0) { block[++n] = line }
close(snippet)
inblock = 0
replaced = 0
}
{
if (!replaced && $0 ~ start) {
for (i=1; i<=n; i++) print block[i]
inblock = 1
replaced = 1
next
}
if (inblock) {
if ($0 ~ end) { inblock = 0 }
next
}
print
}
' "$AGENTS_DST" > "$tmp"
mv "$tmp" "$AGENTS_DST"
rm -f "$snippet_tmp"
replace_placeholders "$AGENTS_DST"
echo "Updated: AGENTS.md ($SECTION_NAME section)"
else
# No markers: append snippet at the end
echo "" >> "$AGENTS_DST"
echo "$snippet_content" >> "$AGENTS_DST"
replace_placeholders "$AGENTS_DST"
echo "Appended: AGENTS.md ($SECTION_NAME section)"
fi
fi
else
echo "Skip: AGENTS.template.md not found"
fi
# 4. Sync AGENT_RULES.md
if [ -f "$AGENT_RULES_SRC" ]; then
AGENT_RULES_DST="$PROJECT_ROOT/AGENT_RULES.md"
if [ -e "$AGENT_RULES_DST" ] && [ "$FORCE" -eq 0 ]; then
echo "AGENT_RULES.md already exists. Use -force to overwrite."
else
backup_if_exists "$AGENT_RULES_DST"
cp "$AGENT_RULES_SRC" "$AGENT_RULES_DST"
replace_placeholders "$AGENT_RULES_DST"
echo "Synced: AGENT_RULES.md"
fi
else
echo "Skip: AGENT_RULES.template.md not found"
fi
# 5. Create TODO.md and CONFIRM.md if not exist
if [ ! -f "$PROJECT_ROOT/TODO.md" ]; then
cat > "$PROJECT_ROOT/TODO.md" <<'EOF'
# TODO
## Plan 1: [计划名称]
- [ ] 任务 1
- [ ] 任务 2
---
**最后更新**{{DATE}}
EOF
date_repl="$(escape_sed_replacement "$SYNC_DATE")"
if ! sed -i "s/{{DATE}}/$date_repl/g" "$PROJECT_ROOT/TODO.md" 2>/dev/null; then
sed "s/{{DATE}}/$date_repl/g" "$PROJECT_ROOT/TODO.md" > "$PROJECT_ROOT/TODO.md.tmp"
mv "$PROJECT_ROOT/TODO.md.tmp" "$PROJECT_ROOT/TODO.md"
fi
echo "Created: TODO.md"
fi
if [ ! -f "$PROJECT_ROOT/CONFIRM.md" ]; then
cat > "$PROJECT_ROOT/CONFIRM.md" <<'EOF'
# 待确认事项
## 待确认
<!-- 记录需要确认的事项 -->
## 已确认
<!-- 已确认的事项移到这里 -->
---
**最后更新**{{DATE}}
EOF
date_repl="$(escape_sed_replacement "$SYNC_DATE")"
if ! sed -i "s/{{DATE}}/$date_repl/g" "$PROJECT_ROOT/CONFIRM.md" 2>/dev/null; then
sed "s/{{DATE}}/$date_repl/g" "$PROJECT_ROOT/CONFIRM.md" > "$PROJECT_ROOT/CONFIRM.md.tmp"
mv "$PROJECT_ROOT/CONFIRM.md.tmp" "$PROJECT_ROOT/CONFIRM.md"
fi
echo "Created: CONFIRM.md"
fi
echo ""
echo "Done."
echo ""
echo "Next steps:"
echo " 1. Edit memory-bank/*.md to fill in project-specific content"
echo " 2. Replace remaining {{PLACEHOLDER}} values"
echo " 3. Run sync_standards.sh -langs <lang> to sync .agents/ rules"