grepmax 0.16.2 → 0.16.3

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.
@@ -1,145 +1,13 @@
1
1
  "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
2
  Object.defineProperty(exports, "__esModule", { value: true });
12
3
  exports.diff = void 0;
13
4
  const commander_1 = require("commander");
14
- const searcher_1 = require("../lib/search/searcher");
15
- const vector_db_1 = require("../lib/store/vector-db");
16
- const filter_builder_1 = require("../lib/utils/filter-builder");
17
- const exit_1 = require("../lib/utils/exit");
18
- const git_1 = require("../lib/utils/git");
19
- const project_registry_1 = require("../lib/utils/project-registry");
20
- const project_root_1 = require("../lib/utils/project-root");
21
- const arrow_1 = require("../lib/utils/arrow");
22
5
  exports.diff = new commander_1.Command("diff")
23
- .description("Search code scoped to git changes")
24
- .argument("[ref]", "Git ref to diff against (e.g. main, HEAD~5)")
25
- .option("-q, --query <query>", "Semantic search within changed files")
26
- .option("-m, --max-count <n>", "Max results (default 10)", "10")
27
- .option("--role <role>", "Filter by role: ORCHESTRATION, DEFINITION, IMPLEMENTATION")
28
- .option("--root <dir>", "Project root directory")
29
- .option("--agent", "Compact output for AI agents", false)
30
- .action((ref, opts) => __awaiter(void 0, void 0, void 0, function* () {
31
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
32
- const limit = Math.min(Math.max(Number.parseInt(opts.maxCount || "10", 10), 1), 50);
33
- let vectorDb = null;
34
- try {
35
- const root = (0, project_registry_1.resolveRootOrExit)(opts.root);
36
- if (root === null)
37
- return;
38
- const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
39
- const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
40
- vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
41
- const changedFiles = (0, git_1.getChangedFiles)(ref, projectRoot);
42
- if (changedFiles.length === 0) {
43
- console.log(ref ? `No changes found relative to ${ref}.` : "No uncommitted changes found.");
44
- return;
45
- }
46
- const rel = (p) => p.startsWith(`${projectRoot}/`) ? p.slice(projectRoot.length + 1) : p;
47
- if (opts.query) {
48
- // Semantic search scoped to changed files
49
- const searcher = new searcher_1.Searcher(vectorDb);
50
- const response = yield searcher.search(opts.query, limit, { rerank: true }, Object.assign({}, (opts.role ? { role: opts.role } : {})), projectRoot);
51
- // Filter results to only changed files
52
- const changedSet = new Set(changedFiles);
53
- const filtered = response.data.filter((r) => {
54
- var _a;
55
- const p = ((_a = r.metadata) === null || _a === void 0 ? void 0 : _a.path) || r.path || "";
56
- return changedSet.has(p);
57
- });
58
- if (filtered.length === 0) {
59
- console.log("No indexed results found in changed files for that query.");
60
- return;
61
- }
62
- if (opts.agent) {
63
- for (const r of filtered.slice(0, limit)) {
64
- const p = String(r.path || ((_b = r.metadata) === null || _b === void 0 ? void 0 : _b.path) || "");
65
- const line = Number((_c = r.start_line) !== null && _c !== void 0 ? _c : 0);
66
- const sym = (_e = (_d = (0, arrow_1.toArr)(r.defined_symbols)) === null || _d === void 0 ? void 0 : _d[0]) !== null && _e !== void 0 ? _e : "";
67
- const role = String(r.role || "IMPL");
68
- console.log(`${rel(p)}:${line + 1} ${sym} [${role}]`);
69
- }
70
- }
71
- else {
72
- console.log(`Changed files matching "${opts.query}":\n`);
73
- for (const r of filtered.slice(0, limit)) {
74
- const p = String(r.path || ((_f = r.metadata) === null || _f === void 0 ? void 0 : _f.path) || "");
75
- const line = Number((_g = r.start_line) !== null && _g !== void 0 ? _g : 0);
76
- const sym = (_j = (_h = (0, arrow_1.toArr)(r.defined_symbols)) === null || _h === void 0 ? void 0 : _h[0]) !== null && _j !== void 0 ? _j : "";
77
- const role = String(r.role || "IMPLEMENTATION");
78
- const score = (_l = (_k = r.score) === null || _k === void 0 ? void 0 : _k.toFixed(3)) !== null && _l !== void 0 ? _l : "?";
79
- console.log(` ${rel(p)}:${line + 1} ${sym} [${role}] (${score})`);
80
- }
81
- }
82
- }
83
- else {
84
- // No query — list changed files with their indexed symbols
85
- const table = yield vectorDb.ensureTable();
86
- if (opts.agent) {
87
- for (const file of changedFiles) {
88
- const chunks = yield table
89
- .query()
90
- .select(["path", "start_line", "defined_symbols", "role"])
91
- .where(`path = '${(0, filter_builder_1.escapeSqlString)(file)}'`)
92
- .limit(50)
93
- .toArray();
94
- if (chunks.length === 0) {
95
- console.log(`${rel(file)}\t(not indexed)`);
96
- }
97
- else {
98
- for (const chunk of chunks) {
99
- const sym = (_o = (_m = (0, arrow_1.toArr)(chunk.defined_symbols)) === null || _m === void 0 ? void 0 : _m[0]) !== null && _o !== void 0 ? _o : "";
100
- const line = (_p = chunk.start_line) !== null && _p !== void 0 ? _p : 0;
101
- const role = (chunk.role || "IMPL").slice(0, 4);
102
- if (sym) {
103
- console.log(`${rel(file)}:${line + 1}\t${sym}\t[${role}]`);
104
- }
105
- }
106
- }
107
- }
108
- }
109
- else {
110
- console.log(`${changedFiles.length} changed file${changedFiles.length === 1 ? "" : "s"}${ref ? ` (vs ${ref})` : ""}:\n`);
111
- for (const file of changedFiles) {
112
- const chunks = yield table
113
- .query()
114
- .select(["defined_symbols", "role"])
115
- .where(`path = '${(0, filter_builder_1.escapeSqlString)(file)}'`)
116
- .limit(50)
117
- .toArray();
118
- const symbols = chunks
119
- .flatMap((c) => (0, arrow_1.toArr)(c.defined_symbols))
120
- .filter(Boolean);
121
- if (symbols.length > 0) {
122
- console.log(` ${rel(file)} (${symbols.length} symbol${symbols.length === 1 ? "" : "s"}: ${symbols.slice(0, 5).join(", ")}${symbols.length > 5 ? "..." : ""})`);
123
- }
124
- else {
125
- console.log(` ${rel(file)}`);
126
- }
127
- }
128
- }
129
- }
130
- }
131
- catch (error) {
132
- const msg = error instanceof Error ? error.message : "Unknown error";
133
- console.error("Diff failed:", msg);
134
- process.exitCode = 1;
135
- }
136
- finally {
137
- if (vectorDb) {
138
- try {
139
- yield vectorDb.close();
140
- }
141
- catch (_q) { }
142
- }
143
- yield (0, exit_1.gracefulExit)();
144
- }
145
- }));
6
+ .description("[deprecated] Use 'gmax log' instead")
7
+ .argument("[ref]", "(ignored diff is deprecated)")
8
+ .allowUnknownOption(true)
9
+ .allowExcessArguments(true)
10
+ .action(() => {
11
+ console.error("gmax diff is deprecated; use 'gmax log <path-or-symbol>' instead");
12
+ process.exitCode = 1;
13
+ });
@@ -0,0 +1,231 @@
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
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.log = void 0;
46
+ const fs = __importStar(require("node:fs"));
47
+ const path = __importStar(require("node:path"));
48
+ const commander_1 = require("commander");
49
+ const vector_db_1 = require("../lib/store/vector-db");
50
+ const exit_1 = require("../lib/utils/exit");
51
+ const filter_builder_1 = require("../lib/utils/filter-builder");
52
+ const git_1 = require("../lib/utils/git");
53
+ const project_registry_1 = require("../lib/utils/project-registry");
54
+ const project_root_1 = require("../lib/utils/project-root");
55
+ const useColors = process.stdout.isTTY && !process.env.NO_COLOR;
56
+ const style = {
57
+ bold: (s) => (useColors ? `\x1b[1m${s}\x1b[22m` : s),
58
+ dim: (s) => (useColors ? `\x1b[2m${s}\x1b[22m` : s),
59
+ cyan: (s) => (useColors ? `\x1b[36m${s}\x1b[39m` : s),
60
+ yellow: (s) => (useColors ? `\x1b[33m${s}\x1b[39m` : s),
61
+ };
62
+ function relativize(p, projectRoot) {
63
+ const prefix = projectRoot.endsWith("/") ? projectRoot : `${projectRoot}/`;
64
+ return p.startsWith(prefix) ? p.slice(prefix.length) : p;
65
+ }
66
+ function resolveSymbolPaths(vectorDb, symbol, projectRoot, inOpt, excludeOpt) {
67
+ return __awaiter(this, void 0, void 0, function* () {
68
+ const { resolveScope, buildScopeWhere } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/scope-filter")));
69
+ const scope = resolveScope({
70
+ projectRoot,
71
+ in: inOpt,
72
+ exclude: excludeOpt,
73
+ });
74
+ const where = buildScopeWhere(scope, `array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(symbol)}')`);
75
+ const table = yield vectorDb.ensureTable();
76
+ const rows = yield table
77
+ .query()
78
+ .select(["path"])
79
+ .where(where)
80
+ .limit(50)
81
+ .toArray();
82
+ const paths = new Set();
83
+ for (const row of rows) {
84
+ const p = String(row.path || "");
85
+ if (p)
86
+ paths.add(p);
87
+ }
88
+ return [...paths];
89
+ });
90
+ }
91
+ function printHuman(commits, projectRoot, targetPaths) {
92
+ for (const c of commits) {
93
+ const header = `${style.yellow(c.shortHash)} ${c.author} ${style.dim(c.relDate)} ${style.bold(c.subject)}`;
94
+ console.log(header);
95
+ const stat = `${c.filesChanged} file${c.filesChanged === 1 ? "" : "s"} changed, +${c.insertions} / -${c.deletions}`;
96
+ console.log(` ${style.dim(stat)}`);
97
+ if (targetPaths && targetPaths.length > 1) {
98
+ const targetSet = new Set(targetPaths);
99
+ const touched = c.numstatLines
100
+ .filter((n) => targetSet.has(n.path) || targetSet.has(path.resolve(projectRoot, n.path)))
101
+ .map((n) => relativize(n.path, projectRoot));
102
+ if (touched.length > 0) {
103
+ console.log(` ${style.dim(`via: ${touched.join(", ")}`)}`);
104
+ }
105
+ }
106
+ console.log();
107
+ }
108
+ }
109
+ function printAgent(commits, projectRoot, targetPaths) {
110
+ const targetSet = targetPaths && targetPaths.length > 0 ? new Set(targetPaths) : null;
111
+ for (const c of commits) {
112
+ let touched = "";
113
+ if (targetSet) {
114
+ const touchedPaths = c.numstatLines
115
+ .filter((n) => targetSet.has(n.path) || targetSet.has(path.resolve(projectRoot, n.path)))
116
+ .map((n) => relativize(n.path, projectRoot));
117
+ touched = touchedPaths.join(",");
118
+ }
119
+ const subject = c.subject.replace(/\t/g, " ");
120
+ console.log(`${c.shortHash}\t${c.isoDate}\t${c.author}\t${subject}\t${c.filesChanged}\t${c.insertions}\t${c.deletions}\t${touched}`);
121
+ }
122
+ }
123
+ exports.log = new commander_1.Command("log")
124
+ .description("Show git commit history for a path or symbol")
125
+ .argument("<path-or-symbol>", "File/dir path or symbol name")
126
+ .option("-l, --limit <n>", "Max commits (default 20)", "20")
127
+ .option("--since <date>", "Filter by date (e.g. '2 weeks ago', '2025-01-01')")
128
+ .option("--from <ref>", "Show commits since git ref (translates to <ref>..HEAD)")
129
+ .option("--author <name>", "Filter by author")
130
+ .option("--root <dir>", "Project root directory")
131
+ .option("--in <subpath>", "Restrict symbol resolution to a sub-path (repeatable; symbol mode only)", (value, prev) => prev ? [...prev, value] : [value])
132
+ .option("--exclude <subpath>", "Exclude a sub-path from symbol resolution (repeatable; symbol mode only)", (value, prev) => prev ? [...prev, value] : [value])
133
+ .option("--no-follow", "Disable rename tracking (default: enabled for single files in path mode)")
134
+ .option("--agent", "Compact TSV output for AI agents", false)
135
+ .action((arg, opts) => __awaiter(void 0, void 0, void 0, function* () {
136
+ var _a;
137
+ const limit = Math.min(Math.max(Number.parseInt(opts.limit || "20", 10), 1), 200);
138
+ const root = (0, project_registry_1.resolveRootOrExit)(opts.root);
139
+ if (root === null)
140
+ return;
141
+ const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
142
+ let vectorDb = null;
143
+ try {
144
+ // 1. Try arg as path (relative to projectRoot, then cwd).
145
+ const candidates = [
146
+ path.resolve(projectRoot, arg),
147
+ path.resolve(process.cwd(), arg),
148
+ ];
149
+ let resolvedPath = null;
150
+ for (const c of candidates) {
151
+ if (fs.existsSync(c)) {
152
+ resolvedPath = c;
153
+ break;
154
+ }
155
+ }
156
+ if (resolvedPath) {
157
+ const isDir = fs.statSync(resolvedPath).isDirectory();
158
+ const commits = (0, git_1.getCommitHistory)({
159
+ paths: [resolvedPath],
160
+ limit,
161
+ since: opts.since,
162
+ from: opts.from,
163
+ author: opts.author,
164
+ follow: !isDir && opts.follow !== false,
165
+ cwd: projectRoot,
166
+ });
167
+ if (commits.length === 0) {
168
+ console.log(opts.agent
169
+ ? ""
170
+ : `No commits found for ${relativize(resolvedPath, projectRoot)}.`);
171
+ return;
172
+ }
173
+ if (opts.agent)
174
+ printAgent(commits, projectRoot, null);
175
+ else
176
+ printHuman(commits, projectRoot, null);
177
+ return;
178
+ }
179
+ // 2. Try arg as symbol via index lookup.
180
+ const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
181
+ vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
182
+ const symbolPaths = yield resolveSymbolPaths(vectorDb, arg, projectRoot, opts.in, opts.exclude);
183
+ if (symbolPaths.length === 0) {
184
+ console.error(`gmax log: no file or symbol matched '${arg}'`);
185
+ if (!opts.agent) {
186
+ console.error("");
187
+ console.error("Try `gmax search <term>` to find a symbol, or pass a file path.");
188
+ }
189
+ process.exitCode = 1;
190
+ return;
191
+ }
192
+ const commits = (0, git_1.getCommitHistory)({
193
+ paths: symbolPaths,
194
+ limit,
195
+ since: opts.since,
196
+ from: opts.from,
197
+ author: opts.author,
198
+ follow: false,
199
+ cwd: projectRoot,
200
+ });
201
+ if (commits.length === 0) {
202
+ console.log(opts.agent
203
+ ? ""
204
+ : `No commits found touching defining files for symbol '${arg}'.`);
205
+ return;
206
+ }
207
+ if (opts.agent)
208
+ printAgent(commits, projectRoot, symbolPaths);
209
+ else {
210
+ if (symbolPaths.length > 1) {
211
+ console.log(style.dim(`// '${arg}' defined in ${symbolPaths.length} files; commits merged by hash.`));
212
+ console.log();
213
+ }
214
+ printHuman(commits, projectRoot, symbolPaths);
215
+ }
216
+ }
217
+ catch (error) {
218
+ const msg = error instanceof Error ? error.message : "Unknown error";
219
+ console.error("Log failed:", msg);
220
+ process.exitCode = 1;
221
+ }
222
+ finally {
223
+ if (vectorDb) {
224
+ try {
225
+ yield vectorDb.close();
226
+ }
227
+ catch (_b) { }
228
+ }
229
+ yield (0, exit_1.gracefulExit)();
230
+ }
231
+ }));
@@ -1,134 +1,12 @@
1
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
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
- return new (P || (P = Promise))(function (resolve, reject) {
38
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
- step((generator = generator.apply(thisArg, _arguments || [])).next());
42
- });
43
- };
44
- var __asyncValues = (this && this.__asyncValues) || function (o) {
45
- if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
46
- var m = o[Symbol.asyncIterator], i;
47
- return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
48
- function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
49
- function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
50
- };
51
2
  Object.defineProperty(exports, "__esModule", { value: true });
52
3
  exports.recent = void 0;
53
- const path = __importStar(require("node:path"));
54
4
  const commander_1 = require("commander");
55
- const config_1 = require("../config");
56
- const meta_cache_1 = require("../lib/store/meta-cache");
57
- const exit_1 = require("../lib/utils/exit");
58
- const format_helpers_1 = require("../lib/utils/format-helpers");
59
- const project_registry_1 = require("../lib/utils/project-registry");
60
- const project_root_1 = require("../lib/utils/project-root");
61
5
  exports.recent = new commander_1.Command("recent")
62
- .description("Show recently modified indexed files")
63
- .option("-l, --limit <n>", "Max files (default 20)", "20")
64
- .option("--root <dir>", "Project root (defaults to current directory)")
65
- .option("--agent", "Compact output for AI agents", false)
66
- .action((opts) => __awaiter(void 0, void 0, void 0, function* () {
67
- var _a, e_1, _b, _c;
68
- var _d;
69
- const limit = Math.min(Math.max(Number.parseInt(opts.limit || "20", 10), 1), 50);
70
- try {
71
- const resolvedRoot = (0, project_registry_1.resolveRootOrExit)(opts.root);
72
- if (resolvedRoot === null)
73
- return;
74
- const root = (_d = (0, project_root_1.findProjectRoot)(resolvedRoot)) !== null && _d !== void 0 ? _d : resolvedRoot;
75
- const prefix = root.endsWith("/") ? root : `${root}/`;
76
- const metaCache = new meta_cache_1.MetaCache(config_1.PATHS.lmdbPath);
77
- try {
78
- const files = [];
79
- try {
80
- for (var _e = true, _f = __asyncValues(metaCache.entries()), _g; _g = yield _f.next(), _a = _g.done, !_a; _e = true) {
81
- _c = _g.value;
82
- _e = false;
83
- const { path: p, entry } = _c;
84
- if (p.startsWith(prefix)) {
85
- files.push({ path: p, mtimeMs: entry.mtimeMs });
86
- }
87
- }
88
- }
89
- catch (e_1_1) { e_1 = { error: e_1_1 }; }
90
- finally {
91
- try {
92
- if (!_e && !_a && (_b = _f.return)) yield _b.call(_f);
93
- }
94
- finally { if (e_1) throw e_1.error; }
95
- }
96
- files.sort((a, b) => b.mtimeMs - a.mtimeMs);
97
- const top = files.slice(0, limit);
98
- if (top.length === 0) {
99
- console.log(`No indexed files found for ${root}`);
100
- console.log("\nTry: `gmax add` to register and index this project, or `gmax status` to see what's indexed.");
101
- process.exitCode = 1;
102
- return;
103
- }
104
- const now = Date.now();
105
- if (opts.agent) {
106
- for (const f of top) {
107
- const rel = f.path.startsWith(prefix)
108
- ? f.path.slice(prefix.length)
109
- : f.path;
110
- console.log(`${rel}\t${(0, format_helpers_1.formatTimeAgo)(now - f.mtimeMs)}`);
111
- }
112
- }
113
- else {
114
- console.log(`Recent changes in ${path.basename(root)} (${top.length} most recent):\n`);
115
- for (const f of top) {
116
- const rel = f.path.startsWith(prefix)
117
- ? f.path.slice(prefix.length)
118
- : f.path;
119
- const ago = (0, format_helpers_1.formatTimeAgo)(now - f.mtimeMs);
120
- console.log(` ${ago.padEnd(10)} ${rel}`);
121
- }
122
- }
123
- }
124
- finally {
125
- yield metaCache.close();
126
- }
127
- }
128
- catch (error) {
129
- const msg = error instanceof Error ? error.message : "Unknown error";
130
- console.error("Recent changes failed:", msg);
131
- process.exitCode = 1;
132
- }
133
- yield (0, exit_1.gracefulExit)();
134
- }));
6
+ .description("[deprecated] Use 'gmax log' instead")
7
+ .allowUnknownOption(true)
8
+ .allowExcessArguments(true)
9
+ .action(() => {
10
+ console.error("gmax recent is deprecated; use 'gmax log <path-or-symbol>' instead");
11
+ process.exitCode = 1;
12
+ });
package/dist/index.js CHANGED
@@ -52,6 +52,7 @@ const index_1 = require("./commands/index");
52
52
  const investigate_1 = require("./commands/investigate");
53
53
  const list_1 = require("./commands/list");
54
54
  const llm_1 = require("./commands/llm");
55
+ const log_1 = require("./commands/log");
55
56
  const mcp_1 = require("./commands/mcp");
56
57
  const peek_1 = require("./commands/peek");
57
58
  const project_1 = require("./commands/project");
@@ -115,6 +116,7 @@ commander_1.program.addCommand(extract_1.extract);
115
116
  commander_1.program.addCommand(peek_1.peek);
116
117
  commander_1.program.addCommand(project_1.project);
117
118
  commander_1.program.addCommand(related_1.related);
119
+ commander_1.program.addCommand(log_1.log);
118
120
  commander_1.program.addCommand(recent_1.recent);
119
121
  commander_1.program.addCommand(diff_1.diff);
120
122
  commander_1.program.addCommand(test_find_1.testFind);
@@ -37,6 +37,7 @@ exports.isWorktree = isWorktree;
37
37
  exports.getGitCommonDir = getGitCommonDir;
38
38
  exports.getMainRepoRoot = getMainRepoRoot;
39
39
  exports.getChangedFiles = getChangedFiles;
40
+ exports.getCommitHistory = getCommitHistory;
40
41
  exports.getUntrackedFiles = getUntrackedFiles;
41
42
  const node_child_process_1 = require("node:child_process");
42
43
  const fs = __importStar(require("node:fs"));
@@ -115,6 +116,95 @@ function getChangedFiles(ref, cwd) {
115
116
  return [];
116
117
  }
117
118
  }
119
+ /**
120
+ * Get commit history for one or more paths. When paths.length > 1 (symbol
121
+ * fan-out), git natively dedupes commits across paths. --follow only works
122
+ * with a single path; auto-disabled otherwise.
123
+ */
124
+ function getCommitHistory(opts) {
125
+ var _a;
126
+ if (opts.paths.length === 0)
127
+ return [];
128
+ const args = [
129
+ "log",
130
+ "--pretty=format:%x1e%H%x1f%aN%x1f%aI%x1f%ar%x1f%s",
131
+ "--numstat",
132
+ ];
133
+ if (opts.follow && opts.paths.length === 1)
134
+ args.push("--follow");
135
+ if (opts.limit > 0)
136
+ args.push(`-n${opts.limit}`);
137
+ if (opts.since)
138
+ args.push(`--since=${opts.since}`);
139
+ if (opts.author)
140
+ args.push(`--author=${opts.author}`);
141
+ if (opts.from)
142
+ args.push(`${opts.from}..HEAD`);
143
+ args.push("--");
144
+ for (const p of opts.paths)
145
+ args.push(p);
146
+ const execOpts = {
147
+ cwd: (_a = opts.cwd) !== null && _a !== void 0 ? _a : process.cwd(),
148
+ encoding: "utf-8",
149
+ timeout: 10000,
150
+ maxBuffer: 16 * 1024 * 1024,
151
+ };
152
+ let output;
153
+ try {
154
+ output = (0, node_child_process_1.execFileSync)("git", args, execOpts);
155
+ }
156
+ catch (_b) {
157
+ return [];
158
+ }
159
+ const records = output.split("\x1e").filter((r) => r.length > 0);
160
+ const commits = [];
161
+ for (const record of records) {
162
+ const lines = record.split("\n");
163
+ if (lines.length === 0)
164
+ continue;
165
+ const headerFields = lines[0].split("\x1f");
166
+ if (headerFields.length < 5)
167
+ continue;
168
+ const [hash, author, isoDate, relDate, subject] = headerFields;
169
+ const numstatLines = [];
170
+ let insertions = 0;
171
+ let deletions = 0;
172
+ for (let i = 1; i < lines.length; i++) {
173
+ const line = lines[i].trim();
174
+ if (!line)
175
+ continue;
176
+ const parts = line.split("\t");
177
+ if (parts.length < 3)
178
+ continue;
179
+ // Binary diffs report '-' for added/removed; treat as 0.
180
+ const added = parts[0] === "-" ? 0 : Number.parseInt(parts[0], 10);
181
+ const removed = parts[1] === "-" ? 0 : Number.parseInt(parts[1], 10);
182
+ const path = parts.slice(2).join("\t");
183
+ if (Number.isFinite(added))
184
+ insertions += added;
185
+ if (Number.isFinite(removed))
186
+ deletions += removed;
187
+ numstatLines.push({
188
+ added: Number.isFinite(added) ? added : 0,
189
+ removed: Number.isFinite(removed) ? removed : 0,
190
+ path,
191
+ });
192
+ }
193
+ commits.push({
194
+ hash,
195
+ shortHash: hash.slice(0, 7),
196
+ author,
197
+ isoDate,
198
+ relDate,
199
+ subject,
200
+ filesChanged: numstatLines.length,
201
+ insertions,
202
+ deletions,
203
+ numstatLines,
204
+ });
205
+ }
206
+ return commits;
207
+ }
118
208
  /**
119
209
  * Get untracked files (not yet added to git).
120
210
  * Returns absolute paths.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.16.2",
3
+ "version": "0.16.3",
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.2",
3
+ "version": "0.16.3",
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",
@@ -125,9 +125,18 @@ gmax related src/lib/index/syncer.ts # dependencies + dependents
125
125
  gmax related src/lib/index/syncer.ts --root ~/project
126
126
  ```
127
127
 
128
- ### Recent changes — `gmax recent`
128
+ ### Commit history — `gmax log <path-or-symbol>`
129
129
  ```
130
- gmax recent # recently modified files
130
+ gmax log src/lib/auth.ts # commits touching this file
131
+ gmax log src/lib/ # commits touching this directory
132
+ gmax log handleAuth # commits across all files defining the symbol
133
+ gmax log handleAuth --in src/ # restrict symbol resolution to a sub-path
134
+ gmax log src/lib/auth.ts --limit 5 # last 5 commits
135
+ gmax log src/lib/auth.ts --from main # commits since main (range main..HEAD)
136
+ gmax log src/lib/auth.ts --since "2 weeks ago"
137
+ gmax log src/lib/auth.ts --author Robert
138
+ gmax log src/lib/auth.ts --no-follow # disable rename tracking (default: on for files)
139
+ gmax log src/lib/auth.ts --agent # TSV: hash\tisoDate\tauthor\tsubject\tfilesChanged\tins\tdel\ttouchedFiles
131
140
  ```
132
141
 
133
142
  ### Symbols — `gmax symbols`
@@ -137,15 +146,6 @@ gmax symbols auth -p src/ --root ~/proj # filter by name, path, project
137
146
  gmax symbols --agent # compact: symbol\tpath:line\tcount
138
147
  ```
139
148
 
140
- ### Diff — `gmax diff [ref]`
141
- ```
142
- gmax diff # uncommitted changes
143
- gmax diff HEAD~5 # last 5 commits
144
- gmax diff main # branch changes vs main
145
- gmax diff main --query "auth changes" # semantic search within changed files
146
- gmax diff --agent # compact output
147
- ```
148
-
149
149
  ### Test — `gmax test <symbol|file>`
150
150
  ```
151
151
  gmax test handleAuth # tests calling handleAuth