Files
the-custodian/workplans/CUST-WP-0014-repo-sync-automation.md
tegwick 07031fa63e feat(CUST-WP-0014): repo sync automation & Gitea inventory
- Migration e2f3a4b5c6d7: add last_state_synced_at to managed_repos
- consistency_check.py: PATCH last_state_synced_at after fix run;
  fix ~ treated as non-empty state_hub_task_id (C-03 vs C-11);
  fix _inject_task_id_into_block skipping injection when field exists
  with null value
- install_hooks.sh: idempotent post-commit hook installer for all
  registered repos (make install-hooks REPO= / install-hooks-all)
- gitea_inventory.py: compare coulomb Gitea org against state-hub
  registered repos — registered / unregistered / hub-only sections
- infra/README.md: document systemd user timer + crontab fallback
- systemd user timer: custodian-sync.{service,timer} runs
  fix-consistency-all every 15 min (enabled)
- dashboard/src/repo-sync.md: Repo Sync Health page — sync age table,
  unregistered Gitea repos, hub-only repos
- api/routers/repos.py: GET /repos/{slug}/dispatch endpoint returning
  active goal, pending tasks per workstream, human interventions
- mcp_server/server.py: get_repo_dispatch() MCP tool

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 01:41:16 +01:00

8.3 KiB
Raw Blame History

id, type, title, domain, repo, status, state_hub_workstream_id, created, updated
id type title domain repo status state_hub_workstream_id created updated
CUST-WP-0014 workplan Repo Sync Automation & Gitea Inventory custodian the-custodian done 27ea80bd-76bf-44a7-b0ed-e09748d5390b 2026-03-16 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=<slug>. 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

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

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 <slug> or --all (iterates GET /repos/)
  • Resolves repo path from slug (convention: /home/worsch/<slug> or via a local_path field — see T05)
  • Writes .git/hooks/post-commit that calls:
    cd ~/the-custodian/state-hub && make fix-consistency REPO=<slug>
    
  • 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=<slug> and make install-hooks-all Makefile targets.

Acceptance: commit in marki-docxlast_state_synced_at updates within 2s.


Task: Periodic cron sync (belt-and-suspenders)

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:

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

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

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 124h, 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

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:

{
  "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:

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