grov 0.1.2 → 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 +8 -3
@@ -0,0 +1,19 @@
1
+ interface ClaudeSettings {
2
+ hooks?: Record<string, unknown>;
3
+ env?: {
4
+ ANTHROPIC_BASE_URL?: string;
5
+ [key: string]: string | undefined;
6
+ };
7
+ [key: string]: unknown;
8
+ }
9
+ export declare function readClaudeSettings(): ClaudeSettings;
10
+ export declare function writeClaudeSettings(settings: ClaudeSettings): void;
11
+ export declare function getSettingsPath(): string;
12
+ /**
13
+ * Set or remove ANTHROPIC_BASE_URL in settings.json env section.
14
+ * This allows users to just type 'claude' instead of setting env var manually.
15
+ */
16
+ export declare function setProxyEnv(enable: boolean): {
17
+ action: 'added' | 'removed' | 'unchanged';
18
+ };
19
+ export {};
@@ -0,0 +1,63 @@
1
+ // Claude Code settings management
2
+ // Handles ~/.claude/settings.json read/write and proxy configuration
3
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
4
+ import { homedir } from 'os';
5
+ import { join } from 'path';
6
+ const CLAUDE_DIR = join(homedir(), '.claude');
7
+ const SETTINGS_PATH = join(CLAUDE_DIR, 'settings.json');
8
+ export function readClaudeSettings() {
9
+ if (!existsSync(SETTINGS_PATH)) {
10
+ return {};
11
+ }
12
+ try {
13
+ const content = readFileSync(SETTINGS_PATH, 'utf-8');
14
+ return JSON.parse(content);
15
+ }
16
+ catch {
17
+ console.error('Warning: Could not parse ~/.claude/settings.json');
18
+ return {};
19
+ }
20
+ }
21
+ export function writeClaudeSettings(settings) {
22
+ // Ensure .claude directory exists with restricted permissions
23
+ if (!existsSync(CLAUDE_DIR)) {
24
+ mkdirSync(CLAUDE_DIR, { recursive: true, mode: 0o700 });
25
+ }
26
+ writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2), { mode: 0o600 });
27
+ }
28
+ export function getSettingsPath() {
29
+ return SETTINGS_PATH;
30
+ }
31
+ /**
32
+ * Set or remove ANTHROPIC_BASE_URL in settings.json env section.
33
+ * This allows users to just type 'claude' instead of setting env var manually.
34
+ */
35
+ export function setProxyEnv(enable) {
36
+ const settings = readClaudeSettings();
37
+ const PROXY_URL = 'http://127.0.0.1:8080';
38
+ if (enable) {
39
+ // Add env.ANTHROPIC_BASE_URL
40
+ if (!settings.env) {
41
+ settings.env = {};
42
+ }
43
+ if (settings.env.ANTHROPIC_BASE_URL === PROXY_URL) {
44
+ return { action: 'unchanged' };
45
+ }
46
+ settings.env.ANTHROPIC_BASE_URL = PROXY_URL;
47
+ writeClaudeSettings(settings);
48
+ return { action: 'added' };
49
+ }
50
+ else {
51
+ // Remove env.ANTHROPIC_BASE_URL
52
+ if (!settings.env?.ANTHROPIC_BASE_URL) {
53
+ return { action: 'unchanged' };
54
+ }
55
+ delete settings.env.ANTHROPIC_BASE_URL;
56
+ // Clean up empty env object
57
+ if (Object.keys(settings.env).length === 0) {
58
+ delete settings.env;
59
+ }
60
+ writeClaudeSettings(settings);
61
+ return { action: 'removed' };
62
+ }
63
+ }
@@ -1,6 +1,7 @@
1
1
  import Database from 'better-sqlite3';
2
2
  import type { ClaudeAction } from './session-parser.js';
3
3
  export type TaskStatus = 'complete' | 'question' | 'partial' | 'abandoned';
4
+ export type TriggerReason = 'complete' | 'threshold' | 'abandoned';
4
5
  export interface Task {
5
6
  id: string;
6
7
  project_path: string;
@@ -9,7 +10,13 @@ export interface Task {
9
10
  goal?: string;
10
11
  reasoning_trace: string[];
11
12
  files_touched: string[];
13
+ decisions: Array<{
14
+ choice: string;
15
+ reason: string;
16
+ }>;
17
+ constraints: string[];
12
18
  status: TaskStatus;
19
+ trigger_reason?: TriggerReason;
13
20
  linked_commit?: string;
14
21
  parent_task_id?: string;
15
22
  turn_number?: number;
@@ -23,35 +30,21 @@ export interface CreateTaskInput {
23
30
  goal?: string;
24
31
  reasoning_trace?: string[];
25
32
  files_touched?: string[];
33
+ decisions?: Array<{
34
+ choice: string;
35
+ reason: string;
36
+ }>;
37
+ constraints?: string[];
26
38
  status: TaskStatus;
39
+ trigger_reason?: TriggerReason;
27
40
  linked_commit?: string;
28
41
  parent_task_id?: string;
29
42
  turn_number?: number;
30
43
  tags?: string[];
31
44
  }
32
45
  export type SessionStatus = 'active' | 'completed' | 'abandoned';
33
- export interface SessionState {
34
- session_id: string;
35
- user_id?: string;
36
- project_path: string;
37
- original_goal?: string;
38
- actions_taken: string[];
39
- files_explored: string[];
40
- current_intent?: string;
41
- drift_warnings: string[];
42
- start_time: string;
43
- last_update: string;
44
- status: SessionStatus;
45
- expected_scope: string[];
46
- constraints: string[];
47
- success_criteria: string[];
48
- keywords: string[];
49
- last_drift_score?: number;
50
- escalation_count: number;
51
- pending_recovery_plan?: RecoveryPlan;
52
- drift_history: DriftEvent[];
53
- last_checked_at: number;
54
- }
46
+ export type SessionMode = 'normal' | 'drifted' | 'forced';
47
+ export type TaskType = 'main' | 'subtask' | 'parallel';
55
48
  export interface RecoveryPlan {
56
49
  steps: Array<{
57
50
  file?: string;
@@ -64,6 +57,41 @@ export interface DriftEvent {
64
57
  level: string;
65
58
  prompt_summary: string;
66
59
  }
60
+ interface SessionStateBase {
61
+ session_id: string;
62
+ user_id?: string;
63
+ project_path: string;
64
+ original_goal?: string;
65
+ expected_scope: string[];
66
+ constraints: string[];
67
+ keywords: string[];
68
+ escalation_count: number;
69
+ last_checked_at: number;
70
+ start_time: string;
71
+ last_update: string;
72
+ status: SessionStatus;
73
+ }
74
+ interface HookFields {
75
+ success_criteria?: string[];
76
+ last_drift_score?: number;
77
+ pending_recovery_plan?: RecoveryPlan;
78
+ drift_history?: DriftEvent[];
79
+ actions_taken?: string[];
80
+ files_explored?: string[];
81
+ current_intent?: string;
82
+ drift_warnings?: string[];
83
+ }
84
+ interface ProxyFields {
85
+ token_count?: number;
86
+ session_mode?: SessionMode;
87
+ waiting_for_recovery?: boolean;
88
+ last_clear_at?: number;
89
+ completed_at?: string;
90
+ parent_session_id?: string;
91
+ task_type?: TaskType;
92
+ }
93
+ export interface SessionState extends SessionStateBase, HookFields, ProxyFields {
94
+ }
67
95
  export interface CreateSessionStateInput {
68
96
  session_id: string;
69
97
  user_id?: string;
@@ -71,9 +99,66 @@ export interface CreateSessionStateInput {
71
99
  original_goal?: string;
72
100
  expected_scope?: string[];
73
101
  constraints?: string[];
102
+ keywords?: string[];
74
103
  success_criteria?: string[];
104
+ parent_session_id?: string;
105
+ task_type?: TaskType;
106
+ }
107
+ export type StepActionType = 'edit' | 'write' | 'bash' | 'read' | 'glob' | 'grep' | 'task' | 'other';
108
+ export type DriftType = 'none' | 'minor' | 'major' | 'critical';
109
+ export type CorrectionLevel = 'nudge' | 'correct' | 'intervene' | 'halt';
110
+ export interface StepRecord {
111
+ id: string;
112
+ session_id: string;
113
+ action_type: StepActionType;
114
+ files: string[];
115
+ folders: string[];
116
+ command?: string;
117
+ reasoning?: string;
118
+ drift_score?: number;
119
+ drift_type?: DriftType;
120
+ is_key_decision: boolean;
121
+ is_validated: boolean;
122
+ correction_given?: string;
123
+ correction_level?: CorrectionLevel;
124
+ keywords: string[];
125
+ timestamp: number;
126
+ }
127
+ export interface CreateStepInput {
128
+ session_id: string;
129
+ action_type: StepActionType;
130
+ files?: string[];
131
+ folders?: string[];
132
+ command?: string;
133
+ reasoning?: string;
134
+ drift_score?: number;
135
+ drift_type?: DriftType;
136
+ is_key_decision?: boolean;
137
+ is_validated?: boolean;
138
+ correction_given?: string;
139
+ correction_level?: CorrectionLevel;
75
140
  keywords?: string[];
76
141
  }
142
+ export interface DriftLogEntry {
143
+ id: string;
144
+ session_id: string;
145
+ timestamp: number;
146
+ action_type?: string;
147
+ files: string[];
148
+ drift_score: number;
149
+ drift_reason?: string;
150
+ correction_given?: string;
151
+ recovery_plan?: Record<string, unknown>;
152
+ }
153
+ export interface CreateDriftLogInput {
154
+ session_id: string;
155
+ action_type?: string;
156
+ files?: string[];
157
+ drift_score: number;
158
+ drift_reason?: string;
159
+ correction_given?: string;
160
+ recovery_plan?: Record<string, unknown>;
161
+ }
77
162
  export type ChangeType = 'read' | 'write' | 'edit' | 'create' | 'delete';
78
163
  export interface FileReasoning {
79
164
  id: string;
@@ -155,9 +240,17 @@ export declare function deleteSessionState(sessionId: string): void;
155
240
  */
156
241
  export declare function getActiveSessionsForProject(projectPath: string): SessionState[];
157
242
  /**
158
- * Correction level types for drift detection
243
+ * Get child sessions (subtasks and parallel tasks) for a parent session
159
244
  */
160
- export type CorrectionLevel = 'nudge' | 'correct' | 'intervene' | 'halt';
245
+ export declare function getChildSessions(parentSessionId: string): SessionState[];
246
+ /**
247
+ * Get active session for a specific user in a project
248
+ */
249
+ export declare function getActiveSessionForUser(projectPath: string, userId?: string): SessionState | null;
250
+ /**
251
+ * Get all active sessions (for proxy-status command)
252
+ */
253
+ export declare function getActiveSessionsForStatus(): SessionState[];
161
254
  /**
162
255
  * Update session drift metrics after a prompt check
163
256
  */
@@ -198,29 +291,39 @@ export declare function getFileReasoningByPathPattern(pathPattern: string, limit
198
291
  */
199
292
  export declare function getDatabasePath(): string;
200
293
  /**
201
- * Step record - a single Claude action stored in DB
294
+ * Create a new step record (proxy version)
202
295
  */
203
- export interface StepRecord {
204
- id: string;
205
- session_id: string;
206
- action_type: string;
207
- files: string[];
208
- folders: string[];
209
- command?: string;
210
- reasoning?: string;
211
- drift_score: number;
212
- is_key_decision: boolean;
213
- keywords: string[];
214
- timestamp: number;
215
- }
296
+ export declare function createStep(input: CreateStepInput): StepRecord;
216
297
  /**
217
- * Save a Claude action as a step
298
+ * Get steps for a session
218
299
  */
219
- export declare function saveStep(sessionId: string, action: ClaudeAction, driftScore: number, isKeyDecision?: boolean, keywords?: string[]): void;
300
+ export declare function getStepsForSession(sessionId: string, limit?: number): StepRecord[];
301
+ /**
302
+ * Get recent steps for a session (most recent N)
303
+ */
304
+ export declare function getRecentSteps(sessionId: string, count?: number): StepRecord[];
305
+ /**
306
+ * Get validated steps only (for summary generation)
307
+ */
308
+ export declare function getValidatedSteps(sessionId: string): StepRecord[];
309
+ /**
310
+ * Delete steps for a session
311
+ */
312
+ export declare function deleteStepsForSession(sessionId: string): void;
220
313
  /**
221
- * Get recent steps for a session (most recent first)
314
+ * Update reasoning for recent steps that don't have reasoning yet
315
+ * Called at end_turn to backfill reasoning from Claude's text response
222
316
  */
223
- export declare function getRecentSteps(sessionId: string, limit?: number): StepRecord[];
317
+ export declare function updateRecentStepsReasoning(sessionId: string, reasoning: string, limit?: number): number;
318
+ /**
319
+ * Get relevant steps (key decisions and write/edit actions) - proxy version
320
+ * Reference: plan_proxy_local.md Section 2.2
321
+ */
322
+ export declare function getRelevantStepsSimple(sessionId: string, limit?: number): StepRecord[];
323
+ /**
324
+ * Save a Claude action as a step (hook version - uses ClaudeAction)
325
+ */
326
+ export declare function saveStep(sessionId: string, action: ClaudeAction, driftScore: number, isKeyDecision?: boolean, keywords?: string[]): void;
224
327
  /**
225
328
  * Update last_checked_at timestamp for a session
226
329
  */
@@ -242,7 +345,62 @@ export declare function getStepsByKeywords(sessionId: string, keywords: string[]
242
345
  */
243
346
  export declare function getKeyDecisionSteps(sessionId: string, limit?: number): StepRecord[];
244
347
  /**
245
- * Combined retrieval: runs all 4 queries and deduplicates
348
+ * Get steps reasoning by file path (for proxy team memory injection)
349
+ * Searches across ALL sessions, returns file-level reasoning from steps table
350
+ */
351
+ export declare function getStepsReasoningByPath(filePath: string, limit?: number): Array<{
352
+ file_path: string;
353
+ reasoning: string;
354
+ anchor?: string;
355
+ }>;
356
+ /**
357
+ * Combined retrieval: runs all 4 queries and deduplicates (hook version)
246
358
  * Priority: key decisions > files > folders > keywords
247
359
  */
248
360
  export declare function getRelevantSteps(sessionId: string, currentFiles: string[], currentFolders: string[], keywords: string[], limit?: number): StepRecord[];
361
+ /**
362
+ * Log a drift event (for rejected actions)
363
+ */
364
+ export declare function logDriftEvent(input: CreateDriftLogInput): DriftLogEntry;
365
+ /**
366
+ * Get drift log for a session
367
+ */
368
+ export declare function getDriftLog(sessionId: string, limit?: number): DriftLogEntry[];
369
+ /**
370
+ * Update token count for a session
371
+ */
372
+ export declare function updateTokenCount(sessionId: string, tokenCount: number): void;
373
+ /**
374
+ * Update session mode
375
+ */
376
+ export declare function updateSessionMode(sessionId: string, mode: SessionMode): void;
377
+ /**
378
+ * Mark session as waiting for recovery
379
+ */
380
+ export declare function markWaitingForRecovery(sessionId: string, waiting: boolean): void;
381
+ /**
382
+ * Increment escalation count
383
+ */
384
+ export declare function incrementEscalation(sessionId: string): void;
385
+ /**
386
+ * Update last clear timestamp and reset token count
387
+ */
388
+ export declare function markCleared(sessionId: string): void;
389
+ /**
390
+ * Mark session as completed (instead of deleting)
391
+ * Session will be cleaned up after 1 hour
392
+ */
393
+ export declare function markSessionCompleted(sessionId: string): void;
394
+ /**
395
+ * Cleanup sessions completed more than 24 hours ago
396
+ * Also deletes associated steps and drift_log entries
397
+ * Skips sessions that have active children (RESTRICT approach)
398
+ * Returns number of sessions cleaned up
399
+ */
400
+ export declare function cleanupOldCompletedSessions(maxAgeMs?: number): number;
401
+ /**
402
+ * Get completed session for project (for new_task detection)
403
+ * Returns most recent completed session if exists
404
+ */
405
+ export declare function getCompletedSessionForProject(projectPath: string): SessionState | null;
406
+ export {};