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.
- package/dist/commands/extract.js +20 -0
- package/dist/commands/peek.js +36 -0
- package/dist/commands/remove.js +39 -3
- package/dist/commands/search.js +3 -1
- package/dist/commands/skeleton.js +10 -20
- package/dist/lib/daemon/daemon.js +16 -2
- package/dist/lib/index/chunker.js +8 -0
- package/dist/lib/utils/language.js +91 -0
- package/package.json +1 -1
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
- package/plugins/grepmax/hooks/start.js +1 -1
package/dist/commands/extract.js
CHANGED
|
@@ -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");
|
package/dist/commands/peek.js
CHANGED
|
@@ -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) {
|
package/dist/commands/remove.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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);
|
package/dist/commands/search.js
CHANGED
|
@@ -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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
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
|
@@ -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));
|