@plumpslabs/kuma 2.2.7 → 2.2.8
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/dist/index.js +88 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -104,12 +104,89 @@ function isRipgrepAvailable() {
|
|
|
104
104
|
function quotePath(p) {
|
|
105
105
|
return `"${p.replace(/"/g, '\\"')}"`;
|
|
106
106
|
}
|
|
107
|
+
async function searchInSingleFile(filePath, patterns, opts) {
|
|
108
|
+
const { maxResults, contextLines, filenamesOnly, countOnly, outputMode, compact } = opts;
|
|
109
|
+
const validation = validateFilePath(filePath);
|
|
110
|
+
if (!validation.valid) {
|
|
111
|
+
return compact ? `ERR:${filePath} - invalid path` : `Error: ${validation.error.message}`;
|
|
112
|
+
}
|
|
113
|
+
const resolvedPath = validation.resolvedPath;
|
|
114
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
115
|
+
return compact ? `ERR:not found ${filePath}` : `Error: File not found: ${filePath}`;
|
|
116
|
+
}
|
|
117
|
+
const stat = fs.statSync(resolvedPath);
|
|
118
|
+
if (stat.size > 5e5) {
|
|
119
|
+
return compact ? `ERR:too large ${filePath}` : `Error: File too large (${(stat.size / 1024).toFixed(0)}KB). Use a more specific query.`;
|
|
120
|
+
}
|
|
121
|
+
if (isBinaryFile(resolvedPath)) {
|
|
122
|
+
return compact ? `ERR:binary ${filePath}` : `Error: Cannot search binary file: ${filePath}`;
|
|
123
|
+
}
|
|
124
|
+
const projectRoot = getProjectRoot();
|
|
125
|
+
const relativePath = resolvedPath.startsWith(projectRoot + "/") ? resolvedPath.slice(projectRoot.length + 1) : resolvedPath;
|
|
126
|
+
try {
|
|
127
|
+
const content = fs.readFileSync(resolvedPath, "utf-8");
|
|
128
|
+
const lines = content.split("\n");
|
|
129
|
+
const regex = createCombinedRegex(patterns);
|
|
130
|
+
const results = [];
|
|
131
|
+
const maxFileResults = Math.min(maxResults, 100);
|
|
132
|
+
let matchCount = 0;
|
|
133
|
+
for (let i = 0; i < lines.length; i++) {
|
|
134
|
+
if (results.length >= maxFileResults) break;
|
|
135
|
+
if (regex.test(lines[i])) {
|
|
136
|
+
matchCount++;
|
|
137
|
+
if (filenamesOnly) {
|
|
138
|
+
return relativePath;
|
|
139
|
+
}
|
|
140
|
+
if (countOnly) continue;
|
|
141
|
+
const ctxLines = [];
|
|
142
|
+
const startCtx = Math.max(0, i - contextLines);
|
|
143
|
+
const endCtx = Math.min(lines.length - 1, i + contextLines);
|
|
144
|
+
for (let ci = startCtx; ci <= endCtx; ci++) {
|
|
145
|
+
const prefix = ci === i ? ">" : " ";
|
|
146
|
+
ctxLines.push(`${prefix}${ci + 1}: ${lines[ci].substring(0, 200)}`);
|
|
147
|
+
}
|
|
148
|
+
results.push({
|
|
149
|
+
file: relativePath,
|
|
150
|
+
line: i + 1,
|
|
151
|
+
content: ctxLines.join("\n")
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (countOnly) {
|
|
156
|
+
const counts = `${filePath}:${matchCount}`;
|
|
157
|
+
if (outputMode === "json") return JSON.stringify({ [filePath]: matchCount });
|
|
158
|
+
if (compact) return counts;
|
|
159
|
+
return `\u{1F4CA} Count results for "${patterns[0]}":
|
|
160
|
+
${counts}`;
|
|
161
|
+
}
|
|
162
|
+
if (matchCount === 0) {
|
|
163
|
+
if (outputMode === "json") return JSON.stringify({ query: patterns[0], matches: 0 });
|
|
164
|
+
if (compact) return `0:${patterns[0]}`;
|
|
165
|
+
return `\u{1F50D} No matches for "${patterns[0]}" in ${filePath}.`;
|
|
166
|
+
}
|
|
167
|
+
if (compact || outputMode === "raw") {
|
|
168
|
+
return results.map((r) => `${r.file}:${r.line}:${r.content.split("\n")[0].replace(/^[>\s]+\d+:\s*/, "")}`).join("\n");
|
|
169
|
+
}
|
|
170
|
+
if (outputMode === "json") {
|
|
171
|
+
return JSON.stringify(results);
|
|
172
|
+
}
|
|
173
|
+
const header = `\u{1F50D} Smart Grep: "${patterns[0]}" \u2014 ${filePath}
|
|
174
|
+
\u{1F4C1} ${results.length} matches
|
|
175
|
+
`;
|
|
176
|
+
const body = results.map((r, i) => `[${i + 1}] \u{1F4C4} ${r.file}:${r.line}
|
|
177
|
+
${r.content}`).join("\n");
|
|
178
|
+
return limitLines(`${header}${body}`, 150);
|
|
179
|
+
} catch (err) {
|
|
180
|
+
return compact ? `ERR:reading ${filePath}` : `Error reading file "${filePath}": ${err instanceof Error ? err.message : String(err)}`;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
107
183
|
async function handleSmartGrep(params) {
|
|
108
184
|
const {
|
|
109
185
|
query,
|
|
110
186
|
queries,
|
|
111
187
|
targetFolder,
|
|
112
188
|
maxResults = 30,
|
|
189
|
+
filePath,
|
|
113
190
|
extensions,
|
|
114
191
|
contextLines = 1,
|
|
115
192
|
filenamesOnly = false,
|
|
@@ -121,7 +198,7 @@ async function handleSmartGrep(params) {
|
|
|
121
198
|
if (patterns.length === 0 || patterns[0].length < 1) {
|
|
122
199
|
return compact ? "ERR:query required" : "Error: 'query' or 'queries' parameter is required.";
|
|
123
200
|
}
|
|
124
|
-
const cacheKey = `${patterns.join("||")}:${targetFolder ?? "root"}:${maxResults}:${contextLines}:${filenamesOnly}:${countOnly}:${outputMode}:${extensions ? extensions.join(",") : ""}`;
|
|
201
|
+
const cacheKey = `${patterns.join("||")}:${filePath ?? ""}:${targetFolder ?? "root"}:${maxResults}:${contextLines}:${filenamesOnly}:${countOnly}:${outputMode}:${extensions ? extensions.join(",") : ""}`;
|
|
125
202
|
const cached = grepCache.get(cacheKey);
|
|
126
203
|
if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
|
|
127
204
|
sessionMemory.recordToolCall("smart_grep", { query: patterns[0], cached: true });
|
|
@@ -129,7 +206,16 @@ async function handleSmartGrep(params) {
|
|
|
129
206
|
}
|
|
130
207
|
const projectRoot = getProjectRoot();
|
|
131
208
|
let result;
|
|
132
|
-
if (
|
|
209
|
+
if (filePath) {
|
|
210
|
+
result = await searchInSingleFile(filePath, patterns, {
|
|
211
|
+
maxResults,
|
|
212
|
+
contextLines,
|
|
213
|
+
filenamesOnly,
|
|
214
|
+
countOnly,
|
|
215
|
+
outputMode,
|
|
216
|
+
compact
|
|
217
|
+
});
|
|
218
|
+
} else if (isRipgrepAvailable()) {
|
|
133
219
|
result = await tryRipgrep(patterns, {
|
|
134
220
|
projectRoot,
|
|
135
221
|
targetFolder,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plumpslabs/kuma",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.8",
|
|
4
4
|
"description": "Zero-setup safety toolkit for AI coding agents. MCP server with code review, git analysis, file editing, session memory, and static analysis — works with Claude Code, Cursor, Gemini CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|