repowise-core

July 28, 2026 · View on GitHub

Core library for repowise — the ingestion pipeline, dependency graph engine, documentation generation system, and persistence layer. All other repowise packages depend on this.

Python >= 3.11 · Apache-2.0


What's in this package

ModulePurpose
repowise.core.ingestionFile traversal, AST parsing (tree-sitter), dependency graph, change detection
repowise.core.generationContext assembly, page generation, Jinja2 prompt templates, job system
repowise.core.persistenceSQLAlchemy models, async CRUD, full-text search, vector store
repowise.core.providersLLM provider abstraction (Anthropic, OpenAI, Ollama, LiteLLM)
repowise.core.analysisDead code detection (unreachable files, unused exports, zombie packages)
repowise.core.rate_limiterToken-bucket rate limiter (RPM + TPM) for all LLM providers

Installation

# Core only (SQLite backend, no vector search)
pip install repowise-core

# With LanceDB vector search (recommended for local/single-server deployments)
pip install "repowise-core[search]"

# With pgvector (PostgreSQL deployments)
pip install "repowise-core[pgvector]"

Supported Languages

AST parsing uses tree-sitter with one .scm query file per language. Adding a new language means writing one .scm file — ASTParser itself does not change.

LanguageExtensionsQuery file
Python.pyqueries/python.scm
TypeScript.ts, .tsxqueries/typescript.scm
JavaScript.js, .jsxqueries/javascript.scm
Go.goqueries/go.scm
Rust.rsqueries/rust.scm
Java.javaqueries/java.scm
C / C++.c, .cpp, .h, .ccqueries/c.scm, queries/cpp.scm
Ruby.rbqueries/ruby.scm
Kotlin.ktqueries/kotlin.scm

Special handlers (purpose-built parsers, not tree-sitter):

  • OpenAPI / Swagger specs (.yaml, .json)
  • Protobuf (.proto)
  • GraphQL schemas (.graphql)
  • Dockerfiles
  • GitHub Actions / CI YAML

LLM Providers

ProviderNameNotes
AnthropicanthropicPrompt caching on shared system/repo context (large-init cost reduction)
OpenAIopenaiAny OpenAI-compatible endpoint
OllamaollamaFully offline, no API key required
LiteLLMlitellm100+ providers through one interface (optional dependency)
MockmockIn-memory stub for tests — no network calls
from repowise.core.providers import get_provider

provider = get_provider("anthropic", api_key="sk-ant-...", model="claude-sonnet-4-6")
response = await provider.generate(system_prompt="...", user_prompt="...")
print(response.content)
print(f"{response.input_tokens} in / {response.output_tokens} out")

Registering a custom provider

from repowise.core.providers import register_provider
from my_package import MyProvider

register_provider("my_provider", lambda **kw: MyProvider(**kw))
provider = get_provider("my_provider", model="my-model-v1")

Key Modules

Ingestion

FileTraverser walks a repository tree respecting .gitignore, .repowiseIgnore, a hardcoded blocklist (node_modules, __pycache__, build artifacts), auto-detected generated files, and binary files.

ASTParser is a single class for all languages. It loads the matching .scm query file, runs tree-sitter queries to extract symbols and imports, and returns a ParsedFile with a consistent shape regardless of language.

GraphBuilder converts all ParsedFile outputs into a networkx.DiGraph and computes PageRank, strongly connected components (SCCs), betweenness centrality, and community detection (Louvain). The resulting graph drives generation ordering, change propagation, and dead code detection.

ChangeDetector diffs two git refs with GitPython, identifies added/modified/deleted/renamed files, detects symbol renames using a signature-similarity heuristic, and determines which wiki pages need regeneration via cascade analysis.

from repowise.core.ingestion import FileTraverser, ASTParser, GraphBuilder, ChangeDetector
from pathlib import Path

# Traverse
traverser = FileTraverser("/path/to/repo")
file_infos = list(traverser.traverse())

# Parse all files
parser = ASTParser()
graph_builder = GraphBuilder()
parsed_files = []

for fi in file_infos:
    source = Path(fi.abs_path).read_bytes()
    parsed = parser.parse_file(fi, source)
    parsed_files.append(parsed)
    graph_builder.add_file(parsed)

graph_builder.build()          # PageRank, SCCs, centrality
graph = graph_builder.graph()  # networkx.DiGraph

# Change detection
detector = ChangeDetector("/path/to/repo")
diffs = detector.get_changed_files("a1b2c3d", "HEAD")
# → [FileDiff(path="src/auth.py", status="modified"), ...]

Generation

ContextAssembler builds the prompt context for each page. For every file it assembles (in priority order, dropping lower-priority items when over the 12K-token budget): full source, symbol signatures, graph metrics, git ownership and commit history, import summaries from already-generated pages, RAG context from the vector store, co-change partner pages, dead code findings, and reverse import context.

PageGenerator orchestrates hierarchical generation across 9 levels (API contracts → symbol spotlights → file pages → SCC pages → module pages → cross-package pages → repo overview → infra pages → index pages). Within each level, up to concurrent_jobs tasks run in parallel via asyncio.Semaphore.

JobSystem checkpoints progress after every completed page so that a long init job can be resumed after an interruption.

from repowise.core.generation import ContextAssembler, GenerationConfig, PageGenerator

config = GenerationConfig()
assembler = ContextAssembler(config)
generator = PageGenerator(provider, assembler, config)

pages = await generator.generate_all(
    parsed_files, source_map, graph_builder, repo_structure, repo_name="my-repo"
)

for page in pages:
    print(page.title, page.page_type, f"{page.confidence_score:.2f}")

Persistence

Two SQL backends (SQLite / PostgreSQL) with identical async SQLAlchemy models. Pass a plain sqlite:///... or postgresql://... URL — get_db_url() injects the correct async driver automatically.

from repowise.core.persistence import (
    create_engine, init_db, create_session_factory,
    get_session, upsert_repository, upsert_page_from_generated,
)

engine = create_engine("sqlite:///path/to/wiki.db")
await init_db(engine)   # creates tables + FTS5 index (idempotent)
sf = create_session_factory(engine)

async with get_session(sf) as session:
    repo = await upsert_repository(session, name="my-repo", local_path="/path/to/repo")
    for page in pages:
        await upsert_page_from_generated(session, page, repo.id)

Key tables: repos, wiki_pages, page_versions, wiki_symbols, generation_jobs, webhook_events, graph_nodes, graph_edges, git_metadata, dead_code_findings.

from repowise.core.persistence import FullTextSearch, InMemoryVectorStore, MockEmbedder

# Full-text search (SQLite FTS5 or PostgreSQL GIN index)
fts = FullTextSearch(engine)
await fts.ensure_index()
results = await fts.search("authentication middleware", limit=10)

# Semantic search — swap MockEmbedder for a real embedder in production
store = InMemoryVectorStore(embedder=MockEmbedder())
results = await store.search("how does rate limiting work?", limit=5)

Vector store backends:

BackendWhen to use
InMemoryVectorStoreDevelopment / testing (no persistence)
LanceDBVectorStoreSQLite mode — embedded, no separate server, stored in .repowise/lancedb/
PgVectorStorePostgreSQL mode — HNSW index via the pgvector extension

Dead Code Detection

Finds unreachable files (in-degree 0 in the dependency graph), unused exports, unused internal symbols, and zombie packages. No LLM calls — purely graph analysis and git metadata.

from repowise.core.analysis.dead_code import DeadCodeAnalyzer

analyzer = DeadCodeAnalyzer(graph, git_meta_map)
report = analyzer.analyze({"min_confidence": 0.5})

for finding in report.findings:
    print(finding.kind.value, finding.file_path, f"{finding.confidence:.0%}")
    if finding.safe_to_delete:
        print("  -> safe to remove")

print(f"Deletable lines: {report.deletable_lines:,}")

Page Types

repowise generates these page types in a strict dependency-aware order. The subsystem-and-overview set (module_page, repo_overview, architecture_diagram, onboarding) is the one model-written layer: model prose when a provider is configured, structural stubs otherwise. Every other type is always rendered from structure, with no model and no key.

LevelPage TypeRenderedDescription
0api_contractstructureOpenAPI specs, Protobuf, GraphQL schemas
1symbol_spotlightstructureHigh-PageRank symbols (top 10% by centrality)
2file_pagestructureOne page per source file
3scc_pagestructureCircular dependency cluster summary
4module_pagemodel or stubSubsystem (concept) tree above the file level
5layer_pagestructureArchitectural layer summary
6repo_overviewmodel or stubRepository overview
6architecture_diagrammodel or stubRepo-level architecture diagram
7infra_pagestructureDockerfile, CI YAML, Makefile documentation
8onboardingmodel or stubCurated onboarding collection

Database Backends

BackendConfigVector search
SQLite + LanceDBsqlite:///path/to/wiki.db + [search] extraLanceDB embedded in .repowise/lancedb/
PostgreSQL + pgvectorpostgresql://user:pass@host/db + [pgvector] extraHNSW index on wiki_pages.embedding column

Development

# Install for development (from repo root)
uv pip install -e "packages/core[search]"

# Run tests
pytest tests/unit/core/

See ARCHITECTURE.md for a deep dive into how the ingestion pipeline, generation engine, and three stores fit together.