Skip to content

fix: split shared 'jsts' tree-sitter query into separate 'js' and 'ts' queries (fixes #1654)#1673

Closed
octo-patch wants to merge 3 commits intothedotmack:mainfrom
octo-patch:fix/issue-1654-split-jsts-query
Closed

fix: split shared 'jsts' tree-sitter query into separate 'js' and 'ts' queries (fixes #1654)#1673
octo-patch wants to merge 3 commits intothedotmack:mainfrom
octo-patch:fix/issue-1654-split-jsts-query

Conversation

@octo-patch
Copy link
Copy Markdown
Contributor

Fixes #1654

Problem

The QUERIES object in src/services/smart-file-read/parser.ts defined a single "jsts" query used for JavaScript, TypeScript, and TSX files. This query included three TypeScript-only node types:

  • interface_declaration
  • type_alias_declaration
  • enum_declaration

These node types do not exist in the tree-sitter-javascript grammar. While tree-sitter currently silently ignores unmatched patterns, relying on this behavior is fragile — a future tree-sitter version that strictly validates patterns would silently return zero results for all .js file parses (the error handler in runBatchQuery catches all exceptions and returns an empty Map).

Solution

Split "jsts" into two separate queries:

  • "js": JavaScript-only patterns (functions, classes, methods, imports, exports — no TS-only types)
  • "ts": All patterns including TypeScript-specific interface, type alias, and enum

Updated getQueryKey() to map:

  • "javascript""js"
  • "typescript" / "tsx""ts"

Testing

No behavior change for TypeScript/TSX files. JavaScript files now use only patterns that are valid for the tree-sitter-javascript grammar, eliminating the silent-ignore dependency.

octo-patch added 3 commits April 9, 2026 09:32
 thedotmack#1644)

When the worker daemon is started from launchd, cron, or nohup, ~/.local/bin
is not in PATH, so `uvx` cannot be found and ChromaDB silently fails.

Add getUvxPath() utility (mirroring getBunPath()) that checks PATH first,
then probes common installation locations including ~/.local/bin/uvx and
~/.cargo/bin/uvx.

Use the resolved absolute path when spawning uvx on non-Windows platforms.
Falls back to bare 'uvx' if not found anywhere (preserving the previous
error message) rather than silently failing with no diagnostic.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 10, 2026

Summary by CodeRabbit

  • Improvements
    • Enhanced JavaScript and TypeScript code parsing with updated tree-sitter query patterns for better language support and accuracy.
    • Improved uvx executable resolution on non-Windows platforms, with automatic detection of common installation directories and proper fallback handling.
    • Updated internal command infrastructure for consistency and compatibility.

Walkthrough

Hook command invocations are updated to use .mjs instead of .js. The tree-sitter query configuration for JavaScript is separated from TypeScript to remove unsupported node types. Runtime resolution of the uvx executable path is added for non-Windows platforms.

Changes

Cohort / File(s) Summary
Hook Command Updates
plugin/hooks/hooks.json, plugin/scripts/bun-runner.mjs
Updated hook invocations from bun-runner.js to bun-runner.mjs for SessionStart, UserPromptSubmit, PostToolUse, and Stop stages. Updated help text reference in runner script to reflect .mjs filename.
Tree-Sitter Query Refactoring
src/services/smart-file-read/parser.ts
Split combined jsts query into separate js and ts entries. JavaScript query now excludes TypeScript-only patterns (interface_declaration, type_alias_declaration, enum_declaration). Updated getQueryKey() to map javascriptjs and typescript/tsxts.
UVX Path Resolution
src/utils/uvx-path.ts, src/services/sync/ChromaMcpManager.ts
Added new utility module to resolve uvx executable location via PATH verification and common installation directory checks. Updated ChromaMcpManager to use resolved path on non-Windows platforms instead of hardcoded 'uvx' string.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • #1619: Modifies src/services/smart-file-read/parser.ts query configuration and getQueryKey() logic—same tree-sitter query selection code paths.
  • #1629: Updates plugin/hooks/hooks.json and modifies bun-runner script invocations—overlaps with hook command changes.
  • #1491: Modifies tree-sitter/JavaScript parsing setup in the same parser module—related refactoring of JSTS query handling.

Poem

🐰 Hops through the code with careful eyes,
Splitting queries, oh what a prize!
One path for JavaScript, one for the rest,
Bun runners renamed—we're on the quest!
Finding uvx in the wilds of PATH,
Finally, a robust developer's path!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Changes to hooks.json, bun-runner.mjs, and uvx-path.ts utilities appear to be related infrastructure updates but are not directly tied to issue #1654's objectives. Clarify if the hooks.json, bun-runner.mjs, and uvx-path.ts changes are related to #1654 or should be separated into a different PR for cleaner separation of concerns.
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately identifies the primary change: splitting a shared tree-sitter query into separate JavaScript and TypeScript queries, and directly references the linked issue.
Description check ✅ Passed The PR description clearly explains the problem, solution, and testing approach with specific details about the tree-sitter query split and the issue being fixed.
Linked Issues check ✅ Passed The PR implementation matches issue #1654 requirements: splits the 'jsts' query into 'js' and 'ts' queries, removes TypeScript-only patterns from JavaScript parsing, and updates getQueryKey() mappings accordingly.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch fix/issue-1654-split-jsts-query

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/services/smart-file-read/parser.ts`:
- Around line 105-109: The JS capture for class names in the Tree-sitter query
is using TypeScript's type_identifier and thus misses JS classes; update the js
query string so the class capture uses (class_declaration name: (identifier)
`@name`) `@cls` instead of (class_declaration name: (type_identifier) `@name`)
`@cls`—i.e., locate the query text containing (class_declaration name:
(type_identifier) `@name`) `@cls` in parser.ts and replace type_identifier with
identifier so JavaScript class names are correctly matched.

In `@src/utils/uvx-path.ts`:
- Around line 22-26: The spawnSync call used to probe uvx in getUvxPath
currently has no timeout and can block initialization; modify the spawnSync
invocation in getUvxPath to include a timeout (e.g., 2000 ms) in its options so
the call fails fast, and update the subsequent handling of the result variable
to treat a timeout (check result.error or result.status for timeout/ETIMEDOUT)
the same as other failures so the function falls back correctly; keep the
existing stdio/encoding options and ensure getUvxPath returns the existing
fallback when the probe times out.
- Around line 45-49: The loop that returns a uvx path currently only checks
existsSync(uvxPath) and may return a non-executable file; update the check
inside the uvxPaths iteration to also ensure the path is a regular file and
executable by using fs.statSync or statSync.isFile() and fs.accessSync(uvxPath,
fs.constants.X_OK) (catching access exceptions) before logging and returning;
reference the uvxPaths iteration and the logger debug call so the fix is applied
where existsSync(uvxPath) is used to prevent StdioClientTransport from later
failing to spawn a non-executable file.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6e8af42c-d8d6-47dd-ade7-52aa568989bf

📥 Commits

Reviewing files that changed from the base of the PR and between cde4faa and fa18db2.

📒 Files selected for processing (5)
  • plugin/hooks/hooks.json
  • plugin/scripts/bun-runner.mjs
  • src/services/smart-file-read/parser.ts
  • src/services/sync/ChromaMcpManager.ts
  • src/utils/uvx-path.ts

Comment on lines +105 to +109
js: `
(function_declaration name: (identifier) @name) @func
(lexical_declaration (variable_declarator name: (identifier) @name value: [(arrow_function) (function_expression)])) @const_func
(class_declaration name: (type_identifier) @name) @cls
(method_definition name: (property_identifier) @name) @method
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify current tree-sitter-javascript grammar shape (read-only, no repo files required).
python - <<'PY'
import re
import urllib.request

url = "https://raw.githubusercontent.com/tree-sitter/tree-sitter-javascript/master/grammar.js"
text = urllib.request.urlopen(url, timeout=20).read().decode("utf-8").replace("\n", " ")

print("contains type_identifier:", "type_identifier" in text)

m = re.search(r"class_declaration:\s*\$ =>.*?field\('name',\s*\$\.[a-z_]+\)", text)
print("class_declaration name field snippet:", m.group(0) if m else "not found")
PY

Repository: thedotmack/claude-mem

Length of output: 2646


🏁 Script executed:

cat -n src/services/smart-file-read/parser.ts | sed -n '100,130p'

Repository: thedotmack/claude-mem

Length of output: 1443


🏁 Script executed:

cat -n src/services/smart-file-read/parser.ts | sed -n '170,185p'

Repository: thedotmack/claude-mem

Length of output: 540


🌐 Web query:

tree-sitter-javascript class_declaration grammar node name type

💡 Result:

In tree-sitter-javascript, a class declaration is the named node type class_declaration, and its class name is exposed via the field name, whose node type is identifier:

  • Grammar rule: class_declaration: ... field('name', $.identifier) ... [1]
  • Static node types: class_declaration.fields.name.types = [{ type: "identifier", named: true }] [2]

So a Tree-sitter query to capture the class name is:

(class_declaration
  name: (identifier) `@class.name`)

Tree-sitter “field names” like name: come from the grammar’s field(...) annotations. [3]

Sources: [1] [2] [3]


Use JavaScript identifier for class names in the js query.

Line 108 references type_identifier, which is TypeScript-oriented and does not match JavaScript class names. The tree-sitter-javascript grammar defines class declaration names as identifier. This keeps the JS query invalid and silently misses JavaScript class symbols.

Suggested fix
- (class_declaration name: (type_identifier) `@name`) `@cls`
+ (class_declaration name: (identifier) `@name`) `@cls`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
js: `
(function_declaration name: (identifier) @name) @func
(lexical_declaration (variable_declarator name: (identifier) @name value: [(arrow_function) (function_expression)])) @const_func
(class_declaration name: (type_identifier) @name) @cls
(method_definition name: (property_identifier) @name) @method
js: `
(function_declaration name: (identifier) `@name`) `@func`
(lexical_declaration (variable_declarator name: (identifier) `@name` value: [(arrow_function) (function_expression)])) `@const_func`
(class_declaration name: (identifier) `@name`) `@cls`
(method_definition name: (property_identifier) `@name`) `@method`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/smart-file-read/parser.ts` around lines 105 - 109, The JS
capture for class names in the Tree-sitter query is using TypeScript's
type_identifier and thus misses JS classes; update the js query string so the
class capture uses (class_declaration name: (identifier) `@name`) `@cls` instead of
(class_declaration name: (type_identifier) `@name`) `@cls`—i.e., locate the query
text containing (class_declaration name: (type_identifier) `@name`) `@cls` in
parser.ts and replace type_identifier with identifier so JavaScript class names
are correctly matched.

Comment thread src/utils/uvx-path.ts
Comment on lines +22 to +26
const result = spawnSync('uvx', ['--version'], {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
shell: false
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether the sync probe has a timeout configured.
rg -n --type=ts -C3 "spawnSync\('uvx'|timeout:" src/utils/uvx-path.ts

Repository: thedotmack/claude-mem

Length of output: 295


🏁 Script executed:

cat -n src/utils/uvx-path.ts

Repository: thedotmack/claude-mem

Length of output: 2594


🏁 Script executed:

rg -n "getUvxPath" --type ts -A 2 -B 2 | head -40

Repository: thedotmack/claude-mem

Length of output: 1677


Add a timeout to the sync uvx --version probe.

The spawnSync call on line 22 has no timeout. Since getUvxPath() is invoked during service initialization (in ChromaMcpManager), an unresponsive uvx process would block initialization indefinitely. Although the function includes error handling and fallback paths, these don't prevent the hang—they only catch exceptions. Add a timeout to fail fast.

Proposed fix
     const result = spawnSync('uvx', ['--version'], {
       encoding: 'utf-8',
       stdio: ['pipe', 'pipe', 'pipe'],
-      shell: false
+      shell: false,
+      timeout: 5000
     });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const result = spawnSync('uvx', ['--version'], {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
shell: false
});
const result = spawnSync('uvx', ['--version'], {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
shell: false,
timeout: 5000
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/uvx-path.ts` around lines 22 - 26, The spawnSync call used to probe
uvx in getUvxPath currently has no timeout and can block initialization; modify
the spawnSync invocation in getUvxPath to include a timeout (e.g., 2000 ms) in
its options so the call fails fast, and update the subsequent handling of the
result variable to treat a timeout (check result.error or result.status for
timeout/ETIMEDOUT) the same as other failures so the function falls back
correctly; keep the existing stdio/encoding options and ensure getUvxPath
returns the existing fallback when the probe times out.

Comment thread src/utils/uvx-path.ts
Comment on lines +45 to +49
for (const uvxPath of uvxPaths) {
if (existsSync(uvxPath)) {
logger.debug('SYSTEM', 'Found uvx at known location', { path: uvxPath });
return uvxPath;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether fallback validation checks executability (X_OK) vs existence only.
rg -n --type=ts -C3 "existsSync\(uvxPath\)|accessSync|X_OK|statSync" src/utils/uvx-path.ts

Repository: thedotmack/claude-mem

Length of output: 266


🏁 Script executed:

cat -n src/utils/uvx-path.ts

Repository: thedotmack/claude-mem

Length of output: 2594


🏁 Script executed:

rg -n --type=ts "getUvxPath|uvxPath" src/ -A2 -B2

Repository: thedotmack/claude-mem

Length of output: 2641


🏁 Script executed:

rg -n --type=ts "spawnSync.*uvx\|getUvxPath" src/ -B5 -A5

Repository: thedotmack/claude-mem

Length of output: 47


🏁 Script executed:

rg -n --type=ts "getUvxPath\(\)" src/ -B3 -A3

Repository: thedotmack/claude-mem

Length of output: 1660


🏁 Script executed:

rg -n --type=ts "spawnSync.*resolvedUvxPath\|spawn.*uvxPath" src/services/ -B2 -A10

Repository: thedotmack/claude-mem

Length of output: 47


🏁 Script executed:

rg -n --type=ts "spawnSync.*uvxSpawnCommand" src/services/sync/ChromaMcpManager.ts -B5 -A10

Repository: thedotmack/claude-mem

Length of output: 47


🏁 Script executed:

grep -n "spawnSync" src/services/sync/ChromaMcpManager.ts | head -20

Repository: thedotmack/claude-mem

Length of output: 47


🏁 Script executed:

sed -n '120,140p' src/services/sync/ChromaMcpManager.ts

Repository: thedotmack/claude-mem

Length of output: 771


🏁 Script executed:

cat -n src/services/sync/ChromaMcpManager.ts | sed -n '115,135p'

Repository: thedotmack/claude-mem

Length of output: 1118


Validate executability before returning a fallback path.

The code at lines 45–49 checks existence only. A file that exists but lacks execute permissions would be returned and fail later when StdioClientTransport attempts to spawn it. Use accessSync with fs.constants.X_OK and verify it's a file to catch this before returning.

Proposed fix
-import { existsSync } from 'fs';
+import { accessSync, constants as fsConstants, existsSync, statSync } from 'fs';
@@
   for (const uvxPath of uvxPaths) {
-    if (existsSync(uvxPath)) {
-      logger.debug('SYSTEM', 'Found uvx at known location', { path: uvxPath });
-      return uvxPath;
-    }
+    if (!existsSync(uvxPath)) continue;
+    try {
+      if (!statSync(uvxPath).isFile()) continue;
+      accessSync(uvxPath, fsConstants.X_OK);
+      logger.debug('SYSTEM', 'Found uvx at known location', { path: uvxPath });
+      return uvxPath;
+    } catch {
+      // Not executable; continue searching.
+    }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (const uvxPath of uvxPaths) {
if (existsSync(uvxPath)) {
logger.debug('SYSTEM', 'Found uvx at known location', { path: uvxPath });
return uvxPath;
}
import { accessSync, constants as fsConstants, existsSync, statSync } from 'fs';
// ... other code ...
for (const uvxPath of uvxPaths) {
if (!existsSync(uvxPath)) continue;
try {
if (!statSync(uvxPath).isFile()) continue;
accessSync(uvxPath, fsConstants.X_OK);
logger.debug('SYSTEM', 'Found uvx at known location', { path: uvxPath });
return uvxPath;
} catch {
// Not executable; continue searching.
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/uvx-path.ts` around lines 45 - 49, The loop that returns a uvx path
currently only checks existsSync(uvxPath) and may return a non-executable file;
update the check inside the uvxPaths iteration to also ensure the path is a
regular file and executable by using fs.statSync or statSync.isFile() and
fs.accessSync(uvxPath, fs.constants.X_OK) (catching access exceptions) before
logging and returning; reference the uvxPaths iteration and the logger debug
call so the fix is applied where existsSync(uvxPath) is used to prevent
StdioClientTransport from later failing to spawn a non-executable file.

@thedotmack
Copy link
Copy Markdown
Owner

Closed by @thedotmack. Please reopen if still relevant. Thank you for your contribution!

@thedotmack thedotmack closed this Apr 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: shared jsts tree-sitter query includes TypeScript-only node types, fragile for .js files

2 participants