grov 0.5.11 → 0.6.13

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 (190) hide show
  1. package/dist/cli/agents/registry.d.ts +17 -0
  2. package/dist/cli/agents/registry.js +132 -0
  3. package/dist/cli/commands/agents.d.ts +1 -0
  4. package/dist/cli/commands/agents.js +48 -0
  5. package/dist/cli/commands/disable.d.ts +1 -0
  6. package/dist/cli/commands/disable.js +179 -0
  7. package/dist/cli/commands/doctor.d.ts +1 -0
  8. package/dist/cli/commands/doctor.js +157 -0
  9. package/dist/{commands → cli/commands}/drift-test.js +39 -26
  10. package/dist/cli/commands/init.d.ts +1 -0
  11. package/dist/cli/commands/init.js +90 -0
  12. package/dist/{commands → cli/commands}/login.js +19 -18
  13. package/dist/{commands → cli/commands}/logout.js +1 -1
  14. package/dist/{commands → cli/commands}/proxy-status.js +1 -1
  15. package/dist/cli/commands/setup.d.ts +6 -0
  16. package/dist/cli/commands/setup.js +309 -0
  17. package/dist/{commands → cli/commands}/status.js +1 -1
  18. package/dist/{commands → cli/commands}/sync.d.ts +1 -0
  19. package/dist/{commands → cli/commands}/sync.js +59 -4
  20. package/dist/{commands → cli/commands}/uninstall.js +2 -2
  21. package/dist/cli/index.js +270 -0
  22. package/dist/{lib → core/cloud}/cloud-sync.d.ts +3 -3
  23. package/dist/{lib → core/cloud}/cloud-sync.js +10 -10
  24. package/dist/{lib → core/extraction}/correction-builder-proxy.d.ts +1 -1
  25. package/dist/{lib → core/extraction}/correction-builder-proxy.js +0 -4
  26. package/dist/{lib → core/extraction}/drift-checker-proxy.d.ts +13 -9
  27. package/dist/core/extraction/drift-checker-proxy.js +510 -0
  28. package/dist/{lib → core/extraction}/llm-extractor.d.ts +8 -38
  29. package/dist/{lib → core/extraction}/llm-extractor.js +132 -220
  30. package/dist/{lib → core}/store/sessions.js +3 -19
  31. package/dist/core/store/store.d.ts +1 -0
  32. package/dist/{lib → core/store}/store.js +1 -1
  33. package/dist/{lib → core}/store/types.d.ts +0 -4
  34. package/dist/integrations/mcp/cache.d.ts +27 -0
  35. package/dist/integrations/mcp/cache.js +106 -0
  36. package/dist/integrations/mcp/capture/antigravity-parser.d.ts +26 -0
  37. package/dist/integrations/mcp/capture/antigravity-parser.js +272 -0
  38. package/dist/integrations/mcp/capture/antigravity-scanner.d.ts +24 -0
  39. package/dist/integrations/mcp/capture/antigravity-scanner.js +153 -0
  40. package/dist/integrations/mcp/capture/antigravity-sync-tracker.d.ts +29 -0
  41. package/dist/integrations/mcp/capture/antigravity-sync-tracker.js +115 -0
  42. package/dist/integrations/mcp/capture/cli-extractor.d.ts +18 -0
  43. package/dist/integrations/mcp/capture/cli-extractor.js +258 -0
  44. package/dist/integrations/mcp/capture/cli-synced.d.ts +4 -0
  45. package/dist/integrations/mcp/capture/cli-synced.js +62 -0
  46. package/dist/integrations/mcp/capture/cli-transform.d.ts +30 -0
  47. package/dist/integrations/mcp/capture/cli-transform.js +62 -0
  48. package/dist/integrations/mcp/capture/cli-watcher.d.ts +31 -0
  49. package/dist/integrations/mcp/capture/cli-watcher.js +106 -0
  50. package/dist/integrations/mcp/capture/hook-handler.d.ts +2 -0
  51. package/dist/integrations/mcp/capture/hook-handler.js +157 -0
  52. package/dist/integrations/mcp/capture/sqlite-reader.d.ts +35 -0
  53. package/dist/integrations/mcp/capture/sqlite-reader.js +388 -0
  54. package/dist/integrations/mcp/capture/sync-tracker.d.ts +16 -0
  55. package/dist/integrations/mcp/capture/sync-tracker.js +102 -0
  56. package/dist/integrations/mcp/clients/cursor/rules-installer.d.ts +19 -0
  57. package/dist/integrations/mcp/clients/cursor/rules-installer.js +123 -0
  58. package/dist/integrations/mcp/index.d.ts +1 -0
  59. package/dist/integrations/mcp/index.js +94 -0
  60. package/dist/integrations/mcp/logger.d.ts +8 -0
  61. package/dist/integrations/mcp/logger.js +50 -0
  62. package/dist/integrations/mcp/server.d.ts +5 -0
  63. package/dist/integrations/mcp/server.js +58 -0
  64. package/dist/integrations/mcp/tools/expand.d.ts +1 -0
  65. package/dist/integrations/mcp/tools/expand.js +53 -0
  66. package/dist/integrations/mcp/tools/preview.d.ts +1 -0
  67. package/dist/integrations/mcp/tools/preview.js +64 -0
  68. package/dist/integrations/proxy/agents/base.d.ts +43 -0
  69. package/dist/integrations/proxy/agents/base.js +13 -0
  70. package/dist/{proxy/utils → integrations/proxy/agents/claude}/extractors.d.ts +4 -8
  71. package/dist/{proxy/utils → integrations/proxy/agents/claude}/extractors.js +4 -33
  72. package/dist/{proxy → integrations/proxy/agents/claude}/forwarder.d.ts +1 -1
  73. package/dist/{proxy → integrations/proxy/agents/claude}/forwarder.js +22 -6
  74. package/dist/integrations/proxy/agents/claude/index.d.ts +43 -0
  75. package/dist/integrations/proxy/agents/claude/index.js +386 -0
  76. package/dist/{proxy/action-parser.d.ts → integrations/proxy/agents/claude/parser.d.ts} +1 -1
  77. package/dist/integrations/proxy/agents/codex/extractors.d.ts +6 -0
  78. package/dist/integrations/proxy/agents/codex/extractors.js +49 -0
  79. package/dist/integrations/proxy/agents/codex/forwarder.d.ts +9 -0
  80. package/dist/integrations/proxy/agents/codex/forwarder.js +125 -0
  81. package/dist/integrations/proxy/agents/codex/index.d.ts +44 -0
  82. package/dist/integrations/proxy/agents/codex/index.js +371 -0
  83. package/dist/integrations/proxy/agents/codex/parser.d.ts +11 -0
  84. package/dist/integrations/proxy/agents/codex/parser.js +104 -0
  85. package/dist/integrations/proxy/agents/codex/patch.d.ts +12 -0
  86. package/dist/integrations/proxy/agents/codex/patch.js +40 -0
  87. package/dist/integrations/proxy/agents/codex/settings.d.ts +18 -0
  88. package/dist/integrations/proxy/agents/codex/settings.js +73 -0
  89. package/dist/integrations/proxy/agents/codex/types.d.ts +59 -0
  90. package/dist/integrations/proxy/agents/codex/types.js +2 -0
  91. package/dist/integrations/proxy/agents/index.d.ts +11 -0
  92. package/dist/integrations/proxy/agents/index.js +25 -0
  93. package/dist/integrations/proxy/agents/types.d.ts +77 -0
  94. package/dist/integrations/proxy/agents/types.js +2 -0
  95. package/dist/{proxy → integrations/proxy/cache}/extended-cache.js +2 -6
  96. package/dist/{proxy → integrations/proxy}/config.js +1 -1
  97. package/dist/{proxy → integrations/proxy}/handlers/preprocess.d.ts +3 -3
  98. package/dist/integrations/proxy/handlers/preprocess.js +194 -0
  99. package/dist/integrations/proxy/index.js +20 -0
  100. package/dist/integrations/proxy/injection/memory-injection.d.ts +56 -0
  101. package/dist/integrations/proxy/injection/memory-injection.js +252 -0
  102. package/dist/integrations/proxy/orchestrator.d.ts +30 -0
  103. package/dist/integrations/proxy/orchestrator.js +954 -0
  104. package/dist/integrations/proxy/request-processor.d.ts +14 -0
  105. package/dist/integrations/proxy/request-processor.js +68 -0
  106. package/dist/{proxy → integrations/proxy}/response-processor.d.ts +4 -3
  107. package/dist/{proxy → integrations/proxy}/response-processor.js +51 -43
  108. package/dist/{proxy → integrations/proxy}/server.d.ts +0 -1
  109. package/dist/integrations/proxy/server.js +146 -0
  110. package/dist/{proxy → integrations/proxy}/types.d.ts +4 -0
  111. package/dist/{proxy → integrations/proxy}/utils/logging.d.ts +1 -0
  112. package/dist/{proxy → integrations/proxy}/utils/logging.js +5 -0
  113. package/package.json +31 -10
  114. package/postinstall.js +62 -6
  115. package/dist/cli.js +0 -149
  116. package/dist/commands/capture.d.ts +0 -6
  117. package/dist/commands/capture.js +0 -324
  118. package/dist/commands/disable.d.ts +0 -1
  119. package/dist/commands/disable.js +0 -14
  120. package/dist/commands/doctor.d.ts +0 -1
  121. package/dist/commands/doctor.js +0 -89
  122. package/dist/commands/init.d.ts +0 -1
  123. package/dist/commands/init.js +0 -52
  124. package/dist/commands/inject.d.ts +0 -5
  125. package/dist/commands/inject.js +0 -88
  126. package/dist/commands/prompt-inject.d.ts +0 -4
  127. package/dist/commands/prompt-inject.js +0 -451
  128. package/dist/commands/unregister.d.ts +0 -1
  129. package/dist/commands/unregister.js +0 -28
  130. package/dist/lib/anchor-extractor.d.ts +0 -30
  131. package/dist/lib/anchor-extractor.js +0 -296
  132. package/dist/lib/correction-builder.d.ts +0 -10
  133. package/dist/lib/correction-builder.js +0 -226
  134. package/dist/lib/drift-checker-proxy.js +0 -373
  135. package/dist/lib/drift-checker.d.ts +0 -66
  136. package/dist/lib/drift-checker.js +0 -341
  137. package/dist/lib/hooks.d.ts +0 -38
  138. package/dist/lib/hooks.js +0 -291
  139. package/dist/lib/jsonl-parser.d.ts +0 -87
  140. package/dist/lib/jsonl-parser.js +0 -281
  141. package/dist/lib/session-parser.d.ts +0 -44
  142. package/dist/lib/session-parser.js +0 -256
  143. package/dist/lib/store.d.ts +0 -1
  144. package/dist/proxy/cache.d.ts +0 -32
  145. package/dist/proxy/cache.js +0 -47
  146. package/dist/proxy/handlers/preprocess.js +0 -186
  147. package/dist/proxy/index.js +0 -30
  148. package/dist/proxy/injection/delta-tracking.d.ts +0 -11
  149. package/dist/proxy/injection/delta-tracking.js +0 -94
  150. package/dist/proxy/injection/injectors.d.ts +0 -7
  151. package/dist/proxy/injection/injectors.js +0 -139
  152. package/dist/proxy/request-processor.d.ts +0 -27
  153. package/dist/proxy/request-processor.js +0 -233
  154. package/dist/proxy/server.js +0 -1289
  155. /package/dist/{commands → cli/commands}/drift-test.d.ts +0 -0
  156. /package/dist/{commands → cli/commands}/login.d.ts +0 -0
  157. /package/dist/{commands → cli/commands}/logout.d.ts +0 -0
  158. /package/dist/{commands → cli/commands}/proxy-status.d.ts +0 -0
  159. /package/dist/{commands → cli/commands}/status.d.ts +0 -0
  160. /package/dist/{commands → cli/commands}/uninstall.d.ts +0 -0
  161. /package/dist/{cli.d.ts → cli/index.d.ts} +0 -0
  162. /package/dist/{lib → core/cloud}/api-client.d.ts +0 -0
  163. /package/dist/{lib → core/cloud}/api-client.js +0 -0
  164. /package/dist/{lib → core/cloud}/credentials.d.ts +0 -0
  165. /package/dist/{lib → core/cloud}/credentials.js +0 -0
  166. /package/dist/{lib → core}/store/convenience.d.ts +0 -0
  167. /package/dist/{lib → core}/store/convenience.js +0 -0
  168. /package/dist/{lib → core}/store/database.d.ts +0 -0
  169. /package/dist/{lib → core}/store/database.js +0 -0
  170. /package/dist/{lib → core}/store/drift.d.ts +0 -0
  171. /package/dist/{lib → core}/store/drift.js +0 -0
  172. /package/dist/{lib → core}/store/index.d.ts +0 -0
  173. /package/dist/{lib → core}/store/index.js +0 -0
  174. /package/dist/{lib → core}/store/sessions.d.ts +0 -0
  175. /package/dist/{lib → core}/store/steps.d.ts +0 -0
  176. /package/dist/{lib → core}/store/steps.js +0 -0
  177. /package/dist/{lib → core}/store/tasks.d.ts +0 -0
  178. /package/dist/{lib → core}/store/tasks.js +0 -0
  179. /package/dist/{lib → core}/store/types.js +0 -0
  180. /package/dist/{proxy/action-parser.js → integrations/proxy/agents/claude/parser.js} +0 -0
  181. /package/dist/{lib → integrations/proxy/agents/claude}/settings.d.ts +0 -0
  182. /package/dist/{lib → integrations/proxy/agents/claude}/settings.js +0 -0
  183. /package/dist/{proxy → integrations/proxy/cache}/extended-cache.d.ts +0 -0
  184. /package/dist/{proxy → integrations/proxy}/config.d.ts +0 -0
  185. /package/dist/{proxy → integrations/proxy}/index.d.ts +0 -0
  186. /package/dist/{proxy → integrations/proxy}/types.js +0 -0
  187. /package/dist/{lib → utils}/debug.d.ts +0 -0
  188. /package/dist/{lib → utils}/debug.js +0 -0
  189. /package/dist/{lib → utils}/utils.d.ts +0 -0
  190. /package/dist/{lib → utils}/utils.js +0 -0
@@ -1,87 +0,0 @@
1
- interface JsonlEntry {
2
- type: 'user' | 'assistant' | 'system';
3
- message?: {
4
- role: string;
5
- content: string | ContentBlock[];
6
- };
7
- timestamp?: string;
8
- session_id?: string;
9
- [key: string]: unknown;
10
- }
11
- interface ContentBlock {
12
- type: string;
13
- text?: string;
14
- name?: string;
15
- input?: unknown;
16
- [key: string]: unknown;
17
- }
18
- export interface ParsedSession {
19
- sessionId: string;
20
- projectPath: string;
21
- startTime: string;
22
- endTime: string;
23
- userMessages: string[];
24
- assistantMessages: string[];
25
- toolCalls: ToolCall[];
26
- filesRead: string[];
27
- filesWritten: string[];
28
- rawEntries: JsonlEntry[];
29
- }
30
- export interface ToolCall {
31
- name: string;
32
- input: unknown;
33
- timestamp?: string;
34
- }
35
- /**
36
- * Encode a project path the same way Claude Code does
37
- * /Users/dev/myapp -> -Users-dev-myapp
38
- */
39
- export declare function encodeProjectPath(projectPath: string): string;
40
- /**
41
- * Decode an encoded project path back to the original.
42
- * SECURITY: Validates that the decoded path doesn't contain traversal sequences.
43
- * @throws Error if path contains traversal sequences
44
- */
45
- export declare function decodeProjectPath(encoded: string): string;
46
- /**
47
- * Validate that a file path is within the expected project boundary.
48
- * SECURITY: Prevents path traversal outside project directory.
49
- */
50
- export declare function isPathWithinProject(projectPath: string, filePath: string): boolean;
51
- /**
52
- * Get the directory where Claude stores sessions for a project
53
- */
54
- export declare function getProjectSessionsDir(projectPath: string): string;
55
- /**
56
- * Extract session ID from a JSONL file path
57
- * ~/.claude/projects/-Users-dev-myapp/abc123def456.jsonl -> abc123def456
58
- * SECURITY: Validates session ID format to prevent path confusion attacks.
59
- */
60
- export declare function getSessionIdFromPath(jsonlPath: string): string;
61
- /**
62
- * Get the current session ID for a project (from the most recent session file)
63
- */
64
- export declare function getCurrentSessionId(projectPath: string): string | null;
65
- /**
66
- * Find the most recent session file for a project.
67
- * SECURITY: Handles TOCTOU race conditions gracefully.
68
- */
69
- export declare function findLatestSessionFile(projectPath: string): string | null;
70
- /**
71
- * List all session files for a project
72
- */
73
- export declare function listSessionFiles(projectPath: string): string[];
74
- /**
75
- * Parse a JSONL file into entries.
76
- * SECURITY: Limits entries to prevent memory exhaustion attacks.
77
- */
78
- export declare function parseJsonlFile(filePath: string): JsonlEntry[];
79
- /**
80
- * Parse a session file and extract structured data
81
- */
82
- export declare function parseSession(filePath: string): ParsedSession;
83
- /**
84
- * Get a summary of the session for LLM extraction
85
- */
86
- export declare function getSessionSummary(session: ParsedSession): string;
87
- export {};
@@ -1,281 +0,0 @@
1
- // Parse Claude Code session JSONL files from ~/.claude/projects/
2
- import { readFileSync, readdirSync, statSync, existsSync } from 'fs';
3
- import { homedir } from 'os';
4
- import { join, basename, resolve, normalize } from 'path';
5
- import { debugParser } from './debug.js';
6
- const CLAUDE_PROJECTS_DIR = join(homedir(), '.claude', 'projects');
7
- /**
8
- * Encode a project path the same way Claude Code does
9
- * /Users/dev/myapp -> -Users-dev-myapp
10
- */
11
- export function encodeProjectPath(projectPath) {
12
- // Normalize the path first to prevent encoding malicious paths
13
- const normalized = projectPath.replace(/\.\.+/g, '.');
14
- return normalized.replace(/\//g, '-');
15
- }
16
- /**
17
- * Decode an encoded project path back to the original.
18
- * SECURITY: Validates that the decoded path doesn't contain traversal sequences.
19
- * @throws Error if path contains traversal sequences
20
- */
21
- export function decodeProjectPath(encoded) {
22
- // First char is always '-' representing the root '/'
23
- const decoded = encoded.replace(/-/g, '/');
24
- // SECURITY: Prevent path traversal attacks
25
- if (decoded.includes('..')) {
26
- throw new Error('Invalid path: traversal sequence detected');
27
- }
28
- return decoded;
29
- }
30
- /**
31
- * Validate that a file path is within the expected project boundary.
32
- * SECURITY: Prevents path traversal outside project directory.
33
- */
34
- export function isPathWithinProject(projectPath, filePath) {
35
- const resolvedProject = resolve(normalize(projectPath));
36
- const resolvedFile = resolve(normalize(filePath));
37
- return resolvedFile.startsWith(resolvedProject);
38
- }
39
- /**
40
- * Get the directory where Claude stores sessions for a project
41
- */
42
- export function getProjectSessionsDir(projectPath) {
43
- const encoded = encodeProjectPath(projectPath);
44
- return join(CLAUDE_PROJECTS_DIR, encoded);
45
- }
46
- // SECURITY: Valid session ID pattern (hex chars, dashes for UUIDs)
47
- const SESSION_ID_PATTERN = /^[a-f0-9-]+$/i;
48
- /**
49
- * Extract session ID from a JSONL file path
50
- * ~/.claude/projects/-Users-dev-myapp/abc123def456.jsonl -> abc123def456
51
- * SECURITY: Validates session ID format to prevent path confusion attacks.
52
- */
53
- export function getSessionIdFromPath(jsonlPath) {
54
- const sessionId = basename(jsonlPath, '.jsonl');
55
- // SECURITY: Validate session ID contains only safe characters
56
- if (!SESSION_ID_PATTERN.test(sessionId)) {
57
- debugParser('Invalid session ID format: %s', sessionId.substring(0, 20));
58
- throw new Error('Invalid session ID format');
59
- }
60
- return sessionId;
61
- }
62
- /**
63
- * Get the current session ID for a project (from the most recent session file)
64
- */
65
- export function getCurrentSessionId(projectPath) {
66
- const latestFile = findLatestSessionFile(projectPath);
67
- return latestFile ? getSessionIdFromPath(latestFile) : null;
68
- }
69
- /**
70
- * Find the most recent session file for a project.
71
- * SECURITY: Handles TOCTOU race conditions gracefully.
72
- */
73
- export function findLatestSessionFile(projectPath) {
74
- const sessionsDir = getProjectSessionsDir(projectPath);
75
- if (!existsSync(sessionsDir)) {
76
- return null;
77
- }
78
- // SECURITY: Handle TOCTOU - files may be deleted between list and stat
79
- const files = [];
80
- try {
81
- const entries = readdirSync(sessionsDir).filter(f => f.endsWith('.jsonl'));
82
- for (const f of entries) {
83
- const filePath = join(sessionsDir, f);
84
- try {
85
- // SECURITY: Wrap stat in try-catch to handle deleted files
86
- const stat = statSync(filePath);
87
- files.push({
88
- name: f,
89
- path: filePath,
90
- mtime: stat.mtime
91
- });
92
- }
93
- catch {
94
- // File was deleted between readdir and stat - skip it
95
- debugParser('File disappeared during listing: %s', f);
96
- }
97
- }
98
- }
99
- catch {
100
- // Directory may have been deleted
101
- debugParser('Could not read sessions directory: %s', sessionsDir);
102
- return null;
103
- }
104
- files.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
105
- return files.length > 0 ? files[0].path : null;
106
- }
107
- /**
108
- * List all session files for a project
109
- */
110
- export function listSessionFiles(projectPath) {
111
- const sessionsDir = getProjectSessionsDir(projectPath);
112
- if (!existsSync(sessionsDir)) {
113
- return [];
114
- }
115
- return readdirSync(sessionsDir)
116
- .filter(f => f.endsWith('.jsonl'))
117
- .map(f => join(sessionsDir, f));
118
- }
119
- // SECURITY: Maximum entries to prevent memory exhaustion
120
- const MAX_JSONL_ENTRIES = 10000;
121
- /**
122
- * Parse a JSONL file into entries.
123
- * SECURITY: Limits entries to prevent memory exhaustion attacks.
124
- */
125
- export function parseJsonlFile(filePath) {
126
- let content;
127
- try {
128
- content = readFileSync(filePath, 'utf-8');
129
- }
130
- catch {
131
- // File may have been deleted/moved between finding and reading
132
- debugParser('Could not read file: %s', filePath);
133
- return [];
134
- }
135
- const lines = content.trim().split('\n').filter(line => line.trim());
136
- const entries = [];
137
- for (const line of lines) {
138
- // SECURITY: Limit entries to prevent memory exhaustion
139
- if (entries.length >= MAX_JSONL_ENTRIES) {
140
- debugParser('Entry limit reached (%d), stopping parse', MAX_JSONL_ENTRIES);
141
- break;
142
- }
143
- try {
144
- const entry = JSON.parse(line);
145
- entries.push(entry);
146
- }
147
- catch {
148
- // Skip malformed lines
149
- debugParser('Skipping malformed JSONL line');
150
- }
151
- }
152
- return entries;
153
- }
154
- /**
155
- * Extract text content from a message content array
156
- */
157
- function extractTextContent(content) {
158
- if (typeof content === 'string') {
159
- return content;
160
- }
161
- return content
162
- .filter(block => block.type === 'text' && block.text)
163
- .map(block => block.text)
164
- .join('\n');
165
- }
166
- /**
167
- * Extract tool calls from assistant message content
168
- */
169
- function extractToolCalls(content, timestamp) {
170
- if (typeof content === 'string') {
171
- return [];
172
- }
173
- return content
174
- .filter(block => block.type === 'tool_use' && block.name)
175
- .map(block => ({
176
- name: block.name,
177
- input: block.input,
178
- timestamp
179
- }));
180
- }
181
- /**
182
- * Parse a session file and extract structured data
183
- */
184
- export function parseSession(filePath) {
185
- const entries = parseJsonlFile(filePath);
186
- const sessionId = basename(filePath, '.jsonl');
187
- const userMessages = [];
188
- const assistantMessages = [];
189
- const toolCalls = [];
190
- const filesRead = new Set();
191
- const filesWritten = new Set();
192
- let startTime = '';
193
- let endTime = '';
194
- let projectPath = '';
195
- for (const entry of entries) {
196
- // Track timestamps
197
- if (entry.timestamp) {
198
- if (!startTime)
199
- startTime = entry.timestamp;
200
- endTime = entry.timestamp;
201
- }
202
- // Extract user messages
203
- if (entry.type === 'user' && entry.message?.content) {
204
- const text = extractTextContent(entry.message.content);
205
- if (text)
206
- userMessages.push(text);
207
- }
208
- // Extract assistant messages and tool calls
209
- if (entry.type === 'assistant' && entry.message?.content) {
210
- const text = extractTextContent(entry.message.content);
211
- if (text)
212
- assistantMessages.push(text);
213
- const tools = extractToolCalls(entry.message.content, entry.timestamp);
214
- toolCalls.push(...tools);
215
- // Track files from tool calls
216
- for (const tool of tools) {
217
- const input = tool.input;
218
- if (input?.file_path && typeof input.file_path === 'string') {
219
- if (tool.name === 'Read') {
220
- filesRead.add(input.file_path);
221
- }
222
- else if (tool.name === 'Write' || tool.name === 'Edit') {
223
- filesWritten.add(input.file_path);
224
- }
225
- }
226
- }
227
- }
228
- }
229
- // Try to infer project path from file paths
230
- const allFiles = [...filesRead, ...filesWritten];
231
- if (allFiles.length > 0) {
232
- // Find common prefix
233
- const firstFile = allFiles[0];
234
- const parts = firstFile.split('/');
235
- // Assume project is a few levels deep (e.g., /Users/dev/project)
236
- if (parts.length >= 4) {
237
- projectPath = parts.slice(0, 4).join('/');
238
- }
239
- }
240
- return {
241
- sessionId,
242
- projectPath,
243
- startTime,
244
- endTime,
245
- userMessages,
246
- assistantMessages,
247
- toolCalls,
248
- filesRead: [...filesRead],
249
- filesWritten: [...filesWritten],
250
- rawEntries: entries
251
- };
252
- }
253
- /**
254
- * Get a summary of the session for LLM extraction
255
- */
256
- export function getSessionSummary(session) {
257
- const lines = [];
258
- lines.push(`Session ID: ${session.sessionId}`);
259
- lines.push(`Time: ${session.startTime} to ${session.endTime}`);
260
- lines.push('');
261
- lines.push('## User Messages');
262
- session.userMessages.forEach((msg, i) => {
263
- lines.push(`[${i + 1}] ${msg.substring(0, 500)}${msg.length > 500 ? '...' : ''}`);
264
- });
265
- lines.push('');
266
- lines.push('## Files Read');
267
- session.filesRead.forEach(f => lines.push(` - ${f}`));
268
- lines.push('');
269
- lines.push('## Files Written/Edited');
270
- session.filesWritten.forEach(f => lines.push(` - ${f}`));
271
- lines.push('');
272
- lines.push('## Tool Calls');
273
- const toolSummary = session.toolCalls.reduce((acc, t) => {
274
- acc[t.name] = (acc[t.name] || 0) + 1;
275
- return acc;
276
- }, {});
277
- Object.entries(toolSummary).forEach(([name, count]) => {
278
- lines.push(` - ${name}: ${count}x`);
279
- });
280
- return lines.join('\n');
281
- }
@@ -1,44 +0,0 @@
1
- /**
2
- * Claude's action extracted from session JSONL
3
- */
4
- export interface ClaudeAction {
5
- type: 'edit' | 'write' | 'bash' | 'read' | 'delete' | 'grep' | 'glob' | 'multiedit';
6
- files: string[];
7
- command?: string;
8
- timestamp: number;
9
- }
10
- /**
11
- * Find session JSONL path from session_id and project path.
12
- *
13
- * Claude Code stores sessions in:
14
- * ~/.claude/projects/<encoded-path>/<session_id>.jsonl
15
- *
16
- * The encoded path uses a specific encoding (not standard URL encoding).
17
- */
18
- export declare function findSessionFile(sessionId: string, projectPath: string): string | null;
19
- /**
20
- * Parse JSONL and extract ALL Claude's tool calls
21
- */
22
- export declare function parseSessionActions(sessionPath: string): ClaudeAction[];
23
- /**
24
- * Get only NEW actions since last check timestamp.
25
- * This is the main function used by prompt-inject.
26
- */
27
- export declare function getNewActions(sessionPath: string, lastCheckedTimestamp: number): ClaudeAction[];
28
- /**
29
- * Get actions that MODIFY files (not reads).
30
- * Use this for drift detection - reads are exploration, not drift.
31
- */
32
- export declare function getModifyingActions(actions: ClaudeAction[]): ClaudeAction[];
33
- /**
34
- * Extract all unique files touched by actions
35
- */
36
- export declare function extractFilesFromActions(actions: ClaudeAction[]): string[];
37
- /**
38
- * Extract unique folders from actions
39
- */
40
- export declare function extractFoldersFromActions(actions: ClaudeAction[]): string[];
41
- /**
42
- * Extract keywords from an action (for step storage)
43
- */
44
- export declare function extractKeywordsFromAction(action: ClaudeAction): string[];
@@ -1,256 +0,0 @@
1
- // Session JSONL parser for anti-drift system
2
- // Parses Claude Code session files to extract Claude's ACTIONS (tool calls)
3
- //
4
- // CRITICAL: We monitor Claude's ACTIONS, NOT user prompts.
5
- // User can explore freely. We check what CLAUDE DOES.
6
- import { existsSync, readFileSync, readdirSync } from 'fs';
7
- import { homedir } from 'os';
8
- import { join, dirname } from 'path';
9
- import { debugInject } from './debug.js';
10
- // ============================================
11
- // MAIN FUNCTIONS
12
- // ============================================
13
- /**
14
- * Find session JSONL path from session_id and project path.
15
- *
16
- * Claude Code stores sessions in:
17
- * ~/.claude/projects/<encoded-path>/<session_id>.jsonl
18
- *
19
- * The encoded path uses a specific encoding (not standard URL encoding).
20
- */
21
- export function findSessionFile(sessionId, projectPath) {
22
- const claudeProjectsDir = join(homedir(), '.claude', 'projects');
23
- if (!existsSync(claudeProjectsDir)) {
24
- debugInject('Claude projects dir not found: %s', claudeProjectsDir);
25
- return null;
26
- }
27
- // Try to find the project folder
28
- // Claude Code uses a specific encoding for the path
29
- // We'll search for folders that might contain our session
30
- const projectFolders = readdirSync(claudeProjectsDir, { withFileTypes: true })
31
- .filter(d => d.isDirectory())
32
- .map(d => d.name);
33
- // First, try URL-encoded path
34
- const urlEncoded = encodeURIComponent(projectPath);
35
- if (projectFolders.includes(urlEncoded)) {
36
- const sessionPath = join(claudeProjectsDir, urlEncoded, `${sessionId}.jsonl`);
37
- if (existsSync(sessionPath)) {
38
- debugInject('Found session via URL encoding: %s', sessionPath);
39
- return sessionPath;
40
- }
41
- }
42
- // Try custom Claude encoding (%2F for /, etc.)
43
- const customEncoded = projectPath
44
- .replace(/\//g, '%2F')
45
- .replace(/:/g, '%3A');
46
- if (projectFolders.includes(customEncoded)) {
47
- const sessionPath = join(claudeProjectsDir, customEncoded, `${sessionId}.jsonl`);
48
- if (existsSync(sessionPath)) {
49
- debugInject('Found session via custom encoding: %s', sessionPath);
50
- return sessionPath;
51
- }
52
- }
53
- // Search in all project folders for the session
54
- for (const folder of projectFolders) {
55
- const sessionPath = join(claudeProjectsDir, folder, `${sessionId}.jsonl`);
56
- if (existsSync(sessionPath)) {
57
- debugInject('Found session by scanning: %s', sessionPath);
58
- return sessionPath;
59
- }
60
- }
61
- debugInject('Session file not found for: %s in %s', sessionId, projectPath);
62
- return null;
63
- }
64
- /**
65
- * Parse JSONL and extract ALL Claude's tool calls
66
- */
67
- export function parseSessionActions(sessionPath) {
68
- if (!existsSync(sessionPath)) {
69
- debugInject('Session file does not exist: %s', sessionPath);
70
- return [];
71
- }
72
- const content = readFileSync(sessionPath, 'utf-8');
73
- const lines = content.trim().split('\n').filter(Boolean);
74
- const actions = [];
75
- for (const line of lines) {
76
- try {
77
- const entry = JSON.parse(line);
78
- // Only process assistant messages (Claude's responses)
79
- if (entry.type !== 'assistant')
80
- continue;
81
- const timestamp = new Date(entry.timestamp).getTime();
82
- // Extract tool calls from content array
83
- for (const block of entry.message?.content || []) {
84
- if (block.type !== 'tool_use')
85
- continue;
86
- const action = parseToolCall(block, timestamp);
87
- if (action) {
88
- actions.push(action);
89
- }
90
- }
91
- }
92
- catch {
93
- // Skip malformed lines silently
94
- continue;
95
- }
96
- }
97
- debugInject('Parsed %d actions from session', actions.length);
98
- return actions;
99
- }
100
- /**
101
- * Get only NEW actions since last check timestamp.
102
- * This is the main function used by prompt-inject.
103
- */
104
- export function getNewActions(sessionPath, lastCheckedTimestamp) {
105
- const allActions = parseSessionActions(sessionPath);
106
- const newActions = allActions.filter(a => a.timestamp > lastCheckedTimestamp);
107
- debugInject('Found %d new actions since %d', newActions.length, lastCheckedTimestamp);
108
- return newActions;
109
- }
110
- /**
111
- * Get actions that MODIFY files (not reads).
112
- * Use this for drift detection - reads are exploration, not drift.
113
- */
114
- export function getModifyingActions(actions) {
115
- return actions.filter(a => a.type !== 'read' && a.type !== 'grep' && a.type !== 'glob');
116
- }
117
- /**
118
- * Extract all unique files touched by actions
119
- */
120
- export function extractFilesFromActions(actions) {
121
- const files = new Set();
122
- for (const action of actions) {
123
- for (const file of action.files) {
124
- files.add(file);
125
- }
126
- }
127
- return [...files];
128
- }
129
- /**
130
- * Extract unique folders from actions
131
- */
132
- export function extractFoldersFromActions(actions) {
133
- const folders = new Set();
134
- for (const action of actions) {
135
- for (const file of action.files) {
136
- const folder = dirname(file);
137
- if (folder && folder !== '.') {
138
- folders.add(folder);
139
- }
140
- }
141
- }
142
- return [...folders];
143
- }
144
- // ============================================
145
- // HELPERS
146
- // ============================================
147
- /**
148
- * Parse a single tool call block into ClaudeAction
149
- */
150
- function parseToolCall(block, timestamp) {
151
- const name = block.name?.toLowerCase();
152
- const input = block.input || {};
153
- switch (name) {
154
- case 'edit':
155
- return {
156
- type: 'edit',
157
- files: [input.file_path].filter(Boolean),
158
- timestamp
159
- };
160
- case 'multiedit':
161
- return {
162
- type: 'multiedit',
163
- files: [input.file_path].filter(Boolean),
164
- timestamp
165
- };
166
- case 'write':
167
- return {
168
- type: 'write',
169
- files: [input.file_path].filter(Boolean),
170
- timestamp
171
- };
172
- case 'bash':
173
- return {
174
- type: 'bash',
175
- files: extractFilesFromCommand(input.command || ''),
176
- command: input.command,
177
- timestamp
178
- };
179
- case 'read':
180
- return {
181
- type: 'read',
182
- files: [input.file_path].filter(Boolean),
183
- timestamp
184
- };
185
- case 'grep':
186
- return {
187
- type: 'grep',
188
- files: [input.path].filter(Boolean),
189
- timestamp
190
- };
191
- case 'glob':
192
- return {
193
- type: 'glob',
194
- files: [input.path].filter(Boolean),
195
- timestamp
196
- };
197
- default:
198
- // Ignore other tools (Task, WebFetch, etc.)
199
- return null;
200
- }
201
- }
202
- /**
203
- * Extract file paths from a bash command.
204
- * Basic extraction - not perfect but catches common patterns.
205
- */
206
- function extractFilesFromCommand(command) {
207
- if (!command)
208
- return [];
209
- const files = [];
210
- const patterns = [
211
- // Absolute paths: /path/to/file.ts
212
- /(?:^|\s)(\/[\w\-\.\/]+\.\w+)/g,
213
- // Relative paths with ./: ./src/file.ts
214
- /(?:^|\s)(\.\/[\w\-\.\/]+\.\w+)/g,
215
- // Relative paths: src/file.ts
216
- /(?:^|\s)([\w\-]+\/[\w\-\.\/]+\.\w+)/g,
217
- ];
218
- for (const pattern of patterns) {
219
- for (const match of command.matchAll(pattern)) {
220
- const file = match[1];
221
- // Filter out common non-files
222
- if (file && !file.startsWith('http') && !file.match(/^\d+\.\d+/)) {
223
- files.push(file);
224
- }
225
- }
226
- }
227
- return [...new Set(files)];
228
- }
229
- /**
230
- * Extract keywords from an action (for step storage)
231
- */
232
- export function extractKeywordsFromAction(action) {
233
- const keywords = [];
234
- // Extract from file names
235
- for (const file of action.files) {
236
- const fileName = file.split('/').pop() || '';
237
- const baseName = fileName.replace(/\.\w+$/, '');
238
- // Split camelCase and kebab-case
239
- const parts = baseName
240
- .replace(/([a-z])([A-Z])/g, '$1 $2')
241
- .replace(/[-_]/g, ' ')
242
- .toLowerCase()
243
- .split(/\s+/)
244
- .filter(p => p.length > 2);
245
- keywords.push(...parts);
246
- }
247
- // Extract from bash command
248
- if (action.command) {
249
- const commandParts = action.command
250
- .toLowerCase()
251
- .split(/\s+/)
252
- .filter(p => p.length > 3 && !p.startsWith('-'));
253
- keywords.push(...commandParts.slice(0, 5));
254
- }
255
- return [...new Set(keywords)];
256
- }
@@ -1 +0,0 @@
1
- export * from './store/index.js';