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 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 Claude figures something out, every dev's Claude knows it.</em></p>
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
- grov init # Configure proxy URL (one-time)
166
- grov proxy # Start the proxy (required)
167
- grov proxy-status # Show active sessions
168
- grov status # Show captured tasks
169
- grov login # Login to cloud dashboard
170
- grov sync # Sync memories to team dashboard
171
- grov doctor # Diagnose setup issues
172
- grov disable # Disable grov
173
- grov uninstall # Remove all grov data and config
174
- grov drift-test # Test drift detection
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
- - Claude Code
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
 
@@ -1 +1,3 @@
1
- export declare function doctor(agent?: string): Promise<void>;
1
+ export declare function doctor(agent?: string, options?: {
2
+ repair?: boolean;
3
+ }): Promise<void>;
@@ -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
- .action(safeAction(async (agent) => {
170
- const { doctor } = await import('./commands/doctor.js');
171
- const agentName = agent === 'claude' || agent === 'codex' ? agent : undefined;
172
- await doctor(agentName);
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
- SELECT session_id FROM session_states
61
- WHERE status IN ('completed', 'abandoned')
62
- AND completed_at < ?
63
- AND session_id NOT IN (
64
- SELECT DISTINCT parent_session_id
65
- FROM session_states
66
- WHERE parent_session_id IS NOT NULL
67
- AND status != 'completed'
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
- `).all(cutoff);
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
- // Delete in correct order to respect FK constraints
74
- for (const session of oldSessions) {
75
- // 1. Delete from drift_log (FK to session_states)
76
- database.prepare('DELETE FROM drift_log WHERE session_id = ?').run(session.session_id);
77
- // 2. Delete from steps (FK to session_states)
78
- database.prepare('DELETE FROM steps WHERE session_id = ?').run(session.session_id);
79
- // 3. Now safe to delete session_states
80
- database.prepare('DELETE FROM session_states WHERE session_id = ?').run(session.session_id);
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';
@@ -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
- query_params?: Record<string, string>;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grov",
3
- "version": "0.6.14",
3
+ "version": "0.6.16",
4
4
  "description": "Collective AI memory for Coding Agents - captures reasoning from sessions and injects context into future sessions",
5
5
  "type": "module",
6
6
  "main": "dist/cli/index.js",