grov 0.1.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -88
- package/dist/cli.js +23 -37
- package/dist/commands/capture.js +1 -1
- package/dist/commands/disable.d.ts +1 -0
- package/dist/commands/disable.js +14 -0
- package/dist/commands/drift-test.js +56 -68
- package/dist/commands/init.js +29 -17
- package/dist/commands/proxy-status.d.ts +1 -0
- package/dist/commands/proxy-status.js +32 -0
- package/dist/commands/unregister.js +7 -1
- package/dist/lib/correction-builder-proxy.d.ts +16 -0
- package/dist/lib/correction-builder-proxy.js +125 -0
- package/dist/lib/correction-builder.js +1 -1
- package/dist/lib/drift-checker-proxy.d.ts +63 -0
- package/dist/lib/drift-checker-proxy.js +373 -0
- package/dist/lib/drift-checker.js +1 -1
- package/dist/lib/hooks.d.ts +11 -0
- package/dist/lib/hooks.js +33 -0
- package/dist/lib/llm-extractor.d.ts +60 -11
- package/dist/lib/llm-extractor.js +431 -98
- package/dist/lib/settings.d.ts +19 -0
- package/dist/lib/settings.js +63 -0
- package/dist/lib/store.d.ts +201 -43
- package/dist/lib/store.js +653 -90
- package/dist/proxy/action-parser.d.ts +58 -0
- package/dist/proxy/action-parser.js +196 -0
- package/dist/proxy/config.d.ts +26 -0
- package/dist/proxy/config.js +67 -0
- package/dist/proxy/forwarder.d.ts +24 -0
- package/dist/proxy/forwarder.js +119 -0
- package/dist/proxy/index.d.ts +1 -0
- package/dist/proxy/index.js +30 -0
- package/dist/proxy/request-processor.d.ts +12 -0
- package/dist/proxy/request-processor.js +120 -0
- package/dist/proxy/response-processor.d.ts +14 -0
- package/dist/proxy/response-processor.js +138 -0
- package/dist/proxy/server.d.ts +9 -0
- package/dist/proxy/server.js +904 -0
- package/package.json +8 -3
|
@@ -2,8 +2,18 @@
|
|
|
2
2
|
// and Anthropic Claude Haiku for drift detection
|
|
3
3
|
import OpenAI from 'openai';
|
|
4
4
|
import Anthropic from '@anthropic-ai/sdk';
|
|
5
|
+
import { config } from 'dotenv';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { homedir } from 'os';
|
|
8
|
+
import { existsSync } from 'fs';
|
|
5
9
|
import { debugLLM } from './debug.js';
|
|
6
10
|
import { truncate } from './utils.js';
|
|
11
|
+
// Load ~/.grov/.env as fallback for API key
|
|
12
|
+
// This allows users to store their API key in a safe location outside any repo
|
|
13
|
+
const grovEnvPath = join(homedir(), '.grov', '.env');
|
|
14
|
+
if (existsSync(grovEnvPath)) {
|
|
15
|
+
config({ path: grovEnvPath });
|
|
16
|
+
}
|
|
7
17
|
let client = null;
|
|
8
18
|
let anthropicClient = null;
|
|
9
19
|
/**
|
|
@@ -39,6 +49,133 @@ function getAnthropicClient() {
|
|
|
39
49
|
export function isLLMAvailable() {
|
|
40
50
|
return !!process.env.OPENAI_API_KEY;
|
|
41
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Extract intent from first user prompt using Haiku
|
|
54
|
+
* Called once at session start to populate session_states
|
|
55
|
+
* Falls back to basic extraction if API unavailable (for hook compatibility)
|
|
56
|
+
*/
|
|
57
|
+
export async function extractIntent(firstPrompt) {
|
|
58
|
+
// Check availability first - allows hook to work without API key
|
|
59
|
+
if (!isIntentExtractionAvailable()) {
|
|
60
|
+
return createFallbackIntent(firstPrompt);
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const client = getAnthropicClient();
|
|
64
|
+
const prompt = `Analyze this user request and extract structured intent for a coding assistant session.
|
|
65
|
+
|
|
66
|
+
USER REQUEST:
|
|
67
|
+
${firstPrompt.substring(0, 2000)}
|
|
68
|
+
|
|
69
|
+
Extract as JSON:
|
|
70
|
+
{
|
|
71
|
+
"goal": "The main objective in 1-2 sentences",
|
|
72
|
+
"expected_scope": ["list", "of", "files/folders", "likely", "to", "be", "modified"],
|
|
73
|
+
"constraints": ["EXPLICIT restrictions from the user - see examples below"],
|
|
74
|
+
"success_criteria": ["How to know when the task is complete"],
|
|
75
|
+
"keywords": ["relevant", "technical", "terms"]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
═══════════════════════════════════════════════════════════════
|
|
79
|
+
CONSTRAINTS EXTRACTION - BE VERY THOROUGH
|
|
80
|
+
═══════════════════════════════════════════════════════════════
|
|
81
|
+
|
|
82
|
+
Look for NEGATIVE constraints (things NOT to do):
|
|
83
|
+
- "NU modifica" / "DON'T modify" / "NEVER change" / "don't touch"
|
|
84
|
+
- "NU rula" / "DON'T run" / "NO commands" / "don't execute"
|
|
85
|
+
- "fără X" / "without X" / "except X" / "not including"
|
|
86
|
+
- "nu scrie cod" / "don't write code" / "just plan"
|
|
87
|
+
|
|
88
|
+
Look for POSITIVE constraints (things MUST do / ONLY do):
|
|
89
|
+
- "ONLY modify X" / "DOAR în X" / "only in folder Y"
|
|
90
|
+
- "must use Y" / "trebuie să folosești Y"
|
|
91
|
+
- "keep it simple" / "no external dependencies"
|
|
92
|
+
- "use TypeScript" / "must be async"
|
|
93
|
+
|
|
94
|
+
EXAMPLES:
|
|
95
|
+
Input: "Fix bug in auth. NU modifica nimic in afara de sandbox/, NU rula comenzi."
|
|
96
|
+
Output constraints: ["DO NOT modify files outside sandbox/", "DO NOT run commands"]
|
|
97
|
+
|
|
98
|
+
Input: "Add feature X. Only use standard library, keep backward compatible."
|
|
99
|
+
Output constraints: ["ONLY use standard library", "Keep backward compatible"]
|
|
100
|
+
|
|
101
|
+
Input: "Analyze code and create plan. Nu scrie cod inca, doar planifica."
|
|
102
|
+
Output constraints: ["DO NOT write code yet", "Only create plan/analysis"]
|
|
103
|
+
|
|
104
|
+
For expected_scope:
|
|
105
|
+
- Include file patterns (e.g., "src/auth/", "*.test.ts", "sandbox/")
|
|
106
|
+
- Include component/module names mentioned
|
|
107
|
+
- Be conservative - only include clearly relevant areas
|
|
108
|
+
|
|
109
|
+
RESPONSE RULES:
|
|
110
|
+
- English only (translate Romanian/other languages to English)
|
|
111
|
+
- No emojis
|
|
112
|
+
- Valid JSON only
|
|
113
|
+
- If no constraints found, return empty array []`;
|
|
114
|
+
const response = await client.messages.create({
|
|
115
|
+
model: 'claude-haiku-4-5-20251001',
|
|
116
|
+
max_tokens: 500,
|
|
117
|
+
messages: [{ role: 'user', content: prompt }],
|
|
118
|
+
});
|
|
119
|
+
const content = response.content?.[0];
|
|
120
|
+
if (!content || content.type !== 'text') {
|
|
121
|
+
return createFallbackIntent(firstPrompt);
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
const jsonMatch = content.text.match(/\{[\s\S]*\}/);
|
|
125
|
+
if (!jsonMatch) {
|
|
126
|
+
return createFallbackIntent(firstPrompt);
|
|
127
|
+
}
|
|
128
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
129
|
+
return {
|
|
130
|
+
goal: typeof parsed.goal === 'string' ? parsed.goal : firstPrompt.substring(0, 200),
|
|
131
|
+
expected_scope: Array.isArray(parsed.expected_scope)
|
|
132
|
+
? parsed.expected_scope.filter((s) => typeof s === 'string')
|
|
133
|
+
: [],
|
|
134
|
+
constraints: Array.isArray(parsed.constraints)
|
|
135
|
+
? parsed.constraints.filter((c) => typeof c === 'string')
|
|
136
|
+
: [],
|
|
137
|
+
success_criteria: Array.isArray(parsed.success_criteria)
|
|
138
|
+
? parsed.success_criteria.filter((s) => typeof s === 'string')
|
|
139
|
+
: [],
|
|
140
|
+
keywords: Array.isArray(parsed.keywords)
|
|
141
|
+
? parsed.keywords.filter((k) => typeof k === 'string')
|
|
142
|
+
: [],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return createFallbackIntent(firstPrompt);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// Outer catch - API errors, network issues, etc.
|
|
151
|
+
return createFallbackIntent(firstPrompt);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Fallback intent extraction without LLM
|
|
156
|
+
*/
|
|
157
|
+
function createFallbackIntent(prompt) {
|
|
158
|
+
// Basic keyword extraction
|
|
159
|
+
const words = prompt.toLowerCase().split(/\s+/);
|
|
160
|
+
const techKeywords = words.filter(w => w.length > 3 &&
|
|
161
|
+
/^[a-z]+$/.test(w) &&
|
|
162
|
+
!['this', 'that', 'with', 'from', 'have', 'will', 'would', 'could', 'should'].includes(w));
|
|
163
|
+
// Extract file patterns
|
|
164
|
+
const filePatterns = prompt.match(/[\w\/.-]+\.(ts|js|tsx|jsx|py|go|rs|java|css|html|md)/g) || [];
|
|
165
|
+
return {
|
|
166
|
+
goal: prompt.substring(0, 200),
|
|
167
|
+
expected_scope: [...new Set(filePatterns)].slice(0, 5),
|
|
168
|
+
constraints: [],
|
|
169
|
+
success_criteria: [],
|
|
170
|
+
keywords: [...new Set(techKeywords)].slice(0, 10),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Check if intent extraction is available
|
|
175
|
+
*/
|
|
176
|
+
export function isIntentExtractionAvailable() {
|
|
177
|
+
return !!(process.env.ANTHROPIC_API_KEY || process.env.GROV_API_KEY);
|
|
178
|
+
}
|
|
42
179
|
/**
|
|
43
180
|
* Check if Anthropic API is available (for drift detection)
|
|
44
181
|
*/
|
|
@@ -75,22 +212,37 @@ ${sessionSummary}
|
|
|
75
212
|
|
|
76
213
|
Extract the following as JSON:
|
|
77
214
|
{
|
|
78
|
-
"task": "Brief description
|
|
79
|
-
"goal": "The underlying
|
|
80
|
-
"reasoning_trace": [
|
|
81
|
-
|
|
82
|
-
|
|
215
|
+
"task": "Brief description (1 sentence)",
|
|
216
|
+
"goal": "The underlying problem being solved",
|
|
217
|
+
"reasoning_trace": [
|
|
218
|
+
"Be SPECIFIC: include file names, function names, line numbers when relevant",
|
|
219
|
+
"Format: '[Action] [target] to/for [purpose]'",
|
|
220
|
+
"Example: 'Read auth.ts:47 to understand token refresh logic'",
|
|
221
|
+
"Example: 'Fixed null check in validateToken() - was causing silent failures'",
|
|
222
|
+
"NOT: 'Investigated auth' or 'Fixed bug'"
|
|
223
|
+
],
|
|
224
|
+
"decisions": [{"choice": "What was decided", "reason": "Why this over alternatives"}],
|
|
225
|
+
"constraints": ["Discovered limitations, rate limits, incompatibilities"],
|
|
83
226
|
"status": "complete|partial|question|abandoned",
|
|
84
227
|
"tags": ["relevant", "domain", "tags"]
|
|
85
228
|
}
|
|
86
229
|
|
|
230
|
+
IMPORTANT for reasoning_trace:
|
|
231
|
+
- Each entry should be ACTIONABLE information for future developers
|
|
232
|
+
- Include specific file:line references when possible
|
|
233
|
+
- Explain WHY not just WHAT (e.g., "Chose JWT over sessions because stateless scales better")
|
|
234
|
+
- Bad: "Fixed the bug" / Good: "Fixed race condition in UserService.save() - was missing await"
|
|
235
|
+
|
|
87
236
|
Status definitions:
|
|
88
237
|
- "complete": Task was finished, implementation done
|
|
89
238
|
- "partial": Work started but not finished
|
|
90
239
|
- "question": Claude asked a question and is waiting for user response
|
|
91
240
|
- "abandoned": User interrupted or moved to different topic
|
|
92
241
|
|
|
93
|
-
|
|
242
|
+
RESPONSE RULES:
|
|
243
|
+
- English only (translate if input is in other language)
|
|
244
|
+
- No emojis
|
|
245
|
+
- Valid JSON only`
|
|
94
246
|
}
|
|
95
247
|
]
|
|
96
248
|
});
|
|
@@ -290,119 +442,300 @@ function validateStatus(status) {
|
|
|
290
442
|
}
|
|
291
443
|
return 'partial'; // Default
|
|
292
444
|
}
|
|
445
|
+
// ============================================
|
|
446
|
+
// SESSION SUMMARY FOR CLEAR OPERATION
|
|
447
|
+
// Reference: plan_proxy_local.md Section 2.3, 4.5
|
|
448
|
+
// ============================================
|
|
293
449
|
/**
|
|
294
|
-
*
|
|
295
|
-
* Falls back to basic extraction if API unavailable
|
|
450
|
+
* Check if session summary generation is available
|
|
296
451
|
*/
|
|
297
|
-
export
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
452
|
+
export function isSummaryAvailable() {
|
|
453
|
+
return !!(process.env.ANTHROPIC_API_KEY || process.env.GROV_API_KEY);
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Generate session summary for CLEAR operation
|
|
457
|
+
* Reference: plan_proxy_local.md Section 2.3, 4.5
|
|
458
|
+
*/
|
|
459
|
+
export async function generateSessionSummary(sessionState, steps) {
|
|
460
|
+
const client = getAnthropicClient();
|
|
461
|
+
const stepsText = steps
|
|
462
|
+
.filter(s => s.is_validated)
|
|
463
|
+
.slice(-20)
|
|
464
|
+
.map(step => {
|
|
465
|
+
let desc = `- ${step.action_type}`;
|
|
466
|
+
if (step.files.length > 0) {
|
|
467
|
+
desc += `: ${step.files.join(', ')}`;
|
|
302
468
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
return extractIntentBasic(prompt);
|
|
469
|
+
if (step.command) {
|
|
470
|
+
desc += ` (${step.command.substring(0, 50)})`;
|
|
306
471
|
}
|
|
472
|
+
return desc;
|
|
473
|
+
})
|
|
474
|
+
.join('\n');
|
|
475
|
+
const prompt = `Create a concise summary of this coding session for context continuation.
|
|
476
|
+
|
|
477
|
+
ORIGINAL GOAL: ${sessionState.original_goal || 'Not specified'}
|
|
478
|
+
|
|
479
|
+
EXPECTED SCOPE: ${sessionState.expected_scope.join(', ') || 'Not specified'}
|
|
480
|
+
|
|
481
|
+
CONSTRAINTS: ${sessionState.constraints.join(', ') || 'None'}
|
|
482
|
+
|
|
483
|
+
ACTIONS TAKEN:
|
|
484
|
+
${stepsText || 'No actions recorded'}
|
|
485
|
+
|
|
486
|
+
Create a summary with these sections (keep total under 500 words):
|
|
487
|
+
1. ORIGINAL GOAL: (1 sentence)
|
|
488
|
+
2. PROGRESS: (2-3 bullet points of what was accomplished)
|
|
489
|
+
3. KEY DECISIONS: (any important choices made)
|
|
490
|
+
4. FILES MODIFIED: (list of files)
|
|
491
|
+
5. CURRENT STATE: (where the work left off)
|
|
492
|
+
6. NEXT STEPS: (recommended next actions)
|
|
493
|
+
|
|
494
|
+
Format as plain text, not JSON.`;
|
|
495
|
+
const response = await client.messages.create({
|
|
496
|
+
model: 'claude-haiku-4-5-20251001',
|
|
497
|
+
max_tokens: 800,
|
|
498
|
+
messages: [{ role: 'user', content: prompt }],
|
|
499
|
+
});
|
|
500
|
+
const content = response.content?.[0];
|
|
501
|
+
if (!content || content.type !== 'text') {
|
|
502
|
+
return createFallbackSummary(sessionState, steps);
|
|
307
503
|
}
|
|
308
|
-
|
|
309
|
-
|
|
504
|
+
return `PREVIOUS SESSION CONTEXT (auto-generated after context limit):
|
|
505
|
+
|
|
506
|
+
${content.text}`;
|
|
310
507
|
}
|
|
311
508
|
/**
|
|
312
|
-
*
|
|
509
|
+
* Create fallback summary without LLM
|
|
313
510
|
*/
|
|
314
|
-
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
const response = await anthropic.messages.create({
|
|
318
|
-
model,
|
|
319
|
-
max_tokens: 1024,
|
|
320
|
-
messages: [
|
|
321
|
-
{
|
|
322
|
-
role: 'user',
|
|
323
|
-
content: `Analyze this user prompt and extract the task intent. Return ONLY valid JSON, no explanation.
|
|
511
|
+
function createFallbackSummary(sessionState, steps) {
|
|
512
|
+
const files = [...new Set(steps.flatMap(s => s.files))];
|
|
513
|
+
return `PREVIOUS SESSION CONTEXT (auto-generated after context limit):
|
|
324
514
|
|
|
325
|
-
|
|
326
|
-
${prompt}
|
|
515
|
+
ORIGINAL GOAL: ${sessionState.original_goal || 'Not specified'}
|
|
327
516
|
|
|
328
|
-
|
|
517
|
+
PROGRESS: ${steps.length} actions taken
|
|
518
|
+
|
|
519
|
+
FILES MODIFIED:
|
|
520
|
+
${files.slice(0, 10).map(f => `- ${f}`).join('\n') || '- None recorded'}
|
|
521
|
+
|
|
522
|
+
Please continue from where you left off.`;
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Check if task analysis is available
|
|
526
|
+
*/
|
|
527
|
+
export function isTaskAnalysisAvailable() {
|
|
528
|
+
return !!(process.env.ANTHROPIC_API_KEY || process.env.GROV_API_KEY);
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Analyze task context to determine task status
|
|
532
|
+
* Called after each main model response to orchestrate sessions
|
|
533
|
+
* Also compresses reasoning for steps if assistantResponse > 1000 chars
|
|
534
|
+
*/
|
|
535
|
+
export async function analyzeTaskContext(currentSession, latestUserMessage, recentSteps, assistantResponse) {
|
|
536
|
+
const client = getAnthropicClient();
|
|
537
|
+
const stepsText = recentSteps.slice(0, 5).map(s => {
|
|
538
|
+
let desc = `- ${s.action_type}`;
|
|
539
|
+
if (s.files.length > 0) {
|
|
540
|
+
desc += `: ${s.files.slice(0, 3).join(', ')}`;
|
|
541
|
+
}
|
|
542
|
+
return desc;
|
|
543
|
+
}).join('\n') || 'None';
|
|
544
|
+
// Check if we need to compress reasoning
|
|
545
|
+
const needsCompression = assistantResponse.length > 1000;
|
|
546
|
+
const compressionInstruction = needsCompression
|
|
547
|
+
? `\n "step_reasoning": "Extract CONCLUSIONS and SPECIFIC RECOMMENDATIONS only. Include: exact file paths (e.g., src/lib/utils.ts), function/component names, architectural patterns discovered, and WHY decisions were made. DO NOT write process descriptions like 'explored' or 'analyzed'. Max 800 chars."`
|
|
548
|
+
: '';
|
|
549
|
+
const compressionRule = needsCompression
|
|
550
|
+
? '\n- step_reasoning: Extract CONCLUSIONS (specific files, patterns, decisions) NOT process descriptions. Example GOOD: "Utilities belong in src/lib/utils.ts alongside cn(), formatDate()". Example BAD: "Explored codebase structure".'
|
|
551
|
+
: '';
|
|
552
|
+
// Extract topic keywords from goal for comparison
|
|
553
|
+
const currentGoalKeywords = currentSession?.original_goal
|
|
554
|
+
? currentSession.original_goal.toLowerCase().match(/\b\w{4,}\b/g)?.slice(0, 10).join(', ') || ''
|
|
555
|
+
: '';
|
|
556
|
+
const prompt = `You are a task orchestrator. Your PRIMARY job is to detect when the user starts a NEW, DIFFERENT task.
|
|
557
|
+
|
|
558
|
+
CURRENT SESSION:
|
|
559
|
+
- Current Goal: "${currentSession?.original_goal || 'No active task'}"
|
|
560
|
+
- Goal Keywords: [${currentGoalKeywords}]
|
|
561
|
+
|
|
562
|
+
LATEST USER MESSAGE:
|
|
563
|
+
"${latestUserMessage.substring(0, 500)}"
|
|
564
|
+
|
|
565
|
+
RECENT ACTIONS (last 5):
|
|
566
|
+
${stepsText}
|
|
567
|
+
|
|
568
|
+
ASSISTANT RESPONSE (truncated):
|
|
569
|
+
"${assistantResponse.substring(0, 1500)}${assistantResponse.length > 1500 ? '...' : ''}"
|
|
570
|
+
|
|
571
|
+
═══════════════════════════════════════════════════════════════
|
|
572
|
+
CRITICAL: Compare the TOPIC of "Current Goal" vs "Latest User Message"
|
|
573
|
+
═══════════════════════════════════════════════════════════════
|
|
574
|
+
|
|
575
|
+
Ask yourself:
|
|
576
|
+
1. Is the user message about the SAME subject/feature/file as the current goal?
|
|
577
|
+
2. Or is it about something COMPLETELY DIFFERENT?
|
|
578
|
+
|
|
579
|
+
EXAMPLES of NEW_TASK (different topic):
|
|
580
|
+
- Goal: "implement authentication" → User: "fix the database migration" → NEW_TASK
|
|
581
|
+
- Goal: "analyze security layer" → User: "create hello.ts script" → NEW_TASK
|
|
582
|
+
- Goal: "refactor user service" → User: "add dark mode to UI" → NEW_TASK
|
|
583
|
+
- Goal: "fix login bug" → User: "write unit tests for payments" → NEW_TASK
|
|
584
|
+
|
|
585
|
+
EXAMPLES of CONTINUE (same topic):
|
|
586
|
+
- Goal: "implement authentication" → User: "now add the logout button" → CONTINUE
|
|
587
|
+
- Goal: "fix login bug" → User: "also check the session timeout" → CONTINUE
|
|
588
|
+
- Goal: "analyze security" → User: "what about rate limiting?" → CONTINUE
|
|
589
|
+
|
|
590
|
+
Return JSON:
|
|
329
591
|
{
|
|
330
|
-
"
|
|
331
|
-
"
|
|
332
|
-
"
|
|
333
|
-
"
|
|
334
|
-
"
|
|
592
|
+
"action": "continue|new_task|subtask|parallel_task|task_complete|subtask_complete",
|
|
593
|
+
"topic_match": "YES if same topic, NO if different topic",
|
|
594
|
+
"task_id": "existing session_id or 'NEW' for new task",
|
|
595
|
+
"current_goal": "the goal based on LATEST user message",
|
|
596
|
+
"reasoning": "1 sentence explaining topic comparison"${compressionInstruction}
|
|
335
597
|
}
|
|
336
598
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
599
|
+
DECISION RULES:
|
|
600
|
+
1. NO current session → "new_task"
|
|
601
|
+
2. topic_match=NO (different subject) → "new_task"
|
|
602
|
+
3. topic_match=YES + user following up → "continue"
|
|
603
|
+
4. Claude said "done/complete/finished" → "task_complete"
|
|
604
|
+
5. Prerequisite work identified → "subtask"${compressionRule}
|
|
605
|
+
|
|
606
|
+
RESPONSE RULES:
|
|
607
|
+
- English only (translate if input is in other language)
|
|
608
|
+
- No emojis
|
|
609
|
+
- Valid JSON only`;
|
|
610
|
+
debugLLM('analyzeTaskContext', `Calling Haiku for task analysis (needsCompression=${needsCompression})`);
|
|
611
|
+
const response = await client.messages.create({
|
|
612
|
+
model: 'claude-haiku-4-5-20251001',
|
|
613
|
+
max_tokens: needsCompression ? 600 : 300,
|
|
614
|
+
messages: [{ role: 'user', content: prompt }],
|
|
340
615
|
});
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
616
|
+
const text = response.content[0].type === 'text' ? response.content[0].text : '';
|
|
617
|
+
try {
|
|
618
|
+
// Try to parse JSON from response (may have extra text)
|
|
619
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
620
|
+
if (!jsonMatch) {
|
|
621
|
+
throw new Error('No JSON found in response');
|
|
622
|
+
}
|
|
623
|
+
const analysis = JSON.parse(jsonMatch[0]);
|
|
624
|
+
// If we didn't need compression but have short response, use it directly
|
|
625
|
+
if (!needsCompression && assistantResponse.length > 0) {
|
|
626
|
+
analysis.step_reasoning = assistantResponse.substring(0, 1000);
|
|
627
|
+
}
|
|
628
|
+
debugLLM('analyzeTaskContext', `Result: action=${analysis.action}, topic_match=${analysis.topic_match}, goal=${analysis.current_goal.substring(0, 50)}`);
|
|
629
|
+
return analysis;
|
|
630
|
+
}
|
|
631
|
+
catch (parseError) {
|
|
632
|
+
debugLLM('analyzeTaskContext', `Parse error: ${String(parseError)}, using fallback`);
|
|
633
|
+
// Fallback: continue existing session or create new
|
|
634
|
+
return {
|
|
635
|
+
action: currentSession ? 'continue' : 'new_task',
|
|
636
|
+
task_id: currentSession?.session_id || 'NEW',
|
|
637
|
+
current_goal: latestUserMessage.substring(0, 200),
|
|
638
|
+
reasoning: 'Fallback due to parse error',
|
|
639
|
+
step_reasoning: assistantResponse.substring(0, 1000),
|
|
640
|
+
};
|
|
345
641
|
}
|
|
346
|
-
const parsed = JSON.parse(content.text);
|
|
347
|
-
return {
|
|
348
|
-
goal: parsed.goal || prompt.substring(0, 100),
|
|
349
|
-
expected_scope: parsed.expected_scope || [],
|
|
350
|
-
constraints: parsed.constraints || [],
|
|
351
|
-
success_criteria: parsed.success_criteria || [],
|
|
352
|
-
keywords: parsed.keywords || extractKeywordsBasic(prompt)
|
|
353
|
-
};
|
|
354
642
|
}
|
|
355
643
|
/**
|
|
356
|
-
*
|
|
644
|
+
* Check if reasoning extraction is available
|
|
357
645
|
*/
|
|
358
|
-
function
|
|
359
|
-
return
|
|
360
|
-
goal: prompt.substring(0, 200),
|
|
361
|
-
expected_scope: extractFilesFromPrompt(prompt),
|
|
362
|
-
constraints: [],
|
|
363
|
-
success_criteria: [],
|
|
364
|
-
keywords: extractKeywordsBasic(prompt)
|
|
365
|
-
};
|
|
646
|
+
export function isReasoningExtractionAvailable() {
|
|
647
|
+
return !!process.env.ANTHROPIC_API_KEY || !!process.env.GROV_API_KEY;
|
|
366
648
|
}
|
|
367
649
|
/**
|
|
368
|
-
* Extract
|
|
650
|
+
* Extract reasoning trace and decisions from steps
|
|
651
|
+
* Called at task_complete to populate team memory with rich context
|
|
369
652
|
*/
|
|
370
|
-
function
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
const matches = prompt.matchAll(pattern);
|
|
380
|
-
for (const match of matches) {
|
|
381
|
-
const file = match[1].trim();
|
|
382
|
-
if (file && !file.match(/^(http|https|ftp|mailto)/) && !file.match(/^\d+\.\d+/)) {
|
|
383
|
-
files.add(file);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
653
|
+
export async function extractReasoningAndDecisions(stepsReasoning, originalGoal) {
|
|
654
|
+
const client = getAnthropicClient();
|
|
655
|
+
// Combine all steps reasoning into one text
|
|
656
|
+
const combinedReasoning = stepsReasoning
|
|
657
|
+
.filter(r => r && r.length > 10)
|
|
658
|
+
.join('\n\n---\n\n')
|
|
659
|
+
.substring(0, 8000);
|
|
660
|
+
if (combinedReasoning.length < 50) {
|
|
661
|
+
return { reasoning_trace: [], decisions: [] };
|
|
386
662
|
}
|
|
387
|
-
|
|
663
|
+
const prompt = `Extract CONCLUSIONS and KNOWLEDGE from Claude's work - NOT process descriptions.
|
|
664
|
+
|
|
665
|
+
ORIGINAL GOAL:
|
|
666
|
+
${originalGoal || 'Not specified'}
|
|
667
|
+
|
|
668
|
+
CLAUDE'S RESPONSE:
|
|
669
|
+
${combinedReasoning}
|
|
670
|
+
|
|
671
|
+
═══════════════════════════════════════════════════════════════
|
|
672
|
+
EXTRACT ACTIONABLE CONCLUSIONS - NOT PROCESS
|
|
673
|
+
═══════════════════════════════════════════════════════════════
|
|
674
|
+
|
|
675
|
+
GOOD examples (specific, reusable knowledge):
|
|
676
|
+
- "Utility functions belong in frontend/lib/utils.ts - existing utils: cn(), formatDate(), debounce()"
|
|
677
|
+
- "Auth tokens stored in localStorage with 15min expiry for long form sessions"
|
|
678
|
+
- "API routes follow REST pattern in /api/v1/ with Zod validation"
|
|
679
|
+
- "Database migrations go in prisma/migrations/ using prisma migrate"
|
|
680
|
+
|
|
681
|
+
BAD examples (process descriptions - DO NOT EXTRACT THESE):
|
|
682
|
+
- "Explored the codebase structure"
|
|
683
|
+
- "Analyzed several approaches"
|
|
684
|
+
- "Searched for utility directories"
|
|
685
|
+
- "Looked at the file organization"
|
|
686
|
+
|
|
687
|
+
1. REASONING TRACE (conclusions and recommendations):
|
|
688
|
+
- WHAT was discovered or decided (specific file paths, patterns)
|
|
689
|
+
- WHY this is the right approach
|
|
690
|
+
- WHERE this applies in the codebase
|
|
691
|
+
- Max 10 entries, prioritize specific file/function recommendations
|
|
692
|
+
|
|
693
|
+
2. DECISIONS (architectural choices):
|
|
694
|
+
- Only significant choices that affect future work
|
|
695
|
+
- What was chosen and why
|
|
696
|
+
- Max 5 decisions
|
|
697
|
+
|
|
698
|
+
Return JSON:
|
|
699
|
+
{
|
|
700
|
+
"reasoning_trace": [
|
|
701
|
+
"Utility functions belong in frontend/lib/utils.ts alongside cn(), formatDate(), debounce(), generateId()",
|
|
702
|
+
"Backend utilities go in backend/app/utils/ with domain-specific files like validation.py",
|
|
703
|
+
"The @/lib/utils import alias is configured for frontend utility access"
|
|
704
|
+
],
|
|
705
|
+
"decisions": [
|
|
706
|
+
{"choice": "Add to existing utils.ts rather than new file", "reason": "Maintains established pattern, easier discoverability"},
|
|
707
|
+
{"choice": "Use frontend/lib/ over src/utils/", "reason": "Follows Next.js conventions used throughout project"}
|
|
708
|
+
]
|
|
388
709
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
.
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
710
|
+
|
|
711
|
+
RESPONSE RULES:
|
|
712
|
+
- English only
|
|
713
|
+
- No emojis
|
|
714
|
+
- Valid JSON only
|
|
715
|
+
- Extract WHAT and WHERE, not just WHAT was done
|
|
716
|
+
- If no specific conclusions found, return empty arrays`;
|
|
717
|
+
debugLLM('extractReasoningAndDecisions', `Analyzing ${stepsReasoning.length} steps, ${combinedReasoning.length} chars`);
|
|
718
|
+
try {
|
|
719
|
+
const response = await client.messages.create({
|
|
720
|
+
model: 'claude-haiku-4-5-20251001',
|
|
721
|
+
max_tokens: 800,
|
|
722
|
+
messages: [{ role: 'user', content: prompt }],
|
|
723
|
+
});
|
|
724
|
+
const text = response.content[0].type === 'text' ? response.content[0].text : '';
|
|
725
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
726
|
+
if (!jsonMatch) {
|
|
727
|
+
debugLLM('extractReasoningAndDecisions', 'No JSON found in response');
|
|
728
|
+
return { reasoning_trace: [], decisions: [] };
|
|
729
|
+
}
|
|
730
|
+
const result = JSON.parse(jsonMatch[0]);
|
|
731
|
+
debugLLM('extractReasoningAndDecisions', `Extracted ${result.reasoning_trace?.length || 0} traces, ${result.decisions?.length || 0} decisions`);
|
|
732
|
+
return {
|
|
733
|
+
reasoning_trace: result.reasoning_trace || [],
|
|
734
|
+
decisions: result.decisions || [],
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
catch (error) {
|
|
738
|
+
debugLLM('extractReasoningAndDecisions', `Error: ${String(error)}`);
|
|
739
|
+
return { reasoning_trace: [], decisions: [] };
|
|
740
|
+
}
|
|
408
741
|
}
|
|
@@ -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 {};
|