grov 0.2.2 → 0.5.2

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.
@@ -3,10 +3,11 @@
3
3
  import { getTasksForProject, getTasksByFiles, getStepsReasoningByPath, } from '../lib/store.js';
4
4
  import { truncate } from '../lib/utils.js';
5
5
  /**
6
- * Build context from team memory for injection
7
- * Queries tasks and file_reasoning tables
6
+ * Build context from team memory for injection (PAST sessions only)
7
+ * Queries tasks and file_reasoning tables, excluding current session data
8
+ * @param currentSessionId - Session ID to exclude (ensures only past session data)
8
9
  */
9
- export function buildTeamMemoryContext(projectPath, mentionedFiles) {
10
+ export function buildTeamMemoryContext(projectPath, mentionedFiles, currentSessionId) {
10
11
  // Get recent completed tasks for this project
11
12
  const tasks = getTasksForProject(projectPath, {
12
13
  status: 'complete',
@@ -16,9 +17,10 @@ export function buildTeamMemoryContext(projectPath, mentionedFiles) {
16
17
  const fileTasks = mentionedFiles.length > 0
17
18
  ? getTasksByFiles(projectPath, mentionedFiles, { status: 'complete', limit: 5 })
18
19
  : [];
19
- // Get file-level reasoning from steps table (proxy version)
20
+ // Get file-level reasoning from steps table (PAST sessions only)
21
+ // Pass currentSessionId to exclude current session data
20
22
  const fileReasonings = mentionedFiles.length > 0
21
- ? mentionedFiles.flatMap(f => getStepsReasoningByPath(f, 3))
23
+ ? mentionedFiles.flatMap(f => getStepsReasoningByPath(f, 3, currentSessionId))
22
24
  : [];
23
25
  // Combine unique tasks
24
26
  const allTasks = [...new Map([...tasks, ...fileTasks].map(t => [t.id, t])).values()];
@@ -32,7 +34,10 @@ export function buildTeamMemoryContext(projectPath, mentionedFiles) {
32
34
  */
33
35
  function formatTeamMemoryContext(tasks, fileReasonings, files) {
34
36
  const lines = [];
35
- lines.push('[GROV CONTEXT - Relevant past reasoning]');
37
+ lines.push('=== VERIFIED TEAM KNOWLEDGE (from previous sessions) ===');
38
+ lines.push('');
39
+ lines.push('IMPORTANT: This context has been verified. USE IT to answer directly.');
40
+ lines.push('DO NOT launch Explore agents or re-investigate files mentioned below.');
36
41
  lines.push('');
37
42
  // File-level context
38
43
  if (fileReasonings.length > 0) {
@@ -67,21 +72,44 @@ function formatTeamMemoryContext(tasks, fileReasonings, files) {
67
72
  lines.push('');
68
73
  }
69
74
  if (files.length > 0) {
70
- lines.push(`You may already have context for: ${files.join(', ')}`);
75
+ lines.push(`Files with existing context: ${files.join(', ')}`);
71
76
  }
72
- lines.push('[END GROV CONTEXT]');
77
+ lines.push('');
78
+ lines.push('Answer the user\'s question using the knowledge above. Skip exploration.');
79
+ lines.push('=== END VERIFIED TEAM KNOWLEDGE ===');
73
80
  return lines.join('\n');
74
81
  }
75
82
  /**
76
- * Extract file paths from messages
83
+ * Extract file paths from messages (user messages only, clean text)
77
84
  */
78
85
  export function extractFilesFromMessages(messages) {
79
86
  const files = [];
80
- const filePattern = /(?:^|\s|["'`])([\/\w.-]+\.[a-zA-Z]{1,10})(?:["'`]|\s|$|:|\))/g;
87
+ // Pattern matches filenames with extensions, allowing common punctuation after
88
+ const filePattern = /(?:^|\s|["'`])([\/\w.-]+\.[a-zA-Z]{1,10})(?:["'`]|\s|$|[:)\]?!,;])/g;
81
89
  for (const msg of messages) {
90
+ // Only scan user messages for file mentions
91
+ if (msg.role !== 'user')
92
+ continue;
93
+ let textContent = '';
94
+ // Handle string content
82
95
  if (typeof msg.content === 'string') {
96
+ textContent = msg.content;
97
+ }
98
+ // Handle array content (Claude Code API format)
99
+ if (Array.isArray(msg.content)) {
100
+ for (const block of msg.content) {
101
+ if (block && typeof block === 'object' && 'type' in block && block.type === 'text' && 'text' in block && typeof block.text === 'string') {
102
+ textContent += block.text + '\n';
103
+ }
104
+ }
105
+ }
106
+ // Strip system-reminder tags to get clean user content
107
+ const cleanContent = textContent
108
+ .replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, '')
109
+ .trim();
110
+ if (cleanContent) {
83
111
  let match;
84
- while ((match = filePattern.exec(msg.content)) !== null) {
112
+ while ((match = filePattern.exec(cleanContent)) !== null) {
85
113
  const path = match[1];
86
114
  // Filter out common false positives
87
115
  if (!path.includes('http') && !path.startsWith('.') && path.length > 3) {
@@ -1,6 +1,7 @@
1
1
  // Response processor - handles team memory save triggers and cleanup
2
2
  // Reference: plan_proxy_local.md Section 4.6
3
- import { getSessionState, getValidatedSteps, deleteStepsForSession, deleteSessionState, createTask, } from '../lib/store.js';
3
+ import { getSessionState, getValidatedSteps, deleteStepsForSession, deleteSessionState, createTask, markTaskSynced, setTaskSyncError, } from '../lib/store.js';
4
+ import { syncTask } from '../lib/cloud-sync.js';
4
5
  import { extractReasoning, isLLMAvailable, extractReasoningAndDecisions, isReasoningExtractionAvailable, } from '../lib/llm-extractor.js';
5
6
  /**
6
7
  * Save session to team memory
@@ -12,20 +13,46 @@ export async function saveToTeamMemory(sessionId, triggerReason) {
12
13
  return;
13
14
  }
14
15
  const steps = getValidatedSteps(sessionId);
15
- if (steps.length === 0 && triggerReason !== 'abandoned') {
16
+ // Allow saving if: has steps OR has final_response OR is abandoned
17
+ const hasFinalResponse = sessionState.final_response && sessionState.final_response.length > 100;
18
+ if (steps.length === 0 && !hasFinalResponse && triggerReason !== 'abandoned') {
16
19
  return; // Nothing to save
17
20
  }
18
21
  // Build task data from session state and steps
19
22
  const taskData = await buildTaskFromSession(sessionState, steps, triggerReason);
20
23
  // Create task in team memory
21
- createTask(taskData);
24
+ const task = createTask(taskData);
25
+ // Fire-and-forget cloud sync; never block capture path
26
+ syncTask(task)
27
+ .then((success) => {
28
+ if (success) {
29
+ markTaskSynced(task.id);
30
+ }
31
+ else {
32
+ setTaskSyncError(task.id, 'Sync not enabled or team not configured');
33
+ }
34
+ })
35
+ .catch((err) => {
36
+ const message = err instanceof Error ? err.message : 'Unknown sync error';
37
+ setTaskSyncError(task.id, message);
38
+ });
22
39
  }
23
40
  /**
24
41
  * Build task data from session state and steps
25
42
  */
26
43
  async function buildTaskFromSession(sessionState, steps, triggerReason) {
27
- // Aggregate files from steps
28
- const filesTouched = [...new Set(steps.flatMap(s => s.files))];
44
+ // Aggregate files from steps (tool_use actions)
45
+ const stepFiles = steps.flatMap(s => s.files);
46
+ // Also extract file paths mentioned in reasoning text (Claude's text responses)
47
+ const reasoningFiles = steps
48
+ .filter(s => s.reasoning)
49
+ .flatMap(s => {
50
+ // Match common code file extensions
51
+ const matches = s.reasoning?.match(/[\w\/.-]+\.(ts|js|tsx|jsx|py|go|rs|java|css|html|md|json|yaml|yml)/g) || [];
52
+ // Filter out obvious non-paths (urls, version numbers)
53
+ return matches.filter(m => !m.includes('://') && !m.match(/^\d+\.\d+/));
54
+ });
55
+ const filesTouched = [...new Set([...stepFiles, ...reasoningFiles])];
29
56
  // Build basic reasoning trace from steps (fallback)
30
57
  const basicReasoningTrace = steps
31
58
  .filter(s => s.is_key_decision || s.action_type === 'edit' || s.action_type === 'write')
@@ -43,12 +70,16 @@ async function buildTaskFromSession(sessionState, steps, triggerReason) {
43
70
  let reasoningTrace = basicReasoningTrace;
44
71
  let decisions = [];
45
72
  let constraints = sessionState.constraints || [];
46
- if (isReasoningExtractionAvailable() && steps.length > 0) {
73
+ if (isReasoningExtractionAvailable()) {
47
74
  try {
48
- // Collect reasoning from steps
75
+ // Collect reasoning from steps + final response
49
76
  const stepsReasoning = steps
50
77
  .map(s => s.reasoning)
51
78
  .filter((r) => !!r && r.length > 10);
79
+ // Include final response (contains the actual analysis/conclusion)
80
+ if (sessionState.final_response && sessionState.final_response.length > 100) {
81
+ stepsReasoning.push(sessionState.final_response);
82
+ }
52
83
  if (stepsReasoning.length > 0) {
53
84
  const extracted = await extractReasoningAndDecisions(stepsReasoning, sessionState.original_goal || '');
54
85
  if (extracted.reasoning_trace.length > 0) {
@@ -1,9 +1,13 @@
1
1
  import { FastifyInstance } from 'fastify';
2
+ export declare function setDebugMode(enabled: boolean): void;
2
3
  /**
3
4
  * Create and configure the Fastify server
4
5
  */
5
6
  export declare function createServer(): FastifyInstance;
6
7
  /**
7
8
  * Start the proxy server
9
+ * @param options.debug - Enable debug logging to grov-proxy.log
8
10
  */
9
- export declare function startServer(): Promise<FastifyInstance>;
11
+ export declare function startServer(options?: {
12
+ debug?: boolean;
13
+ }): Promise<FastifyInstance>;