codehelion scan reads an optional codehelion.toml from the scan root.
codehelion config init writes a fully commented template with every key at its
built-in default, and codehelion config show prints the effective configuration
after files and defaults are resolved.
codehelion config init # write codehelion.toml
codehelion config show # what this run would use, and where it came fromcodehelion.toml is read only from the scan root itself and is never inherited
from a parent directory: a scan says which settings governed it and where they
came from, and a file nobody named that sits above the tree being read is not
that. --config <file> names one explicitly, wherever it lives.
Two settings are deliberately flags rather than configuration keys, because the
file is discovered inside the tree being scanned: --untrusted, whose whole
point is that its subject does not choose it, and --allow-execution, which
would otherwise be granted by the code it is a permission over.
# include = [] # path globs to include; empty means every supported source file
# exclude = [] # path globs to exclude from the include set
# min-clone-tokens = 20 # smallest clone reported, in tokens
# entropy-ratio-floor = 0.60 # below this normalized entropy, a group is degenerate repetition
# literal-normalization = "full" # "preserve", "category" or "full"
# database = ".codehelion/audit.db" # local-database location
# jobs = 4 # frontend read-and-lex workers; omit for automaticjobs sets frontend read-and-lex worker threads only; clone grouping and report
rendering remain serial. Omitted, the worker count follows host parallelism. The
value in the template is an example, not the default.
[languages]
# rust = true
# c = true
# cpp = true
# headers = "detect" # grammar for a bare ".h": "detect", "c", "cpp".h is the one extension C and C++ share, and the grammar it is read with decides
what the analysis can see inside it: a C++ header read as C recovers into shapes
that declare nothing, which both hides its real duplication and invents
duplication between class bodies.
detect counts the files whose extension is not in doubt and follows the
majority. Where a tree has none — a header-only library — it reads the headers
themselves for something only C++ spells, and one of them saying so settles the
run. The choice is part of the run's build variant, so results read one way are
never compared with results read the other.
[priority] # whole numbers, read as shares
# maintenance-risk = 2
# refactoring-ease = 1Only the composition is settable. What a duplication costs to keep and what it costs to remove are questions about the code, and a setting that changed the answers would make two projects' reports incomparable. Every finding carries all three measures whatever these are set to; setting both to zero orders the report on clone confidence alone.
The full set of policies is documented in Suppression. The keys:
[suppression]
# paths = []
# vendored-paths = [...] # hidden by default; [] reads them like any other tree
# symbols = [] # globs over the name of the enclosing unit
# clone-ids = [] # stable clone-group ids (hex; a prefix needs at least 8 characters)
# generated-markers = ["@generated", "do not edit", "automatically generated", "auto-generated", "autogenerated"]
# test-paths = [...]
# test-code = "rank-down"
# split-pairs = "rank-down"
# width-family = "hide"
[suppression.boilerplate]
# trivial-body = "rank-down"
# forwarding = "hide"
# macro-repetition = "rank-down"
# guarded-dispatch = "hide"
# configured-answer = "hide"
# composed-answer = "hide"
# built-answer = "hide"Setting a list key replaces its defaults rather than adding to them, which is why
generated-markers names the built-in banners alongside a project's own.
[semantic]
# disabled = ["sequence-pipeline-v1"]Restricted Semantic rules are all enabled by default. Listing a stable rule id here disables it for this project without broadening the detector for everyone else.
[helpers]
# rust = "/opt/codehelion/codehelion-backend-rust"
# clang = "/opt/codehelion/codehelion-backend-clang"Useful for hermetic CI or an installation outside PATH. These are read only from
a configuration named with --config: this file is discovered inside the tree
being scanned, and a repository must not get to choose which program a scan of it
starts. In a file found at the scan root the section is ignored, with a note
saying so; pass --helper rust=<path> or name the file with --config to pin a
helper.
Every ceiling that fires is accounted for in the report.
[limits]
# max-file-bytes = 2097152 # larger files are skipped and counted
# parse-timeout-ms = 10000 # deterministic parse-work budget, not wall-clock time
# helper-timeout-ms = 300000 # Semantic helper response deadline
# posting-cap = 64 # longest posting list that still enters pairing
# pair-budget = 1000000 # candidate pairs per pairing pass
# near-miss-delta = 0.05 # diagnostic band below the Type-3 gate
# near-miss-cap = 1000 # retained near misses per report
# sibling-candidate-budget = 50000
# sibling-per-group-cap = 8
# sibling-total-cap = 1000
# signature-sibling-candidate-budget = 50000
# signature-sibling-per-group-cap = 8
# signature-sibling-total-cap = 1000
# signature-sibling-max-units-per-signature = 8
# verification-budget = 1000000 # pairs sent to precise verification
# max-alignment-cells = 4000000 # dynamic-programming cells per alignment
# max-component = 1024 # largest set of related units compared as one pieceparse-timeout-ms is a deterministic work budget measured in compatibility
milliseconds (256 input bytes per millisecond), not a wall-clock deadline: the
same tree hits it in the same place on a fast machine and a slow one.
posting-cap and pair-budget override both modes at once. Left out, each mode
keeps the default its own measurements picked — the modes pair different things,
and their candidate counts differ by an order of magnitude on the same tree. Set
them to bound a scan that is taking longer than you will wait; the report then
states how many candidates the ceiling left unexamined.
signature-sibling-max-units-per-signature is a rarity threshold rather than a
resource ceiling. A signature shared by much of a tree proposes work without
proposing duplication, so it stops being evidence — see
Limitations.
[report]
# churn-top = 100How many of each run's highest-ranked groups are compared when saying what became of the work worth looking at. A total counts duplication rather than progress: closing a handful of groups out of thousands leaves it almost where it was, so the comparison is made over the top of each run.
A seam is a set of paths that implement the same semantics in more than one place.
The ledger is written by hand and is the source of truth for codehelion seam and
codehelion guard; nothing discovers or edits it. See
Seam tracking.
[[seam]]
id = "frontend-c-cpp"
members = ["crates/codehelion-frontend-c/**", "crates/codehelion-frontend-cpp/**"]
note = "same semantics implemented twice across the two frontends"members are globs over repository-relative paths, and a seam needs at least two
of them. note is free text that nothing reads. Repeat the [[seam]] block for
each seam.
The settings are a separate section, because TOML cannot spell one name as both an array of tables and a table.
[seam-tracking]
# breach-window = 20 # commits after an asymmetric change in which a fix counts as a breach
# history-limit = 2000 # newest commits read
# max-commit-size = 30 # commits touching more files than this are left out of coupling
# min-coupling = 0.60 # --suggest floor
# min-support = 3 # --suggest floor
# suggest-depth = 2 # leading path components --suggest counts co-change overmax-commit-size applies to the coupling figures alone, not to breach detection:
a sweeping rename hands support to every pair it touched, but a large commit that
broke a seam still broke it. history-limit is a ceiling on how much history is
read rather than a guarantee of how much there is; --until <rev> fixes the other
end of the range, which is what makes two generations' numbers comparable.
codehelion seam records what it measured into the local database described
below, which is what a later report reads to set two generations beside each
other. Which invocations record and which do not is on the
seam-tracking page.
Each scan creates its persistent local audit database at .codehelion/audit.db
by default, placed under the Git repository holding the scan root so that
scanning a subdirectory reuses the repository's database rather than starting a
new one; a scan root outside any repository holds its own. --db <path> overrides
the location.
Add .codehelion/ to the repository's .gitignore. This database is not an
expendable build cache: it is where the run history the reports and the churn
summary are derived from lives.
Completed scans are stored there, and text, JSON and SARIF reports are exports
from those snapshots. An unchanged tree reuses its compatible completed run;
--no-reuse records a fresh one. What reuse saves is the recording half of a run,
and -v prints the two halves separately, so how much it costs is measurable
rather than guessed at.
codehelion cache status # location, size, and what is in it
codehelion cache prune --force # apply the retention limits and compact
codehelion cache clear --force # permanently delete the local audit databaseprune keeps the newest 20 standalone artifact analyses and the newest 20
comparisons of each kind unless --keep-artifacts and --keep-comparisons say
otherwise. Both prune and clear delete retained history, so both require the
explicit confirmation flag.
A database written under a different schema is never migrated. At the default
path a run leaves that database exactly where it is, records into
audit-v<schema>.db beside it, and says which file it used; a database named with
--db is refused instead, since writing somewhere else would ignore the path that
was asked for. codehelion doctor lists every audit database in the directory,
which of them this build can open, and which one a run would take.
Nothing is lost that a fresh scan does not recreate. The audit database holds derived state, so a database an earlier build wrote costs the tree the runs it could have been compared against and nothing beyond that. Removing it is a decision left to the reader, since the file is as large as the one in use and only the reader knows whether an older build still opens it.