grov 0.5.3 → 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 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
 
@@ -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
 
@@ -209,7 +221,7 @@ YOU MAY SKIP EXPLORE AGENTS for files mentioned above.
209
221
  - [x] Anti-drift detection & correction
210
222
  - [x] Team sync (cloud backend)
211
223
  - [x] Web dashboard
212
- - [ ] Semantic search
224
+ - [x] Hybrid search (semantic + lexical)
213
225
  - [ ] VS Code extension
214
226
 
215
227
  ## Contributing
@@ -32,7 +32,6 @@ export interface TaskAnalysis {
32
32
  task_type: 'information' | 'planning' | 'implementation';
33
33
  action: 'continue' | 'new_task' | 'subtask' | 'parallel_task' | 'task_complete' | 'subtask_complete';
34
34
  task_id: string;
35
- current_goal: string;
36
35
  parent_task_id?: string;
37
36
  reasoning: string;
38
37
  step_reasoning?: string;
@@ -311,7 +311,6 @@ Return a JSON object with these fields:
311
311
  - task_type: one of "information", "planning", or "implementation"
312
312
  - action: one of "continue", "task_complete", "new_task", or "subtask_complete"
313
313
  - task_id: existing session_id "${currentSession?.session_id || 'NEW'}" or "NEW" for new task
314
- - current_goal: the goal based on the latest user message
315
314
  - reasoning: brief explanation of why you made this decision${compressionInstruction}
316
315
  </output>
317
316
 
@@ -481,7 +480,7 @@ RESPONSE RULES:
481
480
  if (!needsCompression && assistantResponse.length > 0) {
482
481
  analysis.step_reasoning = assistantResponse.substring(0, 1000);
483
482
  }
484
- debugLLM('analyzeTaskContext', `Result: task_type=${analysis.task_type}, action=${analysis.action}, goal="${analysis.current_goal?.substring(0, 50) || 'N/A'}" reasoning="${analysis.reasoning?.substring(0, 150) || 'none'}"`);
483
+ debugLLM('analyzeTaskContext', `Result: task_type=${analysis.task_type}, action=${analysis.action}, reasoning="${analysis.reasoning?.substring(0, 150) || 'none'}"`);
485
484
  return analysis;
486
485
  }
487
486
  catch (parseError) {
@@ -491,7 +490,6 @@ RESPONSE RULES:
491
490
  task_type: 'implementation',
492
491
  action: currentSession ? 'continue' : 'new_task',
493
492
  task_id: currentSession?.session_id || 'NEW',
494
- current_goal: latestUserMessage.substring(0, 200),
495
493
  reasoning: 'Fallback due to parse error',
496
494
  step_reasoning: assistantResponse.substring(0, 1000),
497
495
  };
@@ -406,10 +406,21 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
406
406
  else if (!activeSession) {
407
407
  // First request, create session without task analysis
408
408
  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
+ }
409
420
  activeSession = createSessionState({
410
421
  session_id: newSessionId,
411
422
  project_path: sessionInfo.projectPath,
412
- original_goal: latestUserMessage.substring(0, 500) || 'Task in progress',
423
+ original_goal: goalSummary,
413
424
  task_type: 'main',
414
425
  });
415
426
  activeSessionId = newSessionId;
@@ -432,7 +443,6 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
432
443
  msg: 'Task analysis',
433
444
  action: taskAnalysis.action,
434
445
  task_type: taskAnalysis.task_type,
435
- goal: taskAnalysis.current_goal?.substring(0, 50),
436
446
  reasoning: taskAnalysis.reasoning,
437
447
  });
438
448
  // TASK LOG: Analysis result
@@ -440,7 +450,6 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
440
450
  sessionId: sessionInfo.sessionId,
441
451
  action: taskAnalysis.action,
442
452
  task_type: taskAnalysis.task_type,
443
- goal: taskAnalysis.current_goal || '',
444
453
  reasoning: taskAnalysis.reasoning || '',
445
454
  userMessage: latestUserMessage.substring(0, 80),
446
455
  hasCurrentSession: !!sessionInfo.currentSession,
@@ -464,22 +473,11 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
464
473
  if (sessionInfo.currentSession) {
465
474
  activeSessionId = sessionInfo.currentSession.session_id;
466
475
  activeSession = sessionInfo.currentSession;
467
- // Update goal if Haiku detected a new instruction from user
468
- // (same task/topic, but new specific instruction)
469
- if (taskAnalysis.current_goal &&
470
- taskAnalysis.current_goal !== activeSession.original_goal &&
471
- latestUserMessage.length > 30) {
472
- updateSessionState(activeSessionId, {
473
- original_goal: taskAnalysis.current_goal,
474
- });
475
- activeSession.original_goal = taskAnalysis.current_goal;
476
- }
477
476
  // TASK LOG: Continue existing session
478
477
  taskLog('ORCHESTRATION_CONTINUE', {
479
478
  sessionId: activeSessionId,
480
479
  source: 'current_session',
481
480
  goal: activeSession.original_goal,
482
- goalUpdated: taskAnalysis.current_goal !== activeSession.original_goal,
483
481
  });
484
482
  }
485
483
  else if (sessionInfo.completedSession) {
@@ -488,7 +486,6 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
488
486
  activeSession = sessionInfo.completedSession;
489
487
  updateSessionState(activeSessionId, {
490
488
  status: 'active',
491
- original_goal: taskAnalysis.current_goal || activeSession.original_goal,
492
489
  });
493
490
  activeSession.status = 'active';
494
491
  activeSessions.set(activeSessionId, {
@@ -513,7 +510,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
513
510
  }
514
511
  // Extract full intent for new task (goal, scope, constraints, keywords)
515
512
  let intentData = {
516
- goal: taskAnalysis.current_goal,
513
+ goal: latestUserMessage.substring(0, 500),
517
514
  expected_scope: [],
518
515
  constraints: [],
519
516
  keywords: [],
@@ -600,7 +597,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
600
597
  case 'subtask': {
601
598
  // Extract intent for subtask
602
599
  let intentData = {
603
- goal: taskAnalysis.current_goal,
600
+ goal: latestUserMessage.substring(0, 500),
604
601
  expected_scope: [],
605
602
  constraints: [],
606
603
  keywords: [],
@@ -650,7 +647,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
650
647
  case 'parallel_task': {
651
648
  // Extract intent for parallel task
652
649
  let intentData = {
653
- goal: taskAnalysis.current_goal,
650
+ goal: latestUserMessage.substring(0, 500),
654
651
  expected_scope: [],
655
652
  constraints: [],
656
653
  keywords: [],
@@ -748,10 +745,21 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
748
745
  // Example: user asks clarification question, answer is provided in single turn
749
746
  try {
750
747
  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
+ }
751
759
  const instantSession = createSessionState({
752
760
  session_id: newSessionId,
753
761
  project_path: sessionInfo.projectPath,
754
- original_goal: taskAnalysis.current_goal || latestUserMessage.substring(0, 500),
762
+ original_goal: goalSummary,
755
763
  task_type: 'main',
756
764
  });
757
765
  // Set final_response for reasoning extraction
@@ -764,7 +772,7 @@ async function postProcessResponse(response, sessionInfo, requestBody, logger, e
764
772
  // TASK LOG: Instant complete (new task that finished in one turn)
765
773
  taskLog('ORCHESTRATION_TASK_COMPLETE', {
766
774
  sessionId: newSessionId,
767
- goal: taskAnalysis.current_goal || latestUserMessage.substring(0, 80),
775
+ goal: goalSummary,
768
776
  source: 'instant_complete',
769
777
  });
770
778
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grov",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
4
4
  "description": "Collective AI memory for Claude Code - captures reasoning from sessions and injects context into future sessions",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",
@@ -13,6 +13,7 @@
13
13
  "dist/**/*.d.ts",
14
14
  "!dist/**/*.test.js",
15
15
  "!dist/**/*.test.d.ts",
16
+ "postinstall.js",
16
17
  "README.md",
17
18
  "LICENSE"
18
19
  ],
@@ -34,6 +35,7 @@
34
35
  "test:watch": "vitest",
35
36
  "test:all": "turbo run test",
36
37
  "typecheck": "turbo run typecheck",
38
+ "postinstall": "node postinstall.js",
37
39
  "prepublishOnly": "npm run build && npm test && npm run security:scan",
38
40
  "prepare": "husky",
39
41
  "security:scan": "./scripts/scan-secrets.sh"
package/postinstall.js ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+
3
+ const green = '\x1b[32m';
4
+ const cyan = '\x1b[36m';
5
+ const dim = '\x1b[2m';
6
+ const reset = '\x1b[0m';
7
+ const bold = '\x1b[1m';
8
+
9
+ console.log(`
10
+ ${green}✓${reset} ${bold}grov installed successfully${reset}
11
+
12
+ ${dim}Sync your AI memories across your team:${reset}
13
+ ${cyan}https://app.grov.dev${reset}
14
+
15
+ ${dim}Quick start:${reset}
16
+ ${green}grov init${reset} Configure proxy
17
+ ${green}grov proxy${reset} Start capturing
18
+ ${green}grov login${reset} Connect to dashboard
19
+ `);