grepmax 0.17.3 → 0.17.5

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/README.md CHANGED
@@ -66,6 +66,7 @@ gmax impact handleAuth # Dependents + affected tests
66
66
  gmax similar handleAuth # Find similar code patterns
67
67
  gmax dead handleAuth # Unused-symbol check via call graph (DEAD / PUBLIC EXPORT / LIVE)
68
68
  gmax context "auth system" --budget 4000 # Token-budgeted topic summary
69
+ gmax context src/lib/auth.ts --budget 4000 # Deterministic file/path context
69
70
  ```
70
71
 
71
72
  ### Project Commands
@@ -117,7 +118,7 @@ Plugins auto-update when you run `npm install -g grepmax@latest` — no need to
117
118
 
118
119
  | Tool | Description |
119
120
  | --- | --- |
120
- | `semantic_search` | Search by meaning. 16+ params: query, limit, role, language, scope (project/all), project filtering, etc. |
121
+ | `semantic_search` | Search by meaning. Pointer mode matches CLI `--agent` output; `detail=code/full` returns snippets. |
121
122
  | `code_skeleton` | File structure with bodies collapsed (~4x fewer tokens). |
122
123
  | `trace_calls` | Call graph: importers, callers (multi-hop), callees with file:line. |
123
124
  | `extract_symbol` | Complete function/class body by symbol name. |
@@ -44,18 +44,111 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.context = void 0;
46
46
  const fs = __importStar(require("node:fs"));
47
+ const path = __importStar(require("node:path"));
47
48
  const commander_1 = require("commander");
48
49
  const searcher_1 = require("../lib/search/searcher");
49
50
  const skeleton_1 = require("../lib/skeleton");
50
51
  const vector_db_1 = require("../lib/store/vector-db");
51
- const filter_builder_1 = require("../lib/utils/filter-builder");
52
+ const arrow_1 = require("../lib/utils/arrow");
52
53
  const exit_1 = require("../lib/utils/exit");
54
+ const filter_builder_1 = require("../lib/utils/filter-builder");
53
55
  const project_registry_1 = require("../lib/utils/project-registry");
54
56
  const project_root_1 = require("../lib/utils/project-root");
55
- const arrow_1 = require("../lib/utils/arrow");
56
57
  function estimateTokens(text) {
57
58
  return Math.ceil(text.length / 4);
58
59
  }
60
+ function addSection(state, text, budget) {
61
+ const tokens = estimateTokens(text);
62
+ if (state.tokensUsed + tokens > budget)
63
+ return false;
64
+ state.sections.push(text);
65
+ state.tokensUsed += tokens;
66
+ return true;
67
+ }
68
+ function relPath(projectRoot, p) {
69
+ return p.startsWith(`${projectRoot}/`) ? p.slice(projectRoot.length + 1) : p;
70
+ }
71
+ function chunkPath(chunk) {
72
+ const metadata = chunk.metadata;
73
+ return String(chunk.path || (metadata === null || metadata === void 0 ? void 0 : metadata.path) || "");
74
+ }
75
+ function chunkStartLine(chunk) {
76
+ var _a, _b, _c, _d;
77
+ return Number((_d = (_b = (_a = chunk.start_line) !== null && _a !== void 0 ? _a : chunk.startLine) !== null && _b !== void 0 ? _b : (_c = chunk.generated_metadata) === null || _c === void 0 ? void 0 : _c.start_line) !== null && _d !== void 0 ? _d : 0);
78
+ }
79
+ function chunkEndLine(chunk) {
80
+ var _a, _b, _c, _d;
81
+ const start = chunkStartLine(chunk);
82
+ return Number((_d = (_b = (_a = chunk.end_line) !== null && _a !== void 0 ? _a : chunk.endLine) !== null && _b !== void 0 ? _b : (_c = chunk.generated_metadata) === null || _c === void 0 ? void 0 : _c.end_line) !== null && _d !== void 0 ? _d : start);
83
+ }
84
+ function resolveExistingPath(target, root, projectRoot) {
85
+ const candidates = [
86
+ path.isAbsolute(target) ? target : path.resolve(root, target),
87
+ path.resolve(projectRoot, target),
88
+ ];
89
+ for (const candidate of candidates) {
90
+ if (fs.existsSync(candidate))
91
+ return candidate;
92
+ }
93
+ return null;
94
+ }
95
+ function renderPathContext(target, absPath, projectRoot, budget) {
96
+ return __awaiter(this, void 0, void 0, function* () {
97
+ const state = {
98
+ sections: [],
99
+ tokensUsed: 0,
100
+ };
101
+ const header = `=== Context: "${target}" ===`;
102
+ addSection(state, header, budget);
103
+ const stat = fs.statSync(absPath);
104
+ const targetSection = [
105
+ "\n## Target",
106
+ `${relPath(projectRoot, absPath)} [${stat.isDirectory() ? "directory" : "file"}]`,
107
+ ].join("\n");
108
+ addSection(state, targetSection, budget);
109
+ if (stat.isDirectory()) {
110
+ const entries = fs
111
+ .readdirSync(absPath, { withFileTypes: true })
112
+ .filter((entry) => !entry.name.startsWith("."))
113
+ .sort((a, b) => a.name.localeCompare(b.name))
114
+ .slice(0, 40)
115
+ .map((entry) => `${entry.isDirectory() ? "dir " : "file"} ${entry.name}`);
116
+ if (entries.length > 0) {
117
+ addSection(state, ["\n## Directory Entries", ...entries].join("\n"), budget);
118
+ }
119
+ return state;
120
+ }
121
+ const content = fs.readFileSync(absPath, "utf-8");
122
+ const skeletonizer = new skeleton_1.Skeletonizer();
123
+ yield skeletonizer.init();
124
+ if (skeletonizer.isSupported(absPath).supported) {
125
+ try {
126
+ const result = yield skeletonizer.skeletonizeFile(absPath, content);
127
+ if (result.success) {
128
+ addSection(state, [
129
+ "\n## File Structure",
130
+ `--- ${relPath(projectRoot, absPath)} (skeleton, ~${result.tokenEstimate} tokens) ---`,
131
+ result.skeleton,
132
+ ].join("\n"), budget);
133
+ }
134
+ }
135
+ catch (_a) {
136
+ // Skeleton is a convenience in path mode; fall through to excerpt.
137
+ }
138
+ }
139
+ const lines = content.split("\n");
140
+ const excerptLines = lines.slice(0, Math.min(lines.length, 120));
141
+ const omitted = lines.length > excerptLines.length
142
+ ? `\n... (+${lines.length - excerptLines.length} more lines)`
143
+ : "";
144
+ addSection(state, [
145
+ "\n## File Excerpt",
146
+ `--- ${relPath(projectRoot, absPath)}:1 ---`,
147
+ `${excerptLines.join("\n")}${omitted}`,
148
+ ].join("\n"), budget);
149
+ return state;
150
+ });
151
+ }
59
152
  exports.context = new commander_1.Command("context")
60
153
  .description("Generate a token-budgeted topic summary (search + skeleton + extract)")
61
154
  .argument("<topic>", "Natural language topic or directory path")
@@ -64,7 +157,7 @@ exports.context = new commander_1.Command("context")
64
157
  .option("--root <dir>", "Project root directory")
65
158
  .option("--agent", "Compact output for AI agents", false)
66
159
  .action((topic, opts) => __awaiter(void 0, void 0, void 0, function* () {
67
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
160
+ var _a, _b, _c, _d, _e;
68
161
  const budget = Number.parseInt(opts.budget || "4000", 10) || 4000;
69
162
  const maxResults = Number.parseInt(opts.maxResults || "10", 10) || 10;
70
163
  let vectorDb = null;
@@ -75,8 +168,14 @@ exports.context = new commander_1.Command("context")
75
168
  const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
76
169
  const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
77
170
  vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
171
+ const pathTarget = resolveExistingPath(topic, root, projectRoot);
172
+ if (pathTarget) {
173
+ const rendered = yield renderPathContext(topic, pathTarget, projectRoot, budget);
174
+ rendered.sections.push(`\n(~${rendered.tokensUsed}/${budget} tokens used)`);
175
+ console.log(rendered.sections.join("\n"));
176
+ return;
177
+ }
78
178
  const searcher = new searcher_1.Searcher(vectorDb);
79
- const rel = (p) => p.startsWith(`${projectRoot}/`) ? p.slice(projectRoot.length + 1) : p;
80
179
  // Phase 1: Semantic search
81
180
  const response = yield searcher.search(topic, maxResults, { rerank: true }, {}, projectRoot);
82
181
  if (response.data.length === 0) {
@@ -94,11 +193,11 @@ exports.context = new commander_1.Command("context")
94
193
  const entryPoints = orchestrators.length > 0 ? orchestrators : response.data.slice(0, 3);
95
194
  const epSection = ["\n## Entry Points"];
96
195
  for (const r of entryPoints.slice(0, 5)) {
97
- const p = String(r.path || ((_b = r.metadata) === null || _b === void 0 ? void 0 : _b.path) || "");
98
- const line = Number((_c = r.start_line) !== null && _c !== void 0 ? _c : 0);
99
- const sym = (_e = (_d = (0, arrow_1.toArr)(r.defined_symbols)) === null || _d === void 0 ? void 0 : _d[0]) !== null && _e !== void 0 ? _e : "";
196
+ const p = chunkPath(r);
197
+ const line = chunkStartLine(r);
198
+ const sym = (_c = (_b = (0, arrow_1.toArr)(r.defined_symbols)) === null || _b === void 0 ? void 0 : _b[0]) !== null && _c !== void 0 ? _c : "";
100
199
  const role = String(r.role || "IMPLEMENTATION");
101
- epSection.push(`${rel(p)}:${line + 1} ${sym} [${role}]`);
200
+ epSection.push(`${relPath(projectRoot, p)}:${line + 1} ${sym} [${role}]`);
102
201
  }
103
202
  const epText = epSection.join("\n");
104
203
  if (tokensUsed + estimateTokens(epText) <= budget) {
@@ -109,24 +208,24 @@ exports.context = new commander_1.Command("context")
109
208
  const topChunks = entryPoints.slice(0, 3);
110
209
  const bodySection = ["\n## Key Functions"];
111
210
  for (const r of topChunks) {
112
- const absP = String(r.path || "");
113
- const startLine = Number((_f = r.start_line) !== null && _f !== void 0 ? _f : 0);
114
- const endLine = Number((_g = r.end_line) !== null && _g !== void 0 ? _g : startLine);
115
- const sym = (_j = (_h = (0, arrow_1.toArr)(r.defined_symbols)) === null || _h === void 0 ? void 0 : _h[0]) !== null && _j !== void 0 ? _j : "";
211
+ const absP = chunkPath(r);
212
+ const startLine = chunkStartLine(r);
213
+ const endLine = chunkEndLine(r);
214
+ const sym = (_e = (_d = (0, arrow_1.toArr)(r.defined_symbols)) === null || _d === void 0 ? void 0 : _d[0]) !== null && _e !== void 0 ? _e : "";
116
215
  try {
117
216
  const content = fs.readFileSync(absP, "utf-8");
118
217
  const allLines = content.split("\n");
119
218
  const body = allLines
120
219
  .slice(startLine, Math.min(endLine + 1, allLines.length))
121
220
  .join("\n");
122
- const blob = `\n--- ${rel(absP)}:${startLine + 1} ${sym} ---\n${body}`;
221
+ const blob = `\n--- ${relPath(projectRoot, absP)}:${startLine + 1} ${sym} ---\n${body}`;
123
222
  const blobTokens = estimateTokens(blob);
124
223
  if (tokensUsed + blobTokens > budget)
125
224
  break;
126
225
  bodySection.push(blob);
127
226
  tokensUsed += blobTokens;
128
227
  }
129
- catch (_k) {
228
+ catch (_f) {
130
229
  // File not readable — skip
131
230
  }
132
231
  }
@@ -135,9 +234,7 @@ exports.context = new commander_1.Command("context")
135
234
  }
136
235
  // Phase 4: File skeletons for unique files
137
236
  const uniqueFiles = [
138
- ...new Set(response.data
139
- .map((r) => String(r.path || ""))
140
- .filter(Boolean)),
237
+ ...new Set(response.data.map((r) => chunkPath(r)).filter(Boolean)),
141
238
  ].slice(0, 5);
142
239
  const skelSection = ["\n## File Structure"];
143
240
  const skeletonizer = new skeleton_1.Skeletonizer();
@@ -150,14 +247,14 @@ exports.context = new commander_1.Command("context")
150
247
  const result = yield skeletonizer.skeletonizeFile(absP, content);
151
248
  if (!result.success)
152
249
  continue;
153
- const blob = `\n--- ${rel(absP)} (skeleton, ~${result.tokenEstimate} tokens) ---\n${result.skeleton}`;
250
+ const blob = `\n--- ${relPath(projectRoot, absP)} (skeleton, ~${result.tokenEstimate} tokens) ---\n${result.skeleton}`;
154
251
  const blobTokens = estimateTokens(blob);
155
252
  if (tokensUsed + blobTokens > budget)
156
253
  break;
157
254
  skelSection.push(blob);
158
255
  tokensUsed += blobTokens;
159
256
  }
160
- catch (_l) {
257
+ catch (_g) {
161
258
  // Skip unreadable files
162
259
  }
163
260
  }
@@ -195,7 +292,7 @@ exports.context = new commander_1.Command("context")
195
292
  if (topRelated.length > 0) {
196
293
  const relSection = ["\n## Related Files"];
197
294
  for (const [p, count] of topRelated) {
198
- relSection.push(`${rel(p)} — ${count} shared symbol${count > 1 ? "s" : ""}`);
295
+ relSection.push(`${relPath(projectRoot, p)} — ${count} shared symbol${count > 1 ? "s" : ""}`);
199
296
  }
200
297
  const relText = relSection.join("\n");
201
298
  if (tokensUsed + estimateTokens(relText) <= budget) {
@@ -218,7 +315,7 @@ exports.context = new commander_1.Command("context")
218
315
  try {
219
316
  yield vectorDb.close();
220
317
  }
221
- catch (_m) { }
318
+ catch (_h) { }
222
319
  }
223
320
  yield (0, exit_1.gracefulExit)();
224
321
  }