350 lines
9.6 KiB
Bash
350 lines
9.6 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> # sync to specified project
|
||
# sh scripts/sync_templates.sh --project-name "MyProject" --date "2026-01-20"
|
||
#
|
||
# Options:
|
||
# --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
|
||
|
||
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-name)
|
||
PROJECT_NAME="$2"
|
||
shift 2
|
||
;;
|
||
--date)
|
||
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] [project-root]
|
||
|
||
Options:
|
||
--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 /path/to/project
|
||
sh scripts/sync_templates.sh --full /path/to/project
|
||
EOF
|
||
exit 0
|
||
;;
|
||
-*)
|
||
echo "ERROR: Unknown option: $1" >&2
|
||
exit 1
|
||
;;
|
||
*)
|
||
PROJECT_ROOT="$1"
|
||
shift
|
||
;;
|
||
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 to sync .agents/ rules"
|