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.
Files changed (70) hide show
  1. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/PKG-INFO +6 -5
  2. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/README.md +5 -4
  3. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/pyproject.toml +1 -1
  4. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/__init__.py +1 -1
  5. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/cli/main.py +19 -3
  6. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/db.py +18 -0
  7. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/indexer/builder.py +2 -0
  8. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/indexer/discover.py +5 -2
  9. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/indexer/imports.py +1 -1
  10. sampler_cli-0.4.4/src/sampler/indexer/parsers/vue.py +73 -0
  11. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/query/engine.py +3 -0
  12. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler_cli.egg-info/PKG-INFO +6 -5
  13. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler_cli.egg-info/SOURCES.txt +3 -1
  14. sampler_cli-0.4.4/tests/test_db.py +46 -0
  15. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_discover.py +4 -0
  16. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_stale_code.py +2 -0
  17. sampler_cli-0.4.4/tests/test_vue_parser.py +91 -0
  18. sampler_cli-0.4.3/tests/test_db.py +0 -22
  19. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/LICENSE +0 -0
  20. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/setup.cfg +0 -0
  21. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/__main__.py +0 -0
  22. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/cli/__init__.py +0 -0
  23. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/cli/render.py +0 -0
  24. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/config.py +0 -0
  25. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/embeddings.py +0 -0
  26. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/indexer/__init__.py +0 -0
  27. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/indexer/embedder.py +0 -0
  28. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/indexer/parsers/__init__.py +0 -0
  29. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/indexer/parsers/base.py +0 -0
  30. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/indexer/parsers/go.py +0 -0
  31. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/indexer/parsers/python.py +0 -0
  32. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/indexer/parsers/typescript.py +0 -0
  33. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/indexer/store.py +0 -0
  34. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/mcp/__init__.py +0 -0
  35. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/mcp/server.py +0 -0
  36. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/models.py +0 -0
  37. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/query/__init__.py +0 -0
  38. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/query/semantic.py +0 -0
  39. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/viz/__init__.py +0 -0
  40. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/viz/bus.py +0 -0
  41. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/viz/canvas.py +0 -0
  42. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/viz/discover_emit.py +0 -0
  43. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/viz/engine.py +0 -0
  44. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/viz/events.py +0 -0
  45. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/viz/headline.py +0 -0
  46. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/viz/layout_algo.py +0 -0
  47. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/viz/live.py +0 -0
  48. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler/viz/pipeline.py +0 -0
  49. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler_cli.egg-info/dependency_links.txt +0 -0
  50. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler_cli.egg-info/entry_points.txt +0 -0
  51. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler_cli.egg-info/requires.txt +0 -0
  52. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/src/sampler_cli.egg-info/top_level.txt +0 -0
  53. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_canvas_graph.py +0 -0
  54. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_cli.py +0 -0
  55. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_config.py +0 -0
  56. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_embeddings.py +0 -0
  57. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_events.py +0 -0
  58. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_go_parser.py +0 -0
  59. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_headline.py +0 -0
  60. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_imports.py +0 -0
  61. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_index_query.py +0 -0
  62. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_python_parser.py +0 -0
  63. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_relationships.py +0 -0
  64. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_render_bars.py +0 -0
  65. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_semantic.py +0 -0
  66. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_smoke.py +0 -0
  67. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_typescript_parser.py +0 -0
  68. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_viz_engine.py +0 -0
  69. {sampler_cli-0.4.3 → sampler_cli-0.4.4}/tests/test_viz_layout.py +0 -0
  70. {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
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.3
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
- - `--language auto`: per-file language detection for monorepos/multi-language projects
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.3
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
- - `--language auto`: per-file language detection for monorepos/multi-language projects
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
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sampler-cli"
3
- version = "0.4.3"
3
+ version = "0.4.4"
4
4
  description = "Token-efficient CLI for indexing and searching code symbols (Python-first, designed for minimal LLM/agent context size)"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -1,3 +1,3 @@
1
1
  __all__ = ["__version__"]
2
2
 
3
- __version__ = "0.4.3"
3
+ __version__ = "0.4.4"
@@ -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
- table.add_row(p.name, disp, p.language, enabled)
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 (or 'auto' for monorepos)"),
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
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.3
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
- - `--language auto`: per-file language detection for monorepos/multi-language projects
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