sampler-cli 0.4.3__tar.gz → 0.4.4__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.
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/PKG-INFO +6 -5
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/README.md +5 -4
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/pyproject.toml +1 -1
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/__init__.py +1 -1
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/cli/main.py +19 -3
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/db.py +18 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/indexer/builder.py +2 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/indexer/discover.py +5 -2
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/indexer/imports.py +1 -1
- sampler_cli-0.4.4/src/sampler/indexer/parsers/vue.py +73 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/query/engine.py +3 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler_cli.egg-info/PKG-INFO +6 -5
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler_cli.egg-info/SOURCES.txt +3 -1
- sampler_cli-0.4.4/tests/test_db.py +46 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_discover.py +4 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_stale_code.py +2 -0
- sampler_cli-0.4.4/tests/test_vue_parser.py +91 -0
- sampler_cli-0.4.3/tests/test_db.py +0 -22
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/LICENSE +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/setup.cfg +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/__main__.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/cli/__init__.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/cli/render.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/config.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/embeddings.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/indexer/__init__.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/indexer/embedder.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/indexer/parsers/__init__.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/indexer/parsers/base.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/indexer/parsers/go.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/indexer/parsers/python.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/indexer/parsers/typescript.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/indexer/store.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/mcp/__init__.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/mcp/server.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/models.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/query/__init__.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/query/semantic.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/viz/__init__.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/viz/bus.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/viz/canvas.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/viz/discover_emit.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/viz/engine.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/viz/events.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/viz/headline.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/viz/layout_algo.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/viz/live.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/viz/pipeline.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler_cli.egg-info/dependency_links.txt +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler_cli.egg-info/entry_points.txt +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler_cli.egg-info/requires.txt +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler_cli.egg-info/top_level.txt +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_canvas_graph.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_cli.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_config.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_embeddings.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_events.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_go_parser.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_headline.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_imports.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_index_query.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_python_parser.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_relationships.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_render_bars.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_semantic.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_smoke.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_typescript_parser.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_viz_engine.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_viz_layout.py +0 -0
- {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_viz_pipeline.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sampler-cli
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.4
|
|
4
4
|
Summary: Token-efficient CLI for indexing and searching code symbols (Python-first, designed for minimal LLM/agent context size)
|
|
5
5
|
Author: Samuel Ignacio Carmona Rodriguez
|
|
6
6
|
License: MIT
|
|
@@ -52,7 +52,7 @@ Dynamic: license-file
|
|
|
52
52
|
|
|
53
53
|
Token-efficient CLI for indexing and searching code symbols across multiple projects.
|
|
54
54
|
|
|
55
|
-
Current version: 0.4.
|
|
55
|
+
Current version: 0.4.4
|
|
56
56
|
|
|
57
57
|
Designed for humans and agents: compact default output, short paths, and low-noise symbol views.
|
|
58
58
|
|
|
@@ -107,7 +107,7 @@ Relationships:
|
|
|
107
107
|
- Selector alternativo: `<path>:<symbol>` (ej. `app/utils/helpers.py:format_kda`)
|
|
108
108
|
|
|
109
109
|
Project management:
|
|
110
|
-
- `sampler project add <name> <path> --language <python|go|typescript|javascript|auto>`
|
|
110
|
+
- `sampler project add <name> <path> --language <python|go|typescript|javascript|vue|auto>`
|
|
111
111
|
- `sampler project update <name> [--path <abs-path>] [--language <lang>]`
|
|
112
112
|
- `sampler project list`
|
|
113
113
|
- `sampler project deps <name>`
|
|
@@ -160,7 +160,8 @@ Offline / air-gapped: `provider: hash` (or just don't install the embeddings ext
|
|
|
160
160
|
- Python parser: stdlib AST (stable)
|
|
161
161
|
- Go parser: tree-sitter-go (real extraction)
|
|
162
162
|
- TypeScript/JavaScript parser: tree-sitter-typescript (real extraction)
|
|
163
|
-
-
|
|
163
|
+
- Vue parser: extracts `<script>`/`<script setup>` + delegates to TS/JS parser (supports lang=ts/js etc.)
|
|
164
|
+
- `--language auto`: per-file language detection for monorepos/multi-language projects (for auto projects, `project list` shows detected languages + file % breakdown)
|
|
164
165
|
|
|
165
166
|
## Stale Code Detection
|
|
166
167
|
|
|
@@ -174,7 +175,7 @@ Test file detection supports common multi-language patterns:
|
|
|
174
175
|
|
|
175
176
|
- Python: `tests/`, `test_*.py`, `*_test.py`
|
|
176
177
|
- Go: `*_test.go`
|
|
177
|
-
- TypeScript/JavaScript: `__tests__/`, `test/`, `spec/`, `*.test.*`, `*.spec.*`
|
|
178
|
+
- TypeScript/JavaScript/Vue: `__tests__/`, `test/`, `spec/`, `*.test.*`, `*.spec.*` (incl. `*.test.vue`)
|
|
178
179
|
|
|
179
180
|
This is heuristic signal, not guaranteed dead-code proof.
|
|
180
181
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Token-efficient CLI for indexing and searching code symbols across multiple projects.
|
|
4
4
|
|
|
5
|
-
Current version: 0.4.
|
|
5
|
+
Current version: 0.4.4
|
|
6
6
|
|
|
7
7
|
Designed for humans and agents: compact default output, short paths, and low-noise symbol views.
|
|
8
8
|
|
|
@@ -57,7 +57,7 @@ Relationships:
|
|
|
57
57
|
- Selector alternativo: `<path>:<symbol>` (ej. `app/utils/helpers.py:format_kda`)
|
|
58
58
|
|
|
59
59
|
Project management:
|
|
60
|
-
- `sampler project add <name> <path> --language <python|go|typescript|javascript|auto>`
|
|
60
|
+
- `sampler project add <name> <path> --language <python|go|typescript|javascript|vue|auto>`
|
|
61
61
|
- `sampler project update <name> [--path <abs-path>] [--language <lang>]`
|
|
62
62
|
- `sampler project list`
|
|
63
63
|
- `sampler project deps <name>`
|
|
@@ -110,7 +110,8 @@ Offline / air-gapped: `provider: hash` (or just don't install the embeddings ext
|
|
|
110
110
|
- Python parser: stdlib AST (stable)
|
|
111
111
|
- Go parser: tree-sitter-go (real extraction)
|
|
112
112
|
- TypeScript/JavaScript parser: tree-sitter-typescript (real extraction)
|
|
113
|
-
-
|
|
113
|
+
- Vue parser: extracts `<script>`/`<script setup>` + delegates to TS/JS parser (supports lang=ts/js etc.)
|
|
114
|
+
- `--language auto`: per-file language detection for monorepos/multi-language projects (for auto projects, `project list` shows detected languages + file % breakdown)
|
|
114
115
|
|
|
115
116
|
## Stale Code Detection
|
|
116
117
|
|
|
@@ -124,7 +125,7 @@ Test file detection supports common multi-language patterns:
|
|
|
124
125
|
|
|
125
126
|
- Python: `tests/`, `test_*.py`, `*_test.py`
|
|
126
127
|
- Go: `*_test.go`
|
|
127
|
-
- TypeScript/JavaScript: `__tests__/`, `test/`, `spec/`, `*.test.*`, `*.spec.*`
|
|
128
|
+
- TypeScript/JavaScript/Vue: `__tests__/`, `test/`, `spec/`, `*.test.*`, `*.spec.*` (incl. `*.test.vue`)
|
|
128
129
|
|
|
129
130
|
This is heuristic signal, not guaranteed dead-code proof.
|
|
130
131
|
|
|
@@ -183,6 +183,8 @@ def project_list() -> None:
|
|
|
183
183
|
table.add_column("Language", style="green")
|
|
184
184
|
table.add_column("Enabled", justify="center")
|
|
185
185
|
|
|
186
|
+
db = _database()
|
|
187
|
+
|
|
186
188
|
for p in projects:
|
|
187
189
|
try:
|
|
188
190
|
pp = Path(p.path).resolve()
|
|
@@ -195,7 +197,21 @@ def project_list() -> None:
|
|
|
195
197
|
except Exception:
|
|
196
198
|
disp = p.path
|
|
197
199
|
enabled = "[green]yes[/green]" if p.enabled else "[dim]no[/dim]"
|
|
198
|
-
|
|
200
|
+
|
|
201
|
+
lang_display = p.language
|
|
202
|
+
if (p.language or "").lower() == "auto":
|
|
203
|
+
breakdown = db.get_project_language_breakdown(p.name)
|
|
204
|
+
total = sum(breakdown.values()) or 1
|
|
205
|
+
parts = []
|
|
206
|
+
for lang, cnt in sorted(breakdown.items(), key=lambda kv: -kv[1])[:4]: # top 4 for brevity
|
|
207
|
+
pct = int(round(cnt * 100 / total))
|
|
208
|
+
parts.append(f"{lang} {pct}%")
|
|
209
|
+
if parts:
|
|
210
|
+
lang_display = f"auto ({', '.join(parts)})"
|
|
211
|
+
else:
|
|
212
|
+
lang_display = "auto (no files yet)"
|
|
213
|
+
|
|
214
|
+
table.add_row(p.name, disp, lang_display, enabled)
|
|
199
215
|
|
|
200
216
|
console.print(table)
|
|
201
217
|
|
|
@@ -205,7 +221,7 @@ def project_add(
|
|
|
205
221
|
name: str,
|
|
206
222
|
path: str,
|
|
207
223
|
language: str = typer.Option(
|
|
208
|
-
"python", "--language", help="python, go, typescript, javascript, or 'auto' for monorepos"
|
|
224
|
+
"python", "--language", help="python, go, typescript, javascript, vue, or 'auto' for monorepos"
|
|
209
225
|
),
|
|
210
226
|
) -> None:
|
|
211
227
|
"""Register project in global config."""
|
|
@@ -232,7 +248,7 @@ def project_remove(name: str) -> None:
|
|
|
232
248
|
def project_update(
|
|
233
249
|
name: str,
|
|
234
250
|
path: str | None = typer.Option(None, "--path", help="New absolute path for the project"),
|
|
235
|
-
language: str | None = typer.Option(None, "--language", help="New language (
|
|
251
|
+
language: str | None = typer.Option(None, "--language", help="New language (python|go|typescript|javascript|vue|auto)"),
|
|
236
252
|
) -> None:
|
|
237
253
|
"""Update a registered project's path/language in place (no remove/add needed)."""
|
|
238
254
|
if path is None and language is None:
|
|
@@ -640,6 +640,24 @@ class Database:
|
|
|
640
640
|
"embeddings": int(row["embeddings"]),
|
|
641
641
|
}
|
|
642
642
|
|
|
643
|
+
def get_project_language_breakdown(self, project_name: str) -> dict[str, int]:
|
|
644
|
+
"""For auto projects (or any), return {language: file_count} from the files table.
|
|
645
|
+
|
|
646
|
+
Used to display per-language % in `project list` for language=auto projects.
|
|
647
|
+
Very cheap (single grouped query); data is already stored during index.
|
|
648
|
+
"""
|
|
649
|
+
sql = """
|
|
650
|
+
SELECT COALESCE(f.language, 'unknown') AS lang, COUNT(*) AS cnt
|
|
651
|
+
FROM files f
|
|
652
|
+
JOIN projects p ON f.project_id = p.id
|
|
653
|
+
WHERE p.name = ?
|
|
654
|
+
GROUP BY lang
|
|
655
|
+
ORDER BY cnt DESC
|
|
656
|
+
"""
|
|
657
|
+
with self.connect() as conn:
|
|
658
|
+
rows = conn.execute(sql, (project_name,)).fetchall()
|
|
659
|
+
return {row["lang"]: int(row["cnt"]) for row in rows}
|
|
660
|
+
|
|
643
661
|
def get_top_symbols_by_degree(self, project_name: str, limit: int = 80) -> list[sqlite3.Row]:
|
|
644
662
|
"""Top symbols by in+out relationship degree for graph preview."""
|
|
645
663
|
sql = """
|
|
@@ -12,6 +12,7 @@ from sampler.indexer.imports import extract_imports
|
|
|
12
12
|
from sampler.indexer.parsers.go import GoParser
|
|
13
13
|
from sampler.indexer.parsers.python import PythonParser
|
|
14
14
|
from sampler.indexer.parsers.typescript import TypeScriptParser
|
|
15
|
+
from sampler.indexer.parsers.vue import VueParser
|
|
15
16
|
from sampler.indexer.store import SymbolStore
|
|
16
17
|
from sampler.viz.discover_emit import emit_discover
|
|
17
18
|
from sampler.viz.events import FileParsing, LogLine, Stage, StageChanged
|
|
@@ -29,6 +30,7 @@ class IndexBuilder:
|
|
|
29
30
|
"go": GoParser(),
|
|
30
31
|
"typescript": TypeScriptParser(),
|
|
31
32
|
"javascript": TypeScriptParser(),
|
|
33
|
+
"vue": VueParser(),
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
def index_project(
|
|
@@ -8,6 +8,7 @@ LANGUAGE_EXTENSIONS: dict[str, set[str]] = {
|
|
|
8
8
|
"go": {".go"},
|
|
9
9
|
"typescript": {".ts", ".tsx", ".js", ".jsx"},
|
|
10
10
|
"javascript": {".js", ".jsx", ".mjs", ".cjs"},
|
|
11
|
+
"vue": {".vue"},
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
DEFAULT_IGNORE_PARTS = {
|
|
@@ -59,10 +60,11 @@ def _build_extension_language_map() -> dict[str, str]:
|
|
|
59
60
|
Iteration order determines the winner for extensions shared by multiple
|
|
60
61
|
languages (.js/.jsx are listed under both "typescript" and "javascript";
|
|
61
62
|
both route to the same real parser implementation, so the choice is
|
|
62
|
-
cosmetic but kept stable).
|
|
63
|
+
cosmetic but kept stable). .vue maps to dedicated "vue" (which delegates
|
|
64
|
+
to the TS/JS parser after <script> extraction).
|
|
63
65
|
"""
|
|
64
66
|
ext_to_lang: dict[str, str] = {}
|
|
65
|
-
for lang in ("python", "go", "typescript", "javascript"):
|
|
67
|
+
for lang in ("python", "go", "vue", "typescript", "javascript"):
|
|
66
68
|
for ext in LANGUAGE_EXTENSIONS[lang]:
|
|
67
69
|
ext_to_lang.setdefault(ext, lang)
|
|
68
70
|
return ext_to_lang
|
|
@@ -74,6 +76,7 @@ def discover_files_multi(
|
|
|
74
76
|
"""Discover files across ALL supported languages, returning (path, detected_language) pairs.
|
|
75
77
|
|
|
76
78
|
Used for monorepo/multi-language projects indexed with language="auto".
|
|
79
|
+
Vue SFCs are detected as language "vue".
|
|
77
80
|
"""
|
|
78
81
|
root = Path(project_path)
|
|
79
82
|
if not root.exists() or not root.is_dir():
|
|
@@ -27,7 +27,7 @@ def extract_imports(content: str, language: str) -> list[str]:
|
|
|
27
27
|
return _extract_python_imports(content)
|
|
28
28
|
if language == "go":
|
|
29
29
|
return _extract_go_imports(content)
|
|
30
|
-
if language in ("typescript", "javascript"):
|
|
30
|
+
if language in ("typescript", "javascript", "vue"):
|
|
31
31
|
return _extract_ts_imports(content)
|
|
32
32
|
return []
|
|
33
33
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from sampler.indexer.parsers.base import BaseParser
|
|
6
|
+
from sampler.indexer.parsers.typescript import TypeScriptParser
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class VueParser(BaseParser):
|
|
10
|
+
"""Parser for Vue single-file components (.vue).
|
|
11
|
+
|
|
12
|
+
Extracts the <script> (or <script setup>) section using stdlib re (supports lang="ts|tsx|js|jsx|typescript",
|
|
13
|
+
setup attribute, various quoting). Delegates symbol/relationship extraction to the existing
|
|
14
|
+
TypeScriptParser (which covers JS/TS + arrows/classes/etc.) using a dummy filepath so that
|
|
15
|
+
the delegate's _select_language picks the right grammar (ts vs tsx).
|
|
16
|
+
|
|
17
|
+
Line numbers in results are offset so they are correct relative to the original .vue file.
|
|
18
|
+
Graceful empty return if no <script> section or other issues.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
language = "vue"
|
|
22
|
+
|
|
23
|
+
_SCRIPT_RE = re.compile(
|
|
24
|
+
r"(?is)<script([^>]*)>(.*?)</script>"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
def parse(self, content: str, filepath: str) -> tuple[list[dict], list[dict]]:
|
|
28
|
+
symbols: list[dict] = []
|
|
29
|
+
relationships: list[dict] = []
|
|
30
|
+
|
|
31
|
+
if not filepath.lower().endswith(".vue"):
|
|
32
|
+
# Shouldn't normally happen, but delegate to TS parser for safety
|
|
33
|
+
return TypeScriptParser().parse(content, filepath)
|
|
34
|
+
|
|
35
|
+
extracted = self._extract_vue_script(content)
|
|
36
|
+
if extracted is None:
|
|
37
|
+
return symbols, relationships
|
|
38
|
+
|
|
39
|
+
script_text, line_offset, is_tsx = extracted
|
|
40
|
+
|
|
41
|
+
# Dummy filepath controls grammar selection inside the (unchanged) TS parser
|
|
42
|
+
dummy = "Comp.script.tsx" if is_tsx else "Comp.script.ts"
|
|
43
|
+
inner_symbols, inner_relationships = TypeScriptParser().parse(script_text, dummy)
|
|
44
|
+
|
|
45
|
+
# Offset lines so they refer to the original .vue file (script content lines are 0-based in tree)
|
|
46
|
+
for s in inner_symbols:
|
|
47
|
+
s["start_line"] += line_offset
|
|
48
|
+
s["end_line"] += line_offset
|
|
49
|
+
for r in inner_relationships:
|
|
50
|
+
if "line" in r and r["line"] is not None:
|
|
51
|
+
r["line"] += line_offset
|
|
52
|
+
|
|
53
|
+
return inner_symbols, inner_relationships
|
|
54
|
+
|
|
55
|
+
def _extract_vue_script(self, content: str) -> tuple[str, int, bool] | None:
|
|
56
|
+
"""Return (script_text, 0-based_line_offset_for_script_body, use_tsx) or None."""
|
|
57
|
+
m = self._SCRIPT_RE.search(content)
|
|
58
|
+
if not m:
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
attrs = m.group(1) or ""
|
|
62
|
+
script = m.group(2)
|
|
63
|
+
|
|
64
|
+
# Compute offset: number of newlines before the start of the script body
|
|
65
|
+
prefix = content[: m.start(2)]
|
|
66
|
+
line_offset = prefix.count("\n")
|
|
67
|
+
|
|
68
|
+
# Detect if we should force TSX grammar inside delegate (rare for Vue but supported)
|
|
69
|
+
lang_match = re.search(r'lang\s*=\s*["\']?([^"\'\s>]+)', attrs, re.IGNORECASE)
|
|
70
|
+
lang_val = (lang_match.group(1) if lang_match else "").lower()
|
|
71
|
+
use_tsx = lang_val in ("tsx", "jsx")
|
|
72
|
+
|
|
73
|
+
return script, line_offset, use_tsx
|
|
@@ -128,6 +128,7 @@ class QueryEngine:
|
|
|
128
128
|
if name.endswith(("_test.py", "_test.go")):
|
|
129
129
|
return True
|
|
130
130
|
|
|
131
|
+
# Note: .vue test files (e.g. Foo.test.vue) are also supported for Vue projects
|
|
131
132
|
return name.endswith(
|
|
132
133
|
(
|
|
133
134
|
".test.ts",
|
|
@@ -136,12 +137,14 @@ class QueryEngine:
|
|
|
136
137
|
".test.jsx",
|
|
137
138
|
".test.mjs",
|
|
138
139
|
".test.cjs",
|
|
140
|
+
".test.vue",
|
|
139
141
|
".spec.ts",
|
|
140
142
|
".spec.tsx",
|
|
141
143
|
".spec.js",
|
|
142
144
|
".spec.jsx",
|
|
143
145
|
".spec.mjs",
|
|
144
146
|
".spec.cjs",
|
|
147
|
+
".spec.vue",
|
|
145
148
|
)
|
|
146
149
|
)
|
|
147
150
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sampler-cli
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.4
|
|
4
4
|
Summary: Token-efficient CLI for indexing and searching code symbols (Python-first, designed for minimal LLM/agent context size)
|
|
5
5
|
Author: Samuel Ignacio Carmona Rodriguez
|
|
6
6
|
License: MIT
|
|
@@ -52,7 +52,7 @@ Dynamic: license-file
|
|
|
52
52
|
|
|
53
53
|
Token-efficient CLI for indexing and searching code symbols across multiple projects.
|
|
54
54
|
|
|
55
|
-
Current version: 0.4.
|
|
55
|
+
Current version: 0.4.4
|
|
56
56
|
|
|
57
57
|
Designed for humans and agents: compact default output, short paths, and low-noise symbol views.
|
|
58
58
|
|
|
@@ -107,7 +107,7 @@ Relationships:
|
|
|
107
107
|
- Selector alternativo: `<path>:<symbol>` (ej. `app/utils/helpers.py:format_kda`)
|
|
108
108
|
|
|
109
109
|
Project management:
|
|
110
|
-
- `sampler project add <name> <path> --language <python|go|typescript|javascript|auto>`
|
|
110
|
+
- `sampler project add <name> <path> --language <python|go|typescript|javascript|vue|auto>`
|
|
111
111
|
- `sampler project update <name> [--path <abs-path>] [--language <lang>]`
|
|
112
112
|
- `sampler project list`
|
|
113
113
|
- `sampler project deps <name>`
|
|
@@ -160,7 +160,8 @@ Offline / air-gapped: `provider: hash` (or just don't install the embeddings ext
|
|
|
160
160
|
- Python parser: stdlib AST (stable)
|
|
161
161
|
- Go parser: tree-sitter-go (real extraction)
|
|
162
162
|
- TypeScript/JavaScript parser: tree-sitter-typescript (real extraction)
|
|
163
|
-
-
|
|
163
|
+
- Vue parser: extracts `<script>`/`<script setup>` + delegates to TS/JS parser (supports lang=ts/js etc.)
|
|
164
|
+
- `--language auto`: per-file language detection for monorepos/multi-language projects (for auto projects, `project list` shows detected languages + file % breakdown)
|
|
164
165
|
|
|
165
166
|
## Stale Code Detection
|
|
166
167
|
|
|
@@ -174,7 +175,7 @@ Test file detection supports common multi-language patterns:
|
|
|
174
175
|
|
|
175
176
|
- Python: `tests/`, `test_*.py`, `*_test.py`
|
|
176
177
|
- Go: `*_test.go`
|
|
177
|
-
- TypeScript/JavaScript: `__tests__/`, `test/`, `spec/`, `*.test.*`, `*.spec.*`
|
|
178
|
+
- TypeScript/JavaScript/Vue: `__tests__/`, `test/`, `spec/`, `*.test.*`, `*.spec.*` (incl. `*.test.vue`)
|
|
178
179
|
|
|
179
180
|
This is heuristic signal, not guaranteed dead-code proof.
|
|
180
181
|
|
|
@@ -21,6 +21,7 @@ src/sampler/indexer/parsers/base.py
|
|
|
21
21
|
src/sampler/indexer/parsers/go.py
|
|
22
22
|
src/sampler/indexer/parsers/python.py
|
|
23
23
|
src/sampler/indexer/parsers/typescript.py
|
|
24
|
+
src/sampler/indexer/parsers/vue.py
|
|
24
25
|
src/sampler/mcp/__init__.py
|
|
25
26
|
src/sampler/mcp/server.py
|
|
26
27
|
src/sampler/query/__init__.py
|
|
@@ -62,4 +63,5 @@ tests/test_stale_code.py
|
|
|
62
63
|
tests/test_typescript_parser.py
|
|
63
64
|
tests/test_viz_engine.py
|
|
64
65
|
tests/test_viz_layout.py
|
|
65
|
-
tests/test_viz_pipeline.py
|
|
66
|
+
tests/test_viz_pipeline.py
|
|
67
|
+
tests/test_vue_parser.py
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from sampler.db import Database
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_db_schema_and_project_crud(tmp_path: Path) -> None:
|
|
7
|
+
db_path = tmp_path / "sampler.db"
|
|
8
|
+
db = Database(db_path)
|
|
9
|
+
db.init_schema()
|
|
10
|
+
|
|
11
|
+
project_id = db.add_project("p1", "/tmp/p1", "python")
|
|
12
|
+
assert project_id > 0
|
|
13
|
+
|
|
14
|
+
project = db.get_project("p1")
|
|
15
|
+
assert project is not None
|
|
16
|
+
assert project["name"] == "p1"
|
|
17
|
+
|
|
18
|
+
projects = db.list_projects()
|
|
19
|
+
assert len(projects) == 1
|
|
20
|
+
|
|
21
|
+
db.remove_project("p1")
|
|
22
|
+
assert db.get_project("p1") is None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_project_language_breakdown_for_auto(tmp_path: Path) -> None:
|
|
26
|
+
db_path = tmp_path / "sampler.db"
|
|
27
|
+
db = Database(db_path)
|
|
28
|
+
db.init_schema()
|
|
29
|
+
|
|
30
|
+
pid = db.add_project("autoproj", "/tmp/ap", "auto")
|
|
31
|
+
|
|
32
|
+
# Simulate files discovered+indexed under different langs (as builder does for auto)
|
|
33
|
+
db.upsert_file(pid, "/tmp/ap/a.ts", "typescript", "h1")
|
|
34
|
+
db.upsert_file(pid, "/tmp/ap/b.ts", "typescript", "h2")
|
|
35
|
+
db.upsert_file(pid, "/tmp/ap/c.js", "javascript", "h3")
|
|
36
|
+
db.upsert_file(pid, "/tmp/ap/d.vue", "vue", "h4")
|
|
37
|
+
db.upsert_file(pid, "/tmp/ap/e.py", "python", "h5")
|
|
38
|
+
|
|
39
|
+
breakdown = db.get_project_language_breakdown("autoproj")
|
|
40
|
+
assert breakdown["typescript"] == 2
|
|
41
|
+
assert breakdown["javascript"] == 1
|
|
42
|
+
assert breakdown["vue"] == 1
|
|
43
|
+
assert breakdown["python"] == 1
|
|
44
|
+
|
|
45
|
+
total = sum(breakdown.values())
|
|
46
|
+
assert total == 5
|
|
@@ -22,6 +22,8 @@ def test_discover_files_multi_detects_language_per_file(tmp_path: Path) -> None:
|
|
|
22
22
|
(tmp_path / "a.py").write_text("print('ok')\n", encoding="utf-8")
|
|
23
23
|
(tmp_path / "b.go").write_text("package main\n", encoding="utf-8")
|
|
24
24
|
(tmp_path / "c.ts").write_text("export const x = 1;\n", encoding="utf-8")
|
|
25
|
+
(tmp_path / "d.js").write_text("export const y = 2;\n", encoding="utf-8")
|
|
26
|
+
(tmp_path / "e.vue").write_text("<script>export const z = 3;</script>\n", encoding="utf-8")
|
|
25
27
|
(tmp_path / "readme.md").write_text("not code\n", encoding="utf-8")
|
|
26
28
|
|
|
27
29
|
ignored = tmp_path / "node_modules"
|
|
@@ -34,5 +36,7 @@ def test_discover_files_multi_detects_language_per_file(tmp_path: Path) -> None:
|
|
|
34
36
|
assert by_suffix[".py"] == "python"
|
|
35
37
|
assert by_suffix[".go"] == "go"
|
|
36
38
|
assert by_suffix[".ts"] == "typescript"
|
|
39
|
+
assert by_suffix[".js"] in ("typescript", "javascript") # shared, order stable
|
|
40
|
+
assert by_suffix[".vue"] == "vue"
|
|
37
41
|
assert ".md" not in by_suffix
|
|
38
42
|
assert all("skip.js" not in p for p, _ in entries)
|
|
@@ -114,6 +114,8 @@ def test_is_test_path_supports_multilanguage_patterns() -> None:
|
|
|
114
114
|
assert QueryEngine._is_test_path("web/Button.spec.ts")
|
|
115
115
|
assert QueryEngine._is_test_path("src/__tests__/helpers.js")
|
|
116
116
|
assert QueryEngine._is_test_path("src/spec/api.test.mjs")
|
|
117
|
+
assert QueryEngine._is_test_path("src/components/Button.test.vue")
|
|
118
|
+
assert QueryEngine._is_test_path("views/Foo.spec.vue")
|
|
117
119
|
assert not QueryEngine._is_test_path("src/app/main.go")
|
|
118
120
|
assert not QueryEngine._is_test_path("src/components/Button.tsx")
|
|
119
121
|
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from sampler.indexer.parsers.vue import VueParser
|
|
2
|
+
from sampler.indexer.parsers.typescript import TypeScriptParser
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_vue_parser_extracts_script_and_offsets_lines() -> None:
|
|
6
|
+
src = """<template>
|
|
7
|
+
<div>Hello {{ msg }}</div>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script setup lang="ts">
|
|
11
|
+
// comment
|
|
12
|
+
export const vArrow = (x: number) => x + 1;
|
|
13
|
+
|
|
14
|
+
export function setupHelper() {
|
|
15
|
+
return vArrow(41);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const run = () => setupHelper();
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<style>
|
|
22
|
+
div { color: red; }
|
|
23
|
+
</style>
|
|
24
|
+
"""
|
|
25
|
+
symbols, relationships = VueParser().parse(src, "Comp.vue")
|
|
26
|
+
|
|
27
|
+
by_name = {s["qualified_name"]: s for s in symbols}
|
|
28
|
+
assert "vArrow" in by_name
|
|
29
|
+
assert by_name["vArrow"]["type"] == "function"
|
|
30
|
+
# script body starts around line 6-7 in the src above; line numbers must be offset (not 1)
|
|
31
|
+
assert by_name["vArrow"]["start_line"] > 5
|
|
32
|
+
assert by_name["setupHelper"]["type"] == "function"
|
|
33
|
+
assert "run" in by_name
|
|
34
|
+
|
|
35
|
+
# Relationships (CALLS) should be present and lines offset
|
|
36
|
+
rel_targets = {r["target"] for r in relationships}
|
|
37
|
+
assert "setupHelper" in rel_targets or "vArrow" in rel_targets # at least one call captured
|
|
38
|
+
|
|
39
|
+
# No template/style symbols
|
|
40
|
+
assert not any("template" in (s.get("name") or "").lower() for s in symbols)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_vue_parser_handles_script_without_lang_and_setup() -> None:
|
|
44
|
+
src = """<script>
|
|
45
|
+
export function plainJs() { return 42; }
|
|
46
|
+
const arr = () => plainJs();
|
|
47
|
+
</script>
|
|
48
|
+
"""
|
|
49
|
+
symbols, _ = VueParser().parse(src, "Widget.vue")
|
|
50
|
+
names = {s["name"] for s in symbols}
|
|
51
|
+
assert "plainJs" in names
|
|
52
|
+
assert "arr" in names
|
|
53
|
+
# lines should be sensible (>1 because of the <script> tag)
|
|
54
|
+
assert any(s["start_line"] > 1 for s in symbols)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_vue_parser_returns_empty_for_no_script() -> None:
|
|
58
|
+
src = """<template><p>no script here</p></template>
|
|
59
|
+
<style>.foo{}</style>
|
|
60
|
+
"""
|
|
61
|
+
symbols, relationships = VueParser().parse(src, "NoScript.vue")
|
|
62
|
+
assert symbols == []
|
|
63
|
+
assert relationships == []
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_vue_parser_graceful_on_bad_inner_content() -> None:
|
|
67
|
+
src = """<script setup>
|
|
68
|
+
function ((( broken
|
|
69
|
+
</script>
|
|
70
|
+
"""
|
|
71
|
+
symbols, relationships = VueParser().parse(src, "Broken.vue")
|
|
72
|
+
# Delegate (TS parser) is tolerant; we just get what it gives (lists)
|
|
73
|
+
assert isinstance(symbols, list)
|
|
74
|
+
assert isinstance(relationships, list)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_vue_parser_delegates_and_reuses_ts_logic() -> None:
|
|
78
|
+
# Sanity: the inner logic (including arrows via const, classes, etc) is provided by delegate
|
|
79
|
+
# (we don't re-test all TS cases here)
|
|
80
|
+
src = """<script lang="ts">
|
|
81
|
+
export class Foo {
|
|
82
|
+
bar = () => 1; // arrow field -> should surface via delegate
|
|
83
|
+
}
|
|
84
|
+
const helper = (y) => y * 2;
|
|
85
|
+
</script>
|
|
86
|
+
"""
|
|
87
|
+
symbols, _ = VueParser().parse(src, "WithClass.vue")
|
|
88
|
+
names = {s["qualified_name"] for s in symbols}
|
|
89
|
+
assert "Foo" in names
|
|
90
|
+
# The delegate will surface "Foo.bar" as method (or at minimum the class); helper too
|
|
91
|
+
assert any("helper" in n for n in names)
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
|
|
3
|
-
from sampler.db import Database
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def test_db_schema_and_project_crud(tmp_path: Path) -> None:
|
|
7
|
-
db_path = tmp_path / "sampler.db"
|
|
8
|
-
db = Database(db_path)
|
|
9
|
-
db.init_schema()
|
|
10
|
-
|
|
11
|
-
project_id = db.add_project("p1", "/tmp/p1", "python")
|
|
12
|
-
assert project_id > 0
|
|
13
|
-
|
|
14
|
-
project = db.get_project("p1")
|
|
15
|
-
assert project is not None
|
|
16
|
-
assert project["name"] == "p1"
|
|
17
|
-
|
|
18
|
-
projects = db.list_projects()
|
|
19
|
-
assert len(projects) == 1
|
|
20
|
-
|
|
21
|
-
db.remove_project("p1")
|
|
22
|
-
assert db.get_project("p1") is None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|