grov 0.5.2 → 0.5.3

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 (48) hide show
  1. package/README.md +19 -1
  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 -38
  6. package/dist/lib/llm-extractor.js +380 -406
  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 +342 -566
  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 +5 -2
package/README.md CHANGED
@@ -36,7 +36,7 @@ Every time you start a new Claude Code session:
36
36
 
37
37
  Grov captures what Claude learns and injects it back on the next session.
38
38
 
39
- ![grov status](docs/grov-status.jpeg)
39
+ ![grov demo](demo-converted.gif)
40
40
 
41
41
  ### What Gets Captured
42
42
 
@@ -129,6 +129,24 @@ Grov monitors what Claude **does** (not what you ask) and corrects if it drifts
129
129
  grov drift-test "refactor the auth system" --goal "fix login bug"
130
130
  ```
131
131
 
132
+ ### Extended Cache (Experimental)
133
+
134
+ 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).
135
+
136
+ ```bash
137
+ grov proxy --extended-cache
138
+ ```
139
+
140
+ **What this does:** Sends minimal keep-alive requests (~$0.002 each) during idle periods to preserve your cache.
141
+
142
+ **Important:** By using `--extended-cache`, you consent to Grov making API requests on your behalf to keep the cache active. These requests:
143
+ - Use your Anthropic API key
144
+ - Are sent automatically during idle periods (every ~4 minutes)
145
+ - Cost approximately $0.002 per keep-alive
146
+ - Are discarded (not added to your conversation)
147
+
148
+ This feature is **disabled by default** and requires explicit opt-in.
149
+
132
150
  ### Environment Variables
133
151
 
134
152
  ```bash
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,21 @@ 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
35
  current_goal: string;
70
36
  parent_task_id?: string;
71
37
  reasoning: string;
72
38
  step_reasoning?: string;
73
39
  }
40
+ /**
41
+ * Conversation message for task analysis
42
+ */
43
+ export interface ConversationMessage {
44
+ role: 'user' | 'assistant';
45
+ content: string;
46
+ }
74
47
  /**
75
48
  * Check if task analysis is available
76
49
  */
@@ -80,7 +53,7 @@ export declare function isTaskAnalysisAvailable(): boolean;
80
53
  * Called after each main model response to orchestrate sessions
81
54
  * Also compresses reasoning for steps if assistantResponse > 1000 chars
82
55
  */
83
- export declare function analyzeTaskContext(currentSession: SessionState | null, latestUserMessage: string, recentSteps: StepRecord[], assistantResponse: string): Promise<TaskAnalysis>;
56
+ export declare function analyzeTaskContext(currentSession: SessionState | null, latestUserMessage: string, recentSteps: StepRecord[], assistantResponse: string, conversationHistory?: ConversationMessage[]): Promise<TaskAnalysis>;
84
57
  export interface ExtractedReasoningAndDecisions {
85
58
  reasoning_trace: string[];
86
59
  decisions: Array<{
@@ -95,5 +68,8 @@ export declare function isReasoningExtractionAvailable(): boolean;
95
68
  /**
96
69
  * Extract reasoning trace and decisions from steps
97
70
  * Called at task_complete to populate team memory with rich context
71
+ *
72
+ * @param formattedSteps - Pre-formatted XML string with grouped steps and actions
73
+ * @param originalGoal - The original task goal
98
74
  */
99
- export declare function extractReasoningAndDecisions(stepsReasoning: string[], originalGoal: string): Promise<ExtractedReasoningAndDecisions>;
75
+ export declare function extractReasoningAndDecisions(formattedSteps: string, originalGoal: string): Promise<ExtractedReasoningAndDecisions>;