# Shared Repo-Grounding Profile Cache Read this when a repo-grounding skill needs the question-agnostic **project profile** (stack, deps, conventions, structure). The profile is derived once and reused within a session and across sessions and skills at an unchanged commit — only the *question-specific* grounding for the current run is ever re-derived. This file is **byte-duplicated** into every consuming skill (the plugin has no cross-skill import mechanism). All copies must stay identical; `tests/repo-profile-cache-parity.test.ts` enforces it. The deterministic cache I/O lives in the co-located `scripts/repo-profile-cache.py`; the derivation-on-miss is done by the co-located `references/agents/repo-profiler.md` persona. ## What is cached (the agnostic profile) A single JSON object, versioned by `profile_schema_version`: - **Stack & versions** — languages, major frameworks + versions, build/test tooling. - **Dependency surface** — manifest + lockfile paths, top-level dependencies, project license + dependency licenses. - **Topology** — monorepo/workspace map, deployment model, API styles, data stores, module layout. - **Conventions & instruction files** — paths + digests of the *root* `AGENTS.md`/`CLAUDE.md`/`GEMINI.md`/`ARCHITECTURE.md`/`README.md`/`CONTRIBUTING.md`/`STRATEGY.md`. - **Vocabulary** — `CONCEPTS.md` canonical terms. ## What is NOT cached (always re-globbed fresh) Never read from the cache — recompute every run: - The `docs/solutions/` enumeration (a new learning, even uncommitted, must be visible — re-globbing it is ~free and the match reads files fresh anyway). - Subdirectory-scoped instruction files (area-scoped `CLAUDE.md`/`AGENTS.md`). - All question-specific grounding: a candidate's call-sites/footprint, prior-decision matches, feature patterns, git history of touched files, tracker/PR activity, external research. ## Cache location & key ``` /tmp/compound-engineering/repo-profile//.json ``` - `` = lexicographically-first `git rev-list --max-parents=0 HEAD` — the repo identity (stable, shared across worktrees and clones). - `` = `git rev-parse HEAD` — the working state. Two checkouts at the same commit share the same entry. Lookup is git metadata only; on a hit, only this one file is read. ## Protocol — how a skill uses it Invoke the helper via the `SKILL_DIR` anchor (set `SKILL_DIR` to the absolute path of the directory containing the SKILL.md you just read; the Bash tool's cwd is the user's project, not the skill dir): ```bash SKILL_DIR=""; python3 "$SKILL_DIR/scripts/repo-profile-cache.py" get ``` `get` prints exactly one of: - `HIT` then the profile JSON on the following lines → load it as the agnostic profile; skip derivation. - `MISS` then a write-path on the next line → dispatch the `repo-profiler` persona to derive the profile, write its JSON output to a file, then persist it. This `put` runs after the profiler, so it is a **separate Bash-tool call** from the `get` above — shell variables do not persist between calls, so **re-set `SKILL_DIR` in the same command**: ```bash SKILL_DIR=""; python3 "$SKILL_DIR/scripts/repo-profile-cache.py" put ``` - `NO-CACHE` → no git repo or no writable cache. Derive the profile fresh for this run and **skip** `put` (nothing to persist). In all three cases, after the agnostic profile is in hand, run **this skill's question-specific grounding fresh** on top of it. ## Freshness (delta-aware) A cached entry is a `HIT` only when, at the current `HEAD`, its `profile_schema_version` matches and **no profile-input path** is dirty or newly-added. Freshness is checked with `git status --porcelain --untracked-files=all`, so untracked (`??`) new inputs invalidate too. The profile-input set is a conservative **superset** of every file the schema derives from — dependency manifests + lockfiles (any depth), license, root instruction/doc files, `CONCEPTS.md`/`STRATEGY.md`, topology sources (`Dockerfile`, `.github/workflows/`, `.cursor/rules`). A dirty source file, `docs/plans/*`, or other non-input path does **not** invalidate. Completeness of this set is the cardinal-rule safety requirement: over-invalidating costs a re-derive; under-invalidating would serve a stale profile. ## Degradation The cache is an optimization, never a correctness dependency. Outside a git repo, with no writable `/tmp`, or on an unreadable/malformed entry, the helper returns `NO-CACHE`/`MISS` (exit 0) and the skill derives fresh. It never blocks and never serves a profile it cannot prove fresh. And if the helper *invocation itself* fails — a non-zero exit, empty output, or an unresolved `SKILL_DIR` so the script isn't found — treat it exactly like `NO-CACHE`: derive the profile fresh this run and proceed. Never stall waiting on the cache.