scip-cli 2.9.0__tar.gz → 2.10.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. {scip_cli-2.9.0 → scip_cli-2.10.0}/PKG-INFO +7 -4
  2. {scip_cli-2.9.0 → scip_cli-2.10.0}/README.md +6 -3
  3. {scip_cli-2.9.0 → scip_cli-2.10.0}/pyproject.toml +2 -1
  4. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/SKILL.md +7 -7
  5. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/__init__.py +1 -1
  6. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/__main__.py +14 -1
  7. scip_cli-2.10.0/scip_cli/analyze/common.py +243 -0
  8. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/analyze/file.py +54 -47
  9. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/analyze/live.py +58 -1
  10. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/analyze/project.py +207 -172
  11. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/analyze/sections.py +75 -42
  12. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/analyze/symbol.py +4 -1
  13. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/commands/analyze.py +42 -4
  14. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/commands/reindex.py +4 -0
  15. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/indexing/__init__.py +2 -1
  16. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/indexing/core.py +17 -0
  17. {scip_cli-2.9.0 → scip_cli-2.10.0}/scripts/bench.sh +1 -1
  18. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/bench_db.py +123 -2
  19. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/test_analyze.py +107 -10
  20. scip_cli-2.10.0/tests/test_analyze_live.py +71 -0
  21. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/test_bench_queries.py +41 -2
  22. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/test_index_prune.py +9 -1
  23. scip_cli-2.9.0/scip_cli/analyze/common.py +0 -133
  24. {scip_cli-2.9.0 → scip_cli-2.10.0}/.agents/commands/migration-problems.md +0 -0
  25. {scip_cli-2.9.0 → scip_cli-2.10.0}/.agents/rules/agent.mdc +0 -0
  26. {scip_cli-2.9.0 → scip_cli-2.10.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  27. {scip_cli-2.9.0 → scip_cli-2.10.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  28. {scip_cli-2.9.0 → scip_cli-2.10.0}/.github/ISSUE_TEMPLATE/question.md +0 -0
  29. {scip_cli-2.9.0 → scip_cli-2.10.0}/.github/pull_request_template.md +0 -0
  30. {scip_cli-2.9.0 → scip_cli-2.10.0}/.github/workflows/ci.yml +0 -0
  31. {scip_cli-2.9.0 → scip_cli-2.10.0}/.gitignore +0 -0
  32. {scip_cli-2.9.0 → scip_cli-2.10.0}/.pre-commit-config.yaml +0 -0
  33. {scip_cli-2.9.0 → scip_cli-2.10.0}/AGENTS.md +0 -0
  34. {scip_cli-2.9.0 → scip_cli-2.10.0}/CODE_OF_CONDUCT.md +0 -0
  35. {scip_cli-2.9.0 → scip_cli-2.10.0}/CONTRIBUTING.md +0 -0
  36. {scip_cli-2.9.0 → scip_cli-2.10.0}/LICENSE +0 -0
  37. {scip_cli-2.9.0 → scip_cli-2.10.0}/SECURITY.md +0 -0
  38. {scip_cli-2.9.0 → scip_cli-2.10.0}/docs/port.md +0 -0
  39. {scip_cli-2.9.0 → scip_cli-2.10.0}/docs/ports/conclusions.md +0 -0
  40. {scip_cli-2.9.0 → scip_cli-2.10.0}/docs/ports/experiment-summary.md +0 -0
  41. {scip_cli-2.9.0 → scip_cli-2.10.0}/docs/ports/go-experiment.md +0 -0
  42. {scip_cli-2.9.0 → scip_cli-2.10.0}/docs/ports/go.md +0 -0
  43. {scip_cli-2.9.0 → scip_cli-2.10.0}/docs/ports/plan.md +0 -0
  44. {scip_cli-2.9.0 → scip_cli-2.10.0}/docs/ports/rust-experiment.md +0 -0
  45. {scip_cli-2.9.0 → scip_cli-2.10.0}/docs/ports/rust.md +0 -0
  46. {scip_cli-2.9.0 → scip_cli-2.10.0}/docs/ports/summary.md +0 -0
  47. {scip_cli-2.9.0 → scip_cli-2.10.0}/docs/ports/zig-experiment.md +0 -0
  48. {scip_cli-2.9.0 → scip_cli-2.10.0}/docs/ports/zig.md +0 -0
  49. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/analyze/__init__.py +0 -0
  50. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/analyze/graph.py +0 -0
  51. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/analyze/targets.py +0 -0
  52. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/cache.py +0 -0
  53. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/cli_args.py +0 -0
  54. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/commands/__init__.py +0 -0
  55. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/commands/code.py +0 -0
  56. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/commands/deps.py +0 -0
  57. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/commands/members.py +0 -0
  58. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/commands/rdeps.py +0 -0
  59. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/commands/refs.py +0 -0
  60. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/commands/search.py +0 -0
  61. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/commands/skill.py +0 -0
  62. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/commands/symbols.py +0 -0
  63. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/config.py +0 -0
  64. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/debug.py +0 -0
  65. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/discover.py +0 -0
  66. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/exclude.py +0 -0
  67. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/indexing/constants.py +0 -0
  68. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/indexing/convert.py +0 -0
  69. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/indexing/languages.py +0 -0
  70. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/indexing/orchestrate.py +0 -0
  71. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/indexing/postprocess.py +0 -0
  72. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/indexing/runners.py +0 -0
  73. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/indexing/typescript.py +0 -0
  74. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/merge.py +0 -0
  75. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/metadata.py +0 -0
  76. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/output.py +0 -0
  77. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/paths.py +0 -0
  78. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/project.py +0 -0
  79. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/queries.py +0 -0
  80. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/scip_tool.py +0 -0
  81. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/scope.py +0 -0
  82. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/session.py +0 -0
  83. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/source.py +0 -0
  84. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/sql.py +0 -0
  85. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/symbols.py +0 -0
  86. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/targets.py +0 -0
  87. {scip_cli-2.9.0 → scip_cli-2.10.0}/scip_cli/tsconfig.py +0 -0
  88. {scip_cli-2.9.0 → scip_cli-2.10.0}/scripts/bench_postprocess.py +0 -0
  89. {scip_cli-2.9.0 → scip_cli-2.10.0}/scripts/build.sh +0 -0
  90. {scip_cli-2.9.0 → scip_cli-2.10.0}/scripts/publish.sh +0 -0
  91. {scip_cli-2.9.0 → scip_cli-2.10.0}/scripts/test.sh +0 -0
  92. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/__init__.py +0 -0
  93. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/analyze_db.py +0 -0
  94. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/conftest.py +0 -0
  95. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/e2e_harness.py +0 -0
  96. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixture_catalog.py +0 -0
  97. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/package.json +0 -0
  98. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/__tests__/fixtureOnly.spec.ts +0 -0
  99. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/app/handler.ts +0 -0
  100. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/config.ts +0 -0
  101. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/consumer.ts +0 -0
  102. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/dead.ts +0 -0
  103. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/domain/i18n/en.ts +0 -0
  104. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/domain/i18n/index.ts +0 -0
  105. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/domain/i18n/useLocale.ts +0 -0
  106. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/domain/labels/index.ts +0 -0
  107. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/events/streamTypes.ts +0 -0
  108. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/gateway/index.ts +0 -0
  109. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/helper.ts +0 -0
  110. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/hooks/useHookA.ts +0 -0
  111. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/hooks/useHookB.ts +0 -0
  112. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/hooks/useItems.ts +0 -0
  113. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/index.ts +0 -0
  114. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/integrations/inferenceClient.ts +0 -0
  115. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/lib/handler.ts +0 -0
  116. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/loaders/useInference.ts +0 -0
  117. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/models/MutationAggregate.ts +0 -0
  118. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/pages/panelPage.ts +0 -0
  119. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/rules/applyRule.ts +0 -0
  120. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/types/a.ts +0 -0
  121. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/types/b.ts +0 -0
  122. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/ui/Button.ts +0 -0
  123. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/ui/LazyPanel.ts +0 -0
  124. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/ui/buttonConsumer.ts +0 -0
  125. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/ui/menuModule.ts +0 -0
  126. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/user.ts +0 -0
  127. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/widget.ts +0 -0
  128. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/src/widgets/OrphanWidget.ts +0 -0
  129. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/fixtures/typescript-project/tsconfig.json +0 -0
  130. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/perf_util.py +0 -0
  131. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/test_analyze_graph.py +0 -0
  132. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/test_analyze_perf.py +0 -0
  133. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/test_cache.py +0 -0
  134. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/test_composability.py +0 -0
  135. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/test_config.py +0 -0
  136. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/test_discover.py +0 -0
  137. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/test_e2e.py +0 -0
  138. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/test_e2e_analyze_patterns.py +0 -0
  139. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/test_e2e_perf.py +0 -0
  140. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/test_exclude_globs.py +0 -0
  141. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/test_index_batching.py +0 -0
  142. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/test_indexer_env.py +0 -0
  143. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/test_indexer_fallback.py +0 -0
  144. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/test_merge.py +0 -0
  145. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/test_metadata.py +0 -0
  146. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/test_multi_symbol.py +0 -0
  147. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/test_pure_functions.py +0 -0
  148. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/test_python_index_integration.py +0 -0
  149. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/test_qualified_symbols.py +0 -0
  150. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/test_reindex.py +0 -0
  151. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/test_scip_tool.py +0 -0
  152. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/test_scope.py +0 -0
  153. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/test_symbols_freq.py +0 -0
  154. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/test_targets.py +0 -0
  155. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/test_tsconfig.py +0 -0
  156. {scip_cli-2.9.0 → scip_cli-2.10.0}/tests/test_typescript_projects.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: scip-cli
3
- Version: 2.9.0
3
+ Version: 2.10.0
4
4
  Summary: Fast code intelligence via SCIP indexes
5
5
  Project-URL: Homepage, https://github.com/flesler/scip-cli
6
6
  Author: Ariel Flesler
@@ -153,7 +153,7 @@ scip-cli <command> [arguments]
153
153
  - `rdeps <file>` - Find files that depend on a file (`--path`)
154
154
  - `deps <symbol|file>` - Find outbound dependencies (what a symbol or file calls) (`--path`, `--paths-only`)
155
155
  - `members <symbol>` - List members of a class/interface (`--path`)
156
- - `analyze [target]` - SQL health dashboards (`--limit`, `--priority`, `--check`, `--include-tests`). No target: project-wide; directory or file path; symbol name. See [Finding easy wins with `analyze`](#finding-easy-wins-with-analyze).
156
+ - `analyze [target]` - SQL health dashboards (`--limit`, `--per-check-limit`, `--priority`, `--check`, `--include-tests`). No target: project-wide; directory or file path; symbol name. See [Finding easy wins with `analyze`](#finding-easy-wins-with-analyze).
157
157
  - `reindex` - Force re-indexing of the current project (`--path` to limit scope; repeatable)
158
158
  - `skill [path]` - Install or dump the SKILL.md
159
159
 
@@ -282,16 +282,19 @@ Use `analyze` on the repo itself before broad refactors or agent review — it s
282
282
 
283
283
  ```bash
284
284
  scip-cli analyze --limit 25
285
+ scip-cli analyze --limit 40 --per-check-limit 5 # spread the global budget across checks
285
286
  scip-cli analyze --priority high --limit 25 # dead exports & cycles only
286
287
  scip-cli analyze --check cycles --limit 25 # one named section
287
288
  scip-cli analyze --check dead_files --limit 25
288
289
  ```
289
290
 
291
+ `--limit` is a **global** finding-row budget (truncation markers do not count). `--per-check-limit` (default unlimited) caps each section. Directory analyze spends that same budget on per-file dumps after dashboards.
292
+
290
293
  Sections are tagged `[high]`, `[medium]`, `[low]` and listed in that order.
291
294
 
292
295
  |Tier|Project sections|Action|
293
296
  |---|---|---|
294
- |**high**|Cycles, unreferenced, dead exports, dead files, stale types|Nuke or fix cycles; delete unused; `_` prefix|
297
+ |**high**|Cycles, dead exports, dead files, stale types (unreferenced is omitted when dead exports is on)|Nuke or fix cycles; delete unused; `_` prefix|
295
298
  |**medium**|Same-file only, change surface (file target)|Module-private by usage|
296
299
  |**low**|Test-only consumers, coupling, bottlenecks, hotspots|Noisy on Python (index omits many same-file calls); verify with `rg`|
297
300
 
@@ -302,7 +305,7 @@ Use `--priority high` for a quick gate; `--priority high,medium` adds context. F
302
305
  |Section|Easy pickings|
303
306
  |---|---|
304
307
  |**Cycles**|Import/mention cycles between production files — break the edge or extract shared code|
305
- |**Unreferenced**|No usage in the index at all — delete|
308
+ |**Unreferenced**|Same survivors as dead exports; omitted unless `--check unreferenced`. Empty `rdeps` is usually a SCIP miss — verify with `rg -F STEM` (file basename in `(...)`)|
306
309
  |**Dead exports**|No external refs — delete or `_` prefix|
307
310
  |**Stale types**|Classes/types with no external consumer in the index — verify in-file or type-only use before removing|
308
311
  |**Same-file only**|Used only inside defining file — rename to `_`|
@@ -132,7 +132,7 @@ scip-cli <command> [arguments]
132
132
  - `rdeps <file>` - Find files that depend on a file (`--path`)
133
133
  - `deps <symbol|file>` - Find outbound dependencies (what a symbol or file calls) (`--path`, `--paths-only`)
134
134
  - `members <symbol>` - List members of a class/interface (`--path`)
135
- - `analyze [target]` - SQL health dashboards (`--limit`, `--priority`, `--check`, `--include-tests`). No target: project-wide; directory or file path; symbol name. See [Finding easy wins with `analyze`](#finding-easy-wins-with-analyze).
135
+ - `analyze [target]` - SQL health dashboards (`--limit`, `--per-check-limit`, `--priority`, `--check`, `--include-tests`). No target: project-wide; directory or file path; symbol name. See [Finding easy wins with `analyze`](#finding-easy-wins-with-analyze).
136
136
  - `reindex` - Force re-indexing of the current project (`--path` to limit scope; repeatable)
137
137
  - `skill [path]` - Install or dump the SKILL.md
138
138
 
@@ -261,16 +261,19 @@ Use `analyze` on the repo itself before broad refactors or agent review — it s
261
261
 
262
262
  ```bash
263
263
  scip-cli analyze --limit 25
264
+ scip-cli analyze --limit 40 --per-check-limit 5 # spread the global budget across checks
264
265
  scip-cli analyze --priority high --limit 25 # dead exports & cycles only
265
266
  scip-cli analyze --check cycles --limit 25 # one named section
266
267
  scip-cli analyze --check dead_files --limit 25
267
268
  ```
268
269
 
270
+ `--limit` is a **global** finding-row budget (truncation markers do not count). `--per-check-limit` (default unlimited) caps each section. Directory analyze spends that same budget on per-file dumps after dashboards.
271
+
269
272
  Sections are tagged `[high]`, `[medium]`, `[low]` and listed in that order.
270
273
 
271
274
  |Tier|Project sections|Action|
272
275
  |---|---|---|
273
- |**high**|Cycles, unreferenced, dead exports, dead files, stale types|Nuke or fix cycles; delete unused; `_` prefix|
276
+ |**high**|Cycles, dead exports, dead files, stale types (unreferenced is omitted when dead exports is on)|Nuke or fix cycles; delete unused; `_` prefix|
274
277
  |**medium**|Same-file only, change surface (file target)|Module-private by usage|
275
278
  |**low**|Test-only consumers, coupling, bottlenecks, hotspots|Noisy on Python (index omits many same-file calls); verify with `rg`|
276
279
 
@@ -281,7 +284,7 @@ Use `--priority high` for a quick gate; `--priority high,medium` adds context. F
281
284
  |Section|Easy pickings|
282
285
  |---|---|
283
286
  |**Cycles**|Import/mention cycles between production files — break the edge or extract shared code|
284
- |**Unreferenced**|No usage in the index at all — delete|
287
+ |**Unreferenced**|Same survivors as dead exports; omitted unless `--check unreferenced`. Empty `rdeps` is usually a SCIP miss — verify with `rg -F STEM` (file basename in `(...)`)|
285
288
  |**Dead exports**|No external refs — delete or `_` prefix|
286
289
  |**Stale types**|Classes/types with no external consumer in the index — verify in-file or type-only use before removing|
287
290
  |**Same-file only**|Used only inside defining file — rename to `_`|
@@ -54,9 +54,10 @@ testpaths = ["tests"]
54
54
  python_files = ["test_*.py"]
55
55
  python_classes = ["Test*"]
56
56
  python_functions = ["test_*"]
57
- addopts = "-q --tb=line --no-header"
57
+ addopts = "-q --tb=line --no-header -m 'not benchmark'"
58
58
  markers = [
59
59
  "integration: uses the shared indexed TS fixture (requires npx / scip-typescript)",
60
+ "benchmark: scaled :memory: query timings (run via scripts/bench.sh only)",
60
61
  ]
61
62
 
62
63
  [tool.basedpyright]
@@ -64,7 +64,7 @@ Commands emit one record per line on stdout; warnings and progress go to stderr.
64
64
  |Goal|Pipeline|
65
65
  |---|---|
66
66
  |Blast radius of a file|`scip-cli rdeps file.ts \|xargs -I{} scip-cli symbols {}`|
67
- |Pre-change / health briefing|`scip-cli analyze` or `scip-cli analyze path/to/file.ts` or `scip-cli analyze SymbolName`|
67
+ |Pre-change / health briefing|`scip-cli analyze --limit 40 --per-check-limit 5` (or a file / symbol)|
68
68
  |Files that import a symbol|`scip-cli refs Foo --paths-only`|
69
69
  |Symbols in referencing files|`scip-cli refs Foo --paths-only \|xargs -I{} scip-cli symbols {}` (barrel files may have no symbols; prefer `search Foo --paths-only` for definition files)|
70
70
  |Find classes, list members|`scip-cli search Handler --kind class --names-only \|xargs -I{} scip-cli members {}`|
@@ -133,7 +133,7 @@ Returns `startLine:endLine kind name` for each member. Members are found via SCI
133
133
  ### analyze
134
134
 
135
135
  ```bash
136
- analyze [--limit N] [--path PATH] [--include-tests] [--priority LEVEL] [--check NAME] [target]
136
+ analyze [--limit N] [--per-check-limit N] [--path PATH] [--include-tests] [--priority LEVEL] [--check NAME] [target]
137
137
  ```
138
138
 
139
139
  |Target|Output|
@@ -143,9 +143,11 @@ analyze [--limit N] [--path PATH] [--include-tests] [--priority LEVEL] [--check
143
143
  |**file**|Scoped project dashboards for that file + per-file sections + top symbols by external consumers|
144
144
  |**symbol**|Symbol pressure, consumers, dependencies, affected|
145
145
 
146
- Sections are ordered **high → medium → low**. `[high]` cycles, unreferenced, dead exports, dead files, stale types; `[medium]` same-file-only, change surface (file); `[low]` test-only consumers (noisy on Python), coupling, bottlenecks, hotspots.
146
+ Sections are ordered **high → medium → low**. `[high]` cycles, dead exports, dead files, stale types (unreferenced is skipped when dead exports is on — same survivors; file-target unreferenced-in-file is skipped when dead-in-file is on); `[medium]` same-file-only, change surface (file); `[low]` test-only consumers (noisy on Python), coupling, bottlenecks, hotspots.
147
147
 
148
- `--limit` caps **result rows across the whole run** (default 20); remaining checks are skipped once the cap is reached. `(none)` sections and section headers do not count.
148
+ `--limit` caps **result rows across the whole run** (default 20); remaining checks are skipped once the cap is reached. `(none)` sections, headers, and `… truncated` / `[note]` lines do not count as findings. `--per-check-limit N` additionally caps each section (default: unlimited) so one noisy check cannot spend the whole budget. Agents: `analyze --limit 40 --per-check-limit 5`.
149
+
150
+ Directory `analyze some/dir` spends the **same global `--limit`** on per-file sections after dashboards (max 30 files). Prefer project/dir dashboards, then `analyze` one file.
149
151
 
150
152
  `--priority high` or `--priority high,medium` (also `1`/`2`/`3`) skips lower tiers.
151
153
 
@@ -153,9 +155,7 @@ Sections are ordered **high → medium → low**. `[high]` cycles, unreferenced,
153
155
 
154
156
  Directory detection uses the filesystem when present, otherwise an indexed path prefix. `--path` narrows ambiguous file/symbol resolution only (not directory scope — pass the dir as `target`).
155
157
 
156
- **Dogfood loop:** `reindex` → `analyze --limit 25` → `analyze scip_cli` or `analyze scip_cli/queries.py` on suspects. Skips test paths in project-wide and directory runs (`tests/`, `*.test.*`, `*.spec.*`); `--include-tests` to include them. File-target analyze always includes that file.
157
-
158
- **Easy pickings:** **Cycles**, **dead exports**, and **dead files** (production paths) — cross-file cleanup. **Stale types** — types with no external refs in the index. Ignore `analyze/*` section helpers in dead exports. “Dead” = no refs from _other_ files in the index (empty `rdeps` for files), not `vulture`. Sections with hits print a short false-positive caveat; `export const` arrows often look like dead files because SCIP indexes the module only.
158
+ **Easy pickings:** **Cycles**, **dead exports**, and **dead files** (production paths) — cross-file cleanup. **Stale types** — types with no external refs in the index. Ignore `analyze/*` section helpers in dead exports. “Dead” = no refs from _other_ files in the index, not `vulture`. Empty `scip-cli rdeps` on a hit is usually a SCIP miss (`require()`, named imports, barrels), not proof unused. Unreferenced is omitted when dead exports is on (same survivors). Sections with hits print one `Warn:`; for each row run `rg -F STEM` where STEM is the **file basename inside `(...)`**, no extension — ignore hits in that same file. Do not `rg` the symbol name.
159
159
 
160
160
  ### reindex
161
161
 
@@ -1,3 +1,3 @@
1
1
  """scip-cli: Fast code intelligence via SCIP indexes."""
2
2
 
3
- __version__ = "2.9.0"
3
+ __version__ = "2.10.0"
@@ -11,7 +11,13 @@ import sys
11
11
  from collections.abc import Callable
12
12
 
13
13
  from . import __version__
14
- from .cli_args import add_limit_argument, add_names_only_argument, add_path_argument, add_paths_only_argument
14
+ from .cli_args import (
15
+ add_limit_argument,
16
+ add_names_only_argument,
17
+ add_path_argument,
18
+ add_paths_only_argument,
19
+ positive_int,
20
+ )
15
21
  from .commands import analyze, code, deps, members, rdeps, refs, reindex, search, skill, symbols
16
22
  from .symbols import SymbolKind
17
23
 
@@ -131,6 +137,13 @@ def main() -> None:
131
137
  )
132
138
  add_path_argument(analyze_parser)
133
139
  add_limit_argument(analyze_parser, default=20, help_suffix="result rows total")
140
+ analyze_parser.add_argument(
141
+ "--per-check-limit",
142
+ type=positive_int(),
143
+ default=None,
144
+ metavar="N",
145
+ help="Max rows per check (default: unlimited). --limit remains the global row budget",
146
+ )
134
147
  analyze_parser.add_argument(
135
148
  "--include-tests",
136
149
  action="store_true",
@@ -0,0 +1,243 @@
1
+ """Shared SQL helpers and formatting for analyze checks."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from collections.abc import Callable, Iterable
7
+
8
+ from ..sql import debug_execute
9
+ from ..symbols import extract_leaf_name, is_module_symbol
10
+
11
+ DEFAULT_LIMIT = 20
12
+ # Cap rows scanned when post-filtering SQL (filters drop many SCIP false hits).
13
+ ANALYZE_MAX_SCAN_ROWS = 10_000
14
+
15
+
16
+ def collect_until_limit(
17
+ limit: int,
18
+ fetch_page: Callable[[int, int], list[object]],
19
+ accept: Callable[[object], str | None],
20
+ *,
21
+ page_size: int | None = None,
22
+ max_scan: int = ANALYZE_MAX_SCAN_ROWS,
23
+ ) -> list[str]:
24
+ """Fetch SQL pages until `limit` rows pass `accept`, or data is exhausted.
25
+
26
+ Use only when a check post-filters SQL rows (analyze_noise, live heuristics, …).
27
+ Checks that format every SQL row as-is should use a single ``LIMIT ?`` instead.
28
+ """
29
+ batch = page_size or max(limit, 50)
30
+ lines: list[str] = []
31
+ offset = 0
32
+ scanned = 0
33
+ while len(lines) < limit:
34
+ rows = fetch_page(batch, offset)
35
+ if not rows:
36
+ break
37
+ for row in rows:
38
+ scanned += 1
39
+ if scanned > max_scan:
40
+ return lines
41
+ line = accept(row)
42
+ if line is None:
43
+ continue
44
+ lines.append(line)
45
+ if len(lines) >= limit:
46
+ return lines
47
+ if len(rows) < batch:
48
+ break
49
+ offset += len(rows)
50
+ return lines
51
+
52
+
53
+ def collect_from_producer(
54
+ limit: int,
55
+ produce: Callable[[int], list[str]],
56
+ accept: Callable[[str], str | None],
57
+ *,
58
+ max_scan: int = ANALYZE_MAX_SCAN_ROWS,
59
+ ) -> list[str]:
60
+ """Fill ``limit`` rows from a capped producer that can return more candidates on retry.
61
+
62
+ Used when results are not SQL-paginated (e.g. graph cycle search). ``produce(cap)``
63
+ must return a deterministic prefix as ``cap`` grows; already-seen strings are skipped.
64
+ """
65
+ lines: list[str] = []
66
+ seen: set[str] = set()
67
+ scanned = 0
68
+ fetch_cap = limit
69
+ while len(lines) < limit and fetch_cap <= max_scan:
70
+ candidates = produce(fetch_cap)
71
+ if not candidates:
72
+ break
73
+ for candidate in candidates:
74
+ if candidate in seen:
75
+ continue
76
+ seen.add(candidate)
77
+ scanned += 1
78
+ if scanned > max_scan:
79
+ return lines
80
+ line = accept(candidate)
81
+ if line is None:
82
+ continue
83
+ lines.append(line)
84
+ if len(lines) >= limit:
85
+ return lines
86
+ if len(candidates) < fetch_cap:
87
+ break
88
+ fetch_cap += limit
89
+ return lines
90
+
91
+
92
+ def collect_from_iterable(
93
+ limit: int,
94
+ candidates: Iterable[str],
95
+ accept: Callable[[str], str | None],
96
+ *,
97
+ max_scan: int = ANALYZE_MAX_SCAN_ROWS,
98
+ ) -> list[str]:
99
+ """Filter an in-memory candidate list until ``limit`` rows pass ``accept``."""
100
+ lines: list[str] = []
101
+ scanned = 0
102
+ for candidate in candidates:
103
+ scanned += 1
104
+ if scanned > max_scan:
105
+ break
106
+ line = accept(candidate)
107
+ if line is None:
108
+ continue
109
+ lines.append(line)
110
+ if len(lines) >= limit:
111
+ break
112
+ return lines
113
+
114
+
115
+ # Definition document for a symbol (our trimmed schema uses defn_enclosing_ranges).
116
+ SYM_DEF_JOIN = """
117
+ JOIN defn_enclosing_ranges sym_def ON sym_def.symbol_id = gs.id
118
+ JOIN documents def_d ON sym_def.document_id = def_d.id
119
+ """
120
+
121
+
122
+ def fetch_all(db, sql: str, params=()):
123
+ return debug_execute(db, sql, params).fetchall()
124
+
125
+
126
+ def fetch_one(db, sql: str, params=()):
127
+ return debug_execute(db, sql, params).fetchone()
128
+
129
+
130
+ def short_name(symbol: str) -> str:
131
+ if is_module_symbol(symbol):
132
+ return "(module)"
133
+ leaf = extract_leaf_name(symbol)
134
+ if leaf:
135
+ return leaf
136
+ if symbol.endswith("/"):
137
+ return "(module)"
138
+ return symbol.split("/")[-1][:60]
139
+
140
+
141
+ def is_test_path(relative_path: str) -> bool:
142
+ """True for common test layout paths and test-like filenames."""
143
+ p = relative_path.replace("\\", "/")
144
+ lower = p.lower()
145
+ if lower.startswith(("tests/", "test/")):
146
+ return True
147
+ if "/tests/" in lower or "/test/" in lower or "/__tests__/" in lower:
148
+ return True
149
+ if "/__fixtures__/" in lower or "/__mocks__/" in lower:
150
+ return True
151
+ name = lower.rsplit("/", 1)[-1]
152
+ if ".test." in name or ".spec." in name or ".mocha." in name:
153
+ return True
154
+ stem = name.rsplit(".", 1)[0]
155
+ if stem.endswith("_test") or stem.endswith("_spec"):
156
+ return True
157
+ if "test-fixture" in name or "test_fixture" in name or "test-mocks" in name:
158
+ return True
159
+ if name.startswith("test_") and name.endswith(".py"):
160
+ return True
161
+ return name == "conftest.py"
162
+
163
+
164
+ def is_dynamic_loader_path(relative_path: str) -> bool:
165
+ """True for migration files typically loaded by filename; SCIP has no import edge."""
166
+ p = relative_path.replace("\\", "/").lower()
167
+ return "/migrations/" in p
168
+
169
+
170
+ def is_python_main_entrypoint(relative_path: str, symbol: str) -> bool:
171
+ """True for main() in __main__.py — argparse entrypoints SCIP does not link to callers."""
172
+ if short_name(symbol) != "main":
173
+ return False
174
+ return relative_path.replace("\\", "/").endswith("__main__.py")
175
+
176
+
177
+ def analyze_noise(relative_path: str, symbol: str, *, include_tests: bool = False) -> bool:
178
+ """True for rows that clutter analyze dashboards (test paths, module-private helpers)."""
179
+ if not include_tests and is_test_path(relative_path):
180
+ return True
181
+ if short_name(symbol).startswith("_"):
182
+ return True
183
+ if is_python_main_entrypoint(relative_path, symbol):
184
+ return True
185
+ if is_dynamic_loader_path(relative_path):
186
+ return True
187
+ if short_name(symbol) in {"<constructor>", "constructor"}:
188
+ return True
189
+ return is_analyze_dashboard_export(relative_path, symbol)
190
+
191
+
192
+ _ANALYZE_DASHBOARD_SUFFIXES = (
193
+ "analyze/project.py",
194
+ "analyze/file.py",
195
+ "analyze/symbol.py",
196
+ )
197
+
198
+
199
+ def is_analyze_dashboard_export(relative_path: str, symbol: str) -> bool:
200
+ """Section runner functions in analyze/* (same-file only, look like dead exports)."""
201
+ path = relative_path.replace("\\", "/")
202
+ if not any(path.endswith(suffix) for suffix in _ANALYZE_DASHBOARD_SUFFIXES):
203
+ return False
204
+ name = short_name(symbol)
205
+ return "()." in symbol or name.endswith(")")
206
+
207
+
208
+ def is_component_props_type(symbol: str) -> bool:
209
+ """React-style Props interfaces — rarely referenced outside the component file."""
210
+ name = short_name(symbol)
211
+ return bool(name and name.endswith("Props") and "#" in symbol.split("/")[-1])
212
+
213
+
214
+ def stale_type_noise(relative_path: str, symbol: str, consumers: int) -> bool:
215
+ """Dataclass-style types with no SCIP consumers (typing-only)."""
216
+ if is_component_props_type(symbol):
217
+ return True
218
+ if consumers > 0:
219
+ return False
220
+ name = short_name(symbol)
221
+ if not name or name == "(module)" or not name[0].isupper():
222
+ return False
223
+ path = relative_path.replace("\\", "/")
224
+ return path.endswith(("config.py", "scope.py", "analyze/targets.py"))
225
+
226
+
227
+ def file_pair_noise(file1: str, file2: str, *, include_tests: bool = False) -> bool:
228
+ if include_tests:
229
+ return False
230
+ return is_test_path(file1) or is_test_path(file2)
231
+
232
+
233
+ def cycle_path_noise(cycle_line: str, *, include_tests: bool = False) -> bool:
234
+ if include_tests:
235
+ return False
236
+ parts = re.split(r"\s<->\s|\s->\s", cycle_line)
237
+ return any(is_test_path(part.strip()) for part in parts if part.strip())
238
+
239
+
240
+ def section(title: str, lines: list[str], *, preface: str | None = None) -> tuple[str, list[str], str | None]:
241
+ if not lines:
242
+ return title, ["(none)"], None
243
+ return title, lines, preface
@@ -2,8 +2,8 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from .common import DEFAULT_LIMIT, SYM_DEF_JOIN, analyze_noise, fetch_all, short_name
6
- from .live import LiveIndex, file_has_scip_importers, has_same_file_reference_usage
5
+ from .common import DEFAULT_LIMIT, SYM_DEF_JOIN, analyze_noise, collect_until_limit, fetch_all, short_name
6
+ from .live import file_has_scip_importers, has_same_file_reference_usage, live_for
7
7
  from .sections import FALSE_POSITIVE_PREFACES, Check, Priority, run_checks
8
8
  from .symbol import symbol_pressure
9
9
 
@@ -83,10 +83,8 @@ def file_consumers(db, relative_path: str, limit: int = DEFAULT_LIMIT) -> list[s
83
83
 
84
84
 
85
85
  def unreferenced_in_file(db, relative_path: str, limit: int = DEFAULT_LIMIT) -> list[str]:
86
- live = LiveIndex(db)
87
- rows = fetch_all(
88
- db,
89
- """
86
+ live = live_for(db)
87
+ sql = """
90
88
  SELECT gs.symbol, der.start_line, der.end_line, def_d.id
91
89
  FROM global_symbols gs
92
90
  JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
@@ -102,25 +100,25 @@ def unreferenced_in_file(db, relative_path: str, limit: int = DEFAULT_LIMIT) ->
102
100
  WHERE m.symbol_id = gs.id AND m.role != 1 AND c.document_id != def_d.id
103
101
  )
104
102
  ORDER BY der.start_line
105
- LIMIT ?
106
- """,
107
- (relative_path, limit),
108
- )
109
- lines = []
110
- for symbol, start, end, def_doc_id in rows:
103
+ """
104
+
105
+ def fetch_page(page_size: int, offset: int):
106
+ return fetch_all(db, sql + " LIMIT ? OFFSET ?", (relative_path, page_size, offset))
107
+
108
+ def accept(row):
109
+ symbol, start, end, def_doc_id = row
111
110
  if analyze_noise(relative_path, symbol, include_tests=True):
112
- continue
111
+ return None
113
112
  if live.dead_export_noise(symbol, def_doc_id):
114
- continue
115
- lines.append(f"{short_name(symbol)} {start + 1}:{end + 1}")
116
- return lines
113
+ return None
114
+ return f"{short_name(symbol)} {start + 1}:{end + 1}"
115
+
116
+ return collect_until_limit(limit, fetch_page, accept)
117
117
 
118
118
 
119
119
  def same_file_only_in_file(db, relative_path: str, limit: int = DEFAULT_LIMIT) -> list[str]:
120
- live = LiveIndex(db)
121
- rows = fetch_all(
122
- db,
123
- """
120
+ live = live_for(db)
121
+ sql = """
124
122
  SELECT gs.symbol, der.start_line, der.end_line, def_d.id
125
123
  FROM global_symbols gs
126
124
  JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
@@ -137,27 +135,27 @@ def same_file_only_in_file(db, relative_path: str, limit: int = DEFAULT_LIMIT) -
137
135
  WHERE m.symbol_id = gs.id AND m.role = 0 AND c.document_id != def_d.id
138
136
  )
139
137
  ORDER BY der.start_line
140
- LIMIT ?
141
- """,
142
- (relative_path, limit),
143
- )
144
- lines = []
145
- for symbol, start, end, def_doc_id in rows:
138
+ """
139
+
140
+ def fetch_page(page_size: int, offset: int):
141
+ return fetch_all(db, sql + " LIMIT ? OFFSET ?", (relative_path, page_size, offset))
142
+
143
+ def accept(row):
144
+ symbol, start, end, def_doc_id = row
146
145
  if analyze_noise(relative_path, symbol, include_tests=True):
147
- continue
146
+ return None
148
147
  if live.same_file_export_noise(symbol, def_doc_id):
149
- continue
148
+ return None
150
149
  if not file_has_scip_importers(db, relative_path, live=live, def_doc_id=def_doc_id):
151
- continue
152
- lines.append(f"{short_name(symbol)} {start + 1}:{end + 1}")
153
- return lines
150
+ return None
151
+ return f"{short_name(symbol)} {start + 1}:{end + 1}"
152
+
153
+ return collect_until_limit(limit, fetch_page, accept)
154
154
 
155
155
 
156
156
  def dead_in_file(db, relative_path: str, limit: int = DEFAULT_LIMIT) -> list[str]:
157
- live = LiveIndex(db)
158
- rows = fetch_all(
159
- db,
160
- """
157
+ live = live_for(db)
158
+ sql = """
161
159
  SELECT gs.id, gs.symbol, der.start_line, der.end_line, def_d.id
162
160
  FROM global_symbols gs
163
161
  JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
@@ -172,20 +170,22 @@ def dead_in_file(db, relative_path: str, limit: int = DEFAULT_LIMIT) -> list[str
172
170
  AND c.document_id != def_d.id
173
171
  )
174
172
  ORDER BY der.start_line
175
- LIMIT ?
176
- """,
177
- (relative_path, limit),
178
- )
179
- lines = []
180
- for sym_id, symbol, start, end, def_doc_id in rows:
173
+ """
174
+
175
+ def fetch_page(page_size: int, offset: int):
176
+ return fetch_all(db, sql + " LIMIT ? OFFSET ?", (relative_path, page_size, offset))
177
+
178
+ def accept(row):
179
+ sym_id, symbol, start, end, def_doc_id = row
181
180
  if analyze_noise(relative_path, symbol, include_tests=True):
182
- continue
181
+ return None
183
182
  if has_same_file_reference_usage(db, sym_id, def_doc_id):
184
- continue
183
+ return None
185
184
  if live.dead_export_noise(symbol, def_doc_id):
186
- continue
187
- lines.append(f"{short_name(symbol)} {start + 1}:{end + 1}")
188
- return lines
185
+ return None
186
+ return f"{short_name(symbol)} {start + 1}:{end + 1}"
187
+
188
+ return collect_until_limit(limit, fetch_page, accept)
189
189
 
190
190
 
191
191
  def imports_summary(db, relative_path: str, limit: int = DEFAULT_LIMIT) -> list[str]:
@@ -342,8 +342,11 @@ def _run_file_checks(
342
342
  *,
343
343
  budget=None,
344
344
  check_keys=None,
345
+ per_check_limit=None,
345
346
  ) -> list[tuple[str, list[str], str | None]]:
346
- return run_checks(checks, db, limit, priorities, budget=budget, check_keys=check_keys)
347
+ return run_checks(
348
+ checks, db, limit, priorities, budget=budget, check_keys=check_keys, per_check_limit=per_check_limit
349
+ )
347
350
 
348
351
 
349
352
  def run_all(
@@ -353,6 +356,7 @@ def run_all(
353
356
  priorities=None,
354
357
  budget=None,
355
358
  check_keys=None,
359
+ per_check_limit=None,
356
360
  ) -> list[tuple[str, list[str], str | None]]:
357
361
  return _run_file_checks(
358
362
  _file_checks(relative_path, include_top_symbols=True),
@@ -361,6 +365,7 @@ def run_all(
361
365
  priorities,
362
366
  budget=budget,
363
367
  check_keys=check_keys,
368
+ per_check_limit=per_check_limit,
364
369
  )
365
370
 
366
371
 
@@ -371,6 +376,7 @@ def run_all_sections_only(
371
376
  priorities=None,
372
377
  budget=None,
373
378
  check_keys=None,
379
+ per_check_limit=None,
374
380
  ) -> list[tuple[str, list[str], str | None]]:
375
381
  """Per-file sections without top-symbols (directory batch)."""
376
382
  return _run_file_checks(
@@ -380,4 +386,5 @@ def run_all_sections_only(
380
386
  priorities,
381
387
  budget=budget,
382
388
  check_keys=check_keys,
389
+ per_check_limit=per_check_limit,
383
390
  )