grepmax 0.7.8 → 0.7.10

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,14 @@ 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
+ },
127
+ mode: {
128
+ type: "string",
129
+ description: "Search mode: 'default' (semantic only) or 'symbol' (semantic + call graph trace appended). Use 'symbol' when query is a function/class name.",
130
+ },
123
131
  },
124
132
  required: ["query"],
125
133
  },
@@ -174,6 +182,10 @@ const TOOLS = [
174
182
  type: "string",
175
183
  description: "Comma-separated project names to exclude (e.g. 'capstone,power').",
176
184
  },
185
+ context_lines: {
186
+ type: "number",
187
+ description: "Include N lines before/after chunk. Only with detail 'code' or 'full'. Max 20.",
188
+ },
177
189
  },
178
190
  required: ["query"],
179
191
  },
@@ -541,19 +553,42 @@ exports.mcp = new commander_1.Command("mcp")
541
553
  ? ` ${parentStr}${callsStr}`
542
554
  : "";
543
555
  let snippet = "";
556
+ const contextN = typeof args.context_lines === "number"
557
+ ? Math.min(Math.max(args.context_lines, 0), 20)
558
+ : 0;
544
559
  if (detail === "code" || detail === "full") {
545
- const raw = typeof r.content === "string"
546
- ? r.content
547
- : typeof r.text === "string"
548
- ? r.text
549
- : "";
550
- const allLines = raw.split("\n");
551
- const linesToShow = detail === "full" ? allLines : allLines.slice(0, 4);
552
- snippet =
553
- "\n" +
554
- linesToShow
555
- .map((l, i) => `${startLine + i + 1}│${l}`)
556
- .join("\n");
560
+ if (contextN > 0 && absPath) {
561
+ // Read surrounding context from file
562
+ try {
563
+ const fileContent = fs.readFileSync(absPath, "utf-8");
564
+ const fileLines = fileContent.split("\n");
565
+ const ctxStart = Math.max(0, startLine - contextN);
566
+ const ctxEnd = Math.min(fileLines.length, endLine + 1 + contextN);
567
+ snippet =
568
+ "\n" +
569
+ fileLines
570
+ .slice(ctxStart, ctxEnd)
571
+ .map((l, i) => `${ctxStart + i + 1}│${l}`)
572
+ .join("\n");
573
+ }
574
+ catch (_o) {
575
+ // Fall through to chunk content
576
+ }
577
+ }
578
+ if (!snippet) {
579
+ const raw = typeof r.content === "string"
580
+ ? r.content
581
+ : typeof r.text === "string"
582
+ ? r.text
583
+ : "";
584
+ const allLines = raw.split("\n");
585
+ const linesToShow = detail === "full" ? allLines : allLines.slice(0, 4);
586
+ snippet =
587
+ "\n" +
588
+ linesToShow
589
+ .map((l, i) => `${startLine + i + 1}│${l}`)
590
+ .join("\n");
591
+ }
557
592
  }
558
593
  const text = line1 +
559
594
  (summaryStr ? `\n${summaryStr}` : "") +
@@ -578,7 +613,50 @@ exports.mcp = new commander_1.Command("mcp")
578
613
  return true;
579
614
  });
580
615
  }
581
- const output = results.map((r) => r.text).join("\n\n");
616
+ let output = results.map((r) => r.text).join("\n\n");
617
+ // Symbol mode: append call graph
618
+ const mode = typeof args.mode === "string" ? args.mode : "default";
619
+ if (mode === "symbol" && !searchAll) {
620
+ try {
621
+ const db = getVectorDb();
622
+ const builder = new graph_builder_1.GraphBuilder(db);
623
+ const graph = yield builder.buildGraph(query);
624
+ if (graph.center) {
625
+ const traceLines = ["", "--- Call graph ---"];
626
+ const centerRel = graph.center.file.startsWith(projectRoot)
627
+ ? graph.center.file.slice(projectRoot.length + 1)
628
+ : graph.center.file;
629
+ traceLines.push(`${graph.center.symbol} [${graph.center.role}] ${centerRel}:${graph.center.line + 1}`);
630
+ if (graph.callers.length > 0) {
631
+ traceLines.push("Callers:");
632
+ for (const caller of graph.callers) {
633
+ const rel = caller.file.startsWith(projectRoot)
634
+ ? caller.file.slice(projectRoot.length + 1)
635
+ : caller.file;
636
+ traceLines.push(` <- ${caller.symbol} ${rel}:${caller.line + 1}`);
637
+ }
638
+ }
639
+ if (graph.callees.length > 0) {
640
+ traceLines.push("Calls:");
641
+ for (const callee of graph.callees.slice(0, 15)) {
642
+ if (callee.file) {
643
+ const rel = callee.file.startsWith(projectRoot)
644
+ ? callee.file.slice(projectRoot.length + 1)
645
+ : callee.file;
646
+ traceLines.push(` -> ${callee.symbol} ${rel}:${callee.line + 1}`);
647
+ }
648
+ else {
649
+ traceLines.push(` -> ${callee.symbol} (not indexed)`);
650
+ }
651
+ }
652
+ }
653
+ output += `\n${traceLines.join("\n")}`;
654
+ }
655
+ }
656
+ catch (_b) {
657
+ // Trace failed — return search results without trace
658
+ }
659
+ }
582
660
  if ((_a = result.warnings) === null || _a === void 0 ? void 0 : _a.length) {
583
661
  return ok(`${result.warnings.join("\n")}\n\n${output}`);
584
662
  }
@@ -590,6 +668,31 @@ exports.mcp = new commander_1.Command("mcp")
590
668
  }
591
669
  });
592
670
  }
671
+ function annotateSkeletonLines(skeleton, sourceContent) {
672
+ const sourceLines = sourceContent.split("\n");
673
+ const skelLines = skeleton.split("\n");
674
+ const used = new Set();
675
+ return skelLines
676
+ .map((skelLine) => {
677
+ const trimmed = skelLine.trim();
678
+ if (!trimmed ||
679
+ trimmed.startsWith("//") ||
680
+ trimmed.startsWith("*") ||
681
+ trimmed.startsWith("/*")) {
682
+ return skelLine;
683
+ }
684
+ // Match against source lines using first 40 chars
685
+ const matchStr = trimmed.slice(0, 40);
686
+ for (let i = 0; i < sourceLines.length; i++) {
687
+ if (!used.has(i) && sourceLines[i].includes(matchStr)) {
688
+ used.add(i);
689
+ return `${String(i + 1).padStart(4)}│${skelLine}`;
690
+ }
691
+ }
692
+ return skelLine;
693
+ })
694
+ .join("\n");
695
+ }
593
696
  function handleCodeSkeleton(args) {
594
697
  return __awaiter(this, void 0, void 0, function* () {
595
698
  const target = String(args.target || "");
@@ -630,25 +733,35 @@ exports.mcp = new commander_1.Command("mcp")
630
733
  parts.push(`// ${t} — file not found`);
631
734
  continue;
632
735
  }
736
+ // Read source for line annotations
737
+ let sourceContent = "";
738
+ try {
739
+ sourceContent = fs.readFileSync(absPath, "utf-8");
740
+ }
741
+ catch (_a) { }
633
742
  // Try cached skeleton first
634
743
  try {
635
744
  const db = getVectorDb();
636
745
  const cached = yield (0, retriever_1.getStoredSkeleton)(db, absPath);
637
746
  if (cached) {
638
747
  const tokens = Math.ceil(cached.length / 4);
639
- parts.push(`// ${t} (~${tokens} tokens)\n\n${cached}`);
748
+ const annotated = sourceContent
749
+ ? annotateSkeletonLines(cached, sourceContent)
750
+ : cached;
751
+ parts.push(`// ${t} (~${tokens} tokens)\n\n${annotated}`);
640
752
  continue;
641
753
  }
642
754
  }
643
- catch (_a) {
755
+ catch (_b) {
644
756
  // Index may not exist yet — fall through to live generation
645
757
  }
646
758
  // Generate live
647
759
  try {
648
- const content = fs.readFileSync(absPath, "utf-8");
760
+ const content = sourceContent || fs.readFileSync(absPath, "utf-8");
649
761
  const result = yield skel.skeletonizeFile(absPath, content);
650
762
  if (result.success) {
651
- parts.push(`// ${t} (~${result.tokenEstimate} tokens)\n\n${result.skeleton}`);
763
+ const annotated = annotateSkeletonLines(result.skeleton, content);
764
+ parts.push(`// ${t} (~${result.tokenEstimate} tokens)\n\n${annotated}`);
652
765
  }
653
766
  else {
654
767
  parts.push(`// ${t} — skeleton failed: ${result.error}`);
@@ -732,7 +845,7 @@ exports.mcp = new commander_1.Command("mcp")
732
845
  const table = yield db.ensureTable();
733
846
  let query = table
734
847
  .query()
735
- .select(["defined_symbols", "path", "start_line"])
848
+ .select(["defined_symbols", "path", "start_line", "role", "is_exported"])
736
849
  .where("array_length(defined_symbols) > 0")
737
850
  .limit(pattern ? 10000 : Math.max(limit * 50, 2000));
738
851
  if (pathPrefix) {
@@ -748,6 +861,8 @@ exports.mcp = new commander_1.Command("mcp")
748
861
  const defs = toStringArray(row.defined_symbols);
749
862
  const rowPath = String(row.path || "");
750
863
  const line = Number(row.start_line || 0);
864
+ const role = String(row.role || "");
865
+ const exported = Boolean(row.is_exported);
751
866
  for (const sym of defs) {
752
867
  if (pattern && !sym.toLowerCase().includes(pattern.toLowerCase())) {
753
868
  continue;
@@ -762,6 +877,8 @@ exports.mcp = new commander_1.Command("mcp")
762
877
  count: 1,
763
878
  path: rowPath,
764
879
  line: Math.max(1, line + 1),
880
+ role,
881
+ exported,
765
882
  });
766
883
  }
767
884
  }
@@ -780,7 +897,9 @@ exports.mcp = new commander_1.Command("mcp")
780
897
  const rel = e.path.startsWith(projectRoot)
781
898
  ? e.path.slice(projectRoot.length + 1)
782
899
  : e.path;
783
- return `${e.symbol}\t${rel}:${e.line}`;
900
+ const roleTag = e.role ? ` [${e.role.slice(0, 4)}]` : "";
901
+ const expTag = e.exported ? " exported" : "";
902
+ return `${e.symbol}${roleTag}${expTag}\t${rel}:${e.line}`;
784
903
  });
785
904
  return ok(lines.join("\n"));
786
905
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.7.8",
3
+ "version": "0.7.10",
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.8",
3
+ "version": "0.7.10",
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,12 +40,14 @@ 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.
46
47
  - `exclude` (optional): Exclude files under this path prefix (e.g. "tests/" or "dist/")
47
48
  - `language` (optional): Filter by file extension (e.g. "ts", "py", "go"). Omit the dot.
48
49
  - `role` (optional): Filter by chunk role: "ORCHESTRATION" (logic/flow), "DEFINITION" (types), or "IMPLEMENTATION"
50
+ - `mode` (optional): `"default"` (semantic only) or `"symbol"` (semantic + call graph appended). Use "symbol" when query is a function or class name — gets search results + callers/callees in one call.
49
51
 
50
52
  **When to use which mode:**
51
53
  - `pointer` — navigation, finding locations, understanding architecture
@@ -70,11 +72,13 @@ Call graph — who calls a symbol and what it calls. Callers and callees include
70
72
  - `symbol` (required): Function/method/class name
71
73
 
72
74
  ### list_symbols
73
- List indexed symbols with definition locations.
75
+ List indexed symbols with definition locations, role, and export status.
74
76
  - `pattern` (optional): Filter by name (case-insensitive substring match)
75
77
  - `limit` (optional): Max results (default 20, max 100)
76
78
  - `path` (optional): Only symbols under this path prefix
77
79
 
80
+ Output: `symbolName [ORCH] exported src/path/file.ts:42`
81
+
78
82
  ### index_status
79
83
  Check centralized index health — chunks, files, indexed directories, model info, watcher status.
80
84