Thanks for helping build the Code Knowledge Graph. This guide gets you (or your
AI assistant) productive fast. Read docs/ARCHITECTURE.md
first for the system map; this doc is the how to work on it.
Tooling is uv, not pip.
uv sync --extra dev --extra bedrock # engine ships in base; dev = test/lint toolchain
cp .env.example .env # optional: AWS creds for live model testsagentforge add moduledoes not work here (it shells out to pip, absent in uv venvs). Declare deps inpyproject.tomlanduv sync.- Run the engine CLI as
uv run ckg …, the framework CLI asuv run agentforge ….
CI runs exactly this; keep it green locally:
uv run pytest # tests, ≥90% coverage floor (enforced via pyproject)
uv run mypy src/agentforge_graph # --strict
uv run ruff format . && uv run ruff check .- No model calls or cloud creds in CI. Deterministic fakes (
FakeEmbedder,ScriptedJudge,ScriptedSummarizer) stand in for the live adapters. - Live model tests are env-gated:
CKG_LIVE_BEDROCK=1(Bedrock embeddings),CKG_LIVE_AGENT=1(Bedrock Claude judge/summarizer) with AWS creds;CKG_LIVE_ANTHROPIC=1(direct Anthropic API, needsANTHROPIC_API_KEY),CKG_LIVE_OPENAI=1(OpenAI embeddings, needsOPENAI_API_KEY+--extra openai).
We follow a design-first, chunked, validated flow:
- Pick a unit of work (a feature spec in
docs/features/, a bug, an enhancement). Never invent feature numbers — branchfeat/NNN-slugmust match an existing spec;bug/<slug>andenh/<slug>for the others. - Design (for features): write
docs/design/design-NNN-slug.mdand get it approved before coding. Bugs/enhancements use theirdocs/<category>/doc as the spec. - Branch from fresh
main:git checkout main && git pull && git checkout -b feat/NNN-slug. - Implement in chunks — several focused commits on the branch.
- Validate — the full quality gate above, plus dogfood the change on a real repo when it's user-facing.
- One PR, opened only when complete and green. Squash-merge.
Findings from evaluation are filed under docs/bugs/, docs/enhancements/,
docs/known-limitations/ — each directory has a README.md template.
The deterministic engine never imports agentforge. Packages core,
config, ingest, store, chunking, embed, retrieve, repomap,
frameworks, knowledge are framework-free and have a layering test that
parses their imports. Only serve (MCP/Tools) and enrich (budget rails
- LLM) may import
agentforge. Keep new engine code framework-free; if you need the framework, you're in the wrong layer.
src/agentforge_graph/ingest/packs/<lang>/:__init__.py(aLanguagePack),structure.scm(defs/imports),references.scm(calls). Pickmodule_style="dotted"(Python/Java) or"relative"(TS/JS).- Register it in
packs/__init__.py(BUILTIN_PACKS). - Add a golden test + the
ExtractorConformancesuite. Mirrorpacks/typescript/. - Note: drive parsing with
Parser(get_language(name)), neverget_parser()(tree-sitter-language-pack ABI quirk — seedocs/framework/).
src/agentforge_graph/frameworks/packs/<name>/: aFrameworkPackwithdep_names/import_markers(detection) andextract()emitting framework nodes/edges. Pick the closest reference pack: routes →fastapi/flask(Python),express(JS/TS),spring/nestjs(decorator style); ORM →sqlalchemy/django.- Reuse the shared rails — don't re-parse from scratch:
packs/_python_ast.py(Python AST helpers:iter_class_assignments,enclosing_class,member_descriptor, …),packs/_js_ast.py(JS/TS), andframeworks/orm.py(ModelIndex+relations_to_edgesforRELATES_TO). - A pack spanning sibling languages (e.g. JS and TS) overrides the
FrameworkPack.slugsproperty and builds ids with the file's own slug. - Cross-file stitching (FK/relationship targets, route prefixes) goes in the
pass-2
resolve()hook — seesqlalchemy/django. It's globally idempotent. - Register in
frameworks/registry.py. Pass-1 facts are merged into theFileSubgraphso they ride incremental indexing automatically; add a golden pack test + an end-to-end test asserting the edge lands on the real symbol node (mirrortests/frameworks/test_<name>_{pack,integration}.py).
- Implement
GraphStoreand/orVectorStore(core/contracts.py). - Pass
GraphStoreConformance/VectorStoreConformance(core/conformance.py). - Register via the entry-point group
agentforge_graph.graph_drivers/…vector_drivers, or add to the built-ins instore/registry.py. - Users select it with
store.graph.driver: <name>inckg.yaml.
The model layer is a provider registry (ENH-003), mirroring storage drivers — no
core change needed. First-party built-ins already ship for embeddings
(bedrock, openai, fake) and enrichment (bedrock, anthropic, scripted);
embed.base_url makes the openai driver work against any OpenAI-compatible
local server. See the full guide:
docs/guides/08-model-providers.md.
To add a new one out-of-tree:
- Implement
Embedder(embed/base.py) and/orPatternJudge/Summarizer(enrich/judge.py,enrich/summarizer.py). For an LLM judge/summarizer, the shortcut is a customClaudeClient(theinvoke()/cost_usdduck type inenrich/claude.py) passed to the sharedClaudeJudge/ClaudeSummarizer— you reuse all prompts, parsing, cost, and budget rails. - Expose a builder
(EmbedConfig|EnrichConfig) -> instanceunder the matching entry-point group:agentforge_graph.embedder_providers,agentforge_graph.judge_providers, oragentforge_graph.summarizer_providers(or add it to the built-ins inembed/registry.py/enrich/registry.pyfor a first-party one). - Users select it from
ckg.yaml:embed.driver: <name>(embeddings) orenrich.provider: <name>(judge + summarizer).fake/scriptedare the built-in credential-free providers for offline runs. Lazy-import the SDK so the offline path never needs it; add an--extrafor any new dependency.
Subclass _CkgTool in serve/tools.py, declare name/description/
input_schema, implement run(), add the class to ALL_TOOLS. The tool set is
locked-by-test, so update tests/serve/test_schemas.py.
Drain a DirtySet consumer, emit llm-provenance facts via store.add, run
under BudgetPolicy, and use clear_outgoing (not add alone) for idempotent
re-derivation. Note: don't delete+recreate stable edges on a live connection —
see the Kuzu forward-rel-scan note in docs/framework/.
This codebase is designed to be worked on with AI agents (Claude Code, …). When you point one at the repo:
- It auto-reads
AGENTS.md(the convention file) — the invariants and anti-patterns. Keep that file accurate. - Have it read
docs/ARCHITECTURE.mdfor the map and the relevantdocs/design/doc before changing a subsystem. - Dogfood: you can run agentforge-graph on itself to give the assistant
grounded context —
ckg index . && ckg serve-mcp --repo .exposes this repo's own graph (decisions, routes, impact, summaries) as tools. - Respect the gate: every AI-authored change still passes
pytest + mypy --strict + ruffand follows the one-PR-per-unit pipeline.
- Conventional-ish prefixes:
feat(NNN):,fix(BUG-NNN):,enh(ENH-NNN):,docs(…):,test(…):,chore(…):. - Never push to
maindirectly; never--no-verifywithout authorization. - Keep PRs scoped to one unit; explain the why and how you verified.