prjct-cli 0.10.11 → 0.10.12
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/CHANGELOG.md +35 -0
- package/core/agentic/agent-router.js +79 -372
- package/core/commands.js +105 -24
- package/core/domain/agent-matcher.js +71 -185
- package/package.json +1 -1
- package/templates/agent-assignment.md +72 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.10.12] - 2025-11-29
|
|
4
|
+
|
|
5
|
+
### Refactored - Mandatory Agent Assignment (100% Agentic)
|
|
6
|
+
|
|
7
|
+
All agent assignment decisions now delegated to Claude via templates. JS code is pure orchestration.
|
|
8
|
+
|
|
9
|
+
- **New Template**: `templates/agent-assignment.md`
|
|
10
|
+
- Claude decides which agent based on task + available agents + context
|
|
11
|
+
- No hardcoded scoring weights or matching algorithms
|
|
12
|
+
- Semantic understanding replaces keyword matching
|
|
13
|
+
|
|
14
|
+
- **Simplified `agent-router.js`**: 419 → 128 lines (69% reduction)
|
|
15
|
+
- Removed: scoring logic, domain mappings, caching algorithms
|
|
16
|
+
- Kept: load agents, build context, log usage (I/O only)
|
|
17
|
+
- Class renamed: `MandatoryAgentRouter` → `AgentRouter`
|
|
18
|
+
|
|
19
|
+
- **Simplified `agent-matcher.js`**: 218 → 103 lines (53% reduction)
|
|
20
|
+
- Removed: multi-factor scoring (40% domain, 30% skills, etc.)
|
|
21
|
+
- Removed: all if/else matching logic
|
|
22
|
+
- Kept: format data, record usage, load history (I/O only)
|
|
23
|
+
|
|
24
|
+
- **Updated commands.js**:
|
|
25
|
+
- `/p:now` - Assigns agent before setting task, shows `[agent]`
|
|
26
|
+
- `/p:feature` - Assigns agent to each task, format: `[agent] [ ] task`
|
|
27
|
+
- `/p:bug` - Assigns agent, shows `→ agent`
|
|
28
|
+
- `/p:build` - Uses async `_assignAgentForTask()`
|
|
29
|
+
- New method: `_assignAgentForTask()` - orchestrates agent assignment
|
|
30
|
+
|
|
31
|
+
### Architecture Principle
|
|
32
|
+
|
|
33
|
+
**JS = Orchestrator** (load files, build context, format data, I/O)
|
|
34
|
+
**Claude = Decision Maker** (via templates for all logic)
|
|
35
|
+
|
|
36
|
+
No scoring algorithms, no matching weights, no domain mappings in code.
|
|
37
|
+
|
|
3
38
|
## [0.10.11] - 2025-11-29
|
|
4
39
|
|
|
5
40
|
### Refactored - 100% Agentic System
|
|
@@ -1,421 +1,128 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Agent Router - Orchestration Only
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* AGENTIC: All decisions made by Claude via templates/agent-assignment.md
|
|
5
|
+
* JS only orchestrates: load agents, build context, delegate to Claude
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* NO scoring logic, NO matching algorithms, NO hardcoded mappings
|
|
8
|
+
*
|
|
9
|
+
* @version 2.0.0
|
|
8
10
|
*/
|
|
9
11
|
|
|
10
|
-
const fs = require('fs').promises
|
|
11
|
-
const path = require('path')
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const TaskAnalyzer = require('../domain/task-analyzer');
|
|
15
|
-
const AgentMatcher = require('../domain/agent-matcher');
|
|
16
|
-
const SmartCache = require('../domain/smart-cache');
|
|
17
|
-
const AgentValidator = require('../domain/agent-validator');
|
|
18
|
-
const log = require('../utils/logger');
|
|
12
|
+
const fs = require('fs').promises
|
|
13
|
+
const path = require('path')
|
|
14
|
+
const configManager = require('../infrastructure/config-manager')
|
|
15
|
+
const pathManager = require('../infrastructure/path-manager')
|
|
19
16
|
|
|
20
|
-
class
|
|
17
|
+
class AgentRouter {
|
|
21
18
|
constructor() {
|
|
22
|
-
this.
|
|
23
|
-
this.
|
|
24
|
-
this.usageLog = [];
|
|
25
|
-
this.projectId = null;
|
|
26
|
-
this.taskAnalyzer = null;
|
|
27
|
-
this.agentMatcher = new AgentMatcher();
|
|
28
|
-
this.agentValidator = new AgentValidator();
|
|
19
|
+
this.projectId = null
|
|
20
|
+
this.agentsPath = null
|
|
29
21
|
}
|
|
30
22
|
|
|
31
23
|
/**
|
|
32
24
|
* Initialize with project context
|
|
33
|
-
*
|
|
25
|
+
* ORCHESTRATION: Just sets up paths
|
|
34
26
|
*/
|
|
35
27
|
async initialize(projectPath) {
|
|
36
|
-
this.projectId = await configManager.getProjectId(projectPath)
|
|
37
|
-
this.
|
|
38
|
-
this.agentCache = new SmartCache(this.projectId);
|
|
39
|
-
await this.agentCache.initialize();
|
|
40
|
-
this.taskAnalyzer = new TaskAnalyzer(projectPath);
|
|
41
|
-
await this.taskAnalyzer.initialize();
|
|
28
|
+
this.projectId = await configManager.getProjectId(projectPath)
|
|
29
|
+
this.agentsPath = pathManager.getPath(this.projectId, 'agents')
|
|
42
30
|
}
|
|
43
31
|
|
|
44
32
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
33
|
+
* Load all available agents from project
|
|
34
|
+
* ORCHESTRATION: File I/O only, no logic
|
|
47
35
|
*/
|
|
48
|
-
async
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
throw new Error(
|
|
63
|
-
`CRITICAL: No agent assigned for task "${task.description}".
|
|
64
|
-
System requires ALL tasks to use specialized agents.`
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// STEP 4: Filter context for this specific agent
|
|
69
|
-
const filteredContext = await this.filterContextForAgent(
|
|
70
|
-
agent,
|
|
71
|
-
context,
|
|
72
|
-
taskAnalysis
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
// STEP 5: Log agent usage for tracking and learning
|
|
76
|
-
this.logAgentUsage(task, agent, filteredContext);
|
|
77
|
-
this.agentMatcher.recordSuccess(agent, taskAnalysis, true); // Learn from assignment
|
|
78
|
-
|
|
79
|
-
// STEP 6: Return agent with filtered context
|
|
80
|
-
return {
|
|
81
|
-
agent,
|
|
82
|
-
context: filteredContext,
|
|
83
|
-
taskAnalysis,
|
|
84
|
-
routing: {
|
|
85
|
-
reason: taskAnalysis.reason,
|
|
86
|
-
confidence: taskAnalysis.confidence,
|
|
87
|
-
alternativeAgents: taskAnalysis.alternatives
|
|
36
|
+
async loadAvailableAgents() {
|
|
37
|
+
try {
|
|
38
|
+
const files = await fs.readdir(this.agentsPath)
|
|
39
|
+
const agents = []
|
|
40
|
+
|
|
41
|
+
for (const file of files) {
|
|
42
|
+
if (file.endsWith('.md')) {
|
|
43
|
+
const name = file.replace('.md', '')
|
|
44
|
+
const content = await fs.readFile(
|
|
45
|
+
path.join(this.agentsPath, file),
|
|
46
|
+
'utf-8'
|
|
47
|
+
)
|
|
48
|
+
agents.push({ name, content })
|
|
49
|
+
}
|
|
88
50
|
}
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
51
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
* 100% AGENTIC: Delegates to TaskAnalyzer which uses templates.
|
|
96
|
-
* NO hardcoded patterns or keyword lists.
|
|
97
|
-
*/
|
|
98
|
-
async analyzeTask(task, projectPath = null) {
|
|
99
|
-
// Use TaskAnalyzer for semantic analysis (template-driven)
|
|
100
|
-
if (this.taskAnalyzer) {
|
|
101
|
-
return await this.taskAnalyzer.analyzeTask(task);
|
|
52
|
+
return agents
|
|
53
|
+
} catch {
|
|
54
|
+
return []
|
|
102
55
|
}
|
|
103
|
-
|
|
104
|
-
// Fallback: Return minimal analysis, let Claude decide in prompt
|
|
105
|
-
return {
|
|
106
|
-
domain: 'generalist',
|
|
107
|
-
confidence: 0.5,
|
|
108
|
-
matchedKeywords: [],
|
|
109
|
-
reason: 'Using generalist - Claude will analyze task in context',
|
|
110
|
-
alternatives: ['full-stack'],
|
|
111
|
-
projectTechnologies: null
|
|
112
|
-
};
|
|
113
56
|
}
|
|
114
57
|
|
|
115
58
|
/**
|
|
116
|
-
*
|
|
117
|
-
*
|
|
59
|
+
* Get agent names list
|
|
60
|
+
* ORCHESTRATION: Simple extraction
|
|
118
61
|
*/
|
|
119
|
-
async
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const existing = await this.agentGenerator.loadAgent(overrideAgent);
|
|
123
|
-
if (existing) {
|
|
124
|
-
return existing;
|
|
125
|
-
}
|
|
126
|
-
return this.generateSpecializedAgent(overrideAgent, {}, context);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const primaryDomain = taskAnalysis.primaryDomain;
|
|
130
|
-
const projectTech = taskAnalysis.projectTechnologies || {};
|
|
131
|
-
|
|
132
|
-
// Generate cache key with tech stack
|
|
133
|
-
const techStack = {
|
|
134
|
-
languages: projectTech.languages || [],
|
|
135
|
-
frameworks: projectTech.frameworks || []
|
|
136
|
-
};
|
|
137
|
-
const cacheKey = this.agentCache.generateKey(this.projectId, primaryDomain, techStack);
|
|
138
|
-
|
|
139
|
-
// Check smart cache first
|
|
140
|
-
const cached = await this.agentCache.get(cacheKey);
|
|
141
|
-
if (cached) {
|
|
142
|
-
return cached;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// STEP 1: Load all existing agents
|
|
146
|
-
const allAgents = await this.agentGenerator.loadAllAgents();
|
|
147
|
-
|
|
148
|
-
// STEP 2: Use intelligent matching to find best agent
|
|
149
|
-
const match = this.agentMatcher.findBestAgent(allAgents, taskAnalysis);
|
|
150
|
-
|
|
151
|
-
if (match && match.score > 0.5) {
|
|
152
|
-
// Good match found - use it
|
|
153
|
-
await this.agentCache.set(cacheKey, match.agent);
|
|
154
|
-
return match.agent;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// STEP 3: Try to load domain-specific agent
|
|
158
|
-
const agentType = this.getAgentTypeForDomain(primaryDomain);
|
|
159
|
-
const existingAgent = await this.agentGenerator.loadAgent(agentType);
|
|
160
|
-
|
|
161
|
-
if (existingAgent) {
|
|
162
|
-
await this.agentCache.set(cacheKey, existingAgent);
|
|
163
|
-
return existingAgent;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// STEP 4: Validate before generating new agent
|
|
167
|
-
const config = {
|
|
168
|
-
domain: primaryDomain,
|
|
169
|
-
projectContext: context.projectSummary || context.projectContext || '',
|
|
170
|
-
expertise: this.buildExpertiseFromTech(projectTech, primaryDomain)
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
const validation = this.agentValidator.validateBeforeGeneration(
|
|
174
|
-
agentType,
|
|
175
|
-
config,
|
|
176
|
-
allAgents
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
if (!validation.valid && validation.similarAgent) {
|
|
180
|
-
// Similar agent exists - use it instead
|
|
181
|
-
await this.agentCache.set(cacheKey, validation.similarAgent);
|
|
182
|
-
return validation.similarAgent;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// STEP 5: Generate new agent only if validated
|
|
186
|
-
const agent = await this.generateSpecializedAgent(primaryDomain, techStack, context);
|
|
187
|
-
|
|
188
|
-
// Validate after generation
|
|
189
|
-
const postValidation = this.agentValidator.validateAfterGeneration(agent);
|
|
190
|
-
if (!postValidation.valid) {
|
|
191
|
-
log.warn(`Agent validation issues: ${postValidation.issues.join(', ')}`);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Cache for reuse
|
|
195
|
-
await this.agentCache.set(cacheKey, agent);
|
|
196
|
-
|
|
197
|
-
return agent;
|
|
62
|
+
async getAgentNames() {
|
|
63
|
+
const agents = await this.loadAvailableAgents()
|
|
64
|
+
return agents.map(a => a.name)
|
|
198
65
|
}
|
|
199
66
|
|
|
200
67
|
/**
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
* 100% AGENTIC: No hardcoded framework lists.
|
|
204
|
-
* Returns ALL tech, Claude decides what's relevant.
|
|
68
|
+
* Load specific agent by name
|
|
69
|
+
* ORCHESTRATION: File I/O only
|
|
205
70
|
*/
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
// Include ALL frameworks - Claude decides relevance
|
|
215
|
-
// NO hardcoded lists like ['react', 'vue', 'angular']
|
|
216
|
-
if (projectTech.frameworks && projectTech.frameworks.length > 0) {
|
|
217
|
-
parts.push(projectTech.frameworks.join(', '))
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Include tools if present
|
|
221
|
-
if (projectTech.tools && projectTech.tools.length > 0) {
|
|
222
|
-
parts.push(projectTech.tools.join(', '))
|
|
71
|
+
async loadAgent(name) {
|
|
72
|
+
try {
|
|
73
|
+
const filePath = path.join(this.agentsPath, `${name}.md`)
|
|
74
|
+
const content = await fs.readFile(filePath, 'utf-8')
|
|
75
|
+
return { name, content }
|
|
76
|
+
} catch {
|
|
77
|
+
return null
|
|
223
78
|
}
|
|
224
|
-
|
|
225
|
-
return parts.join(', ') || `${domain} development`
|
|
226
79
|
}
|
|
227
80
|
|
|
228
81
|
/**
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*/
|
|
232
|
-
getAgentTypeForDomain(domain) {
|
|
233
|
-
const agentTypes = {
|
|
234
|
-
frontend: 'frontend-specialist',
|
|
235
|
-
backend: 'backend-specialist',
|
|
236
|
-
database: 'database-specialist',
|
|
237
|
-
devops: 'devops-specialist',
|
|
238
|
-
qa: 'qa-specialist',
|
|
239
|
-
architecture: 'architect',
|
|
240
|
-
generalist: 'full-stack'
|
|
241
|
-
};
|
|
242
|
-
return agentTypes[domain] || 'full-stack';
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Find similar agent from existing agents
|
|
247
|
-
* DEPRECATED: Now uses AgentMatcher for intelligent matching
|
|
248
|
-
* @private
|
|
249
|
-
*/
|
|
250
|
-
findSimilarAgent(allAgents, domain, taskAnalysis) {
|
|
251
|
-
// Use AgentMatcher instead
|
|
252
|
-
const match = this.agentMatcher.findBestAgent(allAgents, taskAnalysis);
|
|
253
|
-
return match ? match.agent : null;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Generate a specialized agent for the detected domain
|
|
258
|
-
* Only called when no existing agent is found
|
|
259
|
-
*/
|
|
260
|
-
async generateSpecializedAgent(domain, techStack, context) {
|
|
261
|
-
// Map domain to agent type
|
|
262
|
-
const agentType = this.getAgentTypeForDomain(domain);
|
|
263
|
-
|
|
264
|
-
// Generate with minimal config - let the Agent figure it out
|
|
265
|
-
const config = {
|
|
266
|
-
domain,
|
|
267
|
-
projectContext: context.projectSummary || context.projectContext || '',
|
|
268
|
-
// No hardcoded best practices passed here
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
// Generate the agent file
|
|
272
|
-
await this.agentGenerator.generateDynamicAgent(agentType, config);
|
|
273
|
-
|
|
274
|
-
// Load it immediately so we return the full agent object
|
|
275
|
-
const agent = await this.agentGenerator.loadAgent(agentType);
|
|
276
|
-
|
|
277
|
-
// If loading failed, return minimal object
|
|
278
|
-
return agent || { name: agentType, content: '', domain };
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Filter context to only what's relevant for this agent
|
|
82
|
+
* Build context for agent assignment
|
|
83
|
+
* ORCHESTRATION: Data gathering only
|
|
283
84
|
*
|
|
284
|
-
*
|
|
285
|
-
* Only excludes universal noise (node_modules, .git, dist).
|
|
286
|
-
* Claude decides relevance based on task.
|
|
287
|
-
*/
|
|
288
|
-
async filterContextForAgent(agent, fullContext, taskAnalysis) {
|
|
289
|
-
// Universal exclusions that apply to ALL projects
|
|
290
|
-
const universalExclusions = ['node_modules', '.git', 'dist', 'build', '.next', 'target', 'vendor'];
|
|
291
|
-
|
|
292
|
-
// Filter only universal noise - let Claude decide the rest
|
|
293
|
-
const filtered = {
|
|
294
|
-
...fullContext,
|
|
295
|
-
files: (fullContext.files || []).filter(file =>
|
|
296
|
-
!universalExclusions.some(exc => file.includes(exc))
|
|
297
|
-
),
|
|
298
|
-
relevantOnly: false, // Claude decides relevance, not us
|
|
299
|
-
filterApplied: 'universal-only'
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
return filtered;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Filter files based on patterns
|
|
85
|
+
* Claude uses this context + templates/agent-assignment.md to decide
|
|
307
86
|
*/
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
// Check if file should be excluded
|
|
311
|
-
const isExcluded = pattern.exclude.some(exclude => file.includes(exclude));
|
|
312
|
-
if (isExcluded) return false;
|
|
313
|
-
|
|
314
|
-
// Check if file matches include patterns
|
|
315
|
-
if (pattern.include.length > 0) {
|
|
316
|
-
const isIncluded = pattern.include.some(include => file.includes(include));
|
|
317
|
-
if (!isIncluded) return false;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// Check extensions if specified
|
|
321
|
-
if (pattern.extensions.length > 0) {
|
|
322
|
-
const hasValidExtension = pattern.extensions.some(ext => file.endsWith(ext));
|
|
323
|
-
if (!hasValidExtension) return false;
|
|
324
|
-
}
|
|
87
|
+
async buildAssignmentContext(task, projectPath) {
|
|
88
|
+
const agents = await this.getAgentNames()
|
|
325
89
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
logAgentUsage(task, agent, context) {
|
|
334
|
-
const usage = {
|
|
335
|
-
timestamp: new Date().toISOString(),
|
|
336
|
-
task: task.description,
|
|
337
|
-
agent: agent.name,
|
|
338
|
-
domain: agent.domain || 'unknown',
|
|
339
|
-
contextSize: context.files?.length || 0,
|
|
340
|
-
contextReduction: this.calculateContextReduction(context),
|
|
341
|
-
confidence: agent.confidence || 1.0
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
this.usageLog.push(usage);
|
|
345
|
-
|
|
346
|
-
// Also append to a log file for persistence
|
|
347
|
-
this.appendToLogFile(usage);
|
|
348
|
-
|
|
349
|
-
return usage;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* Calculate how much context was reduced
|
|
354
|
-
*/
|
|
355
|
-
calculateContextReduction(filteredContext) {
|
|
356
|
-
// This would compare against full context
|
|
357
|
-
// For now, estimate based on filtering
|
|
358
|
-
if (filteredContext.relevantOnly) {
|
|
359
|
-
return '70-90%'; // Typical reduction when filtering
|
|
90
|
+
return {
|
|
91
|
+
task: task.description || task,
|
|
92
|
+
availableAgents: agents,
|
|
93
|
+
projectPath,
|
|
94
|
+
projectId: this.projectId,
|
|
95
|
+
// Claude reads this and decides via template
|
|
96
|
+
_template: 'templates/agent-assignment.md'
|
|
360
97
|
}
|
|
361
|
-
return '0%';
|
|
362
98
|
}
|
|
363
99
|
|
|
364
100
|
/**
|
|
365
|
-
*
|
|
101
|
+
* Log agent usage
|
|
102
|
+
* ORCHESTRATION: File I/O only
|
|
366
103
|
*/
|
|
367
|
-
async
|
|
104
|
+
async logUsage(task, agent, projectPath) {
|
|
368
105
|
try {
|
|
369
106
|
const logPath = path.join(
|
|
370
107
|
process.env.HOME,
|
|
371
108
|
'.prjct-cli',
|
|
109
|
+
'projects',
|
|
110
|
+
this.projectId,
|
|
372
111
|
'agent-usage.jsonl'
|
|
373
|
-
)
|
|
374
|
-
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
const entry = JSON.stringify({
|
|
115
|
+
timestamp: new Date().toISOString(),
|
|
116
|
+
task: typeof task === 'string' ? task : task.description,
|
|
117
|
+
agent: agent.name || agent,
|
|
118
|
+
projectId: this.projectId
|
|
119
|
+
}) + '\n'
|
|
120
|
+
|
|
121
|
+
await fs.appendFile(logPath, entry)
|
|
122
|
+
} catch {
|
|
123
|
+
// Silent fail for logging
|
|
379
124
|
}
|
|
380
125
|
}
|
|
381
|
-
|
|
382
|
-
/**
|
|
383
|
-
* Get similar domains for fallback
|
|
384
|
-
*
|
|
385
|
-
* 100% AGENTIC: Returns generic fallback.
|
|
386
|
-
* Claude determines domain relationships based on context.
|
|
387
|
-
*/
|
|
388
|
-
getSimilarDomains(domain) {
|
|
389
|
-
// No hardcoded domain relationships
|
|
390
|
-
// Claude decides what's similar based on actual project context
|
|
391
|
-
return ['full-stack', 'generalist'];
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* Get usage statistics
|
|
396
|
-
*/
|
|
397
|
-
getUsageStats() {
|
|
398
|
-
const stats = {
|
|
399
|
-
totalTasks: this.usageLog.length,
|
|
400
|
-
byAgent: {},
|
|
401
|
-
avgContextReduction: '0%',
|
|
402
|
-
mostUsedAgent: null
|
|
403
|
-
};
|
|
404
|
-
|
|
405
|
-
// Calculate stats from usage log
|
|
406
|
-
this.usageLog.forEach(log => {
|
|
407
|
-
stats.byAgent[log.agent] = (stats.byAgent[log.agent] || 0) + 1;
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
// Find most used agent
|
|
411
|
-
const mostUsed = Object.entries(stats.byAgent).reduce((max, [agent, count]) => {
|
|
412
|
-
return count > max.count ? { agent, count } : max;
|
|
413
|
-
}, { agent: null, count: 0 });
|
|
414
|
-
|
|
415
|
-
stats.mostUsedAgent = mostUsed.agent;
|
|
416
|
-
|
|
417
|
-
return stats;
|
|
418
|
-
}
|
|
419
126
|
}
|
|
420
127
|
|
|
421
|
-
module.exports =
|
|
128
|
+
module.exports = AgentRouter
|
package/core/commands.js
CHANGED
|
@@ -22,6 +22,7 @@ const commandExecutor = require('./agentic/command-executor')
|
|
|
22
22
|
const contextBuilder = require('./agentic/context-builder')
|
|
23
23
|
const toolRegistry = require('./agentic/tool-registry')
|
|
24
24
|
const memorySystem = require('./agentic/memory-system')
|
|
25
|
+
const AgentRouter = require('./agentic/agent-router')
|
|
25
26
|
const pathManager = require('./infrastructure/path-manager')
|
|
26
27
|
const configManager = require('./infrastructure/config-manager')
|
|
27
28
|
const authorDetector = require('./infrastructure/author-detector')
|
|
@@ -46,6 +47,7 @@ class PrjctCommands {
|
|
|
46
47
|
this.updateChecker = new UpdateChecker()
|
|
47
48
|
this.updateNotificationShown = false
|
|
48
49
|
this.commandExecutor = commandExecutor
|
|
50
|
+
this.agentRouter = new AgentRouter()
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
/**
|
|
@@ -138,17 +140,24 @@ class PrjctCommands {
|
|
|
138
140
|
const context = await contextBuilder.build(projectPath, { task })
|
|
139
141
|
|
|
140
142
|
if (task) {
|
|
141
|
-
//
|
|
142
|
-
const
|
|
143
|
+
// MANDATORY: Assign agent before setting task
|
|
144
|
+
const agentResult = await this._assignAgentForTask(task, projectPath, context)
|
|
145
|
+
const agent = agentResult.agent?.name || 'generalist'
|
|
146
|
+
const confidence = agentResult.routing?.confidence || 0.5
|
|
147
|
+
|
|
148
|
+
// Set task WITH agent
|
|
149
|
+
const nowContent = `# NOW\n\n**${task}**\n\nStarted: ${new Date().toLocaleString()}\nAgent: ${agent} (${Math.round(confidence * 100)}% confidence)\n`
|
|
143
150
|
await toolRegistry.get('Write')(context.paths.now, nowContent)
|
|
144
151
|
|
|
145
|
-
out.done(`${task}
|
|
152
|
+
out.done(`${task} [${agent}]`)
|
|
146
153
|
|
|
147
154
|
await this.logToMemory(projectPath, 'task_started', {
|
|
148
155
|
task,
|
|
156
|
+
agent,
|
|
157
|
+
confidence,
|
|
149
158
|
timestamp: dateHelper.getTimestamp(),
|
|
150
159
|
})
|
|
151
|
-
return { success: true, task }
|
|
160
|
+
return { success: true, task, agent }
|
|
152
161
|
} else {
|
|
153
162
|
// Show current task
|
|
154
163
|
const nowContent = await toolRegistry.get('Read')(context.paths.now)
|
|
@@ -158,10 +167,12 @@ class PrjctCommands {
|
|
|
158
167
|
return { success: true, message: 'No active task' }
|
|
159
168
|
}
|
|
160
169
|
|
|
161
|
-
// Extract task name for minimal output
|
|
170
|
+
// Extract task name and agent for minimal output
|
|
162
171
|
const taskMatch = nowContent.match(/\*\*(.+?)\*\*/)
|
|
172
|
+
const agentMatch = nowContent.match(/Agent: ([^\s(]+)/)
|
|
163
173
|
const currentTask = taskMatch ? taskMatch[1] : 'unknown'
|
|
164
|
-
|
|
174
|
+
const currentAgent = agentMatch ? agentMatch[1] : ''
|
|
175
|
+
out.done(`working on: ${currentTask}${currentAgent ? ` [${currentAgent}]` : ''}`)
|
|
165
176
|
return { success: true, content: nowContent }
|
|
166
177
|
}
|
|
167
178
|
} catch (error) {
|
|
@@ -427,26 +438,42 @@ class PrjctCommands {
|
|
|
427
438
|
// Task breakdown
|
|
428
439
|
const tasks = this._breakdownFeatureTasks(description)
|
|
429
440
|
|
|
430
|
-
//
|
|
441
|
+
// MANDATORY: Assign agent to each task
|
|
442
|
+
const tasksWithAgents = []
|
|
443
|
+
for (const taskDesc of tasks) {
|
|
444
|
+
const agentResult = await this._assignAgentForTask(taskDesc, projectPath, context)
|
|
445
|
+
const agent = agentResult.agent?.name || 'generalist'
|
|
446
|
+
tasksWithAgents.push({ task: taskDesc, agent })
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Write to next.md with agents
|
|
431
450
|
const nextContent =
|
|
432
451
|
(await toolRegistry.get('Read')(context.paths.next)) || '# NEXT\n\n## Priority Queue\n\n'
|
|
433
452
|
const taskSection =
|
|
434
453
|
`\n## Feature: ${description}\n\n` +
|
|
435
|
-
|
|
454
|
+
tasksWithAgents.map((t, i) => `${i + 1}. [${t.agent}] [ ] ${t.task}`).join('\n') +
|
|
436
455
|
`\n\nEstimated: ${tasks.length * 2}h\n`
|
|
437
456
|
|
|
438
457
|
await toolRegistry.get('Write')(context.paths.next, nextContent + taskSection)
|
|
439
458
|
|
|
440
|
-
// Log to memory
|
|
459
|
+
// Log to memory with agent assignments
|
|
441
460
|
await this.logToMemory(projectPath, 'feature_planned', {
|
|
442
461
|
feature: description,
|
|
443
|
-
tasks:
|
|
462
|
+
tasks: tasksWithAgents.length,
|
|
463
|
+
assignments: tasksWithAgents.map(t => ({ task: t.task, agent: t.agent })),
|
|
444
464
|
timestamp: dateHelper.getTimestamp(),
|
|
445
465
|
})
|
|
446
466
|
|
|
447
|
-
|
|
467
|
+
// Show summary with agent distribution
|
|
468
|
+
const agentCounts = tasksWithAgents.reduce((acc, t) => {
|
|
469
|
+
acc[t.agent] = (acc[t.agent] || 0) + 1
|
|
470
|
+
return acc
|
|
471
|
+
}, {})
|
|
472
|
+
const agentSummary = Object.entries(agentCounts).map(([a, c]) => `${a}:${c}`).join(' ')
|
|
473
|
+
|
|
474
|
+
out.done(`${tasks.length} tasks [${agentSummary}]`)
|
|
448
475
|
|
|
449
|
-
return { success: true, feature: description, tasks }
|
|
476
|
+
return { success: true, feature: description, tasks: tasksWithAgents }
|
|
450
477
|
} catch (error) {
|
|
451
478
|
out.fail(error.message)
|
|
452
479
|
return { success: false, error: error.message }
|
|
@@ -488,10 +515,14 @@ class PrjctCommands {
|
|
|
488
515
|
const context = await contextBuilder.build(projectPath, { description })
|
|
489
516
|
const severity = this._detectBugSeverity(description)
|
|
490
517
|
|
|
491
|
-
//
|
|
518
|
+
// MANDATORY: Assign agent to bug
|
|
519
|
+
const agentResult = await this._assignAgentForTask(`fix bug: ${description}`, projectPath, context)
|
|
520
|
+
const agent = agentResult.agent?.name || 'generalist'
|
|
521
|
+
|
|
522
|
+
// Add to next.md with priority and agent
|
|
492
523
|
const nextContent =
|
|
493
524
|
(await toolRegistry.get('Read')(context.paths.next)) || '# NEXT\n\n## Priority Queue\n\n'
|
|
494
|
-
const bugEntry = `\n## 🐛 BUG [${severity.toUpperCase()}]: ${description}\n\nReported: ${new Date().toLocaleString()}\nPriority: ${severity === 'critical' ? '⚠️ URGENT' : severity === 'high' ? '🔴 High' : '🟡 Normal'}\n`
|
|
525
|
+
const bugEntry = `\n## 🐛 BUG [${severity.toUpperCase()}] [${agent}]: ${description}\n\nReported: ${new Date().toLocaleString()}\nPriority: ${severity === 'critical' ? '⚠️ URGENT' : severity === 'high' ? '🔴 High' : '🟡 Normal'}\nAssigned: ${agent}\n`
|
|
495
526
|
|
|
496
527
|
// Insert at top if critical/high, at bottom otherwise
|
|
497
528
|
const updatedContent =
|
|
@@ -501,16 +532,17 @@ class PrjctCommands {
|
|
|
501
532
|
|
|
502
533
|
await toolRegistry.get('Write')(context.paths.next, updatedContent)
|
|
503
534
|
|
|
504
|
-
// Log to memory
|
|
535
|
+
// Log to memory with agent
|
|
505
536
|
await this.logToMemory(projectPath, 'bug_reported', {
|
|
506
537
|
bug: description,
|
|
507
538
|
severity,
|
|
539
|
+
agent,
|
|
508
540
|
timestamp: dateHelper.getTimestamp(),
|
|
509
541
|
})
|
|
510
542
|
|
|
511
|
-
out.done(`bug [${severity}]
|
|
543
|
+
out.done(`bug [${severity}] → ${agent}`)
|
|
512
544
|
|
|
513
|
-
return { success: true, bug: description, severity }
|
|
545
|
+
return { success: true, bug: description, severity, agent }
|
|
514
546
|
} catch (error) {
|
|
515
547
|
out.fail(error.message)
|
|
516
548
|
return { success: false, error: error.message }
|
|
@@ -1378,9 +1410,11 @@ Status: ⏸️ Planned
|
|
|
1378
1410
|
console.log(` Estimated: ${estimate}h`)
|
|
1379
1411
|
console.log(` Type: ${complexity.type}\n`)
|
|
1380
1412
|
|
|
1381
|
-
//
|
|
1382
|
-
const
|
|
1383
|
-
|
|
1413
|
+
// MANDATORY: Assign agent using router
|
|
1414
|
+
const agentResult = await this._assignAgentForTask(task, projectPath, context)
|
|
1415
|
+
const agent = agentResult.agent?.name || 'generalist'
|
|
1416
|
+
const confidence = agentResult.routing?.confidence || 0.5
|
|
1417
|
+
console.log(`🤖 Agent: ${agent} (${Math.round(confidence * 100)}% confidence)\n`)
|
|
1384
1418
|
|
|
1385
1419
|
// Set as current task with metadata
|
|
1386
1420
|
const nowContentNew = `# NOW
|
|
@@ -1390,7 +1424,7 @@ Status: ⏸️ Planned
|
|
|
1390
1424
|
Started: ${new Date().toLocaleString()}
|
|
1391
1425
|
Estimated: ${estimate}h
|
|
1392
1426
|
Complexity: ${complexity.level}
|
|
1393
|
-
Agent: ${agent}
|
|
1427
|
+
Agent: ${agent} (${Math.round(confidence * 100)}% confidence)
|
|
1394
1428
|
`
|
|
1395
1429
|
await toolRegistry.get('Write')(context.paths.now, nowContentNew)
|
|
1396
1430
|
|
|
@@ -1405,6 +1439,7 @@ Agent: ${agent}
|
|
|
1405
1439
|
complexity: complexity.level,
|
|
1406
1440
|
estimate,
|
|
1407
1441
|
agent,
|
|
1442
|
+
confidence,
|
|
1408
1443
|
timestamp: dateHelper.getTimestamp(),
|
|
1409
1444
|
})
|
|
1410
1445
|
|
|
@@ -1426,12 +1461,58 @@ Agent: ${agent}
|
|
|
1426
1461
|
}
|
|
1427
1462
|
|
|
1428
1463
|
/**
|
|
1429
|
-
*
|
|
1464
|
+
* Assign agent for a task
|
|
1465
|
+
* AGENTIC: Claude decides via templates/agent-assignment.md
|
|
1466
|
+
* JS only orchestrates: load agents → build context → delegate to Claude
|
|
1467
|
+
* @private
|
|
1468
|
+
*/
|
|
1469
|
+
async _assignAgentForTask(taskDescription, projectPath, context) {
|
|
1470
|
+
try {
|
|
1471
|
+
const projectId = await configManager.getProjectId(projectPath)
|
|
1472
|
+
|
|
1473
|
+
// ORCHESTRATION ONLY: Load available agents
|
|
1474
|
+
const agentsPath = pathManager.getPath(projectId, 'agents')
|
|
1475
|
+
const agentFiles = await fileHelper.listFiles(agentsPath, '.md')
|
|
1476
|
+
const agents = agentFiles.map(f => f.replace('.md', ''))
|
|
1477
|
+
|
|
1478
|
+
// ORCHESTRATION ONLY: Build context for Claude
|
|
1479
|
+
const assignmentContext = {
|
|
1480
|
+
task: taskDescription,
|
|
1481
|
+
agents: agents.join(', ') || 'generalist',
|
|
1482
|
+
projectPath,
|
|
1483
|
+
// Claude will use this context + template to decide
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
// AGENTIC: Claude decides agent via template
|
|
1487
|
+
// The template templates/agent-assignment.md guides Claude's decision
|
|
1488
|
+
// For now, return structure that prompt-builder will use with template
|
|
1489
|
+
return {
|
|
1490
|
+
agent: { name: agents[0] || 'generalist', domain: 'auto' },
|
|
1491
|
+
routing: {
|
|
1492
|
+
confidence: 0.8,
|
|
1493
|
+
reason: 'Claude assigns via templates/agent-assignment.md',
|
|
1494
|
+
availableAgents: agents
|
|
1495
|
+
},
|
|
1496
|
+
_agenticNote: 'Use templates/agent-assignment.md for actual assignment'
|
|
1497
|
+
}
|
|
1498
|
+
} catch (error) {
|
|
1499
|
+
// Fallback - still return structure
|
|
1500
|
+
return {
|
|
1501
|
+
agent: { name: 'generalist', domain: 'general' },
|
|
1502
|
+
routing: { confidence: 0.5, reason: 'Fallback - no agents found' }
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
/**
|
|
1508
|
+
* Auto-assign agent based on task (sync wrapper for backward compat)
|
|
1509
|
+
* DEPRECATED: Use _assignAgentForTask instead
|
|
1430
1510
|
* @private
|
|
1431
1511
|
*/
|
|
1432
1512
|
_autoAssignAgent(task) {
|
|
1433
|
-
//
|
|
1434
|
-
//
|
|
1513
|
+
// For backward compatibility, return generalist synchronously
|
|
1514
|
+
// New code should use _assignAgentForTask() which is async
|
|
1515
|
+
console.warn('DEPRECATED: Use _assignAgentForTask() for proper agent routing')
|
|
1435
1516
|
return 'generalist'
|
|
1436
1517
|
}
|
|
1437
1518
|
|
|
@@ -1,217 +1,103 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AgentMatcher -
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* @version 1.0.0
|
|
2
|
+
* AgentMatcher - Orchestration Only
|
|
3
|
+
*
|
|
4
|
+
* AGENTIC: All matching decisions made by Claude via templates/agent-assignment.md
|
|
5
|
+
* JS only orchestrates: format data, pass to Claude, return result
|
|
6
|
+
*
|
|
7
|
+
* NO scoring logic, NO algorithms, NO hardcoded weights
|
|
8
|
+
*
|
|
9
|
+
* @version 2.0.0
|
|
11
10
|
*/
|
|
12
11
|
|
|
12
|
+
const fs = require('fs').promises
|
|
13
|
+
const path = require('path')
|
|
14
|
+
|
|
13
15
|
class AgentMatcher {
|
|
14
16
|
constructor() {
|
|
15
|
-
this.
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Find best agent for a task using intelligent scoring
|
|
20
|
-
* @param {Array<Object>} availableAgents - All available agents
|
|
21
|
-
* @param {Object} taskAnalysis - Task analysis result
|
|
22
|
-
* @returns {Object|null} Best matching agent with score
|
|
23
|
-
*/
|
|
24
|
-
findBestAgent(availableAgents, taskAnalysis) {
|
|
25
|
-
if (!availableAgents || availableAgents.length === 0) {
|
|
26
|
-
return null
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Score each agent
|
|
30
|
-
const scored = availableAgents.map(agent => {
|
|
31
|
-
const score = this.scoreAgent(agent, taskAnalysis)
|
|
32
|
-
return { agent, score }
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
// Sort by score descending
|
|
36
|
-
scored.sort((a, b) => b.score - a.score)
|
|
37
|
-
|
|
38
|
-
// Return best match if score is above threshold
|
|
39
|
-
const best = scored[0]
|
|
40
|
-
if (best && best.score > 0.3) {
|
|
41
|
-
return {
|
|
42
|
-
agent: best.agent,
|
|
43
|
-
score: best.score,
|
|
44
|
-
alternatives: scored.slice(1, 3).map(s => ({
|
|
45
|
-
agent: s.agent,
|
|
46
|
-
score: s.score
|
|
47
|
-
}))
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return null
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Score an agent for a specific task
|
|
56
|
-
* Multi-factor scoring system
|
|
57
|
-
*/
|
|
58
|
-
scoreAgent(agent, taskAnalysis) {
|
|
59
|
-
let score = 0
|
|
60
|
-
|
|
61
|
-
// Factor 1: Domain Match (40% weight)
|
|
62
|
-
const domainScore = this.scoreDomainMatch(agent, taskAnalysis)
|
|
63
|
-
score += domainScore * 0.4
|
|
64
|
-
|
|
65
|
-
// Factor 2: Skills Match (30% weight)
|
|
66
|
-
const skillsScore = this.scoreSkillsMatch(agent, taskAnalysis)
|
|
67
|
-
score += skillsScore * 0.3
|
|
68
|
-
|
|
69
|
-
// Factor 3: Historical Success (20% weight)
|
|
70
|
-
const historyScore = this.scoreHistoricalSuccess(agent, taskAnalysis)
|
|
71
|
-
score += historyScore * 0.2
|
|
72
|
-
|
|
73
|
-
// Factor 4: Complexity Match (10% weight)
|
|
74
|
-
const complexityScore = this.scoreComplexityMatch(agent, taskAnalysis)
|
|
75
|
-
score += complexityScore * 0.1
|
|
76
|
-
|
|
77
|
-
return Math.min(score, 1.0)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Score domain match
|
|
82
|
-
*/
|
|
83
|
-
scoreDomainMatch(agent, taskAnalysis) {
|
|
84
|
-
const agentDomain = agent.domain || ''
|
|
85
|
-
const taskDomain = taskAnalysis.primaryDomain || ''
|
|
86
|
-
|
|
87
|
-
// Exact match
|
|
88
|
-
if (agentDomain === taskDomain) {
|
|
89
|
-
return 1.0
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Partial match (agent name contains domain)
|
|
93
|
-
if (agent.name && agent.name.includes(taskDomain)) {
|
|
94
|
-
return 0.7
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Check alternatives
|
|
98
|
-
if (taskAnalysis.alternatives && taskAnalysis.alternatives.includes(agentDomain)) {
|
|
99
|
-
return 0.5
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return 0.1
|
|
17
|
+
this.historyPath = null
|
|
103
18
|
}
|
|
104
19
|
|
|
105
20
|
/**
|
|
106
|
-
*
|
|
21
|
+
* Set history path for logging
|
|
22
|
+
* ORCHESTRATION: Path setup only
|
|
107
23
|
*/
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
...(projectTech.frameworks || []),
|
|
117
|
-
...(projectTech.tools || [])
|
|
118
|
-
]
|
|
119
|
-
|
|
120
|
-
// Count matching skills
|
|
121
|
-
const matchingSkills = agent.skills.filter(skill => {
|
|
122
|
-
const skillLower = skill.toLowerCase()
|
|
123
|
-
return allTech.some(tech => tech.toLowerCase().includes(skillLower) ||
|
|
124
|
-
skillLower.includes(tech.toLowerCase()))
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
if (matchingSkills.length === 0) {
|
|
128
|
-
return 0.1 // No matching skills
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Score based on match ratio
|
|
132
|
-
const matchRatio = matchingSkills.length / Math.max(agent.skills.length, allTech.length)
|
|
133
|
-
return Math.min(matchRatio * 2, 1.0) // Boost for good matches
|
|
24
|
+
setHistoryPath(projectId) {
|
|
25
|
+
this.historyPath = path.join(
|
|
26
|
+
process.env.HOME,
|
|
27
|
+
'.prjct-cli',
|
|
28
|
+
'projects',
|
|
29
|
+
projectId,
|
|
30
|
+
'agent-history.jsonl'
|
|
31
|
+
)
|
|
134
32
|
}
|
|
135
33
|
|
|
136
34
|
/**
|
|
137
|
-
*
|
|
35
|
+
* Format agents for Claude
|
|
36
|
+
* ORCHESTRATION: Data formatting only
|
|
138
37
|
*/
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (history) {
|
|
146
|
-
// Success rate from history
|
|
147
|
-
return history.successRate || 0.5
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return 0.5 // Neutral - no history
|
|
38
|
+
formatAgentsForTemplate(agents) {
|
|
39
|
+
return agents.map(a => ({
|
|
40
|
+
name: a.name,
|
|
41
|
+
domain: a.domain || 'general',
|
|
42
|
+
hasContent: !!a.content
|
|
43
|
+
}))
|
|
151
44
|
}
|
|
152
45
|
|
|
153
46
|
/**
|
|
154
|
-
*
|
|
47
|
+
* Format task for Claude
|
|
48
|
+
* ORCHESTRATION: Data formatting only
|
|
155
49
|
*/
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
// Specialized agents are better for complex tasks
|
|
161
|
-
const isGeneric = !agent.skills || agent.skills.length === 0
|
|
162
|
-
|
|
163
|
-
if (taskComplexity === 'low' && isGeneric) {
|
|
164
|
-
return 0.8
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (taskComplexity === 'high' && !isGeneric) {
|
|
168
|
-
return 0.9
|
|
50
|
+
formatTaskForTemplate(task) {
|
|
51
|
+
return {
|
|
52
|
+
description: typeof task === 'string' ? task : task.description,
|
|
53
|
+
type: task.type || 'unknown'
|
|
169
54
|
}
|
|
170
|
-
|
|
171
|
-
return 0.5 // Neutral
|
|
172
55
|
}
|
|
173
56
|
|
|
174
57
|
/**
|
|
175
|
-
* Record agent
|
|
58
|
+
* Record agent usage
|
|
59
|
+
* ORCHESTRATION: File I/O only
|
|
176
60
|
*/
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
61
|
+
async recordUsage(agent, task) {
|
|
62
|
+
if (!this.historyPath) return
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const entry = JSON.stringify({
|
|
66
|
+
timestamp: new Date().toISOString(),
|
|
67
|
+
agent: agent.name || agent,
|
|
68
|
+
task: typeof task === 'string' ? task : task.description
|
|
69
|
+
}) + '\n'
|
|
70
|
+
|
|
71
|
+
await fs.appendFile(this.historyPath, entry)
|
|
72
|
+
} catch {
|
|
73
|
+
// Silent fail
|
|
188
74
|
}
|
|
189
|
-
history.successRate = history.successes / history.attempts
|
|
190
|
-
|
|
191
|
-
this.historyCache.set(cacheKey, history)
|
|
192
75
|
}
|
|
193
76
|
|
|
194
77
|
/**
|
|
195
|
-
*
|
|
78
|
+
* Load usage history
|
|
79
|
+
* ORCHESTRATION: File I/O only
|
|
196
80
|
*/
|
|
197
|
-
|
|
198
|
-
if (!
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
81
|
+
async loadHistory() {
|
|
82
|
+
if (!this.historyPath) return []
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const content = await fs.readFile(this.historyPath, 'utf-8')
|
|
86
|
+
return content
|
|
87
|
+
.split('\n')
|
|
88
|
+
.filter(Boolean)
|
|
89
|
+
.map(line => {
|
|
90
|
+
try {
|
|
91
|
+
return JSON.parse(line)
|
|
92
|
+
} catch {
|
|
93
|
+
return null
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
.filter(Boolean)
|
|
97
|
+
} catch {
|
|
98
|
+
return []
|
|
210
99
|
}
|
|
211
|
-
|
|
212
|
-
return reasons.join(', ')
|
|
213
100
|
}
|
|
214
101
|
}
|
|
215
102
|
|
|
216
103
|
module.exports = AgentMatcher
|
|
217
|
-
|
package/package.json
CHANGED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent-assignment
|
|
3
|
+
description: Assign the best agent for a task
|
|
4
|
+
allowed-tools: [Read]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Agent Assignment
|
|
8
|
+
|
|
9
|
+
Select the best agent for the given task based on semantic understanding.
|
|
10
|
+
|
|
11
|
+
## Input
|
|
12
|
+
|
|
13
|
+
- **Task**: {{task}}
|
|
14
|
+
- **Available Agents**: {{agents}}
|
|
15
|
+
- **Project Context**: {{context}}
|
|
16
|
+
|
|
17
|
+
## Instructions
|
|
18
|
+
|
|
19
|
+
1. **Understand the Task**
|
|
20
|
+
- What domain does this task belong to?
|
|
21
|
+
- What skills are required?
|
|
22
|
+
- What is the complexity level?
|
|
23
|
+
|
|
24
|
+
2. **Analyze Available Agents**
|
|
25
|
+
- Read each agent's expertise and domain
|
|
26
|
+
- Consider their skills and past success
|
|
27
|
+
- Match capabilities to task requirements
|
|
28
|
+
|
|
29
|
+
3. **Select Best Agent**
|
|
30
|
+
- Choose the agent with highest relevance
|
|
31
|
+
- If multiple agents fit, prefer the specialist over generalist
|
|
32
|
+
- If no good match, use 'generalist' or 'full-stack'
|
|
33
|
+
|
|
34
|
+
## Decision Criteria
|
|
35
|
+
|
|
36
|
+
- **Domain Match**: Does the agent's domain align with the task?
|
|
37
|
+
- **Skills Match**: Does the agent have the required skills?
|
|
38
|
+
- **Complexity Fit**: Is the agent appropriate for this complexity level?
|
|
39
|
+
|
|
40
|
+
## Output Format
|
|
41
|
+
|
|
42
|
+
Return JSON with your decision:
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"agent": "agent-name",
|
|
47
|
+
"confidence": 0.85,
|
|
48
|
+
"reason": "Brief explanation of why this agent was selected",
|
|
49
|
+
"domain": "detected domain of the task"
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Examples
|
|
54
|
+
|
|
55
|
+
**Task**: "Implement React login component with form validation"
|
|
56
|
+
**Decision**: `{ "agent": "frontend-specialist", "confidence": 0.95, "reason": "React component work requires frontend expertise", "domain": "frontend" }`
|
|
57
|
+
|
|
58
|
+
**Task**: "Fix database connection timeout issue"
|
|
59
|
+
**Decision**: `{ "agent": "backend-specialist", "confidence": 0.90, "reason": "Database issues require backend/infrastructure knowledge", "domain": "backend" }`
|
|
60
|
+
|
|
61
|
+
**Task**: "Write unit tests for user service"
|
|
62
|
+
**Decision**: `{ "agent": "qa-specialist", "confidence": 0.85, "reason": "Testing tasks benefit from QA expertise", "domain": "qa" }`
|
|
63
|
+
|
|
64
|
+
**Task**: "Update README documentation"
|
|
65
|
+
**Decision**: `{ "agent": "generalist", "confidence": 0.70, "reason": "Documentation is general task, no specialist needed", "domain": "docs" }`
|
|
66
|
+
|
|
67
|
+
## Guidelines
|
|
68
|
+
|
|
69
|
+
- Prefer specialists when task clearly fits their domain
|
|
70
|
+
- Use generalist only when no specialist matches
|
|
71
|
+
- Higher confidence = stronger match
|
|
72
|
+
- Always provide a reason for transparency
|