feat(kvb-agent-optimize): index repositories as a code graph for agents

The new category agent holds skills that act on an agent's own setup. kvb-agent-optimize sets up the general code index codebase-memory-mcp (MIT, fully local) for every repository in scope and tightens its agent instructions: agents.py reports whether AGENTS.md exists, whether CLAUDE.md and GEMINI.md point to it, dead relative links, whether it names the index, whether the binary is installed and the git state of the shared snapshot; the operator installs the index through kvb-org-operator; the agent indexes with index_repository and check_index_coverage, asks whether the snapshot is shared, sends code questions to the graph first, audits the instructions with prompt-audit and confirms every change. A domain graph the generic index cannot see goes to the line that owns the repository. kvb-optimize runs it as step 9, so kvb-dream does too. test_naming lists the category, test_dream the optimizer, and test_agent_optimize covers the checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kevin Veen-BirkenbachandClaude Opus 5.5 committed 2026-11-04 17:07:22 +01:00
1 parent 269590a93d
commit 43b0949f6b
7 files changed
+255 -4

No files matched your search

+9 -1
View File
@@ -9,7 +9,7 @@ Compatible with Claude Code, Codex, Gemini CLI, Cursor, Copilot, Windsurf, Cline
## Naming 🏷️ ## Naming 🏷️
Every first-party skill is named `kvb-<category>-<name>` with one of these categories: `think`, `create`, `code`, `ci`, `git`, `check`, `org`, `skill`, `coach`: `think` thinks without producing a file, `create` produces an artifact, `code` works on code, `ci` on pipelines, `git` on history and forges, `check` analyses and reports findings, `org` coordinates agents, `skill` works on skills themselves, `coach` serves the operator's wellbeing. Only `kvb-cleanup`, `kvb-coach`, `kvb-help` and the collectors `kvb-dream`, `kvb-optimize` and `kvb-transcend` stand without a category. Names use `a-z`, `0-9` and single hyphens, at most 64 characters, and equal their directory name. `tests/test_naming.py` enforces all of it, including that the organigram below lists every skill under its category. Every first-party skill is named `kvb-<category>-<name>` with one of these categories: `think`, `create`, `code`, `ci`, `git`, `check`, `org`, `agent`, `skill`, `coach`: `think` thinks without producing a file, `create` produces an artifact, `code` works on code, `ci` on pipelines, `git` on history and forges, `check` analyses and reports findings, `org` coordinates agents, `agent` makes a repository easy for agents (code index, agent instructions), `skill` works on skills themselves, `coach` serves the operator's wellbeing. Only `kvb-cleanup`, `kvb-coach`, `kvb-help` and the collectors `kvb-dream`, `kvb-optimize` and `kvb-transcend` stand without a category. Names use `a-z`, `0-9` and single hyphens, at most 64 characters, and equal their directory name. `tests/test_naming.py` enforces all of it, including that the organigram below lists every skill under its category.
## Organigram 🏛️ ## Organigram 🏛️
@@ -25,6 +25,7 @@ flowchart LR
kvb --> git[git] kvb --> git[git]
kvb --> check[check] kvb --> check[check]
kvb --> org[org] kvb --> org[org]
kvb --> agent[agent]
kvb --> skill[skill] kvb --> skill[skill]
kvb --> coach[coach] kvb --> coach[coach]
kvb --> none[no category] kvb --> none[no category]
@@ -72,6 +73,7 @@ flowchart LR
org --> org_robot[kvb-org-robot] org --> org_robot[kvb-org-robot]
org --> org_operator[kvb-org-operator] org --> org_operator[kvb-org-operator]
org --> org_priority[kvb-org-priority] org --> org_priority[kvb-org-priority]
agent --> agent_optimize[kvb-agent-optimize]
skill --> skill_tune[kvb-skill-tune] skill --> skill_tune[kvb-skill-tune]
skill --> skill_auto[kvb-skill-auto] skill --> skill_auto[kvb-skill-auto]
skill --> skill_essence[kvb-skill-essence] skill --> skill_essence[kvb-skill-essence]
@@ -160,6 +162,9 @@ flowchart LR
operator[kvb-org-operator] operator[kvb-org-operator]
priority[kvb-org-priority] priority[kvb-org-priority]
end end
subgraph cat_agent [agent]
agent_optimize[kvb-agent-optimize]
end
subgraph cat_skill [skill] subgraph cat_skill [skill]
autotune[kvb-skill-tune] autotune[kvb-skill-tune]
autoskill[kvb-skill-auto] autoskill[kvb-skill-auto]
@@ -272,7 +277,10 @@ flowchart LR
optimize --> essence optimize --> essence
optimize --> refactor optimize --> refactor
optimize --> tune optimize --> tune
optimize --> agent_optimize
optimize --> gaps optimize --> gaps
agent_optimize -. install .-> operator
agent_optimize -. domain graphs .-> rasci
coach --> active_listening coach --> active_listening
coach --> operator coach --> operator
coach --> rasci coach --> rasci
+66
View File
@@ -0,0 +1,66 @@
---
name: kvb-agent-optimize
description: >
Make a repository easy for agents: a code graph index (codebase-memory-mcp)
and lean agent instructions. Trigger on /kvb-agent-optimize; kvb-optimize
runs it.
---
An agent that queries a graph reads a fraction of what an agent exploring
file by file reads, and short, current, consistent instructions get followed.
This skill sets up the general code index,
[codebase-memory-mcp](https://github.com/DeusData/codebase-memory-mcp) (MIT,
fully local), and tightens the agent instructions of every repository in
scope: the current one, or those `kvb-optimize` hands over.
## Step 1: inventory
`${CLAUDE_SKILL_DIR}/scripts/agents.py --repo <dir>` prints one line per
check: AGENTS.md, whether CLAUDE.md and GEMINI.md point to it, dead relative
links in it, whether it names the index, whether the index binary is
installed, and the state of the shared snapshot
`.codebase-memory/graph.db.zst`.
## Step 2: index
- **Binary missing**: installing registers an MCP server in the agent
configuration, so it is the operator's step; hand these to the
`kvb-org-operator` skill and continue after the restart:
```bash
yay -S codebase-memory-mcp-bin
claude plugin marketplace add DeusData/codebase-memory-mcp
claude plugin install codebase-memory@codebase-memory
```
- **Index** with the MCP tool `index_repository` on the repository path, then
`check_index_coverage`; every file kind it leaves out is a finding.
- **Snapshot**: ask whether the repository shares its graph. Shared: run
`index_repository` with `persistence: true` and commit
`.codebase-memory/graph.db.zst` at a fixed cadence (every commit of it adds
a full blob; Git LFS avoids that). Not shared: add `.codebase-memory/` to
`.gitignore`, and every agent indexes on its own.
- **Domain graphs** the generic index cannot see, such as dependencies a
project declares in its own metadata, belong to the line that owns the
repository: record them as its task in the `kvb-org-rasci` register, never
build them here.
## Step 3: instructions
- AGENTS.md is the single source; CLAUDE.md and GEMINI.md only point to it
(`@AGENTS.md`).
- AGENTS.md sends code questions to the graph first (`search_graph`,
`trace_path`, `get_architecture`) and to grep only after it.
- `/claude-api prompt-audit` over AGENTS.md and the files it references finds
stale, duplicate and contradicting rules; the dead links of step 1 join its
findings.
## Step 4: confirm
One `AskUserQuestion` per improvement, `multiSelect: false`: the change, its
evidence, and `Apply now`, `Record as a task`, `Skip`. No repository file
changes before the answer, and nothing is committed.
## Output
| Repository | Check | Before | After | Action |
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env python3
"""Report how ready a repository is for agents: instructions, pointers, code index.
Args:
--repo: path of the repository.
Output:
one tab-separated line per check: check, state, detail. States are ok,
missing, drift, or for the snapshot tracked, ignored or untracked.
Exit codes:
0 report printed, 2 usage error.
"""
from __future__ import annotations
import argparse
import re
import shutil
import subprocess
import sys
from pathlib import Path
POINTERS = ("CLAUDE.md", "GEMINI.md")
INDEX = "codebase-memory-mcp"
SNAPSHOT = ".codebase-memory/graph.db.zst"
LINK = re.compile(r"\]\(([^)\s#]+)")
def fail(message: str) -> None:
print(f"agents: {message}", file=sys.stderr)
sys.exit(2)
def git_ok(repo: Path, *args: str) -> bool:
return subprocess.run(["git", *args], cwd=repo, capture_output=True, check=False).returncode == 0
def snapshot_state(repo: Path) -> str:
if not (repo / SNAPSHOT).exists():
return "missing"
if git_ok(repo, "ls-files", "--error-unmatch", SNAPSHOT):
return "tracked"
if git_ok(repo, "check-ignore", "-q", SNAPSHOT):
return "ignored"
return "untracked"
def checks(repo: Path) -> list[tuple[str, str, str]]:
agents = repo / "AGENTS.md"
text = agents.read_text(encoding="utf-8") if agents.is_file() else ""
rows = [("AGENTS.md", "ok" if text else "missing", "single source of the agent instructions")]
for name in POINTERS:
pointer = repo / name
if pointer.is_file():
points = "AGENTS.md" in pointer.read_text(encoding="utf-8")
rows.append((name, "ok" if points else "drift", f"{'points' if points else 'does not point'} to AGENTS.md"))
dead = sorted(
{
target
for target in LINK.findall(text)
if ":" not in target and not (repo / target).exists()
}
)
rows.append(("links", "drift" if dead else "ok", ", ".join(dead) or "every relative link resolves"))
rows.append(("index-pointer", "ok" if INDEX in text else "missing", f"AGENTS.md names {INDEX}"))
rows.append(("index-binary", "ok" if shutil.which(INDEX) else "missing", f"{INDEX} on PATH"))
rows.append(("snapshot", snapshot_state(repo), SNAPSHOT))
return rows
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
parser.add_argument("--repo", required=True)
args = parser.parse_args()
repo = Path(args.repo)
if not repo.is_dir():
fail(f"{repo} is not a directory")
for row in checks(repo):
print("\t".join(row))
return 0
if __name__ == "__main__":
sys.exit(main())
+4 -2
View File
@@ -26,8 +26,10 @@ inventory is skipped with one line.
7. `kvb-code-refactor`: bring the uncommitted changes up to the project's 7. `kvb-code-refactor`: bring the uncommitted changes up to the project's
standards. standards.
8. `kvb-code-tune`: optimize the hot paths the evidence measured. 8. `kvb-code-tune`: optimize the hot paths the evidence measured.
9. `kvb-think-gaps`: find the missing work and hand each pick to 9. `kvb-agent-optimize`: index every repository in scope as a code graph
`kvb-create-requirement`. and tighten its agent instructions.
10. `kvb-think-gaps`: find the missing work and hand each pick to
`kvb-create-requirement`.
## Questions ## Questions
+89
View File
@@ -0,0 +1,89 @@
"""Exercise agents.py: instruction pointers, dead links, index pointer, binary and snapshot state."""
from __future__ import annotations
import os
import shutil
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
SCRIPT = REPO_ROOT / "skills" / "kvb-agent-optimize" / "scripts" / "agents.py"
GIT = shutil.which("git")
class TestAgents(unittest.TestCase):
def setUp(self):
tmp = tempfile.TemporaryDirectory()
self.addCleanup(tmp.cleanup)
self.tmp = Path(tmp.name)
self.bin = self.tmp / "bin"
self.bin.mkdir()
os.symlink(GIT, self.bin / "git")
def _run(self, repo: Path) -> subprocess.CompletedProcess:
return subprocess.run(
[sys.executable, str(SCRIPT), "--repo", str(repo)],
capture_output=True,
text=True,
env={"PATH": str(self.bin), "HOME": str(self.tmp)},
check=False,
)
def _report(self, repo: Path) -> dict[str, tuple[str, str]]:
result = self._run(repo)
self.assertEqual(result.returncode, 0, result.stderr)
return {check: (state, detail) for check, state, detail in (line.split("\t") for line in result.stdout.splitlines())}
def test_ready_repository_with_drift(self):
repo = self.tmp / "repo"
(repo / "docs").mkdir(parents=True)
(repo / "docs" / "a.md").write_text("a", encoding="utf-8")
(repo / "AGENTS.md").write_text(
"Query codebase-memory-mcp first. [a](docs/a.md) [b](docs/b.md#x) [web](https://x.org) [top](#top)\n",
encoding="utf-8",
)
(repo / "CLAUDE.md").write_text("@AGENTS.md\n", encoding="utf-8")
(repo / "GEMINI.md").write_text("own rules\n", encoding="utf-8")
(repo / ".codebase-memory").mkdir()
(repo / ".codebase-memory" / "graph.db.zst").write_bytes(b"x")
(repo / ".gitignore").write_text(".codebase-memory/\n", encoding="utf-8")
subprocess.run([GIT, "init", "-q"], cwd=repo, check=True)
executable = self.bin / "codebase-memory-mcp"
executable.write_text("#!/bin/sh\n", encoding="utf-8")
executable.chmod(0o755)
self.assertEqual(
self._report(repo),
{
"AGENTS.md": ("ok", "single source of the agent instructions"),
"CLAUDE.md": ("ok", "points to AGENTS.md"),
"GEMINI.md": ("drift", "does not point to AGENTS.md"),
"links": ("drift", "docs/b.md"),
"index-pointer": ("ok", "AGENTS.md names codebase-memory-mcp"),
"index-binary": ("ok", "codebase-memory-mcp on PATH"),
"snapshot": ("ignored", ".codebase-memory/graph.db.zst"),
},
)
def test_bare_directory(self):
report = self._report(self.tmp)
self.assertEqual(
{check: state for check, (state, _) in report.items()},
{
"AGENTS.md": "missing",
"links": "ok",
"index-pointer": "missing",
"index-binary": "missing",
"snapshot": "missing",
},
)
def test_missing_repository_exits_2(self):
self.assertEqual(self._run(self.tmp / "none").returncode, 2)
if __name__ == "__main__":
unittest.main()
+1
View File
@@ -38,6 +38,7 @@ class TestDreamSkill(unittest.TestCase):
"kvb-skill-essence", "kvb-skill-essence",
"kvb-code-refactor", "kvb-code-refactor",
"kvb-code-tune", "kvb-code-tune",
"kvb-agent-optimize",
"kvb-think-gaps", "kvb-think-gaps",
): ):
self.assertIn(f"`{skill}`", text) self.assertIn(f"`{skill}`", text)
+1 -1
View File
@@ -9,7 +9,7 @@ from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1] REPO_ROOT = Path(__file__).resolve().parents[1]
SKILLS = REPO_ROOT / "skills" SKILLS = REPO_ROOT / "skills"
README = REPO_ROOT / "README.md" README = REPO_ROOT / "README.md"
CATEGORIES = ("think", "create", "code", "ci", "git", "check", "org", "skill", "coach") CATEGORIES = ("think", "create", "code", "ci", "git", "check", "org", "agent", "skill", "coach")
UNCATEGORIZED = frozenset( UNCATEGORIZED = frozenset(
{ {
"kvb-cleanup", "kvb-cleanup",