@itz4blitz/agentful 0.2.1 → 0.3.0
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/.claude/agents/orchestrator.md +121 -610
- package/.claude/commands/agentful-generate.md +206 -0
- package/.claude/skills/conversation/SKILL.md +152 -975
- package/bin/cli.js +108 -583
- package/bin/hooks/health-check.sh +16 -16
- package/lib/index.js +6 -36
- package/lib/init.js +162 -0
- package/package.json +1 -2
- package/version.json +1 -1
- package/.claude/commands/agentful-agents.md +0 -668
- package/.claude/commands/agentful-skills.md +0 -635
- package/.claude/product/CHANGES.md +0 -276
- package/lib/agent-generator.js +0 -778
- package/lib/domain-detector.js +0 -468
- package/lib/domain-structure-generator.js +0 -770
- package/lib/project-analyzer.js +0 -701
- package/lib/tech-stack-detector.js +0 -1091
- package/lib/template-engine.js +0 -240
- package/templates/agents/domain-agent.template.md +0 -208
- package/templates/agents/tech-agent.template.md +0 -124
|
@@ -7,60 +7,26 @@ tools: Read, Write, Edit, Glob, Grep
|
|
|
7
7
|
|
|
8
8
|
# Conversation Skill
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
## When to Use
|
|
13
|
-
|
|
14
|
-
This skill is invoked when:
|
|
15
|
-
- User runs `/agentful <text>` with natural language input
|
|
16
|
-
- Any command receives ambiguous or unclear input
|
|
17
|
-
- Need to resolve pronouns or references from conversation history
|
|
18
|
-
- Need to classify user intent before routing to handlers
|
|
19
|
-
|
|
20
|
-
**Entry Point**: The `/agentful` command delegates to this skill for all natural language processing.
|
|
10
|
+
Provides natural language processing for understanding user intent, managing conversation context, resolving references, and maintaining conversation history.
|
|
21
11
|
|
|
22
12
|
## Responsibilities
|
|
23
13
|
|
|
24
|
-
1. **Intent Classification** - Determine what the user wants
|
|
14
|
+
1. **Intent Classification** - Determine what the user wants
|
|
25
15
|
2. **Reference Resolution** - Resolve "it", "that", "this" to actual feature names
|
|
26
16
|
3. **Entity Extraction** - Extract features, domains, subtasks mentioned
|
|
27
17
|
4. **Ambiguity Detection** - Identify unclear requests and ask clarifying questions
|
|
28
18
|
5. **Context Management** - Track conversation state, detect context loss
|
|
29
|
-
6. **Routing** - Route to appropriate handler
|
|
19
|
+
6. **Routing** - Route to appropriate handler
|
|
30
20
|
7. **History Tracking** - Maintain conversation history for context
|
|
31
21
|
|
|
32
|
-
##
|
|
33
|
-
|
|
34
|
-
### 1. Intent Classification
|
|
22
|
+
## Intent Classification
|
|
35
23
|
|
|
36
24
|
```typescript
|
|
37
|
-
/**
|
|
38
|
-
* Classify user intent with confidence score
|
|
39
|
-
* @param message - User's current message
|
|
40
|
-
* @param conversation_history - Recent conversation context
|
|
41
|
-
* @param product_spec - Product features and requirements
|
|
42
|
-
* @returns Intent classification with confidence
|
|
43
|
-
*/
|
|
44
25
|
function classify_intent(
|
|
45
26
|
message: string,
|
|
46
27
|
conversation_history: ConversationMessage[],
|
|
47
28
|
product_spec: ProductSpec
|
|
48
29
|
): IntentClassification {
|
|
49
|
-
const intents = [
|
|
50
|
-
'feature_request',
|
|
51
|
-
'bug_report',
|
|
52
|
-
'question',
|
|
53
|
-
'clarification',
|
|
54
|
-
'status_update',
|
|
55
|
-
'mind_change',
|
|
56
|
-
'context_switch',
|
|
57
|
-
'approval',
|
|
58
|
-
'rejection',
|
|
59
|
-
'pause',
|
|
60
|
-
'continue'
|
|
61
|
-
];
|
|
62
|
-
|
|
63
|
-
// Analyze message patterns
|
|
64
30
|
const patterns = {
|
|
65
31
|
feature_request: /(?:add|create|implement|build|new|feature|support|enable)/i,
|
|
66
32
|
bug_report: /(?:bug|broken|error|issue|problem|wrong|doesn't work|fix)/i,
|
|
@@ -75,31 +41,13 @@ function classify_intent(
|
|
|
75
41
|
continue: /(?:continue|resume|let's continue|back)/i
|
|
76
42
|
};
|
|
77
43
|
|
|
78
|
-
// Score each intent
|
|
79
44
|
const scores = {};
|
|
80
45
|
for (const [intent, pattern] of Object.entries(patterns)) {
|
|
81
46
|
const matches = message.match(pattern);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
// Context-aware scoring
|
|
85
|
-
let contextBonus = 0;
|
|
86
|
-
if (conversation_history.length > 0) {
|
|
87
|
-
const lastMessage = conversation_history[conversation_history.length - 1];
|
|
88
|
-
if (lastMessage.role === 'assistant' && intent === 'clarification') {
|
|
89
|
-
contextBonus += 0.2; // User asking for clarification after assistant response
|
|
90
|
-
}
|
|
91
|
-
if (lastMessage.intent === 'question' && intent === 'clarification') {
|
|
92
|
-
contextBonus += 0.15; // Follow-up clarification
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
scores[intent] = Math.min(0.95, baseScore + contextBonus);
|
|
47
|
+
scores[intent] = matches ? matches.length * 0.3 : 0;
|
|
97
48
|
}
|
|
98
49
|
|
|
99
|
-
|
|
100
|
-
const topIntent = Object.entries(scores).reduce((a, b) =>
|
|
101
|
-
a[1] > b[1] ? a : b
|
|
102
|
-
);
|
|
50
|
+
const topIntent = Object.entries(scores).reduce((a, b) => a[1] > b[1] ? a : b);
|
|
103
51
|
|
|
104
52
|
return {
|
|
105
53
|
intent: topIntent[0],
|
|
@@ -111,23 +59,11 @@ function classify_intent(
|
|
|
111
59
|
.map(([intent, score]) => ({ intent, confidence: score }))
|
|
112
60
|
};
|
|
113
61
|
}
|
|
114
|
-
|
|
115
|
-
interface IntentClassification {
|
|
116
|
-
intent: string;
|
|
117
|
-
confidence: number;
|
|
118
|
-
alternative_intents: Array<{ intent: string; confidence: number }>;
|
|
119
|
-
}
|
|
120
62
|
```
|
|
121
63
|
|
|
122
|
-
|
|
64
|
+
## Entity Extraction
|
|
123
65
|
|
|
124
66
|
```typescript
|
|
125
|
-
/**
|
|
126
|
-
* Extract which feature/user is talking about
|
|
127
|
-
* @param message - User's current message
|
|
128
|
-
* @param product_spec - Product features and requirements
|
|
129
|
-
* @returns Extracted feature reference
|
|
130
|
-
*/
|
|
131
67
|
function extract_feature_mention(
|
|
132
68
|
message: string,
|
|
133
69
|
product_spec: ProductSpec
|
|
@@ -149,10 +85,9 @@ function extract_feature_mention(
|
|
|
149
85
|
}
|
|
150
86
|
}
|
|
151
87
|
|
|
152
|
-
//
|
|
88
|
+
// Domain and nested feature matches
|
|
153
89
|
if (product_spec.domains) {
|
|
154
90
|
for (const [domainId, domain] of Object.entries(product_spec.domains)) {
|
|
155
|
-
// Check domain mention
|
|
156
91
|
if (message.toLowerCase().includes(domain.name.toLowerCase())) {
|
|
157
92
|
mentioned.push({
|
|
158
93
|
type: 'domain',
|
|
@@ -162,7 +97,6 @@ function extract_feature_mention(
|
|
|
162
97
|
});
|
|
163
98
|
}
|
|
164
99
|
|
|
165
|
-
// Check feature mentions within domain
|
|
166
100
|
if (domain.features) {
|
|
167
101
|
for (const [featureId, feature] of Object.entries(domain.features)) {
|
|
168
102
|
const featureName = feature.name || featureId;
|
|
@@ -180,9 +114,8 @@ function extract_feature_mention(
|
|
|
180
114
|
}
|
|
181
115
|
}
|
|
182
116
|
|
|
183
|
-
// Subtask
|
|
184
|
-
const
|
|
185
|
-
const subtaskMatch = message.match(subtaskPattern);
|
|
117
|
+
// Subtask references
|
|
118
|
+
const subtaskMatch = message.match(/(?:subtask|task|item)\s+(\d+)/i);
|
|
186
119
|
if (subtaskMatch) {
|
|
187
120
|
mentioned.push({
|
|
188
121
|
type: 'subtask_reference',
|
|
@@ -191,137 +124,32 @@ function extract_feature_mention(
|
|
|
191
124
|
});
|
|
192
125
|
}
|
|
193
126
|
|
|
194
|
-
|
|
195
|
-
if (mentioned.length === 0) {
|
|
196
|
-
return { type: 'none', confidence: 0 };
|
|
197
|
-
}
|
|
198
|
-
|
|
127
|
+
if (mentioned.length === 0) return { type: 'none', confidence: 0 };
|
|
199
128
|
return mentioned.sort((a, b) => b.confidence - a.confidence)[0];
|
|
200
129
|
}
|
|
201
|
-
|
|
202
|
-
interface FeatureMention {
|
|
203
|
-
type: 'direct' | 'domain' | 'feature' | 'subtask_reference' | 'none';
|
|
204
|
-
domain_id?: string;
|
|
205
|
-
domain_name?: string;
|
|
206
|
-
feature_id?: string;
|
|
207
|
-
feature_name?: string;
|
|
208
|
-
reference?: string;
|
|
209
|
-
confidence: number;
|
|
210
|
-
}
|
|
211
130
|
```
|
|
212
131
|
|
|
213
|
-
|
|
132
|
+
## Ambiguity Detection
|
|
214
133
|
|
|
215
134
|
```typescript
|
|
216
|
-
/**
|
|
217
|
-
* Extract bug details from conversation context
|
|
218
|
-
* @param message - User's current message
|
|
219
|
-
* @param conversation_history - Recent conversation context
|
|
220
|
-
* @returns Bug description with context
|
|
221
|
-
*/
|
|
222
|
-
function extract_bug_description(
|
|
223
|
-
message: string,
|
|
224
|
-
conversation_history: ConversationMessage[]
|
|
225
|
-
): BugDescription {
|
|
226
|
-
const bugInfo = {
|
|
227
|
-
description: message,
|
|
228
|
-
steps_to_reproduce: [],
|
|
229
|
-
expected_behavior: null,
|
|
230
|
-
actual_behavior: null,
|
|
231
|
-
related_feature: null,
|
|
232
|
-
severity: 'unknown',
|
|
233
|
-
context_messages: []
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
// Look for "expected" vs "actual" patterns
|
|
237
|
-
const expectedPattern = /(?:expected|should|supposed to):\s*(.+?)(?:\.|$)/i;
|
|
238
|
-
const actualPattern = /(?:actually|but it|instead):\s*(.+?)(?:\.|$)/i;
|
|
239
|
-
|
|
240
|
-
const expectedMatch = message.match(expectedPattern);
|
|
241
|
-
const actualMatch = message.match(actualPattern);
|
|
242
|
-
|
|
243
|
-
if (expectedMatch) {
|
|
244
|
-
bugInfo.expected_behavior = expectedMatch[1].trim();
|
|
245
|
-
}
|
|
246
|
-
if (actualMatch) {
|
|
247
|
-
bugInfo.actual_behavior = actualMatch[1].trim();
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Look for numbered steps
|
|
251
|
-
const stepPattern = /^\d+\.\s*(.+)$/gm;
|
|
252
|
-
const steps = message.match(stepPattern);
|
|
253
|
-
if (steps) {
|
|
254
|
-
bugInfo.steps_to_reproduce = steps.map(step => step.replace(/^\d+\.\s*/, ''));
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Infer severity from keywords
|
|
258
|
-
const severeKeywords = ['crash', 'broken', 'fail', 'critical', 'blocking'];
|
|
259
|
-
const minorKeywords = ['typo', 'cosmetic', 'minor', 'polish'];
|
|
260
|
-
|
|
261
|
-
if (severeKeywords.some(kw => message.toLowerCase().includes(kw))) {
|
|
262
|
-
bugInfo.severity = 'high';
|
|
263
|
-
} else if (minorKeywords.some(kw => message.toLowerCase().includes(kw))) {
|
|
264
|
-
bugInfo.severity = 'low';
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Gather relevant context from conversation history
|
|
268
|
-
const relevantContext = conversation_history
|
|
269
|
-
.filter(msg => {
|
|
270
|
-
const msgTime = new Date(msg.timestamp);
|
|
271
|
-
const now = new Date();
|
|
272
|
-
const hoursDiff = (now.getTime() - msgTime.getTime()) / (1000 * 60 * 60);
|
|
273
|
-
return hoursDiff < 2; // Last 2 hours
|
|
274
|
-
})
|
|
275
|
-
.slice(-5); // Last 5 messages
|
|
276
|
-
|
|
277
|
-
bugInfo.context_messages = relevantContext;
|
|
278
|
-
|
|
279
|
-
return bugInfo;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
interface BugDescription {
|
|
283
|
-
description: string;
|
|
284
|
-
steps_to_reproduce: string[];
|
|
285
|
-
expected_behavior: string | null;
|
|
286
|
-
actual_behavior: string | null;
|
|
287
|
-
related_feature: string | null;
|
|
288
|
-
severity: 'high' | 'medium' | 'low' | 'unknown';
|
|
289
|
-
context_messages: ConversationMessage[];
|
|
290
|
-
}
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
### 4. Ambiguity Detection
|
|
294
|
-
|
|
295
|
-
```typescript
|
|
296
|
-
/**
|
|
297
|
-
* Detect unclear or ambiguous requests
|
|
298
|
-
* @param message - User's current message
|
|
299
|
-
* @returns Ambiguity analysis
|
|
300
|
-
*/
|
|
301
135
|
function detect_ambiguity(message: string): AmbiguityAnalysis {
|
|
302
136
|
const ambiguities = [];
|
|
303
137
|
let confidence = 0;
|
|
304
138
|
|
|
305
|
-
//
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
message: 'Pronoun at start of message without clear referent'
|
|
316
|
-
});
|
|
317
|
-
confidence = Math.max(confidence, 0.8);
|
|
318
|
-
}
|
|
139
|
+
// Pronouns without context
|
|
140
|
+
const pronouns = message.match(/\b(it|that|this|they|them|those)\b/gi);
|
|
141
|
+
if (pronouns && /^(it|that|this|they|them)/i.test(message.trim())) {
|
|
142
|
+
ambiguities.push({
|
|
143
|
+
type: 'pronoun_without_antecedent',
|
|
144
|
+
severity: 'high',
|
|
145
|
+
text: pronouns[0],
|
|
146
|
+
message: 'Pronoun at start of message without clear referent'
|
|
147
|
+
});
|
|
148
|
+
confidence = 0.8;
|
|
319
149
|
}
|
|
320
150
|
|
|
321
|
-
//
|
|
322
|
-
const
|
|
323
|
-
const vagueVerbPattern = new RegExp(`\\b(${vagueVerbs.join('|')})\\b\\s+(?:it|that|this)`, 'i');
|
|
324
|
-
const vagueMatch = message.match(vagueVerbPattern);
|
|
151
|
+
// Vague actions
|
|
152
|
+
const vagueMatch = message.match(/\b(fix|update|change|improve|handle)\b\s+(?:it|that|this)/i);
|
|
325
153
|
if (vagueMatch) {
|
|
326
154
|
ambiguities.push({
|
|
327
155
|
type: 'vague_action',
|
|
@@ -332,19 +160,7 @@ function detect_ambiguity(message: string): AmbiguityAnalysis {
|
|
|
332
160
|
confidence = Math.max(confidence, 0.6);
|
|
333
161
|
}
|
|
334
162
|
|
|
335
|
-
//
|
|
336
|
-
const wordCount = message.split(/\s+/).length;
|
|
337
|
-
if (wordCount < 10 && wordCount > 1) {
|
|
338
|
-
ambiguities.push({
|
|
339
|
-
type: 'insufficient_detail',
|
|
340
|
-
severity: 'low',
|
|
341
|
-
text: message,
|
|
342
|
-
message: 'Message is very short, may lack detail'
|
|
343
|
-
});
|
|
344
|
-
confidence = Math.max(confidence, 0.4);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// Check for multiple possible intents
|
|
163
|
+
// Multiple actions
|
|
348
164
|
const actionWords = message.split(/\s+/).filter(word =>
|
|
349
165
|
/^(add|create|fix|update|delete|remove|test|check|verify|deploy|build|run)/i.test(word)
|
|
350
166
|
);
|
|
@@ -362,180 +178,14 @@ function detect_ambiguity(message: string): AmbiguityAnalysis {
|
|
|
362
178
|
is_ambiguous: ambiguities.length > 0,
|
|
363
179
|
confidence,
|
|
364
180
|
ambiguities,
|
|
365
|
-
suggestion: ambiguities.length > 0 ?
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
interface AmbiguityAnalysis {
|
|
370
|
-
is_ambiguous: boolean;
|
|
371
|
-
confidence: number;
|
|
372
|
-
ambiguities: Array<{
|
|
373
|
-
type: string;
|
|
374
|
-
severity: 'high' | 'medium' | 'low';
|
|
375
|
-
text: string;
|
|
376
|
-
message: string;
|
|
377
|
-
}>;
|
|
378
|
-
suggestion: string | null;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function generate_clarification_suggestion(ambiguities: any[]): string {
|
|
382
|
-
const highSeverity = ambiguities.find(a => a.severity === 'high');
|
|
383
|
-
if (highSeverity) {
|
|
384
|
-
return 'Could you please provide more specific details? What specifically are you referring to?';
|
|
385
|
-
}
|
|
386
|
-
return 'I want to make sure I understand correctly. Could you provide a bit more detail?';
|
|
387
|
-
}
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
### 5. Clarification Suggestions
|
|
391
|
-
|
|
392
|
-
```typescript
|
|
393
|
-
/**
|
|
394
|
-
* Generate helpful clarifying questions
|
|
395
|
-
* @param ambiguous_message - The ambiguous user message
|
|
396
|
-
* @param product_spec - Product features and requirements
|
|
397
|
-
* @returns Suggested clarifying questions
|
|
398
|
-
*/
|
|
399
|
-
function suggest_clarification(
|
|
400
|
-
ambiguous_message: string,
|
|
401
|
-
product_spec: ProductSpec
|
|
402
|
-
): ClarificationSuggestion {
|
|
403
|
-
const questions = [];
|
|
404
|
-
|
|
405
|
-
// Feature-specific clarification
|
|
406
|
-
const featureMention = extract_feature_mention(ambiguous_message, product_spec);
|
|
407
|
-
if (featureMention.type === 'none') {
|
|
408
|
-
// No feature mentioned, ask which one
|
|
409
|
-
if (product_spec.features) {
|
|
410
|
-
const featureNames = Object.values(product_spec.features)
|
|
411
|
-
.map(f => f.name || f.id)
|
|
412
|
-
.slice(0, 5);
|
|
413
|
-
questions.push({
|
|
414
|
-
type: 'feature_selection',
|
|
415
|
-
question: `Which feature are you referring to? For example: ${featureNames.join(', ')}`,
|
|
416
|
-
options: featureNames
|
|
417
|
-
});
|
|
418
|
-
} else if (product_spec.domains) {
|
|
419
|
-
const domainNames = Object.values(product_spec.domains)
|
|
420
|
-
.map(d => d.name)
|
|
421
|
-
.slice(0, 5);
|
|
422
|
-
questions.push({
|
|
423
|
-
type: 'domain_selection',
|
|
424
|
-
question: `Which domain would you like to work on? For example: ${domainNames.join(', ')}`,
|
|
425
|
-
options: domainNames
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
// Action-specific clarification
|
|
431
|
-
const actions = ambiguous_message.match(/(?:add|create|fix|update|delete|remove|test|check)/gi);
|
|
432
|
-
if (actions && actions.length > 1) {
|
|
433
|
-
questions.push({
|
|
434
|
-
type: 'action_priority',
|
|
435
|
-
question: `I see multiple actions: ${actions.join(', ')}. Which would you like me to focus on first?`,
|
|
436
|
-
options: actions
|
|
437
|
-
});
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// Bug report clarification
|
|
441
|
-
if (/bug|broken|error|issue/i.test(ambiguous_message)) {
|
|
442
|
-
questions.push({
|
|
443
|
-
type: 'bug_details',
|
|
444
|
-
question: 'Could you provide more details about the bug? What should happen vs. what actually happens?',
|
|
445
|
-
options: null
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// Generic clarification if no specific ones
|
|
450
|
-
if (questions.length === 0) {
|
|
451
|
-
questions.push({
|
|
452
|
-
type: 'generic',
|
|
453
|
-
question: 'Could you please provide more details about what you\'d like me to do?',
|
|
454
|
-
options: null
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
return {
|
|
459
|
-
primary_question: questions[0].question,
|
|
460
|
-
follow_up_questions: questions.slice(1),
|
|
461
|
-
suggested_responses: questions[0].options || []
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
interface ClarificationSuggestion {
|
|
466
|
-
primary_question: string;
|
|
467
|
-
follow_up_questions: Array<{ type: string; question: string; options: string[] | null }>;
|
|
468
|
-
suggested_responses: string[];
|
|
469
|
-
}
|
|
470
|
-
```
|
|
471
|
-
|
|
472
|
-
## Conversation State Management
|
|
473
|
-
|
|
474
|
-
### State Structure
|
|
475
|
-
|
|
476
|
-
```typescript
|
|
477
|
-
/**
|
|
478
|
-
* Conversation state schema
|
|
479
|
-
* Stored in: .agentful/conversation-state.json
|
|
480
|
-
*/
|
|
481
|
-
interface ConversationState {
|
|
482
|
-
// Current context
|
|
483
|
-
current_feature: {
|
|
484
|
-
domain_id?: string;
|
|
485
|
-
feature_id?: string;
|
|
486
|
-
feature_name?: string;
|
|
487
|
-
subtask_id?: string;
|
|
488
|
-
} | null;
|
|
489
|
-
|
|
490
|
-
current_phase: 'idle' | 'planning' | 'implementing' | 'testing' | 'reviewing' | 'deploying';
|
|
491
|
-
|
|
492
|
-
last_action: {
|
|
493
|
-
type: string;
|
|
494
|
-
description: string;
|
|
495
|
-
timestamp: string;
|
|
496
|
-
result?: any;
|
|
497
|
-
} | null;
|
|
498
|
-
|
|
499
|
-
// Related features context
|
|
500
|
-
related_features: Array<{
|
|
501
|
-
feature_id: string;
|
|
502
|
-
feature_name: string;
|
|
503
|
-
relationship: 'dependency' | 'similar' | 'related';
|
|
504
|
-
}>;
|
|
505
|
-
|
|
506
|
-
// User preferences
|
|
507
|
-
user_preferences: {
|
|
508
|
-
communication_style: 'concise' | 'detailed' | 'balanced';
|
|
509
|
-
update_frequency: 'immediate' | 'summary' | 'on_completion';
|
|
510
|
-
ask_before_deleting: boolean;
|
|
511
|
-
test_automatically: boolean;
|
|
512
|
-
};
|
|
513
|
-
|
|
514
|
-
// Session tracking
|
|
515
|
-
session_start: string;
|
|
516
|
-
last_message_time: string;
|
|
517
|
-
message_count: number;
|
|
518
|
-
|
|
519
|
-
// Context health
|
|
520
|
-
context_health: {
|
|
521
|
-
is_stale: boolean;
|
|
522
|
-
last_confirmed_intent: string;
|
|
523
|
-
ambiguity_count: number;
|
|
524
|
-
clarification_count: number;
|
|
181
|
+
suggestion: ambiguities.length > 0 ? 'Could you please provide more specific details?' : null
|
|
525
182
|
};
|
|
526
183
|
}
|
|
527
184
|
```
|
|
528
185
|
|
|
529
|
-
|
|
186
|
+
## Reference Resolution
|
|
530
187
|
|
|
531
188
|
```typescript
|
|
532
|
-
/**
|
|
533
|
-
* Resolve pronouns and references to previous messages
|
|
534
|
-
* @param message - Current message with potential references
|
|
535
|
-
* @param conversation_history - Message history
|
|
536
|
-
* @param state - Current conversation state
|
|
537
|
-
* @returns Resolved message with references expanded
|
|
538
|
-
*/
|
|
539
189
|
function resolve_references(
|
|
540
190
|
message: string,
|
|
541
191
|
conversation_history: ConversationMessage[],
|
|
@@ -544,7 +194,7 @@ function resolve_references(
|
|
|
544
194
|
let resolved = message;
|
|
545
195
|
const references = [];
|
|
546
196
|
|
|
547
|
-
// Replace
|
|
197
|
+
// Replace pronouns with actual referents
|
|
548
198
|
const pronounMap = {
|
|
549
199
|
'it': state.current_feature?.feature_name,
|
|
550
200
|
'that': state.last_action?.description,
|
|
@@ -556,16 +206,12 @@ function resolve_references(
|
|
|
556
206
|
const pattern = new RegExp(`\\b${pronoun}\\b`, 'gi');
|
|
557
207
|
if (pattern.test(message)) {
|
|
558
208
|
resolved = resolved.replace(pattern, referent);
|
|
559
|
-
references.push({
|
|
560
|
-
original: pronoun,
|
|
561
|
-
resolved: referent,
|
|
562
|
-
type: 'pronoun'
|
|
563
|
-
});
|
|
209
|
+
references.push({ original: pronoun, resolved: referent, type: 'pronoun' });
|
|
564
210
|
}
|
|
565
211
|
}
|
|
566
212
|
}
|
|
567
213
|
|
|
568
|
-
//
|
|
214
|
+
// Replace definite references
|
|
569
215
|
const definiteReferences = {
|
|
570
216
|
'the feature': state.current_feature?.feature_name,
|
|
571
217
|
'the bug': state.last_action?.type === 'bug_fix' ? state.last_action.description : null,
|
|
@@ -575,11 +221,7 @@ function resolve_references(
|
|
|
575
221
|
for (const [phrase, referent] of Object.entries(definiteReferences)) {
|
|
576
222
|
if (referent) {
|
|
577
223
|
resolved = resolved.replace(new RegExp(phrase, 'gi'), referent);
|
|
578
|
-
references.push({
|
|
579
|
-
original: phrase,
|
|
580
|
-
resolved: referent,
|
|
581
|
-
type: 'definite_reference'
|
|
582
|
-
});
|
|
224
|
+
references.push({ original: phrase, resolved: referent, type: 'definite_reference' });
|
|
583
225
|
}
|
|
584
226
|
}
|
|
585
227
|
|
|
@@ -590,28 +232,35 @@ function resolve_references(
|
|
|
590
232
|
confidence: references.length > 0 ? 0.85 : 1.0
|
|
591
233
|
};
|
|
592
234
|
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Context Management
|
|
593
238
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
239
|
+
```typescript
|
|
240
|
+
interface ConversationState {
|
|
241
|
+
current_feature: {
|
|
242
|
+
domain_id?: string;
|
|
243
|
+
feature_id?: string;
|
|
244
|
+
feature_name?: string;
|
|
245
|
+
subtask_id?: string;
|
|
246
|
+
} | null;
|
|
247
|
+
current_phase: 'idle' | 'planning' | 'implementing' | 'testing' | 'reviewing' | 'deploying';
|
|
248
|
+
last_action: {
|
|
600
249
|
type: string;
|
|
250
|
+
description: string;
|
|
251
|
+
timestamp: string;
|
|
252
|
+
result?: any;
|
|
253
|
+
} | null;
|
|
254
|
+
related_features: Array<{
|
|
255
|
+
feature_id: string;
|
|
256
|
+
feature_name: string;
|
|
257
|
+
relationship: 'dependency' | 'similar' | 'related';
|
|
601
258
|
}>;
|
|
602
|
-
|
|
259
|
+
session_start: string;
|
|
260
|
+
last_message_time: string;
|
|
261
|
+
message_count: number;
|
|
603
262
|
}
|
|
604
|
-
```
|
|
605
|
-
|
|
606
|
-
### Context Loss Recovery
|
|
607
263
|
|
|
608
|
-
```typescript
|
|
609
|
-
/**
|
|
610
|
-
* Detect and handle context loss (>24h gaps)
|
|
611
|
-
* @param conversation_history - Full conversation history
|
|
612
|
-
* @param state - Current conversation state
|
|
613
|
-
* @returns Context recovery recommendation
|
|
614
|
-
*/
|
|
615
264
|
function detect_context_loss(
|
|
616
265
|
conversation_history: ConversationMessage[],
|
|
617
266
|
state: ConversationState
|
|
@@ -619,22 +268,15 @@ function detect_context_loss(
|
|
|
619
268
|
const now = new Date();
|
|
620
269
|
const lastMessage = conversation_history[conversation_history.length - 1];
|
|
621
270
|
const lastMessageTime = new Date(lastMessage?.timestamp || state.session_start);
|
|
622
|
-
|
|
623
271
|
const hoursDiff = (now.getTime() - lastMessageTime.getTime()) / (1000 * 60 * 60);
|
|
624
272
|
|
|
625
273
|
if (hoursDiff > 24) {
|
|
626
|
-
// Context is stale
|
|
627
274
|
return {
|
|
628
275
|
is_stale: true,
|
|
629
276
|
hours_since_last_message: Math.round(hoursDiff),
|
|
630
277
|
recommendation: 'summarize_and_confirm',
|
|
631
|
-
message: `It's been ${Math.round(hoursDiff)} hours since our last conversation
|
|
632
|
-
|
|
633
|
-
last_feature: state.current_feature?.feature_name,
|
|
634
|
-
last_phase: state.current_phase,
|
|
635
|
-
last_action: state.last_action?.description
|
|
636
|
-
},
|
|
637
|
-
suggested_confirmation: `We were working on ${state.current_feature?.feature_name || 'a feature'}. Would you like to continue with that, or would you prefer to start something new?`
|
|
278
|
+
message: `It's been ${Math.round(hoursDiff)} hours since our last conversation.`,
|
|
279
|
+
suggested_confirmation: `We were working on ${state.current_feature?.feature_name || 'a feature'}. Continue or start new?`
|
|
638
280
|
};
|
|
639
281
|
}
|
|
640
282
|
|
|
@@ -645,189 +287,11 @@ function detect_context_loss(
|
|
|
645
287
|
message: null
|
|
646
288
|
};
|
|
647
289
|
}
|
|
648
|
-
|
|
649
|
-
interface ContextRecovery {
|
|
650
|
-
is_stale: boolean;
|
|
651
|
-
hours_since_last_message: number;
|
|
652
|
-
recommendation: 'summarize_and_confirm' | 'continue' | 'reset';
|
|
653
|
-
message: string | null;
|
|
654
|
-
context_summary?: {
|
|
655
|
-
last_feature?: string;
|
|
656
|
-
last_phase?: string;
|
|
657
|
-
last_action?: string;
|
|
658
|
-
};
|
|
659
|
-
suggested_confirmation?: string;
|
|
660
|
-
}
|
|
661
|
-
```
|
|
662
|
-
|
|
663
|
-
### User Preference Learning
|
|
664
|
-
|
|
665
|
-
```typescript
|
|
666
|
-
/**
|
|
667
|
-
* Learn and adapt to user preferences
|
|
668
|
-
* @param conversation_history - Recent conversation history
|
|
669
|
-
* @param state - Current conversation state
|
|
670
|
-
* @returns Updated user preferences
|
|
671
|
-
*/
|
|
672
|
-
function learn_user_preferences(
|
|
673
|
-
conversation_history: ConversationMessage[],
|
|
674
|
-
state: ConversationState
|
|
675
|
-
): UserPreferences {
|
|
676
|
-
const preferences = { ...state.user_preferences };
|
|
677
|
-
|
|
678
|
-
// Analyze user's communication style
|
|
679
|
-
const userMessages = conversation_history.filter(m => m.role === 'user').slice(-20);
|
|
680
|
-
const avgMessageLength = userMessages.reduce((sum, m) =>
|
|
681
|
-
sum + m.content.split(/\s+/).length, 0
|
|
682
|
-
) / userMessages.length;
|
|
683
|
-
|
|
684
|
-
if (avgMessageLength < 10) {
|
|
685
|
-
preferences.communication_style = 'concise';
|
|
686
|
-
} else if (avgMessageLength > 30) {
|
|
687
|
-
preferences.communication_style = 'detailed';
|
|
688
|
-
} else {
|
|
689
|
-
preferences.communication_style = 'balanced';
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
// Detect preference for updates
|
|
693
|
-
const statusRequests = userMessages.filter(m =>
|
|
694
|
-
/status|progress|where are we|what's left/i.test(m.content)
|
|
695
|
-
).length;
|
|
696
|
-
|
|
697
|
-
if (statusRequests > 3) {
|
|
698
|
-
preferences.update_frequency = 'summary';
|
|
699
|
-
} else if (statusRequests === 0 && userMessages.length > 10) {
|
|
700
|
-
preferences.update_frequency = 'on_completion';
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
// Detect safety preference
|
|
704
|
-
const confirmationRequests = userMessages.filter(m =>
|
|
705
|
-
/are you sure|double check|verify|confirm/i.test(m.content)
|
|
706
|
-
).length;
|
|
707
|
-
|
|
708
|
-
preferences.ask_before_deleting = confirmationRequests > 2;
|
|
709
|
-
|
|
710
|
-
// Detect testing preference
|
|
711
|
-
const testMentions = userMessages.filter(m =>
|
|
712
|
-
/test|testing|tests|coverage/i.test(m.content)
|
|
713
|
-
).length;
|
|
714
|
-
|
|
715
|
-
preferences.test_automatically = testMentions > 2;
|
|
716
|
-
|
|
717
|
-
return preferences;
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
interface UserPreferences {
|
|
721
|
-
communication_style: 'concise' | 'detailed' | 'balanced';
|
|
722
|
-
update_frequency: 'immediate' | 'summary' | 'on_completion';
|
|
723
|
-
ask_before_deleting: boolean;
|
|
724
|
-
test_automatically: boolean;
|
|
725
|
-
}
|
|
726
|
-
```
|
|
727
|
-
|
|
728
|
-
## Conversation History Management
|
|
729
|
-
|
|
730
|
-
### History File Structure
|
|
731
|
-
|
|
732
|
-
```bash
|
|
733
|
-
# Stored in: .agentful/conversation-history.json
|
|
734
|
-
{
|
|
735
|
-
"version": "1.0",
|
|
736
|
-
"session_id": "uuid",
|
|
737
|
-
"started_at": "2026-01-18T00:00:00Z",
|
|
738
|
-
"messages": [
|
|
739
|
-
{
|
|
740
|
-
"id": "msg-uuid",
|
|
741
|
-
"role": "user|assistant|system",
|
|
742
|
-
"content": "Message text",
|
|
743
|
-
"timestamp": "2026-01-18T00:00:00Z",
|
|
744
|
-
"intent": "feature_request",
|
|
745
|
-
"entities": {
|
|
746
|
-
"feature_id": "login",
|
|
747
|
-
"domain_id": "authentication"
|
|
748
|
-
},
|
|
749
|
-
"references_resolved": ["it -> login feature"]
|
|
750
|
-
}
|
|
751
|
-
],
|
|
752
|
-
"context_snapshot": {
|
|
753
|
-
"current_feature": "login",
|
|
754
|
-
"current_phase": "implementing",
|
|
755
|
-
"related_features": ["register", "logout"]
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
```
|
|
759
|
-
|
|
760
|
-
### History Operations
|
|
761
|
-
|
|
762
|
-
```typescript
|
|
763
|
-
/**
|
|
764
|
-
* Add message to conversation history
|
|
765
|
-
*/
|
|
766
|
-
function add_message_to_history(
|
|
767
|
-
message: ConversationMessage,
|
|
768
|
-
history_path: string = '.agentful/conversation-history.json'
|
|
769
|
-
): void {
|
|
770
|
-
let history = read_conversation_history(history_path);
|
|
771
|
-
|
|
772
|
-
history.messages.push({
|
|
773
|
-
...message,
|
|
774
|
-
id: message.id || generate_uuid(),
|
|
775
|
-
timestamp: message.timestamp || new Date().toISOString()
|
|
776
|
-
});
|
|
777
|
-
|
|
778
|
-
// Keep only last 100 messages to prevent file bloat
|
|
779
|
-
if (history.messages.length > 100) {
|
|
780
|
-
history.messages = history.messages.slice(-100);
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
Write(history_path, JSON.stringify(history, null, 2));
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
/**
|
|
787
|
-
* Read conversation history
|
|
788
|
-
*/
|
|
789
|
-
function read_conversation_history(
|
|
790
|
-
history_path: string = '.agentful/conversation-history.json'
|
|
791
|
-
): ConversationHistory {
|
|
792
|
-
try {
|
|
793
|
-
const content = Read(history_path);
|
|
794
|
-
return JSON.parse(content);
|
|
795
|
-
} catch (error) {
|
|
796
|
-
// Initialize new history
|
|
797
|
-
return {
|
|
798
|
-
version: "1.0",
|
|
799
|
-
session_id: generate_uuid(),
|
|
800
|
-
started_at: new Date().toISOString(),
|
|
801
|
-
messages: [],
|
|
802
|
-
context_snapshot: null
|
|
803
|
-
};
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
/**
|
|
808
|
-
* Get recent conversation context
|
|
809
|
-
*/
|
|
810
|
-
function get_recent_context(
|
|
811
|
-
history_path: string = '.agentful/conversation-history.json',
|
|
812
|
-
message_count: number = 10
|
|
813
|
-
): ConversationMessage[] {
|
|
814
|
-
const history = read_conversation_history(history_path);
|
|
815
|
-
return history.messages.slice(-message_count);
|
|
816
|
-
}
|
|
817
290
|
```
|
|
818
291
|
|
|
819
292
|
## Routing Logic
|
|
820
293
|
|
|
821
|
-
### Route to Handler
|
|
822
|
-
|
|
823
294
|
```typescript
|
|
824
|
-
/**
|
|
825
|
-
* Determine which handler should process the classified intent
|
|
826
|
-
* @param intent - Classified intent from user message
|
|
827
|
-
* @param entities - Extracted entities (features, domains, etc.)
|
|
828
|
-
* @param state - Current conversation state
|
|
829
|
-
* @returns Routing decision with handler and context
|
|
830
|
-
*/
|
|
831
295
|
function route_to_handler(
|
|
832
296
|
intent: IntentClassification,
|
|
833
297
|
entities: FeatureMention,
|
|
@@ -842,9 +306,6 @@ function route_to_handler(
|
|
|
842
306
|
skill: null,
|
|
843
307
|
context: {
|
|
844
308
|
intent: intentName,
|
|
845
|
-
message: entities.feature_name
|
|
846
|
-
? `User wants to ${intentName === 'feature_request' ? 'build' : 'fix'} ${entities.feature_name}`
|
|
847
|
-
: 'User has a request that needs classification',
|
|
848
309
|
feature_id: entities.feature_id,
|
|
849
310
|
domain_id: entities.domain_id,
|
|
850
311
|
work_type: intentName === 'feature_request' ? 'FEATURE_DEVELOPMENT' : 'BUGFIX'
|
|
@@ -852,7 +313,7 @@ function route_to_handler(
|
|
|
852
313
|
};
|
|
853
314
|
}
|
|
854
315
|
|
|
855
|
-
// Status inquiries → product-tracking
|
|
316
|
+
// Status inquiries → product-tracking
|
|
856
317
|
if (intentName === 'status_update') {
|
|
857
318
|
return {
|
|
858
319
|
handler: 'product-tracking',
|
|
@@ -865,7 +326,7 @@ function route_to_handler(
|
|
|
865
326
|
};
|
|
866
327
|
}
|
|
867
328
|
|
|
868
|
-
// Validation
|
|
329
|
+
// Validation → validation skill
|
|
869
330
|
if (/test|validate|check/i.test(intentName)) {
|
|
870
331
|
return {
|
|
871
332
|
handler: 'validation',
|
|
@@ -877,42 +338,28 @@ function route_to_handler(
|
|
|
877
338
|
};
|
|
878
339
|
}
|
|
879
340
|
|
|
880
|
-
// Decision handling → decision-handler
|
|
881
|
-
if (intentName === 'decision' || /decide|choice|option/i.test(intentName)) {
|
|
882
|
-
return {
|
|
883
|
-
handler: 'decision-handler',
|
|
884
|
-
skill: null,
|
|
885
|
-
context: {
|
|
886
|
-
intent: intentName
|
|
887
|
-
}
|
|
888
|
-
};
|
|
889
|
-
}
|
|
890
|
-
|
|
891
341
|
// Product planning → product-planning skill
|
|
892
342
|
if (/plan|requirements|spec|analyze/i.test(intentName)) {
|
|
893
343
|
return {
|
|
894
344
|
handler: 'product-planning',
|
|
895
345
|
skill: 'product-planning',
|
|
896
|
-
context: {
|
|
897
|
-
intent: intentName
|
|
898
|
-
}
|
|
346
|
+
context: { intent: intentName }
|
|
899
347
|
};
|
|
900
348
|
}
|
|
901
349
|
|
|
902
|
-
// Approval/continue → orchestrator (resume
|
|
350
|
+
// Approval/continue → orchestrator (resume)
|
|
903
351
|
if (intentName === 'approval' || intentName === 'continue') {
|
|
904
352
|
return {
|
|
905
353
|
handler: 'orchestrator',
|
|
906
354
|
skill: null,
|
|
907
355
|
context: {
|
|
908
356
|
intent: 'continue',
|
|
909
|
-
message: 'User approved or wants to continue current work',
|
|
910
357
|
resume_feature: state.current_feature
|
|
911
358
|
}
|
|
912
359
|
};
|
|
913
360
|
}
|
|
914
361
|
|
|
915
|
-
// Rejection/
|
|
362
|
+
// Rejection/pause → inline
|
|
916
363
|
if (intentName === 'rejection' || intentName === 'pause') {
|
|
917
364
|
return {
|
|
918
365
|
handler: 'inline',
|
|
@@ -924,7 +371,7 @@ function route_to_handler(
|
|
|
924
371
|
};
|
|
925
372
|
}
|
|
926
373
|
|
|
927
|
-
// Questions
|
|
374
|
+
// Questions → inline
|
|
928
375
|
if (intentName === 'question' || intentName === 'clarification') {
|
|
929
376
|
return {
|
|
930
377
|
handler: 'inline',
|
|
@@ -936,7 +383,7 @@ function route_to_handler(
|
|
|
936
383
|
};
|
|
937
384
|
}
|
|
938
385
|
|
|
939
|
-
// Default:
|
|
386
|
+
// Default: inline with clarification
|
|
940
387
|
return {
|
|
941
388
|
handler: 'inline',
|
|
942
389
|
skill: null,
|
|
@@ -946,337 +393,68 @@ function route_to_handler(
|
|
|
946
393
|
}
|
|
947
394
|
};
|
|
948
395
|
}
|
|
949
|
-
|
|
950
|
-
interface RoutingDecision {
|
|
951
|
-
handler: 'orchestrator' | 'product-tracking' | 'validation' | 'decision-handler' | 'product-planning' | 'inline';
|
|
952
|
-
skill: string | null; // Skill name for Task delegation
|
|
953
|
-
context: {
|
|
954
|
-
intent: string;
|
|
955
|
-
message?: string;
|
|
956
|
-
feature_id?: string;
|
|
957
|
-
domain_id?: string;
|
|
958
|
-
work_type?: string;
|
|
959
|
-
[key: string]: any;
|
|
960
|
-
};
|
|
961
|
-
}
|
|
962
|
-
```
|
|
963
|
-
|
|
964
|
-
### Execute Routing
|
|
965
|
-
|
|
966
|
-
```typescript
|
|
967
|
-
/**
|
|
968
|
-
* Execute the routing decision
|
|
969
|
-
* @param routing - Routing decision from route_to_handler
|
|
970
|
-
* @param userMessage - Original user message
|
|
971
|
-
* @param resolved - Resolved message with references expanded
|
|
972
|
-
*/
|
|
973
|
-
function execute_routing(
|
|
974
|
-
routing: RoutingDecision,
|
|
975
|
-
userMessage: string,
|
|
976
|
-
resolved: ResolvedMessage
|
|
977
|
-
): void {
|
|
978
|
-
switch (routing.handler) {
|
|
979
|
-
case 'orchestrator':
|
|
980
|
-
// Delegate to orchestrator agent
|
|
981
|
-
Task('orchestrator',
|
|
982
|
-
`${routing.context.message || userMessage}
|
|
983
|
-
|
|
984
|
-
Work Type: ${routing.context.work_type || 'FEATURE_DEVELOPMENT'}
|
|
985
|
-
${routing.context.feature_id ? `Feature: ${routing.context.feature_id}` : ''}
|
|
986
|
-
${routing.context.domain_id ? `Domain: ${routing.context.domain_id}` : ''}
|
|
987
|
-
|
|
988
|
-
Classify and execute appropriate workflow.`
|
|
989
|
-
);
|
|
990
|
-
break;
|
|
991
|
-
|
|
992
|
-
case 'product-tracking':
|
|
993
|
-
// Delegate to product-tracking skill
|
|
994
|
-
Task('product-tracking',
|
|
995
|
-
`Show status and progress.
|
|
996
|
-
${routing.context.feature_filter ? `Filter: feature ${routing.context.feature_filter}` : ''}
|
|
997
|
-
${routing.context.domain_filter ? `Filter: domain ${routing.context.domain_filter}` : ''}`
|
|
998
|
-
);
|
|
999
|
-
break;
|
|
1000
|
-
|
|
1001
|
-
case 'validation':
|
|
1002
|
-
// Delegate to validation skill
|
|
1003
|
-
Task('validation',
|
|
1004
|
-
`Run quality gates.
|
|
1005
|
-
Scope: ${routing.context.scope || 'all'}`
|
|
1006
|
-
);
|
|
1007
|
-
break;
|
|
1008
|
-
|
|
1009
|
-
case 'decision-handler':
|
|
1010
|
-
// For now, show how to use /agentful-decide
|
|
1011
|
-
return `You have pending decisions. Run \`/agentful-decide\` to review and resolve them.`;
|
|
1012
|
-
break;
|
|
1013
|
-
|
|
1014
|
-
case 'product-planning':
|
|
1015
|
-
// Delegate to product-planning skill
|
|
1016
|
-
Task('product-planning', userMessage);
|
|
1017
|
-
break;
|
|
1018
|
-
|
|
1019
|
-
case 'inline':
|
|
1020
|
-
// Handle inline (no delegation needed)
|
|
1021
|
-
if (routing.context.needs_clarification) {
|
|
1022
|
-
return generate_clarification_response(userMessage, routing.context);
|
|
1023
|
-
} else if (routing.context.action === 'pause_work') {
|
|
1024
|
-
pause_current_work();
|
|
1025
|
-
return 'Work paused. Run `/agentful` with your next request when ready to continue.';
|
|
1026
|
-
} else if (routing.context.intent === 'question') {
|
|
1027
|
-
return answer_question(userMessage, routing.context);
|
|
1028
|
-
}
|
|
1029
|
-
break;
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
396
|
```
|
|
1033
397
|
|
|
1034
|
-
##
|
|
1035
|
-
|
|
1036
|
-
### Delegation Interface
|
|
398
|
+
## History Management
|
|
1037
399
|
|
|
1038
400
|
```typescript
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
intent: IntentClassification,
|
|
1047
|
-
entities: FeatureMention
|
|
1048
|
-
): DelegationDecision {
|
|
1049
|
-
// Feature-related intents -> delegate to appropriate agent
|
|
1050
|
-
if (['feature_request', 'bug_report', 'status_update'].includes(intent.intent)) {
|
|
1051
|
-
if (entities.type === 'feature' || entities.type === 'direct') {
|
|
1052
|
-
return {
|
|
1053
|
-
should_delegate: true,
|
|
1054
|
-
target_skill: determine_skill_for_feature(entities),
|
|
1055
|
-
context: {
|
|
1056
|
-
intent: intent.intent,
|
|
1057
|
-
feature_id: entities.feature_id,
|
|
1058
|
-
domain_id: entities.domain_id
|
|
1059
|
-
}
|
|
1060
|
-
};
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
// Validation request -> delegate to validation skill
|
|
1065
|
-
if (/test|check|validate|verify/i.test(intent.intent)) {
|
|
1066
|
-
return {
|
|
1067
|
-
should_delegate: true,
|
|
1068
|
-
target_skill: 'validation',
|
|
1069
|
-
context: {
|
|
1070
|
-
intent: intent.intent
|
|
1071
|
-
}
|
|
1072
|
-
};
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
// Product tracking -> delegate to product-tracking skill
|
|
1076
|
-
if (['status_update', 'progress'].includes(intent.intent)) {
|
|
1077
|
-
return {
|
|
1078
|
-
should_delegate: true,
|
|
1079
|
-
target_skill: 'product-tracking',
|
|
1080
|
-
context: {
|
|
1081
|
-
intent: intent.intent
|
|
1082
|
-
}
|
|
1083
|
-
};
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
return {
|
|
1087
|
-
should_delegate: false,
|
|
1088
|
-
target_skill: null,
|
|
1089
|
-
context: null
|
|
1090
|
-
};
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
interface DelegationDecision {
|
|
1094
|
-
should_delegate: boolean;
|
|
1095
|
-
target_skill: string | null;
|
|
1096
|
-
context: any;
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
function determine_skill_for_feature(entities: FeatureMention): string {
|
|
1100
|
-
// Map domains/features to appropriate skills
|
|
1101
|
-
const domainSkillMap = {
|
|
1102
|
-
'authentication': 'backend',
|
|
1103
|
-
'user-management': 'backend',
|
|
1104
|
-
'database': 'backend',
|
|
1105
|
-
'frontend': 'frontend',
|
|
1106
|
-
'ui': 'frontend',
|
|
1107
|
-
'testing': 'tester'
|
|
1108
|
-
};
|
|
1109
|
-
|
|
1110
|
-
if (entities.domain_name) {
|
|
1111
|
-
const domainKey = Object.keys(domainSkillMap).find(key =>
|
|
1112
|
-
entities.domain_name.toLowerCase().includes(key)
|
|
1113
|
-
);
|
|
1114
|
-
if (domainKey) {
|
|
1115
|
-
return domainSkillMap[domainKey];
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
return 'backend'; // Default
|
|
401
|
+
// Stored in: .agentful/conversation-history.json
|
|
402
|
+
interface ConversationHistory {
|
|
403
|
+
version: "1.0";
|
|
404
|
+
session_id: string;
|
|
405
|
+
started_at: string;
|
|
406
|
+
messages: ConversationMessage[];
|
|
407
|
+
context_snapshot: any;
|
|
1120
408
|
}
|
|
1121
|
-
```
|
|
1122
|
-
|
|
1123
|
-
### Completion Tracking Integration
|
|
1124
|
-
|
|
1125
|
-
```typescript
|
|
1126
|
-
/**
|
|
1127
|
-
* Update conversation state after action completion
|
|
1128
|
-
* @param state - Current conversation state
|
|
1129
|
-
* @param action_result - Result from delegated skill
|
|
1130
|
-
* @returns Updated conversation state
|
|
1131
|
-
*/
|
|
1132
|
-
function update_conversation_state(
|
|
1133
|
-
state: ConversationState,
|
|
1134
|
-
action_result: ActionResult
|
|
1135
|
-
): ConversationState {
|
|
1136
|
-
const updated = { ...state };
|
|
1137
|
-
|
|
1138
|
-
// Update last action
|
|
1139
|
-
updated.last_action = {
|
|
1140
|
-
type: action_result.type,
|
|
1141
|
-
description: action_result.description,
|
|
1142
|
-
timestamp: new Date().toISOString(),
|
|
1143
|
-
result: action_result
|
|
1144
|
-
};
|
|
1145
409
|
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
updated.current_phase = 'reviewing';
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
410
|
+
function add_message_to_history(
|
|
411
|
+
message: ConversationMessage,
|
|
412
|
+
history_path: string = '.agentful/conversation-history.json'
|
|
413
|
+
): void {
|
|
414
|
+
let history = read_conversation_history(history_path);
|
|
1154
415
|
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
416
|
+
history.messages.push({
|
|
417
|
+
...message,
|
|
418
|
+
id: message.id || generate_uuid(),
|
|
419
|
+
timestamp: message.timestamp || new Date().toISOString()
|
|
420
|
+
});
|
|
1158
421
|
|
|
1159
|
-
//
|
|
1160
|
-
if (
|
|
1161
|
-
|
|
1162
|
-
updated.context_health.clarification_count = 0;
|
|
422
|
+
// Keep last 100 messages
|
|
423
|
+
if (history.messages.length > 100) {
|
|
424
|
+
history.messages = history.messages.slice(-100);
|
|
1163
425
|
}
|
|
1164
426
|
|
|
1165
|
-
|
|
427
|
+
Write(history_path, JSON.stringify(history, null, 2));
|
|
1166
428
|
}
|
|
1167
|
-
```
|
|
1168
429
|
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
* @param message - Current message
|
|
1177
|
-
* @param state - Current conversation state
|
|
1178
|
-
* @returns Mind change detection result
|
|
1179
|
-
*/
|
|
1180
|
-
function detect_mind_change(
|
|
1181
|
-
message: string,
|
|
1182
|
-
state: ConversationState
|
|
1183
|
-
): MindChangeDetection {
|
|
1184
|
-
const mindChangePatterns = [
|
|
1185
|
-
/actually/i,
|
|
1186
|
-
/wait/i,
|
|
1187
|
-
/never mind/i,
|
|
1188
|
-
/forget that/i,
|
|
1189
|
-
/change\s+my\s+mind/i,
|
|
1190
|
-
/instead/i,
|
|
1191
|
-
/stop\s+(?:that|it)/i
|
|
1192
|
-
];
|
|
1193
|
-
|
|
1194
|
-
const hasMindChange = mindChangePatterns.some(pattern => pattern.test(message));
|
|
1195
|
-
|
|
1196
|
-
if (hasMindChange) {
|
|
430
|
+
function read_conversation_history(
|
|
431
|
+
history_path: string = '.agentful/conversation-history.json'
|
|
432
|
+
): ConversationHistory {
|
|
433
|
+
try {
|
|
434
|
+
const content = Read(history_path);
|
|
435
|
+
return JSON.parse(content);
|
|
436
|
+
} catch (error) {
|
|
1197
437
|
return {
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
438
|
+
version: "1.0",
|
|
439
|
+
session_id: generate_uuid(),
|
|
440
|
+
started_at: new Date().toISOString(),
|
|
441
|
+
messages: [],
|
|
442
|
+
context_snapshot: null
|
|
1203
443
|
};
|
|
1204
444
|
}
|
|
1205
|
-
|
|
1206
|
-
return {
|
|
1207
|
-
detected: false,
|
|
1208
|
-
confidence: 0,
|
|
1209
|
-
previous_intent: null,
|
|
1210
|
-
suggestion: null,
|
|
1211
|
-
reset_context: false
|
|
1212
|
-
};
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
interface MindChangeDetection {
|
|
1216
|
-
detected: boolean;
|
|
1217
|
-
confidence: number;
|
|
1218
|
-
previous_intent: string | null;
|
|
1219
|
-
suggestion: string | null;
|
|
1220
|
-
reset_context: boolean;
|
|
1221
|
-
}
|
|
1222
|
-
```
|
|
1223
|
-
|
|
1224
|
-
### Stale Context Handling
|
|
1225
|
-
|
|
1226
|
-
```typescript
|
|
1227
|
-
/**
|
|
1228
|
-
* Handle stale context when references no longer make sense
|
|
1229
|
-
* @param message - Current message
|
|
1230
|
-
* @param state - Current conversation state
|
|
1231
|
-
* @returns Stale context handling response
|
|
1232
|
-
*/
|
|
1233
|
-
function handle_stale_context(
|
|
1234
|
-
message: string,
|
|
1235
|
-
state: ConversationState
|
|
1236
|
-
): StaleContextResponse {
|
|
1237
|
-
// Check if current feature is referenced but stale
|
|
1238
|
-
const featureReference = extract_feature_mention(message, null);
|
|
1239
|
-
|
|
1240
|
-
if (featureReference.type === 'none' && state.current_feature) {
|
|
1241
|
-
// User said "it" but current feature is old
|
|
1242
|
-
const hoursSinceLastAction = state.last_action
|
|
1243
|
-
? (Date.now() - new Date(state.last_action.timestamp).getTime()) / (1000 * 60 * 60)
|
|
1244
|
-
: Infinity;
|
|
1245
|
-
|
|
1246
|
-
if (hoursSinceLastAction > 4) {
|
|
1247
|
-
return {
|
|
1248
|
-
is_stale: true,
|
|
1249
|
-
suggestion: `I'm not sure what you're referring to. We last worked on ${state.current_feature.feature_name} ${Math.round(hoursSinceLastAction)} hours ago. Are you still referring to that?`,
|
|
1250
|
-
confirm_needed: true
|
|
1251
|
-
};
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
return {
|
|
1256
|
-
is_stale: false,
|
|
1257
|
-
suggestion: null,
|
|
1258
|
-
confirm_needed: false
|
|
1259
|
-
};
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
|
-
interface StaleContextResponse {
|
|
1263
|
-
is_stale: boolean;
|
|
1264
|
-
suggestion: string | null;
|
|
1265
|
-
confirm_needed: boolean;
|
|
1266
445
|
}
|
|
1267
446
|
```
|
|
1268
447
|
|
|
1269
|
-
##
|
|
448
|
+
## Complete Processing Flow
|
|
1270
449
|
|
|
1271
450
|
```typescript
|
|
1272
|
-
// Complete conversation processing flow
|
|
1273
451
|
async function process_conversation(userMessage: string): Promise<ConversationResponse> {
|
|
1274
|
-
// 1. Load
|
|
452
|
+
// 1. Load state and history
|
|
1275
453
|
const state = load_conversation_state('.agentful/conversation-state.json');
|
|
1276
454
|
const history = read_conversation_history('.agentful/conversation-history.json');
|
|
1277
455
|
const productSpec = load_product_spec('.claude/product/');
|
|
1278
456
|
|
|
1279
|
-
// 2. Check
|
|
457
|
+
// 2. Check context loss
|
|
1280
458
|
const contextRecovery = detect_context_loss(history.messages, state);
|
|
1281
459
|
if (contextRecovery.is_stale) {
|
|
1282
460
|
return {
|
|
@@ -1306,18 +484,17 @@ async function process_conversation(userMessage: string): Promise<ConversationRe
|
|
|
1306
484
|
// 7. Detect ambiguity
|
|
1307
485
|
const ambiguity = detect_ambiguity(resolved.resolved);
|
|
1308
486
|
if (ambiguity.is_ambiguous && ambiguity.confidence > 0.6) {
|
|
1309
|
-
const clarification = suggest_clarification(resolved.resolved, productSpec);
|
|
1310
487
|
return {
|
|
1311
488
|
type: 'clarification_needed',
|
|
1312
|
-
message:
|
|
1313
|
-
suggestions:
|
|
489
|
+
message: ambiguity.suggestion,
|
|
490
|
+
suggestions: []
|
|
1314
491
|
};
|
|
1315
492
|
}
|
|
1316
493
|
|
|
1317
|
-
// 8. Route to
|
|
494
|
+
// 8. Route to handler
|
|
1318
495
|
const routing = route_to_handler(intent, entities, state);
|
|
1319
496
|
|
|
1320
|
-
// 9. Add
|
|
497
|
+
// 9. Add to history
|
|
1321
498
|
add_message_to_history({
|
|
1322
499
|
role: 'user',
|
|
1323
500
|
content: userMessage,
|
|
@@ -1329,7 +506,7 @@ async function process_conversation(userMessage: string): Promise<ConversationRe
|
|
|
1329
506
|
// 10. Execute routing
|
|
1330
507
|
execute_routing(routing, userMessage, resolved);
|
|
1331
508
|
|
|
1332
|
-
// 11. Update
|
|
509
|
+
// 11. Update state
|
|
1333
510
|
state.last_message_time = new Date().toISOString();
|
|
1334
511
|
state.message_count++;
|
|
1335
512
|
if (entities.feature_id) {
|
|
@@ -1347,41 +524,46 @@ async function process_conversation(userMessage: string): Promise<ConversationRe
|
|
|
1347
524
|
context: routing.context
|
|
1348
525
|
};
|
|
1349
526
|
}
|
|
1350
|
-
```
|
|
1351
527
|
|
|
1352
|
-
|
|
528
|
+
function execute_routing(
|
|
529
|
+
routing: RoutingDecision,
|
|
530
|
+
userMessage: string,
|
|
531
|
+
resolved: ResolvedMessage
|
|
532
|
+
): void {
|
|
533
|
+
switch (routing.handler) {
|
|
534
|
+
case 'orchestrator':
|
|
535
|
+
Task('orchestrator', `${routing.context.message || userMessage}
|
|
536
|
+
Work Type: ${routing.context.work_type || 'FEATURE_DEVELOPMENT'}
|
|
537
|
+
${routing.context.feature_id ? `Feature: ${routing.context.feature_id}` : ''}
|
|
538
|
+
${routing.context.domain_id ? `Domain: ${routing.context.domain_id}` : ''}`);
|
|
539
|
+
break;
|
|
1353
540
|
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
Add to History
|
|
1381
|
-
↓
|
|
1382
|
-
Update State
|
|
1383
|
-
↓
|
|
1384
|
-
Done
|
|
541
|
+
case 'product-tracking':
|
|
542
|
+
Task('product-tracking', `Show status and progress.
|
|
543
|
+
${routing.context.feature_filter ? `Filter: feature ${routing.context.feature_filter}` : ''}
|
|
544
|
+
${routing.context.domain_filter ? `Filter: domain ${routing.context.domain_filter}` : ''}`);
|
|
545
|
+
break;
|
|
546
|
+
|
|
547
|
+
case 'validation':
|
|
548
|
+
Task('validation', `Run quality gates. Scope: ${routing.context.scope || 'all'}`);
|
|
549
|
+
break;
|
|
550
|
+
|
|
551
|
+
case 'product-planning':
|
|
552
|
+
Task('product-planning', userMessage);
|
|
553
|
+
break;
|
|
554
|
+
|
|
555
|
+
case 'inline':
|
|
556
|
+
if (routing.context.needs_clarification) {
|
|
557
|
+
return 'Could you please provide more details about what you\'d like me to do?';
|
|
558
|
+
} else if (routing.context.action === 'pause_work') {
|
|
559
|
+
pause_current_work();
|
|
560
|
+
return 'Work paused. Run `/agentful` when ready to continue.';
|
|
561
|
+
} else if (routing.context.intent === 'question') {
|
|
562
|
+
return answer_question(userMessage, routing.context);
|
|
563
|
+
}
|
|
564
|
+
break;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
1385
567
|
```
|
|
1386
568
|
|
|
1387
569
|
## File Locations
|
|
@@ -1390,18 +572,13 @@ Done
|
|
|
1390
572
|
.agentful/
|
|
1391
573
|
├── conversation-state.json # Current conversation state
|
|
1392
574
|
├── conversation-history.json # Full message history
|
|
1393
|
-
└── user-preferences.json # Learned user preferences
|
|
575
|
+
└── user-preferences.json # Learned user preferences
|
|
1394
576
|
```
|
|
1395
577
|
|
|
1396
|
-
##
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
6. **Handle mind changes gracefully** without losing all context
|
|
1404
|
-
7. **Delegate appropriately** based on intent and entities
|
|
1405
|
-
8. **Keep history manageable** (last 100 messages)
|
|
1406
|
-
9. **Update state after every action**
|
|
1407
|
-
10. **Provide summaries** after context recovery
|
|
578
|
+
## Flow Diagram
|
|
579
|
+
|
|
580
|
+
```
|
|
581
|
+
User Input → Load State/History → Context Loss Check → Mind Change Detection →
|
|
582
|
+
Reference Resolution → Intent Classification → Entity Extraction →
|
|
583
|
+
Ambiguity Detection → Route to Handler → Add to History → Update State → Done
|
|
584
|
+
```
|