Add codex-task-server-sync skill
这个提交包含在:
@@ -0,0 +1,217 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
project_dir=""
|
||||
host_label=""
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: sync-once.sh --project-dir <path> [--host-label <label>]
|
||||
|
||||
Synchronize one git-backed Codex workspace against its configured origin:
|
||||
- autosave dirty work into a host-stamped commit
|
||||
- fetch origin
|
||||
- rebase onto the configured branch
|
||||
- push
|
||||
- retry once after a non-fast-forward rejection
|
||||
- stop and surface runtime status on conflicts
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--project-dir)
|
||||
project_dir="$2"
|
||||
shift 2
|
||||
;;
|
||||
--host-label)
|
||||
host_label="$2"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$project_dir" ]]; then
|
||||
script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
project_dir="$(cd "$script_dir/../.." && pwd)"
|
||||
fi
|
||||
|
||||
project_dir="$(cd "$project_dir" && pwd)"
|
||||
host_label="${host_label:-$(hostname -s 2>/dev/null || hostname)}"
|
||||
|
||||
manifest_path="$project_dir/.codex-sync/manifest.json"
|
||||
runtime_dir="$project_dir/.codex-sync/runtime"
|
||||
status_path="$runtime_dir/status.json"
|
||||
error_log="$runtime_dir/last-error.log"
|
||||
blocked_path="$runtime_dir/blocked"
|
||||
lock_dir="$runtime_dir/lock"
|
||||
|
||||
mkdir -p "$runtime_dir"
|
||||
|
||||
write_status() {
|
||||
local state="$1"
|
||||
local message="$2"
|
||||
python3 - "$status_path" "$state" "$host_label" "$message" "$project_dir" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
|
||||
path, state, host_label, message, project_dir = sys.argv[1:]
|
||||
payload = {
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
"state": state,
|
||||
"host": host_label,
|
||||
"projectDir": project_dir,
|
||||
"message": message,
|
||||
}
|
||||
with open(path, "w", encoding="utf-8") as handle:
|
||||
json.dump(payload, handle, indent=2)
|
||||
handle.write("\n")
|
||||
PY
|
||||
}
|
||||
|
||||
read_manifest_field() {
|
||||
local expression="$1"
|
||||
python3 - "$manifest_path" "$expression" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
|
||||
path = sys.argv[1]
|
||||
expr = sys.argv[2]
|
||||
data = json.load(open(path, "r", encoding="utf-8"))
|
||||
value = data
|
||||
for part in expr.split("."):
|
||||
value = value.get(part, "") if isinstance(value, dict) else ""
|
||||
print(value if value is not None else "")
|
||||
PY
|
||||
}
|
||||
|
||||
abort_rebase_if_needed() {
|
||||
if [[ -d "$project_dir/.git/rebase-apply" || -d "$project_dir/.git/rebase-merge" ]]; then
|
||||
git -C "$project_dir" rebase --abort >>"$error_log" 2>&1 || true
|
||||
fi
|
||||
}
|
||||
|
||||
block_sync() {
|
||||
local reason="$1"
|
||||
abort_rebase_if_needed
|
||||
{
|
||||
printf 'timestamp=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
printf 'host=%s\n' "$host_label"
|
||||
printf 'reason=%s\n' "$reason"
|
||||
} >"$blocked_path"
|
||||
write_status "blocked" "$reason"
|
||||
exit 1
|
||||
}
|
||||
|
||||
run_git() {
|
||||
git -C "$project_dir" "$@" >>"$error_log" 2>&1
|
||||
}
|
||||
|
||||
if [[ ! -f "$manifest_path" ]]; then
|
||||
write_status "error" "Missing manifest at $manifest_path"
|
||||
echo "Missing manifest at $manifest_path" >"$error_log"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! mkdir "$lock_dir" 2>/dev/null; then
|
||||
write_status "skipped" "Another sync run is already in progress"
|
||||
exit 0
|
||||
fi
|
||||
trap 'rmdir "$lock_dir" 2>/dev/null || true' EXIT
|
||||
|
||||
if [[ -f "$blocked_path" ]]; then
|
||||
write_status "blocked" "Sync is blocked until .codex-sync/runtime/blocked is cleared"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
: >"$error_log"
|
||||
write_status "running" "Synchronizing workspace"
|
||||
|
||||
branch="$(read_manifest_field branch)"
|
||||
git_name="$(read_manifest_field gitIdentity.name)"
|
||||
git_email="$(read_manifest_field gitIdentity.email)"
|
||||
branch="${branch:-main}"
|
||||
git_name="${git_name:-Codex Sync}"
|
||||
git_email="${git_email:-codex-sync@local}"
|
||||
|
||||
if [[ ! -d "$project_dir/.git" ]]; then
|
||||
write_status "error" "Project is not a git repository"
|
||||
echo "Project is not a git repository: $project_dir" >"$error_log"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "$(git -C "$project_dir" config user.name || true)" ]]; then
|
||||
git -C "$project_dir" config user.name "$git_name"
|
||||
fi
|
||||
if [[ -z "$(git -C "$project_dir" config user.email || true)" ]]; then
|
||||
git -C "$project_dir" config user.email "$git_email"
|
||||
fi
|
||||
|
||||
if git -C "$project_dir" show-ref --verify --quiet "refs/heads/$branch"; then
|
||||
git -C "$project_dir" checkout "$branch" >>"$error_log" 2>&1
|
||||
else
|
||||
git -C "$project_dir" checkout -B "$branch" >>"$error_log" 2>&1
|
||||
fi
|
||||
|
||||
if [[ -n "$(git -C "$project_dir" status --porcelain)" ]]; then
|
||||
git -C "$project_dir" add -A >>"$error_log" 2>&1
|
||||
if ! git -C "$project_dir" diff --cached --quiet; then
|
||||
stamp="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
git -C "$project_dir" commit -m "autosync(${host_label}): save work ${stamp}" >>"$error_log" 2>&1 || {
|
||||
write_status "error" "Failed to commit local changes before sync"
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! run_git fetch origin; then
|
||||
write_status "error" "Failed to fetch origin"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if git -C "$project_dir" show-ref --verify --quiet "refs/remotes/origin/$branch"; then
|
||||
if ! git -C "$project_dir" rebase "origin/$branch" >>"$error_log" 2>&1; then
|
||||
block_sync "Rebase conflict while replaying local commits onto origin/$branch"
|
||||
fi
|
||||
fi
|
||||
|
||||
push_ok=0
|
||||
for attempt in 1 2; do
|
||||
if git -C "$project_dir" push -u origin "$branch" >>"$error_log" 2>&1; then
|
||||
push_ok=1
|
||||
break
|
||||
fi
|
||||
|
||||
if [[ "$attempt" -eq 2 ]]; then
|
||||
break
|
||||
fi
|
||||
|
||||
if ! run_git fetch origin; then
|
||||
break
|
||||
fi
|
||||
|
||||
if git -C "$project_dir" show-ref --verify --quiet "refs/remotes/origin/$branch"; then
|
||||
if ! git -C "$project_dir" rebase "origin/$branch" >>"$error_log" 2>&1; then
|
||||
block_sync "Push retry failed because origin/$branch caused a rebase conflict"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$push_ok" -ne 1 ]]; then
|
||||
write_status "error" "Push failed after retry"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -f "$blocked_path"
|
||||
head_commit="$(git -C "$project_dir" rev-parse --short HEAD 2>/dev/null || true)"
|
||||
write_status "ok" "Synchronized successfully at ${head_commit:-unknown}"
|
||||
在新工单中引用
屏蔽一个用户