scip-cli 2.6.0__tar.gz → 2.7.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 (149) hide show
  1. {scip_cli-2.6.0 → scip_cli-2.7.0}/PKG-INFO +9 -7
  2. {scip_cli-2.6.0 → scip_cli-2.7.0}/README.md +8 -6
  3. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/SKILL.md +5 -3
  4. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/__init__.py +1 -1
  5. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/__main__.py +6 -0
  6. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/analyze/file.py +8 -2
  7. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/analyze/project.py +42 -0
  8. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/analyze/sections.py +72 -7
  9. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/analyze/symbol.py +2 -1
  10. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/commands/analyze.py +24 -5
  11. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/test_analyze.py +61 -3
  12. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/test_e2e.py +17 -0
  13. {scip_cli-2.6.0 → scip_cli-2.7.0}/.agents/commands/migration-problems.md +0 -0
  14. {scip_cli-2.6.0 → scip_cli-2.7.0}/.agents/rules/agent.mdc +0 -0
  15. {scip_cli-2.6.0 → scip_cli-2.7.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  16. {scip_cli-2.6.0 → scip_cli-2.7.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  17. {scip_cli-2.6.0 → scip_cli-2.7.0}/.github/ISSUE_TEMPLATE/question.md +0 -0
  18. {scip_cli-2.6.0 → scip_cli-2.7.0}/.github/pull_request_template.md +0 -0
  19. {scip_cli-2.6.0 → scip_cli-2.7.0}/.github/workflows/ci.yml +0 -0
  20. {scip_cli-2.6.0 → scip_cli-2.7.0}/.gitignore +0 -0
  21. {scip_cli-2.6.0 → scip_cli-2.7.0}/.pre-commit-config.yaml +0 -0
  22. {scip_cli-2.6.0 → scip_cli-2.7.0}/AGENTS.md +0 -0
  23. {scip_cli-2.6.0 → scip_cli-2.7.0}/CODE_OF_CONDUCT.md +0 -0
  24. {scip_cli-2.6.0 → scip_cli-2.7.0}/CONTRIBUTING.md +0 -0
  25. {scip_cli-2.6.0 → scip_cli-2.7.0}/LICENSE +0 -0
  26. {scip_cli-2.6.0 → scip_cli-2.7.0}/SECURITY.md +0 -0
  27. {scip_cli-2.6.0 → scip_cli-2.7.0}/docs/port.md +0 -0
  28. {scip_cli-2.6.0 → scip_cli-2.7.0}/docs/ports/conclusions.md +0 -0
  29. {scip_cli-2.6.0 → scip_cli-2.7.0}/docs/ports/experiment-summary.md +0 -0
  30. {scip_cli-2.6.0 → scip_cli-2.7.0}/docs/ports/go-experiment.md +0 -0
  31. {scip_cli-2.6.0 → scip_cli-2.7.0}/docs/ports/go.md +0 -0
  32. {scip_cli-2.6.0 → scip_cli-2.7.0}/docs/ports/plan.md +0 -0
  33. {scip_cli-2.6.0 → scip_cli-2.7.0}/docs/ports/rust-experiment.md +0 -0
  34. {scip_cli-2.6.0 → scip_cli-2.7.0}/docs/ports/rust.md +0 -0
  35. {scip_cli-2.6.0 → scip_cli-2.7.0}/docs/ports/summary.md +0 -0
  36. {scip_cli-2.6.0 → scip_cli-2.7.0}/docs/ports/zig-experiment.md +0 -0
  37. {scip_cli-2.6.0 → scip_cli-2.7.0}/docs/ports/zig.md +0 -0
  38. {scip_cli-2.6.0 → scip_cli-2.7.0}/pyproject.toml +0 -0
  39. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/analyze/__init__.py +0 -0
  40. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/analyze/common.py +0 -0
  41. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/analyze/graph.py +0 -0
  42. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/analyze/live.py +0 -0
  43. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/analyze/targets.py +0 -0
  44. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/cache.py +0 -0
  45. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/cli_args.py +0 -0
  46. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/commands/__init__.py +0 -0
  47. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/commands/code.py +0 -0
  48. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/commands/deps.py +0 -0
  49. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/commands/members.py +0 -0
  50. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/commands/rdeps.py +0 -0
  51. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/commands/refs.py +0 -0
  52. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/commands/reindex.py +0 -0
  53. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/commands/search.py +0 -0
  54. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/commands/skill.py +0 -0
  55. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/commands/symbols.py +0 -0
  56. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/config.py +0 -0
  57. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/debug.py +0 -0
  58. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/discover.py +0 -0
  59. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/indexing/__init__.py +0 -0
  60. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/indexing/constants.py +0 -0
  61. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/indexing/convert.py +0 -0
  62. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/indexing/core.py +0 -0
  63. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/indexing/languages.py +0 -0
  64. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/indexing/orchestrate.py +0 -0
  65. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/indexing/postprocess.py +0 -0
  66. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/indexing/runners.py +0 -0
  67. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/indexing/typescript.py +0 -0
  68. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/merge.py +0 -0
  69. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/output.py +0 -0
  70. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/paths.py +0 -0
  71. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/project.py +0 -0
  72. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/queries.py +0 -0
  73. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/scip_tool.py +0 -0
  74. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/scope.py +0 -0
  75. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/session.py +0 -0
  76. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/source.py +0 -0
  77. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/sql.py +0 -0
  78. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/symbols.py +0 -0
  79. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/targets.py +0 -0
  80. {scip_cli-2.6.0 → scip_cli-2.7.0}/scip_cli/tsconfig.py +0 -0
  81. {scip_cli-2.6.0 → scip_cli-2.7.0}/scripts/bench.sh +0 -0
  82. {scip_cli-2.6.0 → scip_cli-2.7.0}/scripts/bench_postprocess.py +0 -0
  83. {scip_cli-2.6.0 → scip_cli-2.7.0}/scripts/build.sh +0 -0
  84. {scip_cli-2.6.0 → scip_cli-2.7.0}/scripts/publish.sh +0 -0
  85. {scip_cli-2.6.0 → scip_cli-2.7.0}/scripts/test.sh +0 -0
  86. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/__init__.py +0 -0
  87. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/analyze_db.py +0 -0
  88. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/bench_db.py +0 -0
  89. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/conftest.py +0 -0
  90. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/e2e_harness.py +0 -0
  91. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixture_catalog.py +0 -0
  92. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/package.json +0 -0
  93. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/app/handler.ts +0 -0
  94. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/config.ts +0 -0
  95. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/consumer.ts +0 -0
  96. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/dead.ts +0 -0
  97. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/domain/i18n/en.ts +0 -0
  98. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/domain/i18n/index.ts +0 -0
  99. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/domain/i18n/useLocale.ts +0 -0
  100. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/domain/labels/index.ts +0 -0
  101. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/events/streamTypes.ts +0 -0
  102. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/gateway/index.ts +0 -0
  103. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/helper.ts +0 -0
  104. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/hooks/useHookA.ts +0 -0
  105. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/hooks/useHookB.ts +0 -0
  106. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/hooks/useItems.ts +0 -0
  107. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/index.ts +0 -0
  108. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/integrations/inferenceClient.ts +0 -0
  109. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/lib/handler.ts +0 -0
  110. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/loaders/useInference.ts +0 -0
  111. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/models/MutationAggregate.ts +0 -0
  112. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/pages/panelPage.ts +0 -0
  113. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/rules/applyRule.ts +0 -0
  114. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/types/a.ts +0 -0
  115. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/types/b.ts +0 -0
  116. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/ui/Button.ts +0 -0
  117. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/ui/LazyPanel.ts +0 -0
  118. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/ui/buttonConsumer.ts +0 -0
  119. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/ui/menuModule.ts +0 -0
  120. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/user.ts +0 -0
  121. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/widget.ts +0 -0
  122. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/src/widgets/OrphanWidget.ts +0 -0
  123. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/fixtures/typescript-project/tsconfig.json +0 -0
  124. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/perf_util.py +0 -0
  125. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/test_analyze_graph.py +0 -0
  126. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/test_analyze_perf.py +0 -0
  127. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/test_bench_queries.py +0 -0
  128. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/test_cache.py +0 -0
  129. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/test_composability.py +0 -0
  130. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/test_config.py +0 -0
  131. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/test_discover.py +0 -0
  132. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/test_e2e_analyze_patterns.py +0 -0
  133. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/test_e2e_perf.py +0 -0
  134. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/test_index_batching.py +0 -0
  135. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/test_index_prune.py +0 -0
  136. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/test_indexer_env.py +0 -0
  137. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/test_indexer_fallback.py +0 -0
  138. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/test_merge.py +0 -0
  139. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/test_multi_symbol.py +0 -0
  140. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/test_pure_functions.py +0 -0
  141. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/test_python_index_integration.py +0 -0
  142. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/test_qualified_symbols.py +0 -0
  143. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/test_reindex.py +0 -0
  144. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/test_scip_tool.py +0 -0
  145. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/test_scope.py +0 -0
  146. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/test_symbols_freq.py +0 -0
  147. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/test_targets.py +0 -0
  148. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/test_tsconfig.py +0 -0
  149. {scip_cli-2.6.0 → scip_cli-2.7.0}/tests/test_typescript_projects.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scip-cli
3
- Version: 2.6.0
3
+ Version: 2.7.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`, `--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`, `--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
 
@@ -280,15 +280,17 @@ Use `analyze` on the repo itself before broad refactors or agent review — it s
280
280
  ```bash
281
281
  scip-cli analyze --limit 25
282
282
  scip-cli analyze --priority high --limit 25 # dead exports & cycles only
283
+ scip-cli analyze --check cycles --limit 25 # one named section
284
+ scip-cli analyze --check dead_files --limit 25
283
285
  ```
284
286
 
285
287
  Sections are tagged `[high]`, `[medium]`, `[low]` and listed in that order.
286
288
 
287
- | Tier | Project sections | Action |
288
- | ---------- | ---------------------------------------------------- | -------------------------------------------------------------------- |
289
- | **high** | Cycles, unreferenced, dead exports, stale types | Nuke or fix cycles; delete unused; `_` prefix |
290
- | **medium** | Same-file only, change surface (file target) | Module-private by usage |
291
- | **low** | Test-only consumers, coupling, bottlenecks, hotspots | Noisy on Python (index omits many same-file calls); verify with `rg` |
289
+ | Tier | Project sections | Action |
290
+ | ---------- | ----------------------------------------------------------- | -------------------------------------------------------------------- |
291
+ | **high** | Cycles, unreferenced, dead exports, dead files, stale types | Nuke or fix cycles; delete unused; `_` prefix |
292
+ | **medium** | Same-file only, change surface (file target) | Module-private by usage |
293
+ | **low** | Test-only consumers, coupling, bottlenecks, hotspots | Noisy on Python (index omits many same-file calls); verify with `rg` |
292
294
 
293
295
  Use `--priority high` for a quick gate; `--priority high,medium` adds context. File drill-down adds change surface and unused imports.
294
296
 
@@ -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`, `--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`, `--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
 
@@ -259,15 +259,17 @@ Use `analyze` on the repo itself before broad refactors or agent review — it s
259
259
  ```bash
260
260
  scip-cli analyze --limit 25
261
261
  scip-cli analyze --priority high --limit 25 # dead exports & cycles only
262
+ scip-cli analyze --check cycles --limit 25 # one named section
263
+ scip-cli analyze --check dead_files --limit 25
262
264
  ```
263
265
 
264
266
  Sections are tagged `[high]`, `[medium]`, `[low]` and listed in that order.
265
267
 
266
- | Tier | Project sections | Action |
267
- | ---------- | ---------------------------------------------------- | -------------------------------------------------------------------- |
268
- | **high** | Cycles, unreferenced, dead exports, stale types | Nuke or fix cycles; delete unused; `_` prefix |
269
- | **medium** | Same-file only, change surface (file target) | Module-private by usage |
270
- | **low** | Test-only consumers, coupling, bottlenecks, hotspots | Noisy on Python (index omits many same-file calls); verify with `rg` |
268
+ | Tier | Project sections | Action |
269
+ | ---------- | ----------------------------------------------------------- | -------------------------------------------------------------------- |
270
+ | **high** | Cycles, unreferenced, dead exports, dead files, stale types | Nuke or fix cycles; delete unused; `_` prefix |
271
+ | **medium** | Same-file only, change surface (file target) | Module-private by usage |
272
+ | **low** | Test-only consumers, coupling, bottlenecks, hotspots | Noisy on Python (index omits many same-file calls); verify with `rg` |
271
273
 
272
274
  Use `--priority high` for a quick gate; `--priority high,medium` adds context. File drill-down adds change surface and unused imports.
273
275
 
@@ -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] [target]
136
+ analyze [--limit N] [--path PATH] [--include-tests] [--priority LEVEL] [--check NAME] [target]
137
137
  ```
138
138
 
139
139
  | Target | Output |
@@ -143,17 +143,19 @@ analyze [--limit N] [--path PATH] [--include-tests] [target]
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, 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, unreferenced, dead exports, dead files, stale types; `[medium]` same-file-only, change surface (file); `[low]` test-only consumers (noisy on Python), coupling, bottlenecks, hotspots.
147
147
 
148
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.
149
149
 
150
150
  `--priority high` or `--priority high,medium` (also `1`/`2`/`3`) skips lower tiers.
151
151
 
152
+ `--check NAME` (repeatable or comma-separated) runs only those sections and ANDs with `--priority`. Unknown names error. A name that exists for another target (e.g. `unused_imports` on a project-wide run) yields no matching sections.
153
+
152
154
  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`).
153
155
 
154
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.
155
157
 
156
- **Easy pickings:** **Cycles** and **dead exports** (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`.
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.
157
159
 
158
160
  ### reindex
159
161
 
@@ -1,3 +1,3 @@
1
1
  """scip-cli: Fast code intelligence via SCIP indexes."""
2
2
 
3
- __version__ = "2.6.0"
3
+ __version__ = "2.7.0"
@@ -141,6 +141,12 @@ def main() -> None:
141
141
  metavar="LEVEL",
142
142
  help="Comma-separated check tiers: high, medium, low (or 1/2/3). Default: all. E.g. high or high,medium",
143
143
  )
144
+ analyze_parser.add_argument(
145
+ "--check",
146
+ action="append",
147
+ metavar="NAME",
148
+ help="Run only these checks (repeatable or comma-separated). ANDs with --priority",
149
+ )
144
150
 
145
151
  # reindex
146
152
  reindex_parser = subparsers.add_parser("reindex", help="Force re-indexing of the current project")
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from .common import DEFAULT_LIMIT, SYM_DEF_JOIN, analyze_noise, fetch_all, short_name
6
6
  from .live import LiveIndex, file_has_scip_importers, has_same_file_reference_usage
7
- from .sections import Check, Priority, run_checks
7
+ from .sections import FALSE_POSITIVE_PREFACES, Check, Priority, run_checks
8
8
  from .symbol import symbol_pressure
9
9
 
10
10
 
@@ -302,6 +302,7 @@ def _file_checks(relative_path: str, *, include_top_symbols: bool) -> list[Check
302
302
  Priority.HIGH,
303
303
  f"Unreferenced in file {title}",
304
304
  _bind_path(unreferenced_in_file, relative_path),
305
+ false_positive_preface=FALSE_POSITIVE_PREFACES["unreferenced_in_file"],
305
306
  ),
306
307
  Check(
307
308
  "dead_in_file",
@@ -340,8 +341,9 @@ def _run_file_checks(
340
341
  priorities,
341
342
  *,
342
343
  budget=None,
344
+ check_keys=None,
343
345
  ) -> list[tuple[str, list[str], str | None]]:
344
- return run_checks(checks, db, limit, priorities, budget=budget)
346
+ return run_checks(checks, db, limit, priorities, budget=budget, check_keys=check_keys)
345
347
 
346
348
 
347
349
  def run_all(
@@ -350,6 +352,7 @@ def run_all(
350
352
  limit: int = DEFAULT_LIMIT,
351
353
  priorities=None,
352
354
  budget=None,
355
+ check_keys=None,
353
356
  ) -> list[tuple[str, list[str], str | None]]:
354
357
  return _run_file_checks(
355
358
  _file_checks(relative_path, include_top_symbols=True),
@@ -357,6 +360,7 @@ def run_all(
357
360
  limit,
358
361
  priorities,
359
362
  budget=budget,
363
+ check_keys=check_keys,
360
364
  )
361
365
 
362
366
 
@@ -366,6 +370,7 @@ def run_all_sections_only(
366
370
  limit: int = DEFAULT_LIMIT,
367
371
  priorities=None,
368
372
  budget=None,
373
+ check_keys=None,
369
374
  ) -> list[tuple[str, list[str], str | None]]:
370
375
  """Per-file sections without top-symbols (directory batch)."""
371
376
  return _run_file_checks(
@@ -374,4 +379,5 @@ def run_all_sections_only(
374
379
  limit,
375
380
  priorities,
376
381
  budget=budget,
382
+ check_keys=check_keys,
377
383
  )
@@ -13,6 +13,7 @@ from .common import (
13
13
  cycle_path_noise,
14
14
  fetch_all,
15
15
  file_pair_noise,
16
+ is_generated_analyze_path,
16
17
  is_test_path,
17
18
  short_name,
18
19
  stale_type_noise,
@@ -379,6 +380,44 @@ def dead_exports(
379
380
  return _format_dead_export_rows(db, rows, LiveIndex(db), include_tests=include_tests, limit=limit)
380
381
 
381
382
 
383
+ def dead_files(
384
+ db,
385
+ limit: int = DEFAULT_LIMIT,
386
+ *,
387
+ include_tests: bool = False,
388
+ scope: str | None = None,
389
+ ) -> list[str]:
390
+ """Files with no inbound mentions from other documents (empty rdeps)."""
391
+ scope_clause, scope_params = path_filter_sql(db, scope, doc_alias="d")
392
+ rows = fetch_all(
393
+ db,
394
+ f"""
395
+ SELECT d.relative_path
396
+ FROM documents d
397
+ WHERE NOT EXISTS (
398
+ SELECT 1
399
+ FROM defn_enclosing_ranges der
400
+ JOIN mentions m ON m.symbol_id = der.symbol_id AND m.role != 1
401
+ JOIN chunks c ON m.chunk_id = c.id
402
+ WHERE der.document_id = d.id AND c.document_id != d.id
403
+ ){scope_clause}
404
+ ORDER BY d.relative_path
405
+ LIMIT ?
406
+ """,
407
+ (*scope_params, limit * 5),
408
+ )
409
+ lines: list[str] = []
410
+ for (path,) in rows:
411
+ if not include_tests and is_test_path(path):
412
+ continue
413
+ if is_generated_analyze_path(path):
414
+ continue
415
+ lines.append(path)
416
+ if len(lines) >= limit:
417
+ break
418
+ return lines
419
+
420
+
382
421
  def top_coupling(
383
422
  db,
384
423
  limit: int = DEFAULT_LIMIT,
@@ -421,6 +460,7 @@ def run_all(
421
460
  scope: str | None = None,
422
461
  priorities=None,
423
462
  budget=None,
463
+ check_keys=None,
424
464
  ) -> list[tuple[str, list[str], str | None]]:
425
465
  suffix = _scope_suffix(scope)
426
466
  checks = [
@@ -432,6 +472,7 @@ def run_all(
432
472
  f"Dead exports (no in-file or external use){suffix}",
433
473
  dead_exports,
434
474
  ),
475
+ Check("dead_files", Priority.HIGH, f"Dead files (no importers){suffix}", dead_files),
435
476
  Check("stale_types", Priority.HIGH, f"Stale types (no external consumers){suffix}", stale_types),
436
477
  Check("same_file_only", Priority.MEDIUM, f"Same-file only (in-file use, not exported){suffix}", same_file_only),
437
478
  Check(
@@ -452,4 +493,5 @@ def run_all(
452
493
  include_tests=include_tests,
453
494
  scope=scope,
454
495
  budget=budget,
496
+ check_keys=check_keys,
455
497
  )
@@ -48,6 +48,54 @@ def parse_priorities(value: str | None) -> set[Priority] | None:
48
48
  return out
49
49
 
50
50
 
51
+ # Project, file, and symbol Check.key values. --check rejects anything else.
52
+ CHECK_KEYS = frozenset(
53
+ {
54
+ "affected",
55
+ "bottlenecks",
56
+ "change_surface",
57
+ "consumer_files",
58
+ "coupling",
59
+ "cycles",
60
+ "dead_exports",
61
+ "dead_files",
62
+ "dead_in_file",
63
+ "def_context",
64
+ "dependencies",
65
+ "file_consumers",
66
+ "hotspots",
67
+ "imports_summary",
68
+ "same_file_only",
69
+ "stale_types",
70
+ "symbol_pressure",
71
+ "test_only",
72
+ "top_coupling",
73
+ "top_symbols",
74
+ "unreferenced",
75
+ "unused_imports",
76
+ }
77
+ )
78
+
79
+
80
+ def parse_checks(values: list[str] | None) -> set[str] | None:
81
+ """Parse --check (repeatable; commas allowed). None means all checks."""
82
+ if not values:
83
+ return None
84
+ out: set[str] = set()
85
+ for value in values:
86
+ for part in value.replace(" ", "").split(","):
87
+ if not part:
88
+ continue
89
+ key = part.lower()
90
+ if key not in CHECK_KEYS:
91
+ allowed = ", ".join(sorted(CHECK_KEYS))
92
+ raise RuntimeError(f"unknown analyze check {part!r} (use {allowed})")
93
+ out.add(key)
94
+ if not out:
95
+ return None
96
+ return out
97
+
98
+
51
99
  @dataclass(frozen=True)
52
100
  class Check:
53
101
  key: str
@@ -60,24 +108,38 @@ class Check:
60
108
  return f"[{self.priority.value}] {self.title}"
61
109
 
62
110
 
63
- # Shown only when the section has hits (not "(none)").
111
+ # Shown only when the section has hits (not "(none)"). Situation hints, not exhaustive.
64
112
  FALSE_POSITIVE_PREFACES: dict[str, str] = {
65
113
  "dead_exports": (
66
- "SCIP may miss dynamic loading (loadFiles, GraphQL) and default-export object members "
67
- "— verify with rdeps/rg before deleting."
114
+ "SCIP may miss dynamic loading (loadFiles, GraphQL), default-export object members, "
115
+ "and some export const arrows — verify with rdeps/rg before deleting."
116
+ ),
117
+ "dead_files": (
118
+ "Empty rdeps in the index. SCIP often records export const / arrow files as module-only, "
119
+ "so named imports (routes, barrels) may not count; same for dynamic require. Confirm with rg."
68
120
  ),
69
121
  "unreferenced": (
70
- "No mentions in the index — symbols may still run via dynamic import or side-effect registration."
122
+ "No mentions in the index — may still run via dynamic import, side-effect registration, "
123
+ "or a call SCIP did not record."
124
+ ),
125
+ "same_file_only": (
126
+ "Referenced only in the defining file — often handlers or private helpers, "
127
+ "or an external call SCIP missed; not necessarily a dead export."
128
+ ),
129
+ "stale_types": (
130
+ "No cross-file refs in the index — may still be used in-file, as a type-only import SCIP dropped, "
131
+ "or as a structural shape."
71
132
  ),
72
- "same_file_only": ("Referenced only in the defining file — often handlers or private helpers, not dead exports."),
73
- "stale_types": ("No cross-file refs in the index — may still be used in-file or as a type-only shape."),
74
133
  "cycles": "Remaining cycles may be barrel re-exports; confirm before refactoring.",
75
134
  "dead_in_file": (
76
- "SCIP may miss dynamic loading and default-export indirection verify with rdeps/rg before deleting."
135
+ "SCIP may miss dynamic loading, default-export indirection, and some export const arrows "
136
+ "— verify with rdeps/rg before deleting."
77
137
  ),
78
138
  "unreferenced_in_file": (
79
139
  "No mentions in the index — may still be used in-file via handlers or dynamic registration."
80
140
  ),
141
+ "unused_imports": ("Import may still be a type-only use or a name SCIP did not bind — confirm before removing."),
142
+ "test_only": "Index may miss same-file production calls, so this can look test-only when it is not.",
81
143
  }
82
144
 
83
145
 
@@ -104,9 +166,12 @@ def run_checks(
104
166
  include_tests: bool = False,
105
167
  scope: str | None = None,
106
168
  budget: RowBudget | None = None,
169
+ check_keys: set[str] | None = None,
107
170
  ) -> list[tuple[str, list[str], str | None]]:
108
171
  """Run checks in priority order (high → low), optionally filtered."""
109
172
  selected = [check for check in checks if priorities is None or check.priority in priorities]
173
+ if check_keys is not None:
174
+ selected = [check for check in selected if check.key in check_keys]
110
175
  selected.sort(key=lambda check: (_ORDER.index(check.priority), check.key))
111
176
  budget_obj: RowBudget = budget or RowBudget(remaining=limit)
112
177
  sections: list[tuple[str, list[str], str | None]] = []
@@ -169,6 +169,7 @@ def run_all(
169
169
  limit: int = DEFAULT_LIMIT,
170
170
  priorities=None,
171
171
  budget=None,
172
+ check_keys=None,
172
173
  ) -> list[tuple[str, list[str], str | None]]:
173
174
  checks = [
174
175
  Check("consumer_files", Priority.HIGH, "Consumer files (direct)", _bind_symbol(consumer_files, symbol_id)),
@@ -182,4 +183,4 @@ def run_all(
182
183
  ),
183
184
  Check("def_context", Priority.LOW, "Definition context", _bind_symbol0(def_context, symbol_id)),
184
185
  ]
185
- return run_checks(checks, db, limit, priorities, budget=budget)
186
+ return run_checks(checks, db, limit, priorities, budget=budget, check_keys=check_keys)
@@ -8,7 +8,7 @@ from ..analyze import file as file_checks
8
8
  from ..analyze import project as project_checks
9
9
  from ..analyze import symbol as symbol_checks
10
10
  from ..analyze.common import is_test_path, section
11
- from ..analyze.sections import RowBudget, parse_priorities
11
+ from ..analyze.sections import RowBudget, parse_checks, parse_priorities
12
12
  from ..analyze.targets import MAX_DIR_FILES, list_dir_files, resolve_analyze_target
13
13
  from ..cli_args import path_scope_from_args
14
14
  from ..session import resolve_one_symbol, setup
@@ -39,6 +39,7 @@ def _project_sections(
39
39
  scope: str | None,
40
40
  priorities,
41
41
  budget: RowBudget,
42
+ check_keys,
42
43
  ) -> list[tuple[str, list[str], str | None]]:
43
44
  return project_checks.run_all(
44
45
  db,
@@ -47,6 +48,7 @@ def _project_sections(
47
48
  scope=scope,
48
49
  priorities=priorities,
49
50
  budget=budget,
51
+ check_keys=check_keys,
50
52
  )
51
53
 
52
54
 
@@ -57,8 +59,9 @@ def _file_sections(
57
59
  limit: int,
58
60
  priorities,
59
61
  budget: RowBudget,
62
+ check_keys,
60
63
  ) -> list[tuple[str, list[str], str | None]]:
61
- return file_checks.run_all(db, path, limit=limit, priorities=priorities, budget=budget)
64
+ return file_checks.run_all(db, path, limit=limit, priorities=priorities, budget=budget, check_keys=check_keys)
62
65
 
63
66
 
64
67
  def _dir_sections(
@@ -69,6 +72,7 @@ def _dir_sections(
69
72
  include_tests: bool,
70
73
  priorities,
71
74
  budget: RowBudget,
75
+ check_keys,
72
76
  ) -> list[tuple[str, list[str], str | None]]:
73
77
  sections = _project_sections(
74
78
  db,
@@ -77,6 +81,7 @@ def _dir_sections(
77
81
  scope=scope,
78
82
  priorities=priorities,
79
83
  budget=budget,
84
+ check_keys=check_keys,
80
85
  )
81
86
  files = list_dir_files(db, scope, include_tests=include_tests)
82
87
  total = len(files)
@@ -99,7 +104,11 @@ def _dir_sections(
99
104
  for path in files:
100
105
  if budget.exhausted():
101
106
  break
102
- sections.extend(file_checks.run_all_sections_only(db, path, limit=limit, priorities=priorities, budget=budget))
107
+ sections.extend(
108
+ file_checks.run_all_sections_only(
109
+ db, path, limit=limit, priorities=priorities, budget=budget, check_keys=check_keys
110
+ )
111
+ )
103
112
  return sections
104
113
 
105
114
 
@@ -112,6 +121,7 @@ def main(args):
112
121
  budget = RowBudget(remaining=limit)
113
122
  include_tests = getattr(args, "include_tests", False)
114
123
  priorities = parse_priorities(getattr(args, "priority", None))
124
+ check_keys = parse_checks(getattr(args, "check", None))
115
125
  target = getattr(args, "target", None)
116
126
 
117
127
  if target is None:
@@ -128,6 +138,7 @@ def main(args):
128
138
  scope=None,
129
139
  priorities=priorities,
130
140
  budget=budget,
141
+ check_keys=check_keys,
131
142
  )
132
143
  else:
133
144
  resolved = resolve_analyze_target(db, target, project_root, path_scope)
@@ -140,6 +151,7 @@ def main(args):
140
151
  include_tests=include_tests,
141
152
  priorities=priorities,
142
153
  budget=budget,
154
+ check_keys=check_keys,
143
155
  )
144
156
  elif resolved.kind == "file":
145
157
  file_include = _project_include_tests(include_tests, scope)
@@ -150,16 +162,23 @@ def main(args):
150
162
  scope=scope,
151
163
  priorities=priorities,
152
164
  budget=budget,
165
+ check_keys=check_keys,
153
166
  )
154
167
  if not budget.exhausted():
155
- sections.extend(_file_sections(db, scope, limit=limit, priorities=priorities, budget=budget))
168
+ sections.extend(
169
+ _file_sections(
170
+ db, scope, limit=limit, priorities=priorities, budget=budget, check_keys=check_keys
171
+ )
172
+ )
156
173
  else:
157
174
  symbol_id, _symbol_str, _display = resolve_one_symbol(
158
175
  db,
159
176
  resolved.symbol_name,
160
177
  path_scope=path_scope,
161
178
  )
162
- sections = symbol_checks.run_all(db, symbol_id, limit=limit, priorities=priorities, budget=budget)
179
+ sections = symbol_checks.run_all(
180
+ db, symbol_id, limit=limit, priorities=priorities, budget=budget, check_keys=check_keys
181
+ )
163
182
 
164
183
  _print_sections(sections)
165
184
  finally:
@@ -114,10 +114,10 @@ class TestProjectAnalyze:
114
114
  assert total_rows <= 3
115
115
  assert len(sections) < 9
116
116
 
117
- def test_run_all_returns_nine_sections(self):
117
+ def test_run_all_returns_ten_sections(self):
118
118
  db = mini_codebase_db()
119
119
  sections = project_checks.run_all(db, limit=500)
120
- assert len(sections) == 9
120
+ assert len(sections) == 10
121
121
  titles = [title for title, _lines, _preface in sections]
122
122
  assert sum(1 for t in titles if "[low]" in t) == 4
123
123
  assert sum(1 for t in titles if "[medium]" in t) == 1
@@ -142,10 +142,55 @@ class TestProjectAnalyze:
142
142
 
143
143
  db = mini_codebase_db()
144
144
  sections = project_checks.run_all(db, limit=500, priorities={Priority.HIGH})
145
- assert len(sections) == 4
145
+ assert len(sections) == 5
146
146
  titles = [title for title, _lines, _preface in sections]
147
147
  assert all("[high]" in title for title in titles)
148
148
 
149
+ def test_run_all_check_keys_only(self):
150
+ db = mini_codebase_db()
151
+ sections = project_checks.run_all(db, limit=500, check_keys={"cycles"})
152
+ assert len(sections) == 1
153
+ assert "Cycles" in sections[0][0]
154
+
155
+ def test_run_all_check_and_priority_and(self):
156
+ from scip_cli.analyze.sections import Priority
157
+
158
+ db = mini_codebase_db()
159
+ sections = project_checks.run_all(
160
+ db,
161
+ limit=500,
162
+ priorities={Priority.HIGH},
163
+ check_keys={"cycles", "hotspots"},
164
+ )
165
+ assert len(sections) == 1
166
+ assert "Cycles" in sections[0][0]
167
+
168
+ def test_dead_files_empty_rdeps(self):
169
+ b = AnalyzeDbBuilder()
170
+ used = b.define("src/used.ts", "usedFn")
171
+ b.reference("src/entry.ts", used)
172
+ b.define("src/orphan.ts", "orphanFn")
173
+ b.define("tests/orphan.spec.ts", "testHelper")
174
+ db = b.finish()
175
+ lines = project_checks.dead_files(db, limit=20)
176
+ assert "src/orphan.ts" in lines
177
+ assert "src/entry.ts" in lines
178
+ assert "src/used.ts" not in lines
179
+ assert not any("orphan.spec.ts" in line for line in lines)
180
+ with_tests = project_checks.dead_files(db, limit=20, include_tests=True)
181
+ assert any("orphan.spec.ts" in line for line in with_tests)
182
+
183
+ def test_dead_files_preface_when_hits(self):
184
+ db = mini_codebase_db()
185
+ sections = project_checks.run_all(db, limit=20, check_keys={"dead_files"})
186
+ assert len(sections) == 1
187
+ _title, lines, preface = sections[0]
188
+ assert "Dead files" in _title
189
+ if lines != ["(none)"]:
190
+ assert preface is not None
191
+ assert "rdeps" in preface
192
+ assert "export const" in preface
193
+
149
194
 
150
195
  class TestFileAnalyze:
151
196
  def test_change_surface_lists_exports(self):
@@ -254,6 +299,19 @@ class TestAnalyzeSections:
254
299
  assert parse_priorities("high") == {Priority.HIGH}
255
300
  assert parse_priorities("1,medium") == {Priority.HIGH, Priority.MEDIUM}
256
301
 
302
+ def test_parse_checks(self):
303
+ import pytest
304
+
305
+ from scip_cli.analyze.sections import parse_checks
306
+
307
+ assert parse_checks(None) is None
308
+ assert parse_checks([]) is None
309
+ assert parse_checks(["cycles"]) == {"cycles"}
310
+ assert parse_checks(["cycles,hotspots"]) == {"cycles", "hotspots"}
311
+ assert parse_checks(["cycles", "hotspots"]) == {"cycles", "hotspots"}
312
+ with pytest.raises(RuntimeError, match="unknown analyze check"):
313
+ parse_checks(["not_a_check"])
314
+
257
315
 
258
316
  class TestAnalyzeCommand:
259
317
  def test_project_include_tests_for_test_file_path(self):
@@ -272,6 +272,23 @@ class TestAnalyze:
272
272
  assert "[high]" in result.stdout
273
273
  assert "[low]" not in result.stdout
274
274
 
275
+ def test_check_runs_named_section_only(self, cli):
276
+ result = cli.run("analyze", "--check", "cycles", "--limit", "5")
277
+ assert result.returncode == 0
278
+ assert "Cycles" in result.stdout
279
+ assert "Hotspots" not in result.stdout
280
+ assert "Dead exports" not in result.stdout
281
+
282
+ def test_check_dead_files(self, cli):
283
+ result = cli.run("analyze", "--check", "dead_files", "--limit", "5")
284
+ assert result.returncode == 0
285
+ assert "Dead files" in result.stdout
286
+
287
+ def test_check_unknown_exits(self, cli):
288
+ result = cli.run("analyze", "--check", "not_a_check")
289
+ assert result.returncode == 1
290
+ assert "unknown analyze check" in result.stderr
291
+
275
292
 
276
293
  class TestIndex:
277
294
  def test_fixture_index_queryable(self, indexed_fixture):
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes