grov 0.6.14 → 0.6.16
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/README.md +47 -12
- package/dist/cli/commands/doctor.d.ts +3 -1
- package/dist/cli/commands/doctor.js +31 -1
- package/dist/cli/index.js +15 -5
- package/dist/core/store/convenience.d.ts +1 -0
- package/dist/core/store/convenience.js +33 -18
- package/dist/core/store/database.js +7 -0
- package/dist/core/store/index.d.ts +1 -1
- package/dist/core/store/index.js +1 -1
- package/dist/core/store/sessions.d.ts +8 -0
- package/dist/core/store/sessions.js +25 -0
- package/dist/integrations/proxy/agents/codex/settings.d.ts +1 -3
- package/dist/integrations/proxy/agents/codex/settings.js +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
<p align="center"><strong>Collective AI memory for engineering teams.</strong></p>
|
|
8
8
|
|
|
9
|
-
<p align="center"><em>When one dev's
|
|
9
|
+
<p align="center"><em>When one dev's AI figures something out, every dev's AI knows it.</em></p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
12
|
<a href="https://www.npmjs.com/package/grov"><img src="https://img.shields.io/npm/v/grov" alt="npm version"></a>
|
|
@@ -71,6 +71,16 @@ grov proxy # Start (keep running)
|
|
|
71
71
|
|
|
72
72
|
Then use Claude Code normally in another terminal. That's it.
|
|
73
73
|
|
|
74
|
+
### Other Tools
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
grov setup # Interactive setup (Cursor, Zed, Codex)
|
|
78
|
+
grov init cursor # Direct setup for Cursor CLI
|
|
79
|
+
grov init antigravity # Direct setup for Antigravity
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
> IDE integrations (Cursor, Zed, Antigravity) use native MCP - no proxy needed.
|
|
83
|
+
|
|
74
84
|
> **Important:** Your `ANTHROPIC_API_KEY` must be set permanently in your shell profile, not just with `export` in a terminal. See [Troubleshooting](#troubleshooting) for setup instructions.
|
|
75
85
|
|
|
76
86
|
For team sync:
|
|
@@ -162,16 +172,26 @@ No manual `/compact` needed. No lost reasoning.
|
|
|
162
172
|
## Commands
|
|
163
173
|
|
|
164
174
|
```bash
|
|
165
|
-
|
|
166
|
-
grov
|
|
167
|
-
grov
|
|
168
|
-
grov
|
|
169
|
-
grov
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
grov
|
|
173
|
-
grov
|
|
174
|
-
|
|
175
|
+
# Setup
|
|
176
|
+
grov init # Configure for Claude Code (proxy mode)
|
|
177
|
+
grov init cursor # Configure for Cursor CLI
|
|
178
|
+
grov init antigravity # Configure for Antigravity
|
|
179
|
+
grov setup # Interactive setup (Cursor, Zed, Codex)
|
|
180
|
+
|
|
181
|
+
# Proxy (CLI tools only)
|
|
182
|
+
grov proxy # Start the proxy
|
|
183
|
+
grov proxy-status # Show active sessions
|
|
184
|
+
|
|
185
|
+
# Memory & Sync
|
|
186
|
+
grov status # Show captured tasks
|
|
187
|
+
grov login # Login to cloud dashboard
|
|
188
|
+
grov sync # Sync memories to team dashboard
|
|
189
|
+
|
|
190
|
+
# Utilities
|
|
191
|
+
grov doctor # Diagnose setup issues
|
|
192
|
+
grov disable # Disable grov
|
|
193
|
+
grov uninstall # Remove all grov data and config
|
|
194
|
+
grov drift-test # Test drift detection
|
|
175
195
|
```
|
|
176
196
|
|
|
177
197
|
---
|
|
@@ -268,10 +288,23 @@ Get your API key at: https://console.anthropic.com/settings/keys
|
|
|
268
288
|
|
|
269
289
|
---
|
|
270
290
|
|
|
291
|
+
## Supported Tools
|
|
292
|
+
|
|
293
|
+
| Tool | Type | Proxy Required |
|
|
294
|
+
|------|------|----------------|
|
|
295
|
+
| Claude Code | CLI | Yes |
|
|
296
|
+
| Cursor | IDE | No (native MCP) |
|
|
297
|
+
| Cursor CLI | CLI | No |
|
|
298
|
+
| Zed | IDE | No (native MCP) |
|
|
299
|
+
| Antigravity | IDE | No (native MCP) |
|
|
300
|
+
| Codex | CLI | Yes |
|
|
301
|
+
|
|
302
|
+
**Coming soon:** VS Code, Gemini CLI
|
|
303
|
+
|
|
271
304
|
## Requirements
|
|
272
305
|
|
|
273
306
|
- Node.js 18+
|
|
274
|
-
-
|
|
307
|
+
- One of the supported tools above
|
|
275
308
|
|
|
276
309
|
---
|
|
277
310
|
|
|
@@ -293,7 +326,9 @@ Get your API key at: https://console.anthropic.com/settings/keys
|
|
|
293
326
|
- [x] Hybrid search (semantic + lexical)
|
|
294
327
|
- [x] Extended cache (keep prompt cache warm)
|
|
295
328
|
- [x] Auto-compaction with reasoning preservation
|
|
329
|
+
- [x] IDE integrations (Cursor, Zed, Antigravity)
|
|
296
330
|
- [ ] VS Code extension
|
|
331
|
+
- [ ] Gemini CLI support
|
|
297
332
|
|
|
298
333
|
---
|
|
299
334
|
|
|
@@ -5,11 +5,16 @@ import { join } from 'path';
|
|
|
5
5
|
import { request } from 'undici';
|
|
6
6
|
import { readCredentials, getSyncStatus } from '../../core/cloud/credentials.js';
|
|
7
7
|
import { initDatabase } from '../../core/store/database.js';
|
|
8
|
+
import { getOrphanedSessionCount, repairOrphanedSessions } from '../../core/store/sessions.js';
|
|
8
9
|
import { getAllCliAgents, getCliAgentById } from '../agents/registry.js';
|
|
9
10
|
const DB_PATH = join(homedir(), '.grov', 'memory.db');
|
|
10
|
-
export async function doctor(agent) {
|
|
11
|
+
export async function doctor(agent, options) {
|
|
11
12
|
console.log('\nGrov Doctor');
|
|
12
13
|
console.log('===========\n');
|
|
14
|
+
if (options?.repair) {
|
|
15
|
+
await runRepair();
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
13
18
|
if (!agent) {
|
|
14
19
|
await runGeneralChecks();
|
|
15
20
|
console.log('\n--- Agent Status ---\n');
|
|
@@ -60,6 +65,31 @@ async function runGeneralChecks() {
|
|
|
60
65
|
const dbOk = dbStats.tasks > 0 || dbStats.sessions > 0;
|
|
61
66
|
const dbMsg = `${dbStats.tasks} tasks, ${dbStats.unsynced} unsynced, ${dbStats.sessions} active`;
|
|
62
67
|
printCheck('Local Database', dbOk, dbMsg, 'Empty', 'Use Claude/Codex with proxy, or Cursor with MCP');
|
|
68
|
+
const orphans = checkOrphanedSessions();
|
|
69
|
+
printCheck('Session Integrity', orphans === 0, 'No orphaned sessions', `${orphans} orphaned session(s)`, 'grov doctor --repair');
|
|
70
|
+
}
|
|
71
|
+
async function runRepair() {
|
|
72
|
+
console.log('Database Repair\n');
|
|
73
|
+
if (!existsSync(DB_PATH)) {
|
|
74
|
+
console.log('\x1b[31m✗\x1b[0m No database found');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
initDatabase();
|
|
78
|
+
const orphans = getOrphanedSessionCount();
|
|
79
|
+
if (orphans === 0) {
|
|
80
|
+
console.log('\x1b[32m✓\x1b[0m No orphaned sessions found');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
console.log(`Found ${orphans} orphaned session(s)`);
|
|
84
|
+
const repaired = repairOrphanedSessions();
|
|
85
|
+
console.log(`\x1b[32m✓\x1b[0m Repaired ${repaired} session(s)`);
|
|
86
|
+
}
|
|
87
|
+
function checkOrphanedSessions() {
|
|
88
|
+
if (!existsSync(DB_PATH)) {
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
initDatabase();
|
|
92
|
+
return getOrphanedSessionCount();
|
|
63
93
|
}
|
|
64
94
|
async function runClaudeChecks() {
|
|
65
95
|
console.log('Claude Code Checks\n');
|
package/dist/cli/index.js
CHANGED
|
@@ -166,11 +166,21 @@ program
|
|
|
166
166
|
program
|
|
167
167
|
.command('doctor [agent]')
|
|
168
168
|
.description('Check grov setup and diagnose issues (optional: claude, codex)')
|
|
169
|
-
.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
169
|
+
.option('--repair', 'Repair database integrity issues')
|
|
170
|
+
.action(async (agent, options) => {
|
|
171
|
+
try {
|
|
172
|
+
const { doctor } = await import('./commands/doctor.js');
|
|
173
|
+
const agentName = agent === 'claude' || agent === 'codex' ? agent : undefined;
|
|
174
|
+
await doctor(agentName, options);
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
console.error('Error:', error instanceof Error ? error.message : 'Unknown error');
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
closeDatabase();
|
|
182
|
+
}
|
|
183
|
+
});
|
|
174
184
|
// grov agents - List supported agents
|
|
175
185
|
program
|
|
176
186
|
.command('agents')
|
|
@@ -26,6 +26,7 @@ export declare function markCleared(sessionId: string): void;
|
|
|
26
26
|
export declare function markSessionCompleted(sessionId: string): void;
|
|
27
27
|
/**
|
|
28
28
|
* Cleanup old completed/abandoned sessions and their steps/drift_log
|
|
29
|
+
* Orders deletion by hierarchy depth (children first) to respect FK constraints
|
|
29
30
|
*/
|
|
30
31
|
export declare function cleanupOldCompletedSessions(maxAgeMs?: number): number;
|
|
31
32
|
/**
|
|
@@ -52,33 +52,48 @@ export function markSessionCompleted(sessionId) {
|
|
|
52
52
|
}
|
|
53
53
|
/**
|
|
54
54
|
* Cleanup old completed/abandoned sessions and their steps/drift_log
|
|
55
|
+
* Orders deletion by hierarchy depth (children first) to respect FK constraints
|
|
55
56
|
*/
|
|
56
57
|
export function cleanupOldCompletedSessions(maxAgeMs = 86400000) {
|
|
57
58
|
const database = getDb();
|
|
58
59
|
const cutoff = new Date(Date.now() - maxAgeMs).toISOString();
|
|
60
|
+
// Get sessions ordered by depth: leaves first, then parents
|
|
59
61
|
const oldSessions = database.prepare(`
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
WITH RECURSIVE session_depth AS (
|
|
63
|
+
SELECT s.session_id, 0 as depth
|
|
64
|
+
FROM session_states s
|
|
65
|
+
WHERE s.status IN ('completed', 'abandoned')
|
|
66
|
+
AND s.completed_at < ?
|
|
67
|
+
AND NOT EXISTS (
|
|
68
|
+
SELECT 1 FROM session_states child
|
|
69
|
+
WHERE child.parent_session_id = s.session_id
|
|
70
|
+
)
|
|
71
|
+
UNION ALL
|
|
72
|
+
SELECT parent.session_id, sd.depth + 1
|
|
73
|
+
FROM session_states parent
|
|
74
|
+
INNER JOIN session_depth sd ON sd.session_id IN (
|
|
75
|
+
SELECT session_id FROM session_states
|
|
76
|
+
WHERE parent_session_id = parent.session_id
|
|
68
77
|
)
|
|
69
|
-
|
|
78
|
+
WHERE parent.status IN ('completed', 'abandoned')
|
|
79
|
+
AND parent.completed_at < ?
|
|
80
|
+
)
|
|
81
|
+
SELECT DISTINCT session_id, MAX(depth) as depth
|
|
82
|
+
FROM session_depth
|
|
83
|
+
GROUP BY session_id
|
|
84
|
+
ORDER BY depth ASC
|
|
85
|
+
`).all(cutoff, cutoff);
|
|
70
86
|
if (oldSessions.length === 0) {
|
|
71
87
|
return 0;
|
|
72
88
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
89
|
+
const deleteTransaction = database.transaction(() => {
|
|
90
|
+
for (const session of oldSessions) {
|
|
91
|
+
database.prepare('DELETE FROM drift_log WHERE session_id = ?').run(session.session_id);
|
|
92
|
+
database.prepare('DELETE FROM steps WHERE session_id = ?').run(session.session_id);
|
|
93
|
+
database.prepare('DELETE FROM session_states WHERE session_id = ?').run(session.session_id);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
deleteTransaction();
|
|
82
97
|
return oldSessions.length;
|
|
83
98
|
}
|
|
84
99
|
/**
|
|
@@ -377,6 +377,13 @@ export function initDatabase() {
|
|
|
377
377
|
CREATE INDEX IF NOT EXISTS idx_drift_log_session ON drift_log(session_id);
|
|
378
378
|
CREATE INDEX IF NOT EXISTS idx_drift_log_timestamp ON drift_log(timestamp);
|
|
379
379
|
`);
|
|
380
|
+
// Nullify orphaned parent references
|
|
381
|
+
db.prepare(`
|
|
382
|
+
UPDATE session_states
|
|
383
|
+
SET parent_session_id = NULL
|
|
384
|
+
WHERE parent_session_id IS NOT NULL
|
|
385
|
+
AND parent_session_id NOT IN (SELECT session_id FROM session_states)
|
|
386
|
+
`).run();
|
|
380
387
|
return db;
|
|
381
388
|
}
|
|
382
389
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type { TaskStatus, TriggerReason, SessionStatus, SessionMode, TaskType, StepActionType, DriftType, CorrectionLevel, Task, CreateTaskInput, RecoveryPlan, DriftEvent, SessionState, CreateSessionStateInput, StepRecord, CreateStepInput, DriftLogEntry, CreateDriftLogInput, } from './types.js';
|
|
2
2
|
export { initDatabase, closeDatabase, getDatabasePath } from './database.js';
|
|
3
3
|
export { createTask, getTasksForProject, getTaskCount, getUnsyncedTasks, markTaskSynced, setTaskSyncError, getSyncedTaskCount, cleanupOldSyncedTasks, cleanupFailedSyncTasks } from './tasks.js';
|
|
4
|
-
export { createSessionState, getSessionState, updateSessionState, deleteSessionState, getActiveSessionForUser, getActiveSessionsForStatus, getCompletedSessionForProject, clearStalePendingCorrections } from './sessions.js';
|
|
4
|
+
export { createSessionState, getSessionState, updateSessionState, deleteSessionState, getActiveSessionForUser, getActiveSessionsForStatus, getCompletedSessionForProject, clearStalePendingCorrections, getOrphanedSessionCount, repairOrphanedSessions } from './sessions.js';
|
|
5
5
|
export { createStep, getStepsForSession, getRecentSteps, getValidatedSteps, getKeyDecisions, getEditedFiles, deleteStepsForSession, updateRecentStepsReasoning, updateLastChecked } from './steps.js';
|
|
6
6
|
export { updateSessionDrift, logDriftEvent } from './drift.js';
|
|
7
7
|
export { updateTokenCount, updateSessionMode, markWaitingForRecovery, incrementEscalation, markCleared, markSessionCompleted, cleanupOldCompletedSessions, cleanupStaleActiveSessions } from './convenience.js';
|
package/dist/core/store/index.js
CHANGED
|
@@ -4,7 +4,7 @@ export { initDatabase, closeDatabase, getDatabasePath } from './database.js';
|
|
|
4
4
|
// Re-export task functions
|
|
5
5
|
export { createTask, getTasksForProject, getTaskCount, getUnsyncedTasks, markTaskSynced, setTaskSyncError, getSyncedTaskCount, cleanupOldSyncedTasks, cleanupFailedSyncTasks } from './tasks.js';
|
|
6
6
|
// Re-export session functions
|
|
7
|
-
export { createSessionState, getSessionState, updateSessionState, deleteSessionState, getActiveSessionForUser, getActiveSessionsForStatus, getCompletedSessionForProject, clearStalePendingCorrections } from './sessions.js';
|
|
7
|
+
export { createSessionState, getSessionState, updateSessionState, deleteSessionState, getActiveSessionForUser, getActiveSessionsForStatus, getCompletedSessionForProject, clearStalePendingCorrections, getOrphanedSessionCount, repairOrphanedSessions } from './sessions.js';
|
|
8
8
|
// Re-export step functions
|
|
9
9
|
export { createStep, getStepsForSession, getRecentSteps, getValidatedSteps, getKeyDecisions, getEditedFiles, deleteStepsForSession, updateRecentStepsReasoning, updateLastChecked } from './steps.js';
|
|
10
10
|
// Re-export drift functions
|
|
@@ -37,3 +37,11 @@ export declare function getCompletedSessionForProject(projectPath: string): Sess
|
|
|
37
37
|
* Returns number of sessions cleared
|
|
38
38
|
*/
|
|
39
39
|
export declare function clearStalePendingCorrections(): number;
|
|
40
|
+
/**
|
|
41
|
+
* Detect orphaned parent_session_id references
|
|
42
|
+
*/
|
|
43
|
+
export declare function getOrphanedSessionCount(): number;
|
|
44
|
+
/**
|
|
45
|
+
* Nullify orphaned parent_session_id references
|
|
46
|
+
*/
|
|
47
|
+
export declare function repairOrphanedSessions(): number;
|
|
@@ -255,3 +255,28 @@ export function clearStalePendingCorrections() {
|
|
|
255
255
|
`).run();
|
|
256
256
|
return result.changes;
|
|
257
257
|
}
|
|
258
|
+
/**
|
|
259
|
+
* Detect orphaned parent_session_id references
|
|
260
|
+
*/
|
|
261
|
+
export function getOrphanedSessionCount() {
|
|
262
|
+
const database = getDb();
|
|
263
|
+
const result = database.prepare(`
|
|
264
|
+
SELECT COUNT(*) as count FROM session_states
|
|
265
|
+
WHERE parent_session_id IS NOT NULL
|
|
266
|
+
AND parent_session_id NOT IN (SELECT session_id FROM session_states)
|
|
267
|
+
`).get();
|
|
268
|
+
return result.count;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Nullify orphaned parent_session_id references
|
|
272
|
+
*/
|
|
273
|
+
export function repairOrphanedSessions() {
|
|
274
|
+
const database = getDb();
|
|
275
|
+
const result = database.prepare(`
|
|
276
|
+
UPDATE session_states
|
|
277
|
+
SET parent_session_id = NULL
|
|
278
|
+
WHERE parent_session_id IS NOT NULL
|
|
279
|
+
AND parent_session_id NOT IN (SELECT session_id FROM session_states)
|
|
280
|
+
`).run();
|
|
281
|
+
return result.changes;
|
|
282
|
+
}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
interface ModelProvider {
|
|
2
2
|
name: string;
|
|
3
3
|
base_url: string;
|
|
4
|
-
env_key: string;
|
|
5
4
|
wire_api: 'responses' | 'chat';
|
|
6
|
-
|
|
7
|
-
http_headers?: Record<string, string>;
|
|
5
|
+
requires_openai_auth: boolean;
|
|
8
6
|
}
|
|
9
7
|
interface CodexConfig {
|
|
10
8
|
model_providers?: Record<string, ModelProvider>;
|
|
@@ -42,13 +42,14 @@ export function setProxyEnv(enable) {
|
|
|
42
42
|
if (config.model_provider && config.model_provider !== 'grov') {
|
|
43
43
|
config._grov_original_provider = config.model_provider;
|
|
44
44
|
}
|
|
45
|
+
// requires_openai_auth = true uses existing ChatGPT subscription auth
|
|
46
|
+
// This allows users to use Grov with their Plus/Pro subscription - no API key needed
|
|
45
47
|
config.model_providers.grov = {
|
|
46
48
|
name: 'Grov Memory Proxy',
|
|
47
49
|
base_url: PROXY_URL,
|
|
48
|
-
env_key: 'OPENAI_API_KEY',
|
|
49
50
|
wire_api: 'responses',
|
|
51
|
+
requires_openai_auth: true,
|
|
50
52
|
};
|
|
51
|
-
// Tell Codex to use the grov provider
|
|
52
53
|
config.model_provider = 'grov';
|
|
53
54
|
writeCodexConfig(config);
|
|
54
55
|
return { action: 'added' };
|
|
@@ -57,7 +58,6 @@ export function setProxyEnv(enable) {
|
|
|
57
58
|
return { action: 'unchanged' };
|
|
58
59
|
}
|
|
59
60
|
delete config.model_providers.grov;
|
|
60
|
-
// Restore original provider or remove the setting
|
|
61
61
|
if (config._grov_original_provider) {
|
|
62
62
|
config.model_provider = config._grov_original_provider;
|
|
63
63
|
delete config._grov_original_provider;
|
package/package.json
CHANGED