All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Ruby language support across the full applicable rule set, including
the CK class-metrics suite with open-class aggregation. Same shape
of hotfix as v1.0.1 (C) and v1.0.2 (C++): tree-sitter-ruby was a
wheel dependency and .rb was registered in the AST extension map,
but no kernel _LANG_CONFIG registered "ruby". Final of the three
planned single-language hotfixes.
- Ruby language support across structural, information, and
lexical rule families.
structural.complexity.{cyclomatic,cognitive, npath},information.{volume,difficulty,magic_literals, section_comments},structural.{god_module,duplication,redundancy, hotspots,orphans,deps,packages,local_imports},structural.types.sentinels,lexical.{stutter,verbosity,tersity}, andstructural.class.{complexity,coupling,inheritance.depth, inheritance.children}all run on Ruby files. Seedocs/RUBY.md. - New
_ruby_*helpers in_structural/{ccx,npath,halstead}.py. Method name extraction handles regular methods (def foo), singleton/class methods (def self.foo), operator overloads (def ==,def [],def []=,def <=>), and treats lambdas (->{ }), do-blocks (do |x| end), and curly blocks ({ |x| }) as anonymous (<lambda>). - New
_extract_ruby_superclassesand_collect_ruby_classesin the CK kernel. Ruby's class name is positional (aconstantdirect child) rather than a field, so it has its own collector path. - New
_aggregate_ruby_open_classespost-WMC pass. Ruby's open-class semantics let the sameclass Foobe re-declared across files; each declaration parses as a separateclassnode. The aggregator merges them by name within the Ruby subset, summing WMC and method counts, taking max CBO/DIT/NOC, and unioning superclasses. Seedocs/RUBY.md"Open-class aggregation". - New
IMPORT_NODE_PREDICATEShook on_structural/local_imports.py. Ruby's imports are method calls (require 'foo'), not statement nodes; the predicate distinguishes require-style calls from ordinary calls. Other languages don't register and pass through unchanged. structural.packagesfor Ruby treatsmoduleas the abstract analog (modules cannot be instantiated, only mixed in viainclude/extend). Classes are concrete. Ruby has nofinal. Defaultseverity = "warning".structural.depsfor Ruby resolvesrequire_relative './foo'to peer files;require 'gem'is treated as external.
structural.types.escape_hatches(any-type density). Ruby is dynamically typed; every parameter is implicitlyObject. There is no type system to escape. Silent no-op via missing kernel registration; documented in_structural/any_type_density.py.structural.types.hidden_mutators(out-parameters). Ruby's parameter passing is always by reference; every object is mutable. Without a type system the rule has no signal-to-noise floor. Silent no-op via missing kernel registration; documented in_structural/out_parameters.py.
Both match the C-CK and Julia-CK silent-no-op posture established in v1.0.1.
Ruby mixins (include MyMod / extend MyMod) count toward CBO
(captured as type references inside the class body) but NOT toward
DIT (Ruby community convention treats them as composition). NOC is
unaffected. See docs/RUBY.md "Mixin coupling" for the rationale
and the per-rule param hook for projects that want different
semantics.
Users upgrading from v1.0.2 with .rb files in their codebase will
see new violations across the full applicable rule set. By design —
those files were silently passing v1.0.2.
Same pre-existing audit gaps carried forward from v1.0.1 / v1.0.2:
Java's npath switch_node mismatch; lexical.stutter's missing
Java/C#/Julia entries; type-discipline message wording for non-Python
languages.
Ruby-specific deferrals:
- Mixin-aware DIT (could become a per-rule
include_mixins=trueparam). define_methodand metaprogramming visibility.$LOAD_PATH/ Bundler /RUBYLIBresolution forstructural.deps.- Class methods inside
structural.redundancy(free-method only).
C++ language support across the full applicable rule set, including
the CK class-metrics suite (CBO, DIT, NOC, WMC). Same shape of
hotfix as v1.0.1 was for C: tree-sitter-cpp was a wheel dependency
and .cpp / .cc / .cxx were registered in the AST extension map,
but no kernel _LANG_CONFIG registered "cpp" — every metric kernel
silently skipped C++ files.
- C++ language support across structural, information, and
lexical rule families.
structural.complexity.{cyclomatic,cognitive, npath},information.{volume,difficulty,magic_literals, section_comments},structural.{god_module,duplication,redundancy, hotspots,orphans,deps,packages,local_imports},structural.types. {escape_hatches,hidden_mutators,sentinels},lexical.{stutter, verbosity,tersity}, andstructural.class.{complexity,coupling, inheritance.depth,inheritance.children}all run on C++ files. Seedocs/CPP.mdfor the full status sheet. - New
_cpp_*helpers in_structural/{ccx,npath,halstead}.pyparalleling the v1.0.1_c_*helpers but extended to handle every C++ name shape: in-class methods (field_identifier), out-of-line methods (qualified_identifier), operator overloads (operator_name), destructors (destructor_name), pointer/ reference-return wrappers, and lambda expressions (lambda_expression→ named<lambda>). - New
_extract_cpp_superclassescallable on_CkLangConfig["cpp"]that walksbase_class_clausechildren fortype_identifier/qualified_identifierto support single + multiple inheritance. - New
_collect_cpp_outofline_methodssecond-pass walker in the CK kernel. C++ codebases conventionally declare classes in headers and define methods in.cppfiles (void Foo::bar() {}); these out-of-line definitions parse as top-levelfunction_definitions outside the class body. The walker catalogues them, and the WMC computation attributes each one's CCX back to the matching class by name. - New
definition_unwrap_types: frozenset[str]field on_LangConfig/_NpathLangConfig/_HalsteadLangConfig. Lets the kernel descend through wrapper node types (C++template_declarationis the canonical example) to find the wrappedfunction_definition/class_specifier. Default empty preserves existing behaviour for every other language. _split_cpp_classes_by_abstractin_structural/robert.py. A C++ class is abstract iff it has at least one pure-virtual method (virtual T f() = 0;) AND is not declaredfinal. Drives thestructural.packagesabstractness term. Defaultseverity = "warning"..hppand.hxxregistered inEXT_LANGUAGE_MAPfor C++..hcontinues to default to C (conservative; codebases that use.hfor C++ headers can override with explicit globs).
structural.class.*rules now cleanly silent-no-op when the user-requested language set excludes every CK-supported language. The v1.0.1 fix to "no supported languages" returned an empty result without an error; v1.0.2 keeps that behaviour.
Users upgrading from v1.0.1 with C++ files in their codebase will see new violations across the full rule set. By design — those files were silently passing v1.0.1.
- Java's npath
switch_node = "switch_statement"mismatch with the grammar'sswitch_expressionemission (still an unfixed pre-existing bug discovered during v1.0.1). lexical.stutter's missing Java/C#/Julia entries in_SCOPE_NODES(still an unfixed pre-existing audit gap).- C++20 / C++23 features that older
tree-sitter-cppversions don't emit (concepts, modules, coroutines). - C++ class methods inside
structural.redundancy(free-function only). - Bare-name namespace collisions in WMC out-of-line attribution
(documented in
docs/CPP.md). - Cross-translation-unit method definitions whose declaring header is outside the scanned set.
C language support across the applicable rule set. Discovered during a
coverage audit that tree-sitter-c was a wheel dependency and .c /
.h were registered in the AST extension map but no kernel
_LANG_CONFIG registered "c" — same shape of silent-skip failure
that v0.7.0 had with Julia short-form functions. Hotfix.
- C language support (
.c,.h) across structural, information, and lexical rule families.structural.complexity.{cyclomatic, cognitive,npath},information.{volume,difficulty,magic_literals, section_comments},structural.{god_module,duplication,redundancy, hotspots,orphans,deps,packages,local_imports},structural.types. {escape_hatches,hidden_mutators,sentinels}, andlexical.{stutter, verbosity,tersity}all run on C files. The CK class metric family (structural.class.*) silently no-ops on.c/.hfiles (C has no class concept; same posture as Julia for CK). New `tree-sitter-c= 0.21.0
was already a dependency; v1.0.1 wires it into the kernels. Seedocs/C.mdfor the full status sheet, including the documented limitations (no-I` path resolution, A=0 always for packages, three-pattern out-parameter mutation detection). - New
_c_find_function_identifierand_c_name_extractorhelpers on the_LangConfigCallable seam in_structural/ccx.py,_structural/npath.py, and_structural/halstead.py. Same shape as the Julia precedent — tree-sitter-c does not expose function names through anamefield; the name lives infunction_declarator.declarator (identifier), optionally wrapped inpointer_declaratorfor pointer return types. Smaller kernels (magic_literals,section_comments, lexical) inline the declarator-chain walk in their generic_fn_namehelpers. - New
switch_body_types: frozenset[str]field on the npath_NpathLangConfigdataclass. Lets the kernel descend through body-wrapper node types between a switch and its cases — required for C (compound_statement), Java (switch_block/switch_block_statement_group), and C# (switch_body). Default empty-frozenset preserves existing behaviour for Python/JS/TS/Go/ Rust which expose cases as direct switch children.
structural.complexity.npathunder-counted switches in C, C#, and Java. Pre-1.0.1 the kernel iterated direct switch children looking for case nodes, but C wraps cases incompound_statement, C# inswitch_body, and Java inswitch_block/switch_block_statement_group. Cases were therefore invisible and every switch contributednpath = 1. v1.0.1 walks through the language'sswitch_body_typesto find cases. Java's case shape remains constrained by a separate pre-existing issue (switch_node = "switch_statement"does not match tree-sitter- java's actualswitch_expressionemission for classic switches); that's tracked for a follow-up.structural.class.*rules now silently no-op on languages that don't register CK metrics, instead of returning a "No supported languages" error. Previously, running CK on a Julia or C codebase produced a per-rule error that polluted output and could fail CI. v1.0.1 returns a clean empty result when the user explicitly requested languages that this rule does not apply to.structural.depsmodule-resolution index now includes extension-preserving keys. C#include "foo.h"resolves tofoo.h(notfoo, which strips the suffix). The same change also picks up other languages that include extensions in their import strings; existing Python/JS/TS/Go/Java/C# tests are unaffected.
structural.types.escape_hatches(any-type density) regex pattern for C: escape hatch isvoid *; annotation pattern is calibrated tentatively. Defaultseverity = "warning". Seedocs/C.md.- Section-comment divider regex widened to also match C block-style
dividers (
/* === ... ===and/* --- ... ---) alongside the existing#-style and//-style markers.
Users upgrading from v1.0.0 with C files in their codebase may see
new violations across complexity, npath, halstead, packages, deps,
god_module, duplication, redundancy, magic_literals,
section_comments, types.{escape_hatches,hidden_mutators,sentinels},
local_imports, and lexical.* rules. This is by design — C files were
silently passing every rule in v1.0.0. To suppress the new output
gradually, set severity = "warning" per rule or disable
specific rules while you triage.
C, C# and Java codebases that have switch statements will see NPath counts increase to reflect the actual case count. Tighten thresholds if you previously calibrated against the under-counted numbers.
- Java's
structural.complexity.npathswitch_node = "switch_statement"does not match tree-sitter-java'sswitch_expressionnode type for classic switch syntax. The v1.0.1switch_body_typesfix only helps on the wrapper-descent side; Java still needs a switch-node type fix. lexical.stutteris missing per-language entries for Java, C#, and Julia in_SCOPE_NODES— same shape of silent-skip failure C had pre-1.0.1.structural.types.sentinelsadvisory message text references Python remedies ("Literal[...] or Enum") even when reporting C violations. Wording will be language-aware in a follow-up.
1.0.0 - 2026-05-04
First stable release. The 0.9.0 → 1.0.0 jump is mostly additive: a new
lexical.* suite, type-discipline and shape rules under structural.*,
inline-density rules under information.*, and prefix-table semantics
for bulk-disabling rules in config. Rule names and the public CLI are
unchanged from 0.9.0.
lexical.*suite (3 rules). A new measurement substrate covering identifier vocabulary discipline.lexical.stutter— flags identifiers that repeat tokens from the enclosing scope (function, class, module). Catches names likeparse_parser_inputinside classParser.lexical.verbosity— flags functions where the mean identifier word count exceeds a threshold (default 3.0). Catchescompute_total_aggregated_user_score_value-style drift.lexical.tersity— flags functions where more than 50% of identifiers are ≤ 2 characters. Catchesp/q/x-heavy code.
- New structural rules (6).
structural.duplication— Type-2 clone detection: structurally identical function bodies across the codebase.structural.god_module— flags files with > 20 top-level callable definitions.structural.local_imports— flagsimportstatements inside function bodies. Three Python idiomatic patterns (optional heavy deps, CLI deferred imports, test monkeypatch imports) ship as commented-out waiver templates inslop init.structural.redundancy— flags sibling top-level functions that share ≥ 3 non-trivial callees (refactoring signal for shared helper extraction).structural.types.sentinels— flagsstr-annotated parameters with sentinel-shaped names (status,mode,kind, ...) where an enum would be more honest.structural.types.hidden_mutators— flags functions that mutate collection-typed parameters in place.structural.types.escape_hatches— flags files where the fraction of type annotations usingAny,interface{},unknown, etc. exceeds 30%.
- New information rules (2).
information.magic_literals— flags functions with > 3 distinct non-trivial numeric literals.information.section_comments— flags function bodies containing section-divider comments (a function-overload signal).
- Prefix-table config semantics. TOML tables
[rules.<prefix>]propagateenabledandseverityto every descendant rule. More specific tables override broader ones. Disabling an entire suite is now one line:See[rules.lexical] enabled = false
docs/CONFIG.md"Disabling rules, groups, and suites". CITATIONS.md— credits the AI assistance (Augment Code's auggie Prism dynamic routing across Claude Opus 4.7, Claude Sonnet 4.5, and Gemini 2.5 Pro) and points to NOTICE for academic citations.
- README trimmed substantially. The full rule index moved to
docs/rules/README.md(where it was already authoritative). Long-form caveats moved to the rule pages anddocs/JULIA.md. docs/dogfood-deps-kernel.mdremoved (case-study churn, no longer current).- Rule wrappers
run_any_type_densityandrun_clone_densitynow use named module-level constants instead of hard-coded magic numbers. _lexical/stutter.py_scan_filerefactored for cognitive complexity (CogC 17 → 9).
_structural/sibling_calls.py— corrected a logical error (or→and) in the shared-callee predicate that was producing inflated redundancy counts.
- All 0.9.0 rule names and TOML tables continue to work unchanged.
- The legacy-name compatibility shim from 0.9.0 remains in place and is still scheduled for removal in 1.1.0.
0.9.0 - 2026-05-04
- All rule names now carry a suite prefix matching
docs/rules/README.md. This is a breaking change for anything that consumes the JSONrulefield (CI parsers, dashboards, downstream tooling). Legacy names and TOML tables still work via a compatibility shim and trigger a single consolidated deprecation warning to stderr at config-load time. The shim is scheduled for removal in 1.1.0. - Canonical rule names:
complexity.cyclomatic→structural.complexity.cyclomaticcomplexity.cognitive→structural.complexity.cognitivecomplexity.weighted→structural.class.complexitynpath→structural.complexity.npathhotspots→structural.hotspotspackages→structural.packagesdeps→structural.depsorphans→structural.orphansclass.coupling→structural.class.couplingclass.inheritance.depth→structural.class.inheritance.depthclass.inheritance.children→structural.class.inheritance.childrenhalstead.volume→information.volumehalstead.difficulty→information.difficulty
- TOML config tables move under suite-prefixed paths
(
[rules.structural.complexity],[rules.information.volume], ...). The Halsteadvolume_threshold/difficulty_thresholdkeys are renamed tothresholdunder their respective new tables. The CKweighted_thresholdkey moves from[rules.complexity]to[rules.structural.class.complexity]asthreshold. slop check <name>accepts both legacy and canonical names.slop initnow emits canonical TOML.- All bundled documentation and the agent skill ship with canonical names.
slop._compatis the single point of translation. Legacy → canonical maps for rule names, category names, and TOML tables all live there.- Waivers using legacy rule names are translated at load time and listed in the deprecation block.
0.7.1 - 2026-04-26
Released to PyPI on 2026-04-26 as agent-slop-lint==0.7.1. Tag: v0.7.1.
- Per-language name extraction and function-node matching now live as Callable fields on each kernel's
_LangConfig, matching the establishedextract_superclasses: SuperclassExtractorpattern inclass_metrics. Each of_structural/ccx.py,_structural/npath.py, and_structural/halstead.pygainsname_extractor: NameExtractorandis_function_node: FunctionNodeMatcherfields with sensible defaults that reproduce v0.7.0 behaviour for every existing language. Removes the language-branchingif node.type == "function_definition":block that v0.7.0 introduced into shared kernel functions. No behaviour change for Python / JavaScript / TypeScript / Go / Rust / Java / C#.
- Short-form Julia function definitions (
f(x) = x + 1) are now detected and analysed bycomplexity.cyclomatic,complexity.cognitive,complexity.npath,halstead.volume, andhalstead.difficulty. v0.7.0 silently skipped these because tree-sitter parses them asassignmentnodes with acall_expressionLHS rather thanfunction_definition. Variable assignments (x = 1,y = some_func()) are correctly excluded. - Operator-method definitions (
+(a, b) = ...,-(a::Int, b::Int) = ...) are now detected and named by their operator symbol (e.g.+,-). - Do-blocks (
map(xs) do x ... end) are now treated as anonymous functions named<lambda>, with their decisions counted independently from the enclosing call. - Method extensions on dotted names (
function Base.show(...)→ nameshow) — previously returned<anonymous>in violation output. - Where-clause function signatures (
function f(x) where T ... end→ namef) — previously returned<anonymous>because the call_expression was nested inside awhere_expressionthe v0.7.0 walker did not descend into.
Users upgrading from v0.7.0 to v0.7.1 on Julia codebases that contain short-form functions, do-blocks, operator methods, or dotted method extensions may see new complexity / npath / halstead violations on code that previously passed silently. This is by design — those functions were not being analysed at all in v0.7.0.
0.7.0 - 2026-04-26
Released to PyPI on 2026-04-26 as agent-slop-lint==0.7.0. Tag: v0.7.0.
- Julia language support (
.jl) across the structural rule family.complexity.cyclomatic,complexity.cognitive,halstead.volume,halstead.difficulty,dependencies.cycles,architecture.distance,hotspots,dead_code, and AST-basedusageslookups all run on Julia files. Tree-sitter queries coverusing Foo,using Foo, Bar,using Foo.Bar,using Foo: a, b,import Foo, andimport Base: show. Abstract-type detection usesabstract type X end. Newtree-sitter-julia >= 0.21.0runtime dependency. Seedocs/JULIA.mdfor the full status sheet, including known deferrals (short-form functions, do-blocks, CK class metrics) and calibration guidance. - New
_NpathLangConfig.body_skip_typesfield and_npath_of_flat_bodyhelper. Lets the npath kernel walk languages whose tree-sitter grammars have no block-wrapper node (Julia today; potentially Lua, some Ruby shapes later). Default value is the empty set so existing languages are unaffected.
- Repository layout:
_aux/umbrella replaced with substrate-named subpackages. Discovery primitives now live underslop._fs/(fd),slop._text/(ripgrep),slop._ast/(tree-sitter, plustreesitterhelpers). Cross-tool primitives (usages,hotspots,prune,git) live underslop._compose/. Structural metric kernels (ccx,ck,npath,halstead,deps,robert) live underslop._structural/. Cross-cutting plumbing (subprocess,doctor) lives underslop._util/. Apache-2.0 attribution for the vendored kernel tree moves from_aux/LICENSEtoKERNELS_LICENSEat the slop package root. Internal-only change; no public API affected. NOTICE, READMEs, CLAUDE.md,.slop.tomlexclude list, and the ruffextend-excludelist all updated to point at the new paths. - Language tables in README, src/README, SETUP.md, and the CONFIG.md
packagessection now include Julia and document the deferrals.
- Julia short-form functions (
f(x) = x + 1) are not detected as functions by the structural kernels — they parse asassignmentnodes with acall_expressionLHS, notfunction_definition. Same gap for operator-method definitions (+(a, b) = ...). - Julia
do-blocks (map(xs) do x ... end) roll into the enclosing function rather than counted separately. - Julia
npathcounts top-level branches but under-counts nested control flow insideelseif/elseclause bodies. Treat the number as a lower bound. - Julia
class.*(CK CBO/DIT/NOC) is deferred. Same posture as Go and Rust, which also ship without these.
0.6.1 - 2026-04-18
Released to PyPI on 2026-04-18 as agent-slop-lint==0.6.1. Tag: v0.6.1.
- Bundled agent skill's
validatecommand (skill.sh/skill.ps1, installed viaslop skill <dir>) used to fail on every slop-only install witherror: aux not foundand tell users to run./scripts/install.sh. Theauxbinary is not installed bypip install agent-slop-lintand was not a runtime dependency of slop post-0.5.0, so this was a broken instruction.validatenow checks forsloponly and points users atpip install agent-slop-lintor the install script. orphansrule JSON output. Thenext_steps.verify_commandfield used to emitaux usages <symbol> --root <root>, referencing a command slop does not ship. Replaced withrg <symbol> <root>(ripgrep is already a required slop system dependency). The accompanyingmessagestring now mentions "trace with ripgrep" rather than "trace withaux usages".- Rule-file module docstrings (
complexity.py,class_metrics.py,dead_code.py,dependencies.py,hotspots.py) andpreflight.pyno longer describe their kernels as "aux X_kernel"; they now say "the vendored X_kernel" to match post-0.5.0 reality. docs/SETUP.mdtroubleshooting entry for missing system tools no longer referencesaux doctororaux curl; it points atslop doctorand a direct install hint for the three system binaries slop uses.
0.6.0 - 2026-04-18
Released to PyPI on 2026-04-18 as agent-slop-lint==0.6.0. Tag: v0.6.0.
packagesrule now runs on every language slop supports. Previouslypackages(Martin's Distance from the Main Sequence) was Go and Python only. The underlyingrobert_kernelnow has abstract/concrete type detection for Java (interface,abstract class,record), C# (interface,abstract class,struct,record), TypeScript (interface,abstract class), Rust (trait,struct,enum), and JavaScript (all classes counted concrete because the language has no abstract/interface construct). Both the tree-sitter AST path and the regex fallback are implemented per language. See CONFIG.md for per-language semantics and the JavaScript "Zone of Pain by default" caveat.
- Documentation cleanup. Removed every remaining claim that
slopdepends on the externalaux-skillspackage at runtime (it does not since 0.5.0). README's "Architecture" section now describes the kernels as shipped inside the wheel. SETUP.md no longer sayspip install agent-slop-lintpulls inaux-skills. CLAUDE.md rewritten along the same lines. NOTICE's stale "COMPUTATIONAL BACKEND" block removed and the vendor-code path updated to reflect the 0.5.0 restructure._aux/util/doctor.pyinstall hints fortree-sitterandgitnow point atagent-slop-lintandslop hotspotsrespectively rather than the pre-vendoraux-skillsandaux delta. Optional-Python-packages block (for the aux curl kernel, which slop does not ship) removed._aux/__init__.pydocstring reworded to describe what the subpackage is; attribution remains in NOTICE and the vendored LICENSE where Apache 2.0 requires it. - Language support table in README and SETUP.md updated to mark
packagesasyesfor Java, C#, TypeScript, JavaScript, and Rust. CONFIG.mdpackagessection rewritten to document the per-language abstract-type conventions and the JavaScript caveat.
- If you run slop on a codebase containing Java, C#, TypeScript, JavaScript, or Rust, the
packagesrule will now produce output where it previously returned nothing.packagesisseverity = "warning"by default, so this does not convert passing builds to failing builds without a config change. If the new coverage is noisy on your JS-only project (see caveat above), the quickest silencer is[rules.packages]\nenabled = falsein your.slop.toml. pyproject.tomlwithout a[tool.slop]section no longer halts slop's upward config walk (this landed in 0.5.0; called out here again because the implication for nested-project layouts is subtle). If a subproject pyproject was intentionally shielding a monorepo.slop.toml, add an explicit[tool.slop]table to keep that behavior.
0.5.0 - 2026-04-17
Released to PyPI on 2026-04-17 as agent-slop-lint==0.5.0. Tag: v0.5.0.
- Two new complexity metrics exposed as rules, both from well-cited prior art:
halstead.volume(V > 1500) andhalstead.difficulty(D > 30), from Halstead's (1977) Software Science. Volume catches functions with high information content; Difficulty catches functions with dense operator/operand reuse. These cover the "moderate CCX but many distinct symbols" case that McCabe's cyclomatic complexity misses.npath(NPath > 400), from Nejmeh (1988). Counts acyclic execution paths. Unlike CCX (additive), NPath is multiplicative, so ten sequential independentifstatements produce CCX=11 but NPath=1024. This is the specific pattern agents produce when they dispatch on multiple flags.
CHANGELOG.md(this file) documenting release history going forward. Historical entries for 0.1.0 through 0.4.0 are summaries, not exhaustive.- CONFIG.md now has a "Note on default thresholds" section at the top documenting every default that diverges from its cited source, with rationale.
- slop is now self-contained. The metric kernels previously imported from
aux-skillson PyPI are vendored undersrc/cli/slop/_aux/(Apache-2.0 attributed inNOTICEandsrc/cli/slop/_aux/LICENSE). Theaux-skillsruntime dependency is removed;pip install agent-slop-lintnow installs a single package. aux-skills was pre-1.0 and every kernel slop depended on had been modified in the last 90 days, so the external pin was absorbing breaking-change risk on a cadence slop did not control. - Repo layout. The Python project is now under
src/(withsrc/pyproject.toml,src/cli/slop/for the package, andsrc/tests/for tests). The repo top-level now contains only docs, scripts, skills, LICENSE, NOTICE, README, CHANGELOG,.slop.toml, and.github/. Dev workflow requirescd srcbeforeuv sync/uv run pytest/uv build. CI workflows setworking-directory: srcon the relevant steps. - Three default thresholds tuned for contemporary and agentic practice. See CONFIG.md "Note on default thresholds" for per-rule rationale:
complexity.weighted: WMC > 50 → WMC > 40 (tighter; closer to Fowler/Martin era advice, catches god-class drift earlier).halstead.volume: V > 1000 → V > 1500 (looser; 1000 flags legitimate orchestration functions, 1500 still flags the pathological three-responsibilities-fused case).npath: NPath > 200 → NPath > 400 (looser; Nejmeh's 1988 ceiling predates modern CLI dispatch — honestclick/argparsemain functions sit at NPath 256-512 without being rot).
- Profiles also re-calibrated to maintain their semantic relationship to the new defaults:
lax: WMC 100 → 80, Volume 1500 → 3000, NPath 500 → 1000.strict: unchanged (already stricter than the new defaults).
- Config discovery tweaked.
_discover_confignow walks past apyproject.tomlthat has no[tool.slop]table rather than stopping there. This lets sub-project pyproject files (like the newsrc/pyproject.tomlin this repo) coexist with a repo-root.slop.tomlin monorepos and nested layouts. Matches how ruff and mypy behave in practice.
aux-skillsruntime dependency (vendored in; see above).tool.uv.sourcesoverride pointing at a sibling../aux/clipath. Development no longer depends on a locally-cloned aux repo.
- Existing
.slop.tomlconfigs keep working. If you relied on explicitweighted_threshold,volume_threshold, ornpath_thresholdvalues, they take precedence over the new defaults. - If you did NOT set those three thresholds explicitly and your codebase sits in the changed ranges, expect a different violation count on first run after upgrading. WMC went tighter (more violations likely); Volume and NPath went looser (fewer violations likely).
- Users in monorepos with
pyproject.tomlat a sub-project level AND a repo-root.slop.toml: discovery will now correctly find the root config instead of halting at the sub-project pyproject. If this changes the behavior you rely on, add an explicit[tool.slop]table to the sub-project pyproject. - The
README.mdandLICENSEare tracked both at repo root (for GitHub) and insidesrc/(for PyPI, a hatchling constraint). Keep them in sync when editing.
0.4.0 - 2026-04-16
Released to PyPI on 2026-04-16 as agent-slop-lint==0.4.0. Tag: v0.4.0.
slop doctorsubcommand. Reports availability offd,rg, andgitso users can diagnose missing system dependencies before touching configuration.- Preflight system-binary check runs automatically before
slop lintandslop check. Missing required binaries produce an explicit error block and exit code 2 rather than silently returning zero files analyzed. Fixes a failure mode whereslop linton a machine withoutfd(notably some macOS setups) reported✓ cleanwith no violations. - Upward config discovery.
slop lintwalks from the current directory toward the filesystem root looking for.slop.tomlorpyproject.tomlwith[tool.slop], matching ruff/mypy convention.rootkeys in a discovered config now resolve relative to the config file's directory, not CWD. - Per-rule errors surfaced in human output (previously only JSON). Categories whose rules produced errors now show the error line and the status footer reads
ERROR.
- "Zero files analyzed" now renders as
⚠ no files matched(yellow warning) rather than✓ clean, so genuinely empty scans cannot be mistaken for passing scans. - A rule that produced errors and no violations is now coerced from
passtoerrorin the engine layer, so silent failures cannot render as clean. format_humanrefactored from a monolith to named helpers with a_CategoryAggdataclass. Dogfood complexity now within slop's own thresholds.
0.3.1 - 2026-04-13
Released to PyPI on 2026-04-13 as agent-slop-lint==0.3.1. Tag: v0.3.1.
slop hooksubcommand to install or remove a git pre-commit hook that runsslop lint --output quiet.
0.3.0 - 2026-04-13
Released to PyPI on 2026-04-13 as agent-slop-lint==0.3.0. Tag: v0.3.0.
slop skill <dir>subcommand to copy the bundled agent skill into any directory (for Claude Code / Cursor / other agents).slop init [default|lax|strict]profile selection.docs/CONFIG.mdrule-by-rule configuration reference.docs/SETUP.mdinstall-configure-integrate-verify guide.llms.txtfor agent-friendly project discovery.
0.2.0 - 2026-04-12
Released to PyPI on 2026-04-12 as agent-slop-lint==0.2.0. Tag: v0.2.0.
- Hotspot metric moved to LOC-delta churn proxy (was commit count) and defaults tightened to a 14-day window (was 90d), calibrated for agentic code generation timescales.
aux-skillspulled from PyPI rather than a sibling git path (internal-dev convenience).
0.1.0 - 2026-04-10
Released to PyPI on 2026-04-11 as agent-slop-lint==0.1.0. Tag: v0.1.0.
- Initial release. Ten rules across six categories:
complexity.cyclomatic,complexity.cognitive,complexity.weighted,hotspots,packages,deps,orphans,class.coupling,class.inheritance.depth,class.inheritance.children. - Backed by
aux-skillskernels (tree-sitter, ripgrep, fd, git). slop lint,slop check,slop rules,slop init,slop schemasubcommands.- Human, JSON, and quiet output formats.
.slop.tomlandpyproject.toml [tool.slop]config support.- PyPI distribution as
agent-slop-lint.