grov 0.5.9 → 0.5.10

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.
@@ -2,6 +2,7 @@
2
2
  // Reference: plan_proxy_local.md Section 2.1
3
3
  import { truncate } from '../lib/utils.js';
4
4
  import { fetchTeamMemories } from '../lib/api-client.js';
5
+ import { isDebugMode } from './utils/logging.js';
5
6
  /**
6
7
  * Build context from CLOUD team memory for injection
7
8
  * Fetches memories from Supabase via API (cloud-first approach)
@@ -14,62 +15,36 @@ import { fetchTeamMemories } from '../lib/api-client.js';
14
15
  * @returns Formatted context string or null if no memories found
15
16
  */
16
17
  export async function buildTeamMemoryContextCloud(teamId, projectPath, mentionedFiles, userPrompt) {
17
- const startTime = Date.now();
18
18
  const hasContext = userPrompt && userPrompt.trim().length > 0;
19
- console.log(`[CLOUD] ═══════════════════════════════════════════════════════════`);
20
- console.log(`[CLOUD] buildTeamMemoryContextCloud START`);
21
- console.log(`[CLOUD] Team: ${teamId.substring(0, 8)}...`);
22
- console.log(`[CLOUD] Project: ${projectPath}`);
23
- console.log(`[CLOUD] Prompt: "${hasContext ? userPrompt.substring(0, 60) + '...' : 'N/A'}"`);
24
- console.log(`[CLOUD] Files for boost: ${mentionedFiles.length > 0 ? mentionedFiles.join(', ') : 'none'}`);
25
19
  try {
26
20
  // Fetch memories from cloud API (hybrid search if context provided)
27
- const fetchStart = Date.now();
28
21
  const memories = await fetchTeamMemories(teamId, projectPath, {
29
22
  status: 'complete',
30
- limit: 5, // Max 5 memories for injection (Convex Combination scoring)
23
+ limit: 5,
31
24
  files: mentionedFiles.length > 0 ? mentionedFiles : undefined,
32
25
  context: hasContext ? userPrompt : undefined,
33
26
  current_files: mentionedFiles.length > 0 ? mentionedFiles : undefined,
34
27
  });
35
- const fetchTime = Date.now() - fetchStart;
36
28
  if (memories.length === 0) {
37
- console.log(`[CLOUD] No memories found (fetch took ${fetchTime}ms)`);
38
- console.log(`[CLOUD] ═══════════════════════════════════════════════════════════`);
39
29
  return null;
40
30
  }
41
- console.log(`[CLOUD] ───────────────────────────────────────────────────────────`);
42
- console.log(`[CLOUD] Fetched ${memories.length} memories in ${fetchTime}ms`);
43
- console.log(`[CLOUD] ───────────────────────────────────────────────────────────`);
44
- // Log each memory with scores (if available from hybrid search)
45
- for (let i = 0; i < memories.length; i++) {
46
- const mem = memories[i];
47
- const semScore = typeof mem.semantic_score === 'number' ? mem.semantic_score.toFixed(3) : '-';
48
- const lexScore = typeof mem.lexical_score === 'number' ? mem.lexical_score.toFixed(3) : '-';
49
- const combScore = typeof mem.combined_score === 'number' ? mem.combined_score.toFixed(3) : '-';
50
- const boosted = mem.file_boost_applied ? '🚀' : ' ';
51
- const query = String(memories[i].original_query || '').substring(0, 50);
52
- const filesCount = memories[i].files_touched?.length || 0;
53
- const reasoningCount = memories[i].reasoning_trace?.length || 0;
54
- console.log(`[CLOUD] ${i + 1}. ${boosted} [${combScore}] sem=${semScore} lex=${lexScore} | files=${filesCount} reasoning=${reasoningCount}`);
55
- console.log(`[CLOUD] "${query}..."`);
31
+ // Log injected memories (debug mode only)
32
+ if (isDebugMode()) {
33
+ for (const m of memories) {
34
+ const label = m.goal || m.original_query;
35
+ const sem = m.semantic_score?.toFixed(2) || '-';
36
+ const lex = m.lexical_score?.toFixed(2) || '-';
37
+ const comb = m.combined_score?.toFixed(2) || '-';
38
+ console.log(`[INJECT] ${label.substring(0, 50)}... (${comb}|${sem}|${lex})`);
39
+ }
56
40
  }
57
- console.log(`[CLOUD] ───────────────────────────────────────────────────────────`);
58
41
  // Convert Memory[] to Task[] format for the existing formatter
59
42
  const tasks = memories.map(memoryToTask);
60
- // Reuse existing formatter (no file-level reasoning from cloud yet)
43
+ // Reuse existing formatter
61
44
  const context = formatTeamMemoryContext(tasks, [], mentionedFiles);
62
- // Estimate tokens (~4 chars per token)
63
- const estimatedTokens = Math.round(context.length / 4);
64
- const totalTime = Date.now() - startTime;
65
- console.log(`[CLOUD] Context built: ${context.length} chars (~${estimatedTokens} tokens)`);
66
- console.log(`[CLOUD] Total time: ${totalTime}ms (fetch: ${fetchTime}ms, format: ${totalTime - fetchTime}ms)`);
67
- console.log(`[CLOUD] ═══════════════════════════════════════════════════════════`);
68
45
  return context;
69
46
  }
70
47
  catch (err) {
71
- const errorMsg = err instanceof Error ? err.message : 'Unknown error';
72
- console.error(`[CLOUD] buildTeamMemoryContextCloud failed: ${errorMsg}`);
73
48
  return null; // Fail silent - don't block Claude Code
74
49
  }
75
50
  }
@@ -123,25 +98,32 @@ function formatTeamMemoryContext(tasks, fileReasonings, files) {
123
98
  const fileList = task.files_touched.slice(0, 5).map(f => f.split('/').pop()).join(', ');
124
99
  lines.push(` Files: ${fileList}`);
125
100
  }
126
- // Inject knowledge pairs (interleaved: conclusion, insight, conclusion, insight...)
127
- // Take up to 5 pairs (10 entries) per task
101
+ // Inject knowledge pairs - handle both formats:
102
+ // Old format: string[] interleaved (conclusion, insight, conclusion, insight...)
103
+ // New format: Array<{ tags?, conclusion, insight? }> objects
128
104
  if (task.reasoning_trace.length > 0) {
129
105
  lines.push(' Knowledge:');
130
106
  const maxPairs = 5;
131
- const maxEntries = maxPairs * 2; // 10 entries
132
- const entries = task.reasoning_trace.slice(0, maxEntries);
133
- for (let i = 0; i < entries.length; i += 2) {
134
- const conclusion = entries[i];
135
- const insight = entries[i + 1];
136
- // Format conclusion (remove prefix for brevity)
137
- const cText = conclusion?.replace(/^CONCLUSION:\s*/i, '') || '';
138
- if (cText) {
139
- lines.push(` • ${truncate(cText, 120)}`);
107
+ const entries = task.reasoning_trace.slice(0, maxPairs);
108
+ for (const entry of entries) {
109
+ if (typeof entry === 'string') {
110
+ // Old format: plain string - just remove prefix and show
111
+ const text = entry.replace(/^(CONCLUSION|INSIGHT):\s*/i, '');
112
+ if (text) {
113
+ lines.push(` • ${truncate(text, 120)}`);
114
+ }
140
115
  }
141
- // Format insight (indented under conclusion)
142
- if (insight) {
143
- const iText = insight.replace(/^INSIGHT:\s*/i, '');
144
- lines.push(` → ${truncate(iText, 100)}`);
116
+ else if (typeof entry === 'object' && entry !== null) {
117
+ // New format: object with tags, conclusion, insight
118
+ const cText = entry.conclusion?.replace(/^CONCLUSION:\s*/i, '') || '';
119
+ if (cText) {
120
+ const prefix = entry.tags ? `[${entry.tags}] ` : '';
121
+ lines.push(` • ${prefix}${truncate(cText, 110)}`);
122
+ }
123
+ if (entry.insight) {
124
+ const iText = entry.insight.replace(/^INSIGHT:\s*/i, '');
125
+ lines.push(` → ${truncate(iText, 100)}`);
126
+ }
145
127
  }
146
128
  }
147
129
  }
@@ -231,11 +213,19 @@ export function extractLastUserPrompt(messages) {
231
213
  }
232
214
  }
233
215
  }
234
- // Strip system-reminder tags to get clean user content
216
+ // Strip system-reminder tags and continuation artifacts to get clean user content
235
217
  const cleanContent = textContent
218
+ // Remove actual system-reminder tags
236
219
  .replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, '')
220
+ // Remove escaped/broken system-reminder artifacts from continuation summaries
221
+ .replace(/\\n["']?\s*<\/system-reminder>/g, '')
222
+ .replace(/<\/system-reminder>/g, '')
223
+ // Remove continuation summary header if it's the entire content
224
+ .replace(/^This session is being continued from a previous conversation[\s\S]*?Summary:/gi, '')
225
+ // Clean up leading noise (newlines, quotes, whitespace)
226
+ .replace(/^[\s\n"'\\]+/, '')
237
227
  .trim();
238
- if (cleanContent) {
228
+ if (cleanContent && cleanContent.length > 5) {
239
229
  return cleanContent;
240
230
  }
241
231
  }
@@ -2,8 +2,12 @@ import { type TriggerReason } from '../lib/store.js';
2
2
  /**
3
3
  * Save session to team memory
4
4
  * Called on: task complete, subtask complete, session abandoned
5
+ *
6
+ * @param sessionId - Session to save
7
+ * @param triggerReason - Why the save was triggered
8
+ * @param taskType - Task type for shouldUpdateMemory context (information/planning/implementation)
5
9
  */
6
- export declare function saveToTeamMemory(sessionId: string, triggerReason: TriggerReason): Promise<void>;
10
+ export declare function saveToTeamMemory(sessionId: string, triggerReason: TriggerReason, taskType?: 'information' | 'planning' | 'implementation'): Promise<void>;
7
11
  /**
8
12
  * Clean up session data after save
9
13
  */
@@ -11,4 +15,4 @@ export declare function cleanupSession(sessionId: string): void;
11
15
  /**
12
16
  * Save and cleanup session (for session end)
13
17
  */
14
- export declare function saveAndCleanupSession(sessionId: string, triggerReason: TriggerReason): Promise<void>;
18
+ export declare function saveAndCleanupSession(sessionId: string, triggerReason: TriggerReason, taskType?: 'information' | 'planning' | 'implementation'): Promise<void>;
@@ -79,8 +79,12 @@ ${actionLines}
79
79
  /**
80
80
  * Save session to team memory
81
81
  * Called on: task complete, subtask complete, session abandoned
82
+ *
83
+ * @param sessionId - Session to save
84
+ * @param triggerReason - Why the save was triggered
85
+ * @param taskType - Task type for shouldUpdateMemory context (information/planning/implementation)
82
86
  */
83
- export async function saveToTeamMemory(sessionId, triggerReason) {
87
+ export async function saveToTeamMemory(sessionId, triggerReason, taskType) {
84
88
  const sessionState = getSessionState(sessionId);
85
89
  if (!sessionState) {
86
90
  return;
@@ -98,11 +102,11 @@ export async function saveToTeamMemory(sessionId, triggerReason) {
98
102
  // Fire-and-forget cloud sync; never block capture path
99
103
  // NOTE: Do NOT invalidate cache after sync - cache persists until CLEAR/Summary/restart
100
104
  // Next SESSION will get fresh data, current session keeps its context
101
- syncTask(task)
105
+ syncTask(task, undefined, taskType)
102
106
  .then((success) => {
103
107
  if (success) {
104
108
  markTaskSynced(task.id);
105
- console.log(`[SYNC] Task ${task.id.substring(0, 8)} synced to cloud`);
109
+ console.log(`[SYNC] Task ${task.id.substring(0, 8)} synced to cloud (type=${taskType || 'unknown'})`);
106
110
  }
107
111
  else {
108
112
  setTaskSyncError(task.id, 'Sync not enabled or team not configured');
@@ -120,8 +124,11 @@ export async function saveToTeamMemory(sessionId, triggerReason) {
120
124
  * Build task data from session state and steps
121
125
  */
122
126
  async function buildTaskFromSession(sessionState, steps, triggerReason) {
123
- // Aggregate files from steps (tool_use actions)
124
- const stepFiles = steps.flatMap(s => s.files);
127
+ // Aggregate files from steps - ONLY edit/write actions, not read
128
+ // For shouldUpdateMemory: "files modified" means actual changes, not reads
129
+ const stepFiles = steps
130
+ .filter(s => s.action_type === 'edit' || s.action_type === 'write')
131
+ .flatMap(s => s.files);
125
132
  // Also extract file paths mentioned in reasoning text (Claude's text responses)
126
133
  const reasoningFiles = steps
127
134
  .filter(s => s.reasoning)
@@ -146,9 +153,11 @@ async function buildTaskFromSession(sessionState, steps, triggerReason) {
146
153
  return `${s.action_type}: ${s.files.length} files`;
147
154
  });
148
155
  // Try to use Anthropic Haiku for better reasoning & decisions extraction
149
- let reasoningTrace = basicReasoningTrace;
156
+ let reasoningTrace = basicReasoningTrace; // strings are valid ReasoningTraceEntry
150
157
  let decisions = [];
151
158
  let constraints = sessionState.constraints || [];
159
+ let summary = undefined;
160
+ let systemName = undefined; // Parent system anchor for semantic search
152
161
  if (isReasoningExtractionAvailable()) {
153
162
  try {
154
163
  // Group steps by reasoning to avoid duplicates and preserve action context
@@ -166,6 +175,12 @@ async function buildTaskFromSession(sessionState, steps, triggerReason) {
166
175
  }
167
176
  if (formattedSteps.length > 50) {
168
177
  const extracted = await extractReasoningAndDecisions(formattedSteps, sessionState.original_goal || '');
178
+ if (extracted.system_name) {
179
+ systemName = extracted.system_name;
180
+ }
181
+ if (extracted.summary) {
182
+ summary = extracted.summary;
183
+ }
169
184
  if (extracted.reasoning_trace.length > 0) {
170
185
  reasoningTrace = extracted.reasoning_trace;
171
186
  }
@@ -178,11 +193,14 @@ async function buildTaskFromSession(sessionState, steps, triggerReason) {
178
193
  // Fall back to basic extraction
179
194
  }
180
195
  }
196
+ const original_query = sessionState.raw_user_prompt || sessionState.original_goal || 'Unknown task';
181
197
  return {
182
198
  project_path: sessionState.project_path,
183
199
  user: sessionState.user_id,
184
- original_query: sessionState.original_goal || 'Unknown task',
200
+ original_query,
185
201
  goal: sessionState.original_goal,
202
+ system_name: systemName, // Parent system anchor for semantic search
203
+ summary,
186
204
  reasoning_trace: reasoningTrace,
187
205
  files_touched: filesTouched,
188
206
  decisions,
@@ -201,7 +219,7 @@ export function cleanupSession(sessionId) {
201
219
  /**
202
220
  * Save and cleanup session (for session end)
203
221
  */
204
- export async function saveAndCleanupSession(sessionId, triggerReason) {
205
- await saveToTeamMemory(sessionId, triggerReason);
222
+ export async function saveAndCleanupSession(sessionId, triggerReason, taskType) {
223
+ await saveToTeamMemory(sessionId, triggerReason, taskType);
206
224
  cleanupSession(sessionId);
207
225
  }
@@ -358,6 +358,8 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
358
358
  const textContent = extractTextContent(response);
359
359
  // Extract latest user message from request
360
360
  const latestUserMessage = extractGoalFromMessages(requestBody.messages) || '';
361
+ // DEBUG: Commented out for cleaner terminal - uncomment when debugging
362
+ // console.log(`[DEBUG] latestUserMessage extracted: "${latestUserMessage.substring(0, 80)}..." (${latestUserMessage.length} chars)`);
361
363
  // Get recent steps for context
362
364
  const recentSteps = sessionInfo.currentSession
363
365
  ? getRecentSteps(sessionInfo.currentSession.session_id, 5)
@@ -394,7 +396,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
394
396
  timestamp: Date.now(),
395
397
  keepAliveCount: 0,
396
398
  });
397
- log(`Extended cache: CAPTURE project=${cacheKey.split('/').pop()} size=${rawStr.length} sys=${hasSystem} tools=${hasTools} cache_ctrl=${hasCacheCtrl} msg_pos=${msgPos}`);
399
+ // Cache entry captured silently
398
400
  }
399
401
  // If not end_turn (tool_use in progress), skip task orchestration but keep session
400
402
  if (!isEndTurn) {
@@ -402,27 +404,22 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
402
404
  if (sessionInfo.currentSession) {
403
405
  activeSessionId = sessionInfo.currentSession.session_id;
404
406
  activeSession = sessionInfo.currentSession;
407
+ // console.log(`[DEBUG] PATH A: Reusing existing session ${activeSessionId}, raw_user_prompt="${activeSession.raw_user_prompt?.substring(0, 50)}"`);
405
408
  }
406
409
  else if (!activeSession) {
407
410
  // First request, create session without task analysis
411
+ // NOTE: Don't set original_goal to user prompt - let analyzeTaskContext synthesize it
412
+ // If we set it here, Haiku will "keep it stable" instead of synthesizing
408
413
  const newSessionId = randomUUID();
409
- // Extract clean goal summary instead of using raw text
410
- let goalSummary = latestUserMessage.substring(0, 500) || 'Task in progress';
411
- if (isIntentExtractionAvailable() && latestUserMessage.length > 10) {
412
- try {
413
- const intentData = await extractIntent(latestUserMessage);
414
- goalSummary = intentData.goal;
415
- }
416
- catch {
417
- // Keep fallback goalSummary
418
- }
419
- }
414
+ // console.log(`[DEBUG] PATH B: Creating NEW session, raw_user_prompt will be: "${latestUserMessage.substring(0, 50)}"`);
420
415
  activeSession = createSessionState({
421
416
  session_id: newSessionId,
422
417
  project_path: sessionInfo.projectPath,
423
- original_goal: goalSummary,
418
+ original_goal: '', // Empty - will be synthesized by analyzeTaskContext later
419
+ raw_user_prompt: latestUserMessage.substring(0, 500),
424
420
  task_type: 'main',
425
421
  });
422
+ // console.log(`[DEBUG] PATH B: Session created, raw_user_prompt="${activeSession.raw_user_prompt?.substring(0, 50)}"`);
426
423
  activeSessionId = newSessionId;
427
424
  activeSessions.set(newSessionId, {
428
425
  sessionId: newSessionId,
@@ -443,6 +440,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
443
440
  msg: 'Task analysis',
444
441
  action: taskAnalysis.action,
445
442
  task_type: taskAnalysis.task_type,
443
+ goal: taskAnalysis.current_goal?.substring(0, 50),
446
444
  reasoning: taskAnalysis.reasoning,
447
445
  });
448
446
  // TASK LOG: Analysis result
@@ -450,6 +448,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
450
448
  sessionId: sessionInfo.sessionId,
451
449
  action: taskAnalysis.action,
452
450
  task_type: taskAnalysis.task_type,
451
+ goal: taskAnalysis.current_goal || '',
453
452
  reasoning: taskAnalysis.reasoning || '',
454
453
  userMessage: latestUserMessage.substring(0, 80),
455
454
  hasCurrentSession: !!sessionInfo.currentSession,
@@ -473,11 +472,22 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
473
472
  if (sessionInfo.currentSession) {
474
473
  activeSessionId = sessionInfo.currentSession.session_id;
475
474
  activeSession = sessionInfo.currentSession;
475
+ // Update goal if Haiku detected a new instruction from user
476
+ // (same task/topic, but new specific instruction)
477
+ if (taskAnalysis.current_goal &&
478
+ taskAnalysis.current_goal !== activeSession.original_goal &&
479
+ latestUserMessage.length > 30) {
480
+ updateSessionState(activeSessionId, {
481
+ original_goal: taskAnalysis.current_goal,
482
+ });
483
+ activeSession.original_goal = taskAnalysis.current_goal;
484
+ }
476
485
  // TASK LOG: Continue existing session
477
486
  taskLog('ORCHESTRATION_CONTINUE', {
478
487
  sessionId: activeSessionId,
479
488
  source: 'current_session',
480
489
  goal: activeSession.original_goal,
490
+ goalUpdated: taskAnalysis.current_goal !== activeSession.original_goal,
481
491
  });
482
492
  }
483
493
  else if (sessionInfo.completedSession) {
@@ -486,6 +496,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
486
496
  activeSession = sessionInfo.completedSession;
487
497
  updateSessionState(activeSessionId, {
488
498
  status: 'active',
499
+ original_goal: taskAnalysis.current_goal || activeSession.original_goal,
489
500
  });
490
501
  activeSession.status = 'active';
491
502
  activeSessions.set(activeSessionId, {
@@ -510,7 +521,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
510
521
  }
511
522
  // Extract full intent for new task (goal, scope, constraints, keywords)
512
523
  let intentData = {
513
- goal: latestUserMessage.substring(0, 500),
524
+ goal: taskAnalysis.current_goal,
514
525
  expected_scope: [],
515
526
  constraints: [],
516
527
  keywords: [],
@@ -540,15 +551,19 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
540
551
  }
541
552
  }
542
553
  const newSessionId = randomUUID();
554
+ // DEBUG: Commented out for cleaner terminal - uncomment when debugging
555
+ // console.log(`[DEBUG] PATH C (new_task): Creating session, latestUserMessage="${latestUserMessage.substring(0, 50)}", intentData.goal="${intentData.goal?.substring(0, 50)}"`);
543
556
  activeSession = createSessionState({
544
557
  session_id: newSessionId,
545
558
  project_path: sessionInfo.projectPath,
546
559
  original_goal: intentData.goal,
560
+ raw_user_prompt: latestUserMessage.substring(0, 500),
547
561
  expected_scope: intentData.expected_scope,
548
562
  constraints: intentData.constraints,
549
563
  keywords: intentData.keywords,
550
564
  task_type: 'main',
551
565
  });
566
+ // console.log(`[DEBUG] PATH C: Session created, raw_user_prompt="${activeSession.raw_user_prompt?.substring(0, 50)}"`);
552
567
  activeSessionId = newSessionId;
553
568
  activeSessions.set(newSessionId, {
554
569
  sessionId: newSessionId,
@@ -580,7 +595,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
580
595
  final_response: textContent.substring(0, 10000),
581
596
  });
582
597
  // Save to team memory and mark complete
583
- await saveToTeamMemory(newSessionId, 'complete');
598
+ await saveToTeamMemory(newSessionId, 'complete', taskAnalysis.task_type);
584
599
  markSessionCompleted(newSessionId);
585
600
  }
586
601
  else if (taskAnalysis.task_type === 'information' && actions.length > 0) {
@@ -597,7 +612,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
597
612
  case 'subtask': {
598
613
  // Extract intent for subtask
599
614
  let intentData = {
600
- goal: latestUserMessage.substring(0, 500),
615
+ goal: taskAnalysis.current_goal,
601
616
  expected_scope: [],
602
617
  constraints: [],
603
618
  keywords: [],
@@ -623,6 +638,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
623
638
  session_id: subtaskId,
624
639
  project_path: sessionInfo.projectPath,
625
640
  original_goal: intentData.goal,
641
+ raw_user_prompt: latestUserMessage.substring(0, 500),
626
642
  expected_scope: intentData.expected_scope,
627
643
  constraints: intentData.constraints,
628
644
  keywords: intentData.keywords,
@@ -647,7 +663,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
647
663
  case 'parallel_task': {
648
664
  // Extract intent for parallel task
649
665
  let intentData = {
650
- goal: latestUserMessage.substring(0, 500),
666
+ goal: taskAnalysis.current_goal,
651
667
  expected_scope: [],
652
668
  constraints: [],
653
669
  keywords: [],
@@ -673,6 +689,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
673
689
  session_id: parallelId,
674
690
  project_path: sessionInfo.projectPath,
675
691
  original_goal: intentData.goal,
692
+ raw_user_prompt: latestUserMessage.substring(0, 500),
676
693
  expected_scope: intentData.expected_scope,
677
694
  constraints: intentData.constraints,
678
695
  keywords: intentData.keywords,
@@ -698,11 +715,18 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
698
715
  // Save to team memory and mark as completed (don't delete yet - keep for new_task detection)
699
716
  if (sessionInfo.currentSession) {
700
717
  try {
718
+ // Update goal if Haiku synthesized one and current is empty
719
+ if (taskAnalysis.current_goal && !sessionInfo.currentSession.original_goal) {
720
+ updateSessionState(sessionInfo.currentSession.session_id, {
721
+ original_goal: taskAnalysis.current_goal,
722
+ });
723
+ sessionInfo.currentSession.original_goal = taskAnalysis.current_goal;
724
+ }
701
725
  // Set final_response BEFORE saving so reasoning extraction has the data
702
726
  updateSessionState(sessionInfo.currentSession.session_id, {
703
727
  final_response: textContent.substring(0, 10000),
704
728
  });
705
- await saveToTeamMemory(sessionInfo.currentSession.session_id, 'complete');
729
+ await saveToTeamMemory(sessionInfo.currentSession.session_id, 'complete', taskAnalysis.task_type);
706
730
  markSessionCompleted(sessionInfo.currentSession.session_id);
707
731
  activeSessions.delete(sessionInfo.currentSession.session_id);
708
732
  lastDriftResults.delete(sessionInfo.currentSession.session_id);
@@ -745,34 +769,26 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
745
769
  // Example: user asks clarification question, answer is provided in single turn
746
770
  try {
747
771
  const newSessionId = randomUUID();
748
- // Extract clean goal summary instead of using raw text
749
- let goalSummary = latestUserMessage.substring(0, 500);
750
- if (isIntentExtractionAvailable() && latestUserMessage.length > 10) {
751
- try {
752
- const intentData = await extractIntent(latestUserMessage);
753
- goalSummary = intentData.goal;
754
- }
755
- catch {
756
- // Keep fallback goalSummary
757
- }
758
- }
772
+ // DEBUG: Commented out for cleaner terminal - uncomment when debugging
773
+ // console.log(`[DEBUG] PATH D (instant_complete): latestUserMessage="${latestUserMessage.substring(0, 50)}"`);
759
774
  const instantSession = createSessionState({
760
775
  session_id: newSessionId,
761
776
  project_path: sessionInfo.projectPath,
762
- original_goal: goalSummary,
777
+ original_goal: taskAnalysis.current_goal || '', // Don't fallback to user prompt
778
+ raw_user_prompt: latestUserMessage.substring(0, 500),
763
779
  task_type: 'main',
764
780
  });
765
781
  // Set final_response for reasoning extraction
766
782
  updateSessionState(newSessionId, {
767
783
  final_response: textContent.substring(0, 10000),
768
784
  });
769
- await saveToTeamMemory(newSessionId, 'complete');
785
+ await saveToTeamMemory(newSessionId, 'complete', taskAnalysis.task_type);
770
786
  markSessionCompleted(newSessionId);
771
787
  logger.info({ msg: 'Instant complete - new task saved immediately', sessionId: newSessionId.substring(0, 8) });
772
788
  // TASK LOG: Instant complete (new task that finished in one turn)
773
789
  taskLog('ORCHESTRATION_TASK_COMPLETE', {
774
790
  sessionId: newSessionId,
775
- goal: goalSummary,
791
+ goal: taskAnalysis.current_goal || '',
776
792
  source: 'instant_complete',
777
793
  });
778
794
  }
@@ -787,7 +803,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
787
803
  if (sessionInfo.currentSession) {
788
804
  const parentId = sessionInfo.currentSession.parent_session_id;
789
805
  try {
790
- await saveToTeamMemory(sessionInfo.currentSession.session_id, 'complete');
806
+ await saveToTeamMemory(sessionInfo.currentSession.session_id, 'complete', taskAnalysis.task_type);
791
807
  markSessionCompleted(sessionInfo.currentSession.session_id);
792
808
  activeSessions.delete(sessionInfo.currentSession.session_id);
793
809
  lastDriftResults.delete(sessionInfo.currentSession.session_id);
@@ -820,7 +836,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
820
836
  // Fall back to existing session or create new with intent extraction
821
837
  if (!sessionInfo.currentSession) {
822
838
  let intentData = {
823
- goal: latestUserMessage.substring(0, 500),
839
+ goal: '', // Don't copy user prompt - let extractIntent synthesize or leave empty
824
840
  expected_scope: [],
825
841
  constraints: [],
826
842
  keywords: [],
@@ -844,6 +860,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
844
860
  session_id: newSessionId,
845
861
  project_path: sessionInfo.projectPath,
846
862
  original_goal: intentData.goal,
863
+ raw_user_prompt: latestUserMessage.substring(0, 500),
847
864
  expected_scope: intentData.expected_scope,
848
865
  constraints: intentData.constraints,
849
866
  keywords: intentData.keywords,
@@ -862,7 +879,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
862
879
  });
863
880
  if (!sessionInfo.currentSession) {
864
881
  let intentData = {
865
- goal: latestUserMessage.substring(0, 500),
882
+ goal: '', // Don't copy user prompt - let extractIntent synthesize or leave empty
866
883
  expected_scope: [],
867
884
  constraints: [],
868
885
  keywords: [],
@@ -887,6 +904,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
887
904
  session_id: newSessionId,
888
905
  project_path: sessionInfo.projectPath,
889
906
  original_goal: intentData.goal,
907
+ raw_user_prompt: latestUserMessage.substring(0, 500),
890
908
  expected_scope: intentData.expected_scope,
891
909
  constraints: intentData.constraints,
892
910
  keywords: intentData.keywords,
@@ -927,16 +945,32 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
927
945
  // === CLEAR MODE PRE-COMPUTE (85% threshold) ===
928
946
  // Pre-compute summary before hitting 100% threshold to avoid blocking Haiku call
929
947
  const preComputeThreshold = Math.floor(config.TOKEN_CLEAR_THRESHOLD * 0.85);
948
+ // DEBUG: Commented out for cleaner terminal - uncomment when debugging
949
+ // console.log('[CLEAR-PRECOMPUTE] ═══════════════════════════════════════');
950
+ // console.log('[CLEAR-PRECOMPUTE] actualContextSize:', actualContextSize);
951
+ // console.log('[CLEAR-PRECOMPUTE] preComputeThreshold:', preComputeThreshold);
952
+ // console.log('[CLEAR-PRECOMPUTE] hasActiveSession:', !!activeSession);
953
+ // console.log('[CLEAR-PRECOMPUTE] hasPendingSummary:', !!activeSession?.pending_clear_summary);
954
+ // console.log('[CLEAR-PRECOMPUTE] isSummaryAvailable:', isSummaryAvailable());
955
+ // console.log('[CLEAR-PRECOMPUTE] shouldPrecompute:',
956
+ // !!activeSession &&
957
+ // actualContextSize > preComputeThreshold &&
958
+ // !activeSession?.pending_clear_summary &&
959
+ // isSummaryAvailable());
960
+ // console.log('[CLEAR-PRECOMPUTE] ═══════════════════════════════════════');
930
961
  // Use actualContextSize (cacheCreation + cacheRead) as the real context size
931
962
  if (activeSession &&
932
963
  actualContextSize > preComputeThreshold &&
933
964
  !activeSession.pending_clear_summary &&
934
965
  isSummaryAvailable()) {
966
+ // console.log('[CLEAR-PRECOMPUTE] >>> STARTING SUMMARY GENERATION <<<');
935
967
  // Get all validated steps for comprehensive summary
936
968
  const allSteps = getValidatedSteps(activeSessionId);
969
+ // console.log('[CLEAR-PRECOMPUTE] validatedSteps:', allSteps.length);
937
970
  // Generate summary asynchronously (fire-and-forget)
938
971
  generateSessionSummary(activeSession, allSteps, 15000).then(summary => {
939
972
  updateSessionState(activeSessionId, { pending_clear_summary: summary });
973
+ // console.log('[CLEAR-PRECOMPUTE] >>> SUMMARY SAVED <<<', summary.length, 'chars');
940
974
  logger.info({
941
975
  msg: 'CLEAR summary pre-computed',
942
976
  actualContextSize,
@@ -944,6 +978,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
944
978
  summaryLength: summary.length,
945
979
  });
946
980
  }).catch(err => {
981
+ // console.log('[CLEAR-PRECOMPUTE] >>> SUMMARY FAILED <<<', String(err));
947
982
  logger.info({ msg: 'CLEAR summary generation failed', error: String(err) });
948
983
  });
949
984
  }
@@ -1166,7 +1201,8 @@ export async function startServer(options = {}) {
1166
1201
  // Set debug mode based on flag
1167
1202
  if (options.debug) {
1168
1203
  setDebugMode(true);
1169
- console.log('[DEBUG] Logging to grov-proxy.log');
1204
+ // DEBUG: Commented out for cleaner terminal - uncomment when debugging
1205
+ // console.log('[DEBUG] Logging to grov-proxy.log');
1170
1206
  }
1171
1207
  const server = createServer();
1172
1208
  // Set server logger for background tasks
@@ -1194,11 +1230,9 @@ export async function startServer(options = {}) {
1194
1230
  if (extendedCacheTimer) {
1195
1231
  clearInterval(extendedCacheTimer);
1196
1232
  extendedCacheTimer = null;
1197
- log('Extended cache: timer stopped');
1198
1233
  }
1199
1234
  // 2. Clear sensitive cache data
1200
1235
  if (extendedCache.size > 0) {
1201
- log(`Extended cache: clearing ${extendedCache.size} entries`);
1202
1236
  for (const entry of extendedCache.values()) {
1203
1237
  for (const key of Object.keys(entry.headers)) {
1204
1238
  entry.headers[key] = '';
@@ -1,6 +1,7 @@
1
1
  export declare function setDebugMode(enabled: boolean): void;
2
+ export declare function isDebugMode(): boolean;
2
3
  export declare function getNextRequestId(): number;
3
- export declare function taskLog(event: string, data: Record<string, unknown>): void;
4
+ export declare function taskLog(_event: string, _data: Record<string, unknown>): void;
4
5
  interface ProxyLogEntry {
5
6
  timestamp: string;
6
7
  requestId: number;
@@ -8,21 +8,27 @@ const TASK_LOG_PATH = path.join(process.cwd(), 'grov-task.log');
8
8
  export function setDebugMode(enabled) {
9
9
  debugMode = enabled;
10
10
  }
11
+ export function isDebugMode() {
12
+ return debugMode;
13
+ }
11
14
  export function getNextRequestId() {
12
15
  return ++requestCounter;
13
16
  }
14
- export function taskLog(event, data) {
17
+ export function taskLog(_event, _data) {
18
+ // Disabled - minimal logging mode
19
+ // To re-enable, set DEBUG_TASK_LOG=true
20
+ if (process.env.DEBUG_TASK_LOG !== 'true')
21
+ return;
15
22
  const timestamp = new Date().toISOString();
16
- const sessionId = data.sessionId ? String(data.sessionId).substring(0, 8) : '-';
17
- // Format: [timestamp] [session] EVENT: key=value key=value
18
- const kvPairs = Object.entries(data)
23
+ const sessionId = _data.sessionId ? String(_data.sessionId).substring(0, 8) : '-';
24
+ const kvPairs = Object.entries(_data)
19
25
  .filter(([k]) => k !== 'sessionId')
20
26
  .map(([k, v]) => {
21
27
  const val = typeof v === 'string' ? v.substring(0, 100) : JSON.stringify(v);
22
28
  return `${k}=${val}`;
23
29
  })
24
30
  .join(' | ');
25
- const line = `[${timestamp}] [${sessionId}] ${event}: ${kvPairs}\n`;
31
+ const line = `[${timestamp}] [${sessionId}] ${_event}: ${kvPairs}\n`;
26
32
  fs.appendFileSync(TASK_LOG_PATH, line);
27
33
  }
28
34
  export function proxyLog(entry) {