Skip to content

Commit 8078ec6

Browse files
committed
feat: ship full git-auto-sync release-ready automation platform
- add a multi-repo CLI with init/sync/status/config/install/uninstall/update commands and clean exit semantics - implement repository scanning, AI-assisted staging, and fallback rule-based commit message generation - add provider pipeline for claude-cli, anthropic-api, and rules with robust conflict-safe git push flow - add notifier adapters for log, telegram, and lark with structured result summaries - add cross-platform scheduling for launchd/systemd/task scheduler and installer scripts for installer scripts, bootstrap, and updates - add release workflow documentation in .claude/commands/release.md based on the net-auto-switch playbook - add full English docs and tests for CLI, config, engine, git operations, release, scheduler, and installer behavior
0 parents  commit 8078ec6

49 files changed

Lines changed: 3618 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/commands/release.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
description: Cut a release — verify, bump version, push, publish a GitHub release, and validate the install/update path
2+
argument-hint: "[patch|minor|major|X.Y.Z] [no-deploy]"
3+
allowed-tools: Bash, Read, Edit, Write
4+
---
5+
6+
You are running the **full git-auto-sync release flow**. Follow the phases in order.
7+
Treat every verification as a gate: if a step fails, **STOP and report** — do not tag, push, or publish the release.
8+
9+
Arguments: `$ARGUMENTS`
10+
- First token = the bump: `patch` (default), `minor`, `major`, or an explicit `X.Y.Z`.
11+
- If `no-deploy` appears anywhere, skip the optional deployment verification phase.
12+
13+
## Phase 0 — Preflight (abort on any problem)
14+
15+
1. Confirm the branch is `main` and the working tree is clean except for changes you are about to make.
16+
2. `git fetch --tags origin` to ensure release tags are present locally.
17+
3. Run the verification gate and require all green:
18+
- `uv run pytest -q`
19+
- `uv run ruff check .`
20+
- `uv run ruff format --check git_auto_sync/ tests/`
21+
22+
## Phase 1 — Version bump
23+
24+
1. Read current `version` from `pyproject.toml`.
25+
2. Compute new version from bump argument (default `patch`).
26+
3. Update `pyproject.toml` and refresh lockfile:
27+
- `uv sync`
28+
4. Commit exactly version files:
29+
```bash
30+
git add pyproject.toml uv.lock
31+
git commit -m "chore: bump version to X.Y.Z"
32+
```
33+
34+
## Phase 2 — Push
35+
36+
`git push origin main`.
37+
38+
## Phase 3 — GitHub release
39+
40+
1. Resolve previous tag: `git describe --tags --abbrev=0 HEAD^` (after fetching tags).
41+
2. Draft release notes from `git log <prevtag>..HEAD --no-merges` in repo style:
42+
- Title: `vX.Y.Z — <short summary>`
43+
- A short lead, then `## Fixed` / `## Changed` / `## Added` sections as warranted.
44+
- Install one-liner:
45+
`curl -fsSL https://raw.githubusercontent.com/OctopusGarage/git-auto-sync/main/install.sh | bash`
46+
- `**Full Changelog**: https://github.com/OctopusGarage/git-auto-sync/compare/<prevtag>...vX.Y.Z`
47+
3. Create release:
48+
```bash
49+
gh release create vX.Y.Z --target main --title "vX.Y.Z — ..." --notes-file - <<'EOF'
50+
...
51+
EOF
52+
```
53+
4. Verify:
54+
- `gh release list -L 3` shows `vX.Y.Z` as **Latest**
55+
- `git ls-remote --tags origin vX.Y.Z` resolves
56+
57+
## Phase 4 — Self-consistency check
58+
59+
Because install/update pull the latest release tarball, confirm the new tag's tarball includes the built code:
60+
```bash
61+
TAG=$(git describe --tags --abbrev=0)
62+
url=$(curl -fsSLI -o /dev/null -w '%{url_effective}' "https://github.com/OctopusGarage/git-auto-sync/releases/latest")
63+
TAG="${url##*/}"
64+
curl -fsSL "https://github.com/OctopusGarage/git-auto-sync/archive/refs/tags/${TAG}.tar.gz" -o /tmp/git-auto-sync.tar.gz
65+
tar -tzf /tmp/git-auto-sync.tar.gz | rg "setup\.(sh|ps1)|pyproject.toml|git_auto_sync/cli.py"
66+
```
67+
68+
## Phase 5 — Optional local validation (if not skipped)
69+
70+
Run installation/update smoke checks:
71+
1. `curl -fsSL https://raw.githubusercontent.com/OctopusGarage/git-auto-sync/main/install.sh | bash`
72+
2. `git-auto-sync --help`
73+
3. `uv run git-auto-sync config check`
74+
4. `git-auto-sync update --check` expects to report up-to-date at new version.
75+
76+
## Report
77+
78+
Summarize: new version, commit SHA, release URL, and verification results. If stopped early, identify the failing gate and required fix.

.github/workflows/ci.yml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
jobs:
8+
test:
9+
name: Test (${{ matrix.os }})
10+
runs-on: ${{ matrix.os }}
11+
strategy:
12+
fail-fast: false
13+
matrix:
14+
os:
15+
- ubuntu-latest
16+
- macos-latest
17+
- windows-latest
18+
19+
steps:
20+
- name: Check out repository
21+
uses: actions/checkout@v4
22+
23+
- name: Set up Python
24+
uses: actions/setup-python@v5
25+
with:
26+
python-version: "3.12"
27+
28+
- name: Set up uv
29+
uses: astral-sh/setup-uv@v8.2.0
30+
with:
31+
enable-cache: true
32+
33+
- name: Sync dependencies
34+
run: uv sync --dev
35+
36+
- name: Run tests
37+
run: uv run pytest
38+
39+
- name: Run lint
40+
run: uv run ruff check
41+
42+
- name: Check POSIX installer syntax
43+
if: runner.os != 'Windows'
44+
run: bash -n install.sh
45+
46+
- name: Check Windows installer syntax
47+
if: runner.os == 'Windows'
48+
shell: pwsh
49+
run: |
50+
$tokens = $null
51+
$errors = $null
52+
[System.Management.Automation.Language.Parser]::ParseFile(
53+
(Resolve-Path "install.ps1"),
54+
[ref]$tokens,
55+
[ref]$errors
56+
) | Out-Null
57+
if ($errors.Count -gt 0) {
58+
$errors | Format-List
59+
exit 1
60+
}

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
__pycache__/
2+
*.pyc
3+
.venv/
4+
*.egg-info/
5+
docs/superpowers
6+

README.md

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# git-auto-sync
2+
3+
Monitor multiple local Git repositories on a schedule, automatically pick files to commit, generate a commit message, push changes, and notify via your chosen channels.
4+
5+
It is inspired by a single-repo bash script but redesigned for multi-repo workflows with a configuration-driven model, platform-native scheduling, and pluggable AI and notifier providers.
6+
7+
## Workflow
8+
9+
For each configured repository:
10+
11+
1. Skip with no output when there are no changes
12+
2. **Smart staging**: let AI review changed files and select commit candidates, excluding secrets, build artifacts, and large files. Excluded files can be auto-added to `.gitignore` (if AI is unavailable, it falls back to `git add -A`).
13+
3. AI generates a Conventional Commits message.
14+
4. `git commit`
15+
5. `git pull --rebase``git push` (on conflict, abort cleanly, mark as failed, continue with the next repo).
16+
6. Summarize and notify by strategy.
17+
18+
## Installation
19+
20+
### One-line install (recommended)
21+
22+
macOS / Linux:
23+
24+
```bash
25+
curl -fsSL https://raw.githubusercontent.com/OctopusGarage/git-auto-sync/main/install.sh | bash
26+
```
27+
28+
Windows PowerShell:
29+
30+
```powershell
31+
powershell -ExecutionPolicy ByPass -c "irm https://raw.githubusercontent.com/OctopusGarage/git-auto-sync/main/install.ps1 | iex"
32+
```
33+
34+
It installs automatically: installs [uv](https://docs.astral.sh/uv/) when missing, downloads the latest release to `~/.git-auto-sync`, runs `uv sync`, and places a global `git-auto-sync` launcher in `~/.local/bin` (shell script on macOS/Linux, `.cmd` on Windows). The first run starts `init`. Re-running updates the existing installation; existing config is preserved by default. Use `GIT_AUTO_SYNC_VERSION=v0.1.0` for a pinned version and `GIT_AUTO_SYNC_DIR` to change install path.
35+
36+
> Non-interactive installs (`--yes`) write the config and skip scheduler setup, then run `init` so a repository does not become automatically scheduled until you explicitly run `git-auto-sync install`.
37+
38+
### Manual install
39+
40+
```bash
41+
cd git-auto-sync
42+
uv sync # Install dependencies
43+
uv run git-auto-sync init # Run the setup wizard (see below)
44+
# Or install as a CLI command:
45+
uv tool install .
46+
```
47+
48+
### Setup wizard (`init`)
49+
50+
```bash
51+
git-auto-sync init # Interactive: scan directories, choose repos, pick AI provider, configure notifiers, and optionally install scheduler
52+
git-auto-sync init --yes # Non-interactive with defaults; scheduler is not installed
53+
```
54+
55+
The wizard scans up to two levels deep from the parent directory (`~/programming` by default) and lets you pick repositories; you can also add paths manually. After writing `~/.git-auto-sync/config.toml`, it validates immediately.
56+
57+
### Update
58+
59+
```bash
60+
git-auto-sync update # Download latest release and run uv sync (launcher path remains stable, scheduler is not reinstalled)
61+
git-auto-sync update --check # Check for updates only
62+
```
63+
64+
## Configuration
65+
66+
Default config path is `~/.git-auto-sync/config.toml` (override with `--config`). Runtime files (config, logs, scheduler artifacts) are stored in `~/.git-auto-sync/`.
67+
68+
```bash
69+
mkdir -p ~/.git-auto-sync
70+
cp config.example.toml ~/.git-auto-sync/config.toml
71+
```
72+
73+
See [`config.example.toml`](config.example.toml) for all fields.
74+
75+
- `[defaults]` provides global defaults, and each `[[repos]]` can override fields.
76+
- String values can use `env:VARNAME` and are resolved from environment variables at runtime.
77+
- `ai_provider`: `claude-cli` (default, uses local `claude` CLI) / `anthropic-api` (reads `ANTHROPIC_API_KEY`) / `rules` (rule-based fallback). If any AI provider fails, the flow automatically falls back to rule mode.
78+
- `notify_on`: `change_or_fail` (default, changed or failed only), `fail_only`, or `always`.
79+
80+
## Commands
81+
82+
```bash
83+
git-auto-sync init # Run setup wizard and create config
84+
git-auto-sync sync # Sync all repositories
85+
git-auto-sync sync --repo foo # Sync only repos matching path foo
86+
git-auto-sync sync --dry-run # Show candidates without changing anything
87+
git-auto-sync status # Show branches and clean/dirty state per repo
88+
git-auto-sync config check # Validate config file
89+
git-auto-sync install --interval 30m # Install platform-native scheduler
90+
git-auto-sync uninstall # Remove scheduler integration
91+
git-auto-sync update # Self-update to latest release
92+
```
93+
94+
Exit codes: `0` = success, `1` = at least one repository failed, `2` = `--repo` matched no repository.
95+
96+
## Scheduling
97+
98+
`install` creates and enables a platform-native scheduler and does not run a resident process:
99+
100+
- **macOS** → launchd(`~/Library/LaunchAgents/com.octopusgarage.git-auto-sync.plist`)
101+
- **Linux** → systemd user timer(`~/.config/systemd/user/git-auto-sync.timer`)
102+
- **Windows** → Task Scheduler(`OctopusGarage.git-auto-sync`)
103+
104+
Intervals support `30m` / `1h` / `90s`. Uninstall with `git-auto-sync uninstall`.
105+
106+
## Notifiers
107+
108+
- `log`: append timestamped blocks to log file and print to stdout (audit friendly)
109+
- `telegram`: bot token + chat_id
110+
- `lark`: Feishu custom robot webhook
111+
112+
After each sync, a summary is sent through all enabled notifiers according to `notify_on`. Failure in one notifier does not affect others or the main sync flow.
113+
114+
## Safety boundaries
115+
116+
The biggest risk in unattended automation is committing bad content. This tool defends with:
117+
118+
- AI staging excludes secrets, credentials, certificates, `node_modules`/`dist`/`build`/`__pycache__`, large binaries, logs, and other non-source artifacts.
119+
- Excluded files are **never committed** and are written into `.gitignore` when `ai_gitignore_autowrite=true`.
120+
- When uncertain, run `--dry-run` first to preview.
121+
122+
## Development
123+
124+
```bash
125+
uv run pytest # Run tests
126+
uv run ruff check . # Run linting
127+
```
128+
129+
## Release
130+
131+
Use the assistant command:
132+
133+
- `.claude/commands/release.md` contains the full release flow used in this repo:
134+
- preflight checks (`pytest`, `ruff`)
135+
- version bump (`pyproject.toml` + `uv.lock`)
136+
- release publish on GitHub
137+
- install/update validation
138+
139+
For manual release scripts, run the version bump and release flow in that file.
140+
141+
## License
142+
143+
MIT

config.example.toml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Example config for git-auto-sync
2+
# Copy to ~/.git-auto-sync/config.toml and adjust as needed.
3+
4+
[defaults]
5+
ai_provider = "claude-cli" # claude-cli | anthropic-api | rules
6+
ai_staging = true # AI picks files to stage; set false to fall back to git add -A
7+
ai_gitignore_autowrite = true # when ai_staging is enabled, append ignored files into .gitignore
8+
push = true # pull --rebase then push after commit
9+
notify_on = "change_or_fail" # change_or_fail | fail_only | always
10+
11+
# ---- Repositories to sync, supports multiple entries ----
12+
[[repos]]
13+
path = "~/programming/foo"
14+
15+
[[repos]]
16+
path = "~/programming/bar"
17+
push = false # can override any defaults field per repository
18+
19+
# ---- Notifier channels (only entries with enabled=true are active) ----
20+
[notifiers.log]
21+
enabled = true
22+
path = "~/.git-auto-sync/sync.log"
23+
24+
[notifiers.telegram]
25+
enabled = false
26+
bot_token = "env:TELEGRAM_BOT_TOKEN" # env: prefix reads from environment variable and avoids plaintext secrets
27+
chat_id = "123456"
28+
29+
[notifiers.lark]
30+
enabled = false
31+
webhook = "https://open.feishu.cn/open-apis/bot/v2/hook/xxxx"

git_auto_sync/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__version__ = "0.1.0"

0 commit comments

Comments
 (0)