scip-cli 2.6.0__tar.gz → 2.8.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 (154) hide show
  1. {scip_cli-2.6.0 → scip_cli-2.8.0}/PKG-INFO +10 -8
  2. {scip_cli-2.6.0 → scip_cli-2.8.0}/README.md +8 -6
  3. scip_cli-2.8.0/docs/port-v28-board.md +32 -0
  4. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/SKILL.md +8 -4
  5. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/__init__.py +1 -1
  6. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/__main__.py +15 -0
  7. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/analyze/file.py +8 -2
  8. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/analyze/project.py +42 -0
  9. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/analyze/sections.py +72 -7
  10. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/analyze/symbol.py +2 -1
  11. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/commands/analyze.py +24 -5
  12. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/commands/reindex.py +8 -0
  13. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/config.py +6 -0
  14. scip_cli-2.8.0/scip_cli/exclude.py +108 -0
  15. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/indexing/convert.py +8 -2
  16. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/indexing/core.py +8 -1
  17. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/indexing/languages.py +6 -6
  18. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/indexing/orchestrate.py +3 -0
  19. scip_cli-2.8.0/scip_cli/indexing/postprocess.py +296 -0
  20. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/indexing/typescript.py +13 -3
  21. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/e2e_harness.py +3 -1
  22. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixture_catalog.py +8 -0
  23. scip_cli-2.8.0/tests/fixtures/typescript-project/src/__tests__/fixtureOnly.spec.ts +5 -0
  24. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/test_analyze.py +61 -3
  25. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/test_config.py +1 -0
  26. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/test_e2e.py +17 -0
  27. scip_cli-2.8.0/tests/test_exclude_globs.py +162 -0
  28. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/test_python_index_integration.py +1 -1
  29. scip_cli-2.6.0/scip_cli/indexing/postprocess.py +0 -152
  30. {scip_cli-2.6.0 → scip_cli-2.8.0}/.agents/commands/migration-problems.md +0 -0
  31. {scip_cli-2.6.0 → scip_cli-2.8.0}/.agents/rules/agent.mdc +0 -0
  32. {scip_cli-2.6.0 → scip_cli-2.8.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  33. {scip_cli-2.6.0 → scip_cli-2.8.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  34. {scip_cli-2.6.0 → scip_cli-2.8.0}/.github/ISSUE_TEMPLATE/question.md +0 -0
  35. {scip_cli-2.6.0 → scip_cli-2.8.0}/.github/pull_request_template.md +0 -0
  36. {scip_cli-2.6.0 → scip_cli-2.8.0}/.github/workflows/ci.yml +0 -0
  37. {scip_cli-2.6.0 → scip_cli-2.8.0}/.gitignore +0 -0
  38. {scip_cli-2.6.0 → scip_cli-2.8.0}/.pre-commit-config.yaml +0 -0
  39. {scip_cli-2.6.0 → scip_cli-2.8.0}/AGENTS.md +0 -0
  40. {scip_cli-2.6.0 → scip_cli-2.8.0}/CODE_OF_CONDUCT.md +0 -0
  41. {scip_cli-2.6.0 → scip_cli-2.8.0}/CONTRIBUTING.md +0 -0
  42. {scip_cli-2.6.0 → scip_cli-2.8.0}/LICENSE +0 -0
  43. {scip_cli-2.6.0 → scip_cli-2.8.0}/SECURITY.md +0 -0
  44. {scip_cli-2.6.0 → scip_cli-2.8.0}/docs/port.md +0 -0
  45. {scip_cli-2.6.0 → scip_cli-2.8.0}/docs/ports/conclusions.md +0 -0
  46. {scip_cli-2.6.0 → scip_cli-2.8.0}/docs/ports/experiment-summary.md +0 -0
  47. {scip_cli-2.6.0 → scip_cli-2.8.0}/docs/ports/go-experiment.md +0 -0
  48. {scip_cli-2.6.0 → scip_cli-2.8.0}/docs/ports/go.md +0 -0
  49. {scip_cli-2.6.0 → scip_cli-2.8.0}/docs/ports/plan.md +0 -0
  50. {scip_cli-2.6.0 → scip_cli-2.8.0}/docs/ports/rust-experiment.md +0 -0
  51. {scip_cli-2.6.0 → scip_cli-2.8.0}/docs/ports/rust.md +0 -0
  52. {scip_cli-2.6.0 → scip_cli-2.8.0}/docs/ports/summary.md +0 -0
  53. {scip_cli-2.6.0 → scip_cli-2.8.0}/docs/ports/zig-experiment.md +0 -0
  54. {scip_cli-2.6.0 → scip_cli-2.8.0}/docs/ports/zig.md +0 -0
  55. {scip_cli-2.6.0 → scip_cli-2.8.0}/pyproject.toml +0 -0
  56. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/analyze/__init__.py +0 -0
  57. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/analyze/common.py +0 -0
  58. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/analyze/graph.py +0 -0
  59. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/analyze/live.py +0 -0
  60. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/analyze/targets.py +0 -0
  61. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/cache.py +0 -0
  62. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/cli_args.py +0 -0
  63. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/commands/__init__.py +0 -0
  64. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/commands/code.py +0 -0
  65. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/commands/deps.py +0 -0
  66. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/commands/members.py +0 -0
  67. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/commands/rdeps.py +0 -0
  68. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/commands/refs.py +0 -0
  69. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/commands/search.py +0 -0
  70. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/commands/skill.py +0 -0
  71. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/commands/symbols.py +0 -0
  72. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/debug.py +0 -0
  73. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/discover.py +0 -0
  74. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/indexing/__init__.py +0 -0
  75. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/indexing/constants.py +0 -0
  76. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/indexing/runners.py +0 -0
  77. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/merge.py +0 -0
  78. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/output.py +0 -0
  79. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/paths.py +0 -0
  80. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/project.py +0 -0
  81. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/queries.py +0 -0
  82. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/scip_tool.py +0 -0
  83. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/scope.py +0 -0
  84. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/session.py +0 -0
  85. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/source.py +0 -0
  86. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/sql.py +0 -0
  87. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/symbols.py +0 -0
  88. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/targets.py +0 -0
  89. {scip_cli-2.6.0 → scip_cli-2.8.0}/scip_cli/tsconfig.py +0 -0
  90. {scip_cli-2.6.0 → scip_cli-2.8.0}/scripts/bench.sh +0 -0
  91. {scip_cli-2.6.0 → scip_cli-2.8.0}/scripts/bench_postprocess.py +0 -0
  92. {scip_cli-2.6.0 → scip_cli-2.8.0}/scripts/build.sh +0 -0
  93. {scip_cli-2.6.0 → scip_cli-2.8.0}/scripts/publish.sh +0 -0
  94. {scip_cli-2.6.0 → scip_cli-2.8.0}/scripts/test.sh +0 -0
  95. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/__init__.py +0 -0
  96. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/analyze_db.py +0 -0
  97. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/bench_db.py +0 -0
  98. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/conftest.py +0 -0
  99. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/package.json +0 -0
  100. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/app/handler.ts +0 -0
  101. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/config.ts +0 -0
  102. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/consumer.ts +0 -0
  103. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/dead.ts +0 -0
  104. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/domain/i18n/en.ts +0 -0
  105. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/domain/i18n/index.ts +0 -0
  106. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/domain/i18n/useLocale.ts +0 -0
  107. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/domain/labels/index.ts +0 -0
  108. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/events/streamTypes.ts +0 -0
  109. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/gateway/index.ts +0 -0
  110. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/helper.ts +0 -0
  111. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/hooks/useHookA.ts +0 -0
  112. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/hooks/useHookB.ts +0 -0
  113. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/hooks/useItems.ts +0 -0
  114. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/index.ts +0 -0
  115. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/integrations/inferenceClient.ts +0 -0
  116. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/lib/handler.ts +0 -0
  117. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/loaders/useInference.ts +0 -0
  118. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/models/MutationAggregate.ts +0 -0
  119. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/pages/panelPage.ts +0 -0
  120. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/rules/applyRule.ts +0 -0
  121. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/types/a.ts +0 -0
  122. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/types/b.ts +0 -0
  123. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/ui/Button.ts +0 -0
  124. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/ui/LazyPanel.ts +0 -0
  125. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/ui/buttonConsumer.ts +0 -0
  126. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/ui/menuModule.ts +0 -0
  127. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/user.ts +0 -0
  128. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/widget.ts +0 -0
  129. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/src/widgets/OrphanWidget.ts +0 -0
  130. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/fixtures/typescript-project/tsconfig.json +0 -0
  131. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/perf_util.py +0 -0
  132. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/test_analyze_graph.py +0 -0
  133. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/test_analyze_perf.py +0 -0
  134. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/test_bench_queries.py +0 -0
  135. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/test_cache.py +0 -0
  136. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/test_composability.py +0 -0
  137. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/test_discover.py +0 -0
  138. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/test_e2e_analyze_patterns.py +0 -0
  139. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/test_e2e_perf.py +0 -0
  140. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/test_index_batching.py +0 -0
  141. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/test_index_prune.py +0 -0
  142. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/test_indexer_env.py +0 -0
  143. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/test_indexer_fallback.py +0 -0
  144. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/test_merge.py +0 -0
  145. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/test_multi_symbol.py +0 -0
  146. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/test_pure_functions.py +0 -0
  147. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/test_qualified_symbols.py +0 -0
  148. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/test_reindex.py +0 -0
  149. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/test_scip_tool.py +0 -0
  150. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/test_scope.py +0 -0
  151. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/test_symbols_freq.py +0 -0
  152. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/test_targets.py +0 -0
  153. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/test_tsconfig.py +0 -0
  154. {scip_cli-2.6.0 → scip_cli-2.8.0}/tests/test_typescript_projects.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: scip-cli
3
- Version: 2.6.0
3
+ Version: 2.8.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
 
@@ -0,0 +1,32 @@
1
+ # v2.8.0 multi-port board
2
+
3
+ **Goal:** Ship Python v2.8.0 (exclude globs), then port full v2.6.0→v2.8.0 gap to go/rust/zig in `/tmp`, direct-to-main, smoke-tested. Mirror GitHub releases optional.
4
+
5
+ **Frozen reference:** `/tmp/scip-cli-2.8.0-freeze.patch` (pre-commit snapshot)
6
+
7
+ ## Gap to port (per mirror)
8
+
9
+ | Release | Features | Manual port |
10
+ |---|---|---|
11
+ | v2.7.0 | `analyze --check`, `dead_files`, section FP warnings | analyze/*, commands/analyze, tests |
12
+ | v2.8.0 | `reindex --exclude`, `excludeGlobs`, post-process prune | exclude, indexing, config, reindex, tests |
13
+
14
+ `scripts/sync-upstream.sh --ref v2.8.0` covers SKILL, fixtures, templates only.
15
+
16
+ ## Lanes
17
+
18
+ | Lane | Path | Status | Notes |
19
+ |---|---|---|---|
20
+ | P0 Python | `~/Code/scip-cli` | in progress | commit → v2.8.0 → push → release |
21
+ | G1-go | `/tmp/scip-cli-go` | pending | direct-to-main |
22
+ | G1-rust | `/tmp/scip-cli-rust` | pending | direct-to-main |
23
+ | G1-zig | `/tmp/scip-cli-zig` | pending | direct-to-main |
24
+
25
+ ## Verify log
26
+
27
+ | Check | Python | Go | Rust | Zig |
28
+ |---|---|---|---|---|
29
+ | scripts/test.sh | | | | |
30
+ | exclude dogfood | | | | |
31
+ | analyze --check | n/a | | | |
32
+ | readonly /iterate review | | | | |
@@ -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,22 +143,26 @@ 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
 
160
162
  ```bash
161
- reindex [--path DIR ...] [--tsconfig FILE_OR_GLOB ...] [--with-external]
163
+ reindex [--path DIR ...] [--tsconfig FILE_OR_GLOB ...] [--exclude GLOB ...] [--with-external]
162
164
  ```
163
165
 
164
166
  `--path` and `--tsconfig` cannot be combined (**TypeScript only**). `--tsconfig` takes `tsconfig*.json` files (repeatable; globs expanded inside the tool). File-based runs default to one `scip-typescript` process per file so each gets its own heap (`SCIP_CLI_TS_INDEX_BATCH_SIZE` still overrides). Scope is saved as `index-scope.json` and reused until a full `reindex` with neither flag.
167
+
168
+ `--exclude GLOB` omits matching files from the SQLite index after conversion (repeatable; merged with `excludeGlobs` in `.scip-cli.json`). Persisted in `index-exclude.json` until a full `reindex` with no `--exclude`. Patterns without `/` match basenames (`*.test.ts`); patterns with `/` match repo-relative paths (`tests/**`, `**/__tests__/**`). Indexers still parse excluded files when production code imports them — post-process removal is authoritative.
@@ -1,3 +1,3 @@
1
1
  """scip-cli: Fast code intelligence via SCIP indexes."""
2
2
 
3
- __version__ = "2.6.0"
3
+ __version__ = "2.8.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")
@@ -160,6 +166,15 @@ def main() -> None:
160
166
  "Cannot be combined with --path."
161
167
  ),
162
168
  )
169
+ reindex_parser.add_argument(
170
+ "--exclude",
171
+ action="append",
172
+ metavar="GLOB",
173
+ help=(
174
+ "Omit indexed files matching GLOB from the SQLite index "
175
+ "(repeatable; merged with excludeGlobs in .scip-cli.json; persisted until full reindex)"
176
+ ),
177
+ )
163
178
  reindex_parser.add_argument(
164
179
  "--with-external",
165
180
  action="store_true",
@@ -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:
@@ -9,6 +9,7 @@ from ..cache import (
9
9
  index_db_path,
10
10
  promote_next_index,
11
11
  )
12
+ from ..exclude import save_persisted_exclude_globs
12
13
  from ..indexing import index_project, log_index_complete
13
14
  from ..paths import normalize_path_scope
14
15
  from ..project import Language, find_project_root_and_language
@@ -24,6 +25,7 @@ def main(args):
24
25
 
25
26
  path_args = getattr(args, "path", None) or []
26
27
  tsconfig_args = getattr(args, "tsconfig", None) or []
28
+ exclude_args = getattr(args, "exclude", None) or []
27
29
  if path_args and tsconfig_args:
28
30
  print("Error: reindex --path and --tsconfig cannot be combined", file=sys.stderr)
29
31
  sys.exit(1)
@@ -68,6 +70,12 @@ def main(args):
68
70
  else:
69
71
  save_index_scope(root, None)
70
72
 
73
+ if exclude_args:
74
+ save_persisted_exclude_globs(root, exclude_args)
75
+ print(f"Index exclude: {', '.join(exclude_args)}", file=sys.stderr)
76
+ else:
77
+ save_persisted_exclude_globs(root, None)
78
+
71
79
  cache_dir = get_cache_dir(root)
72
80
  cache_dir.mkdir(parents=True, exist_ok=True)
73
81
 
@@ -16,6 +16,7 @@ class ProjectSettings:
16
16
  max_heap_mb: int | None = None
17
17
  index_roots: list[str] = field(default_factory=list)
18
18
  only_index_roots: bool = False
19
+ exclude_globs: list[str] = field(default_factory=list)
19
20
 
20
21
 
21
22
  def load_project_config(project_root: Path) -> ProjectSettings:
@@ -44,10 +45,15 @@ def load_project_config(project_root: Path) -> ProjectSettings:
44
45
  if type(only_index_roots) is not bool:
45
46
  raise RuntimeError(f"Invalid {CONFIG_FILENAME}: onlyIndexRoots must be a boolean")
46
47
 
48
+ exclude_globs = data.get("excludeGlobs", [])
49
+ if not isinstance(exclude_globs, list) or not all(isinstance(p, str) for p in exclude_globs):
50
+ raise RuntimeError(f"Invalid {CONFIG_FILENAME}: excludeGlobs must be a string array")
51
+
47
52
  return ProjectSettings(
48
53
  max_heap_mb=max_heap_mb,
49
54
  index_roots=index_roots,
50
55
  only_index_roots=only_index_roots,
56
+ exclude_globs=exclude_globs,
51
57
  )
52
58
 
53
59