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.
Files changed (49) hide show
  1. package/README.md +34 -4
  2. package/dist/cli.js +8 -0
  3. package/dist/lib/api-client.d.ts +18 -1
  4. package/dist/lib/api-client.js +57 -0
  5. package/dist/lib/llm-extractor.d.ts +14 -39
  6. package/dist/lib/llm-extractor.js +379 -407
  7. package/dist/lib/store/convenience.d.ts +40 -0
  8. package/dist/lib/store/convenience.js +104 -0
  9. package/dist/lib/store/database.d.ts +22 -0
  10. package/dist/lib/store/database.js +375 -0
  11. package/dist/lib/store/drift.d.ts +9 -0
  12. package/dist/lib/store/drift.js +89 -0
  13. package/dist/lib/store/index.d.ts +7 -0
  14. package/dist/lib/store/index.js +13 -0
  15. package/dist/lib/store/sessions.d.ts +32 -0
  16. package/dist/lib/store/sessions.js +240 -0
  17. package/dist/lib/store/steps.d.ts +40 -0
  18. package/dist/lib/store/steps.js +161 -0
  19. package/dist/lib/store/tasks.d.ts +33 -0
  20. package/dist/lib/store/tasks.js +133 -0
  21. package/dist/lib/store/types.d.ts +167 -0
  22. package/dist/lib/store/types.js +2 -0
  23. package/dist/lib/store.d.ts +1 -436
  24. package/dist/lib/store.js +2 -1478
  25. package/dist/proxy/cache.d.ts +36 -0
  26. package/dist/proxy/cache.js +51 -0
  27. package/dist/proxy/config.d.ts +1 -0
  28. package/dist/proxy/config.js +2 -0
  29. package/dist/proxy/extended-cache.d.ts +10 -0
  30. package/dist/proxy/extended-cache.js +155 -0
  31. package/dist/proxy/handlers/preprocess.d.ts +20 -0
  32. package/dist/proxy/handlers/preprocess.js +169 -0
  33. package/dist/proxy/injection/delta-tracking.d.ts +11 -0
  34. package/dist/proxy/injection/delta-tracking.js +93 -0
  35. package/dist/proxy/injection/injectors.d.ts +7 -0
  36. package/dist/proxy/injection/injectors.js +139 -0
  37. package/dist/proxy/request-processor.d.ts +18 -4
  38. package/dist/proxy/request-processor.js +151 -30
  39. package/dist/proxy/response-processor.js +93 -45
  40. package/dist/proxy/server.d.ts +0 -1
  41. package/dist/proxy/server.js +366 -582
  42. package/dist/proxy/types.d.ts +13 -0
  43. package/dist/proxy/types.js +2 -0
  44. package/dist/proxy/utils/extractors.d.ts +18 -0
  45. package/dist/proxy/utils/extractors.js +109 -0
  46. package/dist/proxy/utils/logging.d.ts +18 -0
  47. package/dist/proxy/utils/logging.js +42 -0
  48. package/package.json +7 -2
  49. 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 captures reasoning from your Claude Code sessions and injects it into future sessions. Your AI remembers what it learned.
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
- ![grov status](docs/grov-status.jpeg)
40
+ ![grov demo](demo-converted.gif)
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
- - Search across sessions
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
- - [ ] Semantic search
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
  });
@@ -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
  */
@@ -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 { ParsedSession } from './jsonl-parser.js';
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(stepsReasoning: string[], originalGoal: string): Promise<ExtractedReasoningAndDecisions>;
74
+ export declare function extractReasoningAndDecisions(formattedSteps: string, originalGoal: string): Promise<ExtractedReasoningAndDecisions>;