Repository navigation
Description
Version
codebase-memory-mcp 0.9.0 (Linux x86_64, standalone binary)
Platform
Linux (x64)
Install channel
GitHub release archive / install.sh / install.ps1
Binary variant
standard
What happened, and what did you expect?
Summary
.vue files are discovered and registered as File and Module nodes, and
get_architecture reports them under languages. But their <script setup>
contents are never parsed: no Function/Method nodes are created for
functions defined in a .vue file, and no CALLS edges are created for calls
originating from one.
The index therefore looks healthy while being structurally empty for every
.vue file in the repo.
Impact
This makes search_graph(max_degree=0, exclude_entry_points=true) unsafe on any
Vue project. Every utility called only from components reports in_degree: 0
and is presented as dead code.
On a 9.2k-line Vue/TS codebase the dead-code query returned 29 functions.
6 of the 11 I verified were false positives — all called from .vue files:
| Reported dead | Actual call sites |
|---|---|
showModal |
5 |
hideModal |
6 |
copyToClipboard |
2 |
showToast |
1 |
hideToast |
1 |
now |
1 |
Acting on that list deletes working code. fan_in in get_architecture
hotspots is understated for the same reason (reported clone at fan_in: 3;
actual is 6).
Reproduction
Minimal reproduction
Four files. The .ts caller and the .vue caller do the same thing.
repro/
package.json
tsconfig.json
src/util.ts
src/caller.ts
src/components/Caller.vue
src/util.ts
export function fnRelTs(x: number): number { return x + 1; }
export function fnAliasTs(x: number): number { return x + 2; }
export function fnRelVue(x: number): number { return x + 3; }
export function fnAliasVue(x: number): number { return x + 4; }
export default { fnRelTs, fnAliasTs, fnRelVue, fnAliasVue };src/caller.ts
import uRel from './util';
import uAlias from '~/util';
export function runTs(): number {
return uRel.fnRelTs(1) + uAlias.fnAliasTs(2);
}src/components/Caller.vue
<script setup lang="ts">
import uRel from '../util';
import uAlias from '~/util';
const runVue = (): number => {
return uRel.fnRelVue(1) + uAlias.fnAliasVue(2);
};
</script>
<template>
<div>{{ runVue() }}</div>
</template>tsconfig.json
{ "compilerOptions": { "target": "ES2022", "module": "ESNext", "moduleResolution": "bundler", "strict": true, "baseUrl": ".", "paths": { "~/*": ["src/*"] } } }package.json
{ "name": "repro", "version": "1.0.0", "type": "module" }Then:
index_repository(repo_path="…/repro", mode="full", name="repro")
query_graph(project="repro",
query="MATCH (f) WHERE (f:Function OR f:Method) RETURN f.name, f.file_path, f.in_degree ORDER BY f.name")
Actual
fnAliasTs src/util.ts 1
fnAliasVue src/util.ts 0 ← called from Caller.vue
fnRelTs src/util.ts 1
fnRelVue src/util.ts 0 ← called from Caller.vue
runTs src/caller.ts 0
runVue is absent from the graph entirely — no node is created for it.
MATCH (a)-[r:CALLS]->(b) RETURN a.name, a.file_path, b.name
→ runTs, src/caller.ts, fnRelTs
runTs, src/caller.ts, fnAliasTs
Only two edges. Nothing originates from Caller.vue.
Expected
fnRelVue and fnAliasVue at in_degree: 1, a Function node for runVue
at src/components/Caller.vue, and two additional CALLS edges.
What this is not
I first suspected namespace default imports (import u from './util' then
u.fn()) or path-alias resolution (~/util). Both work correctly — the
2×2 above isolates it: caller.ts resolves relative and aliased namespace
calls fine. The only failing variable is the caller being a .vue file.
Why it is easy to miss
get_architecture reports the files as present, which reads as success:
"languages": [ { "language": "Vue", "file_count": 34 }, … ]Aggregate edge density is the tell — on the same machine:
| Repo | Nodes | Edges | Edges/node |
|---|---|---|---|
| Python, 3.7k LOC | 409 | 1665 | 4.07 |
| Vue+TS, 9.2k LOC | 480 | 924 | 1.93 |
And on the Vue repo:
MATCH (f) WHERE (f:Function OR f:Method) AND f.file_path ENDS WITH '.vue'
RETURN count(f) → 0
MATCH (a)-[r:CALLS]->(b) WHERE a.file_path ENDS WITH '.vue'
RETURN count(r) → 0
MATCH (f:File) WHERE f.path ENDS WITH '.vue' RETURN count(f) → 34
34 file nodes, zero structural content.
Suggested direction
Extract the <script> / <script setup> block from the SFC before handing it
to the TS parser, preserving a line offset so file_path/line attribution stays
correct. Template-block references (@click="handler", {{ fn() }}) are a
larger job and could be a follow-up — script-block extraction alone would
recover the bulk of the missing edges.
Two smaller asks, if useful:
- Until this lands, consider having
index_repositorywarn when a language is
detected but yields zero function nodes — that single signal would have made
this obvious immediately. exclude_entry_points=trueinsearch_graph(max_degree=0, …)implies a
confidence the index cannot support for partially-parsed languages. A
per-language "structural coverage" figure inindex_statuswould let callers
know when not to trust a dead-code result.
Environment
codebase-memory-mcp0.9.0, standalone binary- Linux 7.0.0-28-generic, x86_64
- Repos: Vue 3 + TypeScript (Vite,
~/*path alias), and Python 3 for the
comparison baseline mode="full"in all runs;skipped_count: 0,expected_nodesmatched
nodesin every index (no reported extraction failures)
Logs
Diagnostics trajectory (memory / performance / leak issues)
Project scale (if relevant)
No response
Confirmations
- I searched existing issues and this is not a duplicate.
- My reproduction uses shareable code (a dummy snippet or a public OSS repository), not proprietary code.
Activity
AmirF194 commentedon Aug 2, 2026
Reproduced this on current
main(d6be58ef, 2026-07-31), in a clean container, with your exact 4-file example (util.ts/caller.ts/Caller.vue/tsconfig.json). Same result:runVueis entirely absent from the graph, andfnRelVue/fnAliasVueboth showin_degree: 0despite the calls inCaller.vue.runTs(the.tscaller) correctly shows up with its two outgoing edges.Root cause is exactly what the "what this is not" section rules toward:
internal/cbm/lang_specs.c:1978-1982, theCBM_LANG_VUEspec row, setsfunc_types/class_types/method_types/call_typesall toempty_types:The only special-case handling for
<script>content isvue_embedded_imports(same file, dispatched viaparse_embedded_imports()inextract_imports.c), and its own comment says what it's for: "re-parse that slice with the JS grammar so the existing ES import extractor sees realimport_statementnodes." It exists purely to make imports work throughraw_textnodes; nothing re-parses the same slice for functions or calls. So.vueis not a partially-broken extractor, it's an extractor that was only ever built for one edge type.CBM_LANG_SVELTE's row (right below, same file) has the identical all-empty_typesshape and the samesvelte_embedded_imports-only mechanism, so this is probably not Vue-specific, though I only reproduced the.vuecase above.Competing-PR sweep on
internal/cbm/lang_specs.c+extract_imports.c(87/87 open PRs, uncapped): 10 candidates touch one of the two files, none add function/call extraction for the embedded-script languages. Closest is #1245, which touches theCBM_LANG_VUErow, but only to append one new struct field (an unrelated incremental-indexing change) and leaves everyempty_typesin place.Agree with the suggested direction (extract the
<script>/<script setup>block and hand it to the JS/TS extractor with a line offset, mirroring whatvue_embedded_importsalready does for the import case); happy to take a pass at it if that'd be useful.DeusData commentedon Aug 3, 2026
Thank you for the clear Vue SFC reproduction. Current language metadata routes
.vue, but its definition/class/function extraction sets are empty; only module and embedded-import handling is configured. That explains a successful index with an effectively empty symbol graph for<script setup>. I have removed the unrelated Windows label and routed this as a high-priority parsing bug in0.9.1-rc.bmcnaboe commentedon Aug 20, 2026
Agreeing with @AmirF194's direction and the maintainer's framing — proposing a concrete generalization shape and asking for design feedback before sending a PR.
Approach: generalize the existing embedded-script seam (
vue_embedded_imports/svelte_embedded_importsininternal/cbm/extract_imports.c) from imports-only to the full walker set (defs, classes, methods, calls — everythingfunc_types/class_types/method_types/call_typescurrently leave asempty_typesforCBM_LANG_VUE/CBM_LANG_SVELTE). Concretely:<script>/<script setup>block's byte range from the SFC, same as the import seam already does.ts_parser_set_included_rangesagainst the JS/TS grammar rather than a raw re-slice-and-reparse, so node coordinates come back in the original file's coordinate space — this is what lets defs/calls/edges land on the right line numbers without a separate offset-translation pass, and keeps a single walker pass over the whole file rather than a second synthetic file.lang="ts"on thescript_elementnode and route that block throughCBM_LANG_TYPESCRIPT's walker configuration instead of plain JS, since<script setup lang="ts">is idiomatic in current Vue.CBM_LANG_SVELTE's spec row has the identical all-empty_typesshape and the same single-purpose embedded-import mechanism, the same generalization should cover Svelte at near-zero marginal cost — same seam, same included-ranges technique, different grammar/spec row.This keeps
.vue/.sveltefiles off any second-pass reparse and reuses the walker set that already exists for JS/TS, rather than teaching the graph builder a third extraction path.We're carrying a fork patch along these lines in the meantime and will have it further validated (212-SFC repo, fan-in/dead-code checks against
<script setup>-only exports) before proposing it as a PR — would appreciate a read on whether this shape (included-ranges reparse of the existing seam,langsniffing, Svelte sharing the same code path) matches the direction you'd want to merge, per CONTRIBUTING's ask for a design discussion before code on cross-cutting changes.feat(extract): index embedded <script setup> symbols for Vue and Svelte
DeusData commentedon Aug 27, 2026
@bmcnaboe — thank you for this, and I'm sorry you waited a week for a reply. You asked for a design read before spending effort, which is exactly the right way round, and leaving that unanswered was my mistake.
The read you asked for: yes, that shape is right — on all three points.
ts_parser_set_included_rangesagainst the original buffer is the correct call, and for a reason worth stating explicitly since it's the part that's easy to get wrong: it isn't only about avoiding an offset-translation pass. Parsing the full original buffer through an absolute included range is what keeps byte and point coordinates correct across CRLF line endings and a UTF-8 BOM — a re-slice-and-reparse gets both subtly wrong in exactly the files where nobody looks. Sniffinglang="ts"and routing that block through the TypeScript configuration is right too;<script setup lang="ts">is the common case in current Vue, not an edge case. And your read that Svelte's spec row has the identical all-empty_typesshape and the same single-purpose embedded-import mechanism is correct.Where things stand, so you don't duplicate work. The Vue half is up as #1852 and finishing CI now. It converged independently on the shape you described here — included-ranges over the original buffer, plus a
lang=/src=resolver on thescript_element— which I take as good evidence the design is right rather than a coincidence. Your analysis in this thread stands on its own, and @AmirF194's root-cause narrowing to theempty_typesspec row is what made the whole thing tractable; thank you both.The generalization is yours if you'd like it, and I'd rather it were, because you have a 212-SFC corpus and fan-in/dead-code checks that I don't. Concretely what's left after #1852:
internal/cbm/extract_imports.c:bool extract_structure = ctx->language == CBM_LANG_VUE;. Widening it to Svelte, HTML and Astro is the core of the change.vue_embedded_language()is already host-agnostic despite the name — it walks thescript_element's attributes forsrc=(bail) andlang=(js/ts). Renaming it to something likescript_embedded_language()and reusing it is the intended path.lang_specs.calready declares the embedded specs for all four hosts, and Vue/Svelte/HTML are byte-identical ({"script_element", "raw_text", CBM_LANG_JAVASCRIPT}), so block discovery and re-parse already work — only structural extraction is gated off.type=. Plain HTML routinely carries<script type="application/json">,type="importmap",type="text/x-template". Today those are re-parsed for imports only, which is harmless because a JSON blob yields noimport_statement. Turning on structural extraction makes it harmful — garbage defs and calls in the graph. Accept absent /module/text/javascript/application/javascript, bail on the rest.<script context="module">is a second block in the same file.MAX_EMBEDDED_BLOCKSis 16 so both are collected — worth a test that both contribute.#1852 ships a control test that asserts Svelte/HTML/Astro still produce zero functions and zero calls. Flipping that test from zero to the real symbols is the cleanest possible proof of your change, so please do flip it rather than work around it. Please also add a control that must stay at zero: a
<script type="application/json">block, and a<script src="./x.js">in each host.One more thing worth having on your radar: #1807 is the Svelte-side report of the same bug, with an A/B table. Landing this closes it, and I'd like to credit you there when it does.
No deadline from me, and no rush — take the time to validate against your corpus. If you'd rather not carry it after all, just say so and I'll pick it up; either answer is genuinely fine, I'd just like to know which so #1807's reporter gets an honest ETA. Thanks again for the care you put into the proposal.
DeusData commentedon Aug 27, 2026
Fixed on
mainin e678722 (#1852)..vuefiles now extract functions, classes and calls from<script>/<script setup>, not just imports. Your original repro is the acceptance case:runVueappears in the graph, andfnRelVue/fnAliasVueget real in-edges fromCaller.vue.Mechanically, the embedded region is parsed with
ts_parser_set_included_rangesagainst the original buffer rather than re-sliced into a synthetic source, which is what keeps byte and point coordinates correct across CRLF and a UTF-8 BOM;lang="ts"routes the block through the TypeScript configuration, and a<script src="...">is left alone. Everything lands on existing node and edge types — no new vocabulary.Credit where it belongs: @AmirF194 narrowed this to the
empty_typesspec row atlang_specs.cwith a clean container reproduction, which is what made it tractable, and @bmcnaboe proposed the generalization shape — included-ranges pluslang=sniffing — that the fix independently converged on. Thank you both, and thank you @oscar6echo for a report that was precise enough to act on directly.Closing this one as the Vue case is resolved. #1807 stays open for the same underlying gap in Svelte, and HTML/Astro ride along with it — @bmcnaboe is carrying that generalization, with the remaining detail on that thread.
fix(extract): extract structure from Vue inline scripts
bmcnaboe commentedon Sep 3, 2026
Yes — I'll pick this up. Thanks for the design read and for laying out what's left.
Plan, following your list: widen the
extract_structuregate to Svelte/HTML/Astro; renamevue_embedded_language()toscript_embedded_language()and reuse it; gate HTML ontype=(absent /module/text/javascript/application/javascript, bail on the rest); decide TS vs JS for Astro frontmatter explicitly; cover both Svelte blocks (context="module"+ instance) contributing. Flip #1852's zero-symbol control to the real symbols, and add the stay-at-zero controls (type="application/json",<script src>in each host).First step is sizing it against current
mainand validating on the SFC corpus. I'll post an ETA on #1807 once that's done.