scip-cli 1.0.1__tar.gz → 1.0.3__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 (33) hide show
  1. {scip_cli-1.0.1/scip_cli.egg-info → scip_cli-1.0.3}/PKG-INFO +29 -22
  2. {scip_cli-1.0.1 → scip_cli-1.0.3}/README.md +27 -20
  3. {scip_cli-1.0.1 → scip_cli-1.0.3}/scip_cli/SKILL.md +7 -8
  4. {scip_cli-1.0.1 → scip_cli-1.0.3}/scip_cli/__init__.py +1 -1
  5. {scip_cli-1.0.1 → scip_cli-1.0.3}/scip_cli/__main__.py +28 -20
  6. scip_cli-1.0.3/scip_cli/commands/def_cmd.py +39 -0
  7. scip_cli-1.0.3/scip_cli/commands/members.py +85 -0
  8. scip_cli-1.0.3/scip_cli/commands/rdeps.py +39 -0
  9. {scip_cli-1.0.1 → scip_cli-1.0.3}/scip_cli/commands/refs.py +34 -58
  10. scip_cli-1.0.3/scip_cli/commands/reindex.py +26 -0
  11. scip_cli-1.0.3/scip_cli/commands/search.py +131 -0
  12. {scip_cli-1.0.1 → scip_cli-1.0.3}/scip_cli/commands/skill.py +1 -1
  13. scip_cli-1.0.3/scip_cli/commands/symbols.py +35 -0
  14. scip_cli-1.0.3/scip_cli/lib.py +462 -0
  15. {scip_cli-1.0.1 → scip_cli-1.0.3/scip_cli.egg-info}/PKG-INFO +29 -22
  16. {scip_cli-1.0.1 → scip_cli-1.0.3}/scip_cli.egg-info/SOURCES.txt +1 -0
  17. {scip_cli-1.0.1 → scip_cli-1.0.3}/setup.py +8 -2
  18. scip_cli-1.0.3/tests/test_pure_functions.py +393 -0
  19. scip_cli-1.0.1/scip_cli/commands/def_cmd.py +0 -50
  20. scip_cli-1.0.1/scip_cli/commands/members.py +0 -84
  21. scip_cli-1.0.1/scip_cli/commands/rdeps.py +0 -52
  22. scip_cli-1.0.1/scip_cli/commands/search.py +0 -120
  23. scip_cli-1.0.1/scip_cli/commands/symbols.py +0 -42
  24. scip_cli-1.0.1/scip_cli/lib.py +0 -369
  25. scip_cli-1.0.1/tests/test_pure_functions.py +0 -121
  26. {scip_cli-1.0.1 → scip_cli-1.0.3}/LICENSE +0 -0
  27. {scip_cli-1.0.1 → scip_cli-1.0.3}/MANIFEST.in +0 -0
  28. {scip_cli-1.0.1 → scip_cli-1.0.3}/pyproject.toml +0 -0
  29. {scip_cli-1.0.1 → scip_cli-1.0.3}/scip_cli/commands/__init__.py +0 -0
  30. {scip_cli-1.0.1 → scip_cli-1.0.3}/scip_cli.egg-info/dependency_links.txt +0 -0
  31. {scip_cli-1.0.1 → scip_cli-1.0.3}/scip_cli.egg-info/entry_points.txt +0 -0
  32. {scip_cli-1.0.1 → scip_cli-1.0.3}/scip_cli.egg-info/top_level.txt +0 -0
  33. {scip_cli-1.0.1 → scip_cli-1.0.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scip-cli
3
- Version: 1.0.1
3
+ Version: 1.0.3
4
4
  Summary: Fast code intelligence via SCIP indexes
5
5
  Home-page: https://github.com/flesler/scip-cli
6
6
  Author: Ariel Flesler
@@ -9,7 +9,7 @@ Classifier: Programming Language :: Python :: 3
9
9
  Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Operating System :: OS Independent
11
11
  Classifier: Topic :: Software Development :: Code Generators
12
- Requires-Python: >=3.7
12
+ Requires-Python: >=3.9
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
15
15
  Dynamic: author
@@ -27,11 +27,11 @@ Dynamic: summary
27
27
  [![PyPI version](https://badge.fury.io/py/scip-cli.svg)](https://badge.fury.io/py/scip-cli)
28
28
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
29
29
 
30
- Fast code intelligence CLI for TypeScript/JavaScript projects. Query SCIP indexes directly via SQLite for instant results.
30
+ Fast code intelligence CLI for TypeScript/JavaScript and Python projects. Query SCIP indexes directly via SQLite for instant results.
31
31
 
32
32
  ## Features
33
33
 
34
- - **Fast**: Direct SQLite queries, 100-500x faster than bash wrappers
34
+ - **Fast**: Direct SQLite queries, eliminating skippable overhead
35
35
  - **Simple**: Single binary with subcommands
36
36
  - **Auto-indexing**: Automatically indexes projects on first query
37
37
  - **Token-efficient**: Clean, minimal output optimized for AI consumption
@@ -44,17 +44,24 @@ Fast code intelligence CLI for TypeScript/JavaScript projects. Query SCIP indexe
44
44
  pip install scip-cli
45
45
  ```
46
46
 
47
- ### 2. Install prerequisites
47
+ ### 2. Install prerequisites (optional)
48
48
 
49
- scip-cli requires two external tools for indexing:
49
+ scip-cli can automatically download the required indexing tools when needed, or you can install them globally for faster performance:
50
50
 
51
- **For TypeScript/JavaScript projects:**
51
+ **Option A: Let scip-cli handle it (recommended)**
52
+
53
+ Just use scip-cli - it will automatically download the required tools via `npx` on first use. No global installation needed.
54
+
55
+ **Option B: Install globally for better performance**
52
56
 
53
57
  ```bash
54
- # Install scip-typescript (SCIP indexer)
58
+ # TypeScript/JavaScript indexer (also handles plain JS via --infer-tsconfig)
55
59
  npm install -g @sourcegraph/scip-typescript
56
60
 
57
- # Install scip (SCIP CLI for index conversion)
61
+ # Python indexer
62
+ npm install -g @sourcegraph/scip-python
63
+
64
+ # SCIP CLI for index conversion
58
65
  npm install -g @sourcegraph/scip
59
66
  ```
60
67
 
@@ -62,16 +69,10 @@ npm install -g @sourcegraph/scip
62
69
 
63
70
  ```bash
64
71
  scip-cli --help
65
- scip-typescript --version
66
- scip --version
72
+ scip-typescript --version # Only if you chose Option B
73
+ scip --version # Only if you chose Option B
67
74
  ```
68
75
 
69
- ## Prerequisites
70
-
71
- - Python 3.7+
72
- - `scip-typescript` (for indexing TypeScript/JavaScript)
73
- - `scip` CLI (for converting indexes)
74
-
75
76
  ## Usage
76
77
 
77
78
  All commands are subcommands of `scip-cli`:
@@ -88,6 +89,7 @@ scip-cli <command> [arguments]
88
89
  - `symbols <file>` - List all symbols in a file
89
90
  - `rdeps <file>` - Find files that depend on a file
90
91
  - `members <symbol>` - List members of a class/interface
92
+ - `reindex` - Force re-indexing of the current project
91
93
  - `skill [path]` - Install or dump the SKILL.md
92
94
 
93
95
  ### Examples
@@ -117,14 +119,16 @@ scip-cli skill ~/.claude/skills/scip/SKILL.md
117
119
 
118
120
  ## How It Works
119
121
 
120
- 1. On first query, automatically indexes the project using `scip-typescript`
121
- 2. Converts the SCIP index to SQLite using `scip expt-convert`
122
- 3. Caches the database in `~/.cache/scip-query/projects/<hash>/index.db`
123
- 4. Subsequent queries are instant SQLite lookups
122
+ 1. On first query, automatically detects project language from `package.json` (TS/JS) or `pyproject.toml`/`setup.py` (Python)
123
+ 2. Indexes using `scip-typescript` (adds `--infer-tsconfig` for JS-only projects) or `scip-python`
124
+ 3. Converts the SCIP index to SQLite using `scip expt-convert`
125
+ 4. Caches the database in `~/.cache/scip-cli/projects/<hash>/index.db`
126
+ 5. Subsequent queries are instant SQLite lookups
124
127
 
125
128
  ## Performance
126
129
 
127
- Compared to bash wrappers:
130
+ Inspired by [scip-query](https://github.com/PlunderStruck/scip-query), scip-cli is a lightweight Python reimplementation optimized for speed. Compared to the original bash wrapper scripts:
131
+
128
132
  - `refs`: 6.4s → 0.03s (213x faster)
129
133
  - `def`: 2.8s → 0.05s (56x faster)
130
134
  - `search`: 2.6s → 0.03s (87x faster)
@@ -132,6 +136,8 @@ Compared to bash wrappers:
132
136
  - `rdeps`: 0.2s → 0.02s (10x faster)
133
137
  - `members`: 3.1s → 0.03s (103x faster)
134
138
 
139
+ The speedup comes from direct SQLite queries instead of shell command chains, eliminating subprocess overhead.
140
+
135
141
  ## Architecture
136
142
 
137
143
  ```
@@ -146,6 +152,7 @@ scip_cli/
146
152
  ├── symbols.py
147
153
  ├── rdeps.py
148
154
  ├── members.py
155
+ ├── reindex.py
149
156
  └── skill.py
150
157
  ```
151
158
 
@@ -3,11 +3,11 @@
3
3
  [![PyPI version](https://badge.fury.io/py/scip-cli.svg)](https://badge.fury.io/py/scip-cli)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- Fast code intelligence CLI for TypeScript/JavaScript projects. Query SCIP indexes directly via SQLite for instant results.
6
+ Fast code intelligence CLI for TypeScript/JavaScript and Python projects. Query SCIP indexes directly via SQLite for instant results.
7
7
 
8
8
  ## Features
9
9
 
10
- - **Fast**: Direct SQLite queries, 100-500x faster than bash wrappers
10
+ - **Fast**: Direct SQLite queries, eliminating skippable overhead
11
11
  - **Simple**: Single binary with subcommands
12
12
  - **Auto-indexing**: Automatically indexes projects on first query
13
13
  - **Token-efficient**: Clean, minimal output optimized for AI consumption
@@ -20,17 +20,24 @@ Fast code intelligence CLI for TypeScript/JavaScript projects. Query SCIP indexe
20
20
  pip install scip-cli
21
21
  ```
22
22
 
23
- ### 2. Install prerequisites
23
+ ### 2. Install prerequisites (optional)
24
24
 
25
- scip-cli requires two external tools for indexing:
25
+ scip-cli can automatically download the required indexing tools when needed, or you can install them globally for faster performance:
26
26
 
27
- **For TypeScript/JavaScript projects:**
27
+ **Option A: Let scip-cli handle it (recommended)**
28
+
29
+ Just use scip-cli - it will automatically download the required tools via `npx` on first use. No global installation needed.
30
+
31
+ **Option B: Install globally for better performance**
28
32
 
29
33
  ```bash
30
- # Install scip-typescript (SCIP indexer)
34
+ # TypeScript/JavaScript indexer (also handles plain JS via --infer-tsconfig)
31
35
  npm install -g @sourcegraph/scip-typescript
32
36
 
33
- # Install scip (SCIP CLI for index conversion)
37
+ # Python indexer
38
+ npm install -g @sourcegraph/scip-python
39
+
40
+ # SCIP CLI for index conversion
34
41
  npm install -g @sourcegraph/scip
35
42
  ```
36
43
 
@@ -38,16 +45,10 @@ npm install -g @sourcegraph/scip
38
45
 
39
46
  ```bash
40
47
  scip-cli --help
41
- scip-typescript --version
42
- scip --version
48
+ scip-typescript --version # Only if you chose Option B
49
+ scip --version # Only if you chose Option B
43
50
  ```
44
51
 
45
- ## Prerequisites
46
-
47
- - Python 3.7+
48
- - `scip-typescript` (for indexing TypeScript/JavaScript)
49
- - `scip` CLI (for converting indexes)
50
-
51
52
  ## Usage
52
53
 
53
54
  All commands are subcommands of `scip-cli`:
@@ -64,6 +65,7 @@ scip-cli <command> [arguments]
64
65
  - `symbols <file>` - List all symbols in a file
65
66
  - `rdeps <file>` - Find files that depend on a file
66
67
  - `members <symbol>` - List members of a class/interface
68
+ - `reindex` - Force re-indexing of the current project
67
69
  - `skill [path]` - Install or dump the SKILL.md
68
70
 
69
71
  ### Examples
@@ -93,14 +95,16 @@ scip-cli skill ~/.claude/skills/scip/SKILL.md
93
95
 
94
96
  ## How It Works
95
97
 
96
- 1. On first query, automatically indexes the project using `scip-typescript`
97
- 2. Converts the SCIP index to SQLite using `scip expt-convert`
98
- 3. Caches the database in `~/.cache/scip-query/projects/<hash>/index.db`
99
- 4. Subsequent queries are instant SQLite lookups
98
+ 1. On first query, automatically detects project language from `package.json` (TS/JS) or `pyproject.toml`/`setup.py` (Python)
99
+ 2. Indexes using `scip-typescript` (adds `--infer-tsconfig` for JS-only projects) or `scip-python`
100
+ 3. Converts the SCIP index to SQLite using `scip expt-convert`
101
+ 4. Caches the database in `~/.cache/scip-cli/projects/<hash>/index.db`
102
+ 5. Subsequent queries are instant SQLite lookups
100
103
 
101
104
  ## Performance
102
105
 
103
- Compared to bash wrappers:
106
+ Inspired by [scip-query](https://github.com/PlunderStruck/scip-query), scip-cli is a lightweight Python reimplementation optimized for speed. Compared to the original bash wrapper scripts:
107
+
104
108
  - `refs`: 6.4s → 0.03s (213x faster)
105
109
  - `def`: 2.8s → 0.05s (56x faster)
106
110
  - `search`: 2.6s → 0.03s (87x faster)
@@ -108,6 +112,8 @@ Compared to bash wrappers:
108
112
  - `rdeps`: 0.2s → 0.02s (10x faster)
109
113
  - `members`: 3.1s → 0.03s (103x faster)
110
114
 
115
+ The speedup comes from direct SQLite queries instead of shell command chains, eliminating subprocess overhead.
116
+
111
117
  ## Architecture
112
118
 
113
119
  ```
@@ -122,6 +128,7 @@ scip_cli/
122
128
  ├── symbols.py
123
129
  ├── rdeps.py
124
130
  ├── members.py
131
+ ├── reindex.py
125
132
  └── skill.py
126
133
  ```
127
134
 
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  name: scip-cli
3
- description: Read when needing to find symbols, definitions, references, or members in TypeScript/JavaScript code
3
+ description: Read when needing to find symbols, definitions, references, or members in TypeScript/JavaScript or Python code
4
4
  ---
5
5
 
6
- TypeScript/JavaScript only (.ts, .tsx, .js, .jsx) — not GraphQL, CSS, or other files.
6
+ TypeScript/JavaScript (.ts, .tsx, .js, .jsx) and Python (.py) — not GraphQL, CSS, or other files.
7
7
 
8
8
  All commands are sub-commands of `scip-cli`. Run from the project root.
9
9
 
@@ -20,20 +20,19 @@ All commands are sub-commands of `scip-cli`. Run from the project root.
20
20
 
21
21
  ## Gotchas
22
22
 
23
- - **Bare names** resolve functions, types (aliases + interfaces), and classes. Consts/variables need `def --type variable X` or `search --kind variable X`. Class methods need `members ClassName`, not bare `def methodName`.
24
- - **Ambiguous types** (e.g. `Opts` in multiple hooks) — `def` returns all; `refs` picks the first and warns. Use `search` or a qualified `src:…` name to disambiguate.
25
- - **First run** in a project may auto-index (one-time wait, ~10-30s for large codebases).
26
- - **Precision escape hatch**: qualified names like `src:hooks:usePatientEntries:usePatientEntries()` always work.
23
+ - **Bare names** resolve functions, types (aliases + interfaces), and classes. Consts/variables need `def --kind variable X` or `search --kind variable X`. Class methods need `members ClassName`, not bare `def methodName`.
24
+ - **Ambiguous types** (e.g. `Opts` in multiple hooks) — `def` returns all; `refs` picks the first and warns. Use `search` with a more specific pattern to disambiguate.
25
+ - **First run** in a project may auto-index (one-time wait, ~10-30s for large codebases). JS-only projects (no `tsconfig.json`) are supported automatically.
27
26
 
28
27
  ## Details
29
28
 
30
29
  ### def
31
30
 
32
31
  ```bash
33
- def [--type <kind>] <symbol>
32
+ def [--kind <kind>] <symbol>
34
33
  ```
35
34
 
36
- Kinds: `function`, `class`, `interface`, `type`, `method`, `variable` — use `--type` when the bare name isn't in the default set above.
35
+ Kinds: `function`, `method`, `class`, `property`, `variable` — use `--kind` when the bare name isn't in the default set above.
37
36
 
38
37
  ### refs
39
38
 
@@ -1,2 +1,2 @@
1
1
  """scip-cli: Fast code intelligence via SCIP indexes."""
2
- __version__ = "1.0.1"
2
+ __version__ = "1.0.3"
@@ -1,10 +1,10 @@
1
- #!/usr/bin/env python3
2
1
  """CLI entry point for scip-cli."""
3
2
  import argparse
4
3
  import sys
5
4
 
6
5
  from . import __version__
7
- from .commands import refs, def_cmd, search, symbols, rdeps, members, skill
6
+ from .lib import SymbolKind
7
+ from .commands import refs, def_cmd, search, symbols, rdeps, members, skill, reindex
8
8
 
9
9
 
10
10
  def main():
@@ -21,12 +21,12 @@ def main():
21
21
 
22
22
  # def
23
23
  def_parser = subparsers.add_parser("def", help="Find symbol definition")
24
- def_parser.add_argument("--type", dest="kind", help="Filter by kind (function, class, etc)")
24
+ def_parser.add_argument("--kind", choices=SymbolKind.filterable_values(), help="Filter by kind")
25
25
  def_parser.add_argument("symbol", help="Symbol name")
26
26
 
27
27
  # search
28
28
  search_parser = subparsers.add_parser("search", help="Search symbols by pattern")
29
- search_parser.add_argument("--kind", help="Filter by kind")
29
+ search_parser.add_argument("--kind", choices=SymbolKind.filterable_values(), help="Filter by kind")
30
30
  search_parser.add_argument("pattern", help="Search pattern")
31
31
 
32
32
  # symbols
@@ -45,27 +45,35 @@ def main():
45
45
  skill_parser = subparsers.add_parser("skill", help="Install or dump the scip-cli SKILL.md")
46
46
  skill_parser.add_argument("path", nargs="?", help="Optional file path to write to (creates dirs)")
47
47
 
48
+ # reindex
49
+ subparsers.add_parser("reindex", help="Force re-indexing of the current project")
50
+
48
51
  args = parser.parse_args()
49
52
 
50
- if not args.command:
53
+ # Dispatch to command handlers
54
+ dispatch = {
55
+ "refs": refs.main,
56
+ "def": def_cmd.main,
57
+ "search": search.main,
58
+ "symbols": symbols.main,
59
+ "rdeps": rdeps.main,
60
+ "members": members.main,
61
+ "skill": skill.main,
62
+ "reindex": reindex.main,
63
+ }
64
+
65
+ handler = dispatch.get(args.command)
66
+ if not handler:
51
67
  parser.print_help()
52
68
  sys.exit(1)
53
69
 
54
- # Dispatch to command handlers
55
- if args.command == "refs":
56
- refs.main(args)
57
- elif args.command == "def":
58
- def_cmd.main(args)
59
- elif args.command == "search":
60
- search.main(args)
61
- elif args.command == "symbols":
62
- symbols.main(args)
63
- elif args.command == "rdeps":
64
- rdeps.main(args)
65
- elif args.command == "members":
66
- members.main(args)
67
- elif args.command == "skill":
68
- skill.main(args)
70
+ try:
71
+ handler(args)
72
+ except RuntimeError as e:
73
+ print(f"Error: {e}", file=sys.stderr)
74
+ sys.exit(1)
75
+ except KeyboardInterrupt:
76
+ sys.exit(130)
69
77
 
70
78
 
71
79
  if __name__ == "__main__":
@@ -0,0 +1,39 @@
1
+ """def command - find symbol definitions."""
2
+ import sys
3
+
4
+ from ..lib import (
5
+ setup,
6
+ resolve_symbol,
7
+ read_source_lines,
8
+ infer_kind,
9
+ get_def_location,
10
+ )
11
+
12
+
13
+ def main(args):
14
+ """Find the definition of a symbol."""
15
+ db, project_root = setup()
16
+ try:
17
+ symbols = resolve_symbol(db, args.symbol, args.kind)
18
+ if not symbols:
19
+ print(f"Symbol '{args.symbol}' not found", file=sys.stderr)
20
+ sys.exit(1)
21
+
22
+ for symbol_id, symbol_str, display_name in symbols:
23
+ row = get_def_location(db, symbol_id)
24
+ if not row:
25
+ continue
26
+
27
+ rel_path, start_line, end_line = row
28
+ kind = infer_kind(symbol_str)
29
+
30
+ lines = read_source_lines(project_root, rel_path, start_line, end_line)
31
+ if lines is None:
32
+ source_snippet = "(could not read source)"
33
+ else:
34
+ source_snippet = ''.join(lines).rstrip('\n')
35
+
36
+ print(f"{rel_path}:{start_line + 1}:{end_line + 1}")
37
+ print(source_snippet)
38
+ finally:
39
+ db.close()
@@ -0,0 +1,85 @@
1
+ """members command - list members of a class/interface."""
2
+ import sys
3
+ import re
4
+
5
+ from ..lib import (
6
+ setup,
7
+ resolve_one_symbol,
8
+ get_members,
9
+ get_def_location,
10
+ infer_kind,
11
+ extract_leaf_name,
12
+ read_source_lines,
13
+ SymbolKind,
14
+ )
15
+
16
+
17
+ def _member_source_patterns(member_symbol, short, kind):
18
+ """Build TS/JS and Python regex patterns for finding a member's source line."""
19
+ if "<constructor>" in member_symbol:
20
+ ts_pattern = r'^\s*constructor\s*\('
21
+ elif "<get>" in member_symbol:
22
+ ts_pattern = rf'^\s*(?:public\s+|private\s+|protected\s+|static\s+|readonly\s+)*get\s+{re.escape(short)}\s*\('
23
+ elif "<set>" in member_symbol:
24
+ ts_pattern = rf'^\s*(?:public\s+|private\s+|protected\s+|static\s+|readonly\s+)*set\s+{re.escape(short)}\s*\('
25
+ else:
26
+ ts_pattern = rf'^\s*(?:public\s+|private\s+|protected\s+|static\s+|readonly\s+)*{re.escape(short)}\s*\??\s*[:=(]'
27
+
28
+ py_pattern = None
29
+ if kind == SymbolKind.METHOD:
30
+ py_pattern = rf'^\s*(?:async\s+)?def\s+{re.escape(short)}\s*\('
31
+ elif kind == SymbolKind.PROPERTY:
32
+ py_pattern = rf'^\s*{re.escape(short)}\s*[=:]'
33
+ elif kind == SymbolKind.CLASS:
34
+ py_pattern = rf'^\s*class\s+{re.escape(short)}\s*[:\(]'
35
+
36
+ return ts_pattern, py_pattern
37
+
38
+
39
+ def main(args):
40
+ """List members of a class or interface."""
41
+ db, project_root = setup()
42
+ try:
43
+ symbol_id, _, _ = resolve_one_symbol(db, args.symbol)
44
+ members = get_members(db, symbol_id)
45
+
46
+ if not members:
47
+ print(f"No members found for '{args.symbol}'", file=sys.stderr)
48
+ sys.exit(1)
49
+
50
+ parent_def = get_def_location(db, symbol_id)
51
+ parent_file = parent_def[0] if parent_def else None
52
+ parent_start = parent_def[1] if parent_def else None
53
+ parent_end = parent_def[2] if parent_def else None
54
+
55
+ needs_lookup = any(m[3] is None for m in members)
56
+ source_lines = None
57
+ if needs_lookup and project_root and parent_file and parent_start is not None:
58
+ source_lines = read_source_lines(project_root, parent_file, parent_start, parent_end)
59
+
60
+ for member_id, member_symbol, member_name, start_line, end_line in members:
61
+ kind = infer_kind(member_symbol)
62
+ short = extract_leaf_name(member_symbol)
63
+
64
+ if start_line is None and source_lines:
65
+ ts_pattern, py_pattern = _member_source_patterns(member_symbol, short, kind)
66
+ patterns = []
67
+ if parent_file and parent_file.endswith('.py'):
68
+ if py_pattern:
69
+ patterns.append(py_pattern)
70
+ patterns.append(ts_pattern)
71
+ else:
72
+ patterns.append(ts_pattern)
73
+ if py_pattern:
74
+ patterns.append(py_pattern)
75
+
76
+ for i, line in enumerate(source_lines):
77
+ if any(re.match(p, line) for p in patterns):
78
+ start_line = parent_start + i
79
+ end_line = start_line
80
+ break
81
+
82
+ line_info = f"{start_line + 1}:{end_line + 1}" if start_line is not None else "??"
83
+ print(f"{line_info} {kind} {short}")
84
+ finally:
85
+ db.close()
@@ -0,0 +1,39 @@
1
+ """rdeps command - find reverse dependencies of a file."""
2
+ import sys
3
+
4
+ from ..lib import (
5
+ setup,
6
+ resolve_one_file,
7
+ get_file_symbols,
8
+ get_refs_for_symbols,
9
+ )
10
+
11
+
12
+ def main(args):
13
+ """Find all files that import from this file."""
14
+ db, _ = setup()
15
+ try:
16
+ file_path = resolve_one_file(db, args.file)
17
+
18
+ symbols = get_file_symbols(db, file_path)
19
+ if not symbols:
20
+ print(f"No symbols found in '{file_path}'", file=sys.stderr)
21
+ sys.exit(1)
22
+
23
+ symbol_ids = [s[0] for s in symbols]
24
+ refs = get_refs_for_symbols(db, symbol_ids)
25
+
26
+ rdeps = set()
27
+ for symbol_id, ref_list in refs.items():
28
+ for ref_path, ref_line in ref_list:
29
+ if ref_path != file_path:
30
+ rdeps.add(ref_path)
31
+
32
+ if not rdeps:
33
+ print(f"No reverse dependencies found for '{file_path}'", file=sys.stderr)
34
+ sys.exit(1)
35
+
36
+ for dep_path in sorted(rdeps):
37
+ print(dep_path)
38
+ finally:
39
+ db.close()
@@ -2,10 +2,8 @@
2
2
  import sys
3
3
 
4
4
  from ..lib import (
5
- get_db,
6
- resolve_symbol,
7
- warn_ambiguous,
8
- find_project_root,
5
+ setup,
6
+ resolve_one_symbol,
9
7
  read_source_lines,
10
8
  extract_leaf_name,
11
9
  )
@@ -13,14 +11,12 @@ from ..lib import (
13
11
 
14
12
  def get_exact_refs(db, symbol_id, project_root):
15
13
  """Get references with exact line numbers by reading source files."""
16
- # Get symbol name once
17
14
  sym_row = db.execute("SELECT symbol FROM global_symbols WHERE id = ?", (symbol_id,)).fetchone()
18
15
  if not sym_row:
19
16
  return []
20
-
17
+
21
18
  leaf = extract_leaf_name(sym_row[0])
22
-
23
- # Get all chunks that reference this symbol
19
+
24
20
  chunks = db.execute("""
25
21
  SELECT c.id, c.document_id, c.start_line, c.end_line, d.relative_path
26
22
  FROM mentions m
@@ -28,80 +24,60 @@ def get_exact_refs(db, symbol_id, project_root):
28
24
  JOIN documents d ON c.document_id = d.id
29
25
  WHERE m.symbol_id = ? AND m.role != 1
30
26
  """, (symbol_id,)).fetchall()
31
-
27
+
32
28
  if not chunks:
33
29
  return []
34
-
35
- # Group by document
30
+
36
31
  by_doc = {}
37
32
  for chunk_id, doc_id, start_line, end_line, rel_path in chunks:
38
33
  if doc_id not in by_doc:
39
34
  by_doc[doc_id] = {'path': rel_path, 'chunks': []}
40
35
  by_doc[doc_id]['chunks'].append((chunk_id, start_line, end_line))
41
-
36
+
42
37
  results = []
43
-
44
- # For each document, read source and find exact lines
38
+
45
39
  for doc_id, info in by_doc.items():
46
40
  rel_path = info['path']
47
-
48
- # Get min/max line range for this document
49
41
  min_line = min(c[1] for c in info['chunks'])
50
42
  max_line = max(c[2] for c in info['chunks'])
51
-
52
- # Read only the needed range
43
+
53
44
  lines = read_source_lines(project_root, rel_path, min_line, max_line)
54
45
  if lines is None:
55
- # If can't read file, fall back to chunk start lines
56
46
  for chunk_id, start_line, end_line in info['chunks']:
57
47
  results.append((rel_path, start_line + 1))
58
48
  continue
59
-
60
- # Search for the symbol in each chunk's line range
49
+
61
50
  for chunk_id, start_line, end_line in info['chunks']:
62
- # Search within the chunk range (adjusted for offset)
63
51
  offset = min_line
52
+ found = False
64
53
  for line_idx in range(start_line - offset, min(end_line - offset + 1, len(lines))):
65
- line = lines[line_idx]
66
- # Simple check: does the line contain the symbol name?
67
- if leaf in line:
54
+ if leaf in lines[line_idx]:
68
55
  results.append((rel_path, line_idx + offset + 1))
69
- break # One match per chunk is enough
70
- else:
71
- # Fallback to chunk start line
56
+ found = True
57
+ break
58
+ if not found:
72
59
  results.append((rel_path, start_line + 1))
73
-
60
+
74
61
  return results
75
62
 
76
63
 
77
64
  def main(args):
78
65
  """Find all references to a symbol."""
79
- db = get_db()
80
-
81
- symbols = resolve_symbol(db, args.symbol)
82
- if not symbols:
83
- print(f"Symbol '{args.symbol}' not found", file=sys.stderr)
84
- sys.exit(1)
85
-
86
- warn_ambiguous(args.symbol, symbols, "symbol")
87
-
88
- symbol_id, symbol_str, display_name = symbols[0]
89
-
90
- project_root = find_project_root()
91
- if not project_root:
92
- print("Error: Could not find project root", file=sys.stderr)
93
- sys.exit(1)
94
-
95
- refs = get_exact_refs(db, symbol_id, project_root)
96
-
97
- if not refs:
98
- print(f"No references found for '{args.symbol}'", file=sys.stderr)
99
- sys.exit(1)
100
-
101
- # Deduplicate and sort
102
- seen = set()
103
- for path, line in refs:
104
- key = (path, line)
105
- if key not in seen:
106
- seen.add(key)
107
- print(f"{path}:{line}")
66
+ db, project_root = setup()
67
+ try:
68
+ symbol_id, _, _ = resolve_one_symbol(db, args.symbol)
69
+
70
+ refs = get_exact_refs(db, symbol_id, project_root)
71
+
72
+ if not refs:
73
+ print(f"No references found for '{args.symbol}'", file=sys.stderr)
74
+ sys.exit(1)
75
+
76
+ seen = set()
77
+ for path, line in refs:
78
+ key = (path, line)
79
+ if key not in seen:
80
+ seen.add(key)
81
+ print(f"{path}:{line}")
82
+ finally:
83
+ db.close()