chore(agents): share codebase knowledge graph (#7215)

* chore(agents): share codebase knowledge graph

* fix(agents): address graph review feedback

* test(ci): assert all graph script lanes stay empty

* ci: refresh codebase graph nightly
This commit is contained in:
firat.sertgoz
2026-08-05 13:03:15 +03:00
committed by GitHub
parent c51a573c76
commit ee2d90f8e1
10 changed files with 238 additions and 17 deletions

3
.codebase-memory/.gitattributes vendored Normal file
View File

@@ -0,0 +1,3 @@
# Auto-generated by codebase-memory-mcp
# Treat the compressed snapshot as binary; regenerate it to resolve conflicts.
graph.db.zst binary

Binary file not shown.

View File

@@ -214,6 +214,12 @@ owned evidence and a green portable smoke must not be read as proof of them.
## Deep tier (nightly)
`codebase-graph-refresh.yml` runs at 02:30 UTC, regenerates the committed
`.codebase-memory/graph.db.zst` bootstrap snapshot from the default branch with
a checksum-pinned `codebase-memory-mcp`, and opens or updates a normal review
PR through the repository GitHub App. It never pushes generated state directly
to `main`; required checks and human approval remain in the path.
`nightly-deep-ci.yml` (04:00 UTC) reuses `platform-and-compat.yml`,
`reborn-tests.yml`, and `reborn-e2e.yml` via `workflow_call` at full scope.
`reborn-e2e.yml` owns the deterministic Reborn surface coverage used by pull
@@ -271,7 +277,8 @@ the configuration:
### Nightly alerting
One path only: `nightly-watchdog.yml` (08:00 UTC) checks the latest scheduled
run of each nightly — Nightly Deep CI, Reborn Playwright, IronClaw Stress. A
run of each nightly — Codebase Graph Refresh, Nightly Deep CI, Reborn
Playwright, IronClaw Stress. A
run that is missing, stale (>26h: the cron didn't fire),
or concluded anything but success posts a failure line (workflow, conclusion,
failed job names, run link) to the Slack channel behind

View File

@@ -0,0 +1,176 @@
name: Codebase Graph Refresh
# Regenerate the shared codebase-memory bootstrap snapshot from the default
# branch and open a normal review PR. The GitHub App token is intentional:
# PRs created with GITHUB_TOKEN do not trigger the repository's required checks.
on:
schedule:
- cron: "30 2 * * *" # Daily at 02:30 UTC, before the deep-CI batch.
workflow_dispatch:
permissions:
contents: read
concurrency:
group: codebase-graph-refresh
cancel-in-progress: false
jobs:
refresh:
if: github.repository == 'nearai/ironclaw'
runs-on: ubuntu-latest
timeout-minutes: 45
permissions:
contents: write
pull-requests: write
steps:
- name: Mint GitHub App token
id: app
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
app-id: ${{ secrets.GH_RELEASES_MANAGER_APP_ID }}
private-key: ${{ secrets.GH_RELEASES_MANAGER_APP_PRIVATE_KEY }}
- name: Check out the default branch
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
with:
ref: ${{ github.event.repository.default_branch }}
fetch-depth: 0
token: ${{ steps.app.outputs.token }}
persist-credentials: true
- name: Install pinned codebase-memory-mcp
env:
CBM_VERSION: v0.9.0
CBM_ARCHIVE: codebase-memory-mcp-linux-amd64-portable.tar.gz
CBM_SHA256: 8459d5c9d1457f2c82de3de307ffc7641ecbba2dde893427be1e62eca8ef9b25
run: |
set -euo pipefail
install_dir="${RUNNER_TEMP}/codebase-memory-mcp-bin"
archive="${RUNNER_TEMP}/${CBM_ARCHIVE}"
mkdir -p "${install_dir}"
curl --proto '=https' --tlsv1.2 -fsSLo "${archive}" \
"https://github.com/DeusData/codebase-memory-mcp/releases/download/${CBM_VERSION}/${CBM_ARCHIVE}"
echo "${CBM_SHA256} ${archive}" | sha256sum --check --strict
tar -xzf "${archive}" -C "${install_dir}" codebase-memory-mcp
chmod 0755 "${install_dir}/codebase-memory-mcp"
echo "${install_dir}" >> "${GITHUB_PATH}"
"${install_dir}/codebase-memory-mcp" --version
- name: Regenerate and validate graph snapshot
env:
CBM_CACHE_DIR: ${{ runner.temp }}/codebase-memory-cache
run: |
set -euo pipefail
codebase-memory-mcp cli index_repository \
--repo-path "${GITHUB_WORKSPACE}" \
--mode full \
--persistence true
python3 - <<'PY'
import json
import pathlib
import subprocess
root = pathlib.Path.cwd()
artifact = json.loads(
(root / ".codebase-memory" / "artifact.json").read_text(encoding="utf-8")
)
head = subprocess.check_output(
["git", "rev-parse", "HEAD"], cwd=root, text=True
).strip()
if artifact.get("commit") != head:
raise SystemExit(
"graph metadata commit does not match the checked-out default branch"
)
if artifact.get("nodes", 0) <= 0 or artifact.get("edges", 0) <= 0:
raise SystemExit("graph metadata reports an empty graph")
PY
test -s .codebase-memory/graph.db.zst
zstd --test .codebase-memory/graph.db.zst
git diff --check
changed="$(git diff --name-only)"
if [ -n "${changed}" ] && [ "${changed}" != ".codebase-memory/graph.db.zst" ]; then
echo "Graph refresh changed unexpected tracked paths:" >&2
printf '%s\n' "${changed}" >&2
exit 1
fi
- name: Open graph refresh PR
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8
with:
token: ${{ steps.app.outputs.token }}
add-paths: .codebase-memory/graph.db.zst
branch: automation/codebase-graph-refresh
delete-branch: true
commit-message: "chore(agents): refresh codebase knowledge graph"
title: "chore(agents): refresh codebase knowledge graph"
body: |
## Summary
- Refresh the committed codebase-memory bootstrap snapshot from the current default branch.
- Generated by the nightly `Codebase Graph Refresh` workflow; review and merge normally.
## Change Type
- [x] CI/Infrastructure
## Linked Issue
None.
## Validation
- [x] Relevant tests pass: full graph indexing completed and reported non-zero nodes and edges.
- [x] Manual testing: snapshot metadata matches the indexed default-branch commit and `zstd --test` passes.
## Test Strategy
User behavior: Agents cloning the repository receive a recent bootstrap graph.
Risk areas:
- [x] Persistence
- [x] Side effect
Tests added or updated:
- Unit or contract: Not applicable: generated binary snapshot only.
- Reborn integration: Not applicable: no Reborn behavior changes.
- Recorded fixture: Not applicable: no model behavior changes.
- Browser E2E: Not applicable: no browser behavior changes.
- Backend or runtime: Not applicable: no Ironclaw runtime behavior changes.
- Live canary: Not applicable: the generator is local and deterministic.
What the tests prove: The snapshot is non-empty, readable, and indexed from the expected commit.
Commands run: `codebase-memory-mcp cli index_repository --mode full --persistence true`; `zstd --test .codebase-memory/graph.db.zst`; `git diff --check`.
## Security Impact
The generating workflow keeps credentials host-side and limits the committed path to `.codebase-memory/graph.db.zst`.
## Reborn Trust-Boundary Checklist
N/A: agent bootstrap data only; no Reborn trust boundary changes.
## Database Impact
None.
## Blast Radius
Agent code-discovery bootstrap data only.
## Rollback Plan
Close this PR, or revert the snapshot commit after merge.
## Review Follow-Through
Confirm the snapshot size and CI results before merging.
---
**Review track**: A (generated chore)

View File

@@ -1,7 +1,7 @@
name: Nightly Watchdog
# Single alerting path for the scheduled nightlies (Nightly Deep CI, Reborn
# Playwright, IronClaw Stress): checks each workflow's latest scheduled run
# Single alerting path for the scheduled nightlies (Codebase Graph Refresh,
# Nightly Deep CI, Reborn Playwright, IronClaw Stress): checks each workflow's latest scheduled run
# and posts failures to Slack. Watching from OUTSIDE the runs
# is deliberate — an in-run alert job dies with its own run on a
# startup_failure (zero jobs execute; Nightly Deep CI was dark from its
@@ -32,6 +32,7 @@ jobs:
fail-fast: false
matrix:
workflow:
- Codebase Graph Refresh
- Nightly Deep CI
- Reborn Playwright
- IronClaw Stress

8
.gitignore vendored
View File

@@ -83,10 +83,12 @@ tests/fixtures/llm_traces/live/*.log
.anvil/
.codegraph/
# codebase-memory knowledge graph — build artifact, not source.
# Rebuilt from code via the codebase-memory MCP; per-environment, never committed.
# codebase-memory knowledge graph. Share only the compressed bootstrap snapshot;
# local metadata and runtime state remain per-environment.
# See CLAUDE.md -> "Code Discovery".
.codebase-memory/
.codebase-memory/*
!.codebase-memory/.gitattributes
!.codebase-memory/graph.db.zst
# test-tools fixture-bundle build artifacts — regenerate with
# scripts/build-test-tools.sh; only sources/manifests/schemas/prompts are tracked.

View File

@@ -6,11 +6,11 @@
This repo can be indexed into a **codebase knowledge graph** (the `codebase-memory` MCP server) over `crates/`. For any *where-is / who-calls / how-does-data-flow / what-does-this-touch* question, **probe the graph before reaching for `Grep`** — text search cannot see cross-crate call chains. A WebUI feature normally crosses `webui → ProductSurface → product → composition → runtime`, with the frontend inside `ironclaw_webui`.
**Where it lives:** `.codebase-memory/graph.db.zst` a **git-ignored build artifact, not source**. One per environment, rebuilt from code. Never commit it.
**Where it lives:** `.codebase-memory/graph.db.zst` is a committed, compressed bootstrap snapshot shared by the team. The MCP imports it and incrementally catches up to the current checkout; local databases and `.codebase-memory/artifact.json` remain git-ignored per-environment state. Refresh the shared snapshot from a clean `main` checkout with `index_repository(repo_path=".", persistence=true)`.
**Freshness (check at the start of a discovery task):** run `bash scripts/codebase-graph.sh status` — it compares the graph's indexed commit against `HEAD`. Then:
- **Missing** → `index_repository(repo_path=".")` once to build it.
- **Stale** → `detect_changes(since="<indexed-commit>")` for the changed symbols + blast radius, or re-run `index_repository` to fully refresh.
- **Missing** → `index_repository(repo_path=".", persistence=true)` once to build it.
- **Stale** → `detect_changes(since="<indexed-commit>")` for the changed symbols + blast radius, or re-run `index_repository(repo_path=".", persistence=true)` to fully refresh the shared snapshot.
- The graph is a point-in-time index — verify anything it asserts against live code before acting.
**Discovery recipes (use these instead of `Grep` for code structure):**

View File

@@ -51,7 +51,14 @@ FULL_EVENTS = {"merge_group", "push", "workflow_call", "workflow_dispatch", "sch
# the only satisfiable behaviour for that class was "never edit it", which is
# not a policy anyone chose. Classifying it is the fix; loosening the
# fail-closed arm is not.
IGNORED_PREFIXES = ("docs/", ".claude/", ".github/ISSUE_TEMPLATE/")
IGNORED_PREFIXES = (
"docs/",
".claude/",
".github/ISSUE_TEMPLATE/",
# Agent bootstrap data only. The compressed graph and its attributes do
# not change an Ironclaw crate, test, or runtime surface.
".codebase-memory/",
)
IGNORED_GUIDANCE_PATHS = {
"tests/CLAUDE.md",
"tests/integration/CLAUDE.md",
@@ -168,6 +175,8 @@ PR_STATIC_CONTROL_PATHS = {
# * `run-reborn-webui.sh` is a local developer launcher for the WebUI dev
# server. It is referenced by no workflow at all (a search over
# `.github/` finds nothing), so no lane can be selected for it.
# * `codebase-graph.sh` inspects agent-only graph metadata. It does not
# execute or select a Reborn product test surface.
# * `build-wasm-extensions.sh` is named in the same
# `has_direct_wasm_abi_risk` classifier, which both scopes and runs it,
# and additionally has a Code Style self-test
@@ -178,6 +187,7 @@ PR_STATIC_CONTROL_PATHS = {
"scripts/build-wasm-extensions.sh",
"scripts/check-version-bumps.sh",
"scripts/run-reborn-webui.sh",
"scripts/codebase-graph.sh",
# Container build inputs. `platform-and-compat.yml` keys `has_docker_risk`
# off exactly this pair and owns the image build; Code Style additionally
# proves every `include_str!` target is inside each build context

View File

@@ -707,6 +707,25 @@ class RebornPrTestPlanTests(unittest.TestCase):
self.assertEqual(plan["root_partitions"], [], path)
self.assertEqual(plan["integration_lanes"], [], path)
def test_codebase_memory_artifacts_select_no_rust_lane(self) -> None:
"""Shared agent graph data has no Reborn product or test surface."""
for path in (
".codebase-memory/.gitattributes",
".codebase-memory/graph.db.zst",
):
with self.subTest(path=path):
plan = self.plan("pull_request", [path])
self.assertEqual(plan["mode"], "none", path)
self.assertEqual(plan["crate_buckets"], [], path)
self.assertEqual(plan["root_partitions"], [], path)
self.assertEqual(plan["integration_lanes"], [], path)
paired = self.plan(
"pull_request", [path, "crates/alpha/src/lib.rs"]
)
self.assertEqual(paired["mode"], "selected", path)
self.assertNotEqual(paired["crate_buckets"], [], path)
def test_repo_wide_test_guidance_selects_no_rust_lane(self) -> None:
for path in ("tests/CLAUDE.md", "tests/integration/CLAUDE.md"):
with self.subTest(path=path):
@@ -773,12 +792,15 @@ class RebornPrTestPlanTests(unittest.TestCase):
"scripts/reborn-e2e-rust.sh",
"scripts/check-version-bumps.sh",
"scripts/run-reborn-webui.sh",
"scripts/codebase-graph.sh",
".gitignore",
):
with self.subTest(path=path):
plan = self.plan("pull_request", [path])
self.assertEqual(plan["mode"], "none", path)
self.assertEqual(plan["crate_buckets"], [], path)
self.assertEqual(plan["root_partitions"], [], path)
self.assertEqual(plan["integration_lanes"], [], path)
# The plan must say *why*, so a future reader sees the
# decision rather than a silent "nothing to run".
self.assertTrue(

View File

@@ -2,10 +2,10 @@
#
# codebase-graph.sh — freshness status for the codebase-memory knowledge graph.
#
# The graph (.codebase-memory/graph.db.zst) is a git-ignored build artifact indexed
# by the codebase-memory MCP server. This script only INSPECTS freshness; the actual
# (re)indexing is done through the MCP tools invoked by an agent:
# - build: index_repository(repo_path=".")
# The graph (.codebase-memory/graph.db.zst) is a committed bootstrap snapshot indexed
# by the codebase-memory MCP server. Local artifact metadata remains git-ignored. This
# script only INSPECTS freshness; the actual (re)indexing is done through MCP tools:
# - build/refresh: index_repository(repo_path=".", persistence=true)
# - delta/impact: detect_changes(since="<indexed-commit>")
#
# Usage: bash scripts/codebase-graph.sh [status]
@@ -20,7 +20,7 @@ read_field() { python3 -c "import json,sys;print(json.load(open('$artifact')).ge
status() {
if [ ! -f "$artifact" ]; then
echo "graph: MISSING (no .codebase-memory/artifact.json)"
echo "action: build it once — call index_repository(repo_path=\".\") via the codebase-memory MCP"
echo "action: import/build it — call index_repository(repo_path=\".\", persistence=true) via the codebase-memory MCP"
return 2
fi
@@ -44,12 +44,12 @@ status() {
n="$(git rev-list --count "$indexed"..HEAD 2>/dev/null || echo '?')"
echo "status: STALE — ${n} commit(s) behind HEAD"
echo "action: delta — detect_changes(since=\"$indexed\") (changed symbols + blast radius)"
echo " or full refresh — index_repository(repo_path=\".\")"
echo " or full refresh — index_repository(repo_path=\".\", persistence=true)"
return 1
fi
echo "status: DIVERGED — indexed commit is not in current history (rebase/force-push?)"
echo "action: full re-index — index_repository(repo_path=\".\")"
echo "action: full re-index — index_repository(repo_path=\".\", persistence=true)"
return 1
}