grepmax 0.7.5 → 0.7.7

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.
@@ -61,6 +61,7 @@ const searcher_1 = require("../lib/search/searcher");
61
61
  const retriever_1 = require("../lib/skeleton/retriever");
62
62
  const skeletonizer_1 = require("../lib/skeleton/skeletonizer");
63
63
  const vector_db_1 = require("../lib/store/vector-db");
64
+ const file_utils_1 = require("../lib/utils/file-utils");
64
65
  const filter_builder_1 = require("../lib/utils/filter-builder");
65
66
  const project_registry_1 = require("../lib/utils/project-registry");
66
67
  const project_root_1 = require("../lib/utils/project-root");
@@ -93,7 +94,7 @@ const TOOLS = [
93
94
  },
94
95
  detail: {
95
96
  type: "string",
96
- description: "Output detail: 'pointer' (default, metadata only — symbol, location, role, calls) or 'code' (include 4-line code snippets)",
97
+ description: "Output detail: 'pointer' (default, metadata only), 'code' (4-line snippets), or 'full' (complete chunk content with line numbers)",
97
98
  },
98
99
  min_score: {
99
100
  type: "number",
@@ -111,6 +112,14 @@ const TOOLS = [
111
112
  type: "string",
112
113
  description: "Exclude files under this path prefix (e.g. 'tests/' or 'dist/').",
113
114
  },
115
+ language: {
116
+ type: "string",
117
+ description: "Filter by file extension (e.g. 'ts', 'py', 'go'). Omit the dot.",
118
+ },
119
+ role: {
120
+ type: "string",
121
+ description: "Filter by chunk role: 'ORCHESTRATION' (logic/flow), 'DEFINITION' (types/classes), or 'IMPLEMENTATION'.",
122
+ },
114
123
  },
115
124
  required: ["query"],
116
125
  },
@@ -131,7 +140,7 @@ const TOOLS = [
131
140
  },
132
141
  detail: {
133
142
  type: "string",
134
- description: "Output detail: 'pointer' (default) or 'code' (include snippets)",
143
+ description: "Output detail: 'pointer' (default), 'code' (snippets), or 'full' (complete content)",
135
144
  },
136
145
  min_score: {
137
146
  type: "number",
@@ -149,19 +158,31 @@ const TOOLS = [
149
158
  type: "string",
150
159
  description: "Exclude files under this path prefix (e.g. 'tests/').",
151
160
  },
161
+ language: {
162
+ type: "string",
163
+ description: "Filter by file extension (e.g. 'ts', 'py').",
164
+ },
165
+ role: {
166
+ type: "string",
167
+ description: "Filter by role: 'ORCHESTRATION', 'DEFINITION', or 'IMPLEMENTATION'.",
168
+ },
152
169
  },
153
170
  required: ["query"],
154
171
  },
155
172
  },
156
173
  {
157
174
  name: "code_skeleton",
158
- description: "Show the structure of a source fileall function/class/method signatures with bodies collapsed. Useful for understanding large files without reading every line. Returns ~4x fewer tokens than the full file.",
175
+ description: "Show the structure of source files — signatures with bodies collapsed (~4x fewer tokens). Accepts a file path, a directory path, or comma-separated file paths.",
159
176
  inputSchema: {
160
177
  type: "object",
161
178
  properties: {
162
179
  target: {
163
180
  type: "string",
164
- description: "File path relative to project root (e.g. 'src/services/booking.ts')",
181
+ description: "File path, directory path (e.g. 'src/lib/search/'), or comma-separated files (e.g. 'src/a.ts,src/b.ts'). Relative to project root.",
182
+ },
183
+ limit: {
184
+ type: "number",
185
+ description: "Max files for directory mode (default 10, max 20). Ignored for single files.",
165
186
  },
166
187
  },
167
188
  required: ["target"],
@@ -445,6 +466,12 @@ exports.mcp = new commander_1.Command("mcp")
445
466
  if (typeof args.exclude === "string" && args.exclude) {
446
467
  filters.exclude = args.exclude;
447
468
  }
469
+ if (typeof args.language === "string" && args.language) {
470
+ filters.language = args.language;
471
+ }
472
+ if (typeof args.role === "string" && args.role) {
473
+ filters.role = args.role;
474
+ }
448
475
  const result = yield searcher.search(query, limit, { rerank: true }, Object.keys(filters).length > 0 ? filters : undefined, pathPrefix);
449
476
  if (!result.data || result.data.length === 0) {
450
477
  return ok("No matches found.");
@@ -480,16 +507,17 @@ exports.mcp = new commander_1.Command("mcp")
480
507
  ? ` ${parentStr}${callsStr}`
481
508
  : "";
482
509
  let snippet = "";
483
- if (detail === "code") {
510
+ if (detail === "code" || detail === "full") {
484
511
  const raw = typeof r.content === "string"
485
512
  ? r.content
486
513
  : typeof r.text === "string"
487
514
  ? r.text
488
515
  : "";
489
- const lines = raw.split("\n").slice(0, 4);
516
+ const allLines = raw.split("\n");
517
+ const linesToShow = detail === "full" ? allLines : allLines.slice(0, 4);
490
518
  snippet =
491
519
  "\n" +
492
- lines
520
+ linesToShow
493
521
  .map((l, i) => `${startLine + i + 1}│${l}`)
494
522
  .join("\n");
495
523
  }
@@ -533,36 +561,71 @@ exports.mcp = new commander_1.Command("mcp")
533
561
  const target = String(args.target || "");
534
562
  if (!target)
535
563
  return err("Missing required parameter: target");
536
- const absPath = path.resolve(projectRoot, target);
537
- if (!fs.existsSync(absPath)) {
538
- return err(`File not found: ${target}`);
564
+ const fileLimit = Math.min(Math.max(Number(args.limit) || 10, 1), 20);
565
+ // Determine targets: comma-separated, directory, or single file
566
+ let targets;
567
+ if (target.includes(",")) {
568
+ targets = target
569
+ .split(",")
570
+ .map((t) => t.trim())
571
+ .filter(Boolean);
539
572
  }
540
- // Try cached skeleton first (stored with absolute path)
541
- try {
542
- const db = getVectorDb();
543
- const cached = yield (0, retriever_1.getStoredSkeleton)(db, absPath);
544
- if (cached) {
545
- const tokens = Math.ceil(cached.length / 4);
546
- return ok(`// ${target} (~${tokens} tokens)\n\n${cached}`);
573
+ else {
574
+ const absPath = path.resolve(projectRoot, target);
575
+ if (fs.existsSync(absPath) && fs.statSync(absPath).isDirectory()) {
576
+ const entries = fs.readdirSync(absPath, { withFileTypes: true });
577
+ targets = entries
578
+ .filter((e) => e.isFile() &&
579
+ (0, file_utils_1.isIndexableFile)(path.join(absPath, e.name)))
580
+ .map((e) => path.relative(projectRoot, path.join(absPath, e.name)))
581
+ .slice(0, fileLimit);
582
+ if (targets.length === 0) {
583
+ return err(`No indexable files found in ${target}`);
584
+ }
547
585
  }
548
- }
549
- catch (_a) {
550
- // Index may not exist yet — fall through to live generation
551
- }
552
- // Generate skeleton from file
553
- try {
554
- const content = fs.readFileSync(absPath, "utf-8");
555
- const skel = yield getSkeletonizer();
556
- const result = yield skel.skeletonizeFile(absPath, content);
557
- if (!result.success && result.error) {
558
- return err(`Skeleton generation failed: ${result.error}`);
586
+ else {
587
+ targets = [target];
559
588
  }
560
- return ok(`// ${target} (~${result.tokenEstimate} tokens)\n\n${result.skeleton}`);
561
589
  }
562
- catch (e) {
563
- const msg = e instanceof Error ? e.message : String(e);
564
- return err(`Skeleton failed: ${msg}`);
590
+ // Generate skeletons for all targets
591
+ const parts = [];
592
+ const skel = yield getSkeletonizer();
593
+ for (const t of targets) {
594
+ const absPath = path.resolve(projectRoot, t);
595
+ if (!fs.existsSync(absPath)) {
596
+ parts.push(`// ${t} — file not found`);
597
+ continue;
598
+ }
599
+ // Try cached skeleton first
600
+ try {
601
+ const db = getVectorDb();
602
+ const cached = yield (0, retriever_1.getStoredSkeleton)(db, absPath);
603
+ if (cached) {
604
+ const tokens = Math.ceil(cached.length / 4);
605
+ parts.push(`// ${t} (~${tokens} tokens)\n\n${cached}`);
606
+ continue;
607
+ }
608
+ }
609
+ catch (_a) {
610
+ // Index may not exist yet — fall through to live generation
611
+ }
612
+ // Generate live
613
+ try {
614
+ const content = fs.readFileSync(absPath, "utf-8");
615
+ const result = yield skel.skeletonizeFile(absPath, content);
616
+ if (result.success) {
617
+ parts.push(`// ${t} (~${result.tokenEstimate} tokens)\n\n${result.skeleton}`);
618
+ }
619
+ else {
620
+ parts.push(`// ${t} — skeleton failed: ${result.error}`);
621
+ }
622
+ }
623
+ catch (e) {
624
+ const msg = e instanceof Error ? e.message : String(e);
625
+ parts.push(`// ${t} — error: ${msg}`);
626
+ }
565
627
  }
628
+ return ok(parts.join("\n\n---\n\n"));
566
629
  });
567
630
  }
568
631
  function handleTraceCalls(args) {
@@ -290,6 +290,17 @@ class Searcher {
290
290
  : excludeFilter;
291
291
  whereClauseParts.push(`path NOT LIKE '${(0, filter_builder_1.escapeSqlString)(absExclude)}%'`);
292
292
  }
293
+ // Handle language filter (by file extension)
294
+ const langFilter = _filters === null || _filters === void 0 ? void 0 : _filters.language;
295
+ if (typeof langFilter === "string" && langFilter) {
296
+ const ext = langFilter.startsWith(".") ? langFilter : `.${langFilter}`;
297
+ whereClauseParts.push(`path LIKE '%${(0, filter_builder_1.escapeSqlString)(ext)}'`);
298
+ }
299
+ // Handle role filter
300
+ const roleFilter = _filters === null || _filters === void 0 ? void 0 : _filters.role;
301
+ if (typeof roleFilter === "string" && roleFilter) {
302
+ whereClauseParts.push(`role = '${(0, filter_builder_1.escapeSqlString)(roleFilter)}'`);
303
+ }
293
304
  // Handle --def (definition) filter
294
305
  const defFilter = _filters === null || _filters === void 0 ? void 0 : _filters.def;
295
306
  if (typeof defFilter === "string" && defFilter) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.7.5",
3
+ "version": "0.7.7",
4
4
  "author": "Robert Owens <robowens@me.com>",
5
5
  "homepage": "https://github.com/reowens/grepmax",
6
6
  "bugs": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.7.5",
3
+ "version": "0.7.7",
4
4
  "description": "Semantic code search for Claude Code. Automatically indexes your project and provides intelligent search capabilities.",
5
5
  "author": {
6
6
  "name": "Robert Owens",
@@ -39,11 +39,13 @@ Parameters:
39
39
  - `limit` (optional): Max results (default 3, max 50)
40
40
  - `root` (optional): Absolute path to search a different indexed directory.
41
41
  - `path` (optional): Restrict to path prefix (e.g. "src/auth/"). Relative to the search root.
42
- - `detail` (optional): `"pointer"` (default) or `"code"`
42
+ - `detail` (optional): `"pointer"` (default), `"code"` (4-line snippets), or `"full"` (complete chunk with line numbers)
43
43
  - `min_score` (optional): Filter by minimum relevance score (0-1)
44
44
  - `max_per_file` (optional): Cap results per file for diversity
45
45
  - `file` (optional): Filter to files matching this name (e.g. "syncer.ts"). Matches filename, not full path.
46
46
  - `exclude` (optional): Exclude files under this path prefix (e.g. "tests/" or "dist/")
47
+ - `language` (optional): Filter by file extension (e.g. "ts", "py", "go"). Omit the dot.
48
+ - `role` (optional): Filter by chunk role: "ORCHESTRATION" (logic/flow), "DEFINITION" (types), or "IMPLEMENTATION"
47
49
 
48
50
  **When to use which mode:**
49
51
  - `pointer` — navigation, finding locations, understanding architecture
@@ -55,8 +57,9 @@ Search ALL indexed code across every directory. Same parameters as semantic_sear
55
57
  Use sparingly. Prefer `semantic_search` when you know which directory to search.
56
58
 
57
59
  ### code_skeleton
58
- File structure — signatures with bodies collapsed (~4x fewer tokens).
59
- - `target` (required): File path relative to project root
60
+ File or directory structure — signatures with bodies collapsed (~4x fewer tokens).
61
+ - `target` (required): File path, directory path (e.g. "src/lib/search/"), or comma-separated files
62
+ - `limit` (optional): Max files for directory mode (default 10, max 20)
60
63
 
61
64
  ### trace_calls
62
65
  Call graph — who calls a symbol and what it calls. Callers and callees include file:line locations. Unscoped — follows calls across all indexed directories.