grepmax 0.7.7 → 0.7.9

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.
@@ -120,6 +120,10 @@ const TOOLS = [
120
120
  type: "string",
121
121
  description: "Filter by chunk role: 'ORCHESTRATION' (logic/flow), 'DEFINITION' (types/classes), or 'IMPLEMENTATION'.",
122
122
  },
123
+ context_lines: {
124
+ type: "number",
125
+ description: "Include N lines before and after the chunk (like grep -C). Only with detail 'code' or 'full'. Max 20.",
126
+ },
123
127
  },
124
128
  required: ["query"],
125
129
  },
@@ -166,6 +170,18 @@ const TOOLS = [
166
170
  type: "string",
167
171
  description: "Filter by role: 'ORCHESTRATION', 'DEFINITION', or 'IMPLEMENTATION'.",
168
172
  },
173
+ projects: {
174
+ type: "string",
175
+ description: "Comma-separated project names to include (e.g. 'platform,osgrep'). Use index_status to see names.",
176
+ },
177
+ exclude_projects: {
178
+ type: "string",
179
+ description: "Comma-separated project names to exclude (e.g. 'capstone,power').",
180
+ },
181
+ context_lines: {
182
+ type: "number",
183
+ description: "Include N lines before/after chunk. Only with detail 'code' or 'full'. Max 20.",
184
+ },
169
185
  },
170
186
  required: ["query"],
171
187
  },
@@ -472,6 +488,32 @@ exports.mcp = new commander_1.Command("mcp")
472
488
  if (typeof args.role === "string" && args.role) {
473
489
  filters.role = args.role;
474
490
  }
491
+ if (searchAll) {
492
+ const allProjects = (0, project_registry_1.listProjects)();
493
+ if (typeof args.projects === "string" && args.projects) {
494
+ const names = args.projects
495
+ .split(",")
496
+ .map((s) => s.trim());
497
+ const roots = names
498
+ .map((n) => { var _a; return (_a = allProjects.find((p) => p.name === n)) === null || _a === void 0 ? void 0 : _a.root; })
499
+ .filter(Boolean);
500
+ if (roots.length > 0) {
501
+ filters.project_roots = roots.join(",");
502
+ }
503
+ }
504
+ if (typeof args.exclude_projects === "string" &&
505
+ args.exclude_projects) {
506
+ const names = args.exclude_projects
507
+ .split(",")
508
+ .map((s) => s.trim());
509
+ const roots = names
510
+ .map((n) => { var _a; return (_a = allProjects.find((p) => p.name === n)) === null || _a === void 0 ? void 0 : _a.root; })
511
+ .filter(Boolean);
512
+ if (roots.length > 0) {
513
+ filters.exclude_project_roots = roots.join(",");
514
+ }
515
+ }
516
+ }
475
517
  const result = yield searcher.search(query, limit, { rerank: true }, Object.keys(filters).length > 0 ? filters : undefined, pathPrefix);
476
518
  if (!result.data || result.data.length === 0) {
477
519
  return ok("No matches found.");
@@ -507,19 +549,42 @@ exports.mcp = new commander_1.Command("mcp")
507
549
  ? ` ${parentStr}${callsStr}`
508
550
  : "";
509
551
  let snippet = "";
552
+ const contextN = typeof args.context_lines === "number"
553
+ ? Math.min(Math.max(args.context_lines, 0), 20)
554
+ : 0;
510
555
  if (detail === "code" || detail === "full") {
511
- const raw = typeof r.content === "string"
512
- ? r.content
513
- : typeof r.text === "string"
514
- ? r.text
515
- : "";
516
- const allLines = raw.split("\n");
517
- const linesToShow = detail === "full" ? allLines : allLines.slice(0, 4);
518
- snippet =
519
- "\n" +
520
- linesToShow
521
- .map((l, i) => `${startLine + i + 1}│${l}`)
522
- .join("\n");
556
+ if (contextN > 0 && absPath) {
557
+ // Read surrounding context from file
558
+ try {
559
+ const fileContent = fs.readFileSync(absPath, "utf-8");
560
+ const fileLines = fileContent.split("\n");
561
+ const ctxStart = Math.max(0, startLine - contextN);
562
+ const ctxEnd = Math.min(fileLines.length, endLine + 1 + contextN);
563
+ snippet =
564
+ "\n" +
565
+ fileLines
566
+ .slice(ctxStart, ctxEnd)
567
+ .map((l, i) => `${ctxStart + i + 1}│${l}`)
568
+ .join("\n");
569
+ }
570
+ catch (_o) {
571
+ // Fall through to chunk content
572
+ }
573
+ }
574
+ if (!snippet) {
575
+ const raw = typeof r.content === "string"
576
+ ? r.content
577
+ : typeof r.text === "string"
578
+ ? r.text
579
+ : "";
580
+ const allLines = raw.split("\n");
581
+ const linesToShow = detail === "full" ? allLines : allLines.slice(0, 4);
582
+ snippet =
583
+ "\n" +
584
+ linesToShow
585
+ .map((l, i) => `${startLine + i + 1}│${l}`)
586
+ .join("\n");
587
+ }
523
588
  }
524
589
  const text = line1 +
525
590
  (summaryStr ? `\n${summaryStr}` : "") +
@@ -556,6 +621,31 @@ exports.mcp = new commander_1.Command("mcp")
556
621
  }
557
622
  });
558
623
  }
624
+ function annotateSkeletonLines(skeleton, sourceContent) {
625
+ const sourceLines = sourceContent.split("\n");
626
+ const skelLines = skeleton.split("\n");
627
+ const used = new Set();
628
+ return skelLines
629
+ .map((skelLine) => {
630
+ const trimmed = skelLine.trim();
631
+ if (!trimmed ||
632
+ trimmed.startsWith("//") ||
633
+ trimmed.startsWith("*") ||
634
+ trimmed.startsWith("/*")) {
635
+ return skelLine;
636
+ }
637
+ // Match against source lines using first 40 chars
638
+ const matchStr = trimmed.slice(0, 40);
639
+ for (let i = 0; i < sourceLines.length; i++) {
640
+ if (!used.has(i) && sourceLines[i].includes(matchStr)) {
641
+ used.add(i);
642
+ return `${String(i + 1).padStart(4)}│${skelLine}`;
643
+ }
644
+ }
645
+ return skelLine;
646
+ })
647
+ .join("\n");
648
+ }
559
649
  function handleCodeSkeleton(args) {
560
650
  return __awaiter(this, void 0, void 0, function* () {
561
651
  const target = String(args.target || "");
@@ -596,25 +686,35 @@ exports.mcp = new commander_1.Command("mcp")
596
686
  parts.push(`// ${t} — file not found`);
597
687
  continue;
598
688
  }
689
+ // Read source for line annotations
690
+ let sourceContent = "";
691
+ try {
692
+ sourceContent = fs.readFileSync(absPath, "utf-8");
693
+ }
694
+ catch (_a) { }
599
695
  // Try cached skeleton first
600
696
  try {
601
697
  const db = getVectorDb();
602
698
  const cached = yield (0, retriever_1.getStoredSkeleton)(db, absPath);
603
699
  if (cached) {
604
700
  const tokens = Math.ceil(cached.length / 4);
605
- parts.push(`// ${t} (~${tokens} tokens)\n\n${cached}`);
701
+ const annotated = sourceContent
702
+ ? annotateSkeletonLines(cached, sourceContent)
703
+ : cached;
704
+ parts.push(`// ${t} (~${tokens} tokens)\n\n${annotated}`);
606
705
  continue;
607
706
  }
608
707
  }
609
- catch (_a) {
708
+ catch (_b) {
610
709
  // Index may not exist yet — fall through to live generation
611
710
  }
612
711
  // Generate live
613
712
  try {
614
- const content = fs.readFileSync(absPath, "utf-8");
713
+ const content = sourceContent || fs.readFileSync(absPath, "utf-8");
615
714
  const result = yield skel.skeletonizeFile(absPath, content);
616
715
  if (result.success) {
617
- parts.push(`// ${t} (~${result.tokenEstimate} tokens)\n\n${result.skeleton}`);
716
+ const annotated = annotateSkeletonLines(result.skeleton, content);
717
+ parts.push(`// ${t} (~${result.tokenEstimate} tokens)\n\n${annotated}`);
618
718
  }
619
719
  else {
620
720
  parts.push(`// ${t} — skeleton failed: ${result.error}`);
@@ -698,7 +798,7 @@ exports.mcp = new commander_1.Command("mcp")
698
798
  const table = yield db.ensureTable();
699
799
  let query = table
700
800
  .query()
701
- .select(["defined_symbols", "path", "start_line"])
801
+ .select(["defined_symbols", "path", "start_line", "role", "is_exported"])
702
802
  .where("array_length(defined_symbols) > 0")
703
803
  .limit(pattern ? 10000 : Math.max(limit * 50, 2000));
704
804
  if (pathPrefix) {
@@ -714,6 +814,8 @@ exports.mcp = new commander_1.Command("mcp")
714
814
  const defs = toStringArray(row.defined_symbols);
715
815
  const rowPath = String(row.path || "");
716
816
  const line = Number(row.start_line || 0);
817
+ const role = String(row.role || "");
818
+ const exported = Boolean(row.is_exported);
717
819
  for (const sym of defs) {
718
820
  if (pattern && !sym.toLowerCase().includes(pattern.toLowerCase())) {
719
821
  continue;
@@ -728,6 +830,8 @@ exports.mcp = new commander_1.Command("mcp")
728
830
  count: 1,
729
831
  path: rowPath,
730
832
  line: Math.max(1, line + 1),
833
+ role,
834
+ exported,
731
835
  });
732
836
  }
733
837
  }
@@ -746,7 +850,9 @@ exports.mcp = new commander_1.Command("mcp")
746
850
  const rel = e.path.startsWith(projectRoot)
747
851
  ? e.path.slice(projectRoot.length + 1)
748
852
  : e.path;
749
- return `${e.symbol}\t${rel}:${e.line}`;
853
+ const roleTag = e.role ? ` [${e.role.slice(0, 4)}]` : "";
854
+ const expTag = e.exported ? " exported" : "";
855
+ return `${e.symbol}${roleTag}${expTag}\t${rel}:${e.line}`;
750
856
  });
751
857
  return ok(lines.join("\n"));
752
858
  }
@@ -301,6 +301,24 @@ class Searcher {
301
301
  if (typeof roleFilter === "string" && roleFilter) {
302
302
  whereClauseParts.push(`role = '${(0, filter_builder_1.escapeSqlString)(roleFilter)}'`);
303
303
  }
304
+ // Handle project roots filter (from search_all projects param)
305
+ const projectRoots = _filters === null || _filters === void 0 ? void 0 : _filters.project_roots;
306
+ if (typeof projectRoots === "string" && projectRoots) {
307
+ const roots = projectRoots.split(",");
308
+ const clauses = roots.map((r) => {
309
+ const prefix = r.endsWith("/") ? r : `${r}/`;
310
+ return `path LIKE '${(0, filter_builder_1.escapeSqlString)(prefix)}%'`;
311
+ });
312
+ whereClauseParts.push(`(${clauses.join(" OR ")})`);
313
+ }
314
+ // Handle exclude project roots filter
315
+ const excludeRoots = _filters === null || _filters === void 0 ? void 0 : _filters.exclude_project_roots;
316
+ if (typeof excludeRoots === "string" && excludeRoots) {
317
+ for (const r of excludeRoots.split(",")) {
318
+ const prefix = r.endsWith("/") ? r : `${r}/`;
319
+ whereClauseParts.push(`path NOT LIKE '${(0, filter_builder_1.escapeSqlString)(prefix)}%'`);
320
+ }
321
+ }
304
322
  // Handle --def (definition) filter
305
323
  const defFilter = _filters === null || _filters === void 0 ? void 0 : _filters.def;
306
324
  if (typeof defFilter === "string" && defFilter) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.7.7",
3
+ "version": "0.7.9",
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.7",
3
+ "version": "0.7.9",
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",
@@ -40,6 +40,7 @@ Parameters:
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
42
  - `detail` (optional): `"pointer"` (default), `"code"` (4-line snippets), or `"full"` (complete chunk with line numbers)
43
+ - `context_lines` (optional): Include N lines before/after the chunk (like grep -C). Only with detail "code" or "full". Max 20.
43
44
  - `min_score` (optional): Filter by minimum relevance score (0-1)
44
45
  - `max_per_file` (optional): Cap results per file for diversity
45
46
  - `file` (optional): Filter to files matching this name (e.g. "syncer.ts"). Matches filename, not full path.
@@ -52,7 +53,11 @@ Parameters:
52
53
  - `code` — comparing implementations, finding duplicates, checking syntax
53
54
 
54
55
  ### search_all
55
- Search ALL indexed code across every directory. Same parameters as semantic_search (query, limit, detail, min_score, max_per_file) but without `root` or `path` — searches everything.
56
+ Search ALL indexed code across every directory. Same parameters as semantic_search (query, limit, detail, min_score, max_per_file, file, exclude, language, role) but without `root` or `path`.
57
+
58
+ Additional parameters:
59
+ - `projects` (optional): Comma-separated project names to include (e.g. "platform,osgrep"). Use `index_status` to see names.
60
+ - `exclude_projects` (optional): Comma-separated project names to exclude (e.g. "capstone,power")
56
61
 
57
62
  Use sparingly. Prefer `semantic_search` when you know which directory to search.
58
63
 
@@ -66,11 +71,13 @@ Call graph — who calls a symbol and what it calls. Callers and callees include
66
71
  - `symbol` (required): Function/method/class name
67
72
 
68
73
  ### list_symbols
69
- List indexed symbols with definition locations.
74
+ List indexed symbols with definition locations, role, and export status.
70
75
  - `pattern` (optional): Filter by name (case-insensitive substring match)
71
76
  - `limit` (optional): Max results (default 20, max 100)
72
77
  - `path` (optional): Only symbols under this path prefix
73
78
 
79
+ Output: `symbolName [ORCH] exported src/path/file.ts:42`
80
+
74
81
  ### index_status
75
82
  Check centralized index health — chunks, files, indexed directories, model info, watcher status.
76
83