grepmax 0.23.0 → 0.24.0

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.
@@ -0,0 +1,202 @@
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.relativeToProject = relativeToProject;
37
+ exports.impactPackageBucket = impactPackageBucket;
38
+ exports.buildImpactRollup = buildImpactRollup;
39
+ exports.formatImpactRollupHuman = formatImpactRollupHuman;
40
+ exports.formatImpactRollupAgent = formatImpactRollupAgent;
41
+ const path = __importStar(require("node:path"));
42
+ const impact_1 = require("./impact");
43
+ const test_hits_1 = require("./test-hits");
44
+ function relativeToProject(projectRoot, filePath) {
45
+ return filePath.startsWith(`${projectRoot}/`)
46
+ ? filePath.slice(projectRoot.length + 1)
47
+ : filePath;
48
+ }
49
+ function impactPackageBucket(projectRoot, filePath) {
50
+ const rel = relativeToProject(projectRoot, filePath).replace(/\\/g, "/");
51
+ const parts = rel.split("/").filter(Boolean);
52
+ if (parts[0] === "packages" && parts[1])
53
+ return `packages/${parts[1]}`;
54
+ if (parts[0])
55
+ return parts[0];
56
+ return path.basename(filePath) || ".";
57
+ }
58
+ function sortDetailedDependents(dependents) {
59
+ return [...dependents].sort((a, b) => b.sharedSymbols - a.sharedSymbols || a.file.localeCompare(b.file));
60
+ }
61
+ function buildImpactRollup(opts) {
62
+ var _a, _b, _c;
63
+ const top = Math.max(1, (_a = opts.top) !== null && _a !== void 0 ? _a : 10);
64
+ const productionDependents = sortDetailedDependents(opts.dependents.filter((d) => !(0, impact_1.isTestPath)(d.file)));
65
+ const tests = (0, test_hits_1.groupTestHitsByFile)((_b = opts.tests) !== null && _b !== void 0 ? _b : []);
66
+ const exports = opts.targetSymbols
67
+ .map((symbol) => {
68
+ const matches = productionDependents.filter((d) => d.symbols.includes(symbol));
69
+ return {
70
+ symbol,
71
+ dependentCount: matches.length,
72
+ topDependents: matches.slice(0, top),
73
+ };
74
+ })
75
+ .sort((a, b) => b.dependentCount - a.dependentCount || a.symbol.localeCompare(b.symbol));
76
+ const byPackage = new Map();
77
+ for (const dep of productionDependents) {
78
+ const bucket = impactPackageBucket(opts.projectRoot, dep.file);
79
+ const entry = (_c = byPackage.get(bucket)) !== null && _c !== void 0 ? _c : {
80
+ dependents: [],
81
+ symbols: new Set(),
82
+ };
83
+ entry.dependents.push(dep);
84
+ for (const sym of dep.symbols)
85
+ entry.symbols.add(sym);
86
+ byPackage.set(bucket, entry);
87
+ }
88
+ const packages = [...byPackage.entries()]
89
+ .map(([name, entry]) => ({
90
+ name,
91
+ dependentCount: entry.dependents.length,
92
+ symbols: [...entry.symbols].sort(),
93
+ topDependents: sortDetailedDependents(entry.dependents).slice(0, top),
94
+ }))
95
+ .sort((a, b) => b.dependentCount - a.dependentCount || a.name.localeCompare(b.name));
96
+ return {
97
+ targetSymbols: [...opts.targetSymbols],
98
+ productionDependents,
99
+ packages,
100
+ exports,
101
+ tests,
102
+ topDependents: productionDependents.slice(0, top),
103
+ topTests: tests.slice(0, top),
104
+ };
105
+ }
106
+ function symbolsLabel(symbols) {
107
+ if (symbols.length <= 3)
108
+ return symbols.join(",");
109
+ return `${symbols.slice(0, 3).join(",")}(+${symbols.length - 3})`;
110
+ }
111
+ function formatImpactRollupHuman(rollup, opts) {
112
+ const lines = [`Impact rollup for ${opts.target}:`, ""];
113
+ lines.push("Summary:");
114
+ lines.push(` Exports: ${rollup.targetSymbols.length}`);
115
+ lines.push(` Production dependents: ${rollup.productionDependents.length}`);
116
+ lines.push(` Packages: ${rollup.packages.length}`);
117
+ if (opts.includeTests)
118
+ lines.push(` Affected tests: ${rollup.tests.length}`);
119
+ lines.push("", `Exports (${rollup.exports.length}):`);
120
+ for (const ex of rollup.exports) {
121
+ lines.push(` ${ex.symbol} ${ex.dependentCount} dependent${ex.dependentCount === 1 ? "" : "s"}`);
122
+ for (const dep of ex.topDependents) {
123
+ lines.push(` ${relativeToProject(opts.projectRoot, dep.file)}`);
124
+ }
125
+ }
126
+ lines.push("", `Packages (${rollup.packages.length}):`);
127
+ if (rollup.packages.length === 0) {
128
+ lines.push(" none");
129
+ }
130
+ else {
131
+ for (const pkg of rollup.packages) {
132
+ lines.push(` ${pkg.name} ${pkg.dependentCount} file${pkg.dependentCount === 1 ? "" : "s"} symbols:${symbolsLabel(pkg.symbols)}`);
133
+ for (const dep of pkg.topDependents) {
134
+ lines.push(` ${relativeToProject(opts.projectRoot, dep.file)} (${symbolsLabel(dep.symbols)})`);
135
+ }
136
+ }
137
+ }
138
+ lines.push("", `Top dependents (${rollup.topDependents.length}):`);
139
+ if (rollup.topDependents.length === 0) {
140
+ lines.push(" none");
141
+ }
142
+ else {
143
+ for (const dep of rollup.topDependents) {
144
+ lines.push(` ${relativeToProject(opts.projectRoot, dep.file)} (${symbolsLabel(dep.symbols)})`);
145
+ }
146
+ }
147
+ if (opts.includeTests) {
148
+ lines.push("", `Affected tests (${rollup.tests.length}):`);
149
+ if (rollup.topTests.length === 0) {
150
+ lines.push(" none");
151
+ }
152
+ else {
153
+ for (const test of rollup.topTests) {
154
+ lines.push(` ${relativeToProject(opts.projectRoot, test.file)}:${test.line + 1} (${(0, test_hits_1.hopLabelHuman)(test.hops)}${(0, test_hits_1.formatViaHuman)(test.via)})`);
155
+ }
156
+ }
157
+ }
158
+ return lines.join("\n");
159
+ }
160
+ function formatImpactRollupAgent(rollup, opts) {
161
+ const lines = [
162
+ [
163
+ "summary",
164
+ `target=${opts.target}`,
165
+ `exports=${rollup.targetSymbols.length}`,
166
+ `deps=${rollup.productionDependents.length}`,
167
+ `packages=${rollup.packages.length}`,
168
+ `tests=${opts.includeTests ? rollup.tests.length : "skipped"}`,
169
+ ].join("\t"),
170
+ ];
171
+ for (const ex of rollup.exports) {
172
+ lines.push(["export", ex.symbol, `deps=${ex.dependentCount}`].join("\t"));
173
+ }
174
+ for (const pkg of rollup.packages) {
175
+ lines.push([
176
+ "pkg",
177
+ pkg.name,
178
+ `deps=${pkg.dependentCount}`,
179
+ `symbols=${symbolsLabel(pkg.symbols)}`,
180
+ ].join("\t"));
181
+ }
182
+ for (const dep of rollup.topDependents) {
183
+ lines.push([
184
+ "dep",
185
+ relativeToProject(opts.projectRoot, dep.file),
186
+ `symbols=${symbolsLabel(dep.symbols)}`,
187
+ ].join("\t"));
188
+ }
189
+ if (opts.includeTests) {
190
+ for (const test of rollup.topTests) {
191
+ lines.push([
192
+ "test",
193
+ `${relativeToProject(opts.projectRoot, test.file)}:${test.line + 1}`,
194
+ (0, test_hits_1.hopLabelAgent)(test.hops),
195
+ (0, test_hits_1.formatViaAgent)(test.via).trim(),
196
+ ]
197
+ .filter(Boolean)
198
+ .join("\t"));
199
+ }
200
+ }
201
+ return lines.join("\n");
202
+ }
@@ -13,6 +13,7 @@ exports.isTestPath = isTestPath;
13
13
  exports.resolveTargetSymbols = resolveTargetSymbols;
14
14
  exports.findTests = findTests;
15
15
  exports.findDependents = findDependents;
16
+ exports.findDependentsDetailed = findDependentsDetailed;
16
17
  const languages_1 = require("../core/languages");
17
18
  const filter_builder_1 = require("../utils/filter-builder");
18
19
  const query_timeout_1 = require("../utils/query-timeout");
@@ -244,6 +245,15 @@ function walkCallers(symbol, graphBuilder, testHits, currentHop, maxDepth, visit
244
245
  * Returns files sorted by number of shared symbols (descending).
245
246
  */
246
247
  function findDependents(symbols_1, vectorDb_1, projectRoot_1, excludePaths_1) {
248
+ return __awaiter(this, arguments, void 0, function* (symbols, vectorDb, projectRoot, excludePaths, limit = 10, excludePrefixes, symbolFamilies) {
249
+ return (yield findDependentsDetailed(symbols, vectorDb, projectRoot, excludePaths, limit, excludePrefixes, symbolFamilies)).map(({ file, sharedSymbols }) => ({ file, sharedSymbols }));
250
+ });
251
+ }
252
+ /**
253
+ * Find dependent files and retain which target symbols each file matched.
254
+ * The legacy `findDependents` projects this down to `{file, sharedSymbols}`.
255
+ */
256
+ function findDependentsDetailed(symbols_1, vectorDb_1, projectRoot_1, excludePaths_1) {
247
257
  return __awaiter(this, arguments, void 0, function* (symbols, vectorDb, projectRoot, excludePaths, limit = 10, excludePrefixes, symbolFamilies) {
248
258
  var _a, _b;
249
259
  const table = yield vectorDb.ensureTable();
@@ -279,6 +289,10 @@ function findDependents(symbols_1, vectorDb_1, projectRoot_1, excludePaths_1) {
279
289
  return Array.from(symbolsByFile.entries())
280
290
  .sort((a, b) => b[1].size - a[1].size)
281
291
  .slice(0, limit)
282
- .map(([file, symbols]) => ({ file, sharedSymbols: symbols.size }));
292
+ .map(([file, symbols]) => ({
293
+ file,
294
+ sharedSymbols: symbols.size,
295
+ symbols: [...symbols].sort(),
296
+ }));
283
297
  });
284
298
  }
@@ -31,6 +31,7 @@ Understand:
31
31
  Survey:
32
32
  gmax project codebase overview (langs, structure, key symbols)
33
33
  gmax skeleton <file> file structure (file path, NOT a directory)
34
+ gmax surprises --experimental similar but graph-disconnected file pairs
34
35
  gmax context "topic-or-path" --budget 4000 topic summary or deterministic file/dir context
35
36
  gmax log <path-or-symbol> git commits (replaces recent/diff)
36
37
  gmax status indexed projects
@@ -69,6 +69,8 @@ const DEFAULT_OPTIONS = {
69
69
  includeSummary: true,
70
70
  maxCallsInSummary: 4,
71
71
  };
72
+ const SQL_TEMPLATE_LANGS = new Set(["typescript", "tsx", "javascript"]);
73
+ const SQL_IDENTIFIER_PATTERN = '(?:"[^"]+"|\\[[^\\]]+\\]|`[^`]+`|[A-Za-z_][\\w$]*)(?:\\s*\\.\\s*(?:"[^"]+"|\\[[^\\]]+\\]|`[^`]+`|[A-Za-z_][\\w$]*)){0,2}';
72
74
  // WASM locator (same as chunker.ts)
73
75
  function resolveTreeSitterWasmLocator() {
74
76
  try {
@@ -238,10 +240,12 @@ class Skeletonizer {
238
240
  }
239
241
  // Extract metadata from the body
240
242
  const referencedSymbols = this.extractReferencedSymbols(bodyNode);
243
+ const sqlTemplates = this.extractSqlTemplateSummaries(bodyNode, langId);
241
244
  const complexity = this.calculateComplexity(bodyNode);
242
245
  const role = this.classifyRole(complexity, referencedSymbols.length);
243
246
  const metadata = {
244
247
  referencedSymbols,
248
+ sqlTemplates,
245
249
  complexity,
246
250
  role,
247
251
  };
@@ -450,6 +454,120 @@ class Skeletonizer {
450
454
  extract(node);
451
455
  return refs;
452
456
  }
457
+ /**
458
+ * Surface SQL tagged templates hidden inside elided JS/TS function bodies.
459
+ * This is intentionally a lightweight skeleton hint, not a SQL parser.
460
+ */
461
+ extractSqlTemplateSummaries(node, langId) {
462
+ if (!SQL_TEMPLATE_LANGS.has(langId))
463
+ return [];
464
+ const summaries = [];
465
+ const seen = new Set();
466
+ const extract = (n) => {
467
+ const template = (n.namedChildren || []).find((child) => child.type === "template_string");
468
+ if (template && this.isSqlTaggedTemplate(n, template)) {
469
+ const summary = this.summarizeSqlTemplate(template);
470
+ if (summary && !seen.has(summary)) {
471
+ seen.add(summary);
472
+ summaries.push(summary);
473
+ }
474
+ return;
475
+ }
476
+ for (const child of n.namedChildren || []) {
477
+ extract(child);
478
+ }
479
+ };
480
+ extract(node);
481
+ return summaries;
482
+ }
483
+ isSqlTaggedTemplate(node, template) {
484
+ const tagText = node.text
485
+ .slice(0, template.startIndex - node.startIndex)
486
+ .trim();
487
+ return /(?:^|[.\s])sql(?:$|[.<(])/.test(tagText);
488
+ }
489
+ summarizeSqlTemplate(template) {
490
+ var _a;
491
+ const sqlText = this.extractTemplateText(template)
492
+ .replace(/--[^\n]*/g, " ")
493
+ .replace(/\/\*[\s\S]*?\*\//g, " ")
494
+ .replace(/\s+/g, " ")
495
+ .trim();
496
+ if (!sqlText)
497
+ return null;
498
+ const op = (_a = sqlText.match(/\b(select|insert|update|delete|merge|create|alter|drop|truncate)\b/i)) === null || _a === void 0 ? void 0 : _a[1];
499
+ if (!op)
500
+ return "SQL";
501
+ const operation = op.toUpperCase();
502
+ const tables = this.extractSqlTables(sqlText, operation);
503
+ if (tables.length === 0)
504
+ return operation;
505
+ const tableList = tables.slice(0, 2);
506
+ if (tables.length > tableList.length)
507
+ tableList.push("...");
508
+ return `${operation} ${tableList.join(", ")}`;
509
+ }
510
+ extractTemplateText(template) {
511
+ const parts = [];
512
+ for (const child of template.namedChildren || []) {
513
+ if (child.type === "string_fragment") {
514
+ parts.push(child.text);
515
+ }
516
+ else if (child.type === "template_substitution") {
517
+ parts.push(" ? ");
518
+ }
519
+ }
520
+ if (parts.length > 0)
521
+ return parts.join("");
522
+ return template.text.slice(1, -1).replace(/\$\{[^}]*\}/g, " ? ");
523
+ }
524
+ extractSqlTables(sqlText, operation) {
525
+ const tables = [];
526
+ const seen = new Set();
527
+ const addMatches = (pattern) => {
528
+ for (const match of sqlText.matchAll(pattern)) {
529
+ const table = this.normalizeSqlIdentifier(match[1]);
530
+ if (table && !seen.has(table)) {
531
+ seen.add(table);
532
+ tables.push(table);
533
+ }
534
+ }
535
+ };
536
+ switch (operation) {
537
+ case "SELECT":
538
+ addMatches(new RegExp(`\\b(?:from|join)\\s+(${SQL_IDENTIFIER_PATTERN})`, "gi"));
539
+ break;
540
+ case "INSERT":
541
+ addMatches(new RegExp(`\\binsert\\s+into\\s+(${SQL_IDENTIFIER_PATTERN})`, "gi"));
542
+ break;
543
+ case "UPDATE":
544
+ addMatches(new RegExp(`\\bupdate\\s+(${SQL_IDENTIFIER_PATTERN})`, "gi"));
545
+ break;
546
+ case "DELETE":
547
+ addMatches(new RegExp(`\\bdelete\\s+from\\s+(${SQL_IDENTIFIER_PATTERN})`, "gi"));
548
+ break;
549
+ case "MERGE":
550
+ addMatches(new RegExp(`\\bmerge\\s+into\\s+(${SQL_IDENTIFIER_PATTERN})`, "gi"));
551
+ break;
552
+ case "CREATE":
553
+ addMatches(new RegExp(`\\bcreate\\s+(?:or\\s+replace\\s+)?(?:table|view|materialized\\s+view|index)\\s+(?:if\\s+not\\s+exists\\s+)?(${SQL_IDENTIFIER_PATTERN})`, "gi"));
554
+ break;
555
+ case "ALTER":
556
+ case "DROP":
557
+ addMatches(new RegExp(`\\b${operation.toLowerCase()}\\s+(?:table|view|materialized\\s+view|index)\\s+(?:if\\s+(?:exists|not\\s+exists)\\s+)?(${SQL_IDENTIFIER_PATTERN})`, "gi"));
558
+ break;
559
+ case "TRUNCATE":
560
+ addMatches(new RegExp(`\\btruncate\\s+(?:table\\s+)?(${SQL_IDENTIFIER_PATTERN})`, "gi"));
561
+ break;
562
+ }
563
+ return tables;
564
+ }
565
+ normalizeSqlIdentifier(identifier) {
566
+ const normalized = identifier === null || identifier === void 0 ? void 0 : identifier.replace(/\s*\.\s*/g, ".").replace(/["`[\]]/g, "").trim();
567
+ if (!normalized || normalized === "?")
568
+ return null;
569
+ return normalized;
570
+ }
453
571
  /**
454
572
  * Calculate cyclomatic complexity of a node.
455
573
  */
@@ -25,7 +25,7 @@ const DEFAULT_OPTIONS = {
25
25
  * // Output: "// → findByEmail, compare, sign | C:8 | ORCH"
26
26
  */
27
27
  function formatSummary(metadata, options = {}) {
28
- var _a;
28
+ var _a, _b;
29
29
  const opts = Object.assign(Object.assign({}, DEFAULT_OPTIONS), options);
30
30
  const parts = [];
31
31
  // Calls (referenced symbols)
@@ -36,6 +36,13 @@ function formatSummary(metadata, options = {}) {
36
36
  }
37
37
  parts.push(`→ ${calls.join(", ")}`);
38
38
  }
39
+ if ((_b = metadata.sqlTemplates) === null || _b === void 0 ? void 0 : _b.length) {
40
+ const templates = metadata.sqlTemplates.slice(0, 3);
41
+ if (metadata.sqlTemplates.length > templates.length) {
42
+ templates.push("...");
43
+ }
44
+ parts.push(`SQL: ${templates.join("; ")}`);
45
+ }
39
46
  // Complexity (only if > 1, trivial functions don't need it)
40
47
  if (opts.showComplexity && metadata.complexity && metadata.complexity > 1) {
41
48
  parts.push(`C:${metadata.complexity}`);
@@ -160,7 +160,10 @@ function resolveProcessWorker() {
160
160
  return { filename: jsWorker, execArgv: [] };
161
161
  }
162
162
  if (fs.existsSync(tsWorker)) {
163
- return { filename: tsWorker, execArgv: ["-r", "ts-node/register"] };
163
+ return {
164
+ filename: tsWorker,
165
+ execArgv: ["--import", require.resolve("tsx")],
166
+ };
164
167
  }
165
168
  throw new Error("Process worker file not found");
166
169
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "author": "Robert Owens <78518764+reowens@users.noreply.github.com>",
5
5
  "homepage": "https://github.com/reowens/grepmax",
6
6
  "bugs": {
@@ -31,6 +31,8 @@
31
31
  "bench:oss:json": "GMAX_EVAL_JSON=1 npx tsx src/eval-oss.ts all",
32
32
  "bench:tokens": "npx tsx src/eval-tokens.ts",
33
33
  "bench:tokens:json": "GMAX_EVAL_JSON=1 npx tsx src/eval-tokens.ts",
34
+ "bench:surprises": "npx tsx src/eval-surprising-connections.ts",
35
+ "bench:surprises:json": "GMAX_EVAL_JSON=1 npx tsx src/eval-surprising-connections.ts",
34
36
  "format": "biome format --write .",
35
37
  "format:check": "biome check .",
36
38
  "lint": "biome lint .",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
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",