grov 0.5.2 → 0.5.4
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 +34 -4
- package/dist/cli.js +8 -0
- package/dist/lib/api-client.d.ts +18 -1
- package/dist/lib/api-client.js +57 -0
- package/dist/lib/llm-extractor.d.ts +14 -39
- package/dist/lib/llm-extractor.js +379 -407
- package/dist/lib/store/convenience.d.ts +40 -0
- package/dist/lib/store/convenience.js +104 -0
- package/dist/lib/store/database.d.ts +22 -0
- package/dist/lib/store/database.js +375 -0
- package/dist/lib/store/drift.d.ts +9 -0
- package/dist/lib/store/drift.js +89 -0
- package/dist/lib/store/index.d.ts +7 -0
- package/dist/lib/store/index.js +13 -0
- package/dist/lib/store/sessions.d.ts +32 -0
- package/dist/lib/store/sessions.js +240 -0
- package/dist/lib/store/steps.d.ts +40 -0
- package/dist/lib/store/steps.js +161 -0
- package/dist/lib/store/tasks.d.ts +33 -0
- package/dist/lib/store/tasks.js +133 -0
- package/dist/lib/store/types.d.ts +167 -0
- package/dist/lib/store/types.js +2 -0
- package/dist/lib/store.d.ts +1 -436
- package/dist/lib/store.js +2 -1478
- package/dist/proxy/cache.d.ts +36 -0
- package/dist/proxy/cache.js +51 -0
- package/dist/proxy/config.d.ts +1 -0
- package/dist/proxy/config.js +2 -0
- package/dist/proxy/extended-cache.d.ts +10 -0
- package/dist/proxy/extended-cache.js +155 -0
- package/dist/proxy/handlers/preprocess.d.ts +20 -0
- package/dist/proxy/handlers/preprocess.js +169 -0
- package/dist/proxy/injection/delta-tracking.d.ts +11 -0
- package/dist/proxy/injection/delta-tracking.js +93 -0
- package/dist/proxy/injection/injectors.d.ts +7 -0
- package/dist/proxy/injection/injectors.js +139 -0
- package/dist/proxy/request-processor.d.ts +18 -4
- package/dist/proxy/request-processor.js +151 -30
- package/dist/proxy/response-processor.js +93 -45
- package/dist/proxy/server.d.ts +0 -1
- package/dist/proxy/server.js +366 -582
- package/dist/proxy/types.d.ts +13 -0
- package/dist/proxy/types.js +2 -0
- package/dist/proxy/utils/extractors.d.ts +18 -0
- package/dist/proxy/utils/extractors.js +109 -0
- package/dist/proxy/utils/logging.d.ts +18 -0
- package/dist/proxy/utils/logging.js +42 -0
- package/package.json +7 -2
- package/postinstall.js +19 -0
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
<a href="https://www.npmjs.com/package/grov"><img src="https://img.shields.io/npm/v/grov" alt="npm version"></a>
|
|
11
11
|
<a href="https://www.npmjs.com/package/grov"><img src="https://img.shields.io/npm/dm/grov" alt="npm downloads"></a>
|
|
12
12
|
<a href="https://github.com/TonyStef/Grov/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue" alt="license"></a>
|
|
13
|
+
<a href="https://app.grov.dev"><img src="https://img.shields.io/badge/Dashboard-app.grov.dev-22c55e" alt="dashboard"></a>
|
|
13
14
|
</p>
|
|
14
15
|
|
|
15
16
|
<p align="center">
|
|
@@ -20,7 +21,7 @@
|
|
|
20
21
|
<a href="#contributing">Contributing</a>
|
|
21
22
|
</p>
|
|
22
23
|
|
|
23
|
-
Grov
|
|
24
|
+
Grov gives your team a shared AI memory.
|
|
24
25
|
|
|
25
26
|
## The Problem
|
|
26
27
|
|
|
@@ -36,7 +37,7 @@ Every time you start a new Claude Code session:
|
|
|
36
37
|
|
|
37
38
|
Grov captures what Claude learns and injects it back on the next session.
|
|
38
39
|
|
|
39
|
-

|
|
40
41
|
|
|
41
42
|
### What Gets Captured
|
|
42
43
|
|
|
@@ -89,6 +90,17 @@ grov disable # Disable grov
|
|
|
89
90
|
- **Per-project:** Context is filtered by project path
|
|
90
91
|
- **Local by default:** Memories stay on your machine unless you enable team sync
|
|
91
92
|
|
|
93
|
+
### Why Sync to Dashboard?
|
|
94
|
+
|
|
95
|
+
Local memories work great for solo use. The dashboard unlocks:
|
|
96
|
+
|
|
97
|
+
- **Search across all sessions** - Hybrid semantic + keyword search
|
|
98
|
+
- **Team sharing** - What one dev's AI learns, everyone's AI knows
|
|
99
|
+
- **Cross-device sync** - Switch machines without losing context
|
|
100
|
+
- **Browse & manage** - Visual interface for all captured reasoning
|
|
101
|
+
|
|
102
|
+
[Open Dashboard](https://app.grov.dev)
|
|
103
|
+
|
|
92
104
|
## Team Sync
|
|
93
105
|
|
|
94
106
|
Share memories across your engineering team with the cloud dashboard.
|
|
@@ -100,7 +112,7 @@ grov sync --enable --team ID # Enable sync for a team
|
|
|
100
112
|
|
|
101
113
|
Once enabled, memories automatically sync to [app.grov.dev](https://app.grov.dev) where your team can:
|
|
102
114
|
- Browse all captured reasoning
|
|
103
|
-
-
|
|
115
|
+
- **Hybrid search** - semantic (AI understands meaning) + lexical (keyword matching)
|
|
104
116
|
- Invite team members
|
|
105
117
|
- See who learned what
|
|
106
118
|
|
|
@@ -129,6 +141,24 @@ Grov monitors what Claude **does** (not what you ask) and corrects if it drifts
|
|
|
129
141
|
grov drift-test "refactor the auth system" --goal "fix login bug"
|
|
130
142
|
```
|
|
131
143
|
|
|
144
|
+
### Extended Cache (Experimental)
|
|
145
|
+
|
|
146
|
+
Anthropic's prompt cache expires after 5 minutes of inactivity. If you pause to think between prompts, the cache expires and must be recreated (costs more, takes longer).
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
grov proxy --extended-cache
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**What this does:** Sends minimal keep-alive requests (~$0.002 each) during idle periods to preserve your cache.
|
|
153
|
+
|
|
154
|
+
**Important:** By using `--extended-cache`, you consent to Grov making API requests on your behalf to keep the cache active. These requests:
|
|
155
|
+
- Use your Anthropic API key
|
|
156
|
+
- Are sent automatically during idle periods (every ~4 minutes)
|
|
157
|
+
- Cost approximately $0.002 per keep-alive
|
|
158
|
+
- Are discarded (not added to your conversation)
|
|
159
|
+
|
|
160
|
+
This feature is **disabled by default** and requires explicit opt-in.
|
|
161
|
+
|
|
132
162
|
### Environment Variables
|
|
133
163
|
|
|
134
164
|
```bash
|
|
@@ -191,7 +221,7 @@ YOU MAY SKIP EXPLORE AGENTS for files mentioned above.
|
|
|
191
221
|
- [x] Anti-drift detection & correction
|
|
192
222
|
- [x] Team sync (cloud backend)
|
|
193
223
|
- [x] Web dashboard
|
|
194
|
-
- [
|
|
224
|
+
- [x] Hybrid search (semantic + lexical)
|
|
195
225
|
- [ ] VS Code extension
|
|
196
226
|
|
|
197
227
|
## Contributing
|
package/dist/cli.js
CHANGED
|
@@ -78,7 +78,15 @@ program
|
|
|
78
78
|
.command('proxy')
|
|
79
79
|
.description('Start the Grov proxy server (intercepts Claude API calls)')
|
|
80
80
|
.option('-d, --debug', 'Enable debug logging to grov-proxy.log')
|
|
81
|
+
.option('--extended-cache', 'Keep Anthropic cache alive during idle (sends requests on your behalf)')
|
|
81
82
|
.action(async (options) => {
|
|
83
|
+
if (options.extendedCache) {
|
|
84
|
+
process.env.GROV_EXTENDED_CACHE = 'true';
|
|
85
|
+
console.log('\n⚠️ Extended Cache Enabled');
|
|
86
|
+
console.log(' By using --extended-cache, you consent to Grov making');
|
|
87
|
+
console.log(' minimal keep-alive requests on your behalf to preserve');
|
|
88
|
+
console.log(' Anthropic\'s prompt cache during idle periods.\n');
|
|
89
|
+
}
|
|
82
90
|
const { startServer } = await import('./proxy/server.js');
|
|
83
91
|
await startServer({ debug: options.debug ?? false });
|
|
84
92
|
});
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Team, MemorySyncRequest, MemorySyncResponse, DeviceFlowStartResponse, DeviceFlowPollResponse } from '@grov/shared';
|
|
1
|
+
import type { Team, Memory, MemorySyncRequest, MemorySyncResponse, DeviceFlowStartResponse, DeviceFlowPollResponse } from '@grov/shared';
|
|
2
2
|
export interface ApiResponse<T> {
|
|
3
3
|
data?: T;
|
|
4
4
|
error?: string;
|
|
@@ -30,6 +30,23 @@ export declare function fetchTeam(teamId: string): Promise<Team>;
|
|
|
30
30
|
* Sync memories to team
|
|
31
31
|
*/
|
|
32
32
|
export declare function syncMemories(teamId: string, request: MemorySyncRequest): Promise<MemorySyncResponse>;
|
|
33
|
+
/**
|
|
34
|
+
* Fetch team memories from cloud (Supabase via API)
|
|
35
|
+
* Cloud equivalent of getTasksForProject() from store.ts
|
|
36
|
+
* Supports hybrid search when context is provided
|
|
37
|
+
*
|
|
38
|
+
* @param teamId - Team UUID
|
|
39
|
+
* @param projectPath - Project path to filter by (exact match)
|
|
40
|
+
* @param options - Optional filters (files, status, limit, context, current_files)
|
|
41
|
+
* @returns Array of memories (empty array on error - fail silent)
|
|
42
|
+
*/
|
|
43
|
+
export declare function fetchTeamMemories(teamId: string, projectPath: string, options?: {
|
|
44
|
+
files?: string[];
|
|
45
|
+
status?: string;
|
|
46
|
+
limit?: number;
|
|
47
|
+
context?: string;
|
|
48
|
+
current_files?: string[];
|
|
49
|
+
}): Promise<Memory[]>;
|
|
33
50
|
/**
|
|
34
51
|
* Sleep helper for polling
|
|
35
52
|
*/
|
package/dist/lib/api-client.js
CHANGED
|
@@ -102,6 +102,63 @@ export async function syncMemories(teamId, request) {
|
|
|
102
102
|
}
|
|
103
103
|
return response.data;
|
|
104
104
|
}
|
|
105
|
+
// Security limits for API params
|
|
106
|
+
const MAX_CONTEXT_LENGTH = 2000; // Max chars for semantic search context
|
|
107
|
+
const MAX_FILES_COUNT = 20; // Max files for boost/filter
|
|
108
|
+
/**
|
|
109
|
+
* Fetch team memories from cloud (Supabase via API)
|
|
110
|
+
* Cloud equivalent of getTasksForProject() from store.ts
|
|
111
|
+
* Supports hybrid search when context is provided
|
|
112
|
+
*
|
|
113
|
+
* @param teamId - Team UUID
|
|
114
|
+
* @param projectPath - Project path to filter by (exact match)
|
|
115
|
+
* @param options - Optional filters (files, status, limit, context, current_files)
|
|
116
|
+
* @returns Array of memories (empty array on error - fail silent)
|
|
117
|
+
*/
|
|
118
|
+
export async function fetchTeamMemories(teamId, projectPath, options) {
|
|
119
|
+
// Build query params
|
|
120
|
+
const params = new URLSearchParams();
|
|
121
|
+
params.set('project_path', projectPath);
|
|
122
|
+
if (options?.status) {
|
|
123
|
+
params.set('status', options.status);
|
|
124
|
+
}
|
|
125
|
+
if (options?.limit) {
|
|
126
|
+
params.set('limit', options.limit.toString());
|
|
127
|
+
}
|
|
128
|
+
if (options?.files && options.files.length > 0) {
|
|
129
|
+
// API expects multiple 'files' params for array
|
|
130
|
+
options.files.slice(0, MAX_FILES_COUNT).forEach(f => params.append('files', f));
|
|
131
|
+
}
|
|
132
|
+
// Hybrid search params (with security limits)
|
|
133
|
+
if (options?.context) {
|
|
134
|
+
params.set('context', options.context.substring(0, MAX_CONTEXT_LENGTH));
|
|
135
|
+
}
|
|
136
|
+
if (options?.current_files && options.current_files.length > 0) {
|
|
137
|
+
// Comma-separated for current_files (boost)
|
|
138
|
+
const files = options.current_files.slice(0, MAX_FILES_COUNT);
|
|
139
|
+
params.set('current_files', files.join(','));
|
|
140
|
+
}
|
|
141
|
+
const url = `/teams/${teamId}/memories?${params.toString()}`;
|
|
142
|
+
console.log(`[API] fetchTeamMemories: GET ${url}`);
|
|
143
|
+
try {
|
|
144
|
+
const response = await apiRequest('GET', url);
|
|
145
|
+
if (response.error) {
|
|
146
|
+
console.warn(`[API] fetchTeamMemories failed: ${response.error} (status: ${response.status})`);
|
|
147
|
+
return []; // Fail silent - don't block Claude Code
|
|
148
|
+
}
|
|
149
|
+
if (!response.data || !response.data.memories) {
|
|
150
|
+
console.log('[API] fetchTeamMemories: No memories returned');
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
console.log(`[API] fetchTeamMemories: Got ${response.data.memories.length} memories`);
|
|
154
|
+
return response.data.memories;
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
const errorMsg = err instanceof Error ? err.message : 'Unknown error';
|
|
158
|
+
console.error(`[API] fetchTeamMemories exception: ${errorMsg}`);
|
|
159
|
+
return []; // Fail silent - don't block Claude Code
|
|
160
|
+
}
|
|
161
|
+
}
|
|
105
162
|
// ============= Utility Functions =============
|
|
106
163
|
/**
|
|
107
164
|
* Sleep helper for polling
|
|
@@ -1,22 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { TaskStatus, SessionState, StepRecord } from './store.js';
|
|
3
|
-
export interface ExtractedReasoning {
|
|
4
|
-
task: string;
|
|
5
|
-
goal: string;
|
|
6
|
-
reasoning_trace: string[];
|
|
7
|
-
files_touched: string[];
|
|
8
|
-
decisions: Array<{
|
|
9
|
-
choice: string;
|
|
10
|
-
reason: string;
|
|
11
|
-
}>;
|
|
12
|
-
constraints: string[];
|
|
13
|
-
status: TaskStatus;
|
|
14
|
-
tags: string[];
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Check if LLM extraction is available (OpenAI API key set)
|
|
18
|
-
*/
|
|
19
|
-
export declare function isLLMAvailable(): boolean;
|
|
1
|
+
import type { SessionState, StepRecord } from './store.js';
|
|
20
2
|
export interface ExtractedIntent {
|
|
21
3
|
goal: string;
|
|
22
4
|
expected_scope: string[];
|
|
@@ -34,22 +16,6 @@ export declare function extractIntent(firstPrompt: string): Promise<ExtractedInt
|
|
|
34
16
|
* Check if intent extraction is available
|
|
35
17
|
*/
|
|
36
18
|
export declare function isIntentExtractionAvailable(): boolean;
|
|
37
|
-
/**
|
|
38
|
-
* Check if Anthropic API is available (for drift detection)
|
|
39
|
-
*/
|
|
40
|
-
export declare function isAnthropicAvailable(): boolean;
|
|
41
|
-
/**
|
|
42
|
-
* Get the drift model to use (from env or default)
|
|
43
|
-
*/
|
|
44
|
-
export declare function getDriftModel(): string;
|
|
45
|
-
/**
|
|
46
|
-
* Extract structured reasoning from a parsed session using GPT-3.5-turbo
|
|
47
|
-
*/
|
|
48
|
-
export declare function extractReasoning(session: ParsedSession): Promise<ExtractedReasoning>;
|
|
49
|
-
/**
|
|
50
|
-
* Classify just the task status (lighter weight than full extraction)
|
|
51
|
-
*/
|
|
52
|
-
export declare function classifyTaskStatus(session: ParsedSession): Promise<TaskStatus>;
|
|
53
19
|
/**
|
|
54
20
|
* Check if session summary generation is available
|
|
55
21
|
*/
|
|
@@ -63,14 +29,20 @@ export declare function generateSessionSummary(sessionState: SessionState, steps
|
|
|
63
29
|
* Task analysis result from Haiku
|
|
64
30
|
*/
|
|
65
31
|
export interface TaskAnalysis {
|
|
32
|
+
task_type: 'information' | 'planning' | 'implementation';
|
|
66
33
|
action: 'continue' | 'new_task' | 'subtask' | 'parallel_task' | 'task_complete' | 'subtask_complete';
|
|
67
|
-
topic_match?: 'YES' | 'NO';
|
|
68
34
|
task_id: string;
|
|
69
|
-
current_goal: string;
|
|
70
35
|
parent_task_id?: string;
|
|
71
36
|
reasoning: string;
|
|
72
37
|
step_reasoning?: string;
|
|
73
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Conversation message for task analysis
|
|
41
|
+
*/
|
|
42
|
+
export interface ConversationMessage {
|
|
43
|
+
role: 'user' | 'assistant';
|
|
44
|
+
content: string;
|
|
45
|
+
}
|
|
74
46
|
/**
|
|
75
47
|
* Check if task analysis is available
|
|
76
48
|
*/
|
|
@@ -80,7 +52,7 @@ export declare function isTaskAnalysisAvailable(): boolean;
|
|
|
80
52
|
* Called after each main model response to orchestrate sessions
|
|
81
53
|
* Also compresses reasoning for steps if assistantResponse > 1000 chars
|
|
82
54
|
*/
|
|
83
|
-
export declare function analyzeTaskContext(currentSession: SessionState | null, latestUserMessage: string, recentSteps: StepRecord[], assistantResponse: string): Promise<TaskAnalysis>;
|
|
55
|
+
export declare function analyzeTaskContext(currentSession: SessionState | null, latestUserMessage: string, recentSteps: StepRecord[], assistantResponse: string, conversationHistory?: ConversationMessage[]): Promise<TaskAnalysis>;
|
|
84
56
|
export interface ExtractedReasoningAndDecisions {
|
|
85
57
|
reasoning_trace: string[];
|
|
86
58
|
decisions: Array<{
|
|
@@ -95,5 +67,8 @@ export declare function isReasoningExtractionAvailable(): boolean;
|
|
|
95
67
|
/**
|
|
96
68
|
* Extract reasoning trace and decisions from steps
|
|
97
69
|
* Called at task_complete to populate team memory with rich context
|
|
70
|
+
*
|
|
71
|
+
* @param formattedSteps - Pre-formatted XML string with grouped steps and actions
|
|
72
|
+
* @param originalGoal - The original task goal
|
|
98
73
|
*/
|
|
99
|
-
export declare function extractReasoningAndDecisions(
|
|
74
|
+
export declare function extractReasoningAndDecisions(formattedSteps: string, originalGoal: string): Promise<ExtractedReasoningAndDecisions>;
|