grepmax 0.16.0 → 0.16.1

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.
@@ -50,6 +50,7 @@ const vector_db_1 = require("../lib/store/vector-db");
50
50
  const exit_1 = require("../lib/utils/exit");
51
51
  const filter_builder_1 = require("../lib/utils/filter-builder");
52
52
  const import_extractor_1 = require("../lib/utils/import-extractor");
53
+ const language_1 = require("../lib/utils/language");
53
54
  const project_root_1 = require("../lib/utils/project-root");
54
55
  const useColors = process.stdout.isTTY && !process.env.NO_COLOR;
55
56
  const style = {
@@ -128,6 +129,25 @@ exports.extract = new commander_1.Command("extract")
128
129
  process.exitCode = 1;
129
130
  return;
130
131
  }
132
+ // Cross-language disambiguation: when the symbol is defined in 2+
133
+ // languages, refuse to silently pick one. Listing all matches with a
134
+ // recovery hint avoids the dogfooded failure mode where peek picked
135
+ // Swift but listed TS callers.
136
+ const byLang = (0, language_1.groupByLanguage)(chunks);
137
+ if (byLang.size >= 2) {
138
+ const rel = (p) => p.startsWith(projectRoot) ? p.slice(projectRoot.length + 1) : p;
139
+ const lines = [
140
+ `Symbol '${symbol}' is defined in multiple languages:`,
141
+ ];
142
+ for (const [lang, group] of byLang) {
143
+ const c = group[0];
144
+ lines.push(` ${lang.padEnd(6)} ${rel(c.path)}:${c.startLine + 1}`);
145
+ }
146
+ lines.push(`Disambiguate with --root or pin to a path: gmax extract ${symbol} --root <project-root>`);
147
+ console.log(lines.join("\n"));
148
+ process.exitCode = 1;
149
+ return;
150
+ }
131
151
  const best = pickBestMatch(chunks, symbol);
132
152
  const content = fs.readFileSync(best.path, "utf-8");
133
153
  const allLines = content.split("\n");
@@ -50,6 +50,7 @@ const graph_builder_1 = require("../lib/graph/graph-builder");
50
50
  const vector_db_1 = require("../lib/store/vector-db");
51
51
  const exit_1 = require("../lib/utils/exit");
52
52
  const filter_builder_1 = require("../lib/utils/filter-builder");
53
+ const language_1 = require("../lib/utils/language");
53
54
  const project_root_1 = require("../lib/utils/project-root");
54
55
  const useColors = process.stdout.isTTY && !process.env.NO_COLOR;
55
56
  const style = {
@@ -104,6 +105,41 @@ exports.peek = new commander_1.Command("peek")
104
105
  const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
105
106
  const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
106
107
  vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
108
+ // Cross-language disambiguation: when the symbol is defined in 2+
109
+ // languages, refuse to silently pick one. The graph builder otherwise
110
+ // picks one chunk arbitrarily and lists callers from a different
111
+ // language — verified failure mode.
112
+ {
113
+ const tableForCheck = yield vectorDb.ensureTable();
114
+ const prefixForCheck = projectRoot.endsWith("/")
115
+ ? projectRoot
116
+ : `${projectRoot}/`;
117
+ const allDefs = yield tableForCheck
118
+ .query()
119
+ .select(["path", "start_line"])
120
+ .where(`array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(symbol)}') AND path LIKE '${(0, filter_builder_1.escapeSqlString)(prefixForCheck)}%'`)
121
+ .limit(20)
122
+ .toArray();
123
+ const chunks = allDefs.map((row) => ({
124
+ path: String(row.path || ""),
125
+ startLine: Number(row.start_line || 0),
126
+ }));
127
+ const byLang = (0, language_1.groupByLanguage)(chunks);
128
+ if (byLang.size >= 2) {
129
+ const rel = (p) => p.startsWith(projectRoot) ? p.slice(projectRoot.length + 1) : p;
130
+ const lines = [
131
+ `Symbol '${symbol}' is defined in multiple languages:`,
132
+ ];
133
+ for (const [lang, group] of byLang) {
134
+ const c = group[0];
135
+ lines.push(` ${lang.padEnd(6)} ${rel(c.path)}:${c.startLine + 1}`);
136
+ }
137
+ lines.push(`Disambiguate with --root or pin to a path: gmax peek ${symbol} --root <project-root>`);
138
+ console.log(lines.join("\n"));
139
+ process.exitCode = 1;
140
+ return;
141
+ }
142
+ }
107
143
  const graphBuilder = new graph_builder_1.GraphBuilder(vectorDb, projectRoot);
108
144
  const graph = yield graphBuilder.buildGraph(symbol);
109
145
  if (!graph.center) {
@@ -43,6 +43,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
43
43
  };
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.remove = void 0;
46
+ const fs = __importStar(require("node:fs"));
46
47
  const path = __importStar(require("node:path"));
47
48
  const readline = __importStar(require("node:readline"));
48
49
  const commander_1 = require("commander");
@@ -68,12 +69,13 @@ function confirm(message) {
68
69
  }
69
70
  exports.remove = new commander_1.Command("remove")
70
71
  .description("Remove a project from the gmax index")
71
- .argument("[dir]", "Directory to remove (defaults to current directory)")
72
+ .argument("[dir-or-name]", "Directory or registered project name (defaults to current directory)")
72
73
  .option("-f, --force", "Skip confirmation prompt", false)
73
74
  .addHelpText("after", `
74
75
  Examples:
75
76
  gmax remove Remove the current project
76
- gmax remove ~/projects/app Remove a specific project
77
+ gmax remove ~/projects/app Remove a specific project by path
78
+ gmax remove my-app Remove a registered project by name
77
79
  gmax remove --force Skip confirmation
78
80
  `)
79
81
  .action((dir, opts) => __awaiter(void 0, void 0, void 0, function* () {
@@ -81,7 +83,41 @@ Examples:
81
83
  let vectorDb = null;
82
84
  let metaCache = null;
83
85
  try {
84
- const targetDir = dir ? path.resolve(dir) : process.cwd();
86
+ // Resolve name registered root when arg has no path separator and isn't a dir.
87
+ // Avoids the footgun where a typo'd name silently removed the cwd project.
88
+ let resolvedDir = dir;
89
+ if (dir &&
90
+ !dir.includes("/") &&
91
+ !dir.includes("\\") &&
92
+ !fs.existsSync(path.resolve(dir))) {
93
+ const matches = (0, project_registry_1.listProjects)().filter((p) => p.name === dir);
94
+ if (matches.length === 0) {
95
+ console.error(`No registered project named "${dir}".`);
96
+ const all = (0, project_registry_1.listProjects)();
97
+ if (all.length > 0) {
98
+ console.error("Available projects:");
99
+ for (const p of all) {
100
+ console.error(` ${p.name.padEnd(24)} ${p.root}`);
101
+ }
102
+ }
103
+ else {
104
+ console.error("No projects are currently registered.");
105
+ }
106
+ process.exitCode = 1;
107
+ return;
108
+ }
109
+ if (matches.length > 1) {
110
+ console.error(`Multiple registered projects named "${dir}":`);
111
+ for (const p of matches) {
112
+ console.error(` ${p.root}`);
113
+ }
114
+ console.error("Pass an absolute path to disambiguate.");
115
+ process.exitCode = 1;
116
+ return;
117
+ }
118
+ resolvedDir = matches[0].root;
119
+ }
120
+ const targetDir = resolvedDir ? path.resolve(resolvedDir) : process.cwd();
85
121
  const projectRoot = (_a = (0, project_root_1.findProjectRoot)(targetDir)) !== null && _a !== void 0 ? _a : targetDir;
86
122
  const projectName = path.basename(projectRoot);
87
123
  const project = (0, project_registry_1.getProject)(projectRoot);
@@ -743,10 +743,12 @@ Examples:
743
743
  }
744
744
  const sym = symbol ? ` ${symbol}` : "";
745
745
  const rl = role ? ` [${role}]` : "";
746
+ const score = r.score;
747
+ const scoreCol = typeof score === "number" ? `\ts=${score.toFixed(3)}` : "";
746
748
  const explainSuffix = options.explain && r.scoreBreakdown
747
749
  ? `\texplain:rerank=${r.scoreBreakdown.rerank.toFixed(3)},fused=${r.scoreBreakdown.fused.toFixed(3)},boost=${r.scoreBreakdown.boost.toFixed(2)}x,score=${r.scoreBreakdown.normalized.toFixed(3)}`
748
750
  : "";
749
- console.log(`${relPath}:${startLine}${sym}${rl}${hint}${explainSuffix}`);
751
+ console.log(`${relPath}:${startLine}${scoreCol}${sym}${rl}${hint}${explainSuffix}`);
750
752
  }
751
753
  }
752
754
  // Agent trace (compact)
@@ -61,7 +61,6 @@ const setup_helpers_1 = require("../lib/setup/setup-helpers");
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");
65
64
  const exit_1 = require("../lib/utils/exit");
66
65
  const project_registry_1 = require("../lib/utils/project-registry");
67
66
  const project_root_1 = require("../lib/utils/project-root");
@@ -156,27 +155,18 @@ Examples:
156
155
  };
157
156
  // Determine mode based on target
158
157
  const resolvedTarget = path.resolve(target);
159
- // Directory mode
158
+ // Directory mode is unsupported. Auto-picking files from a directory
159
+ // was confusingly magical (and on '.' it fell through to the resolver
160
+ // path and skeletonized .gitignore). Refuse and point at the file form.
160
161
  if (fs.existsSync(resolvedTarget) &&
161
162
  fs.statSync(resolvedTarget).isDirectory()) {
162
- const entries = fs.readdirSync(resolvedTarget, {
163
- withFileTypes: true,
164
- });
165
- const files = entries
166
- .filter((e) => e.isFile() &&
167
- (0, file_utils_1.isIndexableFile)(path.join(resolvedTarget, e.name)))
168
- .map((e) => path.join(resolvedTarget, e.name))
169
- .slice(0, Number.parseInt(options.limit, 10));
170
- if (files.length === 0) {
171
- console.error(`No indexable files in ${target}`);
172
- process.exitCode = 1;
173
- return;
174
- }
175
- for (const filePath of files) {
176
- const content = fs.readFileSync(filePath, "utf-8");
177
- const result = yield skeletonizer.skeletonizeFile(filePath, content, skeletonOpts);
178
- outputResult(result, options);
179
- }
163
+ console.error([
164
+ "skeleton expects a file or symbol, not a directory.",
165
+ "Try:",
166
+ " gmax skeleton src/foo.ts # one file's structure",
167
+ ' gmax search "<topic>" --agent # find relevant files first',
168
+ ].join("\n"));
169
+ process.exitCode = 1;
180
170
  return;
181
171
  }
182
172
  // Batch mode (comma-separated)
@@ -397,8 +397,22 @@ class Daemon {
397
397
  },
398
398
  });
399
399
  this.processors.set(root, processor);
400
- // Subscribe with @parcel/watcher — native backend, no polling
401
- yield this.subscribeWatcher(root, processor);
400
+ // Subscribe with @parcel/watcher — native backend, no polling.
401
+ // If the kernel refuses (e.g. FSEvents slots stuck after a prior kill -9
402
+ // — see docs/known-limitations.md), fall straight through to poll mode.
403
+ // The retry/backoff path inside recoverWatcher is for transient overflows,
404
+ // not hard kernel-level subscribe failures, so we skip it on startup by
405
+ // priming failCount past MAX before invoking it.
406
+ try {
407
+ yield this.subscribeWatcher(root, processor);
408
+ }
409
+ catch (err) {
410
+ const name = path.basename(root);
411
+ console.error(`[daemon:${name}] Subscribe failed at startup (${err instanceof Error ? err.message : err}) — switching to poll mode`);
412
+ this.watcherFailCount.set(root, 1000); // > MAX_WATCHER_RETRIES
413
+ this.lastOverflowMs.set(root, Date.now());
414
+ this.recoverWatcher(root, processor);
415
+ }
402
416
  (0, watcher_store_1.registerWatcher)({
403
417
  pid: process.pid,
404
418
  projectRoot: root,
@@ -362,6 +362,14 @@ class TreeSitterChunker {
362
362
  const allowedParents = ["program", "module", "source_file", "class_body", "export_statement"];
363
363
  if (parentType && !allowedParents.includes(parentType))
364
364
  return false;
365
+ // Exported declarations are part of the API surface, so always index
366
+ // them regardless of RHS shape. Without this, files whose only export
367
+ // is `export const typeDefs = gql\`...\`` (template literals, object
368
+ // literals, or non-PascalCase consts) had no defined_symbols entry
369
+ // and peek/extract returned "Symbol not found".
370
+ if (parentType === "export_statement" || parentType === "export_declaration") {
371
+ return true;
372
+ }
365
373
  const text = node.text || "";
366
374
  if (text.includes("=>"))
367
375
  return true;
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.languageOf = languageOf;
37
+ exports.groupByLanguage = groupByLanguage;
38
+ const path = __importStar(require("node:path"));
39
+ const EXTENSION_TO_LANGUAGE = {
40
+ ".ts": "ts",
41
+ ".tsx": "ts",
42
+ ".js": "js",
43
+ ".jsx": "js",
44
+ ".mjs": "js",
45
+ ".cjs": "js",
46
+ ".py": "py",
47
+ ".swift": "swift",
48
+ ".kt": "kotlin",
49
+ ".kts": "kotlin",
50
+ ".go": "go",
51
+ ".rs": "rust",
52
+ ".rb": "ruby",
53
+ ".java": "java",
54
+ ".c": "c",
55
+ ".h": "c",
56
+ ".cpp": "cpp",
57
+ ".cc": "cpp",
58
+ ".hpp": "cpp",
59
+ ".cs": "csharp",
60
+ ".php": "php",
61
+ ".lua": "lua",
62
+ ".sh": "bash",
63
+ ".bash": "bash",
64
+ ".zsh": "bash",
65
+ ".sql": "sql",
66
+ ".md": "md",
67
+ ".mdx": "md",
68
+ ".json": "json",
69
+ ".yaml": "yaml",
70
+ ".yml": "yaml",
71
+ ".toml": "toml",
72
+ };
73
+ function languageOf(filePath) {
74
+ const ext = path.extname(filePath).toLowerCase();
75
+ if (EXTENSION_TO_LANGUAGE[ext])
76
+ return EXTENSION_TO_LANGUAGE[ext];
77
+ const fallback = ext.replace(/^\./, "");
78
+ return fallback || "unknown";
79
+ }
80
+ function groupByLanguage(chunks) {
81
+ const out = new Map();
82
+ for (const c of chunks) {
83
+ const lang = languageOf(c.path);
84
+ const arr = out.get(lang);
85
+ if (arr)
86
+ arr.push(c);
87
+ else
88
+ out.set(lang, [c]);
89
+ }
90
+ return out;
91
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.16.0",
3
+ "version": "0.16.1",
4
4
  "author": "Robert Owens <78518764+reowens@users.noreply.github.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.16.0",
3
+ "version": "0.16.1",
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",
@@ -176,7 +176,7 @@ async function main() {
176
176
  hookSpecificOutput: {
177
177
  hookEventName: "SessionStart",
178
178
  additionalContext:
179
- 'gmax ready. Use Bash(gmax "query" --agent) for search (one line per result, 89% fewer tokens). Bash(gmax extract <symbol>) for full function body. Bash(gmax peek <symbol>) for quick overview (sig+callers+callees). Bash(gmax trace <symbol>) for call graphs. Bash(gmax skeleton <path>) for structure. Bash(gmax diff [ref]) for git changes. Bash(gmax test <symbol>) for test coverage. Bash(gmax impact <symbol>) for blast radius. Bash(gmax similar <symbol>) for similar code. Bash(gmax context "topic" --budget 4000) for topic summary. Bash(gmax status) to check indexed projects. --agent flag works on search, trace, symbols, related, recent, status, project, extract, peek, diff, test, impact, similar. If search says "not added yet", run Bash(gmax add). If results look stale, run Bash(gmax index) to repair.',
179
+ 'gmax ready. Use Bash(gmax "query" --agent) for search (one line per result, 89% fewer tokens). Bash(gmax extract <symbol>) for full function body. Bash(gmax peek <symbol>) for quick overview (sig+callers+callees). Bash(gmax trace <symbol>) for call graphs. Bash(gmax skeleton <path>) for structure. Bash(gmax diff [ref]) for git changes. Bash(gmax test <symbol>) for test coverage. Bash(gmax impact <symbol>) for blast radius. Bash(gmax similar <symbol>) for similar code. Bash(gmax context "topic" --budget 4000) for topic summary. Bash(gmax status) to check indexed projects. Role tags in results: [DEFI]=definition, [ORCH]=orchestration, [IMPL]=implementation, [DOCS]=docs. --agent flag works on search, trace, symbols, related, recent, status, project, extract, peek, diff, test, impact, similar. If search says "not added yet", run Bash(gmax add). If results look stale, run Bash(gmax index) to repair.',
180
180
  },
181
181
  };
182
182
  process.stdout.write(JSON.stringify(response));