grov 0.1.1 → 0.2.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.
Files changed (39) hide show
  1. package/README.md +66 -87
  2. package/dist/cli.js +23 -37
  3. package/dist/commands/capture.js +1 -1
  4. package/dist/commands/disable.d.ts +1 -0
  5. package/dist/commands/disable.js +14 -0
  6. package/dist/commands/drift-test.js +56 -68
  7. package/dist/commands/init.js +29 -17
  8. package/dist/commands/proxy-status.d.ts +1 -0
  9. package/dist/commands/proxy-status.js +32 -0
  10. package/dist/commands/unregister.js +7 -1
  11. package/dist/lib/correction-builder-proxy.d.ts +16 -0
  12. package/dist/lib/correction-builder-proxy.js +125 -0
  13. package/dist/lib/correction-builder.js +1 -1
  14. package/dist/lib/drift-checker-proxy.d.ts +63 -0
  15. package/dist/lib/drift-checker-proxy.js +373 -0
  16. package/dist/lib/drift-checker.js +1 -1
  17. package/dist/lib/hooks.d.ts +11 -0
  18. package/dist/lib/hooks.js +33 -0
  19. package/dist/lib/llm-extractor.d.ts +60 -11
  20. package/dist/lib/llm-extractor.js +419 -98
  21. package/dist/lib/settings.d.ts +19 -0
  22. package/dist/lib/settings.js +63 -0
  23. package/dist/lib/store.d.ts +201 -43
  24. package/dist/lib/store.js +653 -90
  25. package/dist/proxy/action-parser.d.ts +58 -0
  26. package/dist/proxy/action-parser.js +196 -0
  27. package/dist/proxy/config.d.ts +26 -0
  28. package/dist/proxy/config.js +67 -0
  29. package/dist/proxy/forwarder.d.ts +24 -0
  30. package/dist/proxy/forwarder.js +119 -0
  31. package/dist/proxy/index.d.ts +1 -0
  32. package/dist/proxy/index.js +30 -0
  33. package/dist/proxy/request-processor.d.ts +12 -0
  34. package/dist/proxy/request-processor.js +94 -0
  35. package/dist/proxy/response-processor.d.ts +14 -0
  36. package/dist/proxy/response-processor.js +128 -0
  37. package/dist/proxy/server.d.ts +9 -0
  38. package/dist/proxy/server.js +911 -0
  39. package/package.json +10 -4
package/README.md CHANGED
@@ -1,15 +1,25 @@
1
+ <p align="center">
2
+ <img src="landing/public/images/logos/grov-nobg.png" alt="grov logo" width="120">
3
+ </p>
4
+
1
5
  <h1 align="center">grov</h1>
2
6
 
3
7
  <p align="center"><strong>Collective AI memory for engineering teams.</strong></p>
4
8
 
9
+ <p align="center">
10
+ <a href="https://www.npmjs.com/package/grov"><img src="https://img.shields.io/npm/v/grov" alt="npm version"></a>
11
+ <a href="https://www.npmjs.com/package/grov"><img src="https://img.shields.io/npm/dm/grov" alt="npm downloads"></a>
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
+ </p>
14
+
5
15
  <p align="center">
6
16
  <a href="https://grov.dev">Website</a> •
7
17
  <a href="#quick-start">Quick Start</a> •
8
- <a href="#roadmap">Roadmap</a> •
18
+ <a href="#advanced-features">Advanced</a> •
9
19
  <a href="#contributing">Contributing</a>
10
20
  </p>
11
21
 
12
- Grov automatically captures reasoning from your Claude Code sessions and injects relevant context into future sessions. Your AI remembers what it learned.
22
+ Grov captures reasoning from your Claude Code sessions and injects it into future sessions. Your AI remembers what it learned.
13
23
 
14
24
  ## The Problem
15
25
 
@@ -19,7 +29,7 @@ Every time you start a new Claude Code session:
19
29
  - It rediscovers patterns you've already established
20
30
  - You burn tokens on redundant exploration
21
31
 
22
- **Measured impact:** A typical task takes 10+ minutes, 7%+ token usage, and 3+ explore agents just to understand the codebase.
32
+ **Measured impact:** A typical task takes 10+ minutes, 7%+ token usage, and 3+ explore agents just to understand the codebase.*
23
33
 
24
34
  ## The Solution
25
35
 
@@ -27,6 +37,18 @@ Grov captures what Claude learns and injects it back on the next session.
27
37
 
28
38
  **With grov:** Same task takes ~1-2 minutes, <2% tokens, 0 explore agents. Claude reads files directly because it already has context.
29
39
 
40
+ <sub>*Based on controlled testing: Auth file modification task without grov launched 3+ subagents and read ~10 files for exploration. With grov (after initial memory capture), an adjacent task went directly to reading relevant files with full context.</sub>
41
+
42
+ ## Quick Start
43
+
44
+ ```bash
45
+ npm install -g grov # Install
46
+ grov init # Configure (one-time)
47
+ grov proxy # Start (keep running)
48
+ ```
49
+
50
+ Then use Claude Code normally in another terminal. That's it.
51
+
30
52
  ## How It Works
31
53
 
32
54
  ```
@@ -42,83 +64,60 @@ Session 2: User asks about related feature
42
64
  Claude skips exploration, reads files directly
43
65
  ```
44
66
 
45
- **Zero friction.** You don't change anything about how you use Claude Code.
46
-
47
- ## Quick Start
48
-
49
- ```bash
50
- # Install globally
51
- npm install -g grov
52
-
53
- # One-time setup (registers hooks in Claude Code)
54
- grov init
55
-
56
- # Done. Use Claude Code normally.
57
- claude
58
- ```
59
-
60
- That's it. Grov works invisibly in the background.
61
-
62
67
  ## Commands
63
68
 
64
69
  ```bash
65
- grov init # Register hooks (run once)
66
- grov status # Show captured tasks for current project
67
- grov status -a # Show all tasks (including partial/abandoned)
68
- grov unregister # Disable grov
69
- grov drift-test # Test drift detection (debug)
70
+ grov init # Configure proxy URL (one-time)
71
+ grov proxy # Start the proxy (required)
72
+ grov proxy-status # Show active sessions
73
+ grov status # Show captured tasks
74
+ grov disable # Disable grov
70
75
  ```
71
76
 
72
- ## How It Actually Works
77
+ ## Data Storage
73
78
 
74
- 1. **SessionStart hook** fires when you run `claude`
75
- - Grov queries its database for relevant past reasoning
76
- - Outputs context that Claude Code injects into the session
79
+ - **Database:** `~/.grov/memory.db` (SQLite)
80
+ - **Per-project:** Context is filtered by project path
81
+ - **Local only:** Nothing leaves your machine
77
82
 
78
- 2. **UserPromptSubmit hook** fires on every prompt
79
- - Monitors Claude's actions (files touched, commands run)
80
- - Detects scope drift and injects corrections if needed
81
- - Smart filtering skips simple prompts ("yes", "ok", "continue")
83
+ ## Requirements
82
84
 
83
- 3. **You work normally** with Claude Code
85
+ - Node.js 18+
86
+ - Claude Code
84
87
 
85
- 4. **Stop hook** fires when the session ends
86
- - Grov parses the session's JSONL file
87
- - Extracts reasoning via LLM (Claude Haiku 4.5)
88
- - Stores structured summary in SQLite
88
+ ---
89
89
 
90
- 5. **Next session**, Claude has context and skips re-exploration
90
+ ## Advanced Features
91
91
 
92
- ## Anti-Drift Detection
92
+ ### Anti-Drift Detection
93
93
 
94
- Grov monitors what Claude **does** (not what you ask) and warns if it drifts from your original goal.
94
+ Grov monitors what Claude **does** (not what you ask) and corrects if it drifts from your goal.
95
95
 
96
- **How it works:**
97
96
  - Extracts your intent from the first prompt
98
97
  - Monitors Claude's actions (file edits, commands, explorations)
99
- - Uses Claude Haiku 4.5 to score alignment (1-10)
100
- - Injects corrections at 5 levels: nudge → correct → intervene → halt
101
-
102
- **Key principle:** You can explore freely. Grov watches Claude's actions, not your prompts.
98
+ - Uses Claude Haiku to score alignment (1-10)
99
+ - Injects corrections at 4 levels: nudge → correct → intervene → halt
103
100
 
104
101
  ```bash
105
- # Test drift detection manually
102
+ # Test drift detection
106
103
  grov drift-test "refactor the auth system" --goal "fix login bug"
107
104
  ```
108
105
 
109
- ## Environment Variables
106
+ ### Environment Variables
110
107
 
111
108
  ```bash
112
109
  # Required for drift detection and LLM extraction
113
110
  export ANTHROPIC_API_KEY=sk-ant-...
114
111
 
115
- # Optional: Override drift model (default: claude-haiku-4-5)
116
- export GROV_DRIFT_MODEL=claude-sonnet-4-20250514
112
+ # Optional
113
+ export GROV_DRIFT_MODEL=claude-sonnet-4-20250514 # Override model
114
+ export PROXY_HOST=127.0.0.1 # Proxy host
115
+ export PROXY_PORT=8080 # Proxy port
117
116
  ```
118
117
 
119
- Without an API key, grov uses basic extraction (files touched, tool usage counts) and disables drift detection.
118
+ Without an API key, grov uses basic extraction and disables drift detection.
120
119
 
121
- ## What Gets Stored
120
+ ### What Gets Stored
122
121
 
123
122
  ```json
124
123
  {
@@ -130,12 +129,11 @@ Without an API key, grov uses basic extraction (files touched, tool usage counts
130
129
  "Found refresh window was too short",
131
130
  "Extended from 5min to 15min"
132
131
  ],
133
- "status": "complete",
134
- "tags": ["auth", "session", "token"]
132
+ "status": "complete"
135
133
  }
136
134
  ```
137
135
 
138
- ## What Gets Injected
136
+ ### What Gets Injected
139
137
 
140
138
  ```
141
139
  VERIFIED CONTEXT FROM PREVIOUS SESSIONS:
@@ -146,26 +144,24 @@ VERIFIED CONTEXT FROM PREVIOUS SESSIONS:
146
144
  - Reason: Users were getting logged out during long forms
147
145
 
148
146
  YOU MAY SKIP EXPLORE AGENTS for files mentioned above.
149
- Read them directly if relevant to the current task.
150
147
  ```
151
148
 
152
- ## Data Storage
153
-
154
- - **Database:** `~/.grov/memory.db` (SQLite)
155
- - **Per-project:** Context is filtered by project path
156
- - **Local only:** Nothing leaves your machine (unless you add cloud sync)
149
+ ### How the Proxy Works
157
150
 
158
- ## Requirements
151
+ 1. **`grov init`** sets `ANTHROPIC_BASE_URL=http://127.0.0.1:8080` in Claude's settings
152
+ 2. **`grov proxy`** intercepts all API calls and:
153
+ - Extracts intent from first prompt
154
+ - Injects context from team memory
155
+ - Tracks actions and detects drift
156
+ - Saves reasoning when tasks complete
159
157
 
160
- - Node.js 18+
161
- - Claude Code v2.0+
158
+ ---
162
159
 
163
160
  ## Roadmap
164
161
 
165
162
  - [x] Local capture & inject
166
- - [x] LLM-powered extraction (Claude Haiku 4.5)
167
- - [x] Zero-friction hooks
168
- - [x] Per-prompt context injection
163
+ - [x] LLM-powered extraction
164
+ - [x] Local proxy with real-time monitoring
169
165
  - [x] Anti-drift detection & correction
170
166
  - [ ] Team sync (cloud backend)
171
167
  - [ ] Web dashboard
@@ -173,34 +169,17 @@ Read them directly if relevant to the current task.
173
169
 
174
170
  ## Contributing
175
171
 
176
- We welcome contributions! Here's how to get started:
177
-
178
172
  1. **Fork the repo** and clone locally
179
173
  2. **Install dependencies:** `npm install`
180
174
  3. **Build:** `npm run build`
181
175
  4. **Test locally:** `node dist/cli.js --help`
182
176
 
183
- ### Development
184
-
185
177
  ```bash
186
- # Watch mode for development
187
- npm run dev
188
-
189
- # Test the CLI
190
- node dist/cli.js init
191
- node dist/cli.js status
178
+ npm run dev # Watch mode
179
+ node dist/cli.js init # Test CLI
192
180
  ```
193
181
 
194
- ### Guidelines
195
-
196
- - Keep PRs focused on a single change
197
- - Follow existing code style
198
- - Update tests if applicable
199
- - Update docs if adding features
200
-
201
- ### Reporting Issues
202
-
203
- Found a bug or have a feature request? [Open an issue](https://github.com/TonyStef/Grov/issues).
182
+ Found a bug? [Open an issue](https://github.com/TonyStef/Grov/issues).
204
183
 
205
184
  ## License
206
185
 
package/dist/cli.js CHANGED
@@ -36,43 +36,21 @@ program
36
36
  .name('grov')
37
37
  .description('Collective AI memory for engineering teams')
38
38
  .version('0.1.0');
39
- // grov init - Register hooks in Claude Code
39
+ // grov init - Configure Claude Code to use grov proxy
40
40
  program
41
41
  .command('init')
42
- .description('Register grov hooks in Claude Code settings')
42
+ .description('Configure Claude Code to use grov proxy (run once)')
43
43
  .action(safeAction(async () => {
44
44
  const { init } = await import('./commands/init.js');
45
45
  await init();
46
46
  }));
47
- // grov capture - Called by Stop hook, extracts and stores reasoning
47
+ // grov disable - Remove proxy configuration
48
48
  program
49
- .command('capture')
50
- .description('Capture reasoning from current session (called by Stop hook)')
51
- .option('--session-dir <path>', 'Path to session directory')
52
- .action(safeAction(async (options) => {
53
- // SECURITY: Validate session-dir doesn't contain path traversal
54
- if (options.sessionDir && options.sessionDir.includes('..')) {
55
- throw new Error('Invalid session directory path');
56
- }
57
- const { capture } = await import('./commands/capture.js');
58
- await capture(options);
59
- }));
60
- // grov inject - Called by SessionStart hook, outputs context JSON
61
- program
62
- .command('inject')
63
- .description('Inject relevant context for new session (called by SessionStart hook)')
64
- .option('--task <description>', 'Task description from user prompt')
65
- .action(safeAction(async (options) => {
66
- const { inject } = await import('./commands/inject.js');
67
- await inject(options);
68
- }));
69
- // grov prompt-inject - Called by UserPromptSubmit hook, outputs context JSON per-turn
70
- program
71
- .command('prompt-inject')
72
- .description('Inject context before each prompt (called by UserPromptSubmit hook)')
49
+ .command('disable')
50
+ .description('Disable grov and restore direct Anthropic connection')
73
51
  .action(safeAction(async () => {
74
- const { promptInject } = await import('./commands/prompt-inject.js');
75
- await promptInject({});
52
+ const { disable } = await import('./commands/disable.js');
53
+ await disable();
76
54
  }));
77
55
  // grov status - Show stored reasoning for current project
78
56
  program
@@ -83,14 +61,6 @@ program
83
61
  const { status } = await import('./commands/status.js');
84
62
  await status(options);
85
63
  }));
86
- // grov unregister - Remove hooks from Claude Code
87
- program
88
- .command('unregister')
89
- .description('Remove grov hooks from Claude Code settings')
90
- .action(safeAction(async () => {
91
- const { unregister } = await import('./commands/unregister.js');
92
- await unregister();
93
- }));
94
64
  // grov drift-test - Test drift detection on a prompt
95
65
  program
96
66
  .command('drift-test')
@@ -103,4 +73,20 @@ program
103
73
  const { driftTest } = await import('./commands/drift-test.js');
104
74
  await driftTest(prompt, options);
105
75
  });
76
+ // grov proxy - Start the proxy server
77
+ program
78
+ .command('proxy')
79
+ .description('Start the Grov proxy server (intercepts Claude API calls)')
80
+ .action(async () => {
81
+ const { startServer } = await import('./proxy/server.js');
82
+ await startServer();
83
+ });
84
+ // grov proxy-status - Show active proxy sessions
85
+ program
86
+ .command('proxy-status')
87
+ .description('Show active proxy sessions')
88
+ .action(safeAction(async () => {
89
+ const { proxyStatus } = await import('./commands/proxy-status.js');
90
+ await proxyStatus();
91
+ }));
106
92
  program.parse();
@@ -114,7 +114,7 @@ export async function capture(options) {
114
114
  if (sessionState) {
115
115
  updateSessionState(sessionId, {
116
116
  status: finalStatus === 'complete' ? 'completed' : 'abandoned',
117
- files_explored: [...new Set([...sessionState.files_explored, ...filesTouched])],
117
+ files_explored: [...new Set([...(sessionState.files_explored || []), ...filesTouched])],
118
118
  original_goal: goal,
119
119
  });
120
120
  debugCapture('Updated session state: %s...', sessionId.substring(0, 8));
@@ -0,0 +1 @@
1
+ export declare function disable(): Promise<void>;
@@ -0,0 +1,14 @@
1
+ // grov disable - Remove proxy configuration and restore direct Anthropic connection
2
+ import { setProxyEnv, getSettingsPath } from '../lib/settings.js';
3
+ export async function disable() {
4
+ const result = setProxyEnv(false);
5
+ if (result.action === 'removed') {
6
+ console.log('Grov disabled.');
7
+ console.log(' - ANTHROPIC_BASE_URL removed from settings');
8
+ }
9
+ else {
10
+ console.log('Grov was not configured.');
11
+ }
12
+ console.log(`\nSettings file: ${getSettingsPath()}`);
13
+ console.log('\nClaude will now connect directly to Anthropic.');
14
+ }
@@ -1,17 +1,17 @@
1
1
  // grov drift-test - Debug command for testing drift detection
2
2
  // Usage: grov drift-test "your prompt here" [--session <id>] [--goal "original goal"]
3
3
  //
4
- // NOTE: This command creates mock ACTIONS from the prompt for testing.
5
- // In real usage, actions are parsed from Claude's JSONL session file.
4
+ // NOTE: This command creates mock STEPS from the prompt for testing.
5
+ // In real usage, steps are tracked by the proxy from Claude's actions.
6
6
  import 'dotenv/config';
7
7
  import { getSessionState, createSessionState } from '../lib/store.js';
8
- import { extractIntent, isAnthropicAvailable } from '../lib/llm-extractor.js';
9
- import { buildDriftCheckInput, checkDrift, checkDriftBasic, DRIFT_CONFIG } from '../lib/drift-checker.js';
10
- import { determineCorrectionLevel, buildCorrection } from '../lib/correction-builder.js';
8
+ import { extractIntent } from '../lib/llm-extractor.js';
9
+ import { checkDrift, checkDriftBasic, isDriftCheckAvailable, scoreToCorrectionLevel } from '../lib/drift-checker-proxy.js';
10
+ import { buildCorrection, formatCorrectionForInjection } from '../lib/correction-builder-proxy.js';
11
11
  export async function driftTest(prompt, options) {
12
12
  console.log('=== GROV DRIFT TEST ===\n');
13
13
  // Check API availability
14
- const llmAvailable = isAnthropicAvailable();
14
+ const llmAvailable = isDriftCheckAvailable();
15
15
  console.log(`Anthropic API: ${llmAvailable ? 'AVAILABLE' : 'NOT AVAILABLE (using fallback)'}`);
16
16
  console.log('');
17
17
  // Get or create session state
@@ -27,52 +27,21 @@ export async function driftTest(prompt, options) {
27
27
  console.log(`Constraints: ${intent.constraints.join(', ') || 'none'}`);
28
28
  console.log(`Keywords: ${intent.keywords.join(', ')}`);
29
29
  console.log('');
30
- // Create temporary session state in memory (not persisted unless session ID provided)
31
- sessionState = {
32
- session_id: options.session || 'test-session',
30
+ // Create temporary session state
31
+ sessionState = createSessionState({
32
+ session_id: options.session || 'test-session-' + Date.now(),
33
33
  project_path: process.cwd(),
34
34
  original_goal: intent.goal,
35
- actions_taken: [],
36
- files_explored: [],
37
- current_intent: undefined,
38
- drift_warnings: [],
39
- start_time: new Date().toISOString(),
40
- last_update: new Date().toISOString(),
41
- status: 'active',
42
35
  expected_scope: intent.expected_scope,
43
36
  constraints: intent.constraints,
44
- success_criteria: intent.success_criteria,
45
37
  keywords: intent.keywords,
46
- last_drift_score: undefined,
47
- escalation_count: 0,
48
- pending_recovery_plan: undefined,
49
- drift_history: [],
50
- last_checked_at: 0 // New field for action tracking
51
- };
52
- // Persist if session ID was provided
53
- if (options.session) {
54
- try {
55
- createSessionState({
56
- session_id: options.session,
57
- project_path: process.cwd(),
58
- original_goal: intent.goal,
59
- expected_scope: intent.expected_scope,
60
- constraints: intent.constraints,
61
- success_criteria: intent.success_criteria,
62
- keywords: intent.keywords
63
- });
64
- console.log(`Session state persisted: ${options.session}`);
65
- }
66
- catch {
67
- // Might already exist, ignore
68
- }
69
- }
38
+ task_type: 'main',
39
+ });
70
40
  }
71
41
  else {
72
42
  console.log(`Using existing session: ${options.session}`);
73
43
  console.log(`Original goal: ${sessionState.original_goal}`);
74
44
  console.log(`Escalation count: ${sessionState.escalation_count}`);
75
- console.log(`Drift history: ${sessionState.drift_history.length} events`);
76
45
  console.log('');
77
46
  }
78
47
  // Ensure sessionState is not null at this point
@@ -80,23 +49,42 @@ export async function driftTest(prompt, options) {
80
49
  console.error('Failed to create session state');
81
50
  process.exit(1);
82
51
  }
83
- // Create mock actions from prompt for testing
84
- // In real usage, actions are parsed from Claude's JSONL session file
52
+ // Create mock steps from prompt for testing
85
53
  const mockFiles = extractFilesFromPrompt(prompt);
86
- const mockActions = mockFiles.length > 0
54
+ const mockSteps = mockFiles.length > 0
87
55
  ? mockFiles.map((file, i) => ({
88
- type: 'edit',
56
+ id: `step-${i}`,
57
+ session_id: sessionState.session_id,
58
+ action_type: 'edit',
89
59
  files: [file],
90
- timestamp: Date.now() + i * 1000
60
+ folders: [],
61
+ timestamp: Date.now() + i * 1000,
62
+ is_validated: true,
63
+ is_key_decision: false,
64
+ keywords: [],
91
65
  }))
92
- : [{ type: 'edit', files: ['mock-file.ts'], timestamp: Date.now() }];
93
- console.log('--- Mock Actions (from prompt) ---');
66
+ : [{
67
+ id: 'step-0',
68
+ session_id: sessionState.session_id,
69
+ action_type: 'edit',
70
+ files: ['mock-file.ts'],
71
+ folders: [],
72
+ timestamp: Date.now(),
73
+ is_validated: true,
74
+ is_key_decision: false,
75
+ keywords: [],
76
+ }];
77
+ console.log('--- Mock Steps (from prompt) ---');
94
78
  console.log(`Files detected: ${mockFiles.join(', ') || 'none (using mock-file.ts)'}`);
95
79
  console.log('');
96
- // Build drift check input using ACTIONS (not prompt!)
97
- const driftInput = buildDriftCheckInput(mockActions, sessionState.session_id, sessionState);
80
+ // Build drift check input
81
+ const driftInput = {
82
+ sessionState,
83
+ recentSteps: mockSteps,
84
+ latestUserMessage: prompt,
85
+ };
98
86
  console.log('--- Drift Check Input ---');
99
- console.log(`Actions: ${mockActions.map(a => `${a.type}:${a.files.join(',')}`).join(' | ')}`);
87
+ console.log(`Steps: ${mockSteps.map(s => `${s.action_type}:${s.files.join(',')}`).join(' | ')}`);
100
88
  console.log('');
101
89
  // Run drift check
102
90
  console.log('--- Running Drift Check ---');
@@ -112,37 +100,37 @@ export async function driftTest(prompt, options) {
112
100
  console.log('');
113
101
  console.log('--- Drift Check Result ---');
114
102
  console.log(`Score: ${result.score}/10`);
115
- console.log(`Type: ${result.type}`);
103
+ console.log(`Type: ${result.driftType}`);
116
104
  console.log(`Diagnostic: ${result.diagnostic}`);
117
- if (result.boundaries.length > 0) {
118
- console.log(`Boundaries: ${result.boundaries.join(', ')}`);
105
+ if (result.suggestedAction) {
106
+ console.log(`Suggested Action: ${result.suggestedAction}`);
119
107
  }
120
- if (result.recoveryPlan?.steps) {
108
+ if (result.recoverySteps && result.recoverySteps.length > 0) {
121
109
  console.log('Recovery steps:');
122
- for (const step of result.recoveryPlan.steps) {
123
- const file = step.file ? `[${step.file}] ` : '';
124
- console.log(` - ${file}${step.action}`);
110
+ for (const step of result.recoverySteps) {
111
+ console.log(` - ${step}`);
125
112
  }
126
113
  }
127
114
  console.log('');
128
115
  // Determine correction level
129
- const level = determineCorrectionLevel(result.score, sessionState.escalation_count);
116
+ const level = scoreToCorrectionLevel(result.score);
130
117
  console.log('--- Correction Level ---');
131
118
  console.log(`Level: ${level || 'NONE (no correction needed)'}`);
132
119
  console.log('');
133
120
  // Show thresholds
134
- console.log('--- Thresholds (with escalation=%d) ---', sessionState.escalation_count);
135
- console.log(`>= ${DRIFT_CONFIG.SCORE_NO_INJECTION - sessionState.escalation_count}: No correction`);
136
- console.log(`>= ${DRIFT_CONFIG.SCORE_NUDGE - sessionState.escalation_count}: Nudge`);
137
- console.log(`>= ${DRIFT_CONFIG.SCORE_CORRECT - sessionState.escalation_count}: Correct`);
138
- console.log(`>= ${DRIFT_CONFIG.SCORE_INTERVENE - sessionState.escalation_count}: Intervene`);
139
- console.log(`< ${DRIFT_CONFIG.SCORE_INTERVENE - sessionState.escalation_count}: Halt`);
121
+ console.log('--- Thresholds ---');
122
+ console.log(`>= 8: No correction`);
123
+ console.log(`= 7: Nudge`);
124
+ console.log(`5-6: Correct`);
125
+ console.log(`3-4: Intervene`);
126
+ console.log(`< 3: Halt`);
140
127
  console.log('');
141
128
  // Build and show correction if applicable
142
129
  if (level) {
143
130
  console.log('--- Correction Output ---');
144
131
  const correction = buildCorrection(result, sessionState, level);
145
- console.log(correction);
132
+ const formatted = formatCorrectionForInjection(correction);
133
+ console.log(formatted);
146
134
  }
147
135
  else {
148
136
  console.log('No correction needed for this prompt.');
@@ -150,7 +138,7 @@ export async function driftTest(prompt, options) {
150
138
  console.log('\n=== END DRIFT TEST ===');
151
139
  }
152
140
  /**
153
- * Extract file paths from a prompt for mock action creation
141
+ * Extract file paths from a prompt for mock step creation
154
142
  */
155
143
  function extractFilesFromPrompt(prompt) {
156
144
  const patterns = [
@@ -1,27 +1,39 @@
1
- // grov init - Register hooks in Claude Code settings
2
- import { registerGrovHooks, getSettingsPath } from '../lib/hooks.js';
1
+ // grov init - Configure Claude Code to use grov proxy
2
+ import { setProxyEnv, getSettingsPath } from '../lib/settings.js';
3
3
  export async function init() {
4
- console.log('Registering grov hooks in Claude Code...\n');
4
+ console.log('Configuring grov...\n');
5
5
  try {
6
- const { added, alreadyExists } = registerGrovHooks();
7
- if (added.length > 0) {
8
- console.log('Added hooks:');
9
- added.forEach(hook => console.log(` + ${hook}`));
6
+ // Set up proxy URL in settings so users just type 'claude'
7
+ const result = setProxyEnv(true);
8
+ if (result.action === 'added') {
9
+ console.log(' + ANTHROPIC_BASE_URL → http://127.0.0.1:8080');
10
10
  }
11
- if (alreadyExists.length > 0) {
12
- console.log('Already registered:');
13
- alreadyExists.forEach(hook => console.log(` = ${hook}`));
11
+ else if (result.action === 'unchanged') {
12
+ console.log(' = ANTHROPIC_BASE_URL already configured');
14
13
  }
15
14
  console.log(`\nSettings file: ${getSettingsPath()}`);
16
- console.log('\nGrov is now active! Your Claude Code sessions will automatically:');
17
- console.log(' - Capture reasoning after each task (Stop hook)');
18
- console.log(' - Inject relevant context at session start (SessionStart hook)');
19
- console.log(' - Inject targeted context before each prompt (UserPromptSubmit hook)');
20
- console.log('\nJust use Claude Code normally. Grov works in the background.');
15
+ // Check for API key and provide helpful instructions
16
+ if (!process.env.ANTHROPIC_API_KEY) {
17
+ console.log('\n' + '='.repeat(50));
18
+ console.log(' ANTHROPIC_API_KEY not found');
19
+ console.log('='.repeat(50));
20
+ console.log('\nTo enable drift detection and smart extraction:\n');
21
+ console.log(' 1. Get your API key at:');
22
+ console.log(' https://console.anthropic.com/settings/keys\n');
23
+ console.log(' 2. Add to your shell profile (~/.zshrc or ~/.bashrc):');
24
+ console.log(' export ANTHROPIC_API_KEY=sk-ant-...\n');
25
+ console.log(' 3. Restart terminal or run: source ~/.zshrc\n');
26
+ }
27
+ else {
28
+ console.log('\n ANTHROPIC_API_KEY found');
29
+ }
30
+ console.log('\n--- Next Steps ---');
31
+ console.log('1. Terminal 1: grov proxy');
32
+ console.log('2. Terminal 2: claude');
33
+ console.log('\nGrov will automatically capture reasoning and inject context.');
21
34
  }
22
35
  catch (error) {
23
- // SECURITY: Only show error message, not full stack trace with paths
24
- console.error('Failed to register hooks:', error instanceof Error ? error.message : 'Unknown error');
36
+ console.error('Failed to configure grov:', error instanceof Error ? error.message : 'Unknown error');
25
37
  process.exit(1);
26
38
  }
27
39
  }
@@ -0,0 +1 @@
1
+ export declare function proxyStatus(): Promise<void>;
@@ -0,0 +1,32 @@
1
+ // grov proxy-status - Show active proxy sessions
2
+ import { getActiveSessionsForStatus } from '../lib/store.js';
3
+ export async function proxyStatus() {
4
+ const sessions = getActiveSessionsForStatus();
5
+ if (sessions.length === 0) {
6
+ console.log('No active proxy sessions.');
7
+ return;
8
+ }
9
+ console.log(`\n=== Active Proxy Sessions (${sessions.length}) ===\n`);
10
+ for (const session of sessions) {
11
+ const elapsed = getElapsedTime(session.start_time);
12
+ const goal = session.original_goal || 'No goal set';
13
+ console.log(`Session: ${session.session_id.substring(0, 8)}...`);
14
+ console.log(` Status: ${session.status}`);
15
+ console.log(` Mode: ${session.session_mode || 'normal'}`);
16
+ console.log(` Goal: ${goal.substring(0, 60)}${goal.length > 60 ? '...' : ''}`);
17
+ console.log(` Drift: ${session.escalation_count} escalations`);
18
+ console.log(` Started: ${elapsed} ago`);
19
+ console.log('');
20
+ }
21
+ }
22
+ function getElapsedTime(startTime) {
23
+ const start = new Date(startTime).getTime();
24
+ const now = Date.now();
25
+ const diff = now - start;
26
+ const minutes = Math.floor(diff / 60000);
27
+ const hours = Math.floor(minutes / 60);
28
+ if (hours > 0) {
29
+ return `${hours}h ${minutes % 60}m`;
30
+ }
31
+ return `${minutes}m`;
32
+ }