scip-cli 2.3.0__tar.gz → 2.4.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 (115) hide show
  1. {scip_cli-2.3.0 → scip_cli-2.4.0}/.cursor/rules/agent.mdc +2 -1
  2. {scip_cli-2.3.0 → scip_cli-2.4.0}/.pre-commit-config.yaml +4 -0
  3. {scip_cli-2.3.0 → scip_cli-2.4.0}/PKG-INFO +3 -2
  4. {scip_cli-2.3.0 → scip_cli-2.4.0}/README.md +1 -1
  5. {scip_cli-2.3.0 → scip_cli-2.4.0}/pyproject.toml +18 -0
  6. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/__init__.py +1 -1
  7. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/__main__.py +6 -3
  8. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/analyze/common.py +1 -1
  9. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/analyze/file.py +1 -1
  10. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/analyze/live.py +1 -1
  11. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/analyze/project.py +9 -2
  12. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/analyze/sections.py +9 -7
  13. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/analyze/symbol.py +2 -2
  14. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/cache.py +4 -2
  15. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/commands/analyze.py +12 -10
  16. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/commands/deps.py +1 -1
  17. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/commands/members.py +1 -1
  18. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/commands/reindex.py +13 -5
  19. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/commands/search.py +5 -3
  20. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/discover.py +4 -2
  21. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/indexing.py +10 -8
  22. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/output.py +4 -2
  23. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/paths.py +5 -4
  24. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/queries.py +25 -9
  25. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/scip_tool.py +8 -7
  26. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/source.py +20 -5
  27. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/sql.py +7 -3
  28. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/symbols.py +11 -9
  29. {scip_cli-2.3.0 → scip_cli-2.4.0}/scripts/test.sh +4 -1
  30. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/test_analyze.py +2 -1
  31. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/test_analyze_perf.py +5 -3
  32. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/test_discover.py +5 -0
  33. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/test_pure_functions.py +2 -0
  34. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/test_reindex.py +15 -1
  35. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/test_scope.py +3 -2
  36. {scip_cli-2.3.0 → scip_cli-2.4.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  37. {scip_cli-2.3.0 → scip_cli-2.4.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  38. {scip_cli-2.3.0 → scip_cli-2.4.0}/.github/ISSUE_TEMPLATE/question.md +0 -0
  39. {scip_cli-2.3.0 → scip_cli-2.4.0}/.github/pull_request_template.md +0 -0
  40. {scip_cli-2.3.0 → scip_cli-2.4.0}/.gitignore +0 -0
  41. {scip_cli-2.3.0 → scip_cli-2.4.0}/CODE_OF_CONDUCT.md +0 -0
  42. {scip_cli-2.3.0 → scip_cli-2.4.0}/CONTRIBUTING.md +0 -0
  43. {scip_cli-2.3.0 → scip_cli-2.4.0}/LICENSE +0 -0
  44. {scip_cli-2.3.0 → scip_cli-2.4.0}/SECURITY.md +0 -0
  45. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/SKILL.md +0 -0
  46. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/analyze/__init__.py +0 -0
  47. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/analyze/graph.py +0 -0
  48. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/analyze/targets.py +0 -0
  49. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/cli_args.py +0 -0
  50. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/commands/__init__.py +0 -0
  51. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/commands/code.py +0 -0
  52. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/commands/rdeps.py +0 -0
  53. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/commands/refs.py +0 -0
  54. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/commands/skill.py +0 -0
  55. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/commands/symbols.py +0 -0
  56. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/config.py +0 -0
  57. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/debug.py +0 -0
  58. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/merge.py +0 -0
  59. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/project.py +0 -0
  60. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/scope.py +0 -0
  61. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/session.py +0 -0
  62. {scip_cli-2.3.0 → scip_cli-2.4.0}/scip_cli/targets.py +0 -0
  63. {scip_cli-2.3.0 → scip_cli-2.4.0}/scripts/build.sh +0 -0
  64. {scip_cli-2.3.0 → scip_cli-2.4.0}/scripts/publish.sh +0 -0
  65. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/__init__.py +0 -0
  66. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/analyze_db.py +0 -0
  67. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/conftest.py +0 -0
  68. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/e2e_harness.py +0 -0
  69. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixture_catalog.py +0 -0
  70. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/package.json +0 -0
  71. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/src/app/handler.ts +0 -0
  72. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/src/config.ts +0 -0
  73. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/src/consumer.ts +0 -0
  74. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/src/dead.ts +0 -0
  75. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/src/domain/i18n/en.ts +0 -0
  76. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/src/domain/i18n/index.ts +0 -0
  77. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/src/domain/i18n/useLocale.ts +0 -0
  78. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/src/domain/labels/index.ts +0 -0
  79. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/src/events/streamTypes.ts +0 -0
  80. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/src/gateway/index.ts +0 -0
  81. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/src/helper.ts +0 -0
  82. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/src/hooks/useHookA.ts +0 -0
  83. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/src/hooks/useHookB.ts +0 -0
  84. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/src/hooks/useItems.ts +0 -0
  85. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/src/index.ts +0 -0
  86. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/src/integrations/inferenceClient.ts +0 -0
  87. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/src/loaders/useInference.ts +0 -0
  88. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/src/pages/panelPage.ts +0 -0
  89. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/src/rules/applyRule.ts +0 -0
  90. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/src/types/a.ts +0 -0
  91. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/src/types/b.ts +0 -0
  92. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/src/ui/Button.ts +0 -0
  93. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/src/ui/LazyPanel.ts +0 -0
  94. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/src/ui/buttonConsumer.ts +0 -0
  95. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/src/ui/menuModule.ts +0 -0
  96. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/src/user.ts +0 -0
  97. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/src/widget.ts +0 -0
  98. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/src/widgets/OrphanWidget.ts +0 -0
  99. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/fixtures/sample-project/tsconfig.json +0 -0
  100. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/perf_util.py +0 -0
  101. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/test_analyze_graph.py +0 -0
  102. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/test_cache.py +0 -0
  103. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/test_composability.py +0 -0
  104. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/test_config.py +0 -0
  105. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/test_e2e.py +0 -0
  106. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/test_e2e_analyze_patterns.py +0 -0
  107. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/test_e2e_perf.py +0 -0
  108. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/test_index_prune.py +0 -0
  109. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/test_indexer_env.py +0 -0
  110. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/test_merge.py +0 -0
  111. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/test_multi_symbol.py +0 -0
  112. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/test_qualified_symbols.py +0 -0
  113. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/test_scip_tool.py +0 -0
  114. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/test_targets.py +0 -0
  115. {scip_cli-2.3.0 → scip_cli-2.4.0}/tests/test_typescript_projects.py +0 -0
@@ -33,8 +33,9 @@ Node.js + `npx` required for integration tests (`scip-typescript`). Optional: `S
33
33
  | Full suite | `pytest` |
34
34
  | E2e loop | `pytest tests/test_e2e.py tests/test_e2e_analyze_patterns.py` |
35
35
  | Lint / format | `ruff check .` / `ruff format .` |
36
+ | Typecheck | `basedpyright scip_cli/` |
36
37
 
37
- Pre-commit: ruff + ruff-format + full pytest. E2e before commit on command changes.
38
+ Pre-commit: ruff + ruff-format + basedpyright + full pytest. E2e before commit on command changes.
38
39
 
39
40
  ## Improving this repo (do this before review subagents)
40
41
 
@@ -5,6 +5,10 @@ repos:
5
5
  - id: ruff
6
6
  args: [--fix]
7
7
  - id: ruff-format
8
+ - repo: https://github.com/DetachHead/basedpyright-pre-commit-mirror
9
+ rev: 1.39.9
10
+ hooks:
11
+ - id: basedpyright
8
12
  - repo: local
9
13
  hooks:
10
14
  - id: pytest
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scip-cli
3
- Version: 2.3.0
3
+ Version: 2.4.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
@@ -12,6 +12,7 @@ Classifier: Programming Language :: Python :: 3
12
12
  Classifier: Topic :: Software Development :: Code Generators
13
13
  Requires-Python: >=3.9
14
14
  Provides-Extra: dev
15
+ Requires-Dist: basedpyright>=1.39.9; extra == 'dev'
15
16
  Requires-Dist: build>=1; extra == 'dev'
16
17
  Requires-Dist: pre-commit>=4.0; extra == 'dev'
17
18
  Requires-Dist: pytest>=7; extra == 'dev'
@@ -79,7 +80,7 @@ python -m venv .venv && source .venv/bin/activate
79
80
  pip install -e ".[dev]"
80
81
  ```
81
82
 
82
- Tests and lint: `pytest`, `ruff check .` from the same environment.
83
+ Tests and lint: `pytest`, `ruff check .`, `basedpyright scip_cli/` from the same environment (or `scripts/test.sh` for the publish gate).
83
84
 
84
85
  ### 2. Install prerequisites (optional)
85
86
 
@@ -59,7 +59,7 @@ python -m venv .venv && source .venv/bin/activate
59
59
  pip install -e ".[dev]"
60
60
  ```
61
61
 
62
- Tests and lint: `pytest`, `ruff check .` from the same environment.
62
+ Tests and lint: `pytest`, `ruff check .`, `basedpyright scip_cli/` from the same environment (or `scripts/test.sh` for the publish gate).
63
63
 
64
64
  ### 2. Install prerequisites (optional)
65
65
 
@@ -32,6 +32,7 @@ packages = ["scip_cli"]
32
32
  [project.optional-dependencies]
33
33
  dev = [
34
34
  "ruff>=0.11",
35
+ "basedpyright>=1.39.9",
35
36
  "pytest>=7",
36
37
  "pre-commit>=4.0",
37
38
  "build>=1",
@@ -57,3 +58,20 @@ addopts = "-q --tb=line --no-header"
57
58
  markers = [
58
59
  "integration: uses the shared indexed TS fixture (requires npx / scip-typescript)",
59
60
  ]
61
+
62
+ [tool.basedpyright]
63
+ pythonVersion = "3.10"
64
+ venvPath = "."
65
+ venv = ".venv"
66
+ include = ["scip_cli"]
67
+ exclude = ["tests"]
68
+
69
+ # Gradual typing: basedpyright's unknown* rules fire hundreds of times on CLI/SQL glue
70
+ # pythonVersion=3.10 matches union syntax in annotations; package still supports 3.9 via __future__ annotations
71
+ reportAny = "none"
72
+ reportUnknownParameterType = "none"
73
+ reportUnknownArgumentType = "none"
74
+ reportUnknownVariableType = "none"
75
+ reportUnknownMemberType = "none"
76
+ reportMissingParameterType = "none"
77
+ reportUnusedCallResult = "none"
@@ -1,3 +1,3 @@
1
1
  """scip-cli: Fast code intelligence via SCIP indexes."""
2
2
 
3
- __version__ = "2.3.0"
3
+ __version__ = "2.4.0"
@@ -1,11 +1,14 @@
1
1
  """CLI entry point for scip-cli."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import argparse
4
6
  import logging
5
7
  import os
6
8
  import shutil
7
9
  import sqlite3
8
10
  import sys
11
+ from collections.abc import Callable
9
12
 
10
13
  from . import __version__
11
14
  from .cli_args import add_limit_argument, add_names_only_argument, add_path_argument, add_paths_only_argument
@@ -19,7 +22,7 @@ else:
19
22
  logging.disable(logging.DEBUG)
20
23
 
21
24
 
22
- def main():
25
+ def main() -> None:
23
26
  parser = argparse.ArgumentParser(
24
27
  prog="scip-cli",
25
28
  description="Fast code intelligence via SCIP indexes",
@@ -45,7 +48,7 @@ def main():
45
48
  type=int,
46
49
  default=None,
47
50
  metavar="N",
48
- help=("Max source lines per definition body (default: 80, env SCIP_CLI_MAX_DEF_LINES). Use 0 for unlimited."),
51
+ help="Max source lines per definition body (default: 80, env SCIP_CLI_MAX_DEF_LINES). Use 0 for unlimited.",
49
52
  )
50
53
  code_parser.add_argument(
51
54
  "--full",
@@ -151,7 +154,7 @@ def main():
151
154
  return
152
155
 
153
156
  # Dispatch to command handlers
154
- dispatch = {
157
+ dispatch: dict[str, Callable[..., None]] = {
155
158
  "refs": refs.main,
156
159
  "code": code.main,
157
160
  "search": search.main,
@@ -16,7 +16,7 @@ SYM_DEF_JOIN = """
16
16
  """
17
17
 
18
18
 
19
- def fetch_all(db, sql: str, params=()) -> list:
19
+ def fetch_all(db, sql: str, params=()):
20
20
  return debug_execute(db, sql, params).fetchall()
21
21
 
22
22
 
@@ -298,7 +298,7 @@ def top_symbol_pressure(db, relative_path: str, limit: int = DEFAULT_LIMIT) -> l
298
298
 
299
299
 
300
300
  def _bind_path(fn, relative_path: str):
301
- def run(db, limit: int, **kwargs):
301
+ def run(db, limit: int, **_kwargs):
302
302
  return fn(db, relative_path, limit)
303
303
 
304
304
  return run
@@ -15,7 +15,7 @@ _EXTERNAL_MENTION = """
15
15
  """
16
16
 
17
17
 
18
- def _fetch_all(db, sql: str, params=()) -> list:
18
+ def _fetch_all(db, sql: str, params=()):
19
19
  return debug_execute(db, sql, params).fetchall()
20
20
 
21
21
 
@@ -416,7 +416,6 @@ def run_all(
416
416
  budget=None,
417
417
  ) -> list[tuple[str, list[str], str | None]]:
418
418
  suffix = _scope_suffix(scope)
419
- opts = {"include_tests": include_tests, "scope": scope, "budget": budget}
420
419
  checks = [
421
420
  Check("cycles", Priority.HIGH, f"Cycles (file dependencies){suffix}", cycles),
422
421
  Check("unreferenced", Priority.HIGH, f"Unreferenced symbols (no refs){suffix}", unreferenced_symbols),
@@ -438,4 +437,12 @@ def run_all(
438
437
  Check("bottlenecks", Priority.LOW, f"Bottlenecks (fan-in x fan-out){suffix}", bottlenecks),
439
438
  Check("hotspots", Priority.LOW, f"Hotspots (most referenced){suffix}", hotspots),
440
439
  ]
441
- return run_checks(checks, db, limit, priorities, **opts)
440
+ return run_checks(
441
+ checks,
442
+ db,
443
+ limit,
444
+ priorities,
445
+ include_tests=include_tests,
446
+ scope=scope,
447
+ budget=budget,
448
+ )
@@ -5,7 +5,6 @@ from __future__ import annotations
5
5
  from collections.abc import Callable
6
6
  from dataclasses import dataclass
7
7
  from enum import Enum
8
- from typing import Any
9
8
 
10
9
  from .common import section
11
10
 
@@ -101,20 +100,23 @@ def run_checks(
101
100
  db,
102
101
  limit: int,
103
102
  priorities: set[Priority] | None,
104
- **kwargs: Any,
103
+ *,
104
+ include_tests: bool = False,
105
+ scope: str | None = None,
106
+ budget: RowBudget | None = None,
105
107
  ) -> list[tuple[str, list[str], str | None]]:
106
108
  """Run checks in priority order (high → low), optionally filtered."""
107
109
  selected = [check for check in checks if priorities is None or check.priority in priorities]
108
110
  selected.sort(key=lambda check: (_ORDER.index(check.priority), check.key))
109
- budget: RowBudget = kwargs.pop("budget", None) or RowBudget(remaining=limit)
111
+ budget_obj: RowBudget = budget or RowBudget(remaining=limit)
110
112
  sections: list[tuple[str, list[str], str | None]] = []
111
113
  for check in selected:
112
- if budget.exhausted():
114
+ if budget_obj.exhausted():
113
115
  break
114
- lines = check.run(db, budget.remaining, **kwargs)
116
+ lines = check.run(db, budget_obj.remaining, include_tests=include_tests, scope=scope)
115
117
  if lines != ["(none)"]:
116
- lines = lines[: budget.remaining]
117
- budget.remaining -= len(lines)
118
+ lines = lines[: budget_obj.remaining]
119
+ budget_obj.remaining -= len(lines)
118
120
  preface = check.false_positive_preface if check.false_positive_preface else _preface_for(check.key)
119
121
  sections.append(section(check.labeled_title(), lines, preface=preface))
120
122
  return sections
@@ -8,14 +8,14 @@ from .sections import Check, Priority, run_checks
8
8
 
9
9
 
10
10
  def _bind_symbol(fn, symbol_id: int):
11
- def run(db, limit: int, **kwargs):
11
+ def run(db, limit: int, **_kwargs):
12
12
  return fn(db, symbol_id, limit)
13
13
 
14
14
  return run
15
15
 
16
16
 
17
17
  def _bind_symbol0(fn, symbol_id: int):
18
- def run(db, limit: int, **kwargs):
18
+ def run(db, _limit: int, **_kwargs):
19
19
  return fn(db, symbol_id)
20
20
 
21
21
  return run
@@ -1,5 +1,7 @@
1
1
  """Index cache path resolution."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import contextlib
4
6
  import fcntl
5
7
  import hashlib
@@ -78,13 +80,13 @@ def project_cache_slug(project_root: Path) -> str:
78
80
  return f"{slug}-{digest}"
79
81
 
80
82
 
81
- def get_cache_dir(project_root):
83
+ def get_cache_dir(project_root: Path | str) -> Path:
82
84
  """Get the cache directory for a project (one dir per repo root)."""
83
85
  root = Path(project_root).resolve()
84
86
  return Path.home() / ".cache" / "scip-cli" / "projects" / project_cache_slug(root)
85
87
 
86
88
 
87
- def find_db(project_root=None):
89
+ def find_db(project_root: Path | str | None = None) -> Path | None:
88
90
  """Find the index.db for the given project (or cwd)."""
89
91
  root = project_root or find_project_root()
90
92
  if not root:
@@ -1,5 +1,7 @@
1
1
  """analyze command — run SQL-based analysis dashboards."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import sys
4
6
 
5
7
  from ..analyze import file as file_checks
@@ -85,8 +87,10 @@ def _dir_sections(
85
87
  )
86
88
  elif total > MAX_DIR_FILES:
87
89
  print(
88
- f"Note: {total} indexed files under {scope}; showing first {MAX_DIR_FILES} "
89
- f"(analyze one file for full detail)",
90
+ (
91
+ f"Note: {total} indexed files under {scope}; showing first {MAX_DIR_FILES} "
92
+ f"(analyze one file for full detail)"
93
+ ),
90
94
  file=sys.stderr,
91
95
  )
92
96
  files = files[:MAX_DIR_FILES]
@@ -127,29 +131,28 @@ def main(args):
127
131
  )
128
132
  else:
129
133
  resolved = resolve_analyze_target(db, target, project_root, path_scope)
134
+ scope = resolved.scope or ""
130
135
  if resolved.kind == "dir":
131
136
  sections = _dir_sections(
132
137
  db,
133
- resolved.scope,
138
+ scope,
134
139
  limit=limit,
135
140
  include_tests=include_tests,
136
141
  priorities=priorities,
137
142
  budget=budget,
138
143
  )
139
144
  elif resolved.kind == "file":
140
- file_include = _project_include_tests(include_tests, resolved.scope)
145
+ file_include = _project_include_tests(include_tests, scope)
141
146
  sections = _project_sections(
142
147
  db,
143
148
  limit=limit,
144
149
  include_tests=file_include,
145
- scope=resolved.scope,
150
+ scope=scope,
146
151
  priorities=priorities,
147
152
  budget=budget,
148
153
  )
149
154
  if not budget.exhausted():
150
- sections.extend(
151
- _file_sections(db, resolved.scope, limit=limit, priorities=priorities, budget=budget)
152
- )
155
+ sections.extend(_file_sections(db, scope, limit=limit, priorities=priorities, budget=budget))
153
156
  else:
154
157
  symbol_id, _symbol_str, _display = resolve_one_symbol(
155
158
  db,
@@ -160,5 +163,4 @@ def main(args):
160
163
 
161
164
  _print_sections(sections)
162
165
  finally:
163
- if db is not None:
164
- db.close()
166
+ db.close()
@@ -5,8 +5,8 @@ import sys
5
5
  from ..cli_args import path_scope_from_args
6
6
  from ..output import limit_and_warn
7
7
  from ..paths import path_in_scope
8
- from ..queries import debug_execute
9
8
  from ..session import resolve_one_file, resolve_one_symbol, setup
9
+ from ..sql import debug_execute
10
10
  from ..symbols import extract_leaf_name
11
11
 
12
12
 
@@ -79,7 +79,7 @@ def main(args):
79
79
 
80
80
  for i, line in enumerate(source_lines):
81
81
  if any(re.match(p, line) for p in patterns):
82
- start_line = parent_start + i
82
+ start_line = (parent_start or 0) + i
83
83
  end_line = start_line
84
84
  break
85
85
 
@@ -9,7 +9,7 @@ from ..cache import (
9
9
  index_db_path,
10
10
  promote_next_index,
11
11
  )
12
- from ..indexing import _index_project, log_index_complete
12
+ from ..indexing import index_project, log_index_complete
13
13
  from ..paths import normalize_path_scope
14
14
  from ..project import Language, find_project_root_and_language
15
15
  from ..scope import save_index_scope
@@ -27,12 +27,20 @@ def main(args):
27
27
  sys.exit(1)
28
28
 
29
29
  if path_args:
30
- scope_paths = [normalize_path_scope(path, root) for path in path_args]
30
+ scope_paths: list[str] = []
31
+ for path in path_args:
32
+ normalized = normalize_path_scope(path, root)
33
+ if normalized is None:
34
+ print(f"Error: invalid or empty --path: {path!r}", file=sys.stderr)
35
+ sys.exit(1)
36
+ scope_paths.append(normalized)
31
37
  save_index_scope(root, scope_paths)
32
38
  print(f"Index scope: {', '.join(scope_paths)}", file=sys.stderr)
33
39
  print(
34
- "Warning: scoped reindex replaces the cache with only these projects; "
35
- "run reindex with no --path to restore the full index",
40
+ (
41
+ "Warning: scoped reindex replaces the cache with only these projects; "
42
+ "run reindex with no --path to restore the full index"
43
+ ),
36
44
  file=sys.stderr,
37
45
  )
38
46
  else:
@@ -48,7 +56,7 @@ def main(args):
48
56
  with index_build_lock(cache_dir):
49
57
  cleanup_in_progress_index(cache_dir)
50
58
  try:
51
- _output_db, skipped, total = _index_project(root, lang, cache_dir, replace=True, log=False)
59
+ _output_db, skipped, total = index_project(root, lang, cache_dir, replace=True, log=False)
52
60
  except RuntimeError as e:
53
61
  cleanup_in_progress_index(cache_dir)
54
62
  print(f"Error: {e}", file=sys.stderr)
@@ -1,5 +1,7 @@
1
1
  """search command - search symbols by pattern."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import re
4
6
  import sys
5
7
 
@@ -117,12 +119,12 @@ def _qualified_pattern(pattern: str) -> bool:
117
119
  def _search_results_from_symbols(db, project_root, symbols, limit):
118
120
  """Turn resolve_symbol rows into search result tuples."""
119
121
  symbols = limit_and_warn(symbols, limit, "results")
120
- results = []
122
+ results: list[tuple[str, int | str, str, str]] = []
121
123
  for symbol_id, symbol_str, _display_name in symbols:
122
124
  loc = resolve_def_location(db, project_root, symbol_id, symbol_str)
123
125
  if loc:
124
126
  file_path, start_line, _end_line = loc
125
- line = start_line + 1
127
+ line: int | str = start_line + 1
126
128
  else:
127
129
  file_path = _resolve_file_path(db, symbol_str)
128
130
  line = "?"
@@ -229,7 +231,7 @@ def main(args):
229
231
  loc = resolve_def_location(db, project_root, symbol_id, symbol_str)
230
232
  if loc:
231
233
  file_path, resolved_start, _end = loc
232
- line = resolved_start + 1
234
+ line: int | str = resolved_start + 1
233
235
  else:
234
236
  file_path = _resolve_file_path(db, symbol_str, doc_path)
235
237
  line = start_line + 1 if start_line is not None else "?"
@@ -23,7 +23,7 @@ _SKIP_DIR_NAMES = frozenset(
23
23
  )
24
24
 
25
25
 
26
- def _read_json(path: Path) -> dict | None:
26
+ def _read_json(path: Path) -> dict[str, object] | None:
27
27
  try:
28
28
  return json.loads(path.read_text(encoding="utf-8"))
29
29
  except (OSError, json.JSONDecodeError):
@@ -52,7 +52,9 @@ def _tsconfig_covers_subdirectories(tsconfig_path: Path) -> bool:
52
52
  if not data:
53
53
  return False
54
54
  include = data.get("include")
55
- if include is None or not include:
55
+ if isinstance(include, str):
56
+ include = [include]
57
+ if not isinstance(include, list) or not include:
56
58
  return False
57
59
  return any(isinstance(pattern, str) and ("**" in pattern or "/" in pattern) for pattern in include)
58
60
 
@@ -1,5 +1,7 @@
1
1
  """SCIP index building and SQLite database access."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import os
4
6
  import re
5
7
  import shutil
@@ -9,7 +11,6 @@ import sys
9
11
  import tempfile
10
12
  from concurrent.futures import ThreadPoolExecutor, as_completed
11
13
  from pathlib import Path
12
- from typing import Optional
13
14
 
14
15
  from .cache import (
15
16
  cleanup_in_progress_index,
@@ -59,7 +60,7 @@ def log_index_complete(
59
60
  db_path: Path,
60
61
  lang: str,
61
62
  *,
62
- projects: Optional[int] = None,
63
+ projects: int | None = None,
63
64
  skipped: int = 0,
64
65
  ) -> None:
65
66
  """One-line stderr summary after a successful index write."""
@@ -188,8 +189,8 @@ def _warn_old_scip(binary):
188
189
  _scip_version_warned = True
189
190
  print(
190
191
  f"Warning: {binary} {'.'.join(map(str, version))} is older than 0.8.0; "
191
- "upgrade from "
192
- f"{SCIP_INSTALL_URL} if indexing fails.",
192
+ + "upgrade from "
193
+ + f"{SCIP_INSTALL_URL} if indexing fails.",
193
194
  file=sys.stderr,
194
195
  )
195
196
 
@@ -388,6 +389,7 @@ def _index_typescript(root, cache_dir, projects, env, *, replace=False):
388
389
  ): index
389
390
  for index, project in enumerate(projects, start=1)
390
391
  }
392
+ indexed_parts: list[tuple[int, Path]] = []
391
393
  for future in as_completed(futures):
392
394
  label, db_path, error = future.result()
393
395
  completed += 1
@@ -395,10 +397,10 @@ def _index_typescript(root, cache_dir, projects, env, *, replace=False):
395
397
  skipped += 1
396
398
  print(f"Warning: skipped {label}: {error}", file=sys.stderr)
397
399
  else:
398
- part_dbs.append((futures[future], db_path))
400
+ indexed_parts.append((futures[future], db_path))
399
401
  if show_progress:
400
402
  print(f"Indexed {completed}/{total}: {label}", file=sys.stderr)
401
- part_dbs = [db for _, db in sorted(part_dbs, key=lambda item: item[0])]
403
+ part_dbs = [db for _, db in sorted(indexed_parts, key=lambda item: item[0])]
402
404
  else:
403
405
  for index, project in enumerate(projects, start=1):
404
406
  label = "." if project == Path(".") else str(project)
@@ -427,7 +429,7 @@ def _index_typescript(root, cache_dir, projects, env, *, replace=False):
427
429
  return output_db, len(part_dbs), skipped, total
428
430
 
429
431
 
430
- def _index_project(root, lang, cache_dir, *, replace=False, log=True):
432
+ def index_project(root, lang, cache_dir, *, replace=False, log=True):
431
433
  """Run the language-specific indexer and convert to DB."""
432
434
  from .project import Language
433
435
 
@@ -497,7 +499,7 @@ def get_db(project_root=None):
497
499
  else:
498
500
  cleanup_in_progress_index(cache_dir)
499
501
  try:
500
- _output_db, skipped, total = _index_project(root, lang, cache_dir, replace=True, log=False)
502
+ _output_db, skipped, total = index_project(root, lang, cache_dir, replace=True, log=False)
501
503
  promote_next_index(cache_dir)
502
504
  log_index_complete(
503
505
  index_db_path(cache_dir),
@@ -153,7 +153,9 @@ def print_def_truncation_notice(query_name, body_offset, lines_shown, def_body_l
153
153
  return
154
154
  at_line = body_offset + lines_shown
155
155
  print(
156
- f"Warning: truncated at line {at_line}/{def_body_lines} of definition. "
157
- f"Continue: code --offset {next_offset} {query_name}",
156
+ (
157
+ f"Warning: truncated at line {at_line}/{def_body_lines} of definition. "
158
+ f"Continue: code --offset {next_offset} {query_name}"
159
+ ),
158
160
  file=sys.stderr,
159
161
  )
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import sqlite3
5
6
  from pathlib import Path
6
7
 
7
8
  from .sql import debug_execute, escape_like
@@ -33,7 +34,7 @@ def path_in_scope(relative_path: str, scope: str | None) -> bool:
33
34
  return relative_path.startswith(scope.rstrip("/") + "/")
34
35
 
35
36
 
36
- def path_filter_sql(db, scope: str | None, doc_alias: str = "d") -> tuple[str, list]:
37
+ def path_filter_sql(db: sqlite3.Connection, scope: str | None, doc_alias: str = "d") -> tuple[str, list[str]]:
37
38
  """Build a SQL AND-clause that restricts rows to a file or directory scope."""
38
39
  if not scope:
39
40
  return "", []
@@ -49,13 +50,13 @@ def path_filter_sql(db, scope: str | None, doc_alias: str = "d") -> tuple[str, l
49
50
  return f" AND ({col} = ? OR {col} LIKE ? ESCAPE '\\')", [scope, f"{escaped}/%"]
50
51
 
51
52
 
52
- def path_filter_sql_any(db, scope: str | None, *doc_aliases: str) -> tuple[str, list]:
53
+ def path_filter_sql_any(db: sqlite3.Connection, scope: str | None, *doc_aliases: str) -> tuple[str, list[str]]:
53
54
  """AND-clause: row matches when any document alias column falls in scope."""
54
55
  if not scope:
55
56
  return "", []
56
57
 
57
58
  parts: list[str] = []
58
- params: list = []
59
+ params: list[str] = []
59
60
  for alias in doc_aliases:
60
61
  clause, clause_params = path_filter_sql(db, scope, doc_alias=alias)
61
62
  if not clause:
@@ -69,7 +70,7 @@ def path_filter_sql_any(db, scope: str | None, *doc_aliases: str) -> tuple[str,
69
70
  return " AND (" + " OR ".join(parts) + ")", params
70
71
 
71
72
 
72
- def list_indexed_paths_in_scope(db, scope: str) -> list[str]:
73
+ def list_indexed_paths_in_scope(db: sqlite3.Connection, scope: str) -> list[str]:
73
74
  """Repo-relative document paths under scope (file or directory), sorted."""
74
75
  clause, params = path_filter_sql(db, scope, doc_alias="d")
75
76
  if not clause: