关于 swain-sync
用于同步仓库变更的工作流技能,涵盖与上游同步、暂存、检查、提交和推送。它包含会话、密钥、Gitignore 模式、工件检查及提交钩子的保护步骤。
主要功能
- 01检查是否存在活动会话
- 02获取上游并执行变基或合并
- 03暂存变更并排除密钥文件
- 04检查 Gitignore 与仓库工件
- 05生成 Conventional Commit 提交信息
- 06提交并推送变更
适用场景
- 01提交前将分支与上游同步
- 02通过检查暂存并提交仓库更新
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ cristoslc/ swain --skill swain-sync安装后在 Claude Code 中输入/swain-sync确认已加载。
附带文件scripts/swain-trunk.shscripts/test-swain-trunk.sh
详细介绍
在执行任何会更改状态的操作之前,请检查是否存在活动会话:
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
bash "$REPO_ROOT/.agents/bin/swain-session-check.sh" 2>/dev/null
如果 JSON 输出中的 "status" 不是 "active",请告知操作员:“没有活动会话 — 要用 /swain-init 启动一个吗?”如果他们忽略此提示,则继续。
按顺序执行以下步骤,除非某个决策点明确要求确认,否则不要暂停等待确认。
将此任务委派给子 Agent,以保持主对话线程整洁。请在 Agent 提示中包含这些指令的全文,因为子 Agent 无法直接读取技能文件。
步骤 1 — 检测 worktree 上下文并获取/变基上游更改
首先,检测当前是否在 git 链接 worktree 中运行:
GIT_COMMON=$(git rev-parse --git-common-dir)
GIT_DIR=$(git rev-parse --git-dir)
IN_WORKTREE=$( [ "$GIT_COMMON" != "$GIT_DIR" ] && echo "yes" || echo "no" )
REPO_ROOT=$(git rev-parse --show-toplevel)
TRUNK=$(bash "$REPO_ROOT/.agents/bin/swain-trunk.sh")
IN_WORKTREE=yes 表示当前目录位于链接 worktree 中(例如 .worktrees/agent-abc123)。在步骤 3、步骤 6 和会话书签步骤中使用此标志。
接下来,检查当前分支是否有上游跟踪分支:
git --no-pager rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null
如果有上游分支,请先获取并变基,以纳入上游更改,然后再暂存或提交:
git fetch origin
如果存在本地更改(工作树不干净),请先暂存这些更改。使用定向 pop,避免干扰其他 worktree 的暂存内容(SPEC-252:共享暂存是冲突来源):
BRANCH=$(git rev-parse --abbrev-ref HEAD)
STASH_MSG="swain-sync: auto-stash [$BRANCH]"
git stash push -m "$STASH_MSG"
STASH_REF=$(git stash list --format='%gd %s' | grep -F "$STASH_MSG" | head -1 | cut -d' ' -f1)
git --no-pager rebase origin/$BRANCH
git stash pop "$STASH_REF"
如果弹出暂存后变基发生冲突,请中止并报告:
git rebase --abort # if rebase itself conflicts
git stash pop "$STASH_REF" # recover stashed changes (targeted, not bare pop)
向用户显示冲突文件并停止。不要强制推送或丢弃更改。
如果没有上游(@{u} 返回错误)且 IN_WORKTREE=yes,则 worktree 分支没有远程跟踪分支。合并主干分支,以整合 Agent 的更改以及分支创建后合入的更改:
git fetch origin
git merge "origin/$TRUNK" --no-edit
如果合并发生冲突,请报告冲突并停止。不要尝试自动解决。
如果无法获取 origin,则跳过获取/变基并继续步骤 2。
如果没有上游 且 IN_WORKTREE=no(主 worktree、新建分支),则完全跳过此步骤。
步骤 2 — 检查工作树
git --no-pager status
git --no-pager diff # unstaged changes
git --no-pager diff --cached # already-staged changes
如果工作树完全干净且没有任何内容需要推送,请报告这一情况并停止。
步骤 3 — 暂存更改
识别疑似机密的文件(.env、*.pem、*_rsa、credentials.*、secrets.*)。如果存在此类文件,请警告用户,并将其排除在暂存之外。
如果更改的文件不超过 10 个(不包括机密文件),请逐个暂存:
git add file1 file2 ...
如果更改的文件超过 10 个,请暂存所有文件,然后取消暂存机密文件:
git add -A
git reset HEAD -- <secret-file-1> <secret-file-2> ...
步骤 3.5 — 检查 Gitignore
提交前,请检查 .gitignore 是否规范。此步骤为阻断步骤 — 如果缺少相关模式,请停止,并要求用户修复 .gitignore 后再继续。
1. 检查是否存在
如果仓库根目录中没有 .gitignore 文件:
停止:未找到
.gitignore文件。请在提交前创建一个 — 否则机密、构建产物和操作系统文件可能会进入 git 历史记录。 最简起始配置:curl -sL https://www.toptal.com/developers/gitignore/api/macos,linux,node,python > .gitignore
**停止执行。**不要提交。
2. 检测相关模式
根据磁盘上实际存在的内容,检查哪些模式与此仓库相关:
| 模式 | 相关条件 |
|---|---|
.env | 存在 .env.example,或存在任何未跟踪/已跟踪的 .env 或 .env.* 文件(不包括 .env.example),或 package.json 或 requirements.txt 中出现 dotenv |
node_modules/ | 仓库根目录或任意子目录中存在 package.json |
__pycache__/ | 仓库中存在任何 *.py 文件 |
*.pyc | 与 __pycache__/ 相同 |
.DS_Store | 仓库位于 macOS 上(uname 返回 Darwin) |
对于每个相关模式,检查 .gitignore 是否包含该模式(完全匹配或子字符串匹配)。收集缺失的模式。
3. 判断是否阻止
-
如果没有缺少相关模式:此步骤不输出内容。继续执行第 3.7 步。
-
如果缺少任何相关模式:停止并报告:
STOP:
.gitignore缺少与此仓库相关的模式:.env— 找到了.env.example;如果没有此模式,本地.env文件可能会被提交node_modules/— 找到了package.json
提交前添加缺少的模式: echo ".env" >> .gitignore echo "node_modules/" >> .gitignore
若要永久跳过特定模式检查(有意省略),请在
.gitignore中添加注释:swain-sync: allow .env
停止执行。 用户解决此问题前,不要提交。
4. 跳过逻辑
如果 .gitignore 中包含针对某个模式的 # swain-sync: allow <pattern>,则将该模式视为有意省略,不要标记它。
第 3.7 步 — ADR 合规性检查
如果已修改的文件包含任何 swain 工件(docs/spec/、docs/epic/、docs/vision/、docs/research/、docs/journey/、docs/persona/、docs/runbook/、docs/design/、docs/train/),则对每个已修改的工件运行 ADR 合规性检查:
bash "$REPO_ROOT/.agents/bin/adr-check.sh" <artifact-path>
对于存在发现的每个工件(退出代码 1 = 建议性 RELEVANT 发现,退出代码 2 = 可操作的 DEAD_REF 或 STALE 发现),收集输出,并在所有检查完成后汇总为一条警告:
ADR 合规性:N 个工件存在可能需要关注的问题。 <精简的发现摘要>
此步骤仅供参考——只会发出警告,不会阻止提交。无论如何都继续执行第 4 步。
如果找不到 adr-check.sh 脚本,或脚本以退出代码 3 失败,则静默跳过——此检查仅适用于安装了 swain-design 的仓库。
第 3.8 步 — 设计漂移检查
不带参数运行 design-check.sh(扫描所有启用的 DESIGN),以检测设计与代码之间的漂移:
bash "$REPO_ROOT/.agents/bin/design-check.sh" 2>/dev/null
对于存在发现的每个 DESIGN(sourcecode-refs 过时或损坏),收集输出,并在检查完成后汇总为一条警告:
设计漂移:N 个 DESIGN 的
sourcecode-refs已过时或损坏。 <精简的发现摘要>
此步骤仅供参考——只会发出警告,不会阻止提交。无论如何都继续执行第 4 步。
如果找不到 design-check.sh 脚本,或脚本以退出代码 2 失败,则静默跳过——此检查仅适用于安装了 swain-design 的仓库。
第 3.85 步 — README 对照检查(ADR-023)
如果 README.md 存在,且已暂存的更改包含可能影响 README 声明的工件或代码,则运行轻量级漂移检查:
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
if [ -f "$REPO_ROOT/README.md" ]; then
STAGED_ARTIFACTS=$(git diff --cached --name-only | grep -E "^docs/(spec|epic|vision|design)/" || true)
STAGED_CODE=$(git diff --cached --name-only | grep -vE "^docs/" | grep -E "\.(py|js|ts|sh|go|rs)$" || true)
if [ -n "$STAGED_ARTIFACTS" ] || [ -n "$STAGED_CODE" ]; then
echo "README_CHECK: staged changes touch artifacts or code — verify README alignment"
fi
fi
此步骤仅供参考——提醒 Agent 在提交涉及可能影响 README 声明的代码或工件时检查 README 的准确性。此步骤不会阻止提交。完整的 README 门禁位于 swain-release(第 5.7 步)。
第 3.9 步 — 工件编号冲突检查
运行 detect-duplicate-numbers.sh,查找合并或并发 worktree 工作引入的重复工件编号:
bash "$REPO_ROOT/.agents/bin/detect-duplicate-numbers.sh" 2>/dev/null
如果发现冲突(退出代码 1),此步骤会阻止提交——解决冲突前不要提交:
检测到工件编号冲突: <冲突输出>
可自动修复:运行
fix-collisions.sh重新编号较新的工件。 或运行fix-collisions.sh --dry-run预览更改。
主动提出自动运行 fix-collisions.sh。如果操作员接受,则运行该脚本、暂存更改并继续执行第 4 步。如果操作员拒绝,则停止执行——不要在编号重复的情况下提交。
如果找不到该脚本,则静默跳过——此检查仅适用于安装了 swain-design 的仓库。
步骤 4 — 生成提交消息
阅读暂存的差异(git --no-pager diff --cached),并编写一条提交消息,要求:
- 以与主要变更类型相符的约定式提交前缀开头:
feat— 新功能或能力fix— 修复错误docs— 仅修改文档chore— 工具、依赖、配置变更,不涉及行为变化refactor— 重构,不涉及行为变化test— 添加或修复测试
- 添加简洁的祈使语气主题行(≤ 72 个字符)。
- 如果差异并非简单变更,添加简短正文(2–5 行),概述原因,而不只是变更内容。
- 为每位模型参与者附加一条
Co-Authored-By标记。从系统提示中确定模型名称(例如,Claude Opus 4.6、Gemini 2.5 Pro、GLM-5.1)。如果无法获知模型身份,则使用AI Assistant作为备用名称。如果会话中涉及由不同模型运行的子 Agent,则每个模型都要附加一条标记——先写监督模型,再将每个子 Agent 模型各自写在单独一行。不要硬编码模型名称。
示例格式(单个模型):
feat(terraform): add Cloudflare DNS module for hub provisioning
Operators can now point DNS at Cloudflare without migrating their zone.
Module is activated by dns_provider=cloudflare and requires only
CLOUDFLARE_API_TOKEN — no other provider credentials are validated.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
示例格式(监督模型 + 子 Agent):
feat(terraform): add Cloudflare DNS module for hub provisioning
Operators can now point DNS at Cloudflare without migrating their zone.
Module is activated by dns_provider=cloudflare and requires only
CLOUDFLARE_API_TOKEN — no other provider credentials are validated.
Co-Authored-By: GLM-5.1 <noreply@unknown>
Co-Authored-By: Kimi-K2.5 <noreply@unknown>
步骤 4.5 — 提交前钩子检查
检查是否已配置提交前钩子:
test -f .pre-commit-config.yaml && command -v pre-commit >/dev/null 2>&1 && echo "hooks-configured" || echo "no-hooks"
如果为 no-hooks,发出一次性警告(如果本次会话中已发出相同警告,则不要重复):
WARN:未配置提交前钩子。运行
/swain-init以设置安全扫描。
无论如何都继续执行步骤 5——钩子是建议配置项,但并非必需。
步骤 5 — 提交
git --no-pager commit -m "$(cat <<'EOF'
<generated message here>
EOF
)"
使用 heredoc,确保多行消息可以在 shell 中保留,而不需要转义。
**重要:**绝不使用 --no-verify。如果已安装提交前钩子,则必须运行。不得绕过。
如果提交因提交前钩子拒绝而失败:
- 解析输出,确定失败的钩子及其发现的问题
- 清晰地展示发现的问题:
提交前钩子失败: gitleaks:发现 2 个问题(描述标记出的内容)
修复问题后,再次运行
/swain-sync。 抑制误报:添加到.gitleaksignore - 停止执行——不要推送。不要自动重试。
步骤 6 — 推送
**如果 IN_WORKTREE=yes:**获取并整合上游变更,然后将工作树中的提交直接推送到 trunk(开发分支):
# Always fetch and merge trunk before the first push attempt.
# This prevents avoidable rejections when trunk moved since the worktree was created.
git fetch origin
git merge "origin/$TRUNK" --no-edit || {
echo "Merge conflict with upstream trunk. Resolve before pushing."
git merge --abort
exit 1
}
# Link safety — scan changed files for worktree-specific path links (SPEC-216/217/218)
# Runs after merge, before push, so any auto-fixes land in the same push.
DETECT_SCRIPT="$(git rev-parse --show-toplevel)/.agents/bin/detect-worktree-links.sh"
RESOLVE_SCRIPT="$(git rev-parse --show-toplevel)/.agents/bin/resolve-worktree-links.sh"
if [ -x "$DETECT_SCRIPT" ]; then
MERGE_BASE=$(git merge-base HEAD "origin/$TRUNK" 2>/dev/null || true)
CHANGED_FILES=$([ -n "$MERGE_BASE" ] && git diff --name-only "$MERGE_BASE" HEAD 2>/dev/null || true)
if [ -n "$CHANGED_FILES" ]; then
if ! echo "$CHANGED_FILES" | xargs "$DETECT_SCRIPT" --repo-root "$(git rev-parse --show-toplevel)" > /dev/null 2>&1; then
echo "[link-safety] Found suspicious links. Resolving automatically..."
if ! echo "$CHANGED_FILES" | xargs "$RESOLVE_SCRIPT" --repo-root "$(git rev-parse --show-toplevel)"; then
echo "[link-safety] UNRESOLVABLE links remain — merge aborted. Fix before pushing."
exit 1
fi
git add -u
ORIG_MSG=$(git log -1 --pretty=%B)
git commit --amend -m "$ORIG_MSG
[link-safety: auto-resolved worktree path links]"
fi
fi
fi
MAX_RETRIES=3
ATTEMPT=0
while [ $ATTEMPT -lt $MAX_RETRIES ]; do
git push origin "HEAD:$TRUNK" && break
ATTEMPT=$((ATTEMPT + 1))
if [ $ATTEMPT -lt $MAX_RETRIES ]; then
echo "Push rejected (attempt $ATTEMPT/$MAX_RETRIES). Fetching and re-merging..."
git fetch origin
git merge "origin/$TRUNK" --no-edit || {
echo "Merge conflict during retry. Reporting to operator."
git merge --abort
break
}
# Run tests on the merged result before retrying push
# (project-specific test command — detect from project structure)
fi
done
if [ $ATTEMPT -ge $MAX_RETRIES ]; then
echo "Push failed after $MAX_RETRIES attempts. Reporting to operator."
fi
如果因分支保护规则或必须经过评审而导致推送被拒绝(检查拒绝消息),则改为创建 PR:
BRANCH=$(git rev-parse --abbrev-ref HEAD)
SUBJECT=$(git log -1 --pretty=format:'%s')
BODY=$(git log -1 --pretty=format:'%b')
gh pr create --base "$TRUNK" --head "$BRANCH" --title "$SUBJECT" --body "$BODY"
报告 PR URL。不要重试推送。接着执行下方的工作树清理步骤。
推送成功或创建 PR 后,将锁文件标记为可清理(SPEC-249、EPIC-056)。swain-sync 不会自行移除工作树——清理工作会延后到 bin/swain 执行;它会在运行时退出后运行,并在清理前验证提交哈希。
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
BRANCH=$(git rev-parse --abbrev-ref HEAD)
# Mark lockfile ready_for_cleanup if lockfile library is available
LOCKFILE_SCRIPT="$REPO_ROOT/.agents/bin/swain-lockfile.sh"
if [ -f "$LOCKFILE_SCRIPT" ]; then
bash "$LOCKFILE_SCRIPT" mark-ready "$BRANCH" 2>/dev/null || true
echo "Worktree marked ready_for_cleanup — bin/swain will prune after session ends."
else
echo "Note: lockfile library not found — worktree cleanup must be done manually."
fi
不要从 swain-sync 调用 git worktree remove。运行时无法离开自身的工作树目录(大多数运行时无法持久保留 CWD),而在运行中的运行时下移除工作树会导致 ENOENT 错误。
**If `IN_WORKTREE=no`** (main worktree, normal case):
```bash
git push # or: git push -u origin HEAD (if no upstream)
如果推送因历史分歧而失败(步骤 1 中已执行变基,因此不应该发生,但此处作为安全措施):
git --no-pager pull --rebase
git push
步骤 7 — 验证
运行 git --no-pager status 和 git --no-pager log --oneline -3,验证推送已生效,并向用户展示最终状态。不要请求确认——直接报告结果。
重建索引(SPEC-047)
提交前(暂存后、步骤 5 前),检查是否有任何工件索引文件(list-*.md)已过期。如果重建脚本存在,则针对有变更被暂存的每种工件类型运行该脚本:
REBUILD_SCRIPT="$REPO_ROOT/.agents/bin/rebuild-index.sh"
if [[ -x "$REBUILD_SCRIPT" ]]; then
# Detect which types had staged changes
for type in spec epic initiative spike adr persona runbook design vision journey train; do
if git diff --cached --name-only | grep -q "^docs/$type/"; then
bash "$REBUILD_SCRIPT" "$type"
git add "docs/$type/list-${type}.md" 2>/dev/null || true
fi
done
fi
这样可以确保本次会话的提交生效时,索引是最新的。
会话书签
推送成功后,更新书签。使用 $REPO_ROOT(在步骤 1 中设置)作为搜索根目录,以确保在主工作树和关联工作树中都能正常工作:
bash "$REPO_ROOT/.agents/bin/swain-bookmark.sh" "Pushed {n} commits to {branch}"