grepmax 0.7.8 → 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
  },
@@ -174,6 +178,10 @@ const TOOLS = [
174
178
  type: "string",
175
179
  description: "Comma-separated project names to exclude (e.g. 'capstone,power').",
176
180
  },
181
+ context_lines: {
182
+ type: "number",
183
+ description: "Include N lines before/after chunk. Only with detail 'code' or 'full'. Max 20.",
184
+ },
177
185
  },
178
186
  required: ["query"],
179
187
  },
@@ -541,19 +549,42 @@ exports.mcp = new commander_1.Command("mcp")
541
549
  ? ` ${parentStr}${callsStr}`
542
550
  : "";
543
551
  let snippet = "";
552
+ const contextN = typeof args.context_lines === "number"
553
+ ? Math.min(Math.max(args.context_lines, 0), 20)
554
+ : 0;
544
555
  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");
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
+ }
557
588
  }
558
589
  const text = line1 +
559
590
  (summaryStr ? `\n${summaryStr}` : "") +
@@ -590,6 +621,31 @@ exports.mcp = new commander_1.Command("mcp")
590
621
  }
591
622
  });
592
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
+ }
593
649
  function handleCodeSkeleton(args) {
594
650
  return __awaiter(this, void 0, void 0, function* () {
595
651
  const target = String(args.target || "");
@@ -630,25 +686,35 @@ exports.mcp = new commander_1.Command("mcp")
630
686
  parts.push(`// ${t} — file not found`);
631
687
  continue;
632
688
  }
689
+ // Read source for line annotations
690
+ let sourceContent = "";
691
+ try {
692
+ sourceContent = fs.readFileSync(absPath, "utf-8");
693
+ }
694
+ catch (_a) { }
633
695
  // Try cached skeleton first
634
696
  try {
635
697
  const db = getVectorDb();
636
698
  const cached = yield (0, retriever_1.getStoredSkeleton)(db, absPath);
637
699
  if (cached) {
638
700
  const tokens = Math.ceil(cached.length / 4);
639
- 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}`);
640
705
  continue;
641
706
  }
642
707
  }
643
- catch (_a) {
708
+ catch (_b) {
644
709
  // Index may not exist yet — fall through to live generation
645
710
  }
646
711
  // Generate live
647
712
  try {
648
- const content = fs.readFileSync(absPath, "utf-8");
713
+ const content = sourceContent || fs.readFileSync(absPath, "utf-8");
649
714
  const result = yield skel.skeletonizeFile(absPath, content);
650
715
  if (result.success) {
651
- 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}`);
652
718
  }
653
719
  else {
654
720
  parts.push(`// ${t} — skeleton failed: ${result.error}`);
@@ -732,7 +798,7 @@ exports.mcp = new commander_1.Command("mcp")
732
798
  const table = yield db.ensureTable();
733
799
  let query = table
734
800
  .query()
735
- .select(["defined_symbols", "path", "start_line"])
801
+ .select(["defined_symbols", "path", "start_line", "role", "is_exported"])
736
802
  .where("array_length(defined_symbols) > 0")
737
803
  .limit(pattern ? 10000 : Math.max(limit * 50, 2000));
738
804
  if (pathPrefix) {
@@ -748,6 +814,8 @@ exports.mcp = new commander_1.Command("mcp")
748
814
  const defs = toStringArray(row.defined_symbols);
749
815
  const rowPath = String(row.path || "");
750
816
  const line = Number(row.start_line || 0);
817
+ const role = String(row.role || "");
818
+ const exported = Boolean(row.is_exported);
751
819
  for (const sym of defs) {
752
820
  if (pattern && !sym.toLowerCase().includes(pattern.toLowerCase())) {
753
821
  continue;
@@ -762,6 +830,8 @@ exports.mcp = new commander_1.Command("mcp")
762
830
  count: 1,
763
831
  path: rowPath,
764
832
  line: Math.max(1, line + 1),
833
+ role,
834
+ exported,
765
835
  });
766
836
  }
767
837
  }
@@ -780,7 +850,9 @@ exports.mcp = new commander_1.Command("mcp")
780
850
  const rel = e.path.startsWith(projectRoot)
781
851
  ? e.path.slice(projectRoot.length + 1)
782
852
  : e.path;
783
- 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}`;
784
856
  });
785
857
  return ok(lines.join("\n"));
786
858
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.7.8",
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.8",
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.
@@ -70,11 +71,13 @@ Call graph — who calls a symbol and what it calls. Callers and callees include
70
71
  - `symbol` (required): Function/method/class name
71
72
 
72
73
  ### list_symbols
73
- List indexed symbols with definition locations.
74
+ List indexed symbols with definition locations, role, and export status.
74
75
  - `pattern` (optional): Filter by name (case-insensitive substring match)
75
76
  - `limit` (optional): Max results (default 20, max 100)
76
77
  - `path` (optional): Only symbols under this path prefix
77
78
 
79
+ Output: `symbolName [ORCH] exported src/path/file.ts:42`
80
+
78
81
  ### index_status
79
82
  Check centralized index health — chunks, files, indexed directories, model info, watcher status.
80
83