Aletheore
Evidence-grounded repository intelligence: a deterministic scanner (tree-sitter + git log, no
LLM, fully unit-tested) reads a repository and writes .aletheore/air.json — languages, module
dependency graph, modularity-based clusters, git ownership and commit cadence, secrets,
dependency vulnerabilities, layer-convention violations, dependency licenses, and static API
endpoint maps. Every other feature below (the AI-written audit report, the GitHub Action's PR
comments, the MCP server, the dashboard) is built on top of that same evidence and never states
anything it can't cite back to a specific field in it.
This is the real, working CLI package — the same code that runs in CI (a real test suite,
1,600+ tests across this package and the hosted service) and in production behind the hosted
GitHub App (see ../github-app/). Full project overview: ../README.md.
Quickstart
pipx install aletheore
aletheore scan .
cat .aletheore/air.json # the evidence everything else reads from
That's the whole deterministic path: no LLM call, no account, no network access beyond the
dependency-vulnerability/license registry lookups (skip those with --no-check-vulnerabilities --no-check-licenses for a fully offline run). Everything below - the per-language import
resolution details, audit's LLM-written report, the MCP server, the dashboard - builds on
top of that one air.json file.
Secrets, git activity, and dependency-vulnerability checks are language-agnostic. The module dependency graph (imports, clusters, layer violations) currently understands Python, JavaScript/JSX, TypeScript/TSX, Go, Rust, Java, Kotlin, Ruby, PHP, C, C++, C#, and Swift — other languages are still scanned for secrets/git/vulnerabilities, but get no dependency-graph or architecture analysis until a grammar is added for them.
Go resolution needs a go.mod at the repo root to know the module's own import-path prefix;
without one, Go imports are left unresolved (same as any import Aletheore can't place) rather
than guessed at. An import is resolved to every non-test .go file in its target directory,
since Go imports whole packages, not individual files.
Swift resolution works the same way, one level up: a Swift import names a whole compiled
target, not a file, so it's resolved to every .swift file belonging to that target - inferred
from Package.swift's own path: overrides where present, falling back to the SwiftPM
convention (Sources/<TargetName>/, Tests/<TargetName>/) everywhere else.
Rust resolution needs src/lib.rs or src/main.rs at the repo root (workspace repos with
multiple crates aren't supported yet); without one, nothing resolves. It assumes directory
structure mirrors the module tree (true for the vast majority of real code; #[path = "..."]
escape hatches aren't supported), and handles crate::/self::/super:: paths, the implicit
crate-relative form (use handlers::Handler; from the crate root), grouped ({Bar, Baz}),
wildcard (::*), and aliased (as) forms.
Java resolution has no repo-root config to read at all (no go.mod/Cargo.toml equivalent) - the
source root (Maven/Gradle's src/main/java, a bare src/, or the repo root itself) is
inferred per-file from each file's own package declaration matching its actual directory,
so it works across layouts without assuming one. Handles direct imports, wildcard imports
(fanning out to every .java file in that package, same idea as Go's package-level imports),
and import static (resolving to the class, not the imported member).
Kotlin resolution infers its source root the same way Java's does - per-file, from each
file's own package declaration matching its actual directory (Gradle's conventional
src/main/kotlin, a bare src/, or the repo root all work). Unlike Java, a Kotlin file's
name doesn't need to match any top-level declaration - multiple top-level classes/functions
per file are idiomatic and common (confirmed against android/architecture-samples) - so
import resolution tries the same-named .kt file first, then falls back to searching the
containing directory for the actual matching declaration.
Ruby's require_relative always resolves relative to the current file, unambiguous. Plain
require is genuinely ambiguous (the overwhelming majority are gems, external), so it only
resolves against a repo-root lib/ directory - the near-universal Ruby convention for a
project's own internal requires - and is left unresolved otherwise, same as an unrecognized
import in any other language here.
PHP reads composer.json's autoload.psr-4 mapping (namespace prefix -> directory, longest
prefix wins when more than one could match) to resolve use statements; with no composer.json,
use doesn't resolve at all. require/require_once/include/include_once (including the
idiomatic __DIR__ . '/../lib/util.php' form) resolve relative to the current file, the same
as Ruby's require_relative.
C/C++ only resolves quoted #include "foo.h" (relative to the current file's own directory,
the only part of the real preprocessor search order knowable without a build system's -I
flags) - angle-bracket #include <foo.h> is always treated as external/system, never resolved,
since a project using <> for its own headers via -I isn't distinguishable from a real system
header without that same build info. .h is parsed with the C++ grammar (a superset that
parses valid C too) since header files are ambiguously C-or-C++.
C# resolves using Namespace; at namespace granularity, not class granularity - unlike every
other language here, a C# using doesn't name a specific type at all, only a namespace, so it's
resolved the same way Go's package-level import already is: fan out to every .cs file in the
directory that namespace corresponds to (namespace-mirrors-directory is only a convention here,
not compiler-enforced, so real misses are expected for code that doesn't follow it). Also
accounts for <RootNamespace> (set by every dotnet new template by default), which prepends
an implicit prefix to every file's effective namespace with no corresponding directory on disk
at all - verified directly against a real dotnet build/dotnet run, which is also what
surfaced this: a naive "namespace must fully mirror the directory" version (correct for Java,
which has no such feature) resolved nothing at all until this was accounted for.
Setup
Published on PyPI (pypi.org/project/aletheore), released via a tag-triggered publish
workflow (../.github/workflows/publish-pypi.yml):
pipx install aletheore # or: pip install aletheore
To work on aletheore itself, install from source instead:
cd src
pip install -e ".[dev]"
pytest
Requires Python 3.11+.
Configuration
A scanned repo can commit a .aletheore.json at its root to extend the architecture checks —
it's read as part of scan/audit, the same deterministic way requirements.txt or a policy
doc already is (repo-declared conventions are themselves a fact about the repo, so this
doesn't break reproducibility: same repo content in, same evidence out).
{
"layer_markers": { "biz": 1 },
"cluster_resolution": 1.5,
"dead_code_entry_points": ["scripts/migrate.py"],
"accepted_secrets": [
{ "path": "tests/fixtures/sample.py", "pattern": "aws_access_key_id", "match_preview": "AKIA****...MNOP" }
]
}
aletheore init [path] scaffolds this file with all four keys present (empty/default), plus
a one-line explanation of each printed to the console.
layer_markers— extends/overrides the built-in folder-name -> layer-rank table used by layer-violation detection (e.g. a repo using abiz/folder that isn't one of the built-in names would otherwise never getconvention_detected: true). Merges with the built-in table for non-overlapping keys; only overlapping keys get overridden.cluster_resolution— passed straight into the modularity-clustering algorithm (default1.0). Higher values favor more, smaller clusters; lower values favor fewer, larger ones.dead_code_entry_points— extra file paths (beyond what's auto-detected: a framework'smain.py/app.py, test files,__init__.pyre-exports) to treat as reachable roots when computing unreferenced code - a script only ever invoked by a cron job or CI step, never imported from anywhere else in the repo, would otherwise be flagged as dead code.accepted_secrets— a baseline of reviewed, accepted secret findings (e.g. a genuinely fake key in a test fixture that will always match a pattern). Every secrets scanner needs this: without it,--fail-on-new-secretshas no escape hatch for a known false positive - one review-and-accept, and it stops blocking CI, permanently, for that exact finding. Match on the finding's exactpath,pattern, andmatch_preview(copy these from a scan's output oraletheore query secrets <path>-match_previewis already redacted, safe to commit). Accepted findings are not hidden - they still appear inair.json,aletheore query secrets, the dashboard, and the PR comment, each flagged"accepted": true/labeled "accepted (in .aletheore.json baseline)" - only the fail-gates and inline PR annotations skip them.
All four keys are optional and independently defaulted/empty if the file is missing,
malformed, or only sets some of them. layer_markers/cluster_resolution (or null if
there's no config file at all) are recorded verbatim in air.json at
architecture.config_applied, so a report can cite exactly what convention was in effect for
that scan.
Commands
aletheore init [path]
Scaffolds a .aletheore.json at the repository root with all four Configuration keys above
present (empty/default), refusing to overwrite an existing one. Entirely optional - scan/
audit work identically with no config file at all.
aletheore init .
aletheore scan [path]
Runs only the deterministic scan phase. Writes .aletheore/air.json and a rolling history
snapshot under .aletheore/history/. No LLM call — safe to run repeatedly, in CI, or from a
script.
aletheore scan .
aletheore scan . --no-check-vulnerabilities # skip the OSV.dev dependency check
aletheore scan . --no-scan-git-history # skip walking git history for secrets
aletheore scan . --no-check-licenses # skip the dependency-license check
aletheore scan . --no-map-endpoints # skip static API endpoint mapping
The license check reads each pinned PyPI/npm dependency's registry metadata (PyPI's license
field falling back to its OSI classifiers; npm's license field) and categorizes it as
permissive, copyleft-weak (LGPL, MPL, EPL), copyleft-strong (GPL, AGPL), or unknown —
only non-permissive dependencies show up as findings, the same way OSV vulnerability checking
only reports actual vulnerabilities, not every clean dependency. It also detects the repo's own
declared license (pyproject.toml's license field, package.json's license field, or
pattern-matching a LICENSE file's text) so a report can flag a copyleft dependency alongside
what license the repo itself claims to be under - a factual categorization, not a legal
compatibility verdict, which is genuinely subjective and outside what a deterministic scanner
should claim.
Static API endpoint mapping records repository.api_endpoints for Flask, FastAPI-style
decorators, Django urlpatterns, Express route calls, Go (net/http/gorilla/mux and Gin),
Rust (Axum), Java (Spring Boot), Ruby (Rails), PHP (Laravel), and C# (both attribute-routed
Controllers and Minimal API). It is intentionally source-derived: literal route declarations
are recorded with method, path, framework, file, line, handler, whether the entry is an
unresolved include/mount-style indirection, and an optional note for known same-file prefixes
that are present but deliberately not composed into the recorded path.
scan prints its progress through each major phase as it runs — module graph build, git
history, secrets, vulnerability/license checks, endpoint mapping — since some of these (the
license check especially, one real network request per pinned dependency, no batching) can
take a while with no other feedback otherwise. On a real terminal the license-check counter
updates in place; piped to a log or CI, every dependency prints on its own line instead.
Alongside air.json, scan also writes .aletheore/air.toon — the same evidence,
TOON-encoded (~30-60% fewer tokens for the same data, biggest win on
the uniform arrays of same-shaped objects most of air.json actually is). air.json
stays the canonical file for the dashboard and any external tooling; air.toon exists
specifically for audit's coding-agent adapter to read instead.
Extracted module symbols include exact 1-indexed line bounds (name, start_line, end_line)
for top-level functions/classes across every supported parser language. Those bounds power the
semantic code index below.
aletheore index [path]
Builds a local LanceDB vector index over the repository's code chunks from an existing scan.
This is explicit and never runs as a side effect of scan. Embeddings use local Ollama's
OpenAI-compatible endpoint and jina-embeddings-v2-base-code (the same model Aletheore's
hosted tier uses, served locally via its official int8 GGUF quantization).
If Ollama is unreachable and OPENAI_API_KEY is configured (same lookup audit already uses -
environment variable first, then a saved credential), Aletheore asks for explicit confirmation
before falling back to OpenAI's text-embedding-3-small embeddings instead - this sends real
source code chunks to OpenAI's API, so it's never used silently and never used at all when running
non-interactively (e.g. from the MCP server).
aletheore scan .
ollama pull hf.co/ggml-org/jina-embeddings-v2-base-code-Q8_0-GGUF
aletheore index .
aletheore audit [path]
Runs a scan, then uses a selected reasoning provider to write a full grounded report to
.aletheore/audit-report.md, following the per-section instructions in manual/ (repository
intelligence, git intelligence, architecture, security, AI-usage detection, audience
perspectives, roadmap synthesis) and citing exact evidence fields throughout.
This is a genuinely different kind of operation from everything else in this list: it spawns a full second reasoning process (up to a 10-minute timeout for CLI-backed providers) to produce prose, rather than answering a fast, deterministic query. It's meant to be run by hand, when you actually want a written document — it is not wired into CI or the MCP server, and shouldn't be: a CI gate needs to be fast and pass/fail on concrete facts, and an agent already driving an MCP session can reason over the evidence itself without spawning a nested agent process.
There are twelve distinct --agent values:
| Provider family | CLI adapter | API/local adapter |
|---|---|---|
| Anthropic | claude |
anthropic |
| OpenAI | codex |
openai |
gemini-cli |
gemini |
|
| Mistral | mistral-vibe |
mistral |
| xAI | grok-build |
grok |
| Provider-agnostic/local | opencode |
ollama |
CLI adapters require that the vendor's own CLI is installed and already authenticated. They run as local subprocesses in the repository working directory, and Aletheore does not add its own consent prompt because the vendor CLI owns its own auth, permissions, and network behavior.
API-key adapters (anthropic, openai, gemini, mistral, grok) show a fresh per-run
consent prompt before the provider call. That prompt names the provider and explains that only
already-computed repository evidence is sent, not raw source code. Declining exits cleanly
after writing evidence. ollama is local and key-free, so it does not show that API consent
prompt.
API keys are read from the provider's environment variable first (ANTHROPIC_API_KEY,
OPENAI_API_KEY, MISTRAL_API_KEY, XAI_API_KEY, GEMINI_API_KEY). If no environment
variable or saved key exists, Aletheore prompts for a key and asks whether to use it once or
save it to ~/.config/aletheore/credentials.json with 0600 permissions. Keys are never
printed or included in adapter error messages.
A note on local model reliability for ollama: treat local-model support as
experimental. The default tag (llama3.1:8b) reliably fails this audit's structured,
multi-round tool-calling contract — it will typically fail fast with a clear
"finished without writing required section(s)" error rather than hang (Aletheore fails fast
on this, it doesn't retry forever), but it will fail. This is not specific to that one model:
live-tested against four local models on ordinary consumer hardware (llama3.1:8b,
qwen2.5-coder:14b, deepseek-coder-v2:latest, gpt-oss:20b), all four failed this specific
task — one lacked Ollama tool-calling support entirely (deepseek-coder-v2 returns
does not support tools), and three produced tool calls but never reliably completed all nine
sections. gpt-oss:20b initially hit the per-request timeout at the default 120s; re-tested
with a 600s timeout it did not time out, but took ~10 minutes to arrive at the identical
tool-calling failure the other models hit in under 3 - confirming this is a model-capability
limit, not a timeout tuning problem. Raising the timeout only makes a guaranteed failure
slower, so the default stays conservative. No local model has been confirmed to complete this
audit successfully yet. If you get one working, especially something in the 30B+ range on
capable hardware, that's genuinely useful data — there is currently no --model override
flag, so trying a different tag than the built-in llama3.1:8b requires editing
KNOWN_ADAPTERS in aletheore/cli.py if you're running from source. For local-model use
today, the practical recommendation is: use ollama for quick/free experimentation with the
understanding that it may fail, and rely on an API-key provider or a CLI-based agent (claude,
codex, gemini-cli, mistral-vibe, grok-build, opencode) for a run you actually need to
complete.
API/local adapters are deliberately bounded: they receive no raw repository files and no
filesystem tools. They can only call read_evidence_section against
.aletheore/air.toon, write named report sections, and finish the report.
Interactive runs always show a provider-selection menu, even if exactly one provider is
available. Non-interactive runs must pass --agent NAME explicitly so automation never
silently chooses a provider.
While the reasoning provider runs, an elapsed-time indicator prints so a multi-minute wait
doesn't look identical to a hang (updates in place on a real terminal; prints once at the
start and once at the end when piped to a log). Providers are instructed to use
.aletheore/air.toon (see scan above) rather than the JSON copy.
aletheore audit .
aletheore audit . --agent claude
aletheore audit . --agent codex
aletheore audit . --agent openai
aletheore audit . --agent ollama
--managed: BYOK vs. Aletheore's own key
Everything above is BYOK (bring your own key) - audit uses whichever provider/CLI you
already have configured, and the reasoning cost is yours. aletheore audit . --managed is the
alternative: it runs the identical report against Aletheore's own hosted reasoning service
instead, using a token tied to a paid GitHub App installation rather than any of your own
provider credentials.
aletheore login # GitHub device-flow auth, saves a managed-audit token
aletheore audit . --managed
aletheore status # confirm login state and installed version
aletheore logout # clear the saved token
--managed reads the token from ALETHEORE_API_TOKEN first, then the credential
aletheore login saved to ~/.config/aletheore/credentials.json (--token overrides both,
and only has any effect together with --managed). --agent is BYOK-only and has no effect
with --managed, since there's no local/API provider selection to make.
aletheore login
Authenticates with GitHub via device flow (prints a one-time code and a URL to enter it at), resolves which of your paid GitHub App installations to attach to (prompting if you have more than one), and saves a managed-audit API token locally. Replaces any previously saved token.
aletheore logout
Clears the locally saved managed-audit token. Safe to run even if not currently logged in.
aletheore status
Prints the installed version, whether a newer one is available on PyPI, and - if a managed-audit token is saved - who it's logged in as and which plan that installation is on.
aletheore query <kind> [target]
Answers one targeted question from an existing air.json, without re-scanning or an LLM
call.
aletheore query imports app/routes.py --path .
aletheore query imported-by app/routes.py --path .
aletheore query symbols app/routes.py --path .
aletheore query symbol-source app/routes.py handle_login --path .
aletheore query branch main --path .
aletheore query ownership --path .
aletheore query secrets app/routes.py --path . # findings within just that file
aletheore query vulnerabilities --path .
aletheore query licenses --path .
aletheore query endpoints --path .
aletheore query cluster app/routes.py --path .
aletheore query layer-violations --path .
aletheore query dead-code --path .
aletheore query hotspots --path . # files with the most git churn/co-change
aletheore query database --path . # detected ORMs, connection strings, migrations
aletheore query infrastructure --path . # detected Docker/CI/IaC config
aletheore query environment-variables --path .
aletheore query evidence-for-endpoint "GET /users/:id" --path .
aletheore query evidence-for-symbol handle_login --path .
aletheore query evidence-for-dependency requests --path .
aletheore query changes --path . # diff against the previous history snapshot
aletheore query search-codebase "how does auth work?" --path .
aletheore query answer "how does auth work?" --path . --agent ollama
search-codebase returns TOON-encoded semantic retrieval results from the local index.
answer retrieves code chunks from that same index, gates low-confidence matches, and then
uses the selected provider's simple completion path to answer with citations.
aletheore diff <old.json> <new.json>
Compares two air.json files directly — new/resolved secrets, API endpoints, layer
violations, dependency vulnerabilities, architecture deltas. Powers the GitHub Action below.
aletheore diff old/air.json new/air.json
aletheore diff old/air.json new/air.json --fail-on-new-secrets
aletheore diff old/air.json new/air.json --fail-on-new-vulnerabilities
aletheore diff old/air.json new/air.json --fail-on-new-layer-violations
All three --fail-on-new-* flags can be combined; the command exits 1 if any of them find
something new.
--format sarif renders new secrets, dependency vulnerabilities, and layer violations as a
SARIF 2.1.0 log instead of the default JSON, for tools that ingest SARIF directly (e.g. GitHub
code scanning). Incompatible with --full, since SARIF needs the curated diff shape.
aletheore diff old/air.json new/air.json --format sarif
aletheore verify <report.md> [--path <repo>]
Checks a report's file:line citations against a repository's evidence - works on any
markdown report, not just one aletheore audit produced, since it only reads the report's
text and <repo>/.aletheore/air.json. Exits 1 if any citation can't be verified, so it also
works as a CI gate on hand-written or third-party reports.
aletheore verify audit-report.md --path .
aletheore healthcheck [path] --base-url <url>
Runs a GET-only live check of mapped API endpoints against a running app instance. This reads
repository.api_endpoints from evidence, substitutes placeholder values for path parameters
such as <int:id>, {id}, and :id, skips non-GET endpoints without calling them, and writes
a rotated result file under .aletheore/healthchecks/.
This command depends on live runtime state, so it is deliberately not part of deterministic
scan evidence or aletheore diff.
aletheore healthcheck . --base-url http://127.0.0.1:5000
aletheore mcp [path]
Starts a stdio MCP server scoped to one repository, so a coding agent can query its structure
directly instead of shelling out via Bash or re-reading files on every lookup. Every tool
result is TOON-encoded rather than plain JSON — the calling agent's
own token budget is what actually pays for reading these results, and evidence's shape (almost
entirely uniform arrays of same-shaped objects) is exactly TOON's best case. Exposes 27 tools in
a read-only posture, 31 by default, and 32 with every effect permitted — see
Tool permissions for what gates the rest — plus one optional answer tool
when started with --agent:
- The 16 query kinds above as tools, each named
aletheore_<kind>with underscores in place of hyphens (aletheore_imports,aletheore_imported_by,aletheore_symbols,aletheore_branch,aletheore_ownership,aletheore_secrets,aletheore_vulnerabilities,aletheore_licenses,aletheore_endpoints,aletheore_cluster,aletheore_layer_violations,aletheore_dead_code,aletheore_hotspots,aletheore_database,aletheore_infrastructure,aletheore_environment_variables) — each tool's own description states whattargetexpects (a file path, a branch name, or that the tool takes no target at all). aletheore_changes(full=False)— what changed between the two most recent scans.aletheore_overview()— a repo-level summary: languages, frameworks, monorepo structure, dependency-graph size, module/cluster counts, and git age/commit cadence/branch count. The starting point for "what is this repo?" — call this before anything else on an unfamiliar repository.aletheore_list(kind)— the valid names/identifiers for one evidence collection (modules,clusters, orbranches), so another tool's exact-matchtargetargument can be filled in correctly.aletheore_neighborhood(target)— a module's imports, dependents, and cluster in one call, instead of three round-trips.aletheore_search(pattern, regex=False, path_glob=None)— literal or regex full-text search over tracked source files, capped at 200 matches.aletheore_ast_pattern(language, query)— structural search by shape, not words: a raw tree-sitter S-expression query against every file of one language, re-parsed from disk (not air.json) since a structural match needs the real parse tree.aletheore_symbol_source(module, symbol)— exact source text for one named function/class, with resolved line bounds.aletheore_verify_citations(report_text)— checks everyfile:linecitation in a report against this repo's real evidence and real file line counts, same check asaletheore verify.aletheore_find_evidence_for_endpoint(method, path),aletheore_find_evidence_for_symbol(symbol),aletheore_find_evidence_for_dependency(dependency)— resolve an endpoint/symbol/dependency to full source evidence: file, line, owner, commit, and (for endpoints) live-health risk.aletheore_scan(...)— triggers a fresh deterministic scan and returns a compact summary (not the full evidence dump). Does not run the agent-drivenauditreport — see the note underaletheore auditabove for why that's a deliberate boundary, not a gap.aletheore_healthcheck(base_url)— runs the same GET-only live health check as the CLI and persists the result under.aletheore/healthchecks/.aletheore_index()— builds the local semantic search index, same asaletheore index.aletheore_search_codebase(query, k=10)— semantic search over the local code index.aletheore_managed_audit(token=None)— not registered by default (see below). Runs a full managed audit report via Aletheore's hosted service, resolving the token the same wayaletheore logindoes: an explicittokenargument, thenALETHEORE_API_TOKEN, then the credential saved byaletheore login.- Optional:
aletheore_answer(question, k=5)— available only when the MCP server is started with--agent, answers from the semantic index using the selected provider.
aletheore mcp .
Tool permissions
Most of these tools only read .aletheore/air.json. A few do more, so each one carries standard
MCP tool annotations —
readOnlyHint, destructiveHint, idempotentHint, openWorldHint — that any MCP client can
read and display.
Annotations are hints, though; the MCP spec is explicit that clients should not make tool-use
decisions based on them. So the actual boundary is ALETHEORE_MCP_ALLOW, which controls which
effect classes are permitted. A tool whose effects aren't permitted is never registered — it
does not appear in the tool list and cannot be called.
| Effect | Meaning | Default |
|---|---|---|
| (read) | Reads evidence. Always permitted; not gateable. | on |
write |
Writes files under .aletheore/. |
on |
network |
Outbound requests — OSV.dev, package registries, health probes, embeddings. | on |
external |
Transmits this repository's evidence to a third-party service. | off |
external is the one class off by default. Scanning and indexing are what the tool is for, and
their effects stay on this machine; the genuinely surprising action is your repository's evidence
leaving it. That could previously happen with no consent step at all, because
aletheore_managed_audit silently resolves a token from the OS keychain — anyone who had once run
aletheore login had an agent that could upload without being asked.
# Default: everything except evidence upload.
aletheore mcp .
# Allow the managed-audit tool to upload evidence.
ALETHEORE_MCP_ALLOW=write,network,external aletheore mcp .
# Read-only server: evidence queries only, no scans, writes, or network.
ALETHEORE_MCP_ALLOW=read aletheore mcp .
An explicit value replaces the default rather than adding to it, so read really does mean
read-only. An unrecognized effect name is a startup error rather than a silent no-op. When tools
are withheld, the server prints one line to stderr naming them and the variable to set.
aletheore_answer reaches an LLM provider but is not gated, because it is registered only when
you pass aletheore mcp --agent — that flag is already the consent step.
aletheore_search_codebase (and the query side of aletheore_answer) always embeds through a
local Ollama instance and can fall back to OpenAI, which is why it might look like it belongs
under external. It doesn't: that fallback requires an interactive confirmation and is refused
outright when stdin isn't a TTY, and an MCP server is always spawned with piped stdio. From MCP
this tool reaches Ollama on localhost or fails — it cannot send code to OpenAI. If Ollama isn't
running, expect an "embedding provider unavailable" error rather than a silent upload.
aletheore_index embeds the same way by default, but it has a second path aletheore_search_codebase
doesn't: when external is permitted, it prefers Aletheore's own hosted embedding endpoint over
the local instance, uploading this repository's code chunks there instead of embedding on your
machine. With external withheld (the default posture), aletheore_index never reaches that
hosted endpoint either — it falls through to the local Ollama/OpenAI-fallback path above, for the
same reason aletheore_managed_audit doesn't upload evidence without being asked. Set
ALETHEORE_MCP_ALLOW=write,network,external to opt in.
aletheore mcp-install [path]
Writes the MCP server registration into your coding tool's own config, so it launches
aletheore mcp for you automatically instead of you running it by hand. By default writes for
every scriptable target: Claude Code (.mcp.json), Cursor (.cursor/mcp.json), VS Code
(.vscode/mcp.json), Kiro (.kiro/settings/mcp.json), Opencode (opencode.json), and OpenAI
Codex CLI (.codex/config.toml). Use --target to restrict to one or more
(e.g. --target cursor --target vscode). Safe to re-run - it merges into any existing config
file rather than overwriting it, so other MCP servers you've already configured are left alone.
aletheore mcp-install .
PyCharm / other JetBrains IDEs: not auto-configured. There's no single stable, publicly
documented file format to safely script against - the supported path is Settings | Tools | AI
Assistant | Model Context Protocol, using "Import a Claude MCP config" against the .mcp.json
this command already wrote.
vim, Neovim, Emacs, and other terminal editors: none of these have a native MCP client -
support depends entirely on whichever AI plugin you've installed (e.g. avante.nvim,
codecompanion.nvim), each with its own config. Point that plugin at aletheore mcp <path>.
OpenAI Codex CLI: writes .codex/config.toml, but Codex only reads project-scoped MCP
config for projects it already trusts - check Codex's own trust prompt if the tools don't
appear. Writing this file reformats it; hand-written comments in an existing config.toml are
not preserved.
aletheore dashboard [path]
A live local web UI (Starlette + SSE, opens in your browser): repo overview, git activity, trend charts for module/secrets/vulnerability counts across scan history, an interactive dependency graph, a separate community-aware "clusters" graph with zoom/pan, and the list of MCP tools available for the repo.
aletheore dashboard . --port 8420
GitHub Action
../action.yml ("Aletheore" on the Marketplace) is a composite Action that scans a PR's
base and head refs and reports the diff three ways:
- A PR comment — new/resolved secrets, new/resolved secrets found in git history, new/resolved dependency vulnerabilities, new/resolved layer-convention violations, and aggregate deltas (module count, dependency-graph edge count, commit count). Updates the same comment on subsequent pushes instead of spamming new ones.
- Inline annotations on new secrets specifically — shown directly on the changed line in the PR's "Files changed" tab. Scoped to current-tree secrets only, since that's the only finding type with both a real file path and a real line number: history-secret findings point at an old commit with no line in the current tree, vulnerabilities are package-level, and layer violations are file-level (a "from" file imports a "to" file) — none of those have a specific line to honestly point at, so they stay in the PR comment rather than getting a fabricated line number.
- The run's Step Summary — the same content as the PR comment, written on every run regardless of event type, so a plain push (no PR to comment on) still shows something.
A secret accepted via .aletheore.json's accepted_secrets (see Configuration above) is
labeled, not omitted, in the comment and Step Summary, and is excluded from inline annotations
and every --fail-on-new-* gate.
It only ever calls aletheore scan and aletheore diff, matching the reasoning above: CI needs
something fast and deterministic, not a full agent-driven audit.
- uses: Aletheore/Aletheore@v0.7.2 # pin to a tagged release, not @master
with:
fail-on-new-secrets: true # exit 1 if a new real (non-placeholder) secret appears
fail-on-new-vulnerabilities: true # exit 1 if a new dependency vulnerability appears
fail-on-new-layer-violations: true # exit 1 if a new layer-convention violation appears
Posting the PR comment needs permissions: pull-requests: write (and issues: write, since
PR comments use the Issues API) on the calling workflow's job — set post-pr-comment: false
to skip just that part and still get annotations, the step summary, and the diff-json
output.
Continuity
Every scan (and audit, which scans first) saves a timestamped snapshot to
.aletheore/history/ (last 20 kept). aletheore query changes / aletheore_changes diff the
two most recent snapshots, and the dashboard's trend charts read the full history.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file aletheore-0.9.17.tar.gz.
File metadata
- Download URL: aletheore-0.9.17.tar.gz
- Upload date:
- Size: 1.5 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c4a5dd96ff1c2c585e299267bb7880f6d8be82d414c115df454cbeeced742740
|
|
| MD5 |
030d449400ce6e8556aa3c47eb93b217
|
|
| BLAKE2b-256 |
e74521d8770fc671e76dd40379129efe7d052884c898c053b4ff8b5679c3fe16
|
Provenance
The following attestation bundles were made for aletheore-0.9.17.tar.gz:
Publisher:
publish-pypi.yml on Aletheore/Aletheore
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aletheore-0.9.17.tar.gz -
Subject digest:
c4a5dd96ff1c2c585e299267bb7880f6d8be82d414c115df454cbeeced742740 - Sigstore transparency entry: 2814600925
- Sigstore integration time:
-
Permalink:
Aletheore/Aletheore@38550695bafa446e63a623233112a3121d058244 -
Branch / Tag:
refs/tags/v0.9.17 - Owner: https://github.com/Aletheore
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@38550695bafa446e63a623233112a3121d058244 -
Trigger Event:
release
-
Statement type:
File details
Details for the file aletheore-0.9.17-py3-none-any.whl.
File metadata
- Download URL: aletheore-0.9.17-py3-none-any.whl
- Upload date:
- Size: 1.3 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6d0fba5c360f9b9ac2fe25bec9f4794f572b4c3abc4e6066ebade9a5b5d068db
|
|
| MD5 |
2b8c3167a57dd08962cf0211ee4cbd97
|
|
| BLAKE2b-256 |
b2e6550c32c440436ec7ee172a077e52e60950d85554a205ad7a526f8d049b51
|
Provenance
The following attestation bundles were made for aletheore-0.9.17-py3-none-any.whl:
Publisher:
publish-pypi.yml on Aletheore/Aletheore
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aletheore-0.9.17-py3-none-any.whl -
Subject digest:
6d0fba5c360f9b9ac2fe25bec9f4794f572b4c3abc4e6066ebade9a5b5d068db - Sigstore transparency entry: 2814600978
- Sigstore integration time:
-
Permalink:
Aletheore/Aletheore@38550695bafa446e63a623233112a3121d058244 -
Branch / Tag:
refs/tags/v0.9.17 - Owner: https://github.com/Aletheore
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@38550695bafa446e63a623233112a3121d058244 -
Trigger Event:
release
-
Statement type: