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,174 +1,50 @@
1
1
  // LLM-based extraction using Anthropic Claude Haiku for drift detection
2
- import Anthropic from '@anthropic-ai/sdk';
3
- import { config } from 'dotenv';
4
- import { join } from 'path';
5
- import { homedir } from 'os';
6
- import { existsSync } from 'fs';
7
- import { debugLLM } from './debug.js';
8
- // Load ~/.grov/.env as fallback for API key
9
- // This allows users to store their API key in a safe location outside any repo
10
- const grovEnvPath = join(homedir(), '.grov', '.env');
11
- if (existsSync(grovEnvPath)) {
12
- config({ path: grovEnvPath });
13
- }
14
- let anthropicClient = null;
15
- /**
16
- * Initialize the Anthropic client
17
- */
18
- function getAnthropicClient() {
19
- if (!anthropicClient) {
20
- const apiKey = process.env.ANTHROPIC_API_KEY;
21
- if (!apiKey) {
22
- throw new Error('ANTHROPIC_API_KEY environment variable is required for drift detection');
23
- }
24
- anthropicClient = new Anthropic({ apiKey });
25
- }
26
- return anthropicClient;
27
- }
28
- /**
29
- * Extract intent from first user prompt using Haiku
30
- * Called once at session start to populate session_states
31
- * Falls back to basic extraction if API unavailable (for hook compatibility)
32
- */
33
- export async function extractIntent(firstPrompt) {
34
- // Check availability first - allows hook to work without API key
35
- if (!isIntentExtractionAvailable()) {
36
- return createFallbackIntent(firstPrompt);
37
- }
2
+ import { debugLLM } from '../../utils/debug.js';
3
+ import { forwardToAnthropic } from '../../integrations/proxy/agents/claude/forwarder.js';
4
+ import { buildSafeHeaders } from '../../integrations/proxy/config.js';
5
+ import { isDebugMode } from '../../integrations/proxy/utils/logging.js';
6
+ // Haiku model constant
7
+ // Model list: https://docs.anthropic.com/en/docs/about-claude/models
8
+ const HAIKU_MODEL = 'claude-haiku-4-5-20251001';
9
+ async function callHaiku(maxTokens, prompt, headers, context = 'unknown') {
10
+ if (isDebugMode())
11
+ console.log(`[HAIKU] ${context} started`);
12
+ // Use same header filtering as proxy forward - includes all Claude Code headers
13
+ const safeHeaders = buildSafeHeaders(headers);
38
14
  try {
39
- const client = getAnthropicClient();
40
- const prompt = `Analyze this user request and extract structured intent for a coding assistant session.
41
-
42
- USER REQUEST:
43
- ${firstPrompt.substring(0, 2000)}
44
-
45
- Extract as JSON:
46
- {
47
- "goal": "A single, high-density sentence describing the technical intent. RULES: 1. No bullet points, no newlines. 2. Must include the main Technology Name (e.g. 'Prometheus', 'React', 'AWS') if inferred. 3. If the user provided a list, synthesize it into one summary statement. Example: 'Implement Prometheus metrics collection with counter and gauge primitives' instead of 'Add metrics: - counters - gauges'.",
48
- "expected_scope": ["list", "of", "files/folders", "likely", "to", "be", "modified"],
49
- "constraints": ["EXPLICIT restrictions from the user - see examples below"],
50
- "success_criteria": ["How to know when the task is complete"],
51
- "keywords": ["relevant", "technical", "terms"]
52
- }
53
-
54
- ═══════════════════════════════════════════════════════════════
55
- CONSTRAINTS EXTRACTION - BE VERY THOROUGH
56
- ═══════════════════════════════════════════════════════════════
57
-
58
- Look for NEGATIVE constraints (things NOT to do):
59
- - "NU modifica" / "DON'T modify" / "NEVER change" / "don't touch"
60
- - "NU rula" / "DON'T run" / "NO commands" / "don't execute"
61
- - "fără X" / "without X" / "except X" / "not including"
62
- - "nu scrie cod" / "don't write code" / "just plan"
63
-
64
- Look for POSITIVE constraints (things MUST do / ONLY do):
65
- - "ONLY modify X" / "DOAR în X" / "only in folder Y"
66
- - "must use Y" / "trebuie să folosești Y"
67
- - "keep it simple" / "no external dependencies"
68
- - "use TypeScript" / "must be async"
69
-
70
- EXAMPLES:
71
- Input: "Fix bug in auth. NU modifica nimic in afara de sandbox/, NU rula comenzi."
72
- Output constraints: ["DO NOT modify files outside sandbox/", "DO NOT run commands"]
73
-
74
- Input: "Add feature X. Only use standard library, keep backward compatible."
75
- Output constraints: ["ONLY use standard library", "Keep backward compatible"]
76
-
77
- Input: "Analyze code and create plan. Nu scrie cod inca, doar planifica."
78
- Output constraints: ["DO NOT write code yet", "Only create plan/analysis"]
79
-
80
- For expected_scope:
81
- - Include file patterns (e.g., "src/auth/", "*.test.ts", "sandbox/")
82
- - Include component/module names mentioned
83
- - Be conservative - only include clearly relevant areas
84
-
85
- RESPONSE RULES:
86
- - English only (translate Romanian/other languages to English)
87
- - No emojis
88
- - Valid JSON only
89
- - If no constraints found, return empty array []`;
90
- const response = await client.messages.create({
91
- model: 'claude-haiku-4-5-20251001',
92
- max_tokens: 500,
15
+ const result = await forwardToAnthropic({
16
+ model: HAIKU_MODEL,
17
+ max_tokens: maxTokens,
93
18
  messages: [{ role: 'user', content: prompt }],
94
- });
95
- const content = response.content?.[0];
96
- if (!content || content.type !== 'text') {
97
- return createFallbackIntent(firstPrompt);
98
- }
99
- try {
100
- const jsonMatch = content.text.match(/\{[\s\S]*\}/);
101
- if (!jsonMatch) {
102
- return createFallbackIntent(firstPrompt);
103
- }
104
- const parsed = JSON.parse(jsonMatch[0]);
105
- return {
106
- goal: typeof parsed.goal === 'string' ? parsed.goal : '', // Don't fallback to prompt
107
- expected_scope: Array.isArray(parsed.expected_scope)
108
- ? parsed.expected_scope.filter((s) => typeof s === 'string')
109
- : [],
110
- constraints: Array.isArray(parsed.constraints)
111
- ? parsed.constraints.filter((c) => typeof c === 'string')
112
- : [],
113
- success_criteria: Array.isArray(parsed.success_criteria)
114
- ? parsed.success_criteria.filter((s) => typeof s === 'string')
115
- : [],
116
- keywords: Array.isArray(parsed.keywords)
117
- ? parsed.keywords.filter((k) => typeof k === 'string')
118
- : [],
119
- };
120
- }
121
- catch {
122
- return createFallbackIntent(firstPrompt);
19
+ }, safeHeaders);
20
+ // Check for error response
21
+ if (result.statusCode >= 400) {
22
+ const errorBody = result.body;
23
+ throw new Error(errorBody.error?.message || `HTTP ${result.statusCode}`);
123
24
  }
25
+ // Parse response
26
+ const body = result.body;
27
+ const text = body.content?.[0]?.type === 'text' ? body.content[0].text || '' : '';
28
+ if (isDebugMode())
29
+ console.log(`[HAIKU] ${context} success`);
30
+ return { text, success: true };
124
31
  }
125
- catch {
126
- // Outer catch - API errors, network issues, etc.
127
- return createFallbackIntent(firstPrompt);
32
+ catch (err) {
33
+ // Always log errors
34
+ console.error(`[HAIKU] ${context} error:`, err.message);
35
+ return { text: '', success: false };
128
36
  }
129
37
  }
130
- /**
131
- * Fallback intent extraction without LLM
132
- */
133
- function createFallbackIntent(prompt) {
134
- // Basic keyword extraction
135
- const words = prompt.toLowerCase().split(/\s+/);
136
- const techKeywords = words.filter(w => w.length > 3 &&
137
- /^[a-z]+$/.test(w) &&
138
- !['this', 'that', 'with', 'from', 'have', 'will', 'would', 'could', 'should'].includes(w));
139
- // Extract file patterns
140
- const filePatterns = prompt.match(/[\w\/.-]+\.(ts|js|tsx|jsx|py|go|rs|java|css|html|md)/g) || [];
141
- return {
142
- goal: '', // Empty - don't copy user prompt as goal; goal should be synthesized only
143
- expected_scope: [...new Set(filePatterns)].slice(0, 5),
144
- constraints: [],
145
- success_criteria: [],
146
- keywords: [...new Set(techKeywords)].slice(0, 10),
147
- };
148
- }
149
- /**
150
- * Check if intent extraction is available
151
- */
152
- export function isIntentExtractionAvailable() {
153
- return !!(process.env.ANTHROPIC_API_KEY || process.env.GROV_API_KEY);
154
- }
155
38
  // ============================================
156
39
  // SESSION SUMMARY FOR CLEAR OPERATION
157
40
  // Reference: plan_proxy_local.md Section 2.3, 4.5
158
41
  // ============================================
159
- /**
160
- * Check if session summary generation is available
161
- */
162
- export function isSummaryAvailable() {
163
- return !!(process.env.ANTHROPIC_API_KEY || process.env.GROV_API_KEY);
164
- }
165
42
  /**
166
43
  * Generate session summary for CLEAR operation
167
44
  * Reference: plan_proxy_local.md Section 2.3, 4.5
168
45
  */
169
- export async function generateSessionSummary(sessionState, steps, maxTokens = 800 // Default 800, CLEAR mode uses 15000
170
- ) {
171
- const client = getAnthropicClient();
46
+ export async function generateSessionSummary(sessionState, steps, maxTokens = 800, // Default 800, CLEAR mode uses 15000
47
+ headers) {
172
48
  // For larger summaries, include more steps
173
49
  const stepLimit = maxTokens > 5000 ? 50 : 20;
174
50
  const wordLimit = Math.min(Math.floor(maxTokens / 2), 10000); // ~2 tokens per word
@@ -193,8 +69,6 @@ export async function generateSessionSummary(sessionState, steps, maxTokens = 80
193
69
 
194
70
  ORIGINAL GOAL: ${sessionState.original_goal || 'Not specified'}
195
71
 
196
- EXPECTED SCOPE: ${sessionState.expected_scope.join(', ') || 'Not specified'}
197
-
198
72
  CONSTRAINTS: ${sessionState.constraints.join(', ') || 'None'}
199
73
 
200
74
  ACTIONS TAKEN:
@@ -210,18 +84,13 @@ Create a summary with these sections (keep total under ${wordLimit} words):
210
84
  ${maxTokens > 5000 ? '7. IMPORTANT CONTEXT: (any critical information that must not be lost)' : ''}
211
85
 
212
86
  Format as plain text, not JSON.`;
213
- const response = await client.messages.create({
214
- model: 'claude-haiku-4-5-20251001',
215
- max_tokens: maxTokens,
216
- messages: [{ role: 'user', content: prompt }],
217
- });
218
- const content = response.content?.[0];
219
- if (!content || content.type !== 'text') {
87
+ const haikuResult = await callHaiku(maxTokens, prompt, headers, 'generateSessionSummary');
88
+ if (!haikuResult.success || !haikuResult.text) {
220
89
  return createFallbackSummary(sessionState, steps);
221
90
  }
222
91
  return `PREVIOUS SESSION CONTEXT (auto-generated after context limit):
223
92
 
224
- ${content.text}`;
93
+ ${haikuResult.text}`;
225
94
  }
226
95
  /**
227
96
  * Create fallback summary without LLM
@@ -239,12 +108,6 @@ ${files.slice(0, 10).map(f => `- ${f}`).join('\n') || '- None recorded'}
239
108
 
240
109
  Please continue from where you left off.`;
241
110
  }
242
- /**
243
- * Check if task analysis is available
244
- */
245
- export function isTaskAnalysisAvailable() {
246
- return !!(process.env.ANTHROPIC_API_KEY || process.env.GROV_API_KEY);
247
- }
248
111
  /**
249
112
  * Format conversation messages for prompt
250
113
  */
@@ -280,8 +143,7 @@ function formatToolCalls(steps) {
280
143
  * Called after each main model response to orchestrate sessions
281
144
  * Also compresses reasoning for steps if assistantResponse > 1000 chars
282
145
  */
283
- export async function analyzeTaskContext(currentSession, latestUserMessage, recentSteps, assistantResponse, conversationHistory) {
284
- const client = getAnthropicClient();
146
+ export async function analyzeTaskContext(currentSession, latestUserMessage, recentSteps, assistantResponse, conversationHistory, headers) {
285
147
  // Check if we need to compress reasoning
286
148
  const needsCompression = assistantResponse.length > 1000;
287
149
  const compressionInstruction = needsCompression
@@ -309,10 +171,13 @@ ${toolCallsText}
309
171
  <output>
310
172
  Return a JSON object with these fields:
311
173
  - task_type: one of "information", "planning", or "implementation"
312
- - action: one of "continue", "task_complete", "new_task", or "subtask_complete"
174
+ - action: one of "continue", "task_complete", "new_task", "subtask", "parallel_task", or "subtask_complete"
313
175
  - task_id: existing session_id "${currentSession?.session_id || 'NEW'}" or "NEW" for new task
314
176
  - current_goal: "SYNTHESIZE a concise goal (max 150 chars). RULES: 1. If original_goal is empty, SYNTHESIZE from user messages. 2. DO NOT copy the user's request verbatim - summarize it. 3. Start with Technology/Component name. 4. One sentence, no newlines. Example: 'TypeScript Logger with level filtering and JSON output' NOT 'Create a structured logger in /home/... with debug, info...'"
315
177
  - reasoning: brief explanation of why you made this decision${compressionInstruction}
178
+
179
+ CONDITIONAL FIELD (only include when action is "new_task", "subtask", or "parallel_task"):
180
+ - constraints: array of explicit restrictions from user (see step_4_extract_constraints). If no restrictions found, use empty array [].
316
181
  </output>
317
182
 
318
183
  <step_1_identify_task_type>
@@ -471,6 +336,57 @@ Reason: The new task was requested AND completed. Use task_complete so it gets s
471
336
  The key insight: task_complete saves the memory. If you return new_task, the work won't be saved until a FUTURE completion. If Claude already finished the work, use task_complete.
472
337
  </step_3_detect_new_task>
473
338
 
339
+ <step_4_extract_constraints>
340
+ ONLY perform this step if action is "new_task", "subtask", or "parallel_task".
341
+ If action is "continue" or "task_complete", skip this and set constraints to empty array [].
342
+
343
+ WHAT IS A CONSTRAINT?
344
+ A constraint is an EXPLICIT restriction the user stated about HOW to do the task.
345
+ It is NOT the task itself - it is a LIMIT on how the task should be done.
346
+
347
+ TWO TYPES:
348
+
349
+ 1. NEGATIVE CONSTRAINTS - Things Claude must NOT do:
350
+ Pattern: "don't", "do not", "never", "no", "without", "except", "avoid"
351
+
352
+ Examples:
353
+ - "Don't modify files outside src/" becomes "DO NOT modify files outside src/"
354
+ - "No external dependencies" becomes "DO NOT add external dependencies"
355
+ - "Just plan, don't code yet" becomes "DO NOT write code"
356
+
357
+ 2. POSITIVE CONSTRAINTS - Things Claude MUST do or ONLY do:
358
+ Pattern: "only", "must", "always", "keep", "use only"
359
+
360
+ Examples:
361
+ - "Only modify the auth module" becomes "ONLY modify auth module"
362
+ - "Must use TypeScript" becomes "MUST use TypeScript"
363
+ - "Keep backward compatible" becomes "MUST keep backward compatible"
364
+
365
+ WHAT IS NOT A CONSTRAINT:
366
+ - The task itself: "Fix the bug" is the goal, not a constraint
367
+ - General preferences: "Make it fast" is too vague, not an explicit restriction
368
+ - Questions: "Should I use Redis?" is not a restriction
369
+
370
+ OUTPUT FORMAT:
371
+ - Start each constraint with "DO NOT", "ONLY", or "MUST"
372
+ - Be specific and actionable
373
+ - If no constraints found, return empty array []
374
+
375
+ EXAMPLES:
376
+
377
+ User message: "Fix the auth bug. Don't touch the database code."
378
+ constraints: ["DO NOT modify database code"]
379
+
380
+ User message: "Add caching. Only use Redis, must be async."
381
+ constraints: ["ONLY use Redis", "MUST be async"]
382
+
383
+ User message: "Explain how auth works"
384
+ constraints: []
385
+
386
+ User message: "Implement feature X"
387
+ constraints: []
388
+ </step_4_extract_constraints>
389
+
474
390
  <important_notes>
475
391
  Do not rely on specific keywords in any language. The same intent can be expressed many different ways across languages and phrasings. Always understand the intent from the full context.
476
392
 
@@ -490,12 +406,21 @@ RESPONSE RULES:
490
406
  - No markdown formatting, no emojis
491
407
  </important_notes>`;
492
408
  debugLLM('analyzeTaskContext', `Calling Haiku for task analysis (needsCompression=${needsCompression})`);
493
- const response = await client.messages.create({
494
- model: 'claude-haiku-4-5-20251001',
495
- max_tokens: needsCompression ? 800 : 400,
496
- messages: [{ role: 'user', content: prompt }],
497
- });
498
- const text = response.content[0].type === 'text' ? response.content[0].text : '';
409
+ const haikuResult = await callHaiku(needsCompression ? 800 : 400, prompt, headers, 'analyzeTaskContext');
410
+ if (!haikuResult.success) {
411
+ // Fallback on error
412
+ const fallbackGoal = currentSession?.original_goal || '';
413
+ return {
414
+ task_type: 'implementation',
415
+ action: currentSession ? 'continue' : 'new_task',
416
+ task_id: currentSession?.session_id || 'NEW',
417
+ current_goal: fallbackGoal,
418
+ reasoning: 'Fallback due to Haiku error',
419
+ step_reasoning: assistantResponse.substring(0, 1000),
420
+ constraints: [],
421
+ };
422
+ }
423
+ const text = haikuResult.text;
499
424
  try {
500
425
  // Try to parse JSON from response (may have extra text)
501
426
  const jsonMatch = text.match(/\{[\s\S]*\}/);
@@ -511,7 +436,16 @@ RESPONSE RULES:
511
436
  if (!needsCompression && assistantResponse.length > 0) {
512
437
  analysis.step_reasoning = assistantResponse.substring(0, 1000);
513
438
  }
514
- debugLLM('analyzeTaskContext', `Result: task_type=${analysis.task_type}, action=${analysis.action}, goal="${analysis.current_goal?.substring(0, 50) || 'N/A'}" reasoning="${analysis.reasoning?.substring(0, 150) || 'none'}"`);
439
+ // Parse constraints (only for new_task/subtask/parallel_task actions)
440
+ if (analysis.constraints) {
441
+ analysis.constraints = Array.isArray(analysis.constraints)
442
+ ? analysis.constraints.filter((c) => typeof c === 'string')
443
+ : [];
444
+ }
445
+ else {
446
+ analysis.constraints = [];
447
+ }
448
+ debugLLM('analyzeTaskContext', `Result: task_type=${analysis.task_type}, action=${analysis.action}, goal="${analysis.current_goal?.substring(0, 50) || 'N/A'}" constraints=${analysis.constraints.length} reasoning="${analysis.reasoning?.substring(0, 150) || 'none'}"`);
515
449
  return analysis;
516
450
  }
517
451
  catch (parseError) {
@@ -528,15 +462,10 @@ RESPONSE RULES:
528
462
  current_goal: fallbackGoal,
529
463
  reasoning: 'Fallback due to parse error',
530
464
  step_reasoning: assistantResponse.substring(0, 1000),
465
+ constraints: [],
531
466
  };
532
467
  }
533
468
  }
534
- /**
535
- * Check if reasoning extraction is available
536
- */
537
- export function isReasoningExtractionAvailable() {
538
- return !!process.env.ANTHROPIC_API_KEY || !!process.env.GROV_API_KEY;
539
- }
540
469
  /**
541
470
  * Extract reasoning trace and decisions from steps
542
471
  * Called at task_complete to populate team memory with rich context
@@ -544,8 +473,7 @@ export function isReasoningExtractionAvailable() {
544
473
  * @param formattedSteps - Pre-formatted XML string with grouped steps and actions
545
474
  * @param originalGoal - The original task goal
546
475
  */
547
- export async function extractReasoningAndDecisions(formattedSteps, originalGoal) {
548
- const client = getAnthropicClient();
476
+ export async function extractReasoningAndDecisions(formattedSteps, originalGoal, headers) {
549
477
  if (formattedSteps.length < 50) {
550
478
  return { system_name: null, summary: null, reasoning_trace: [], decisions: [] };
551
479
  }
@@ -900,16 +828,14 @@ Before responding, verify:
900
828
 
901
829
  Return ONLY valid JSON, no markdown code blocks, no explanation.`;
902
830
  debugLLM('extractReasoningAndDecisions', `Analyzing formatted steps, ${formattedSteps.length} chars`);
831
+ const haikuResult = await callHaiku(1500, prompt, headers, 'extractReasoningAndDecisions');
832
+ if (!haikuResult.success) {
833
+ return { system_name: null, summary: null, reasoning_trace: [], decisions: [] };
834
+ }
903
835
  try {
904
- const response = await client.messages.create({
905
- model: 'claude-haiku-4-5-20251001',
906
- max_tokens: 1500,
907
- messages: [{ role: 'user', content: prompt }],
908
- });
909
- const text = response.content[0].type === 'text' ? response.content[0].text : '';
836
+ const text = haikuResult.text;
910
837
  const jsonMatch = text.match(/\{[\s\S]*\}/);
911
838
  if (!jsonMatch) {
912
- console.error('[LLM-EXTRACTOR] No JSON in response');
913
839
  return { system_name: null, summary: null, reasoning_trace: [], decisions: [] };
914
840
  }
915
841
  // Try to parse JSON, with repair attempts for common Haiku formatting issues
@@ -938,13 +864,11 @@ Return ONLY valid JSON, no markdown code blocks, no explanation.`;
938
864
  const extractedSystemName = systemMatch ? systemMatch[1] : undefined;
939
865
  result = { system_name: extractedSystemName, knowledge_pairs: pairs, decisions: [] };
940
866
  }
941
- catch (fallbackError) {
942
- console.error('[LLM-EXTRACTOR] JSON parse failed');
867
+ catch {
943
868
  throw parseError;
944
869
  }
945
870
  }
946
871
  else {
947
- console.error('[LLM-EXTRACTOR] JSON parse failed');
948
872
  throw parseError;
949
873
  }
950
874
  }
@@ -984,12 +908,6 @@ Return ONLY valid JSON, no markdown code blocks, no explanation.`;
984
908
  return { system_name: null, summary: null, reasoning_trace: [], decisions: [] };
985
909
  }
986
910
  }
987
- /**
988
- * Check if shouldUpdateMemory is available
989
- */
990
- export function isShouldUpdateAvailable() {
991
- return !!process.env.ANTHROPIC_API_KEY || !!process.env.GROV_API_KEY;
992
- }
993
911
  /**
994
912
  * Decide if a memory should be updated based on new session data
995
913
  * Called when a match is found before sync
@@ -1447,25 +1365,21 @@ Input: task_type=information, query="Ok I understand now", files_touched=[]
1447
1365
  Output: {"should_update": false, "reason": "User acknowledged explanation but did not confirm any change", "superseded_mapping": [], "condensed_old_reasoning": null, "evolution_summary": null}
1448
1366
  </examples>`;
1449
1367
  }
1450
- export async function shouldUpdateMemory(existingMemory, newData, sessionContext) {
1451
- const client = getAnthropicClient();
1368
+ export async function shouldUpdateMemory(existingMemory, newData, sessionContext, headers) {
1452
1369
  // Check if evolution_steps consolidation is needed
1453
1370
  const evolutionCount = existingMemory.evolution_steps?.length || 0;
1454
1371
  const needsConsolidation = evolutionCount > 10;
1455
1372
  // Build the prompt with all context
1456
1373
  const prompt = buildShouldUpdatePrompt(existingMemory, newData, sessionContext, needsConsolidation, evolutionCount);
1457
1374
  debugLLM('shouldUpdateMemory', `Analyzing memory update (needsConsolidation=${needsConsolidation})`);
1375
+ const haikuResult = await callHaiku(needsConsolidation ? 1500 : 800, prompt, headers, 'shouldUpdateMemory');
1376
+ if (!haikuResult.success) {
1377
+ return createFallbackResult(sessionContext);
1378
+ }
1458
1379
  try {
1459
- const response = await client.messages.create({
1460
- model: 'claude-haiku-4-5-20251001',
1461
- max_tokens: needsConsolidation ? 1500 : 800,
1462
- messages: [{ role: 'user', content: prompt }],
1463
- });
1464
- const text = response.content[0].type === 'text' ? response.content[0].text : '';
1465
- // Try to parse JSON from response
1380
+ const text = haikuResult.text;
1466
1381
  const jsonMatch = text.match(/\{[\s\S]*\}/);
1467
1382
  if (!jsonMatch) {
1468
- console.error('[HAIKU] No JSON in response');
1469
1383
  return createFallbackResult(sessionContext);
1470
1384
  }
1471
1385
  // Parse and validate response
@@ -1473,8 +1387,7 @@ export async function shouldUpdateMemory(existingMemory, newData, sessionContext
1473
1387
  try {
1474
1388
  result = JSON.parse(jsonMatch[0]);
1475
1389
  }
1476
- catch (parseErr) {
1477
- console.error('[HAIKU] JSON parse failed');
1390
+ catch {
1478
1391
  return createFallbackResult(sessionContext);
1479
1392
  }
1480
1393
  // Ensure required fields have defaults
@@ -1487,8 +1400,7 @@ export async function shouldUpdateMemory(existingMemory, newData, sessionContext
1487
1400
  debugLLM('shouldUpdateMemory', `Result: should_update=${result.should_update}, reason="${result.reason.substring(0, 50)}"`);
1488
1401
  return result;
1489
1402
  }
1490
- catch (error) {
1491
- console.error('[HAIKU] Error:', String(error));
1403
+ catch {
1492
1404
  return createFallbackResult(sessionContext);
1493
1405
  }
1494
1406
  }
@@ -11,9 +11,7 @@ function rowToSessionState(row) {
11
11
  project_path: row.project_path,
12
12
  original_goal: row.original_goal,
13
13
  raw_user_prompt: row.raw_user_prompt,
14
- expected_scope: safeJsonParse(row.expected_scope, []),
15
14
  constraints: safeJsonParse(row.constraints, []),
16
- keywords: safeJsonParse(row.keywords, []),
17
15
  escalation_count: row.escalation_count || 0,
18
16
  last_checked_at: row.last_checked_at || 0,
19
17
  start_time: row.start_time,
@@ -47,8 +45,6 @@ function rowToSessionState(row) {
47
45
  * Uses INSERT OR IGNORE to handle race conditions safely.
48
46
  */
49
47
  export function createSessionState(input) {
50
- // DEBUG: Commented out for cleaner terminal - uncomment when debugging
51
- // console.log(`[DEBUG-DB] createSessionState called with raw_user_prompt="${input.raw_user_prompt?.substring(0, 50)}"`);
52
48
  const database = getDb();
53
49
  const now = new Date().toISOString();
54
50
  const sessionState = {
@@ -58,9 +54,7 @@ export function createSessionState(input) {
58
54
  project_path: input.project_path,
59
55
  original_goal: input.original_goal,
60
56
  raw_user_prompt: input.raw_user_prompt,
61
- expected_scope: input.expected_scope || [],
62
57
  constraints: input.constraints || [],
63
- keywords: input.keywords || [],
64
58
  escalation_count: 0,
65
59
  last_checked_at: 0,
66
60
  start_time: now,
@@ -87,18 +81,16 @@ export function createSessionState(input) {
87
81
  const stmt = database.prepare(`
88
82
  INSERT OR IGNORE INTO session_states (
89
83
  session_id, user_id, project_path, original_goal, raw_user_prompt,
90
- expected_scope, constraints, keywords,
84
+ constraints,
91
85
  token_count, escalation_count, session_mode,
92
86
  waiting_for_recovery, last_checked_at, last_clear_at,
93
87
  start_time, last_update, status,
94
88
  parent_session_id, task_type,
95
89
  success_criteria, last_drift_score, pending_recovery_plan, drift_history,
96
90
  completed_at
97
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
91
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
98
92
  `);
99
- stmt.run(sessionState.session_id, sessionState.user_id || null, sessionState.project_path, sessionState.original_goal || null, sessionState.raw_user_prompt || null, JSON.stringify(sessionState.expected_scope), JSON.stringify(sessionState.constraints), JSON.stringify(sessionState.keywords), sessionState.token_count, sessionState.escalation_count, sessionState.session_mode, sessionState.waiting_for_recovery ? 1 : 0, sessionState.last_checked_at, sessionState.last_clear_at || null, sessionState.start_time, sessionState.last_update, sessionState.status, sessionState.parent_session_id || null, sessionState.task_type, JSON.stringify(sessionState.success_criteria || []), sessionState.last_drift_score || null, sessionState.pending_recovery_plan ? JSON.stringify(sessionState.pending_recovery_plan) : null, JSON.stringify(sessionState.drift_history || []), sessionState.completed_at || null);
100
- // DEBUG: Commented out for cleaner terminal - uncomment when debugging
101
- // console.log(`[DEBUG-DB] INSERT done. sessionState.raw_user_prompt="${sessionState.raw_user_prompt?.substring(0, 50)}"`);
93
+ stmt.run(sessionState.session_id, sessionState.user_id || null, sessionState.project_path, sessionState.original_goal || null, sessionState.raw_user_prompt || null, JSON.stringify(sessionState.constraints), sessionState.token_count, sessionState.escalation_count, sessionState.session_mode, sessionState.waiting_for_recovery ? 1 : 0, sessionState.last_checked_at, sessionState.last_clear_at || null, sessionState.start_time, sessionState.last_update, sessionState.status, sessionState.parent_session_id || null, sessionState.task_type, JSON.stringify(sessionState.success_criteria || []), sessionState.last_drift_score || null, sessionState.pending_recovery_plan ? JSON.stringify(sessionState.pending_recovery_plan) : null, JSON.stringify(sessionState.drift_history || []), sessionState.completed_at || null);
102
94
  return sessionState;
103
95
  }
104
96
  /**
@@ -130,18 +122,10 @@ export function updateSessionState(sessionId, updates) {
130
122
  setClauses.push('original_goal = ?');
131
123
  params.push(updates.original_goal || null);
132
124
  }
133
- if (updates.expected_scope !== undefined) {
134
- setClauses.push('expected_scope = ?');
135
- params.push(JSON.stringify(updates.expected_scope));
136
- }
137
125
  if (updates.constraints !== undefined) {
138
126
  setClauses.push('constraints = ?');
139
127
  params.push(JSON.stringify(updates.constraints));
140
128
  }
141
- if (updates.keywords !== undefined) {
142
- setClauses.push('keywords = ?');
143
- params.push(JSON.stringify(updates.keywords));
144
- }
145
129
  if (updates.token_count !== undefined) {
146
130
  setClauses.push('token_count = ?');
147
131
  params.push(updates.token_count);
@@ -0,0 +1 @@
1
+ export * from './index.js';
@@ -1,2 +1,2 @@
1
1
  // Re-export from modular store for backward compatibility
2
- export * from './store/index.js';
2
+ export * from './index.js';
@@ -72,9 +72,7 @@ interface SessionStateBase {
72
72
  project_path: string;
73
73
  original_goal?: string;
74
74
  raw_user_prompt?: string;
75
- expected_scope: string[];
76
75
  constraints: string[];
77
- keywords: string[];
78
76
  escalation_count: number;
79
77
  last_checked_at: number;
80
78
  start_time: string;
@@ -113,9 +111,7 @@ export interface CreateSessionStateInput {
113
111
  project_path: string;
114
112
  original_goal?: string;
115
113
  raw_user_prompt?: string;
116
- expected_scope?: string[];
117
114
  constraints?: string[];
118
- keywords?: string[];
119
115
  success_criteria?: string[];
120
116
  parent_session_id?: string;
121
117
  task_type?: TaskType;
@@ -0,0 +1,27 @@
1
+ import type { Memory } from '@grov/shared';
2
+ /**
3
+ * Get current project path
4
+ * Used as cache key and for API filtering
5
+ *
6
+ * Cursor sets WORKSPACE_FOLDER_PATHS env var with the open workspace path.
7
+ * We extract just the folder name to match how proxy stores project_path.
8
+ */
9
+ export declare function getProjectPath(): string;
10
+ /**
11
+ * Store memories from preview call
12
+ * Indexes by 8-char ID prefix for fast lookup
13
+ */
14
+ export declare function setPreviewCache(memories: Memory[]): void;
15
+ /**
16
+ * Get memory by 8-char ID
17
+ * Handles both 8-char and full UUID (extracts first 8 chars)
18
+ */
19
+ export declare function getMemoryById(id: string): Memory | null;
20
+ /**
21
+ * Get list of cached IDs (for error messages)
22
+ */
23
+ export declare function getCachedIds(): string[];
24
+ /**
25
+ * Clear preview cache (e.g., when project changes)
26
+ */
27
+ export declare function clearPreviewCache(): void;