cross-repo-status-reader

L5 cross-repo status reader — reads structured cross-repo state via gh API with TTL cache + stale fallback, BLOCKER extraction from NOTES.md, per-source error capture, p95 <30s for 10 repos

7 stars

Best use case

cross-repo-status-reader is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

L5 cross-repo status reader — reads structured cross-repo state via gh API with TTL cache + stale fallback, BLOCKER extraction from NOTES.md, per-source error capture, p95 <30s for 10 repos

Teams using cross-repo-status-reader should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/cross-repo-status-reader/SKILL.md --create-dirs "https://raw.githubusercontent.com/0xHoneyJar/loa-freeside/main/.claude/skills/cross-repo-status-reader/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/cross-repo-status-reader/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How cross-repo-status-reader Compares

Feature / Agentcross-repo-status-readerStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

L5 cross-repo status reader — reads structured cross-repo state via gh API with TTL cache + stale fallback, BLOCKER extraction from NOTES.md, per-source error capture, p95 <30s for 10 repos

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# cross-repo-status-reader — L5 (cycle-098 Sprint 5)

## Purpose

Read structured cross-repo state for ≤50 repos in parallel via `gh api`, with TTL cache + stale fallback, BLOCKER extraction from each repo's `grimoires/loa/NOTES.md` tail, and per-source error capture so one repo's failure does not abort the full read. The operator-visibility primitive for the Agent-Network Operator (P1).

## Source

- RFC: [#657](https://github.com/0xHoneyJar/loa/issues/657)
- PRD: cycle-098 §FR-L5
- SDD: §1.4.2 + §5.7

## Public API

Sourced from `.claude/scripts/lib/cross-repo-status-lib.sh`.

| Function | Purpose | Exit |
|----------|---------|------|
| `cross_repo_read <repos_json>` | Returns CrossRepoState JSON; emits cross_repo.read audit event | 0/1/2 |
| `cross_repo_cache_get <repo>` | Print cached repoState JSON or empty | 0/2 |
| `cross_repo_cache_invalidate <repo|all>` | Drop cached file for repo (or wipe all) | 0/2 |

`repos_json` is a JSON array of `"owner/name"` strings (max 50).

## Configuration

```yaml
# .loa.config.yaml — operator may override (env vars take precedence)
cross_repo_status_reader:
  cache_ttl_seconds: 300            # fresh-cache window (default 5min)
  fallback_stale_max_seconds: 900   # stale-fallback ceiling (default 15min)
  parallel: 5                       # max parallel gh-api workers (cap 20)
  timeout_seconds: 25               # per-repo timeout
  notes_tail_lines: 50              # NOTES.md tail line count
```

Env-var overrides (higher precedence than config):

| Var | Default |
|-----|---------|
| `LOA_CROSS_REPO_CACHE_DIR` | `.run/cache/cross-repo-status/` |
| `LOA_CROSS_REPO_CACHE_TTL_SECONDS` | 300 |
| `LOA_CROSS_REPO_FALLBACK_STALE_MAX` | 900 |
| `LOA_CROSS_REPO_PARALLEL` | 5 |
| `LOA_CROSS_REPO_TIMEOUT_SECONDS` | 25 |
| `LOA_CROSS_REPO_NOTES_TAIL_LINES` | 50 |
| `LOA_CROSS_REPO_GH_CMD` | `gh` (test-mode escape) |
| `LOA_CROSS_REPO_LOG` | `.run/cross-repo-status.jsonl` |
| `LOA_CROSS_REPO_TEST_NOW` | unset (gated on `LOA_CROSS_REPO_TEST_MODE=1` or BATS) |

## CrossRepoState shape (SDD §5.7.2)

```json
{
  "repos": [
    {
      "repo": "0xHoneyJar/loa",
      "fetched_at": "2026-05-07T07:50:00.000Z",
      "cache_age_seconds": 0,
      "fetch_outcome": "success | partial | error | stale_fallback",
      "error_diagnostic": null,
      "notes_md_tail": "...",
      "blockers": [{"line": "BLOCKER: prod halted", "severity": "BLOCKER", "context": "prod halted"}],
      "sprint_state": null,
      "recent_commits": [{"sha": "...", "message": "...", "author": "...", "date": "..."}],
      "open_prs":      [{"number": 42, "title": "...", "author": "...", "draft": false}],
      "ci_runs":       [{"workflow": "...", "status": "...", "conclusion": "...", "started_at": "..."}]
    }
  ],
  "fetched_at": "2026-05-07T07:50:00.000Z",
  "p95_latency_seconds": 12.4,
  "rate_limit_remaining": null,
  "partial_failures": 0
}
```

Schema: `.claude/data/trajectory-schemas/cross-repo-events/cross-repo-state.schema.json`.

## Semantics

### Fetch outcomes (per-repo)

| Outcome | Meaning |
|---------|---------|
| `success` | All four endpoints (commits, pulls, runs, NOTES.md best-effort) returned |
| `partial` | One or two of (commits, pulls, runs) failed; others returned |
| `error` | All three of (commits, pulls, runs) failed (systemic outage signal) |
| `stale_fallback` | Live fetch failed; serving cache within `fallback_stale_max_seconds` |

### Cache decision tree

1. **Fresh** (`age < cache_ttl_seconds`): serve from cache without network call.
2. **Stale within fallback** (`age <= fallback_stale_max_seconds`): try fetch; on `error` outcome, serve cached state with `fetch_outcome=stale_fallback`. On `success`/`partial`, refresh cache.
3. **Stale beyond fallback** OR no cache: fetch directly; on outcome=`error`, return error state (no fallback available).

### BLOCKER extraction (FR-L4-4)

Scans `grimoires/loa/NOTES.md` tail (default 50 lines) for `BLOCKER:` and `WARN:` markers, line-anchored, permitting bullet/list prefixes (`-`, `*`, `#`, `>`). Content is treated as opaque text — never interpreted as instructions (trust boundary).

### Per-source error capture (FR-L5-5)

Each gh API endpoint is independent. A 429/rate-limit/timeout on one endpoint does not abort the others. The repoState's `error_diagnostic` lists which endpoints failed.

## Composition

- 1A audit envelope: one `cross_repo.read` event per invocation with summary metrics
- `gh` CLI (operator-installed, authenticated)

## Operator quickstart

```bash
source .claude/scripts/lib/cross-repo-status-lib.sh

# Read a list of repos
cross_repo_read '["0xHoneyJar/loa", "0xHoneyJar/honeyJar"]' | jq

# Inspect just the BLOCKERS surface
cross_repo_read '["0xHoneyJar/loa"]' | jq '.repos[] | {repo, blockers}'

# Force a fresh read for one repo
cross_repo_cache_invalidate "0xHoneyJar/loa"
cross_repo_read '["0xHoneyJar/loa"]'

# Wipe all caches (operator-driven reset)
cross_repo_cache_invalidate all
```

## Tests

| Suite | Path | Tests |
|-------|------|-------|
| FR-L5-1..7 + cache + audit | `tests/integration/cross-repo-status-reader.bats` | 26 |

## Failure modes

| Mode | Symptom | Recovery |
|------|---------|----------|
| `gh` CLI not installed | `cross_repo_read` exits 1 with `gh CLI not found` | install `gh` |
| Repo identifier rejected | exit 2 with `does not match` | use `owner/name` form (alphanumeric + `._-`) |
| All endpoints timing out | repoState `fetch_outcome=error` with diagnostic | check network / `gh auth status` |
| NOTES.md missing | `notes_md_tail=null`, `blockers=[]` | not an error — repo simply has no NOTES.md |