ctxgraph 0.2.2__tar.gz → 0.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/PKG-INFO +87 -66
  2. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/README.md +86 -65
  3. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/pyproject.toml +1 -1
  4. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph.egg-info/PKG-INFO +87 -66
  5. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/setup.cfg +0 -0
  6. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/__init__.py +0 -0
  7. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/analyzers/__init__.py +0 -0
  8. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/analyzers/python/__init__.py +0 -0
  9. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/analyzers/python/importer.py +0 -0
  10. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/analyzers/python/semantic.py +0 -0
  11. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/analyzers/python/symbols.py +0 -0
  12. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/capsule/__init__.py +0 -0
  13. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/capsule/renderer.py +0 -0
  14. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/cli/__init__.py +0 -0
  15. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/cli/main.py +0 -0
  16. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/clients/__init__.py +0 -0
  17. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/clients/models.py +0 -0
  18. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/config/__init__.py +0 -0
  19. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/config/providers.py +0 -0
  20. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/config/settings.py +0 -0
  21. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/exclude/__init__.py +0 -0
  22. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/exclude/patterns.py +0 -0
  23. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/graph/__init__.py +0 -0
  24. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/graph/builder.py +0 -0
  25. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/graph/models.py +0 -0
  26. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/graph/query.py +0 -0
  27. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/graph/storage.py +0 -0
  28. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/mcp/__init__.py +0 -0
  29. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/mcp/server.py +0 -0
  30. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/view/__init__.py +0 -0
  31. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/view/visualizer.py +0 -0
  32. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/wrapper/__init__.py +0 -0
  33. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph/wrapper/claude.py +0 -0
  34. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph.egg-info/SOURCES.txt +0 -0
  35. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph.egg-info/dependency_links.txt +0 -0
  36. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph.egg-info/entry_points.txt +0 -0
  37. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph.egg-info/requires.txt +0 -0
  38. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/src/ctxgraph.egg-info/top_level.txt +0 -0
  39. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/tests/test_analyzers.py +0 -0
  40. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/tests/test_benchmark.py +0 -0
  41. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/tests/test_capsule.py +0 -0
  42. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/tests/test_config.py +0 -0
  43. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/tests/test_integration.py +0 -0
  44. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/tests/test_model_mode.py +0 -0
  45. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/tests/test_models.py +0 -0
  46. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/tests/test_query.py +0 -0
  47. {ctxgraph-0.2.2 → ctxgraph-0.2.4}/tests/test_storage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ctxgraph
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: AI context engine for Python — cuts LLM tokens 97% via code knowledge graphs. Build, query, and generate compact context capsules for Claude, OpenAI, Ollama.
5
5
  Author: ctxgraph contributors
6
6
  License: MIT
@@ -270,7 +270,7 @@ max_depth = 2
270
270
  | `AZURE_OPENAI_API_KEY` | Azure OpenAI API |
271
271
 
272
272
  ```bash
273
- # Ollama (default)
273
+ # Ollama (default — no env vars needed)
274
274
  ctx capsule "query"
275
275
 
276
276
  # Claude
@@ -290,6 +290,17 @@ CTXGRAPH_PROVIDER=azure \
290
290
  CTXGRAPH_PROVIDER=custom CTXGRAPH_ENDPOINT=http://my-api/v1 ctx capsule "query"
291
291
  ```
292
292
 
293
+ > **Windows (PowerShell):** Use `$env:` prefix instead:
294
+ > ```powershell
295
+ > $env:CTXGRAPH_PROVIDER = "azure"; $env:CTXGRAPH_MODEL = "gpt-4o"; ctx capsule "query"
296
+ > ```
297
+ > Or set them once per session:
298
+ > ```powershell
299
+ > $env:CTXGRAPH_PROVIDER = "azure"
300
+ > $env:AZURE_OPENAI_API_KEY = "sk-..."
301
+ > ctx capsule "query"
302
+ > ```
303
+
293
304
  ---
294
305
 
295
306
  ## Use Cases
@@ -319,9 +330,15 @@ ctx capsule "extract payment processing into separate module" --mode deep
319
330
 
320
331
  ## Framework Integrations
321
332
 
322
- ctxgraph can be used as a Python library — not just a CLI. This makes it easy to plug into LangChain, LangGraph, OpenAI Agents, or any custom AI pipeline.
333
+ ctxgraph is a Python library first the CLI is just a wrapper. This makes it easy to feed code context into LangChain, LangGraph, OpenAI Agents, or any LLM pipeline.
334
+
335
+ ### How the Python API works
336
+
337
+ The flow is always the same:
323
338
 
324
- ### Python API
339
+ 1. **`build_graph(path)`** → scans your code, stores a knowledge graph in `path/.ctxgraph/graph.db`
340
+ 2. **`get_storage(path)`** → opens that SQLite database for queries (fast, no re-scanning)
341
+ 3. **`render_capsule(storage, query)`** → searches the graph, returns a compact text capsule
325
342
 
326
343
  ```python
327
344
  from pathlib import Path
@@ -329,154 +346,158 @@ from ctxgraph.graph.builder import build_graph, get_storage
329
346
  from ctxgraph.capsule.renderer import render_capsule
330
347
  from ctxgraph.graph.query import search_relevant_nodes
331
348
 
332
- # 1. Build the graph (one-time setup)
333
- stats = build_graph(Path("/path/to/project"))
349
+ # --- Step 1: Build (one-time, ~0.1-1s per project) ---
350
+ stats = build_graph(Path("/path/to/my_project"))
334
351
  print(f"Built: {stats['total_nodes']} nodes, {stats['total_edges']} edges")
335
352
 
336
- # 2. Get storage for an existing graph
337
- storage = get_storage(Path("/path/to/project"))
353
+ # --- Step 2: Use (instant reads the .db file) ---
354
+ storage = get_storage(Path("/path/to/my_project"))
338
355
 
339
- # 3. Generate a context capsule (token-efficient text)
356
+ # Generate a capsule — a token-efficient DSL string
340
357
  capsule = render_capsule(storage, "fix JWT token validation", max_nodes=20)
341
358
  print(capsule)
342
-
343
- # 4. Search for relevant nodes programmatically
359
+ # → [CTX]fix JWT token validation
360
+ # [F]src/auth/jwt.py
361
+ # D:JWT token creation and validation
362
+ # [F]src/auth/middleware.py
363
+ # D:Auth middleware for request validation
364
+ # ...
365
+
366
+ # Or search for nodes programmatically
344
367
  results = search_relevant_nodes(storage, "auth login", max_nodes=10, max_depth=2)
345
368
  for node, score in results:
346
369
  print(f" {node.type}:{node.name} (score={score})")
347
370
  ```
348
371
 
372
+ > **Tip:** `build_graph` is a one-time setup. In production, run `ctx build` during CI/deployment and let your app code only call `get_storage` + `render_capsule`.
373
+
349
374
  ### LangChain
350
375
 
351
- Inject ctxgraph capsules directly into your LangChain prompts dramatically reducing token usage while providing precise code context.
376
+ Pass the capsule as context in your prompt template. The LLM gets exactly the files, classes, and dependencies it needs — no token waste.
352
377
 
353
378
  ```python
354
379
  from pathlib import Path
355
380
  from langchain_openai import ChatOpenAI
356
381
  from langchain_core.prompts import ChatPromptTemplate
357
- from ctxgraph.graph.builder import build_graph, get_storage
382
+ from ctxgraph.graph.builder import get_storage # graph already built
358
383
  from ctxgraph.capsule.renderer import render_capsule
359
384
 
360
- # Build graph once
361
- build_graph(Path("./my_project"))
385
+ # Load existing graph (zero build time)
362
386
  storage = get_storage(Path("./my_project"))
363
387
 
364
- # Generate context for a specific task
365
- context = render_capsule(storage, "user authentication flow", max_nodes=20)
388
+ # Generate capsule for the question
389
+ context = render_capsule(storage, "login rate limiter", max_nodes=15)
366
390
 
367
391
  prompt = ChatPromptTemplate.from_messages([
368
- ("system", "You are a senior Python developer. Use the code context below to answer the question.\n\n{context}"),
392
+ ("system", "You are a senior Python dev. Answer using the code context below.\n\n{context}"),
369
393
  ("user", "{question}"),
370
394
  ])
371
395
 
372
396
  llm = ChatOpenAI(model="gpt-4o")
373
- chain = prompt | llm
397
+ response = prompt | llm | (lambda msg: msg.content)
374
398
 
375
- response = chain.invoke({
399
+ print(response.invoke({
376
400
  "context": context,
377
- "question": "Where is the login rate limiter implemented?",
378
- })
401
+ "question": "Where is the rate limiter applied in the login flow?",
402
+ }))
403
+ # → "The rate limiter is in src/auth/middleware.py at line 42.
404
+ # It wraps the login endpoint with a 5req/min limit per IP."
379
405
  ```
380
406
 
381
407
  ### LangGraph
382
408
 
383
- Use ctxgraph as a tool within a LangGraph agent the agent requests context capsules when it needs to understand the codebase.
409
+ Expose ctxgraph as a tool the agent calls on-demand. The agent fetches context only when it hits a code-related question.
384
410
 
385
411
  ```python
386
412
  from pathlib import Path
387
- from typing import Literal
388
413
  from langgraph.graph import StateGraph, MessagesState
389
414
  from langgraph.prebuilt import ToolNode
390
415
  from langchain_openai import ChatOpenAI
391
416
  from langchain_core.tools import tool
392
- from ctxgraph.graph.builder import build_graph, get_storage
417
+ from ctxgraph.graph.builder import get_storage
393
418
  from ctxgraph.capsule.renderer import render_capsule
394
419
 
395
- # Pre-build graph
396
- build_graph(Path("./my_project"))
397
- storage = get_storage(Path("./my_project"))
420
+ # Pre-built graph — loaded instantly
421
+ _storage = get_storage(Path("./my_project"))
398
422
 
399
423
  @tool
400
424
  def code_context(task: str) -> str:
401
- """Get code context relevant to a task. Use this before answering code questions."""
402
- return render_capsule(storage, task, max_nodes=20)
425
+ """Fetch relevant source code for a development task.
426
+ Use this whenever the user asks about implementation details,
427
+ bug fixes, or architecture in the codebase."""
428
+ return render_capsule(_storage, task, max_nodes=20)
403
429
 
430
+ # --- Build LangGraph ---
404
431
  tools = [code_context]
405
- tool_node = ToolNode(tools)
406
-
407
432
  model = ChatOpenAI(model="gpt-4o", temperature=0).bind_tools(tools)
408
433
 
409
- def should_continue(state: MessagesState) -> Literal["tools", "__end__"]:
410
- return "tools" if state["messages"][-1].tool_calls else "__end__"
411
-
412
- def call_model(state: MessagesState):
434
+ def agent_node(state: MessagesState):
413
435
  return {"messages": [model.invoke(state["messages"])]}
414
436
 
415
437
  graph = StateGraph(MessagesState)
416
- graph.add_node("agent", call_model)
417
- graph.add_node("tools", tool_node)
438
+ graph.add_node("agent", agent_node)
439
+ graph.add_node("tools", ToolNode(tools))
418
440
  graph.set_entry_point("agent")
419
- graph.add_conditional_edges("agent", should_continue)
441
+ graph.add_conditional_edges(
442
+ "agent",
443
+ lambda s: "tools" if s["messages"][-1].tool_calls else "__end__",
444
+ )
420
445
  graph.add_edge("tools", "agent")
421
446
 
422
447
  app = graph.compile()
423
448
 
424
- for chunk in app.stream({"messages": [("user", "Find the bug in the payment processor")]}):
425
- for node, msg in chunk.items():
426
- print(f"[{node}]: {msg['messages'][0].content[:200] if msg.get('messages') else ''}")
449
+ # --- Run ---
450
+ for chunk in app.stream({"messages": [("user", "How does payment retry work?")]}):
451
+ for node, vals in chunk.items():
452
+ msg = vals["messages"][0]
453
+ if hasattr(msg, "content") and msg.content:
454
+ print(f"[{node}]: {msg.content[:300]}")
427
455
  ```
428
456
 
457
+ When the user asks about code, the agent calls `code_context("payment retry")`, gets back a capsule with `[F]src/payment/retry.py`, `[F]src/payment/processor.py`, and their dependency edges, then answers with those files in context.
458
+
429
459
  ### OpenAI Agents SDK
430
460
 
431
- Use ctxgraph with the official OpenAI Agents SDK (also works with Azure OpenAI via `AzureOpenAIChatCompletionAgent`).
461
+ Same pattern ctxgraph is a function tool the agent invokes.
432
462
 
433
463
  ```python
434
464
  from pathlib import Path
435
- from openai import AzureOpenAI # or OpenAI for standard API
436
465
  from agents import Agent, Runner, function_tool
437
- from ctxgraph.graph.builder import build_graph, get_storage
466
+ from ctxgraph.graph.builder import get_storage
438
467
  from ctxgraph.capsule.renderer import render_capsule
439
468
 
440
- # Pre-build the graph
441
- build_graph(Path("./my_project"))
442
- storage = get_storage(Path("./my_project"))
469
+ _storage = get_storage(Path("./my_project"))
443
470
 
444
471
  @function_tool
445
472
  def fetch_code_context(task_description: str) -> str:
446
- """Retrieve relevant code context for a development task."""
447
- return render_capsule(storage, task_description, max_nodes=20)
473
+ """Retrieve code context from the project's knowledge graph.
474
+ Provide a task description like 'JWT auth middleware' or 'payment processor'."""
475
+ return render_capsule(_storage, task_description, max_nodes=20)
448
476
 
449
477
  agent = Agent(
450
478
  name="Code Assistant",
451
- instructions="You are a helpful coding assistant. Use the code context tool to understand the codebase before answering.",
452
- model="gpt-4o", # or AzureOpenAIChatCompletionAgent(deployment="gpt-4o", ...)
479
+ instructions="You help developers understand their codebase. Use fetch_code_context to get relevant files before answering.",
480
+ model="gpt-4o",
453
481
  tools=[fetch_code_context],
454
482
  )
455
483
 
456
- result = Runner.run_sync(
457
- agent,
458
- "How does the JWT authentication middleware work?",
459
- )
484
+ result = Runner.run_sync(agent, "How does the notification system handle email vs SMS?")
460
485
  print(result.final_output)
461
486
  ```
462
487
 
463
- ### Azure OpenAI with Custom Agent
488
+ ### Azure OpenAI (direct client)
464
489
 
465
- For Azure OpenAI, configure the client directly and inject ctxgraph context:
490
+ For Azure OpenAI or any OpenAI-compatible endpoint, inject the capsule directly into the system message.
466
491
 
467
492
  ```python
468
493
  import os
469
494
  from openai import AzureOpenAI
470
495
  from pathlib import Path
471
- from ctxgraph.graph.builder import build_graph, get_storage
496
+ from ctxgraph.graph.builder import get_storage
472
497
  from ctxgraph.capsule.renderer import render_capsule
473
498
 
474
- # Build graph
475
- build_graph(Path("./my_project"))
476
499
  storage = get_storage(Path("./my_project"))
477
-
478
- # Generate context capsule
479
- context = render_capsule(storage, "authentication and authorization", max_nodes=25)
500
+ context = render_capsule(storage, "event bus architecture", max_nodes=25)
480
501
 
481
502
  client = AzureOpenAI(
482
503
  api_version="2024-08-01-preview",
@@ -487,8 +508,8 @@ client = AzureOpenAI(
487
508
  response = client.chat.completions.create(
488
509
  model="gpt-4o", # deployment name
489
510
  messages=[
490
- {"role": "system", "content": f"You are a senior Python developer. Use the code context below.\n\n{context}"},
491
- {"role": "user", "content": "Explain the role-based access control (RBAC) implementation."},
511
+ {"role": "system", "content": f"You are a senior developer. Code context:\n\n{context}"},
512
+ {"role": "user", "content": "How do I add a new event handler?"},
492
513
  ],
493
514
  )
494
515
  print(response.choices[0].message.content)
@@ -241,7 +241,7 @@ max_depth = 2
241
241
  | `AZURE_OPENAI_API_KEY` | Azure OpenAI API |
242
242
 
243
243
  ```bash
244
- # Ollama (default)
244
+ # Ollama (default — no env vars needed)
245
245
  ctx capsule "query"
246
246
 
247
247
  # Claude
@@ -261,6 +261,17 @@ CTXGRAPH_PROVIDER=azure \
261
261
  CTXGRAPH_PROVIDER=custom CTXGRAPH_ENDPOINT=http://my-api/v1 ctx capsule "query"
262
262
  ```
263
263
 
264
+ > **Windows (PowerShell):** Use `$env:` prefix instead:
265
+ > ```powershell
266
+ > $env:CTXGRAPH_PROVIDER = "azure"; $env:CTXGRAPH_MODEL = "gpt-4o"; ctx capsule "query"
267
+ > ```
268
+ > Or set them once per session:
269
+ > ```powershell
270
+ > $env:CTXGRAPH_PROVIDER = "azure"
271
+ > $env:AZURE_OPENAI_API_KEY = "sk-..."
272
+ > ctx capsule "query"
273
+ > ```
274
+
264
275
  ---
265
276
 
266
277
  ## Use Cases
@@ -290,9 +301,15 @@ ctx capsule "extract payment processing into separate module" --mode deep
290
301
 
291
302
  ## Framework Integrations
292
303
 
293
- ctxgraph can be used as a Python library — not just a CLI. This makes it easy to plug into LangChain, LangGraph, OpenAI Agents, or any custom AI pipeline.
304
+ ctxgraph is a Python library first the CLI is just a wrapper. This makes it easy to feed code context into LangChain, LangGraph, OpenAI Agents, or any LLM pipeline.
305
+
306
+ ### How the Python API works
307
+
308
+ The flow is always the same:
294
309
 
295
- ### Python API
310
+ 1. **`build_graph(path)`** → scans your code, stores a knowledge graph in `path/.ctxgraph/graph.db`
311
+ 2. **`get_storage(path)`** → opens that SQLite database for queries (fast, no re-scanning)
312
+ 3. **`render_capsule(storage, query)`** → searches the graph, returns a compact text capsule
296
313
 
297
314
  ```python
298
315
  from pathlib import Path
@@ -300,154 +317,158 @@ from ctxgraph.graph.builder import build_graph, get_storage
300
317
  from ctxgraph.capsule.renderer import render_capsule
301
318
  from ctxgraph.graph.query import search_relevant_nodes
302
319
 
303
- # 1. Build the graph (one-time setup)
304
- stats = build_graph(Path("/path/to/project"))
320
+ # --- Step 1: Build (one-time, ~0.1-1s per project) ---
321
+ stats = build_graph(Path("/path/to/my_project"))
305
322
  print(f"Built: {stats['total_nodes']} nodes, {stats['total_edges']} edges")
306
323
 
307
- # 2. Get storage for an existing graph
308
- storage = get_storage(Path("/path/to/project"))
324
+ # --- Step 2: Use (instant reads the .db file) ---
325
+ storage = get_storage(Path("/path/to/my_project"))
309
326
 
310
- # 3. Generate a context capsule (token-efficient text)
327
+ # Generate a capsule — a token-efficient DSL string
311
328
  capsule = render_capsule(storage, "fix JWT token validation", max_nodes=20)
312
329
  print(capsule)
313
-
314
- # 4. Search for relevant nodes programmatically
330
+ # → [CTX]fix JWT token validation
331
+ # [F]src/auth/jwt.py
332
+ # D:JWT token creation and validation
333
+ # [F]src/auth/middleware.py
334
+ # D:Auth middleware for request validation
335
+ # ...
336
+
337
+ # Or search for nodes programmatically
315
338
  results = search_relevant_nodes(storage, "auth login", max_nodes=10, max_depth=2)
316
339
  for node, score in results:
317
340
  print(f" {node.type}:{node.name} (score={score})")
318
341
  ```
319
342
 
343
+ > **Tip:** `build_graph` is a one-time setup. In production, run `ctx build` during CI/deployment and let your app code only call `get_storage` + `render_capsule`.
344
+
320
345
  ### LangChain
321
346
 
322
- Inject ctxgraph capsules directly into your LangChain prompts dramatically reducing token usage while providing precise code context.
347
+ Pass the capsule as context in your prompt template. The LLM gets exactly the files, classes, and dependencies it needs — no token waste.
323
348
 
324
349
  ```python
325
350
  from pathlib import Path
326
351
  from langchain_openai import ChatOpenAI
327
352
  from langchain_core.prompts import ChatPromptTemplate
328
- from ctxgraph.graph.builder import build_graph, get_storage
353
+ from ctxgraph.graph.builder import get_storage # graph already built
329
354
  from ctxgraph.capsule.renderer import render_capsule
330
355
 
331
- # Build graph once
332
- build_graph(Path("./my_project"))
356
+ # Load existing graph (zero build time)
333
357
  storage = get_storage(Path("./my_project"))
334
358
 
335
- # Generate context for a specific task
336
- context = render_capsule(storage, "user authentication flow", max_nodes=20)
359
+ # Generate capsule for the question
360
+ context = render_capsule(storage, "login rate limiter", max_nodes=15)
337
361
 
338
362
  prompt = ChatPromptTemplate.from_messages([
339
- ("system", "You are a senior Python developer. Use the code context below to answer the question.\n\n{context}"),
363
+ ("system", "You are a senior Python dev. Answer using the code context below.\n\n{context}"),
340
364
  ("user", "{question}"),
341
365
  ])
342
366
 
343
367
  llm = ChatOpenAI(model="gpt-4o")
344
- chain = prompt | llm
368
+ response = prompt | llm | (lambda msg: msg.content)
345
369
 
346
- response = chain.invoke({
370
+ print(response.invoke({
347
371
  "context": context,
348
- "question": "Where is the login rate limiter implemented?",
349
- })
372
+ "question": "Where is the rate limiter applied in the login flow?",
373
+ }))
374
+ # → "The rate limiter is in src/auth/middleware.py at line 42.
375
+ # It wraps the login endpoint with a 5req/min limit per IP."
350
376
  ```
351
377
 
352
378
  ### LangGraph
353
379
 
354
- Use ctxgraph as a tool within a LangGraph agent the agent requests context capsules when it needs to understand the codebase.
380
+ Expose ctxgraph as a tool the agent calls on-demand. The agent fetches context only when it hits a code-related question.
355
381
 
356
382
  ```python
357
383
  from pathlib import Path
358
- from typing import Literal
359
384
  from langgraph.graph import StateGraph, MessagesState
360
385
  from langgraph.prebuilt import ToolNode
361
386
  from langchain_openai import ChatOpenAI
362
387
  from langchain_core.tools import tool
363
- from ctxgraph.graph.builder import build_graph, get_storage
388
+ from ctxgraph.graph.builder import get_storage
364
389
  from ctxgraph.capsule.renderer import render_capsule
365
390
 
366
- # Pre-build graph
367
- build_graph(Path("./my_project"))
368
- storage = get_storage(Path("./my_project"))
391
+ # Pre-built graph — loaded instantly
392
+ _storage = get_storage(Path("./my_project"))
369
393
 
370
394
  @tool
371
395
  def code_context(task: str) -> str:
372
- """Get code context relevant to a task. Use this before answering code questions."""
373
- return render_capsule(storage, task, max_nodes=20)
396
+ """Fetch relevant source code for a development task.
397
+ Use this whenever the user asks about implementation details,
398
+ bug fixes, or architecture in the codebase."""
399
+ return render_capsule(_storage, task, max_nodes=20)
374
400
 
401
+ # --- Build LangGraph ---
375
402
  tools = [code_context]
376
- tool_node = ToolNode(tools)
377
-
378
403
  model = ChatOpenAI(model="gpt-4o", temperature=0).bind_tools(tools)
379
404
 
380
- def should_continue(state: MessagesState) -> Literal["tools", "__end__"]:
381
- return "tools" if state["messages"][-1].tool_calls else "__end__"
382
-
383
- def call_model(state: MessagesState):
405
+ def agent_node(state: MessagesState):
384
406
  return {"messages": [model.invoke(state["messages"])]}
385
407
 
386
408
  graph = StateGraph(MessagesState)
387
- graph.add_node("agent", call_model)
388
- graph.add_node("tools", tool_node)
409
+ graph.add_node("agent", agent_node)
410
+ graph.add_node("tools", ToolNode(tools))
389
411
  graph.set_entry_point("agent")
390
- graph.add_conditional_edges("agent", should_continue)
412
+ graph.add_conditional_edges(
413
+ "agent",
414
+ lambda s: "tools" if s["messages"][-1].tool_calls else "__end__",
415
+ )
391
416
  graph.add_edge("tools", "agent")
392
417
 
393
418
  app = graph.compile()
394
419
 
395
- for chunk in app.stream({"messages": [("user", "Find the bug in the payment processor")]}):
396
- for node, msg in chunk.items():
397
- print(f"[{node}]: {msg['messages'][0].content[:200] if msg.get('messages') else ''}")
420
+ # --- Run ---
421
+ for chunk in app.stream({"messages": [("user", "How does payment retry work?")]}):
422
+ for node, vals in chunk.items():
423
+ msg = vals["messages"][0]
424
+ if hasattr(msg, "content") and msg.content:
425
+ print(f"[{node}]: {msg.content[:300]}")
398
426
  ```
399
427
 
428
+ When the user asks about code, the agent calls `code_context("payment retry")`, gets back a capsule with `[F]src/payment/retry.py`, `[F]src/payment/processor.py`, and their dependency edges, then answers with those files in context.
429
+
400
430
  ### OpenAI Agents SDK
401
431
 
402
- Use ctxgraph with the official OpenAI Agents SDK (also works with Azure OpenAI via `AzureOpenAIChatCompletionAgent`).
432
+ Same pattern ctxgraph is a function tool the agent invokes.
403
433
 
404
434
  ```python
405
435
  from pathlib import Path
406
- from openai import AzureOpenAI # or OpenAI for standard API
407
436
  from agents import Agent, Runner, function_tool
408
- from ctxgraph.graph.builder import build_graph, get_storage
437
+ from ctxgraph.graph.builder import get_storage
409
438
  from ctxgraph.capsule.renderer import render_capsule
410
439
 
411
- # Pre-build the graph
412
- build_graph(Path("./my_project"))
413
- storage = get_storage(Path("./my_project"))
440
+ _storage = get_storage(Path("./my_project"))
414
441
 
415
442
  @function_tool
416
443
  def fetch_code_context(task_description: str) -> str:
417
- """Retrieve relevant code context for a development task."""
418
- return render_capsule(storage, task_description, max_nodes=20)
444
+ """Retrieve code context from the project's knowledge graph.
445
+ Provide a task description like 'JWT auth middleware' or 'payment processor'."""
446
+ return render_capsule(_storage, task_description, max_nodes=20)
419
447
 
420
448
  agent = Agent(
421
449
  name="Code Assistant",
422
- instructions="You are a helpful coding assistant. Use the code context tool to understand the codebase before answering.",
423
- model="gpt-4o", # or AzureOpenAIChatCompletionAgent(deployment="gpt-4o", ...)
450
+ instructions="You help developers understand their codebase. Use fetch_code_context to get relevant files before answering.",
451
+ model="gpt-4o",
424
452
  tools=[fetch_code_context],
425
453
  )
426
454
 
427
- result = Runner.run_sync(
428
- agent,
429
- "How does the JWT authentication middleware work?",
430
- )
455
+ result = Runner.run_sync(agent, "How does the notification system handle email vs SMS?")
431
456
  print(result.final_output)
432
457
  ```
433
458
 
434
- ### Azure OpenAI with Custom Agent
459
+ ### Azure OpenAI (direct client)
435
460
 
436
- For Azure OpenAI, configure the client directly and inject ctxgraph context:
461
+ For Azure OpenAI or any OpenAI-compatible endpoint, inject the capsule directly into the system message.
437
462
 
438
463
  ```python
439
464
  import os
440
465
  from openai import AzureOpenAI
441
466
  from pathlib import Path
442
- from ctxgraph.graph.builder import build_graph, get_storage
467
+ from ctxgraph.graph.builder import get_storage
443
468
  from ctxgraph.capsule.renderer import render_capsule
444
469
 
445
- # Build graph
446
- build_graph(Path("./my_project"))
447
470
  storage = get_storage(Path("./my_project"))
448
-
449
- # Generate context capsule
450
- context = render_capsule(storage, "authentication and authorization", max_nodes=25)
471
+ context = render_capsule(storage, "event bus architecture", max_nodes=25)
451
472
 
452
473
  client = AzureOpenAI(
453
474
  api_version="2024-08-01-preview",
@@ -458,8 +479,8 @@ client = AzureOpenAI(
458
479
  response = client.chat.completions.create(
459
480
  model="gpt-4o", # deployment name
460
481
  messages=[
461
- {"role": "system", "content": f"You are a senior Python developer. Use the code context below.\n\n{context}"},
462
- {"role": "user", "content": "Explain the role-based access control (RBAC) implementation."},
482
+ {"role": "system", "content": f"You are a senior developer. Code context:\n\n{context}"},
483
+ {"role": "user", "content": "How do I add a new event handler?"},
463
484
  ],
464
485
  )
465
486
  print(response.choices[0].message.content)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ctxgraph"
7
- version = "0.2.2"
7
+ version = "0.2.4"
8
8
  description = "AI context engine for Python — cuts LLM tokens 97% via code knowledge graphs. Build, query, and generate compact context capsules for Claude, OpenAI, Ollama."
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ctxgraph
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: AI context engine for Python — cuts LLM tokens 97% via code knowledge graphs. Build, query, and generate compact context capsules for Claude, OpenAI, Ollama.
5
5
  Author: ctxgraph contributors
6
6
  License: MIT
@@ -270,7 +270,7 @@ max_depth = 2
270
270
  | `AZURE_OPENAI_API_KEY` | Azure OpenAI API |
271
271
 
272
272
  ```bash
273
- # Ollama (default)
273
+ # Ollama (default — no env vars needed)
274
274
  ctx capsule "query"
275
275
 
276
276
  # Claude
@@ -290,6 +290,17 @@ CTXGRAPH_PROVIDER=azure \
290
290
  CTXGRAPH_PROVIDER=custom CTXGRAPH_ENDPOINT=http://my-api/v1 ctx capsule "query"
291
291
  ```
292
292
 
293
+ > **Windows (PowerShell):** Use `$env:` prefix instead:
294
+ > ```powershell
295
+ > $env:CTXGRAPH_PROVIDER = "azure"; $env:CTXGRAPH_MODEL = "gpt-4o"; ctx capsule "query"
296
+ > ```
297
+ > Or set them once per session:
298
+ > ```powershell
299
+ > $env:CTXGRAPH_PROVIDER = "azure"
300
+ > $env:AZURE_OPENAI_API_KEY = "sk-..."
301
+ > ctx capsule "query"
302
+ > ```
303
+
293
304
  ---
294
305
 
295
306
  ## Use Cases
@@ -319,9 +330,15 @@ ctx capsule "extract payment processing into separate module" --mode deep
319
330
 
320
331
  ## Framework Integrations
321
332
 
322
- ctxgraph can be used as a Python library — not just a CLI. This makes it easy to plug into LangChain, LangGraph, OpenAI Agents, or any custom AI pipeline.
333
+ ctxgraph is a Python library first the CLI is just a wrapper. This makes it easy to feed code context into LangChain, LangGraph, OpenAI Agents, or any LLM pipeline.
334
+
335
+ ### How the Python API works
336
+
337
+ The flow is always the same:
323
338
 
324
- ### Python API
339
+ 1. **`build_graph(path)`** → scans your code, stores a knowledge graph in `path/.ctxgraph/graph.db`
340
+ 2. **`get_storage(path)`** → opens that SQLite database for queries (fast, no re-scanning)
341
+ 3. **`render_capsule(storage, query)`** → searches the graph, returns a compact text capsule
325
342
 
326
343
  ```python
327
344
  from pathlib import Path
@@ -329,154 +346,158 @@ from ctxgraph.graph.builder import build_graph, get_storage
329
346
  from ctxgraph.capsule.renderer import render_capsule
330
347
  from ctxgraph.graph.query import search_relevant_nodes
331
348
 
332
- # 1. Build the graph (one-time setup)
333
- stats = build_graph(Path("/path/to/project"))
349
+ # --- Step 1: Build (one-time, ~0.1-1s per project) ---
350
+ stats = build_graph(Path("/path/to/my_project"))
334
351
  print(f"Built: {stats['total_nodes']} nodes, {stats['total_edges']} edges")
335
352
 
336
- # 2. Get storage for an existing graph
337
- storage = get_storage(Path("/path/to/project"))
353
+ # --- Step 2: Use (instant reads the .db file) ---
354
+ storage = get_storage(Path("/path/to/my_project"))
338
355
 
339
- # 3. Generate a context capsule (token-efficient text)
356
+ # Generate a capsule — a token-efficient DSL string
340
357
  capsule = render_capsule(storage, "fix JWT token validation", max_nodes=20)
341
358
  print(capsule)
342
-
343
- # 4. Search for relevant nodes programmatically
359
+ # → [CTX]fix JWT token validation
360
+ # [F]src/auth/jwt.py
361
+ # D:JWT token creation and validation
362
+ # [F]src/auth/middleware.py
363
+ # D:Auth middleware for request validation
364
+ # ...
365
+
366
+ # Or search for nodes programmatically
344
367
  results = search_relevant_nodes(storage, "auth login", max_nodes=10, max_depth=2)
345
368
  for node, score in results:
346
369
  print(f" {node.type}:{node.name} (score={score})")
347
370
  ```
348
371
 
372
+ > **Tip:** `build_graph` is a one-time setup. In production, run `ctx build` during CI/deployment and let your app code only call `get_storage` + `render_capsule`.
373
+
349
374
  ### LangChain
350
375
 
351
- Inject ctxgraph capsules directly into your LangChain prompts dramatically reducing token usage while providing precise code context.
376
+ Pass the capsule as context in your prompt template. The LLM gets exactly the files, classes, and dependencies it needs — no token waste.
352
377
 
353
378
  ```python
354
379
  from pathlib import Path
355
380
  from langchain_openai import ChatOpenAI
356
381
  from langchain_core.prompts import ChatPromptTemplate
357
- from ctxgraph.graph.builder import build_graph, get_storage
382
+ from ctxgraph.graph.builder import get_storage # graph already built
358
383
  from ctxgraph.capsule.renderer import render_capsule
359
384
 
360
- # Build graph once
361
- build_graph(Path("./my_project"))
385
+ # Load existing graph (zero build time)
362
386
  storage = get_storage(Path("./my_project"))
363
387
 
364
- # Generate context for a specific task
365
- context = render_capsule(storage, "user authentication flow", max_nodes=20)
388
+ # Generate capsule for the question
389
+ context = render_capsule(storage, "login rate limiter", max_nodes=15)
366
390
 
367
391
  prompt = ChatPromptTemplate.from_messages([
368
- ("system", "You are a senior Python developer. Use the code context below to answer the question.\n\n{context}"),
392
+ ("system", "You are a senior Python dev. Answer using the code context below.\n\n{context}"),
369
393
  ("user", "{question}"),
370
394
  ])
371
395
 
372
396
  llm = ChatOpenAI(model="gpt-4o")
373
- chain = prompt | llm
397
+ response = prompt | llm | (lambda msg: msg.content)
374
398
 
375
- response = chain.invoke({
399
+ print(response.invoke({
376
400
  "context": context,
377
- "question": "Where is the login rate limiter implemented?",
378
- })
401
+ "question": "Where is the rate limiter applied in the login flow?",
402
+ }))
403
+ # → "The rate limiter is in src/auth/middleware.py at line 42.
404
+ # It wraps the login endpoint with a 5req/min limit per IP."
379
405
  ```
380
406
 
381
407
  ### LangGraph
382
408
 
383
- Use ctxgraph as a tool within a LangGraph agent the agent requests context capsules when it needs to understand the codebase.
409
+ Expose ctxgraph as a tool the agent calls on-demand. The agent fetches context only when it hits a code-related question.
384
410
 
385
411
  ```python
386
412
  from pathlib import Path
387
- from typing import Literal
388
413
  from langgraph.graph import StateGraph, MessagesState
389
414
  from langgraph.prebuilt import ToolNode
390
415
  from langchain_openai import ChatOpenAI
391
416
  from langchain_core.tools import tool
392
- from ctxgraph.graph.builder import build_graph, get_storage
417
+ from ctxgraph.graph.builder import get_storage
393
418
  from ctxgraph.capsule.renderer import render_capsule
394
419
 
395
- # Pre-build graph
396
- build_graph(Path("./my_project"))
397
- storage = get_storage(Path("./my_project"))
420
+ # Pre-built graph — loaded instantly
421
+ _storage = get_storage(Path("./my_project"))
398
422
 
399
423
  @tool
400
424
  def code_context(task: str) -> str:
401
- """Get code context relevant to a task. Use this before answering code questions."""
402
- return render_capsule(storage, task, max_nodes=20)
425
+ """Fetch relevant source code for a development task.
426
+ Use this whenever the user asks about implementation details,
427
+ bug fixes, or architecture in the codebase."""
428
+ return render_capsule(_storage, task, max_nodes=20)
403
429
 
430
+ # --- Build LangGraph ---
404
431
  tools = [code_context]
405
- tool_node = ToolNode(tools)
406
-
407
432
  model = ChatOpenAI(model="gpt-4o", temperature=0).bind_tools(tools)
408
433
 
409
- def should_continue(state: MessagesState) -> Literal["tools", "__end__"]:
410
- return "tools" if state["messages"][-1].tool_calls else "__end__"
411
-
412
- def call_model(state: MessagesState):
434
+ def agent_node(state: MessagesState):
413
435
  return {"messages": [model.invoke(state["messages"])]}
414
436
 
415
437
  graph = StateGraph(MessagesState)
416
- graph.add_node("agent", call_model)
417
- graph.add_node("tools", tool_node)
438
+ graph.add_node("agent", agent_node)
439
+ graph.add_node("tools", ToolNode(tools))
418
440
  graph.set_entry_point("agent")
419
- graph.add_conditional_edges("agent", should_continue)
441
+ graph.add_conditional_edges(
442
+ "agent",
443
+ lambda s: "tools" if s["messages"][-1].tool_calls else "__end__",
444
+ )
420
445
  graph.add_edge("tools", "agent")
421
446
 
422
447
  app = graph.compile()
423
448
 
424
- for chunk in app.stream({"messages": [("user", "Find the bug in the payment processor")]}):
425
- for node, msg in chunk.items():
426
- print(f"[{node}]: {msg['messages'][0].content[:200] if msg.get('messages') else ''}")
449
+ # --- Run ---
450
+ for chunk in app.stream({"messages": [("user", "How does payment retry work?")]}):
451
+ for node, vals in chunk.items():
452
+ msg = vals["messages"][0]
453
+ if hasattr(msg, "content") and msg.content:
454
+ print(f"[{node}]: {msg.content[:300]}")
427
455
  ```
428
456
 
457
+ When the user asks about code, the agent calls `code_context("payment retry")`, gets back a capsule with `[F]src/payment/retry.py`, `[F]src/payment/processor.py`, and their dependency edges, then answers with those files in context.
458
+
429
459
  ### OpenAI Agents SDK
430
460
 
431
- Use ctxgraph with the official OpenAI Agents SDK (also works with Azure OpenAI via `AzureOpenAIChatCompletionAgent`).
461
+ Same pattern ctxgraph is a function tool the agent invokes.
432
462
 
433
463
  ```python
434
464
  from pathlib import Path
435
- from openai import AzureOpenAI # or OpenAI for standard API
436
465
  from agents import Agent, Runner, function_tool
437
- from ctxgraph.graph.builder import build_graph, get_storage
466
+ from ctxgraph.graph.builder import get_storage
438
467
  from ctxgraph.capsule.renderer import render_capsule
439
468
 
440
- # Pre-build the graph
441
- build_graph(Path("./my_project"))
442
- storage = get_storage(Path("./my_project"))
469
+ _storage = get_storage(Path("./my_project"))
443
470
 
444
471
  @function_tool
445
472
  def fetch_code_context(task_description: str) -> str:
446
- """Retrieve relevant code context for a development task."""
447
- return render_capsule(storage, task_description, max_nodes=20)
473
+ """Retrieve code context from the project's knowledge graph.
474
+ Provide a task description like 'JWT auth middleware' or 'payment processor'."""
475
+ return render_capsule(_storage, task_description, max_nodes=20)
448
476
 
449
477
  agent = Agent(
450
478
  name="Code Assistant",
451
- instructions="You are a helpful coding assistant. Use the code context tool to understand the codebase before answering.",
452
- model="gpt-4o", # or AzureOpenAIChatCompletionAgent(deployment="gpt-4o", ...)
479
+ instructions="You help developers understand their codebase. Use fetch_code_context to get relevant files before answering.",
480
+ model="gpt-4o",
453
481
  tools=[fetch_code_context],
454
482
  )
455
483
 
456
- result = Runner.run_sync(
457
- agent,
458
- "How does the JWT authentication middleware work?",
459
- )
484
+ result = Runner.run_sync(agent, "How does the notification system handle email vs SMS?")
460
485
  print(result.final_output)
461
486
  ```
462
487
 
463
- ### Azure OpenAI with Custom Agent
488
+ ### Azure OpenAI (direct client)
464
489
 
465
- For Azure OpenAI, configure the client directly and inject ctxgraph context:
490
+ For Azure OpenAI or any OpenAI-compatible endpoint, inject the capsule directly into the system message.
466
491
 
467
492
  ```python
468
493
  import os
469
494
  from openai import AzureOpenAI
470
495
  from pathlib import Path
471
- from ctxgraph.graph.builder import build_graph, get_storage
496
+ from ctxgraph.graph.builder import get_storage
472
497
  from ctxgraph.capsule.renderer import render_capsule
473
498
 
474
- # Build graph
475
- build_graph(Path("./my_project"))
476
499
  storage = get_storage(Path("./my_project"))
477
-
478
- # Generate context capsule
479
- context = render_capsule(storage, "authentication and authorization", max_nodes=25)
500
+ context = render_capsule(storage, "event bus architecture", max_nodes=25)
480
501
 
481
502
  client = AzureOpenAI(
482
503
  api_version="2024-08-01-preview",
@@ -487,8 +508,8 @@ client = AzureOpenAI(
487
508
  response = client.chat.completions.create(
488
509
  model="gpt-4o", # deployment name
489
510
  messages=[
490
- {"role": "system", "content": f"You are a senior Python developer. Use the code context below.\n\n{context}"},
491
- {"role": "user", "content": "Explain the role-based access control (RBAC) implementation."},
511
+ {"role": "system", "content": f"You are a senior developer. Code context:\n\n{context}"},
512
+ {"role": "user", "content": "How do I add a new event handler?"},
492
513
  ],
493
514
  )
494
515
  print(response.choices[0].message.content)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes