grepmax 0.7.44 → 0.8.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.
@@ -43,10 +43,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
43
43
  };
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.add = void 0;
46
- const node_child_process_1 = require("node:child_process");
47
46
  const path = __importStar(require("node:path"));
48
47
  const commander_1 = require("commander");
49
48
  const grammar_loader_1 = require("../lib/index/grammar-loader");
49
+ const index_config_1 = require("../lib/index/index-config");
50
50
  const sync_helpers_1 = require("../lib/index/sync-helpers");
51
51
  const syncer_1 = require("../lib/index/syncer");
52
52
  const setup_helpers_1 = require("../lib/setup/setup-helpers");
@@ -55,7 +55,7 @@ const exit_1 = require("../lib/utils/exit");
55
55
  const project_marker_1 = require("../lib/utils/project-marker");
56
56
  const project_registry_1 = require("../lib/utils/project-registry");
57
57
  const project_root_1 = require("../lib/utils/project-root");
58
- const index_config_1 = require("../lib/index/index-config");
58
+ const watcher_launcher_1 = require("../lib/utils/watcher-launcher");
59
59
  exports.add = new commander_1.Command("add")
60
60
  .description("Add a project to the gmax index")
61
61
  .argument("[dir]", "Directory to add (defaults to current directory)")
@@ -75,11 +75,27 @@ Examples:
75
75
  const projectName = path.basename(projectRoot);
76
76
  // Check if already registered
77
77
  const existing = (0, project_registry_1.getProject)(projectRoot);
78
- if (existing || (0, project_marker_1.hasMarker)(projectRoot)) {
79
- console.log(`${projectName} is already added (${(_b = existing === null || existing === void 0 ? void 0 : existing.chunkCount) !== null && _b !== void 0 ? _b : 0} chunks).`);
78
+ if (existing) {
79
+ console.log(`${projectName} is already added (${(_b = existing.chunkCount) !== null && _b !== void 0 ? _b : 0} chunks).`);
80
80
  console.log(`Run \`gmax index\` to re-index, or \`gmax index --reset\` for a full rebuild.`);
81
81
  return;
82
82
  }
83
+ // Check if a parent project already covers this path
84
+ const parent = (0, project_registry_1.getParentProject)(projectRoot);
85
+ if (parent) {
86
+ console.log(`Already covered by ${path.basename(parent.root)} (${parent.root}).`);
87
+ console.log(`Use \`gmax status\` to see indexed projects.`);
88
+ return;
89
+ }
90
+ // If this is a parent of existing projects, absorb them
91
+ const children = (0, project_registry_1.getChildProjects)(projectRoot);
92
+ if (children.length > 0) {
93
+ const names = children.map((c) => c.name).join(", ");
94
+ console.log(`Absorbing ${children.length} sub-project(s): ${names}`);
95
+ for (const child of children) {
96
+ (0, project_registry_1.removeProject)(child.root);
97
+ }
98
+ }
83
99
  // Create marker file
84
100
  (0, project_marker_1.createMarker)(projectRoot);
85
101
  // Register as pending
@@ -109,11 +125,21 @@ Examples:
109
125
  projectRoot,
110
126
  onProgress,
111
127
  });
128
+ // Update registry: pending → indexed
129
+ (0, project_registry_1.registerProject)({
130
+ root: projectRoot,
131
+ name: projectName,
132
+ vectorDim: globalConfig.vectorDim,
133
+ modelTier: globalConfig.modelTier,
134
+ embedMode: globalConfig.embedMode,
135
+ lastIndexed: new Date().toISOString(),
136
+ chunkCount: result.indexed,
137
+ status: "indexed",
138
+ });
112
139
  const failedSuffix = result.failedFiles > 0 ? ` · ${result.failedFiles} failed` : "";
113
140
  spinner.succeed(`Added ${projectName} (${result.total} files, ${result.indexed} chunks${failedSuffix})`);
114
141
  }
115
142
  catch (e) {
116
- // Update status to error
117
143
  (0, project_registry_1.registerProject)({
118
144
  root: projectRoot,
119
145
  name: projectName,
@@ -127,14 +153,10 @@ Examples:
127
153
  spinner.fail(`Failed to index ${projectName}`);
128
154
  throw e;
129
155
  }
130
- // Start watcher in background
131
- try {
132
- const child = (0, node_child_process_1.spawn)(process.argv[0], [process.argv[1], "watch", "--path", projectRoot], { detached: true, stdio: "ignore" });
133
- child.unref();
134
- console.log(`Watcher started (PID: ${child.pid})`);
135
- }
136
- catch (_c) {
137
- console.log(`Note: could not start watcher. Run: gmax watch --path ${projectRoot} -b`);
156
+ // Start watcher
157
+ const launched = (0, watcher_launcher_1.launchWatcher)(projectRoot);
158
+ if (launched) {
159
+ console.log(`Watcher started (PID: ${launched.pid})`);
138
160
  }
139
161
  }
140
162
  catch (error) {
@@ -147,7 +169,7 @@ Examples:
147
169
  try {
148
170
  yield vectorDb.close();
149
171
  }
150
- catch (_d) { }
172
+ catch (_c) { }
151
173
  }
152
174
  yield (0, exit_1.gracefulExit)();
153
175
  }
@@ -43,17 +43,19 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
43
43
  };
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.index = void 0;
46
- const node_child_process_1 = require("node:child_process");
47
46
  const path = __importStar(require("node:path"));
48
47
  const commander_1 = require("commander");
48
+ const index_config_1 = require("../lib/index/index-config");
49
49
  const grammar_loader_1 = require("../lib/index/grammar-loader");
50
50
  const sync_helpers_1 = require("../lib/index/sync-helpers");
51
51
  const syncer_1 = require("../lib/index/syncer");
52
52
  const setup_helpers_1 = require("../lib/setup/setup-helpers");
53
53
  const vector_db_1 = require("../lib/store/vector-db");
54
54
  const exit_1 = require("../lib/utils/exit");
55
+ const project_registry_1 = require("../lib/utils/project-registry");
55
56
  const project_root_1 = require("../lib/utils/project-root");
56
- const watcher_registry_1 = require("../lib/utils/watcher-registry");
57
+ const watcher_launcher_1 = require("../lib/utils/watcher-launcher");
58
+ const watcher_store_1 = require("../lib/utils/watcher-store");
57
59
  exports.index = new commander_1.Command("index")
58
60
  .description("Index the current directory and create searchable store")
59
61
  .option("-d, --dry-run", "Dry run the indexing process (no actual file syncing)", false)
@@ -87,6 +89,12 @@ Examples:
87
89
  ? path.resolve(options.path)
88
90
  : process.cwd();
89
91
  const projectRoot = (_a = (0, project_root_1.findProjectRoot)(indexRoot)) !== null && _a !== void 0 ? _a : indexRoot;
92
+ // Project must be registered before reindexing
93
+ if (!(0, project_registry_1.getProject)(projectRoot)) {
94
+ console.error(`This project hasn't been added yet.\n\nRun: gmax add ${projectRoot}\n`);
95
+ process.exitCode = 1;
96
+ return;
97
+ }
90
98
  const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
91
99
  vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
92
100
  if (options.reset) {
@@ -97,7 +105,7 @@ Examples:
97
105
  // Ensure grammars are present before indexing (silent if already exist)
98
106
  yield (0, grammar_loader_1.ensureGrammars)(console.log, { silent: true });
99
107
  // Stop any watcher that covers this project — it holds the shared lock
100
- const watcher = (0, watcher_registry_1.getWatcherCoveringPath)(projectRoot);
108
+ const watcher = (0, watcher_store_1.getWatcherCoveringPath)(projectRoot);
101
109
  let restartWatcher = null;
102
110
  if (watcher) {
103
111
  console.log(`Stopping watcher (PID: ${watcher.pid}) for ${path.basename(watcher.projectRoot)}...`);
@@ -107,11 +115,11 @@ Examples:
107
115
  catch (_b) { }
108
116
  // Wait for process to exit (up to 5s)
109
117
  for (let i = 0; i < 50; i++) {
110
- if (!(0, watcher_registry_1.isProcessRunning)(watcher.pid))
118
+ if (!(0, watcher_store_1.isProcessRunning)(watcher.pid))
111
119
  break;
112
120
  yield new Promise((r) => setTimeout(r, 100));
113
121
  }
114
- (0, watcher_registry_1.unregisterWatcher)(watcher.pid);
122
+ (0, watcher_store_1.unregisterWatcher)(watcher.pid);
115
123
  restartWatcher = {
116
124
  pid: watcher.pid,
117
125
  projectRoot: watcher.projectRoot,
@@ -138,6 +146,18 @@ Examples:
138
146
  }));
139
147
  return;
140
148
  }
149
+ // Update registry with new stats
150
+ const globalConfig = (0, index_config_1.readGlobalConfig)();
151
+ (0, project_registry_1.registerProject)({
152
+ root: projectRoot,
153
+ name: path.basename(projectRoot),
154
+ vectorDim: globalConfig.vectorDim,
155
+ modelTier: globalConfig.modelTier,
156
+ embedMode: globalConfig.embedMode,
157
+ lastIndexed: new Date().toISOString(),
158
+ chunkCount: result.indexed,
159
+ status: "indexed",
160
+ });
141
161
  const failedSuffix = result.failedFiles > 0 ? ` • ${result.failedFiles} failed` : "";
142
162
  spinner.succeed(`Indexing complete(${result.processed} / ${result.total}) • indexed ${result.indexed}${failedSuffix} `);
143
163
  }
@@ -148,13 +168,9 @@ Examples:
148
168
  finally {
149
169
  // Restart the watcher if we stopped one
150
170
  if (restartWatcher) {
151
- try {
152
- const child = (0, node_child_process_1.spawn)(process.argv[0], [process.argv[1], "watch", "--path", restartWatcher.projectRoot], { detached: true, stdio: "ignore" });
153
- child.unref();
154
- console.log(`Restarted watcher for ${path.basename(restartWatcher.projectRoot)} (PID: ${child.pid})`);
155
- }
156
- catch (_c) {
157
- console.log(`Note: could not restart watcher. Run: gmax watch --path ${restartWatcher.projectRoot} -b`);
171
+ const launched = (0, watcher_launcher_1.launchWatcher)(restartWatcher.projectRoot);
172
+ if (launched) {
173
+ console.log(`Restarted watcher for ${path.basename(restartWatcher.projectRoot)} (PID: ${launched.pid})`);
158
174
  }
159
175
  }
160
176
  }
@@ -77,7 +77,7 @@ const format_helpers_1 = require("../lib/utils/format-helpers");
77
77
  const import_extractor_1 = require("../lib/utils/import-extractor");
78
78
  const project_registry_1 = require("../lib/utils/project-registry");
79
79
  const project_root_1 = require("../lib/utils/project-root");
80
- const watcher_registry_1 = require("../lib/utils/watcher-registry");
80
+ const watcher_store_1 = require("../lib/utils/watcher-store");
81
81
  // ---------------------------------------------------------------------------
82
82
  // Tool definitions
83
83
  // ---------------------------------------------------------------------------
@@ -331,8 +331,8 @@ exports.mcp = new commander_1.Command("mcp")
331
331
  return;
332
332
  _indexing = true;
333
333
  _indexProgress = "starting...";
334
- console.log("[MCP] First-time index for this project...");
335
- const child = (0, node_child_process_1.spawn)(process.argv[0], [process.argv[1], "index", "--path", projectRoot], { detached: true, stdio: "ignore" });
334
+ console.log("[MCP] First-time setup for this project...");
335
+ const child = (0, node_child_process_1.spawn)(process.argv[0], [process.argv[1], "add", projectRoot], { detached: true, stdio: "ignore" });
336
336
  _indexChildPid = (_a = child.pid) !== null && _a !== void 0 ? _a : null;
337
337
  child.unref();
338
338
  _indexProgress = `PID ${_indexChildPid}`;
@@ -342,7 +342,7 @@ exports.mcp = new commander_1.Command("mcp")
342
342
  _indexChildPid = null;
343
343
  if (code === 0) {
344
344
  _indexReady = true;
345
- console.log("[MCP] First-time indexing complete.");
345
+ console.log("[MCP] First-time setup complete.");
346
346
  }
347
347
  else {
348
348
  console.error(`[MCP] Indexing failed (exit code: ${code})`);
@@ -352,7 +352,10 @@ exports.mcp = new commander_1.Command("mcp")
352
352
  }
353
353
  // --- Background watcher ---
354
354
  function ensureWatcher() {
355
- if ((0, watcher_registry_1.getWatcherCoveringPath)(projectRoot))
355
+ // Only start watcher for registered projects
356
+ if (!(0, project_registry_1.getProject)(projectRoot))
357
+ return;
358
+ if ((0, watcher_store_1.getWatcherCoveringPath)(projectRoot))
356
359
  return;
357
360
  const child = (0, node_child_process_1.spawn)("gmax", ["watch", "-b", "--path", projectRoot], {
358
361
  detached: true,
@@ -611,6 +614,7 @@ exports.mcp = new commander_1.Command("mcp")
611
614
  }
612
615
  function handleCodeSkeleton(args) {
613
616
  return __awaiter(this, void 0, void 0, function* () {
617
+ ensureWatcher();
614
618
  const target = String(args.target || "");
615
619
  if (!target)
616
620
  return err("Missing required parameter: target");
@@ -725,6 +729,7 @@ exports.mcp = new commander_1.Command("mcp")
725
729
  }
726
730
  function handleTraceCalls(args) {
727
731
  return __awaiter(this, void 0, void 0, function* () {
732
+ ensureWatcher();
728
733
  const symbol = String(args.symbol || "");
729
734
  if (!symbol)
730
735
  return err("Missing required parameter: symbol");
@@ -806,6 +811,7 @@ exports.mcp = new commander_1.Command("mcp")
806
811
  }
807
812
  function handleListSymbols(args) {
808
813
  return __awaiter(this, void 0, void 0, function* () {
814
+ ensureWatcher();
809
815
  const pattern = typeof args.pattern === "string" ? args.pattern : undefined;
810
816
  const limit = Math.min(Math.max(Number(args.limit) || 20, 1), 100);
811
817
  const pathPrefix = typeof args.path === "string" ? args.path : undefined;
@@ -892,7 +898,7 @@ exports.mcp = new commander_1.Command("mcp")
892
898
  const stats = yield db.getStats();
893
899
  const fileCount = yield db.getDistinctFileCount();
894
900
  // Watcher status
895
- const watcher = (0, watcher_registry_1.getWatcherCoveringPath)(projectRoot);
901
+ const watcher = (0, watcher_store_1.getWatcherCoveringPath)(projectRoot);
896
902
  let watcherLine = "Watcher: not running";
897
903
  if (watcher) {
898
904
  const status = (_a = watcher.status) !== null && _a !== void 0 ? _a : "unknown";
@@ -1096,6 +1102,7 @@ exports.mcp = new commander_1.Command("mcp")
1096
1102
  }
1097
1103
  function handleRelatedFiles(args) {
1098
1104
  return __awaiter(this, void 0, void 0, function* () {
1105
+ ensureWatcher();
1099
1106
  const file = String(args.file || "");
1100
1107
  if (!file)
1101
1108
  return err("Missing required parameter: file");
@@ -1198,6 +1205,7 @@ exports.mcp = new commander_1.Command("mcp")
1198
1205
  function handleRecentChanges(args) {
1199
1206
  return __awaiter(this, void 0, void 0, function* () {
1200
1207
  var _a, e_1, _b, _c;
1208
+ ensureWatcher();
1201
1209
  const limit = Math.min(Math.max(Number(args.limit) || 20, 1), 50);
1202
1210
  const root = typeof args.root === "string"
1203
1211
  ? path.resolve(args.root)
@@ -52,7 +52,7 @@ const exit_1 = require("../lib/utils/exit");
52
52
  const project_marker_1 = require("../lib/utils/project-marker");
53
53
  const project_registry_1 = require("../lib/utils/project-registry");
54
54
  const project_root_1 = require("../lib/utils/project-root");
55
- const watcher_registry_1 = require("../lib/utils/watcher-registry");
55
+ const watcher_store_1 = require("../lib/utils/watcher-store");
56
56
  function confirm(message) {
57
57
  const rl = readline.createInterface({
58
58
  input: process.stdin,
@@ -99,7 +99,7 @@ Examples:
99
99
  }
100
100
  }
101
101
  // Stop any watcher
102
- const watcher = (0, watcher_registry_1.getWatcherForProject)(projectRoot);
102
+ const watcher = (0, watcher_store_1.getWatcherForProject)(projectRoot);
103
103
  if (watcher) {
104
104
  console.log(`Stopping watcher (PID: ${watcher.pid})...`);
105
105
  try {
@@ -107,11 +107,11 @@ Examples:
107
107
  }
108
108
  catch (_b) { }
109
109
  for (let i = 0; i < 50; i++) {
110
- if (!(0, watcher_registry_1.isProcessRunning)(watcher.pid))
110
+ if (!(0, watcher_store_1.isProcessRunning)(watcher.pid))
111
111
  break;
112
112
  yield new Promise((r) => setTimeout(r, 100));
113
113
  }
114
- (0, watcher_registry_1.unregisterWatcher)(watcher.pid);
114
+ (0, watcher_store_1.unregisterWatcher)(watcher.pid);
115
115
  }
116
116
  // Delete vectors from LanceDB
117
117
  const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
@@ -452,20 +452,18 @@ Examples:
452
452
  const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
453
453
  // Propagate project root to worker processes
454
454
  process.env.GMAX_PROJECT_ROOT = projectRoot;
455
- // Check if project is registered (skip for --sync which auto-indexes)
456
- if (!options.sync) {
457
- const checkRoot = options.root
458
- ? (_c = (0, project_root_1.findProjectRoot)(path.resolve(options.root))) !== null && _c !== void 0 ? _c : path.resolve(options.root)
459
- : projectRoot;
460
- const project = (0, project_registry_1.getProject)(checkRoot);
461
- if (!project) {
462
- console.error(`This project hasn't been added to gmax yet.\n\nRun: gmax add ${checkRoot}\n`);
463
- process.exitCode = 1;
464
- return;
465
- }
466
- if (project.status === "pending") {
467
- console.warn("This project is still being indexed. Results may be incomplete.\n");
468
- }
455
+ // Check if project is registered
456
+ const checkRoot = options.root
457
+ ? (_c = (0, project_root_1.findProjectRoot)(path.resolve(options.root))) !== null && _c !== void 0 ? _c : path.resolve(options.root)
458
+ : projectRoot;
459
+ const project = (0, project_registry_1.getProject)(checkRoot);
460
+ if (!project) {
461
+ console.error(`This project hasn't been added to gmax yet.\n\nRun: gmax add ${checkRoot}\n`);
462
+ process.exitCode = 1;
463
+ return;
464
+ }
465
+ if (project.status === "pending") {
466
+ console.warn("This project is still being indexed. Results may be incomplete.\n");
469
467
  }
470
468
  vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
471
469
  // Check for active indexing lock and warn if present
@@ -506,6 +504,20 @@ Examples:
506
504
  return;
507
505
  }
508
506
  yield vectorDb.createFTSIndex();
507
+ // Update registry after sync
508
+ const { readGlobalConfig } = yield Promise.resolve().then(() => __importStar(require("../lib/index/index-config")));
509
+ const { registerProject } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/project-registry")));
510
+ const gc = readGlobalConfig();
511
+ registerProject({
512
+ root: projectRoot,
513
+ name: path.basename(projectRoot),
514
+ vectorDim: gc.vectorDim,
515
+ modelTier: gc.modelTier,
516
+ embedMode: gc.embedMode,
517
+ lastIndexed: new Date().toISOString(),
518
+ chunkCount: result.indexed,
519
+ status: "indexed",
520
+ });
509
521
  const failedSuffix = result.failedFiles > 0 ? ` • ${result.failedFiles} failed` : "";
510
522
  spinner.succeed(`${options.sync ? "Indexing" : "Initial indexing"} complete (${result.processed}/${result.total}) • indexed ${result.indexed}${failedSuffix}`);
511
523
  }
@@ -516,16 +528,8 @@ Examples:
516
528
  }
517
529
  // Ensure a watcher is running for live reindexing
518
530
  if (!process.env.VITEST && !((_d = process.env.NODE_ENV) === null || _d === void 0 ? void 0 : _d.includes("test"))) {
519
- try {
520
- const { execFileSync } = yield Promise.resolve().then(() => __importStar(require("node:child_process")));
521
- execFileSync("gmax", ["watch", "-b", "--path", projectRoot], {
522
- timeout: 5000,
523
- stdio: "ignore",
524
- });
525
- }
526
- catch (_v) {
527
- // Watcher may already be running — ignore
528
- }
531
+ const { launchWatcher } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/watcher-launcher")));
532
+ launchWatcher(projectRoot);
529
533
  }
530
534
  const searcher = new searcher_1.Searcher(vectorDb);
531
535
  // Use --root or fall back to project root
@@ -566,7 +570,7 @@ Examples:
566
570
  return defs.some((d) => regex.test(d));
567
571
  });
568
572
  }
569
- catch (_w) {
573
+ catch (_v) {
570
574
  // Invalid regex — skip
571
575
  }
572
576
  }
@@ -670,7 +674,7 @@ Examples:
670
674
  }
671
675
  }
672
676
  }
673
- catch (_x) { }
677
+ catch (_w) { }
674
678
  }
675
679
  return;
676
680
  }
@@ -772,7 +776,7 @@ Examples:
772
776
  console.log(lines.join("\n"));
773
777
  }
774
778
  }
775
- catch (_y) {
779
+ catch (_x) {
776
780
  // Trace failed — skip silently
777
781
  }
778
782
  }
@@ -50,7 +50,7 @@ const exit_1 = require("../lib/utils/exit");
50
50
  const lock_1 = require("../lib/utils/lock");
51
51
  const project_registry_1 = require("../lib/utils/project-registry");
52
52
  const project_root_1 = require("../lib/utils/project-root");
53
- const watcher_registry_1 = require("../lib/utils/watcher-registry");
53
+ const watcher_store_1 = require("../lib/utils/watcher-store");
54
54
  const config_1 = require("../config");
55
55
  const style = {
56
56
  bold: (s) => `\x1b[1m${s}\x1b[22m`,
@@ -99,7 +99,7 @@ Examples:
99
99
  var _a;
100
100
  const globalConfig = (0, index_config_1.readGlobalConfig)();
101
101
  const projects = (0, project_registry_1.listProjects)();
102
- (0, watcher_registry_1.listWatchers)(); // cleans stale entries as side effect
102
+ (0, watcher_store_1.listWatchers)(); // cleans stale entries as side effect
103
103
  const indexing = (0, lock_1.isLocked)(config_1.PATHS.globalRoot);
104
104
  const currentRoot = (0, project_root_1.findProjectRoot)(process.cwd());
105
105
  // Header
@@ -114,7 +114,7 @@ Examples:
114
114
  console.log();
115
115
  for (const project of projects) {
116
116
  const isCurrent = project.root === currentRoot;
117
- const watcher = (0, watcher_registry_1.getWatcherForProject)(project.root);
117
+ const watcher = (0, watcher_store_1.getWatcherForProject)(project.root);
118
118
  // Status column
119
119
  let statusStr;
120
120
  const projectStatus = (_a = project.status) !== null && _a !== void 0 ? _a : "indexed";
@@ -52,7 +52,7 @@ const MODEL_PATH = path.join(MODEL_DIR, "model.onnx");
52
52
  const SKIPLIST_PATH = path.join(MODEL_DIR, "skiplist.json");
53
53
  function main() {
54
54
  return __awaiter(this, void 0, void 0, function* () {
55
- var _a, _b;
55
+ var _a, _b, _c;
56
56
  console.log("🔍 Starting ColBERT Integrity Check...\n");
57
57
  // --- CHECK 1: FILES EXIST ---
58
58
  if (!fs.existsSync(MODEL_PATH))
@@ -68,19 +68,19 @@ function main() {
68
68
  // Note: We use the ID we know works from your export: 50368
69
69
  // But let's see if the tokenizer resolves "[Q] " correctly.
70
70
  const encoded = yield tokenizer(queryText, { add_special_tokens: false });
71
- const inputIds = encoded.input_ids; // BigInt64Array in newer transformers versions
71
+ const inputIds = (_a = encoded.input_ids.data) !== null && _a !== void 0 ? _a : encoded.input_ids;
72
72
  // Convert to standard array for inspection
73
73
  const ids = Array.from(inputIds).map(Number);
74
74
  // Mixedbread expects: [CLS] [Q] ...tokens... [SEP]
75
75
  // Let's verify we can construct that.
76
76
  const Q_ID = 50368;
77
- const CLS_ID = (_a = tokenizer.model.tokens_to_ids.get("[CLS]")) !== null && _a !== void 0 ? _a : 50281; // Fallback to standard if null
77
+ const CLS_ID = (_b = tokenizer.convert_tokens_to_ids("[CLS]")) !== null && _b !== void 0 ? _b : 50281; // Fallback to standard if null
78
78
  console.log(`\n--- Tokenizer Check ---`);
79
79
  console.log(`Query: "${queryText}"`);
80
80
  console.log(`Raw IDs:`, ids);
81
81
  // Check if tokenizer recognizes the special tokens by text
82
- const qCheck = tokenizer.model.tokens_to_ids.get("[Q] ");
83
- const dCheck = tokenizer.model.tokens_to_ids.get("[D] ");
82
+ const qCheck = tokenizer.convert_tokens_to_ids("[Q] ");
83
+ const dCheck = tokenizer.convert_tokens_to_ids("[D] ");
84
84
  if (qCheck === 50368 && dCheck === 50369) {
85
85
  console.log(`✅ Tokenizer Map Correct: [Q] -> ${qCheck}, [D] -> ${dCheck}`);
86
86
  }
@@ -93,8 +93,8 @@ function main() {
93
93
  console.log(`\n--- Skiplist Check ---`);
94
94
  console.log(`Skiplist size: ${skiplist.size}`);
95
95
  // Check common punctuation
96
- const commaId = tokenizer.model.tokens_to_ids.get(",");
97
- const dotId = tokenizer.model.tokens_to_ids.get(".");
96
+ const commaId = tokenizer.convert_tokens_to_ids(",");
97
+ const dotId = tokenizer.convert_tokens_to_ids(".");
98
98
  if (skiplist.has(commaId) && skiplist.has(dotId)) {
99
99
  console.log(`✅ Skiplist contains punctuation ('.'=${dotId}, ','=${commaId})`);
100
100
  }
@@ -110,7 +110,7 @@ function main() {
110
110
  BigInt(CLS_ID),
111
111
  BigInt(Q_ID),
112
112
  BigInt(1234),
113
- BigInt((_b = tokenizer.sep_token_id) !== null && _b !== void 0 ? _b : 50282),
113
+ BigInt((_c = tokenizer.sep_token_id) !== null && _c !== void 0 ? _c : 50282),
114
114
  ];
115
115
  const tensorIds = new ort.Tensor("int64", new BigInt64Array(batchIds), [1, 4]);
116
116
  const tensorMask = new ort.Tensor("int64", new BigInt64Array([BigInt(1), BigInt(1), BigInt(1), BigInt(1)]), [1, 4]);
@@ -48,14 +48,16 @@ const fs = __importStar(require("node:fs"));
48
48
  const path = __importStar(require("node:path"));
49
49
  const commander_1 = require("commander");
50
50
  const config_1 = require("../config");
51
+ const index_config_1 = require("../lib/index/index-config");
51
52
  const filter_builder_1 = require("../lib/utils/filter-builder");
52
53
  const syncer_1 = require("../lib/index/syncer");
53
54
  const watcher_1 = require("../lib/index/watcher");
54
55
  const meta_cache_1 = require("../lib/store/meta-cache");
55
56
  const vector_db_1 = require("../lib/store/vector-db");
56
57
  const exit_1 = require("../lib/utils/exit");
58
+ const project_registry_1 = require("../lib/utils/project-registry");
57
59
  const project_root_1 = require("../lib/utils/project-root");
58
- const watcher_registry_1 = require("../lib/utils/watcher-registry");
60
+ const watcher_store_1 = require("../lib/utils/watcher-store");
59
61
  const IDLE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
60
62
  const IDLE_CHECK_INTERVAL_MS = 60 * 1000; // check every minute
61
63
  const MAX_LOG_BYTES = 5 * 1024 * 1024; // 5 MB — rotate log when exceeded
@@ -71,8 +73,8 @@ exports.watch = new commander_1.Command("watch")
71
73
  : (_a = (0, project_root_1.findProjectRoot)(process.cwd())) !== null && _a !== void 0 ? _a : process.cwd();
72
74
  const projectName = path.basename(projectRoot);
73
75
  // Check if watcher already running (exact match or parent covering this dir)
74
- const existing = (_b = (0, watcher_registry_1.getWatcherForProject)(projectRoot)) !== null && _b !== void 0 ? _b : (0, watcher_registry_1.getWatcherCoveringPath)(projectRoot);
75
- if (existing && (0, watcher_registry_1.isProcessRunning)(existing.pid)) {
76
+ const existing = (_b = (0, watcher_store_1.getWatcherForProject)(projectRoot)) !== null && _b !== void 0 ? _b : (0, watcher_store_1.getWatcherCoveringPath)(projectRoot);
77
+ if (existing && (0, watcher_store_1.isProcessRunning)(existing.pid)) {
76
78
  console.log(`Watcher already running for ${path.basename(existing.projectRoot)} (PID: ${existing.pid})`);
77
79
  return;
78
80
  }
@@ -106,12 +108,20 @@ exports.watch = new commander_1.Command("watch")
106
108
  process.exit(0);
107
109
  }
108
110
  // --- Foreground mode ---
111
+ // Migrate legacy watchers.json to LMDB on first use
112
+ (0, watcher_store_1.migrateFromJson)();
113
+ // Watcher requires project to be registered
114
+ if (!(0, project_registry_1.getProject)(projectRoot)) {
115
+ console.error(`[watch:${projectName}] Project not registered. Run: gmax add ${projectRoot}`);
116
+ process.exitCode = 1;
117
+ return;
118
+ }
109
119
  const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
110
120
  // Propagate project root to worker processes
111
121
  process.env.GMAX_PROJECT_ROOT = paths.root;
112
122
  console.log(`[watch:${projectName}] Starting...`);
113
123
  // Register early so MCP can see status
114
- (0, watcher_registry_1.registerWatcher)({
124
+ (0, watcher_store_1.registerWatcher)({
115
125
  pid: process.pid,
116
126
  projectRoot,
117
127
  startTime: Date.now(),
@@ -129,10 +139,22 @@ exports.watch = new commander_1.Command("watch")
129
139
  .toArray();
130
140
  if (indexed.length === 0) {
131
141
  console.log(`[watch:${projectName}] No index found for ${projectRoot}, running initial sync...`);
132
- yield (0, syncer_1.initialSync)({ projectRoot });
142
+ const syncResult = yield (0, syncer_1.initialSync)({ projectRoot });
143
+ // Update registry after sync
144
+ const globalConfig = (0, index_config_1.readGlobalConfig)();
145
+ (0, project_registry_1.registerProject)({
146
+ root: projectRoot,
147
+ name: projectName,
148
+ vectorDim: globalConfig.vectorDim,
149
+ modelTier: globalConfig.modelTier,
150
+ embedMode: globalConfig.embedMode,
151
+ lastIndexed: new Date().toISOString(),
152
+ chunkCount: syncResult.indexed,
153
+ status: "indexed",
154
+ });
133
155
  console.log(`[watch:${projectName}] Initial sync complete.`);
134
156
  }
135
- (0, watcher_registry_1.updateWatcherStatus)(process.pid, "watching");
157
+ (0, watcher_store_1.updateWatcherStatus)(process.pid, "watching");
136
158
  // Open resources for watcher
137
159
  const metaCache = new meta_cache_1.MetaCache(paths.lmdbPath);
138
160
  // Start watching
@@ -144,10 +166,14 @@ exports.watch = new commander_1.Command("watch")
144
166
  onReindex: (files, ms) => {
145
167
  console.log(`[watch:${projectName}] Reindexed ${files} file${files !== 1 ? "s" : ""} (${(ms / 1000).toFixed(1)}s)`);
146
168
  lastActivity = Date.now();
147
- (0, watcher_registry_1.updateWatcherStatus)(process.pid, "watching", Date.now());
169
+ (0, watcher_store_1.updateWatcherStatus)(process.pid, "watching", Date.now());
148
170
  },
149
171
  });
150
172
  console.log(`[watch:${projectName}] File watcher active`);
173
+ // Heartbeat — update LMDB every 60s so other processes can detect liveliness
174
+ const heartbeatInterval = setInterval(() => {
175
+ (0, watcher_store_1.heartbeat)(process.pid);
176
+ }, IDLE_CHECK_INTERVAL_MS);
151
177
  // Idle timeout
152
178
  let lastActivity = Date.now();
153
179
  if (options.idleTimeout !== false) {
@@ -161,13 +187,14 @@ exports.watch = new commander_1.Command("watch")
161
187
  // Graceful shutdown
162
188
  function shutdown() {
163
189
  return __awaiter(this, void 0, void 0, function* () {
190
+ clearInterval(heartbeatInterval);
164
191
  try {
165
192
  yield watcher.close();
166
193
  }
167
194
  catch (_a) { }
168
195
  yield metaCache.close();
169
196
  yield vectorDb.close();
170
- (0, watcher_registry_1.unregisterWatcher)(process.pid);
197
+ (0, watcher_store_1.unregisterWatcher)(process.pid);
171
198
  yield (0, exit_1.gracefulExit)();
172
199
  });
173
200
  }
@@ -179,7 +206,7 @@ exports.watch
179
206
  .command("status")
180
207
  .description("Show running watchers")
181
208
  .action(() => __awaiter(void 0, void 0, void 0, function* () {
182
- const watchers = (0, watcher_registry_1.listWatchers)();
209
+ const watchers = (0, watcher_store_1.listWatchers)();
183
210
  if (watchers.length === 0) {
184
211
  console.log("No running watchers.");
185
212
  yield (0, exit_1.gracefulExit)();
@@ -199,11 +226,11 @@ exports.watch
199
226
  .action((options) => __awaiter(void 0, void 0, void 0, function* () {
200
227
  var _a;
201
228
  if (options.all) {
202
- const watchers = (0, watcher_registry_1.listWatchers)();
229
+ const watchers = (0, watcher_store_1.listWatchers)();
203
230
  for (const w of watchers) {
204
231
  try {
205
232
  process.kill(w.pid, "SIGTERM");
206
- (0, watcher_registry_1.unregisterWatcher)(w.pid);
233
+ (0, watcher_store_1.unregisterWatcher)(w.pid);
207
234
  }
208
235
  catch (_b) { }
209
236
  }
@@ -212,7 +239,7 @@ exports.watch
212
239
  return;
213
240
  }
214
241
  const projectRoot = (_a = (0, project_root_1.findProjectRoot)(process.cwd())) !== null && _a !== void 0 ? _a : process.cwd();
215
- const watcher = (0, watcher_registry_1.getWatcherForProject)(projectRoot);
242
+ const watcher = (0, watcher_store_1.getWatcherForProject)(projectRoot);
216
243
  if (!watcher) {
217
244
  console.log("No watcher running for this project.");
218
245
  yield (0, exit_1.gracefulExit)();
@@ -220,12 +247,12 @@ exports.watch
220
247
  }
221
248
  try {
222
249
  process.kill(watcher.pid, "SIGTERM");
223
- (0, watcher_registry_1.unregisterWatcher)(watcher.pid);
250
+ (0, watcher_store_1.unregisterWatcher)(watcher.pid);
224
251
  console.log(`Stopped watcher (PID: ${watcher.pid})`);
225
252
  }
226
253
  catch (_c) {
227
254
  console.log("Watcher process not found.");
228
- (0, watcher_registry_1.unregisterWatcher)(watcher.pid);
255
+ (0, watcher_store_1.unregisterWatcher)(watcher.pid);
229
256
  }
230
257
  yield (0, exit_1.gracefulExit)();
231
258
  }));
@@ -61,7 +61,6 @@ const vector_db_1 = require("../store/vector-db");
61
61
  const filter_builder_1 = require("../utils/filter-builder");
62
62
  // isIndexableFile no longer used — extension check inlined for performance
63
63
  const lock_1 = require("../utils/lock");
64
- const project_registry_1 = require("../utils/project-registry");
65
64
  const project_root_1 = require("../utils/project-root");
66
65
  const pool_1 = require("../workers/pool");
67
66
  const index_config_1 = require("./index-config");
@@ -507,18 +506,6 @@ function initialSync(options) {
507
506
  // Write model config so future runs can detect model changes
508
507
  if (!dryRun) {
509
508
  (0, index_config_1.writeIndexConfig)(paths.configPath);
510
- // Register project in global registry
511
- const globalConfig = (0, index_config_1.readGlobalConfig)();
512
- (0, project_registry_1.registerProject)({
513
- root: paths.root,
514
- name: path.basename(paths.root),
515
- vectorDim: globalConfig.vectorDim,
516
- modelTier: globalConfig.modelTier,
517
- embedMode: globalConfig.embedMode,
518
- lastIndexed: new Date().toISOString(),
519
- chunkCount: indexed,
520
- status: "indexed",
521
- });
522
509
  }
523
510
  // Finalize total so callers can display a meaningful summary.
524
511
  total = processed;
@@ -43,6 +43,8 @@ exports.registerProject = registerProject;
43
43
  exports.listProjects = listProjects;
44
44
  exports.getProject = getProject;
45
45
  exports.removeProject = removeProject;
46
+ exports.getParentProject = getParentProject;
47
+ exports.getChildProjects = getChildProjects;
46
48
  const fs = __importStar(require("node:fs"));
47
49
  const path = __importStar(require("node:path"));
48
50
  const config_1 = require("../../config");
@@ -81,3 +83,17 @@ function removeProject(root) {
81
83
  const entries = loadRegistry().filter((e) => e.root !== root);
82
84
  saveRegistry(entries);
83
85
  }
86
+ /**
87
+ * Find a registered parent that covers this path, if any.
88
+ */
89
+ function getParentProject(root) {
90
+ const resolved = root.endsWith("/") ? root : `${root}/`;
91
+ return loadRegistry().find((e) => e.root !== root && resolved.startsWith(e.root.endsWith("/") ? e.root : `${e.root}/`));
92
+ }
93
+ /**
94
+ * Find registered projects that are children of this path.
95
+ */
96
+ function getChildProjects(root) {
97
+ const prefix = root.endsWith("/") ? root : `${root}/`;
98
+ return loadRegistry().filter((e) => e.root !== root && e.root.startsWith(prefix));
99
+ }
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ /**
3
+ * Centralized watcher launch logic.
4
+ * Single function that all code paths use to spawn a watcher.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.launchWatcher = launchWatcher;
8
+ const node_child_process_1 = require("node:child_process");
9
+ const project_registry_1 = require("./project-registry");
10
+ const watcher_store_1 = require("./watcher-store");
11
+ /**
12
+ * Launch a background watcher for a project.
13
+ *
14
+ * Returns { pid } on success, null if:
15
+ * - Project is not registered
16
+ * - Watcher is already running
17
+ * - Spawn fails
18
+ */
19
+ function launchWatcher(projectRoot) {
20
+ var _a;
21
+ // 1. Project must be registered
22
+ const project = (0, project_registry_1.getProject)(projectRoot);
23
+ if (!project) {
24
+ return null;
25
+ }
26
+ // 2. Check if watcher already running
27
+ const existing = (_a = (0, watcher_store_1.getWatcherForProject)(projectRoot)) !== null && _a !== void 0 ? _a : (0, watcher_store_1.getWatcherCoveringPath)(projectRoot);
28
+ if (existing && (0, watcher_store_1.isProcessRunning)(existing.pid)) {
29
+ return { pid: existing.pid };
30
+ }
31
+ // 3. Spawn
32
+ try {
33
+ const child = (0, node_child_process_1.spawn)(process.argv[0], [process.argv[1], "watch", "--path", projectRoot, "-b"], { detached: true, stdio: "ignore" });
34
+ child.unref();
35
+ if (child.pid) {
36
+ return { pid: child.pid };
37
+ }
38
+ console.error(`[watcher-launcher] Spawn returned no PID for ${projectRoot}`);
39
+ return null;
40
+ }
41
+ catch (err) {
42
+ const msg = err instanceof Error ? err.message : String(err);
43
+ console.error(`[watcher-launcher] Failed to start watcher for ${projectRoot}: ${msg}`);
44
+ return null;
45
+ }
46
+ }
@@ -0,0 +1,193 @@
1
+ "use strict";
2
+ /**
3
+ * LMDB-backed watcher registry — replaces the JSON-based watcher-registry.ts.
4
+ *
5
+ * Provides ACID transactions for watcher state, eliminating race conditions
6
+ * when multiple processes (Claude sessions, MCP, CLI) read/write concurrently.
7
+ *
8
+ * Stored in ~/.gmax/cache/watchers.lmdb
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.registerWatcher = registerWatcher;
45
+ exports.updateWatcherStatus = updateWatcherStatus;
46
+ exports.heartbeat = heartbeat;
47
+ exports.unregisterWatcher = unregisterWatcher;
48
+ exports.getWatcherForProject = getWatcherForProject;
49
+ exports.getWatcherCoveringPath = getWatcherCoveringPath;
50
+ exports.listWatchers = listWatchers;
51
+ exports.migrateFromJson = migrateFromJson;
52
+ exports.isProcessRunning = isProcessRunning;
53
+ const fs = __importStar(require("node:fs"));
54
+ const path = __importStar(require("node:path"));
55
+ const lmdb_1 = require("lmdb");
56
+ const config_1 = require("../../config");
57
+ const STORE_PATH = path.join(config_1.PATHS.cacheDir, "watchers.lmdb");
58
+ const HEARTBEAT_STALE_MS = 5 * 60 * 1000; // 5 minutes
59
+ let _db = null;
60
+ function getDb() {
61
+ if (!_db) {
62
+ fs.mkdirSync(path.dirname(STORE_PATH), { recursive: true });
63
+ _db = (0, lmdb_1.open)({
64
+ path: STORE_PATH,
65
+ compression: true,
66
+ });
67
+ }
68
+ return _db;
69
+ }
70
+ function isProcessRunning(pid) {
71
+ try {
72
+ process.kill(pid, 0);
73
+ return true;
74
+ }
75
+ catch (_a) {
76
+ return false;
77
+ }
78
+ }
79
+ function isAlive(info) {
80
+ if (!isProcessRunning(info.pid))
81
+ return false;
82
+ // If heartbeat exists and is stale, treat as dead (possibly deadlocked)
83
+ if (info.lastHeartbeat && Date.now() - info.lastHeartbeat > HEARTBEAT_STALE_MS) {
84
+ return false;
85
+ }
86
+ return true;
87
+ }
88
+ function registerWatcher(info) {
89
+ const db = getDb();
90
+ // Prune any existing dead entry for this project
91
+ const existing = db.get(info.projectRoot);
92
+ if (existing && !isAlive(existing)) {
93
+ db.remove(info.projectRoot);
94
+ }
95
+ db.put(info.projectRoot, Object.assign(Object.assign({}, info), { lastHeartbeat: Date.now() }));
96
+ }
97
+ function updateWatcherStatus(pid, status, lastReindex) {
98
+ const db = getDb();
99
+ // Find entry by PID (iterate since key is projectRoot)
100
+ for (const { key, value } of db.getRange()) {
101
+ if (value && value.pid === pid) {
102
+ db.put(String(key), Object.assign(Object.assign(Object.assign({}, value), { status, lastHeartbeat: Date.now() }), (lastReindex ? { lastReindex } : {})));
103
+ return;
104
+ }
105
+ }
106
+ }
107
+ function heartbeat(pid) {
108
+ const db = getDb();
109
+ for (const { key, value } of db.getRange()) {
110
+ if (value && value.pid === pid) {
111
+ db.put(String(key), Object.assign(Object.assign({}, value), { lastHeartbeat: Date.now() }));
112
+ return;
113
+ }
114
+ }
115
+ }
116
+ function unregisterWatcher(pid) {
117
+ const db = getDb();
118
+ for (const { key, value } of db.getRange()) {
119
+ if (value && value.pid === pid) {
120
+ db.remove(String(key));
121
+ return;
122
+ }
123
+ }
124
+ }
125
+ function getWatcherForProject(projectRoot) {
126
+ const db = getDb();
127
+ const info = db.get(projectRoot);
128
+ if (!info)
129
+ return undefined;
130
+ if (isAlive(info))
131
+ return info;
132
+ // Clean stale entry
133
+ db.remove(projectRoot);
134
+ return undefined;
135
+ }
136
+ function getWatcherCoveringPath(dir) {
137
+ const resolved = dir.endsWith("/") ? dir : `${dir}/`;
138
+ const db = getDb();
139
+ for (const { key, value } of db.getRange()) {
140
+ if (!value)
141
+ continue;
142
+ const root = String(key);
143
+ const prefix = root.endsWith("/") ? root : `${root}/`;
144
+ if (resolved.startsWith(prefix) && isAlive(value)) {
145
+ return value;
146
+ }
147
+ }
148
+ return undefined;
149
+ }
150
+ function listWatchers() {
151
+ const db = getDb();
152
+ const alive = [];
153
+ const dead = [];
154
+ for (const { key, value } of db.getRange()) {
155
+ if (!value)
156
+ continue;
157
+ if (isAlive(value)) {
158
+ alive.push(value);
159
+ }
160
+ else {
161
+ dead.push(String(key));
162
+ }
163
+ }
164
+ // Prune dead entries
165
+ for (const key of dead) {
166
+ db.remove(key);
167
+ }
168
+ return alive;
169
+ }
170
+ /**
171
+ * Migrate from legacy watchers.json if it exists.
172
+ * Call once on startup.
173
+ */
174
+ function migrateFromJson() {
175
+ const jsonPath = path.join(config_1.PATHS.globalRoot, "watchers.json");
176
+ if (!fs.existsSync(jsonPath))
177
+ return;
178
+ try {
179
+ const raw = fs.readFileSync(jsonPath, "utf-8");
180
+ const entries = JSON.parse(raw);
181
+ const db = getDb();
182
+ for (const entry of entries) {
183
+ if (entry.projectRoot && isProcessRunning(entry.pid)) {
184
+ db.put(entry.projectRoot, Object.assign(Object.assign({}, entry), { lastHeartbeat: Date.now() }));
185
+ }
186
+ }
187
+ // Remove legacy file
188
+ fs.unlinkSync(jsonPath);
189
+ }
190
+ catch (_a) {
191
+ // Best effort — ignore
192
+ }
193
+ }
@@ -23,20 +23,22 @@ class ColBERTTokenizer {
23
23
  }
24
24
  init(modelPath) {
25
25
  return __awaiter(this, void 0, void 0, function* () {
26
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
26
+ var _a, _b, _c, _d, _e, _f;
27
27
  this.tokenizer = yield transformers_1.AutoTokenizer.from_pretrained(modelPath);
28
28
  // Get special token IDs with fallbacks
29
29
  // We use the IDs we discovered in validation: [Q]=50368, [D]=50369
30
30
  // But we still try to look them up dynamically first.
31
31
  const tokenizer = this.tokenizer;
32
- const get = (token) => tokenizer === null || tokenizer === void 0 ? void 0 : tokenizer.model.tokens_to_ids.get(token);
33
- const specialTokens = tokenizer;
34
- const clsId = (_b = get((_a = specialTokens.cls_token) !== null && _a !== void 0 ? _a : "[CLS]")) !== null && _b !== void 0 ? _b : 50281;
35
- const sepId = (_d = get((_c = specialTokens.sep_token) !== null && _c !== void 0 ? _c : "[SEP]")) !== null && _d !== void 0 ? _d : 50282;
36
- const padId = (_f = get((_e = specialTokens.pad_token) !== null && _e !== void 0 ? _e : "[PAD]")) !== null && _f !== void 0 ? _f : 50283;
37
- const maskId = (_g = get(MASK_TOKEN)) !== null && _g !== void 0 ? _g : 50284;
38
- const queryMarkerId = (_h = get(QUERY_MARKER_TOKEN)) !== null && _h !== void 0 ? _h : 50368;
39
- const docMarkerId = (_j = get(DOC_MARKER_TOKEN)) !== null && _j !== void 0 ? _j : 50369;
32
+ const get = (token) => {
33
+ const id = tokenizer === null || tokenizer === void 0 ? void 0 : tokenizer.convert_tokens_to_ids(token);
34
+ return typeof id === "number" && id >= 0 ? id : undefined;
35
+ };
36
+ const clsId = (_a = get("[CLS]")) !== null && _a !== void 0 ? _a : 50281;
37
+ const sepId = (_b = get("[SEP]")) !== null && _b !== void 0 ? _b : 50282;
38
+ const padId = (_c = get("[PAD]")) !== null && _c !== void 0 ? _c : 50283;
39
+ const maskId = (_d = get(MASK_TOKEN)) !== null && _d !== void 0 ? _d : 50284;
40
+ const queryMarkerId = (_e = get(QUERY_MARKER_TOKEN)) !== null && _e !== void 0 ? _e : 50368;
41
+ const docMarkerId = (_f = get(DOC_MARKER_TOKEN)) !== null && _f !== void 0 ? _f : 50369;
40
42
  this.specialTokenIds = {
41
43
  cls: clsId,
42
44
  sep: sepId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.7.44",
3
+ "version": "0.8.1",
4
4
  "author": "Robert Owens <robowens@me.com>",
5
5
  "homepage": "https://github.com/reowens/grepmax",
6
6
  "bugs": {
@@ -33,9 +33,9 @@
33
33
  "description": "Semantic code search for coding agents. Local embeddings, LLM summaries, call graph tracing.",
34
34
  "dependencies": {
35
35
  "@clack/prompts": "^1.1.0",
36
- "@huggingface/transformers": "^3.8.0",
37
- "@lancedb/lancedb": "^0.26.2",
38
- "@modelcontextprotocol/sdk": "^1.24.3",
36
+ "@huggingface/transformers": "^4.0.0",
37
+ "@lancedb/lancedb": "^0.27.1",
38
+ "@modelcontextprotocol/sdk": "^1.29.0",
39
39
  "apache-arrow": "^18.1.0",
40
40
  "chalk": "^5.6.2",
41
41
  "chokidar": "^5.0.0",
@@ -44,23 +44,24 @@
44
44
  "dotenv": "^17.2.3",
45
45
  "fast-glob": "^3.3.3",
46
46
  "ignore": "^7.0.5",
47
- "lmdb": "^3.4.4",
47
+ "lmdb": "^3.5.2",
48
48
  "onnxruntime-node": "1.24.3",
49
- "ora": "^5.4.1",
49
+ "ora": "^9.3.0",
50
50
  "piscina": "^5.1.4",
51
51
  "simsimd": "^6.5.5",
52
52
  "uuid": "^13.0.0",
53
- "web-tree-sitter": "^0.26.6",
53
+ "web-tree-sitter": "^0.26.7",
54
54
  "zod": "^4.1.12"
55
55
  },
56
56
  "devDependencies": {
57
- "@anthropic-ai/claude-agent-sdk": "^0.2.76",
58
- "@biomejs/biome": "2.4.7",
57
+ "@anthropic-ai/claude-agent-sdk": "^0.2.87",
58
+ "@biomejs/biome": "2.4.10",
59
59
  "@types/node": "^25.5.0",
60
60
  "node-gyp": "^12.1.0",
61
61
  "ts-node": "^10.9.2",
62
- "typescript": "^5.9.3",
63
- "vitest": "^1.6.1"
62
+ "typescript": "^6.0.2",
63
+ "vite": "^8.0.3",
64
+ "vitest": "^4.1.2"
64
65
  },
65
66
  "scripts": {
66
67
  "postinstall": "node scripts/postinstall.js",
@@ -80,6 +81,6 @@
80
81
  "typecheck": "tsc --noEmit",
81
82
  "preversion": "pnpm test && pnpm typecheck",
82
83
  "version": "bash scripts/sync-versions.sh && git add -A",
83
- "postversion": "git push origin main --tags && gh release create v$npm_package_version --generate-notes --title v$npm_package_version && sleep 5 && gh run watch $(gh run list --workflow=release.yml --limit 1 --json databaseId --jq '.[0].databaseId') --exit-status && sleep 30 && npm cache clean --force && npm install -g grepmax@$npm_package_version"
84
+ "postversion": "git push origin main && git push origin v$npm_package_version && gh release create v$npm_package_version --generate-notes --title v$npm_package_version && sleep 5 && gh run watch $(gh run list --workflow=release.yml --branch v$npm_package_version --limit 1 --json databaseId --jq '.[0].databaseId') --exit-status && sleep 30 && npm cache clean --force && npm install -g grepmax@$npm_package_version"
84
85
  }
85
86
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.7.44",
3
+ "version": "0.8.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",
@@ -45,7 +45,18 @@ function findMlxServerDir() {
45
45
  function startPythonServer(serverDir, scriptName, logName) {
46
46
  if (!serverDir) return;
47
47
 
48
- const logPath = `/tmp/${logName}.log`;
48
+ const logDir = _path.join(require("node:os").homedir(), ".gmax", "logs");
49
+ fs.mkdirSync(logDir, { recursive: true });
50
+ const logPath = _path.join(logDir, `${logName}.log`);
51
+
52
+ // Rotate if > 5MB (same threshold as watch.ts)
53
+ try {
54
+ const stat = fs.statSync(logPath);
55
+ if (stat.size > 5 * 1024 * 1024) {
56
+ fs.renameSync(logPath, `${logPath}.prev`);
57
+ }
58
+ } catch {}
59
+
49
60
  const out = fs.openSync(logPath, "a");
50
61
 
51
62
  const child = spawn("uv", ["run", "python", scriptName], {
@@ -57,7 +68,23 @@ function startPythonServer(serverDir, scriptName, logName) {
57
68
  child.unref();
58
69
  }
59
70
 
71
+ function isProjectRegistered() {
72
+ try {
73
+ const projectsPath = _path.join(
74
+ require("node:os").homedir(),
75
+ ".gmax",
76
+ "projects.json",
77
+ );
78
+ const projects = JSON.parse(require("node:fs").readFileSync(projectsPath, "utf-8"));
79
+ const cwd = process.cwd();
80
+ return projects.some((p) => cwd.startsWith(p.root));
81
+ } catch {
82
+ return false;
83
+ }
84
+ }
85
+
60
86
  function startWatcher() {
87
+ if (!isProjectRegistered()) return;
61
88
  try {
62
89
  execFileSync("gmax", ["watch", "-b"], { timeout: 5000, stdio: "ignore" });
63
90
  } catch {
@@ -24,16 +24,17 @@ Bash(gmax "auth handler" --role ORCHESTRATION --lang ts --agent -m 3)
24
24
 
25
25
  ## Project management
26
26
 
27
- Projects must be added before they can be searched:
27
+ Projects must be added before CLI search works. MCP tools auto-add on first use, but CLI requires an explicit step:
28
28
 
29
29
  ```
30
- gmax add # add current directory to the index
30
+ gmax add # add + index current directory
31
31
  gmax add ~/projects/myapp # add a specific project
32
32
  gmax status # see all indexed projects and their state
33
33
  gmax remove # remove current project from the index
34
+ gmax index # reindex an already-added project
34
35
  ```
35
36
 
36
- If search returns "This project hasn't been added to gmax yet", run `gmax add` first.
37
+ If search returns "This project hasn't been added to gmax yet", run `Bash(gmax add)` first.
37
38
 
38
39
  ## CLI commands
39
40