codebasesearch 0.1.18 → 0.1.20
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.
- package/.prd +58 -0
- package/mcp.js +11 -9
- package/package.json +3 -3
- package/src/search-worker.js +33 -4
package/.prd
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"project": "thorns-mcp",
|
|
3
|
+
"created": "2026-03-06",
|
|
4
|
+
"objective": "Improve search output to be maximally revealing so agents need minimal follow-up exploration",
|
|
5
|
+
"items": [
|
|
6
|
+
{
|
|
7
|
+
"id": "1",
|
|
8
|
+
"subject": "Expand snippet from 3 lines to full chunk content in search-worker.js",
|
|
9
|
+
"status": "pending",
|
|
10
|
+
"description": "In search-worker.js line 63, snippet is truncated to 3 lines. Show full chunk content (up to ~30 lines) so agents see the complete context. Also add totalLines to each result by counting newlines in full file content.",
|
|
11
|
+
"blocking": ["3"],
|
|
12
|
+
"blockedBy": [],
|
|
13
|
+
"effort": "small",
|
|
14
|
+
"category": "feature"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"id": "2",
|
|
18
|
+
"subject": "Add enclosing function/class context detection to search-worker.js",
|
|
19
|
+
"status": "pending",
|
|
20
|
+
"description": "For each result, detect the nearest enclosing function or class name above the match line. Pass this as 'context' field in the result object. Use regex scan backwards through lines above line_start.",
|
|
21
|
+
"blocking": ["3"],
|
|
22
|
+
"blockedBy": [],
|
|
23
|
+
"effort": "medium",
|
|
24
|
+
"category": "feature"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "3",
|
|
28
|
+
"subject": "Update mcp.js output formatter to display all new fields",
|
|
29
|
+
"status": "pending",
|
|
30
|
+
"description": "Update the result formatting in mcp.js to show: totalLines alongside path (e.g. 'file.js [142 lines]:5-20'), enclosing context (e.g. 'in: functionName'), and the full snippet. Also show relative path when repository_path is provided.",
|
|
31
|
+
"blocking": ["4"],
|
|
32
|
+
"blockedBy": ["1", "2"],
|
|
33
|
+
"effort": "small",
|
|
34
|
+
"category": "feature"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "4",
|
|
38
|
+
"subject": "Verify output quality by running CLI on /mnt/c/dev/spawnpoint",
|
|
39
|
+
"status": "pending",
|
|
40
|
+
"description": "Run: node src/cli.js /mnt/c/dev/spawnpoint with a test query and inspect the output quality. Confirm snippets are longer, context is shown, line counts are shown.",
|
|
41
|
+
"blocking": ["5"],
|
|
42
|
+
"blockedBy": ["3"],
|
|
43
|
+
"effort": "small",
|
|
44
|
+
"category": "feature"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"id": "5",
|
|
48
|
+
"subject": "Bump version to 0.1.19 and git push",
|
|
49
|
+
"status": "pending",
|
|
50
|
+
"description": "Update package.json version from 0.1.18 to 0.1.19, git add -A, commit, push.",
|
|
51
|
+
"blocking": [],
|
|
52
|
+
"blockedBy": ["4"],
|
|
53
|
+
"effort": "small",
|
|
54
|
+
"category": "infra"
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
"completed": []
|
|
58
|
+
}
|
package/mcp.js
CHANGED
|
@@ -134,17 +134,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
134
134
|
};
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
const repoPath = typeof repositoryPath === 'string' ? repositoryPath : null;
|
|
137
138
|
const text =
|
|
138
139
|
result.resultsCount === 0
|
|
139
|
-
? `No results found for query: "${query}"
|
|
140
|
-
: `Found ${result.resultsCount} result${result.resultsCount !== 1 ? 's' : ''} for query: "${query}"\
|
|
141
|
-
.map(
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
140
|
+
? `No results found for query: "${query}"`
|
|
141
|
+
: `Found ${result.resultsCount} result${result.resultsCount !== 1 ? 's' : ''} for query: "${query}"\n\n${result.results
|
|
142
|
+
.map((r) => {
|
|
143
|
+
const pathPart = r.relativePath || r.absolutePath;
|
|
144
|
+
const lineCount = r.totalLines ? ` [${r.totalLines}L]` : '';
|
|
145
|
+
const ctx = r.enclosingContext ? ` (in: ${r.enclosingContext})` : '';
|
|
146
|
+
const header = `${r.rank}. ${pathPart}${lineCount}:${r.lines}${ctx} (score: ${r.score}%)`;
|
|
147
|
+
const body = r.snippet.split('\n').map((line) => ` ${line}`).join('\n');
|
|
148
|
+
return `${header}\n${body}`;
|
|
149
|
+
})
|
|
148
150
|
.join('\n\n')}`;
|
|
149
151
|
|
|
150
152
|
return {
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codebasesearch",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
4
4
|
"description": "Ultra-simple code search tool with Jina embeddings, LanceDB, and MCP protocol support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"codebasesearch": "
|
|
8
|
-
"codebasesearch-mcp": "
|
|
7
|
+
"codebasesearch": "bin/code-search.js",
|
|
8
|
+
"codebasesearch-mcp": "mcp.js"
|
|
9
9
|
},
|
|
10
10
|
"main": "src/cli.js",
|
|
11
11
|
"repository": {
|
package/src/search-worker.js
CHANGED
|
@@ -1,10 +1,34 @@
|
|
|
1
1
|
import { parentPort } from 'worker_threads';
|
|
2
2
|
import { resolve, relative } from 'path';
|
|
3
|
-
import { existsSync } from 'fs';
|
|
3
|
+
import { existsSync, readFileSync } from 'fs';
|
|
4
4
|
import { loadIgnorePatterns } from './ignore-parser.js';
|
|
5
5
|
import { scanRepository } from './scanner.js';
|
|
6
6
|
import { buildTextIndex, searchText } from './text-search.js';
|
|
7
7
|
|
|
8
|
+
function findEnclosingContext(content, lineStart) {
|
|
9
|
+
const lines = content.split('\n');
|
|
10
|
+
const targetLine = Math.min(lineStart - 1, lines.length - 1);
|
|
11
|
+
const skip = new Set(['if', 'for', 'while', 'switch', 'catch', 'else']);
|
|
12
|
+
for (let i = targetLine; i >= 0; i--) {
|
|
13
|
+
const line = lines[i];
|
|
14
|
+
const m = line.match(/(?:^|\s)(?:async\s+)?(?:function\s+(\w+)|class\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s*)?\(|(?:static\s+)?(?:async\s+)?(\w+)\s*\([^)]*\)\s*\{)/);
|
|
15
|
+
if (m) {
|
|
16
|
+
const name = m[1] || m[2] || m[3] || m[4];
|
|
17
|
+
if (name && !skip.has(name)) return name;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getFileTotalLines(absoluteFilePath) {
|
|
24
|
+
try {
|
|
25
|
+
const content = readFileSync(absoluteFilePath, 'utf8');
|
|
26
|
+
return content.split('\n').length;
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
8
32
|
let indexCache = new Map();
|
|
9
33
|
|
|
10
34
|
async function initializeIndex(repositoryPath) {
|
|
@@ -55,13 +79,18 @@ async function performSearch(repositoryPath, query) {
|
|
|
55
79
|
resultsCount: results.length,
|
|
56
80
|
results: results.slice(0, 10).map((result, idx) => {
|
|
57
81
|
const absoluteFilePath = resolve(absolutePath, result.file_path);
|
|
82
|
+
const totalLines = getFileTotalLines(absoluteFilePath);
|
|
83
|
+
const enclosingContext = findEnclosingContext(result.content, result.line_start);
|
|
84
|
+
const relPath = relative(absolutePath, absoluteFilePath);
|
|
58
85
|
return {
|
|
59
86
|
rank: idx + 1,
|
|
60
|
-
|
|
61
|
-
relativePath:
|
|
87
|
+
absolutePath: absoluteFilePath,
|
|
88
|
+
relativePath: relPath,
|
|
62
89
|
lines: `${result.line_start}-${result.line_end}`,
|
|
90
|
+
totalLines,
|
|
91
|
+
enclosingContext,
|
|
63
92
|
score: (result.score * 100).toFixed(1),
|
|
64
|
-
snippet: result.content.split('\n').slice(0,
|
|
93
|
+
snippet: result.content.split('\n').slice(0, 30).join('\n'),
|
|
65
94
|
};
|
|
66
95
|
}),
|
|
67
96
|
};
|