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.
- package/dist/commands/add.js +36 -14
- package/dist/commands/index.js +28 -12
- package/dist/commands/mcp.js +14 -6
- package/dist/commands/remove.js +4 -4
- package/dist/commands/search.js +31 -27
- package/dist/commands/status.js +3 -3
- package/dist/commands/verify.js +8 -8
- package/dist/commands/watch.js +41 -14
- package/dist/lib/index/syncer.js +0 -13
- package/dist/lib/utils/project-registry.js +16 -0
- package/dist/lib/utils/watcher-launcher.js +46 -0
- package/dist/lib/utils/watcher-store.js +193 -0
- package/dist/lib/workers/colbert-tokenizer.js +11 -9
- package/package.json +13 -12
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
- package/plugins/grepmax/hooks/start.js +28 -1
- package/plugins/grepmax/skills/grepmax/SKILL.md +4 -3
package/dist/commands/add.js
CHANGED
|
@@ -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
|
|
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
|
|
79
|
-
console.log(`${projectName} is already added (${(_b = existing
|
|
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
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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 (
|
|
172
|
+
catch (_c) { }
|
|
151
173
|
}
|
|
152
174
|
yield (0, exit_1.gracefulExit)();
|
|
153
175
|
}
|
package/dist/commands/index.js
CHANGED
|
@@ -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
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
}
|
package/dist/commands/mcp.js
CHANGED
|
@@ -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
|
|
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
|
|
335
|
-
const child = (0, node_child_process_1.spawn)(process.argv[0], [process.argv[1], "
|
|
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
|
|
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
|
-
|
|
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,
|
|
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)
|
package/dist/commands/remove.js
CHANGED
|
@@ -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
|
|
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,
|
|
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,
|
|
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,
|
|
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);
|
package/dist/commands/search.js
CHANGED
|
@@ -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
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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
|
-
|
|
520
|
-
|
|
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 (
|
|
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 (
|
|
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 (
|
|
779
|
+
catch (_x) {
|
|
776
780
|
// Trace failed — skip silently
|
|
777
781
|
}
|
|
778
782
|
}
|
package/dist/commands/status.js
CHANGED
|
@@ -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
|
|
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,
|
|
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,
|
|
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";
|
package/dist/commands/verify.js
CHANGED
|
@@ -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
|
|
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 = (
|
|
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.
|
|
83
|
-
const dCheck = tokenizer.
|
|
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.
|
|
97
|
-
const dotId = tokenizer.
|
|
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((
|
|
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]);
|
package/dist/commands/watch.js
CHANGED
|
@@ -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
|
|
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,
|
|
75
|
-
if (existing && (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
255
|
+
(0, watcher_store_1.unregisterWatcher)(watcher.pid);
|
|
229
256
|
}
|
|
230
257
|
yield (0, exit_1.gracefulExit)();
|
|
231
258
|
}));
|
package/dist/lib/index/syncer.js
CHANGED
|
@@ -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
|
|
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) =>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
const
|
|
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.
|
|
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": "^
|
|
37
|
-
"@lancedb/lancedb": "^0.
|
|
38
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
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.
|
|
47
|
+
"lmdb": "^3.5.2",
|
|
48
48
|
"onnxruntime-node": "1.24.3",
|
|
49
|
-
"ora": "^
|
|
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.
|
|
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.
|
|
58
|
-
"@biomejs/biome": "2.4.
|
|
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": "^
|
|
63
|
-
"
|
|
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
|
|
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
|
}
|
|
@@ -45,7 +45,18 @@ function findMlxServerDir() {
|
|
|
45
45
|
function startPythonServer(serverDir, scriptName, logName) {
|
|
46
46
|
if (!serverDir) return;
|
|
47
47
|
|
|
48
|
-
const
|
|
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
|
|
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
|
|
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
|
|