grepmax 0.8.0 → 0.8.2
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 +38 -13
- package/dist/commands/droid.js +4 -1
- package/dist/commands/index.js +30 -11
- package/dist/commands/mcp.js +15 -13
- package/dist/commands/remove.js +7 -14
- package/dist/commands/search.js +33 -26
- package/dist/commands/serve.js +5 -7
- package/dist/commands/status.js +3 -3
- package/dist/commands/watch.js +51 -36
- package/dist/config.js +1 -0
- package/dist/lib/index/syncer.js +0 -13
- package/dist/lib/index/watcher.js +8 -0
- package/dist/lib/utils/log-rotate.js +55 -0
- package/dist/lib/utils/process.js +45 -0
- package/dist/lib/utils/project-registry.js +16 -0
- package/dist/lib/utils/watcher-launcher.js +48 -0
- package/dist/lib/utils/watcher-store.js +193 -0
- package/package.json +1 -1
- 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,13 @@ 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})`);
|
|
156
|
+
// Start watcher
|
|
157
|
+
const launched = (0, watcher_launcher_1.launchWatcher)(projectRoot);
|
|
158
|
+
if (launched.ok) {
|
|
159
|
+
console.log(`Watcher started (PID: ${launched.pid})`);
|
|
135
160
|
}
|
|
136
|
-
|
|
137
|
-
console.
|
|
161
|
+
else if (launched.reason === "spawn-failed") {
|
|
162
|
+
console.warn(`[add] ${launched.message}`);
|
|
138
163
|
}
|
|
139
164
|
}
|
|
140
165
|
catch (error) {
|
|
@@ -147,7 +172,7 @@ Examples:
|
|
|
147
172
|
try {
|
|
148
173
|
yield vectorDb.close();
|
|
149
174
|
}
|
|
150
|
-
catch (
|
|
175
|
+
catch (_c) { }
|
|
151
176
|
}
|
|
152
177
|
yield (0, exit_1.gracefulExit)();
|
|
153
178
|
}
|
package/dist/commands/droid.js
CHANGED
|
@@ -130,7 +130,10 @@ function installPlugin() {
|
|
|
130
130
|
const startScript = `
|
|
131
131
|
const { spawn } = require("child_process");
|
|
132
132
|
const fs = require("fs");
|
|
133
|
-
const
|
|
133
|
+
const path = require("path");
|
|
134
|
+
const logDir = path.join(require("os").homedir(), ".gmax", "logs");
|
|
135
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
136
|
+
const out = fs.openSync(path.join(logDir, "gmax.log"), "a");
|
|
134
137
|
const child = spawn("gmax", ["serve"], { detached: true, stdio: ["ignore", out, out] });
|
|
135
138
|
child.unref();
|
|
136
139
|
`;
|
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,12 @@ 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})`);
|
|
171
|
+
const launched = (0, watcher_launcher_1.launchWatcher)(restartWatcher.projectRoot);
|
|
172
|
+
if (launched.ok) {
|
|
173
|
+
console.log(`Restarted watcher for ${path.basename(restartWatcher.projectRoot)} (PID: ${launched.pid})`);
|
|
155
174
|
}
|
|
156
|
-
|
|
157
|
-
console.
|
|
175
|
+
else if (launched.reason === "spawn-failed") {
|
|
176
|
+
console.warn(`[index] ${launched.message}`);
|
|
158
177
|
}
|
|
159
178
|
}
|
|
160
179
|
}
|
package/dist/commands/mcp.js
CHANGED
|
@@ -77,7 +77,8 @@ 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_launcher_1 = require("../lib/utils/watcher-launcher");
|
|
81
|
+
const watcher_store_1 = require("../lib/utils/watcher-store");
|
|
81
82
|
// ---------------------------------------------------------------------------
|
|
82
83
|
// Tool definitions
|
|
83
84
|
// ---------------------------------------------------------------------------
|
|
@@ -331,8 +332,8 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
331
332
|
return;
|
|
332
333
|
_indexing = true;
|
|
333
334
|
_indexProgress = "starting...";
|
|
334
|
-
console.log("[MCP] First-time
|
|
335
|
-
const child = (0, node_child_process_1.spawn)(process.argv[0], [process.argv[1], "
|
|
335
|
+
console.log("[MCP] First-time setup for this project...");
|
|
336
|
+
const child = (0, node_child_process_1.spawn)(process.argv[0], [process.argv[1], "add", projectRoot], { detached: true, stdio: "ignore" });
|
|
336
337
|
_indexChildPid = (_a = child.pid) !== null && _a !== void 0 ? _a : null;
|
|
337
338
|
child.unref();
|
|
338
339
|
_indexProgress = `PID ${_indexChildPid}`;
|
|
@@ -342,7 +343,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
342
343
|
_indexChildPid = null;
|
|
343
344
|
if (code === 0) {
|
|
344
345
|
_indexReady = true;
|
|
345
|
-
console.log("[MCP] First-time
|
|
346
|
+
console.log("[MCP] First-time setup complete.");
|
|
346
347
|
}
|
|
347
348
|
else {
|
|
348
349
|
console.error(`[MCP] Indexing failed (exit code: ${code})`);
|
|
@@ -352,14 +353,10 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
352
353
|
}
|
|
353
354
|
// --- Background watcher ---
|
|
354
355
|
function ensureWatcher() {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
stdio: "ignore",
|
|
360
|
-
});
|
|
361
|
-
child.unref();
|
|
362
|
-
console.log(`[MCP] Started background watcher for ${projectRoot}`);
|
|
356
|
+
const result = (0, watcher_launcher_1.launchWatcher)(projectRoot);
|
|
357
|
+
if (result.ok && !result.reused) {
|
|
358
|
+
console.log(`[MCP] Started background watcher for ${projectRoot} (PID: ${result.pid})`);
|
|
359
|
+
}
|
|
363
360
|
}
|
|
364
361
|
// --- Tool handlers ---
|
|
365
362
|
function handleSemanticSearch(args_1) {
|
|
@@ -611,6 +608,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
611
608
|
}
|
|
612
609
|
function handleCodeSkeleton(args) {
|
|
613
610
|
return __awaiter(this, void 0, void 0, function* () {
|
|
611
|
+
ensureWatcher();
|
|
614
612
|
const target = String(args.target || "");
|
|
615
613
|
if (!target)
|
|
616
614
|
return err("Missing required parameter: target");
|
|
@@ -725,6 +723,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
725
723
|
}
|
|
726
724
|
function handleTraceCalls(args) {
|
|
727
725
|
return __awaiter(this, void 0, void 0, function* () {
|
|
726
|
+
ensureWatcher();
|
|
728
727
|
const symbol = String(args.symbol || "");
|
|
729
728
|
if (!symbol)
|
|
730
729
|
return err("Missing required parameter: symbol");
|
|
@@ -806,6 +805,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
806
805
|
}
|
|
807
806
|
function handleListSymbols(args) {
|
|
808
807
|
return __awaiter(this, void 0, void 0, function* () {
|
|
808
|
+
ensureWatcher();
|
|
809
809
|
const pattern = typeof args.pattern === "string" ? args.pattern : undefined;
|
|
810
810
|
const limit = Math.min(Math.max(Number(args.limit) || 20, 1), 100);
|
|
811
811
|
const pathPrefix = typeof args.path === "string" ? args.path : undefined;
|
|
@@ -892,7 +892,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
892
892
|
const stats = yield db.getStats();
|
|
893
893
|
const fileCount = yield db.getDistinctFileCount();
|
|
894
894
|
// Watcher status
|
|
895
|
-
const watcher = (0,
|
|
895
|
+
const watcher = (0, watcher_store_1.getWatcherCoveringPath)(projectRoot);
|
|
896
896
|
let watcherLine = "Watcher: not running";
|
|
897
897
|
if (watcher) {
|
|
898
898
|
const status = (_a = watcher.status) !== null && _a !== void 0 ? _a : "unknown";
|
|
@@ -1096,6 +1096,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1096
1096
|
}
|
|
1097
1097
|
function handleRelatedFiles(args) {
|
|
1098
1098
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1099
|
+
ensureWatcher();
|
|
1099
1100
|
const file = String(args.file || "");
|
|
1100
1101
|
if (!file)
|
|
1101
1102
|
return err("Missing required parameter: file");
|
|
@@ -1198,6 +1199,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1198
1199
|
function handleRecentChanges(args) {
|
|
1199
1200
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1200
1201
|
var _a, e_1, _b, _c;
|
|
1202
|
+
ensureWatcher();
|
|
1201
1203
|
const limit = Math.min(Math.max(Number(args.limit) || 20, 1), 50);
|
|
1202
1204
|
const root = typeof args.root === "string"
|
|
1203
1205
|
? path.resolve(args.root)
|
package/dist/commands/remove.js
CHANGED
|
@@ -49,10 +49,11 @@ const commander_1 = require("commander");
|
|
|
49
49
|
const meta_cache_1 = require("../lib/store/meta-cache");
|
|
50
50
|
const vector_db_1 = require("../lib/store/vector-db");
|
|
51
51
|
const exit_1 = require("../lib/utils/exit");
|
|
52
|
+
const process_1 = require("../lib/utils/process");
|
|
52
53
|
const project_marker_1 = require("../lib/utils/project-marker");
|
|
53
54
|
const project_registry_1 = require("../lib/utils/project-registry");
|
|
54
55
|
const project_root_1 = require("../lib/utils/project-root");
|
|
55
|
-
const
|
|
56
|
+
const watcher_store_1 = require("../lib/utils/watcher-store");
|
|
56
57
|
function confirm(message) {
|
|
57
58
|
const rl = readline.createInterface({
|
|
58
59
|
input: process.stdin,
|
|
@@ -99,19 +100,11 @@ Examples:
|
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
// Stop any watcher
|
|
102
|
-
const watcher = (0,
|
|
103
|
+
const watcher = (0, watcher_store_1.getWatcherForProject)(projectRoot);
|
|
103
104
|
if (watcher) {
|
|
104
105
|
console.log(`Stopping watcher (PID: ${watcher.pid})...`);
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
catch (_b) { }
|
|
109
|
-
for (let i = 0; i < 50; i++) {
|
|
110
|
-
if (!(0, watcher_registry_1.isProcessRunning)(watcher.pid))
|
|
111
|
-
break;
|
|
112
|
-
yield new Promise((r) => setTimeout(r, 100));
|
|
113
|
-
}
|
|
114
|
-
(0, watcher_registry_1.unregisterWatcher)(watcher.pid);
|
|
106
|
+
yield (0, process_1.killProcess)(watcher.pid);
|
|
107
|
+
(0, watcher_store_1.unregisterWatcher)(watcher.pid);
|
|
115
108
|
}
|
|
116
109
|
// Delete vectors from LanceDB
|
|
117
110
|
const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
|
|
@@ -139,13 +132,13 @@ Examples:
|
|
|
139
132
|
try {
|
|
140
133
|
metaCache.close();
|
|
141
134
|
}
|
|
142
|
-
catch (
|
|
135
|
+
catch (_b) { }
|
|
143
136
|
}
|
|
144
137
|
if (vectorDb) {
|
|
145
138
|
try {
|
|
146
139
|
yield vectorDb.close();
|
|
147
140
|
}
|
|
148
|
-
catch (
|
|
141
|
+
catch (_c) { }
|
|
149
142
|
}
|
|
150
143
|
yield (0, exit_1.gracefulExit)();
|
|
151
144
|
}
|
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,15 +528,10 @@ 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
|
-
|
|
522
|
-
|
|
523
|
-
stdio: "ignore",
|
|
524
|
-
});
|
|
525
|
-
}
|
|
526
|
-
catch (_v) {
|
|
527
|
-
// Watcher may already be running — ignore
|
|
531
|
+
const { launchWatcher } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/watcher-launcher")));
|
|
532
|
+
const launched = launchWatcher(projectRoot);
|
|
533
|
+
if (!launched.ok && launched.reason === "spawn-failed") {
|
|
534
|
+
console.warn(`[search] ${launched.message}`);
|
|
528
535
|
}
|
|
529
536
|
}
|
|
530
537
|
const searcher = new searcher_1.Searcher(vectorDb);
|
|
@@ -566,7 +573,7 @@ Examples:
|
|
|
566
573
|
return defs.some((d) => regex.test(d));
|
|
567
574
|
});
|
|
568
575
|
}
|
|
569
|
-
catch (
|
|
576
|
+
catch (_v) {
|
|
570
577
|
// Invalid regex — skip
|
|
571
578
|
}
|
|
572
579
|
}
|
|
@@ -670,7 +677,7 @@ Examples:
|
|
|
670
677
|
}
|
|
671
678
|
}
|
|
672
679
|
}
|
|
673
|
-
catch (
|
|
680
|
+
catch (_w) { }
|
|
674
681
|
}
|
|
675
682
|
return;
|
|
676
683
|
}
|
|
@@ -772,7 +779,7 @@ Examples:
|
|
|
772
779
|
console.log(lines.join("\n"));
|
|
773
780
|
}
|
|
774
781
|
}
|
|
775
|
-
catch (
|
|
782
|
+
catch (_x) {
|
|
776
783
|
// Trace failed — skip silently
|
|
777
784
|
}
|
|
778
785
|
}
|
package/dist/commands/serve.js
CHANGED
|
@@ -59,6 +59,7 @@ const setup_helpers_1 = require("../lib/setup/setup-helpers");
|
|
|
59
59
|
const meta_cache_1 = require("../lib/store/meta-cache");
|
|
60
60
|
const vector_db_1 = require("../lib/store/vector-db");
|
|
61
61
|
const exit_1 = require("../lib/utils/exit");
|
|
62
|
+
const log_rotate_1 = require("../lib/utils/log-rotate");
|
|
62
63
|
const project_root_1 = require("../lib/utils/project-root");
|
|
63
64
|
const server_registry_1 = require("../lib/utils/server-registry");
|
|
64
65
|
function isMlxServerUp() {
|
|
@@ -84,8 +85,7 @@ function startMlxServer(mlxModel) {
|
|
|
84
85
|
const serverDir = candidates.find((d) => fs.existsSync(path.join(d, "server.py")));
|
|
85
86
|
if (!serverDir)
|
|
86
87
|
return null;
|
|
87
|
-
const
|
|
88
|
-
const out = fs.openSync(logPath, "a");
|
|
88
|
+
const out = (0, log_rotate_1.openRotatedLog)(path.join(config_1.PATHS.logsDir, "mlx-embed-server.log"));
|
|
89
89
|
const env = Object.assign({}, process.env);
|
|
90
90
|
if (mlxModel) {
|
|
91
91
|
env.MLX_EMBED_MODEL = mlxModel;
|
|
@@ -121,14 +121,12 @@ exports.serve = new commander_1.Command("serve")
|
|
|
121
121
|
const args = process.argv
|
|
122
122
|
.slice(2)
|
|
123
123
|
.filter((arg) => arg !== "-b" && arg !== "--background");
|
|
124
|
-
const logDir = path.join(config_1.PATHS.globalRoot, "logs");
|
|
125
|
-
fs.mkdirSync(logDir, { recursive: true });
|
|
126
124
|
const safeName = path
|
|
127
125
|
.basename(projectRoot)
|
|
128
126
|
.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
129
|
-
const logFile = path.join(
|
|
130
|
-
const out =
|
|
131
|
-
const err =
|
|
127
|
+
const logFile = path.join(config_1.PATHS.logsDir, `server-${safeName}.log`);
|
|
128
|
+
const out = (0, log_rotate_1.openRotatedLog)(logFile);
|
|
129
|
+
const err = (0, log_rotate_1.openRotatedLog)(logFile);
|
|
132
130
|
const child = (0, node_child_process_1.spawn)(process.argv[0], [process.argv[1], ...args], {
|
|
133
131
|
detached: true,
|
|
134
132
|
stdio: ["ignore", out, err],
|
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/watch.js
CHANGED
|
@@ -44,21 +44,23 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
45
|
exports.watch = void 0;
|
|
46
46
|
const node_child_process_1 = require("node:child_process");
|
|
47
|
-
const fs = __importStar(require("node:fs"));
|
|
48
47
|
const path = __importStar(require("node:path"));
|
|
49
48
|
const commander_1 = require("commander");
|
|
50
49
|
const config_1 = require("../config");
|
|
50
|
+
const index_config_1 = require("../lib/index/index-config");
|
|
51
51
|
const filter_builder_1 = require("../lib/utils/filter-builder");
|
|
52
52
|
const syncer_1 = require("../lib/index/syncer");
|
|
53
53
|
const watcher_1 = require("../lib/index/watcher");
|
|
54
54
|
const meta_cache_1 = require("../lib/store/meta-cache");
|
|
55
55
|
const vector_db_1 = require("../lib/store/vector-db");
|
|
56
56
|
const exit_1 = require("../lib/utils/exit");
|
|
57
|
+
const log_rotate_1 = require("../lib/utils/log-rotate");
|
|
58
|
+
const process_1 = require("../lib/utils/process");
|
|
59
|
+
const project_registry_1 = require("../lib/utils/project-registry");
|
|
57
60
|
const project_root_1 = require("../lib/utils/project-root");
|
|
58
|
-
const
|
|
61
|
+
const watcher_store_1 = require("../lib/utils/watcher-store");
|
|
59
62
|
const IDLE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
|
60
63
|
const IDLE_CHECK_INTERVAL_MS = 60 * 1000; // check every minute
|
|
61
|
-
const MAX_LOG_BYTES = 5 * 1024 * 1024; // 5 MB — rotate log when exceeded
|
|
62
64
|
exports.watch = new commander_1.Command("watch")
|
|
63
65
|
.description("Start background file watcher for live reindexing")
|
|
64
66
|
.option("-b, --background", "Run watcher in background and exit")
|
|
@@ -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
|
}
|
|
@@ -81,20 +83,9 @@ exports.watch = new commander_1.Command("watch")
|
|
|
81
83
|
const args = process.argv
|
|
82
84
|
.slice(2)
|
|
83
85
|
.filter((arg) => arg !== "-b" && arg !== "--background");
|
|
84
|
-
const logDir = path.join(config_1.PATHS.globalRoot, "logs");
|
|
85
|
-
fs.mkdirSync(logDir, { recursive: true });
|
|
86
86
|
const safeName = projectName.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
87
|
-
const logFile = path.join(
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
const logStat = fs.statSync(logFile);
|
|
91
|
-
if (logStat.size > MAX_LOG_BYTES) {
|
|
92
|
-
const prev = `${logFile}.prev`;
|
|
93
|
-
fs.renameSync(logFile, prev);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
catch (_c) { }
|
|
97
|
-
const out = fs.openSync(logFile, "a");
|
|
87
|
+
const logFile = path.join(config_1.PATHS.logsDir, `watch-${safeName}.log`);
|
|
88
|
+
const out = (0, log_rotate_1.openRotatedLog)(logFile);
|
|
98
89
|
const child = (0, node_child_process_1.spawn)(process.argv[0], [process.argv[1], ...args], {
|
|
99
90
|
detached: true,
|
|
100
91
|
stdio: ["ignore", out, out],
|
|
@@ -106,12 +97,20 @@ exports.watch = new commander_1.Command("watch")
|
|
|
106
97
|
process.exit(0);
|
|
107
98
|
}
|
|
108
99
|
// --- Foreground mode ---
|
|
100
|
+
// Migrate legacy watchers.json to LMDB on first use
|
|
101
|
+
(0, watcher_store_1.migrateFromJson)();
|
|
102
|
+
// Watcher requires project to be registered
|
|
103
|
+
if (!(0, project_registry_1.getProject)(projectRoot)) {
|
|
104
|
+
console.error(`[watch:${projectName}] Project not registered. Run: gmax add ${projectRoot}`);
|
|
105
|
+
process.exitCode = 1;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
109
108
|
const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
|
|
110
109
|
// Propagate project root to worker processes
|
|
111
110
|
process.env.GMAX_PROJECT_ROOT = paths.root;
|
|
112
111
|
console.log(`[watch:${projectName}] Starting...`);
|
|
113
112
|
// Register early so MCP can see status
|
|
114
|
-
(0,
|
|
113
|
+
(0, watcher_store_1.registerWatcher)({
|
|
115
114
|
pid: process.pid,
|
|
116
115
|
projectRoot,
|
|
117
116
|
startTime: Date.now(),
|
|
@@ -129,10 +128,22 @@ exports.watch = new commander_1.Command("watch")
|
|
|
129
128
|
.toArray();
|
|
130
129
|
if (indexed.length === 0) {
|
|
131
130
|
console.log(`[watch:${projectName}] No index found for ${projectRoot}, running initial sync...`);
|
|
132
|
-
yield (0, syncer_1.initialSync)({ projectRoot });
|
|
131
|
+
const syncResult = yield (0, syncer_1.initialSync)({ projectRoot });
|
|
132
|
+
// Update registry after sync
|
|
133
|
+
const globalConfig = (0, index_config_1.readGlobalConfig)();
|
|
134
|
+
(0, project_registry_1.registerProject)({
|
|
135
|
+
root: projectRoot,
|
|
136
|
+
name: projectName,
|
|
137
|
+
vectorDim: globalConfig.vectorDim,
|
|
138
|
+
modelTier: globalConfig.modelTier,
|
|
139
|
+
embedMode: globalConfig.embedMode,
|
|
140
|
+
lastIndexed: new Date().toISOString(),
|
|
141
|
+
chunkCount: syncResult.indexed,
|
|
142
|
+
status: "indexed",
|
|
143
|
+
});
|
|
133
144
|
console.log(`[watch:${projectName}] Initial sync complete.`);
|
|
134
145
|
}
|
|
135
|
-
(0,
|
|
146
|
+
(0, watcher_store_1.updateWatcherStatus)(process.pid, "watching");
|
|
136
147
|
// Open resources for watcher
|
|
137
148
|
const metaCache = new meta_cache_1.MetaCache(paths.lmdbPath);
|
|
138
149
|
// Start watching
|
|
@@ -144,10 +155,14 @@ exports.watch = new commander_1.Command("watch")
|
|
|
144
155
|
onReindex: (files, ms) => {
|
|
145
156
|
console.log(`[watch:${projectName}] Reindexed ${files} file${files !== 1 ? "s" : ""} (${(ms / 1000).toFixed(1)}s)`);
|
|
146
157
|
lastActivity = Date.now();
|
|
147
|
-
(0,
|
|
158
|
+
(0, watcher_store_1.updateWatcherStatus)(process.pid, "watching", Date.now());
|
|
148
159
|
},
|
|
149
160
|
});
|
|
150
161
|
console.log(`[watch:${projectName}] File watcher active`);
|
|
162
|
+
// Heartbeat — update LMDB every 60s so other processes can detect liveliness
|
|
163
|
+
const heartbeatInterval = setInterval(() => {
|
|
164
|
+
(0, watcher_store_1.heartbeat)(process.pid);
|
|
165
|
+
}, IDLE_CHECK_INTERVAL_MS);
|
|
151
166
|
// Idle timeout
|
|
152
167
|
let lastActivity = Date.now();
|
|
153
168
|
if (options.idleTimeout !== false) {
|
|
@@ -161,13 +176,14 @@ exports.watch = new commander_1.Command("watch")
|
|
|
161
176
|
// Graceful shutdown
|
|
162
177
|
function shutdown() {
|
|
163
178
|
return __awaiter(this, void 0, void 0, function* () {
|
|
179
|
+
clearInterval(heartbeatInterval);
|
|
164
180
|
try {
|
|
165
181
|
yield watcher.close();
|
|
166
182
|
}
|
|
167
183
|
catch (_a) { }
|
|
168
184
|
yield metaCache.close();
|
|
169
185
|
yield vectorDb.close();
|
|
170
|
-
(0,
|
|
186
|
+
(0, watcher_store_1.unregisterWatcher)(process.pid);
|
|
171
187
|
yield (0, exit_1.gracefulExit)();
|
|
172
188
|
});
|
|
173
189
|
}
|
|
@@ -179,7 +195,7 @@ exports.watch
|
|
|
179
195
|
.command("status")
|
|
180
196
|
.description("Show running watchers")
|
|
181
197
|
.action(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
182
|
-
const watchers = (0,
|
|
198
|
+
const watchers = (0, watcher_store_1.listWatchers)();
|
|
183
199
|
if (watchers.length === 0) {
|
|
184
200
|
console.log("No running watchers.");
|
|
185
201
|
yield (0, exit_1.gracefulExit)();
|
|
@@ -199,33 +215,32 @@ exports.watch
|
|
|
199
215
|
.action((options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
200
216
|
var _a;
|
|
201
217
|
if (options.all) {
|
|
202
|
-
const watchers = (0,
|
|
218
|
+
const watchers = (0, watcher_store_1.listWatchers)();
|
|
203
219
|
for (const w of watchers) {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
220
|
+
const killed = yield (0, process_1.killProcess)(w.pid);
|
|
221
|
+
(0, watcher_store_1.unregisterWatcher)(w.pid);
|
|
222
|
+
if (!killed) {
|
|
223
|
+
console.warn(`Warning: PID ${w.pid} did not exit after SIGKILL`);
|
|
207
224
|
}
|
|
208
|
-
catch (_b) { }
|
|
209
225
|
}
|
|
210
226
|
console.log(`Stopped ${watchers.length} watcher(s).`);
|
|
211
227
|
yield (0, exit_1.gracefulExit)();
|
|
212
228
|
return;
|
|
213
229
|
}
|
|
214
230
|
const projectRoot = (_a = (0, project_root_1.findProjectRoot)(process.cwd())) !== null && _a !== void 0 ? _a : process.cwd();
|
|
215
|
-
const watcher = (0,
|
|
231
|
+
const watcher = (0, watcher_store_1.getWatcherForProject)(projectRoot);
|
|
216
232
|
if (!watcher) {
|
|
217
233
|
console.log("No watcher running for this project.");
|
|
218
234
|
yield (0, exit_1.gracefulExit)();
|
|
219
235
|
return;
|
|
220
236
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
237
|
+
const killed = yield (0, process_1.killProcess)(watcher.pid);
|
|
238
|
+
(0, watcher_store_1.unregisterWatcher)(watcher.pid);
|
|
239
|
+
if (killed) {
|
|
224
240
|
console.log(`Stopped watcher (PID: ${watcher.pid})`);
|
|
225
241
|
}
|
|
226
|
-
|
|
227
|
-
console.
|
|
228
|
-
(0, watcher_registry_1.unregisterWatcher)(watcher.pid);
|
|
242
|
+
else {
|
|
243
|
+
console.warn(`Warning: watcher PID ${watcher.pid} did not exit`);
|
|
229
244
|
}
|
|
230
245
|
yield (0, exit_1.gracefulExit)();
|
|
231
246
|
}));
|
package/dist/config.js
CHANGED
|
@@ -93,6 +93,7 @@ exports.PATHS = {
|
|
|
93
93
|
globalRoot: GLOBAL_ROOT,
|
|
94
94
|
models: path.join(GLOBAL_ROOT, "models"),
|
|
95
95
|
grammars: path.join(GLOBAL_ROOT, "grammars"),
|
|
96
|
+
logsDir: path.join(GLOBAL_ROOT, "logs"),
|
|
96
97
|
// Centralized index storage — one database for all indexed directories
|
|
97
98
|
lancedbDir: path.join(GLOBAL_ROOT, "lancedb"),
|
|
98
99
|
cacheDir: path.join(GLOBAL_ROOT, "cache"),
|
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;
|
|
@@ -136,9 +136,11 @@ function startWatcher(opts) {
|
|
|
136
136
|
const vectors = [];
|
|
137
137
|
const metaUpdates = new Map();
|
|
138
138
|
const metaDeletes = [];
|
|
139
|
+
const attempted = new Set();
|
|
139
140
|
for (const [absPath, event] of batch) {
|
|
140
141
|
if (batchAc.signal.aborted)
|
|
141
142
|
break;
|
|
143
|
+
attempted.add(absPath);
|
|
142
144
|
if (event === "unlink") {
|
|
143
145
|
deletes.push(absPath);
|
|
144
146
|
metaDeletes.push(absPath);
|
|
@@ -200,6 +202,12 @@ function startWatcher(opts) {
|
|
|
200
202
|
}
|
|
201
203
|
}
|
|
202
204
|
}
|
|
205
|
+
// Requeue files that weren't attempted (aborted or pool unhealthy)
|
|
206
|
+
for (const [absPath, event] of batch) {
|
|
207
|
+
if (!attempted.has(absPath) && !pending.has(absPath)) {
|
|
208
|
+
pending.set(absPath, event);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
203
211
|
// Flush to VectorDB: insert first, then delete old (preserving new)
|
|
204
212
|
const newIds = vectors.map((v) => v.id);
|
|
205
213
|
if (vectors.length > 0) {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.openRotatedLog = openRotatedLog;
|
|
37
|
+
const fs = __importStar(require("node:fs"));
|
|
38
|
+
const path = __importStar(require("node:path"));
|
|
39
|
+
const MAX_LOG_BYTES = 5 * 1024 * 1024; // 5 MB
|
|
40
|
+
/**
|
|
41
|
+
* Open a log file with rotation. Creates parent directories if needed.
|
|
42
|
+
* Rotates {name}.log -> {name}.log.prev when size exceeds maxBytes.
|
|
43
|
+
* Returns an fd suitable for stdio redirection.
|
|
44
|
+
*/
|
|
45
|
+
function openRotatedLog(logPath, maxBytes = MAX_LOG_BYTES) {
|
|
46
|
+
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
|
47
|
+
try {
|
|
48
|
+
const stat = fs.statSync(logPath);
|
|
49
|
+
if (stat.size > maxBytes) {
|
|
50
|
+
fs.renameSync(logPath, `${logPath}.prev`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (_a) { }
|
|
54
|
+
return fs.openSync(logPath, "a");
|
|
55
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.killProcess = killProcess;
|
|
13
|
+
const watcher_store_1 = require("./watcher-store");
|
|
14
|
+
/**
|
|
15
|
+
* Send SIGTERM, wait up to 3s, then SIGKILL if still alive.
|
|
16
|
+
* Returns true if process is confirmed dead.
|
|
17
|
+
*/
|
|
18
|
+
function killProcess(pid) {
|
|
19
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
20
|
+
try {
|
|
21
|
+
process.kill(pid, "SIGTERM");
|
|
22
|
+
}
|
|
23
|
+
catch (_a) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
// Poll up to 3s for graceful exit
|
|
27
|
+
for (let i = 0; i < 30; i++) {
|
|
28
|
+
if (!(0, watcher_store_1.isProcessRunning)(pid))
|
|
29
|
+
return true;
|
|
30
|
+
yield new Promise((r) => setTimeout(r, 100));
|
|
31
|
+
}
|
|
32
|
+
// Force kill
|
|
33
|
+
try {
|
|
34
|
+
process.kill(pid, "SIGKILL");
|
|
35
|
+
}
|
|
36
|
+
catch (_b) { }
|
|
37
|
+
// Give SIGKILL a moment
|
|
38
|
+
for (let i = 0; i < 10; i++) {
|
|
39
|
+
if (!(0, watcher_store_1.isProcessRunning)(pid))
|
|
40
|
+
return true;
|
|
41
|
+
yield new Promise((r) => setTimeout(r, 100));
|
|
42
|
+
}
|
|
43
|
+
return !(0, watcher_store_1.isProcessRunning)(pid);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
@@ -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,48 @@
|
|
|
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
|
+
function launchWatcher(projectRoot) {
|
|
12
|
+
var _a;
|
|
13
|
+
// 1. Project must be registered
|
|
14
|
+
const project = (0, project_registry_1.getProject)(projectRoot);
|
|
15
|
+
if (!project) {
|
|
16
|
+
return {
|
|
17
|
+
ok: false,
|
|
18
|
+
reason: "not-registered",
|
|
19
|
+
message: `Project not registered. Run: gmax add ${projectRoot}`,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
// 2. Check if watcher already running
|
|
23
|
+
const existing = (_a = (0, watcher_store_1.getWatcherForProject)(projectRoot)) !== null && _a !== void 0 ? _a : (0, watcher_store_1.getWatcherCoveringPath)(projectRoot);
|
|
24
|
+
if (existing && (0, watcher_store_1.isProcessRunning)(existing.pid)) {
|
|
25
|
+
return { ok: true, pid: existing.pid, reused: true };
|
|
26
|
+
}
|
|
27
|
+
// 3. Spawn
|
|
28
|
+
try {
|
|
29
|
+
const child = (0, node_child_process_1.spawn)(process.argv[0], [process.argv[1], "watch", "--path", projectRoot, "-b"], { detached: true, stdio: "ignore" });
|
|
30
|
+
child.unref();
|
|
31
|
+
if (child.pid) {
|
|
32
|
+
return { ok: true, pid: child.pid, reused: false };
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
ok: false,
|
|
36
|
+
reason: "spawn-failed",
|
|
37
|
+
message: `Spawn returned no PID for ${projectRoot}`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
42
|
+
return {
|
|
43
|
+
ok: false,
|
|
44
|
+
reason: "spawn-failed",
|
|
45
|
+
message: `Failed to start watcher for ${projectRoot}: ${msg}`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -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
|
+
}
|
package/package.json
CHANGED
|
@@ -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
|
|