mcpField
S
swain-sync

swain-sync

github.com/cristoslc开发288 次安装26 周前更新
代码仓库

关于 swain-sync

用于同步仓库变更的工作流技能,涵盖与上游同步、暂存、检查、提交和推送。它包含会话、密钥、Gitignore 模式、工件检查及提交钩子的保护步骤。

主要功能

  1. 01检查是否存在活动会话
  2. 02获取上游并执行变基或合并
  3. 03暂存变更并排除密钥文件
  4. 04检查 Gitignore 与仓库工件
  5. 05生成 Conventional Commit 提交信息
  6. 06提交并推送变更

适用场景

  1. 01提交前将分支与上游同步
  2. 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。如果已安装提交前钩子,则必须运行。不得绕过。

如果提交因提交前钩子拒绝而失败:

  1. 解析输出,确定失败的钩子及其发现的问题
  2. 清晰地展示发现的问题:

    提交前钩子失败: gitleaks:发现 2 个问题(描述标记出的内容)

    修复问题后,再次运行 /swain-sync。 抑制误报:添加到 .gitleaksignore

  3. 停止执行——不要推送。不要自动重试。

步骤 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}"

评论

支持 Markdown