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.
- package/README.md +66 -87
- package/dist/cli.js +23 -37
- package/dist/commands/capture.js +1 -1
- package/dist/commands/disable.d.ts +1 -0
- package/dist/commands/disable.js +14 -0
- package/dist/commands/drift-test.js +56 -68
- package/dist/commands/init.js +29 -17
- package/dist/commands/proxy-status.d.ts +1 -0
- package/dist/commands/proxy-status.js +32 -0
- package/dist/commands/unregister.js +7 -1
- package/dist/lib/correction-builder-proxy.d.ts +16 -0
- package/dist/lib/correction-builder-proxy.js +125 -0
- package/dist/lib/correction-builder.js +1 -1
- package/dist/lib/drift-checker-proxy.d.ts +63 -0
- package/dist/lib/drift-checker-proxy.js +373 -0
- package/dist/lib/drift-checker.js +1 -1
- package/dist/lib/hooks.d.ts +11 -0
- package/dist/lib/hooks.js +33 -0
- package/dist/lib/llm-extractor.d.ts +60 -11
- package/dist/lib/llm-extractor.js +419 -98
- package/dist/lib/settings.d.ts +19 -0
- package/dist/lib/settings.js +63 -0
- package/dist/lib/store.d.ts +201 -43
- package/dist/lib/store.js +653 -90
- package/dist/proxy/action-parser.d.ts +58 -0
- package/dist/proxy/action-parser.js +196 -0
- package/dist/proxy/config.d.ts +26 -0
- package/dist/proxy/config.js +67 -0
- package/dist/proxy/forwarder.d.ts +24 -0
- package/dist/proxy/forwarder.js +119 -0
- package/dist/proxy/index.d.ts +1 -0
- package/dist/proxy/index.js +30 -0
- package/dist/proxy/request-processor.d.ts +12 -0
- package/dist/proxy/request-processor.js +94 -0
- package/dist/proxy/response-processor.d.ts +14 -0
- package/dist/proxy/response-processor.js +128 -0
- package/dist/proxy/server.d.ts +9 -0
- package/dist/proxy/server.js +911 -0
- 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="#
|
|
18
|
+
<a href="#advanced-features">Advanced</a> •
|
|
9
19
|
<a href="#contributing">Contributing</a>
|
|
10
20
|
</p>
|
|
11
21
|
|
|
12
|
-
Grov
|
|
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
|
|
66
|
-
grov
|
|
67
|
-
grov status
|
|
68
|
-
grov
|
|
69
|
-
grov
|
|
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
|
-
##
|
|
77
|
+
## Data Storage
|
|
73
78
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
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
|
-
|
|
85
|
+
- Node.js 18+
|
|
86
|
+
- Claude Code
|
|
84
87
|
|
|
85
|
-
|
|
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
|
-
|
|
90
|
+
## Advanced Features
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
### Anti-Drift Detection
|
|
93
93
|
|
|
94
|
-
Grov monitors what Claude **does** (not what you ask) and
|
|
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
|
|
100
|
-
- Injects corrections at
|
|
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
|
|
102
|
+
# Test drift detection
|
|
106
103
|
grov drift-test "refactor the auth system" --goal "fix login bug"
|
|
107
104
|
```
|
|
108
105
|
|
|
109
|
-
|
|
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
|
|
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
|
|
118
|
+
Without an API key, grov uses basic extraction and disables drift detection.
|
|
120
119
|
|
|
121
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
161
|
-
- Claude Code v2.0+
|
|
158
|
+
---
|
|
162
159
|
|
|
163
160
|
## Roadmap
|
|
164
161
|
|
|
165
162
|
- [x] Local capture & inject
|
|
166
|
-
- [x] LLM-powered extraction
|
|
167
|
-
- [x]
|
|
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
|
|
187
|
-
|
|
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
|
-
|
|
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 -
|
|
39
|
+
// grov init - Configure Claude Code to use grov proxy
|
|
40
40
|
program
|
|
41
41
|
.command('init')
|
|
42
|
-
.description('
|
|
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
|
|
47
|
+
// grov disable - Remove proxy configuration
|
|
48
48
|
program
|
|
49
|
-
.command('
|
|
50
|
-
.description('
|
|
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 {
|
|
75
|
-
await
|
|
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();
|
package/dist/commands/capture.js
CHANGED
|
@@ -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
|
|
5
|
-
// In real usage,
|
|
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
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
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 =
|
|
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
|
|
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
|
-
|
|
47
|
-
|
|
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
|
|
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
|
|
54
|
+
const mockSteps = mockFiles.length > 0
|
|
87
55
|
? mockFiles.map((file, i) => ({
|
|
88
|
-
|
|
56
|
+
id: `step-${i}`,
|
|
57
|
+
session_id: sessionState.session_id,
|
|
58
|
+
action_type: 'edit',
|
|
89
59
|
files: [file],
|
|
90
|
-
|
|
60
|
+
folders: [],
|
|
61
|
+
timestamp: Date.now() + i * 1000,
|
|
62
|
+
is_validated: true,
|
|
63
|
+
is_key_decision: false,
|
|
64
|
+
keywords: [],
|
|
91
65
|
}))
|
|
92
|
-
: [{
|
|
93
|
-
|
|
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
|
|
97
|
-
const driftInput =
|
|
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(`
|
|
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.
|
|
103
|
+
console.log(`Type: ${result.driftType}`);
|
|
116
104
|
console.log(`Diagnostic: ${result.diagnostic}`);
|
|
117
|
-
if (result.
|
|
118
|
-
console.log(`
|
|
105
|
+
if (result.suggestedAction) {
|
|
106
|
+
console.log(`Suggested Action: ${result.suggestedAction}`);
|
|
119
107
|
}
|
|
120
|
-
if (result.
|
|
108
|
+
if (result.recoverySteps && result.recoverySteps.length > 0) {
|
|
121
109
|
console.log('Recovery steps:');
|
|
122
|
-
for (const step of result.
|
|
123
|
-
|
|
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 =
|
|
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
|
|
135
|
-
console.log(`>=
|
|
136
|
-
console.log(
|
|
137
|
-
console.log(
|
|
138
|
-
console.log(
|
|
139
|
-
console.log(`<
|
|
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
|
-
|
|
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
|
|
141
|
+
* Extract file paths from a prompt for mock step creation
|
|
154
142
|
*/
|
|
155
143
|
function extractFilesFromPrompt(prompt) {
|
|
156
144
|
const patterns = [
|
package/dist/commands/init.js
CHANGED
|
@@ -1,27 +1,39 @@
|
|
|
1
|
-
// grov init -
|
|
2
|
-
import {
|
|
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('
|
|
4
|
+
console.log('Configuring grov...\n');
|
|
5
5
|
try {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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 (
|
|
12
|
-
console.log('
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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
|
+
}
|