#!/usr/bin/env sh set -eu # Sync project templates to target project. # - Copies templates/memory-bank/ -> /memory-bank/ # - Copies templates/prompts/ -> /docs/prompts/ # - Copies templates/AGENTS.template.md -> /AGENTS.md # - Copies templates/AGENT_RULES.template.md -> /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="" MARKER_END="" SECTION_NAME="framework" else MARKER_START="" MARKER_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 to sync .agents/ rules"