playbook/scripts/sync_templates.sh

324 lines
9.3 KiB
Bash

#!/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
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"