oct.linter.oct_lint module#
Purpose#
Provide a strict, deterministic linter for Option C projects that validates mandatory header blocks, module docstrings, diagnostics profiles, and basic testing presence, and can optionally auto-fix header blocks.
Responsibilities#
Walk the project tree from a given Option C project root or directory or single file given as an argument.
Skip excluded directories and the linter’s own files.
Validate the 4-line mandatory header block for every Python module.
Validate the presence and structure of the module docstring.
Validate the presence of a Diagnostics profile block (Domain + L2/L3/L4).
Validate basic
_dbgusage in non-trivial modules;_dbgis the structured debug logger provided by theoc_diagnosticspackage (from oc_diagnostics import _dbg).Validate the presence of regression tests for each module.
Optionally auto-fix header blocks while archiving originals.
Produce a detailed log file and a colorized terminal summary.
Optionally emit machine-readable JSON to stdout (
--json).
Diagnostics#
Domain: OCT-LINTER Levels:
L2 — lifecycle L3 — semantic details L4 — deep tracing
Contracts#
Must not modify files unless
--fix-headersis explicitly requested.Must treat the provided project root as authoritative for all relative paths.
Must write a deterministic log file into the project’s
logs/directory when writable; warns and continues if the log directory is unwritable.Must not import project modules; analysis is purely static.
Must be callable both from the OCT CLI and as a standalone module.
Dependencies#
oct.core.exclusions (directory and wildcard exclusion lists)
oct.core.parsing (safe AST parsing helper)
oct.core.paths (project-relative path helper)
oct.core.waivers (inline waiver parsing)
- class oct.linter.oct_lint.LinterContext(project_root: Path, project_name: str, diagnostics_dir: Path, tests_dir: Path, docs_dir: Path, log_handle: Any = None, log_path: Path | None = None, log_lock: lock = <factory>, test_index: dict[str, set[str]] | None=None, secret_match_mode: str = 'substring', code_entropy_threshold: float = 4.5, docstring_entropy_threshold: float = 5.0, comment_entropy_threshold: float = 5.0, entropy_exclusions: list = <factory>, excluded_field_patterns: list = <factory>, profile_name: str = 'strict', lint_cache: Any = None, no_cache_read: bool = False)[source]#
Bases:
objectImmutable per-run context for the linter. Replaces module-level globals so that concurrent linter invocations do not share mutable state.
- code_entropy_threshold: float = 4.5#
- comment_entropy_threshold: float = 5.0#
- diagnostics_dir: Path#
- docs_dir: Path#
- docstring_entropy_threshold: float = 5.0#
- entropy_exclusions: list#
- excluded_field_patterns: list#
- lint_cache: Any = None#
- log_handle: Any = None#
- log_lock: lock#
- log_path: Path | None = None#
- no_cache_read: bool = False#
- profile_name: str = 'strict'#
- project_name: str#
- project_root: Path#
- secret_match_mode: str = 'substring'#
- test_index: dict[str, set[str]] | None = None#
- tests_dir: Path#
- oct.linter.oct_lint.build_test_index(ctx: LinterContext) dict[str, set[str]][source]#
OI-510 / FS-516: one-shot scan of
ctx.tests_dirproducing two lookup sets socheck_tests_exist()becomes O(1) per module instead of re-walking the tests tree for every file linted.The index is cached on
LinterContext.test_index— subsequent calls return the cached value directly.Returns a dict with:
name_stems— stems<s>for whichtest_<s>.pyexists.imported_stems— module stems referenced in any test file by a whole-wordimport/fromstatement.
- oct.linter.oct_lint.check_dbg_assert_safety(text: str, tree: Module | None = None, filename: str = '') tuple[bool, str][source]#
Flag
_dbg_assertcalls made inside security-sensitive contexts._dbg_assertis a diagnostic aid — in production mode it is a no-op. Per AP-16 it must never be used for safety invariants (authentication, authorization, input validation, crypto, etc.). This check walks the AST looking for calls named_dbg_assert(bare or attribute access) and verifies that none of the enclosing functions, classes, or the module’s filename stem contain a security-sensitive keyword.
- oct.linter.oct_lint.check_dbg_import(text: str, tree: Module | None = None)[source]#
Verify that
_dbgis imported fromoc_diagnosticsat module level.The canonical import is
from oc_diagnostics import _dbg. Files shorter than 20 lines are exempt (same threshold ascheck_dbg_usage). Aliased imports (import _dbg as ...) are not accepted — the Option C spec requires the canonical form.
- oct.linter.oct_lint.check_dbg_usage(text: str, tree: Module | None = None)[source]#
Validate that non-trivial modules call
_dbg()for diagnostics._dbgis the structured debug logger fromoc_diagnostics(from oc_diagnostics import _dbg). Files shorter than 20 lines are considered trivial scaffolding and are exempt from this check.Uses AST parsing to detect actual
_dbg()call nodes, avoiding false positives from comments or string literals.
- oct.linter.oct_lint.check_dependencies_section(text: str)[source]#
Validate the Dependencies section of a module docstring at strict profile.
Spec reference: Appendix C — Dependencies docstring format. Each entry must follow
- module_name (purpose description)sooct depscan parse the dependency graph.Returns
(ok, msg). Empty Dependencies sections are accepted (a module may have no project-internal dependencies); malformed entries are rejected.
- oct.linter.oct_lint.check_diagnostics_profile(text: str)[source]#
Validate that a Diagnostics profile is present in the module docstring.
Scopes the search to the module docstring (text before the first
def/class) to avoid false positives from in-code string literals that happen to contain ‘L2’, ‘L3’, ‘L4’, ‘Diagnostics’, or ‘Domain:’. Falls back to the full file if no module docstring is found.
- oct.linter.oct_lint.check_docstring(text: str)[source]#
Validate that a module docstring exists and contains the required Option C sections.
- oct.linter.oct_lint.check_func_pattern(text: str, tree: Module | None = None)[source]#
Validate that every function body containing
_dbg()first definesfunc = "function_name".Uses
ast.parse()to build a syntax tree, then walks each function’s body (stopping at nested function boundaries) to check for the pattern. Exempt trivial files (< 20 lines) — same threshold as check_dbg_usage.Delegates to
find_func_pattern_violations()for the actual detection; this wrapper converts the result into a(bool, message)pair for the linter pipeline.
- oct.linter.oct_lint.check_syntax_warnings(text: str, warnings_list: list[str] | None = None)[source]#
Check for Python syntax warnings (invalid escape sequences etc.).
Python 3.12+ emits
SyntaxWarningfor invalid escape sequences in string literals. These will becomeSyntaxErrorin a future Python version, so they should be surfaced as lint findings.
- oct.linter.oct_lint.check_tests_exist(module_path: Path, ctx: LinterContext)[source]#
Validate that at least one test file references the module either by naming convention (
test_<module>.py) or by an explicit import statement (import <module>orfrom <module>).Two-stage check, now O(1) per call: consults the index built once per
run_linter()invocation bybuild_test_index().
- oct.linter.oct_lint.check_type_hints(text: str, tree: Module | None = None, compact_mode: bool = False)[source]#
Validate that public functions have type annotations on all parameters and a return annotation.
Private functions (leading underscore) and dunder methods are always exempt — they are internal implementation details (OI-417). The
compact_modeparameter is retained for API compatibility but no longer changes behaviour.Trivial files (< 20 lines) are exempt.
- oct.linter.oct_lint.expected_path_header(path: Path, ctx: LinterContext) str[source]#
Compute the expected file identity header for a given module path.
- oct.linter.oct_lint.find_func_pattern_violations(text: str, tree: Module | None = None) list[tuple[str, int]][source]#
Return
[(func_name, lineno), ...]for every function that calls_dbg()without a priorfunc = "..."assignment.Returns an empty list for trivial files (< 20 lines) or files with syntax errors. This is the single source of truth for func-pattern detection — used by
check_func_pattern()(linter pass/fail) and byoct.formatter.oct_format.fix_func_pattern()(auto-fix).
- oct.linter.oct_lint.find_python_files(root: Path, exclude_dirs: set | None = None, wildcard_exclude_dirs: list | None = None)[source]#
Yield all Python files under root, excluding the linter itself and excluded directories.
- Parameters:
root – The project root to scan.
exclude_dirs – Set of directory names to exclude. Defaults to the module-level
EXCLUDE_DIRSconstant.wildcard_exclude_dirs – List of substrings; directories whose name contains any of these are excluded. Defaults to
WILDCARD_EXCLUDE_DIRS.
- oct.linter.oct_lint.fix_header_block(path: Path, text: str, ctx: LinterContext) bool[source]#
Rewrite the top of the file to enforce the Option C 4-line header. The original file is archived in a .linter_archive directory.
- oct.linter.oct_lint.is_excluded_dir(path: Path, exclude_dirs: set, wildcard_exclude_dirs: list) bool[source]#
Return True if the directory name should be excluded from scanning.
- oct.linter.oct_lint.log(message: str, ctx: LinterContext | None = None) None[source]#
Write a single line to the linter log file.
OI-513:
ctxis required in practice — every internal callsite threads it through. TheNonebranch is kept as a silent no-op so external callers that importlog()standalone do not crash.
- oct.linter.oct_lint.preview_header_fix(path: Path, ctx: LinterContext) str[source]#
Compute what fix_header_block would produce, without writing. Returns the corrected 4-line header block as a string.
- oct.linter.oct_lint.print_header(title: str, ctx: LinterContext | None = None) None[source]#
Write a section header to the log.
- oct.linter.oct_lint.read_file(path: Path, ctx: LinterContext | None = None) str[source]#
Read a file as UTF-8, returning an empty string on failure.
- oct.linter.oct_lint.report(result: bool, message: str, fix_mode: bool = False, ctx: LinterContext | None = None) bool[source]#
Log a PASS/FAIL (or FIXED) line and return the result.
- oct.linter.oct_lint.run_linter(project_root: Path, argv: list[str] | None = None) int[source]#
Run the Option C linter on the given project root.
- Parameters:
project_root – The detected Option C project root directory.
argv – Optional list of command-line arguments (e.g. [”–fix-headers”]). If None,
argparsewill usesys.argv.
- Returns:
Exit code: 0 if all checks passed, 1 if any failures detected.
- Return type:
int
- oct.linter.oct_lint.validate_header_block(lines: list[str], path: Path, ctx: LinterContext)[source]#
Validate the mandatory 4-line header block.
Returns (ok, errors) where: - ok = True if header block is correct - errors = list of error messages