fix: preserve existing agent guide files during install
This commit is contained in:
parent
c27b85193f
commit
e16da89569
15
README.md
15
README.md
@ -56,7 +56,7 @@ RequirementsDoc ──▶ PRD ──▶ FeatureSummary ──▶ DevelopmentPlan
|
|||||||
|
|
||||||
## 安装 & 更新
|
## 安装 & 更新
|
||||||
|
|
||||||
一行命令搞定安装和更新。脚本会智能处理:新 skill 直接装,已有的对比更新,本地魔改自动备份。
|
一行命令搞定安装和更新。脚本会智能处理:新 skill 直接装,已有的对比更新,本地魔改自动备份;项目根目录的 `AGENTS.md` / `CLAUDE.md` 仅在缺失时生成,已存在则跳过。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Claude Code + Codex(推荐)
|
# Claude Code + Codex(推荐)
|
||||||
@ -78,9 +78,9 @@ bash /tmp/install-skills.sh both
|
|||||||
|
|
||||||
### 安装脚本行为
|
### 安装脚本行为
|
||||||
|
|
||||||
- `both` 模式:同时安装 `.codex/skills/` 和 `.claude/skills/`,并生成 `AGENTS.md` + `CLAUDE.md`
|
- `both` 模式:同时安装 `.codex/skills/` 和 `.claude/skills/`,并生成缺失的 `AGENTS.md` + `CLAUDE.md`;已有则跳过
|
||||||
- `codex` 模式:安装到 `.codex/skills/`,并在项目根目录生成或更新 `AGENTS.md`
|
- `codex` 模式:安装到 `.codex/skills/`,并在项目根目录生成缺失的 `AGENTS.md`;已有则跳过
|
||||||
- `claude` 模式:安装到 `.claude/skills/`,并在项目根目录生成或更新 `CLAUDE.md`
|
- `claude` 模式:安装到 `.claude/skills/`,并在项目根目录生成缺失的 `CLAUDE.md`;已有则跳过
|
||||||
- 如果传入自定义目标目录,脚本会优先安装 Codex 版 skills;目标路径包含 `.claude/skills` 时自动切到 Claude 版
|
- 如果传入自定义目标目录,脚本会优先安装 Codex 版 skills;目标路径包含 `.claude/skills` 时自动切到 Claude 版
|
||||||
|
|
||||||
### 推荐用法
|
### 推荐用法
|
||||||
@ -98,6 +98,13 @@ bash /tmp/install-skills.sh both
|
|||||||
| 本地魔改过 + 上游有更新 | 写入上游新版,本地版备份为 `SKILL.md.local.bak` |
|
| 本地魔改过 + 上游有更新 | 写入上游新版,本地版备份为 `SKILL.md.local.bak` |
|
||||||
| 本地和上游一致 | 跳过 |
|
| 本地和上游一致 | 跳过 |
|
||||||
|
|
||||||
|
项目根目录 guide 文件单独处理:
|
||||||
|
|
||||||
|
| 情况 | 处理方式 |
|
||||||
|
|------|---------|
|
||||||
|
| `AGENTS.md` / `CLAUDE.md` 不存在 | 从模板创建 |
|
||||||
|
| `AGENTS.md` / `CLAUDE.md` 已存在 | 跳过,保持原样 |
|
||||||
|
|
||||||
恢复本地版本:`mv SKILL.md.local.bak SKILL.md`
|
恢复本地版本:`mv SKILL.md.local.bak SKILL.md`
|
||||||
对比差异:`diff SKILL.md SKILL.md.local.bak`
|
对比差异:`diff SKILL.md SKILL.md.local.bak`
|
||||||
|
|
||||||
|
|||||||
27
install.sh
27
install.sh
@ -91,6 +91,23 @@ sync_file() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sync_guide_file() {
|
||||||
|
local src="$1"
|
||||||
|
local dst="$2"
|
||||||
|
local create_msg="$3"
|
||||||
|
local skip_msg="$4"
|
||||||
|
|
||||||
|
if [ ! -f "$dst" ]; then
|
||||||
|
mkdir -p "$(dirname "$dst")"
|
||||||
|
cp "$src" "$dst"
|
||||||
|
log_info "$create_msg"
|
||||||
|
TOTAL_NEW=$((TOTAL_NEW + 1))
|
||||||
|
else
|
||||||
|
log_info "$skip_msg"
|
||||||
|
TOTAL_SKIPPED=$((TOTAL_SKIPPED + 1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
install_layout() {
|
install_layout() {
|
||||||
local input="$1"
|
local input="$1"
|
||||||
local skill_dir
|
local skill_dir
|
||||||
@ -147,11 +164,11 @@ install_layout() {
|
|||||||
|
|
||||||
guide_src_path="$TMP_DIR/$GUIDE_SRC"
|
guide_src_path="$TMP_DIR/$GUIDE_SRC"
|
||||||
if [ -f "$guide_src_path" ]; then
|
if [ -f "$guide_src_path" ]; then
|
||||||
sync_file \
|
sync_guide_file \
|
||||||
"$guide_src_path" \
|
"$guide_src_path" \
|
||||||
"$GUIDE_DST" \
|
"$GUIDE_DST" \
|
||||||
"✨ 新增项目引导: $GUIDE_DST" \
|
"✨ 新增项目引导: ${GUIDE_DST}" \
|
||||||
"🔄 更新项目引导: $GUIDE_DST (本地版本已备份为 ${GUIDE_DST}.local.bak)"
|
"⏭️ 跳过项目引导: ${GUIDE_DST}(已存在,保持原样)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
INSTALLED_TARGETS="${INSTALLED_TARGETS}${INSTALLED_TARGETS:+, }$TARGET"
|
INSTALLED_TARGETS="${INSTALLED_TARGETS}${INSTALLED_TARGETS:+, }$TARGET"
|
||||||
@ -188,12 +205,12 @@ echo " 📁 目标目录: $INSTALLED_TARGETS"
|
|||||||
echo " 📦 版本: $VERSION"
|
echo " 📦 版本: $VERSION"
|
||||||
echo ""
|
echo ""
|
||||||
echo " ✨ 新增: $TOTAL_NEW"
|
echo " ✨ 新增: $TOTAL_NEW"
|
||||||
echo " 🔄 更新: $TOTAL_UPDATED(本地版本已备份为 .local.bak)"
|
echo " 🔄 更新: ${TOTAL_UPDATED}(本地版本已备份为 .local.bak)"
|
||||||
echo " ⏭️ 无变化: $TOTAL_SKIPPED"
|
echo " ⏭️ 无变化: $TOTAL_SKIPPED"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
if [ "$TOTAL_UPDATED" -gt 0 ]; then
|
if [ "$TOTAL_UPDATED" -gt 0 ]; then
|
||||||
log_warn "有 $TOTAL_UPDATED 个文件被更新,本地修改已备份为 .local.bak"
|
log_warn "有 ${TOTAL_UPDATED} 个文件被更新,本地修改已备份为 .local.bak"
|
||||||
log_warn "如需恢复本地版本: mv SKILL.md.local.bak SKILL.md"
|
log_warn "如需恢复本地版本: mv SKILL.md.local.bak SKILL.md"
|
||||||
log_warn "如需对比差异: diff SKILL.md SKILL.md.local.bak"
|
log_warn "如需对比差异: diff SKILL.md SKILL.md.local.bak"
|
||||||
fi
|
fi
|
||||||
|
|||||||
164
tests/install.sh.test
Normal file
164
tests/install.sh.test
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
INSTALL_SCRIPT="$REPO_ROOT/install.sh"
|
||||||
|
TMP_ROOT=""
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "FAIL: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_file_contains() {
|
||||||
|
local file="$1"
|
||||||
|
local expected="$2"
|
||||||
|
grep -F "$expected" "$file" >/dev/null || fail "expected '$expected' in $file"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_output_contains() {
|
||||||
|
local output="$1"
|
||||||
|
local expected="$2"
|
||||||
|
[[ "$output" == *"$expected"* ]] || fail "expected output to contain '$expected'"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equals() {
|
||||||
|
local actual="$1"
|
||||||
|
local expected="$2"
|
||||||
|
[[ "$actual" == "$expected" ]] || fail "expected '$expected', got '$actual'"
|
||||||
|
}
|
||||||
|
|
||||||
|
make_upstream_repo() {
|
||||||
|
local dir="$1"
|
||||||
|
local version="$2"
|
||||||
|
|
||||||
|
mkdir -p "$dir/.codex/skills/demo" "$dir/.claude/skills/demo"
|
||||||
|
|
||||||
|
cat >"$dir/.codex/skills/demo/SKILL.md" <<EOF
|
||||||
|
# demo codex $version
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat >"$dir/.claude/skills/demo/SKILL.md" <<EOF
|
||||||
|
# demo claude $version
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat >"$dir/AGENTS.md.template" <<EOF
|
||||||
|
# AGENTS template $version
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat >"$dir/CLAUDE.md.template" <<EOF
|
||||||
|
# CLAUDE template $version
|
||||||
|
EOF
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$dir"
|
||||||
|
git init -q
|
||||||
|
git config user.name "Test User"
|
||||||
|
git config user.email "test@example.com"
|
||||||
|
git add .
|
||||||
|
git commit -qm "upstream $version"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
make_git_wrapper() {
|
||||||
|
local wrapper_dir="$1"
|
||||||
|
local real_git="$2"
|
||||||
|
|
||||||
|
mkdir -p "$wrapper_dir"
|
||||||
|
|
||||||
|
cat >"$wrapper_dir/git" <<EOF
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ "\${1:-}" == "clone" ]]; then
|
||||||
|
dst="\${@: -1}"
|
||||||
|
exec "$real_git" clone --quiet "\$FAKE_UPSTREAM_REPO" "\$dst"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$real_git" "\$@"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x "$wrapper_dir/git"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_install() {
|
||||||
|
local target_dir="$1"
|
||||||
|
local mode="$2"
|
||||||
|
(
|
||||||
|
cd "$target_dir"
|
||||||
|
PATH="$GIT_WRAPPER_DIR:$PATH" FAKE_UPSTREAM_REPO="$UPSTREAM_REPO" bash "$INSTALL_SCRIPT" "$mode"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
local output
|
||||||
|
local guide_before
|
||||||
|
|
||||||
|
TMP_ROOT="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$TMP_ROOT"' EXIT
|
||||||
|
|
||||||
|
UPSTREAM_REPO="$TMP_ROOT/upstream"
|
||||||
|
GIT_WRAPPER_DIR="$TMP_ROOT/bin"
|
||||||
|
export UPSTREAM_REPO GIT_WRAPPER_DIR
|
||||||
|
|
||||||
|
make_upstream_repo "$UPSTREAM_REPO" "v1"
|
||||||
|
make_git_wrapper "$GIT_WRAPPER_DIR" "$(command -v git)"
|
||||||
|
|
||||||
|
mkdir -p "$TMP_ROOT/project-codex"
|
||||||
|
output="$(run_install "$TMP_ROOT/project-codex" codex)"
|
||||||
|
assert_file_contains "$TMP_ROOT/project-codex/.codex/skills/demo/SKILL.md" "demo codex v1"
|
||||||
|
assert_file_contains "$TMP_ROOT/project-codex/AGENTS.md" "AGENTS template v1"
|
||||||
|
|
||||||
|
printf 'custom agents\n' >"$TMP_ROOT/project-codex/AGENTS.md"
|
||||||
|
guide_before="$(cat "$TMP_ROOT/project-codex/AGENTS.md")"
|
||||||
|
(
|
||||||
|
cd "$UPSTREAM_REPO"
|
||||||
|
printf '# demo codex v2\n' > .codex/skills/demo/SKILL.md
|
||||||
|
printf '# AGENTS template v2\n' > AGENTS.md.template
|
||||||
|
git add .codex/skills/demo/SKILL.md AGENTS.md.template
|
||||||
|
git commit -qm "upstream v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
output="$(run_install "$TMP_ROOT/project-codex" codex)"
|
||||||
|
assert_equals "$(cat "$TMP_ROOT/project-codex/AGENTS.md")" "$guide_before"
|
||||||
|
[[ ! -f "$TMP_ROOT/project-codex/AGENTS.md.local.bak" ]] || fail "AGENTS.md.local.bak should not exist"
|
||||||
|
assert_output_contains "$output" "跳过项目引导"
|
||||||
|
assert_file_contains "$TMP_ROOT/project-codex/.codex/skills/demo/SKILL.md" "demo codex v2"
|
||||||
|
[[ -f "$TMP_ROOT/project-codex/.codex/skills/demo/SKILL.md.local.bak" ]] || fail "skill backup should exist"
|
||||||
|
|
||||||
|
mkdir -p "$TMP_ROOT/project-claude"
|
||||||
|
output="$(run_install "$TMP_ROOT/project-claude" claude)"
|
||||||
|
assert_file_contains "$TMP_ROOT/project-claude/.claude/skills/demo/SKILL.md" "demo claude v1"
|
||||||
|
assert_file_contains "$TMP_ROOT/project-claude/CLAUDE.md" "CLAUDE template v1"
|
||||||
|
|
||||||
|
printf 'custom claude\n' >"$TMP_ROOT/project-claude/CLAUDE.md"
|
||||||
|
(
|
||||||
|
cd "$UPSTREAM_REPO"
|
||||||
|
printf '# demo claude v2\n' > .claude/skills/demo/SKILL.md
|
||||||
|
printf '# CLAUDE template v2\n' > CLAUDE.md.template
|
||||||
|
git add .claude/skills/demo/SKILL.md CLAUDE.md.template
|
||||||
|
git commit -qm "upstream claude v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
output="$(run_install "$TMP_ROOT/project-claude" claude)"
|
||||||
|
assert_equals "$(cat "$TMP_ROOT/project-claude/CLAUDE.md")" "custom claude"
|
||||||
|
[[ ! -f "$TMP_ROOT/project-claude/CLAUDE.md.local.bak" ]] || fail "CLAUDE.md.local.bak should not exist"
|
||||||
|
assert_output_contains "$output" "跳过项目引导"
|
||||||
|
assert_file_contains "$TMP_ROOT/project-claude/.claude/skills/demo/SKILL.md" "demo claude v2"
|
||||||
|
|
||||||
|
mkdir -p "$TMP_ROOT/project-both"
|
||||||
|
printf 'team agents\n' >"$TMP_ROOT/project-both/AGENTS.md"
|
||||||
|
printf 'team claude\n' >"$TMP_ROOT/project-both/CLAUDE.md"
|
||||||
|
output="$(run_install "$TMP_ROOT/project-both" both)"
|
||||||
|
assert_equals "$(cat "$TMP_ROOT/project-both/AGENTS.md")" "team agents"
|
||||||
|
assert_equals "$(cat "$TMP_ROOT/project-both/CLAUDE.md")" "team claude"
|
||||||
|
assert_file_contains "$TMP_ROOT/project-both/.codex/skills/demo/SKILL.md" "demo codex v2"
|
||||||
|
assert_file_contains "$TMP_ROOT/project-both/.claude/skills/demo/SKILL.md" "demo claude v2"
|
||||||
|
assert_output_contains "$output" "跳过项目引导: AGENTS.md"
|
||||||
|
assert_output_contains "$output" "跳过项目引导: CLAUDE.md"
|
||||||
|
|
||||||
|
echo "PASS"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
Loading…
x
Reference in New Issue
Block a user