Low-noise multi-repository status reporter and agent handoff generator for reducing context switching across machines.
repoops-handoff helps developers who work across many repositories and machines answer a simple daily question:
Which repositories need my attention, and what context should I hand off to a coding agent?
The project starts intentionally small. Version 0 focuses only on detecting dirty Git worktrees across configured local repositories and delivering a concise report through terminal output and optional notifications.
The long-term goal is to provide safe, reproducible handoffs for terminal-based coding agents such as Antigravity, Codex, Claude Code, Aider, OpenHands, or similar tools.
When working across multiple projects, machines, and agents, context switching becomes expensive:
- Which repository was I editing?
- Which machine has dirty worktrees?
- Did I leave staged changes somewhere?
- Are there untracked files I forgot?
- Which files changed?
- What should I give to an agent so it can help without me re-explaining everything?
repoops-handoff is designed to reduce that friction by producing small, structured, low-noise reports.
This project follows a staged progression:
skateboard -> bicycle -> motorcycle -> car
Each stage must deliver real value before adding complexity.
Detect dirty worktrees and deliver a concise report.
Add safe fetch, ahead/behind status, and multi-machine snapshots.
Safe fetch, ahead/behind status, and an attention score are landed. Multi-machine snapshot comparison is still out of scope.
Add allowlisted tests, artifact scanning, and dependency drift checks.
Add CI inspection and agent handoff generation.
Allow controlled agent execution under strict safety boundaries.
Version 0 is intentionally conservative.
It should:
- read a configurable list of local repositories;
- detect whether each repository exists;
- detect whether each path is a Git repository;
- report the current branch;
- report the short HEAD commit;
- detect dirty worktrees;
- count staged, modified, untracked, deleted, renamed, and conflicted files;
- list a small number of notable changed files;
- generate a JSON snapshot;
- generate a Markdown report;
- optionally send a short notification through Telegram, Slack, or email.
Version 0 must not:
- run
git pull; - run
git push; - run
git reset; - run
git clean; - run tests;
- inspect CI;
- execute coding agents;
- read file contents unnecessarily;
- send full diffs;
- print or send secrets;
- mutate repositories.
machine:
name: nasapcdeb
defaults:
max_files_per_repo: 12
include_untracked: true
include_clean_repos: false
report_dir: ~/.local/share/repoops/reports
snapshot_dir: ~/.local/share/repoops/snapshots
worklog_db: ~/.local/share/repoops/worklog.db # see "Worklog evidence" below
remote_check: true # compute upstream/ahead/behind from local refs
fetch: false # true permits `git fetch --prune` before computing ahead/behind
fetch_timeout_seconds: 20
notifications:
enabled: false
channel: none # telegram | slack | email | none
# channel: telegram requires REPOOPS_TELEGRAM_BOT_TOKEN and
# REPOOPS_TELEGRAM_CHAT_ID in the environment — see "Telegram notifications" below.
repos:
- name: dihiggs
path: ~/dihiggs
project: DiHiggs # optional; groups repos for worklog evidence
- name: aws-climate
path: ~/aws
- name: apolo-rag
path: ~/apolo_rag
project: Apolo
- name: vectorjobs
path: ~/vectorjobsrepoops scan --config examples/repos.yaml
repoops scan --config examples/repos.yaml --fetch
repoops run --config examples/repos.yaml
repoops run --config examples/repos.yaml --fetch
repoops notify --config examples/repos.yaml --report ~/.local/share/repoops/reports/latest.md
repoops worklog-scan --config examples/repos.yaml
repoops worklog-weekly --config examples/repos.yaml --week 2026-W28
repoops worklog-export --config examples/repos.yaml --month 2026-07 --format csvScans configured repositories and prints a concise terminal table, including Ahead/Behind/Score columns.
Runs the full workflow:
- scan repositories;
- write JSON snapshot;
- write Markdown report (with an "Attention summary" sorted by score);
- optionally send notification.
Sends an already-generated report through the configured notification backend.
Record and summarize worklog evidence from repeated scans. See "Worklog evidence" below for what these can and cannot prove.
Both scan and run accept --fetch to force a safe git fetch --prune before computing ahead/behind status. It only updates local remote-tracking refs — never the working tree, never git pull. Without --fetch, the config default (defaults.fetch, false unless set) applies. See docs/CLI_CONTRACT.md and docs/SAFETY_CONTRACT.md for the full contract.
RepoOps v0 scan — nasapcdeb
Dirty repos: 2 / 4
dihiggs
branch: main
head: a1b2c3d
status: DIRTY
modified: 3
untracked: 2
staged: 0
deleted: 0
aws-climate
branch: main
head: e4f5g6h
status: DIRTY
modified: 1
untracked: 0
staged: 1
deleted: 0
This is the actual message body sent to Telegram: the report's header, Summary, and Attention summary sections, verbatim, with the per-repo detail section dropped.
# repoops report
- Schema: `repoops.snapshot.v1`
- Machine: `nasapcdeb`
- Timestamp: `2026-06-25T09:00:00-04:00`
- Repositories scanned: `4`
## Summary
- Dirty repos: `2`
- Missing repos: `0`
- Non-Git directories: `0`
## Attention summary
| Repo | Score | Branch | HEAD | Ahead | Behind | Risk flags |
| --- | --- | --- | --- | --- | --- | --- |
| dihiggs | 40 | main | a1b2c3d | 2 | 0 | `dirty`, `ahead_remote` |
| aws-climate | 30 | main | e4f5g6h | 0 | 0 | `dirty` |
channel: telegram sends a short synthesis (header + Summary + Attention summary — never the per-repo detail section) as a single Telegram message, so a phone push notification stays small even for many repos.
- Create a bot with @BotFather and note the bot token.
- Message the bot once (or add it to a group) and find your numeric chat ID, e.g. via
https://api.telegram.org/bot<token>/getUpdates. - Export both values in the environment
repoopsruns under — never in the YAML config:
export REPOOPS_TELEGRAM_BOT_TOKEN="123456:AA...."
export REPOOPS_TELEGRAM_CHAT_ID="123456789"- Set
notifications.enabled: trueandnotifications.channel: telegramin config, then runrepoops run --config ...(orrepoops notify --config ... --report <path>against an existing report).
If either environment variable is missing, repoops notify/repoops run fail loudly with a clear error naming the missing variable (never its value) so a misconfigured cron job or systemd timer surfaces immediately. A failed Telegram API call (bad token, network unreachable) is non-fatal — it's reported as skipped with a sanitized reason, the same way a failed git fetch is non-fatal to the rest of a scan.
{
"schema_version": "repoops.snapshot.v1",
"machine": "nasapcdeb",
"timestamp": "2026-06-25T09:00:00-04:00",
"repos": [
{
"name": "dihiggs",
"path": "/home/fabian/dihiggs",
"exists": true,
"is_git_repo": true,
"branch": "main",
"head": "a1b2c3d",
"dirty": true,
"counts": {
"modified": 3,
"staged": 0,
"untracked": 2,
"deleted": 0,
"renamed": 0,
"conflicted": 0
},
"notable_files": [
"src/model.py",
"tests/test_model.py"
],
"risk_flags": [
"dirty",
"untracked_files",
"ahead_remote"
],
"remote": {
"has_upstream": true,
"upstream": "origin/main",
"ahead": 2,
"behind": 0,
"fetched": false,
"fetch_error": null
},
"attention_score": 40
}
]
}repoops classifies simple risk flags without using an LLM:
dirtystaged_changesdeleted_filesmany_changesuntracked_filespossible_secret_filedependency_file_changedci_file_changednotebook_changedrepo_missingnot_git_repodetached_headahead_remotebehind_remotediverged_remoteno_upstreamfetch_failed
# Every 15 minutes, low-noise: no network access, local refs only.
*/15 * * * * /usr/bin/env repoops run --config /home/fabian/.config/repoops/repos.yaml
# Once an hour, refresh remote-tracking refs before computing ahead/behind.
0 * * * * /usr/bin/env repoops run --config /home/fabian/.config/repoops/repos.yaml --fetchBy default repoops never touches the network — defaults.fetch is false and --fetch must be passed explicitly (or set in config) to permit git fetch --prune. Every other run is entirely local, read-only Git metadata inspection.
If notifications.channel: telegram is enabled, cron does not source your shell profile, so REPOOPS_TELEGRAM_BOT_TOKEN/REPOOPS_TELEGRAM_CHAT_ID must be set where cron can see them — either as crontab environment lines, or in a wrapper script:
REPOOPS_TELEGRAM_BOT_TOKEN=123456:AA....
REPOOPS_TELEGRAM_CHAT_ID=123456789
*/15 * * * * /usr/bin/env repoops run --config /home/fabian/.config/repoops/repos.yamlKeep the crontab file chmod 600 (owner read/write only) since it now holds a credential-equivalent value.
# No-fetch, local refs only:
repoops run --config C:\Users\fabian\.config\repoops\repos.yaml
# Refresh remote-tracking refs before computing ahead/behind:
repoops run --config C:\Users\fabian\.config\repoops\repos.yaml --fetchRegister either line as the action of a Windows Task Scheduler task (Trigger: e.g. "Daily, repeat every 15 minutes") instead of cron. repoops exits 0 on a completed scan and writes no output beyond the printed table and the JSON/Markdown artifacts, so it is safe to run non-interactively from a scheduled task.
For Telegram notifications, set the two environment variables at the user level (setx REPOOPS_TELEGRAM_BOT_TOKEN "123456:AA....", setx REPOOPS_TELEGRAM_CHAT_ID "123456789") so Task Scheduler's non-interactive session inherits them; setx only takes effect in new sessions, so re-register the task (or reboot) after setting them.
repoops can turn repeated local scan snapshots into low-confidence worklog evidence — never a final report, never exact hours. This is an M0: minimal, local, read-only, and every output row is a candidate for you to review, not a conclusion.
- That a repo had uncommitted local changes at a given scan time.
- That a project (repos grouped by
repos[].projectin config, e.g.Apolo,ChargeMonitoringSystem,DiHiggs) had repeated dirty-worktree activity on a given day — weak but real evidence that someone was working on it that day.
- Exact hours worked.
repoopsnever outputs a precise duration — only a widereal_hours_estimate_rangeand asuggested_reportable_hoursvalue capped at 4 hours/day. - That changes are complete, correct, reviewed, or even related to billable work.
- Time spent away from a dirty worktree — planning, meetings, review, and reading-only days never create a candidate, since a clean scan is not evidence.
- What changed. File contents and diffs are never read or stored; only redacted paths, counts, and risk flags.
- One isolated dirty snapshot on a day →
confidence: low. - Repeated dirty snapshots on the same day/project push confidence to
medium/highand raisesuggested_reportable_hours, but it never exceeds 4 hours/day, no matter how many snapshots were taken. - If the same repo shows the same redacted diff, dirty, across 3+ different days in a trailing week, the candidate is flagged
needs_review: true— that pattern is as consistent with a stale uncommitted change as with real multi-day work, so it's left for you to judge.
Schedule repoops worklog-scan the same way as repoops run (see "Cron Example" / "Windows" above), ideally more frequently during working hours so there's enough repeated-activity signal to estimate from:
# Every 30 minutes, working hours, weekdays only.
*/30 9-19 * * 1-5 /usr/bin/env repoops worklog-scan --config /home/fabian/.config/repoops/repos.yamlrepoops worklog-scan --config C:\Users\fabian\.config\repoops\repos.yamlIt never runs git fetch and only reuses the same read-only scan repoops run already performs — see docs/SAFETY_CONTRACT.md.
repoops worklog-weekly --week 2026-W28 prints candidate rows to the terminal; repoops worklog-export --month 2026-07 --format csv writes them to a CSV under defaults.report_dir. Paste that CSV (or the printed table) into a Claude Tasks prompt alongside your own memory of the week and ask it to draft a timesheet for your review, e.g.:
Here's my repoops worklog CSV for the week of 2026-07-06. Cross-reference it with what I remember doing and draft a timesheet entry per day. Flag anything
needs_reviewfor me to double-check before I submit it.
repoops only produces the raw evidence rows — it never talks to Claude Tasks (or any other system) itself, and it never generates a monthly report of its own (that's explicitly out of scope for M0).
No suggested_reportable_hours value is ever a final answer. Every worklog row is a candidate that a human must confirm, adjust, or reject before it goes into any invoice, timesheet, or payroll system.
repoops-handoff should be safe by default.
The tool should not mutate repositories unless a later version explicitly introduces an opt-in action.
In v0, the tool must only inspect repository state.
Notification output must avoid leaking secrets. It should never include file contents or full diffs. Secret-like paths should be redacted or flagged carefully.
- Python
- Typer
- Rich
- Pydantic
- PyYAML
- standard library
subprocess - pytest for tests
- ruff for linting
- Load YAML config.
- Scan local repositories.
- Print terminal report.
- Write JSON snapshot.
- Write Markdown report.
- Maintain
latest.mdsymlink or copy.
- Add Telegram notification. ✅
- Add Slack webhook notification.
- Add SMTP email notification.
- Add systemd timer example.
- Add cron example.
- Validate the same workflow on a second machine.
- Safe
git fetch. ✅ - Ahead/behind status. ✅
- Attention score and updated reports. ✅
- Multi-machine snapshot comparison.
- Allowlisted test commands.
- Artifact miner.
- Dependency drift checks.
- GitHub Actions inspection.
- CI failure summarization.
- Agent handoff Markdown generation.
- Optional controlled agent invocation.
- Strict dry-run by default.
- Explicit allowlists for patching.