Coverage reporting watches edges. A module no configured group claims is a
different gap: every import it makes or receives sits outside group-based
enforcement, so a package added without a matching prefix can drift unchecked
while each existing check still passes. unclassified_modules reports the
module itself:
[tool.hecate]
unclassified_modules = "error"
The setting accepts three values:
| Mode | Behaviour |
|---|---|
ignore |
Report nothing. This is the default and preserves earlier output. |
warn |
Report each unclassified module as a warning; the exit code stays 0. |
error |
Report each unclassified module as an error; the check exits 1. |
A finding names the module and says why it was reported:
HEC002 error sample.newthing: no architecture group matched this module
Module findings carry the fixed rule identifier HEC002, so a consumer can
tell them apart from edge diagnostics, which use the configured
default_rule_id. For that reason HEC002 is reserved: a configuration that
sets default_rule_id = "HEC002" is rejected as invalid.
What counts as a discovered module
- Every
*.pyfile beneath a configured package root, including__init__.pyand files with no imports at all. A new package is reported on its first run, before it grows its first import. - Classification reuses the ordered, first-match rule described under
group tables. A dotted prefix matches at a
boundary, so a group for
sample.domaindoes not classify a lookalikesample.domainx. - A package
__init__.pyis claimed when any configured prefix lies beneath it. An initializer imports and re-exports what its submodules bind, so groups forsample.domainalready cover thesample.domainpackage without a recursive group forsample. That containment stops at the initializer:sample.newthingis a plain module and is still reported. - External imports are not modules for this purpose. They continue to follow
include_external_packagesand the external prefixes groups declare.
Reading the output
Text output shows module findings whenever the mode is warn or error,
because the configuration already asked for them. Under warn the pass notice
still appears, since warnings do not change the exit code:
hecate: architecture check passed
HEC002 warning sample.newthing: no architecture group matched this module
Under error there is no pass notice, and the exit code is 1.
JSON output carries an unclassified_modules array in both reporting modes,
sorted by module name. The array is present and empty when the tree is fully
classified, which a consumer reads as "evaluated and clean" rather than as "not
reported":
{
"ok": false,
"unclassified_modules": [
{
"message": "no architecture group matched this module",
"module": "sample.newthing",
"rule_id": "HEC002",
"severity": "error",
"source_path": "/repo/sample/newthing/__init__.py"
}
]
}
Under ignore the key is absent entirely, so the default payload is unchanged.
The exit code follows from the findings: warn leaves it at 0, error makes
it 1.
A module finding does not change how the edges around that module are classified. An import edge with an unclassified endpoint stays outside group-based enforcement in all three modes; the finding exists to make that gap visible. See coverage reporting for how such edges are reported by strict mode.