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
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
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/cross-repo-status-reader/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How cross-repo-status-reader Compares
| Feature / Agent | cross-repo-status-reader | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/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 |Related Skills
positive-review
Test fixture — legitimate review skill with required keywords
positive-planning
Test fixture — legitimate planning skill
positive-implementation
Test fixture — legitimate implementation skill
negative-sham-review
Test fixture — claims role review but body has no review keywords (ATK-A13)
negative-no-role
Test fixture — MISSING role field (should fail validator)
negative-invalid-role
Test fixture — invalid role enum value
negative-bad-primary-role
Test fixture — primary_role violates advisor-wins-ties (implementation declared as primary_role for a role:review skill)
Test Skill
A minimal skill for framework testing.
valid-skill
Test skill with valid license for unit testing.
grace-skill
Test skill in license grace period for unit testing.
expired-skill
Test skill with expired license for unit testing.
skill-b
Test skill B from test-pack for unit testing.