--- id: CUST-WP-0014 type: workplan title: Repo Sync Automation & Gitea Inventory domain: custodian repo: the-custodian status: done state_hub_workstream_id: 27ea80bd-76bf-44a7-b0ed-e09748d5390b created: 2026-03-16 updated: 2026-03-16 --- # CUST-WP-0014 — Repo Sync Automation & Gitea Inventory ## Problem When a repo agent completes work and commits, the state-hub does not automatically learn about it. Task statuses in workplan `.md` files go unsynced until a human manually runs `make fix-consistency REPO=`. This breaks the episodic memory loop: future sessions see stale hub state and give wrong orientation. In parallel, the custodian only tracks repos that have been manually registered. All other repos living on Gitea (`http://92.205.130.254:32166`, org `coulomb`) are invisible — no workplan tracking, no SBOM, no goal alignment. ## Goal 1. **Automatic sync**: after every commit in a registered repo, the state-hub learns about it within seconds — no agent discipline required. 2. **Gitea inventory**: the hub knows about every repo on Gitea; unregistered repos are surfaced so they can be onboarded or explicitly marked out-of-scope. 3. **Sync timestamp**: every registered repo carries a `last_state_synced_at` timestamp so health dashboards can detect stale repos at a glance. 4. **Dispatch endpoint** (Tier 3): the hub can tell any repo what active workplan it should be working on and what tasks are pending — foundation for autonomous agent sessions. ## Architecture ``` ┌──────────────┐ post-commit hook ┌───────────────────────────┐ │ repo agent │ ──────────────────► │ fix-consistency REPO=x │ │ (any repo) │ │ → updates task statuses │ └──────────────┘ │ → sets last_state_synced │ └───────────────────────────┘ ▲ ┌──────────────────────────┐ cron (15 min) │ │ fix-consistency-all │ ─────────────────────┘ (belt & suspenders) └──────────────────────────┘ ┌─────────────────────────────┐ │ Gitea API (:32166/coulomb) │ ──► gitea_inventory.py ──► surface gaps └─────────────────────────────┘ ┌─────────────────────────────────────┐ │ GET /repos/{slug}/dispatch │ ──► active workplan + pending tasks └─────────────────────────────────────┘ for autonomous agent sessions ``` --- ## Task: Add `last_state_synced_at` to managed_repos ```task id: CUST-WP-0014-T01 status: todo priority: high state_hub_task_id: "f35c86a9-d927-4543-9e74-ff32cadcc766" ``` Migration: add `last_state_synced_at: DateTime (nullable)` to `managed_repos`. Update `consistency_check.py` to PATCH this field to `utcnow()` after every successful `--fix` run via `PATCH /repos/{slug}/` (add endpoint if missing). Update `ManagedRepoRead` schema to include the field. Acceptance: `GET /repos/the-custodian/` shows `last_state_synced_at` non-null after running `make fix-consistency REPO=the-custodian`. --- ## Task: Git post-commit hook installer ```task id: CUST-WP-0014-T02 status: todo priority: high state_hub_task_id: "97c831d9-d915-4b77-9dd6-929ff24dfd5e" ``` Create `state-hub/scripts/install_hooks.sh`: - Accepts `--repo ` or `--all` (iterates `GET /repos/`) - Resolves repo path from slug (convention: `/home/worsch/` or via a `local_path` field — see T05) - Writes `.git/hooks/post-commit` that calls: ```bash cd ~/the-custodian/state-hub && make fix-consistency REPO= ``` - Idempotent: prepends block guarded by `# custodian-sync-hook` marker if hook already exists; skips if marker present - Makes hook executable Add `make install-hooks REPO=` and `make install-hooks-all` Makefile targets. Acceptance: commit in `marki-docx` → `last_state_synced_at` updates within 2s. --- ## Task: Periodic cron sync (belt-and-suspenders) ```task id: CUST-WP-0014-T03 status: todo priority: medium state_hub_task_id: "06be1c0b-893b-4fbb-967c-9842ba59ffaa" ``` Add a cron entry (via systemd user timer or direct crontab) that runs: ``` cd ~/the-custodian/state-hub && make fix-consistency-all ``` every 15 minutes when the state-hub API is reachable. Use a guard: ```bash curl -sf http://127.0.0.1:8000/state/health || exit 0 ``` Document the timer setup in `state-hub/infra/README.md` (systemd user timer preferred on WSL2 if systemd is available; otherwise crontab fallback). Acceptance: after stopping all agents for 15 min and making a manual workplan edit, `last_state_synced_at` updates without human intervention. --- ## Task: Gitea repo discovery tool ```task id: CUST-WP-0014-T04 status: todo priority: high state_hub_task_id: "f05a04e4-10f3-4c41-a73f-057f0dea5126" ``` Create `state-hub/scripts/gitea_inventory.py`: - Reads Gitea base URL + token from env (`GITEA_URL`, `GITEA_TOKEN`) or `.env` - Calls `GET /api/v1/orgs/coulomb/repos?limit=50&page=N` (paginate) - Also includes user repos if needed: `GET /api/v1/user/repos` - Compares result against `GET /repos/` from state-hub - Outputs three sections: 1. **Registered** — in both (show `last_state_synced_at`) 2. **Unregistered** — on Gitea but not in hub (candidate for onboarding) 3. **Hub-only** — in hub but no matching Gitea remote (stale or local-only) Add `make gitea-inventory` Makefile target. Add `GITEA_URL=http://92.205.130.254:32166` and `GITEA_TOKEN=` to `.env.example`. Acceptance: running `make gitea-inventory` with a valid token prints a clear three-section report. --- ## Task: Dashboard — Repo Sync Health page ```task id: CUST-WP-0014-T05 status: todo priority: medium state_hub_task_id: "ceae2737-4762-49e5-ae41-9eca3ca79dda" ``` Add `dashboard/src/repo-sync.md` Observable page: - Table: all registered repos, `last_state_synced_at` (age in h/m), colour-coded (green < 1h, orange 1–24h, red > 24h or null) - Section: Gitea repos not yet registered (calls a new data loader that wraps `gitea_inventory.py --json`) - Inline "Register" action placeholder (links to `make register-project` docs) Add to nav in `observablehq.config.js`. --- ## Task: Dispatch endpoint ```task id: CUST-WP-0014-T06 status: todo priority: low state_hub_task_id: "86b646f3-a966-4ff4-9c9f-8684f1e81c54" ``` Add `GET /repos/{slug}/dispatch` router in `api/routers/repos.py`: Response shape: ```json { "repo_slug": "marki-docx", "active_goal": { "id": "...", "title": "...", "description": "..." }, "active_workstreams": [ { "id": "...", "title": "...", "workplan_file": "workplans/MRKD-WP-0001-level1-core.md", "pending_tasks": [ { "id": "...", "title": "...", "priority": "high", "needs_human": false } ] } ], "human_interventions": [...], "last_state_synced_at": "2026-03-16T..." } ``` `workplan_file` is derived from the workstream's `slug` field matched against known workplan naming conventions — or stored explicitly (stretch: add `workplan_path` column to workstreams). This endpoint is the foundation for a cron-triggered autonomous agent session: ```bash curl http://127.0.0.1:8000/repos/marki-docx/dispatch | \ claude --print "You are the marki-docx agent. $(cat -)" ``` MCP tool: `get_repo_dispatch(repo_slug)`. --- ## Milestones | # | Milestone | Tasks | |---|-----------|-------| | M1 | Sync timestamp live | T01 | | M2 | Auto-sync on commit | T01, T02 | | M3 | Belt-and-suspenders | T03 | | M4 | Gitea inventory visible | T04, T05 | | M5 | Dispatch endpoint ready | T06 | ## Dependencies - Consistency engine (CUST-WP-0008) — completed ✓ - `managed_repos` table (v0.5) — live ✓ ## Out of Scope - Autonomous agent scheduling (that builds on T06 but is a separate workplan) - Gitea webhook integration (post-commit hook covers the same use case locally) - Multi-user Gitea orgs beyond `coulomb`