Repository navigation
ldd-lint: a gap linter for file length and package size, one set of thresholds - #100
Merged
Merged
Conversation
…hresholds Add scripts/ldd-lint.sh to every plugin. The script measures lines per source file (file-length) and source files per directory (package-size), where the repository's linter does not hold them. It never edits a file. The threshold comes from the first source that applies: - the repository's linter config, counted the way that linter counts (revive file-length-limit, sonarjs/max-lines, ESLint max-lines, pylint max-module-lines); - .ldd-lint.yaml at the repository root; - the profile defaults: 450 code lines per file, a warning at 8 source files per directory, the limit at 13. The first output line names the threshold and its source. With the defaults, it also prints the setting that makes the linter hold the limit. - Three profile scalars hold the thresholds. The generator rejects a value that is not a positive integer, and a warning at or above the limit. - A core include, scripts/ldd-lint-langs.sh, is the linter table, keyed by language id. It is the one place core names linters (residue-exempt). - ldd-lint_test.sh runs 20 cases per fixture row in every plugin. The bindings' fixture rows add the source suffix, the comment forms, a test file name, the file a count leaves out, and a linter config. - The skills and routing tables use the scalars in place of 400, 450, 600, 8-12 and 13, and the package-size procedure runs the gap linter. - Remove the Go plugin's hooks/check-package-sizes.sh. It was never registered, so it never ran. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ig forms
- The threshold line offers the linter setting as a proposal ("a setting to
propose, never to add on your own"). An agent that runs the script does
not edit a linter config, as the house rules require.
- Every path the script gives to find, head or awk starts with "./", and a
directory is cut from a path by parameter expansion. A file or directory
whose name starts with "-" is measured and never read as an option. A
matrix case covers it; dirname failed that case before this change.
- The fixture rows pin more config forms in CI: revive's bare-number form
(Go plugin), its map form (generic plugin's Go row), revive with the skip
flags and sonarjs/max-lines, which count only code lines.
- docs/token-budget.md keeps the planned Go analyzer apart from the gap
linter's name.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The gap linter read each linter's limit and copied each linter's way of counting. That code was a second copy of the linters' rules to maintain and test. The linter is the authority for every threshold it owns, so the script now asks one question: does a linter config at the repository root name a file-length rule (revive file-length-limit, sonarjs/max-lines, ESLint max-lines, pylint max-module-lines) on a line that is not a comment? - Yes: the threshold line names the rule, and the script measures no file length. The rule's limit, its counting and whether it is on are the repository's decision. - No: the script counts code lines against .ldd-lint.yaml or the plugin default, as before. The config readers, the counting modes and their fixture builders are removed (about 120 lines). The matrix pins the new behavior: a named rule, a rule of a similar name (argument-limit, max-args, max-lines-per-function) and a commented-out rule, for every fixture row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
Option parsing can hang, and comment handling and file counting can produce incorrect lint results.
5 open issues
What changed in this PR
Adds a shared gap linter to the generated plugins, covering file length and package size while deferring to repository linter rules.
Changes:
- Adds configurable thresholds, validation, and machine-readable findings.
- Updates refactoring guidance and removes the unused Go package-size hook.
- Adds fixture matrices to the test gate.
| File | Description |
|---|---|
| ts-react-linter-driven-development/skills/refactoring/SKILL.md | Routes gap-linter findings. |
| ts-react-linter-driven-development/skills/refactoring/reference.md | Uses gap-linter measurements. |
| ts-react-linter-driven-development/skills/code-designing/SKILL.md | Updates file-length guidance. |
| ts-react-linter-driven-development/scripts/ldd-scope_test.sh | Expands shared fixtures. |
| ts-react-linter-driven-development/scripts/ldd-lint_test.sh | Adds lint fixture matrix. |
| ts-react-linter-driven-development/scripts/ldd-detect_test.sh | Expands shared fixtures. |
| ts-react-linter-driven-development/rules/R5-vertical-slice.md | Removes example-specific threshold. |
| ts-react-linter-driven-development/CHANGELOG.md | Documents gap linter. |
| ts-react-linter-driven-development/agents/lint-fixer.md | Routes gap-linter findings. |
| tools/ldd-gen/main_test.go | Adds fixture thresholds. |
| tools/ldd-gen/internal/render/render_test.go | Adds fixture thresholds. |
| tools/ldd-gen/internal/profile/profile.go | Validates threshold scalars. |
| tools/ldd-gen/internal/profile/profile_test.go | Tests threshold validation. |
| tools/ldd-gen/internal/gen/gen_test.go | Adds fixture thresholds. |
| tools/ldd-gen/internal/binding/binding_test.go | Adds fixture thresholds. |
| Taskfile.yaml | Registers lint matrices. |
| python-linter-driven-development/skills/refactoring/SKILL.md | Routes file-length findings. |
| python-linter-driven-development/skills/refactoring/reference.md | Uses gap-linter measurements. |
| python-linter-driven-development/skills/code-designing/SKILL.md | Updates file-length guidance. |
| python-linter-driven-development/scripts/ldd-scope_test.sh | Expands shared fixtures. |
| python-linter-driven-development/scripts/ldd-lint.sh | Adds Python gap linter. |
| python-linter-driven-development/scripts/ldd-lint_test.sh | Adds lint fixture matrix. |
| python-linter-driven-development/scripts/ldd-detect_test.sh | Expands shared fixtures. |
| python-linter-driven-development/CHANGELOG.md | Documents gap linter. |
| linter-driven-development/skills/refactoring/reference.md | Uses gap-linter measurements. |
| linter-driven-development/scripts/ldd-scope_test.sh | Expands shared fixtures. |
| linter-driven-development/scripts/ldd-lint_test.sh | Adds multilingual lint matrix. |
| linter-driven-development/scripts/ldd-detect_test.sh | Expands shared fixtures. |
| linter-driven-development/README.md | Removes obsolete hook reference. |
| linter-driven-development/CHANGELOG.md | Documents gap linter. |
| lang/ts-react/skills/refactoring/routing-table.md | Routes gap-linter findings. |
| lang/ts-react/skills/refactoring/file-and-package-routing.md | Uses thresholds and measurements. |
| lang/ts-react/skills/code-designing/linter-triggers.md | Uses threshold scalar. |
| lang/ts-react/scripts/ldd-fixture.sh | Adds TypeScript lint fixtures. |
| lang/ts-react/rules/R5/falsifying-questions.md | Removes example-specific threshold. |
| lang/ts-react/profile.yaml | Defines threshold defaults. |
| lang/ts-react/passthrough/CHANGELOG.md | Documents gap linter. |
| lang/ts-react/agents/lint-fixer/routing-table.md | Routes gap-linter findings. |
| lang/python/skills/refactoring/routing-table.md | Routes file-length findings. |
| lang/python/skills/refactoring/file-and-package-routing.md | Uses thresholds and measurements. |
| lang/python/skills/code-designing/linter-triggers.md | Uses threshold scalar. |
| lang/python/scripts/ldd-fixture.sh | Adds Python lint fixtures. |
| lang/python/profile.yaml | Defines threshold defaults. |
| lang/python/passthrough/CHANGELOG.md | Documents gap linter. |
| lang/go/skills/refactoring/routing-table.md | Replaces hook routing. |
| lang/go/skills/refactoring/file-and-package-routing.md | Uses thresholds and measurements. |
| lang/go/skills/code-designing/linter-triggers.md | Uses threshold scalar. |
| lang/go/scripts/ldd-fixture.sh | Adds Go lint fixtures. |
| lang/go/profile.yaml | Defines threshold defaults. |
| lang/go/passthrough/README.md | Lists gap linter instead of hook. |
| lang/go/passthrough/hooks/hooks.json | Removes empty hook configuration. |
| lang/go/passthrough/hooks/check-package-sizes.sh | Removes unused hook source. |
| lang/go/passthrough/CHANGELOG.md | Documents linter and hook removal. |
| lang/go/agents/lint-fixer/routing-table.md | Replaces hook routing. |
| lang/generic/scripts/ldd-fixture.sh | Adds multilingual lint fixtures. |
| lang/generic/profile.yaml | Defines threshold defaults. |
| lang/generic/passthrough/README.md | Removes obsolete hook reference. |
| lang/generic/passthrough/CHANGELOG.md | Documents gap linter. |
| go-linter-driven-development/skills/refactoring/SKILL.md | Replaces hook routing. |
| go-linter-driven-development/skills/refactoring/reference.md | Uses gap-linter measurements. |
| go-linter-driven-development/skills/code-designing/SKILL.md | Updates file-length guidance. |
| go-linter-driven-development/scripts/ldd-scope_test.sh | Expands shared fixtures. |
| go-linter-driven-development/scripts/ldd-lint.sh | Adds Go gap linter. |
| go-linter-driven-development/scripts/ldd-lint_test.sh | Adds lint fixture matrix. |
| go-linter-driven-development/scripts/ldd-detect_test.sh | Expands shared fixtures. |
| go-linter-driven-development/README.md | Lists gap linter instead of hook. |
| go-linter-driven-development/hooks/hooks.json | Removes empty hook configuration. |
| go-linter-driven-development/hooks/check-package-sizes.sh | Removes unused generated hook. |
| go-linter-driven-development/CHANGELOG.md | Documents linter and hook removal. |
| go-linter-driven-development/agents/lint-fixer.md | Replaces hook routing. |
| docs/token-budget.md | Distinguishes planned analyzer; removes hook claim. |
| docs/language-residue.md | Describes shared linter table. |
| docs/generator.md | Removes obsolete hook rationale. |
| docs/eval-fixture.md | Updates fixture-layout rationale. |
| core/skills/refactoring/reference.md | Uses package threshold scalars. |
| core/scripts/ldd-lint.sh | Implements shared gap-linter template. |
| core/scripts/ldd-lint_test.sh | Adds shared lint matrix template. |
| core/README.md | Documents scalars and lint templates. |
| core/includes/skills/refactoring/file-and-package-routing.md | Uses shared measurements and thresholds. |
| core/includes/scripts/ldd-lint-langs.sh | Defines language-specific lint handling. |
Review effort: Balanced
…t inventory Fix five review findings: - A rule name inside a block or trailing comment of a linter config no longer counts as a named rule. A new LINT_STRIP_AWK removes comments one character at a time and keeps string literals. The config check and the line count both use it. - A block comment that opens after code on a line now ends the code there. The lines inside the block are not counted. - --root, --files, --glob or --test-re as the last argument is a usage error (exit 2). Before, the loop never ended. - The directory count reads the repository inventory (git ls-files when the root is a repository). A git-ignored sibling no longer counts, and a --files run counts each directory it names in full. - *.stories.ts and the other story files are left out of the TypeScript directory count again. Each fix has a matrix case that fails on the previous script. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve the conflict in ts-react R5 Q2. Keep main's mechanical lead for
the god file (the longest scope files by wc -l). Replace the limits it
names with the shared threshold: the repository linter's rule where the
repository sets it, else {{.FileMaxLines}} code lines. Apply the same
change to the Go, Python and core R5 Q2 text, which named 450 lines.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # go-linter-driven-development/CHANGELOG.md # lang/generic/passthrough/CHANGELOG.md # lang/go/passthrough/CHANGELOG.md # lang/python/passthrough/CHANGELOG.md # lang/ts-react/passthrough/CHANGELOG.md # linter-driven-development/CHANGELOG.md # python-linter-driven-development/CHANGELOG.md # ts-react-linter-driven-development/CHANGELOG.md
buzzdan
marked this pull request as ready for review
October 18, 2026 19:10
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


What
Every plugin gets
scripts/ldd-lint.sh, a gap linter. It measures two design triggers:file-length: lines per source file.package-size: source files per directory. No linter in any of the four ecosystems counts these.The script never edits a file. The linter is the authority for every threshold it owns, and the gap linter fills only the gaps. The first output line names the source of each threshold.
file-length-limit,sonarjs/max-lines, ESLintmax-lines, pylintmax-module-lines). Then the linter holds file length, and the script measures none. The linter's limit, its way of counting and whether the rule is on are the repository's decision. Otherwise the script counts code lines against.ldd-lint.yaml, else the default of 450. With the default, it prints a linter setting to propose to the repository's owners..ldd-lint.yaml, else from the defaults: a warning at 8 source files per directory and the limit at 13.--tsvwrites findings aspath TAB 0 TAB messagerows. A later PR adds theDetect-scriptdetect-line kind, which reads these rows.Changes
file_max_lines,package_warn_filesandpackage_max_files. The generator rejects a value that is not a positive integer. It also rejects a warning size at or above the limit.scripts/ldd-lint-langs.sh, is keyed by language id. It holds the comment syntax, the files that a directory count leaves out, and a check that a linter config names a file-length rule. It is the one place in core that names linters, and it carries aresidue-exempt:marker. The table does not read any limit or counting mode out of a config.find.hooks/check-package-sizes.shis deleted. It was never registered, so it never ran.ldd-lint_test.shruns intask test-gatefor every plugin, with 22 to 23 cases per fixture row. The cases include a named rule, a rule with a similar name and a commented-out rule.Departures from the plan
Hook registration moved to a separate PR. Every eval fixture has directories at or over the limit:
internal/modelsinternal/modelssrc/typessrc/servicesA registered hook would speak to the agent during eval cases, so it needs its own eval gate.
No config parsing. The plan read each linter's limit and copied its way of counting. That was a second copy of the linters' rules to maintain and test. The script now only checks whether a config names the rule.
No new field in the language block. The per-language data goes into a shared table, the same way as the language table. The generic plugin then covers every language id with no extra work.
Text the agents read that changed
The review rules did not change, except for one question. Three texts changed:
ldd-lint.shnow. On the eval fixtures, the script measures file length only on py-mini, where it finds no file over 450 code lines. go-mini and ts-react-mini name a file-length rule, so the linter holds file length there. All three fixtures report the package-size zones in the table above.The package-size zones are not new. The same procedure counted them before, with a
findcommand. I propose to merge without an eval run.Checks
task check,task lint-core,task docs:check, and the generator's tests and lint all pass.ldd-scope,ldd-detectandldd-lintmatrices pass for all four plugins on macOS. CI on Ubuntu passed on the earlier head, which ran the counting awk under mawk. Thecheck-repo-brainmatrix fails two Q7 cases, the same as onmain.🤖 Generated with Claude Code