prjct-cli 0.8.8 → 0.9.2
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 +194 -0
- package/core/__tests__/agentic/agent-router.test.js +398 -0
- package/core/__tests__/agentic/context-filter.test.js +494 -0
- package/core/__tests__/domain/analyzer.test.js +324 -0
- package/core/__tests__/infrastructure/author-detector.test.js +103 -0
- package/core/__tests__/infrastructure/config-manager.test.js +454 -0
- package/core/__tests__/infrastructure/path-manager.test.js +412 -0
- package/core/__tests__/utils/jsonl-helper.test.js +387 -0
- package/core/agentic/agent-router.js +482 -0
- package/core/agentic/command-executor.js +70 -15
- package/core/agentic/context-filter.js +549 -0
- package/core/agentic/prompt-builder.js +48 -38
- package/core/command-registry.js +104 -164
- package/core/domain/agent-generator.js +55 -44
- package/core/domain/architecture-generator.js +561 -0
- package/core/domain/task-stack.js +496 -0
- package/package.json +2 -1
- package/templates/commands/analyze.md +10 -53
- package/templates/commands/bug.md +11 -70
- package/templates/commands/build.md +7 -37
- package/templates/commands/cleanup.md +9 -32
- package/templates/commands/dash.md +241 -0
- package/templates/commands/design.md +5 -28
- package/templates/commands/done.md +6 -20
- package/templates/commands/feature.md +11 -83
- package/templates/commands/help.md +9 -38
- package/templates/commands/idea.md +7 -28
- package/templates/commands/init.md +10 -89
- package/templates/commands/next.md +6 -26
- package/templates/commands/now.md +6 -26
- package/templates/commands/pause.md +18 -0
- package/templates/commands/progress.md +5 -50
- package/templates/commands/recap.md +5 -54
- package/templates/commands/resume.md +97 -0
- package/templates/commands/ship.md +13 -68
- package/templates/commands/status.md +7 -32
- package/templates/commands/sync.md +7 -24
- package/templates/commands/work.md +44 -0
- package/templates/commands/workflow.md +3 -25
- package/templates/planning-methodology.md +195 -0
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mandatory Agent Router
|
|
3
|
+
*
|
|
4
|
+
* CRITICAL: Ensures EVERY task is executed by a specialized agent
|
|
5
|
+
* No task can run without an assigned expert agent
|
|
6
|
+
*
|
|
7
|
+
* @version 1.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs').promises;
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const AgentGenerator = require('../domain/agent-generator');
|
|
13
|
+
|
|
14
|
+
class MandatoryAgentRouter {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.agentGenerator = new AgentGenerator();
|
|
17
|
+
this.agentCache = new Map();
|
|
18
|
+
this.usageLog = [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Main entry point - ALL tasks MUST go through here
|
|
23
|
+
* @throws {Error} If no agent can be assigned
|
|
24
|
+
*/
|
|
25
|
+
async executeTask(task, context, projectPath) {
|
|
26
|
+
// STEP 1: Analyze task to determine required expertise
|
|
27
|
+
const taskAnalysis = this.analyzeTask(task);
|
|
28
|
+
|
|
29
|
+
// STEP 2: Select or generate specialized agent (MANDATORY)
|
|
30
|
+
const agent = await this.assignAgent(taskAnalysis, context);
|
|
31
|
+
|
|
32
|
+
// STEP 3: Validate agent assignment
|
|
33
|
+
if (!agent || !agent.name) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`CRITICAL: No agent assigned for task "${task.description}".
|
|
36
|
+
System requires ALL tasks to use specialized agents.`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// STEP 4: Filter context for this specific agent
|
|
41
|
+
const filteredContext = await this.filterContextForAgent(
|
|
42
|
+
agent,
|
|
43
|
+
context,
|
|
44
|
+
taskAnalysis
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// STEP 5: Log agent usage for tracking
|
|
48
|
+
this.logAgentUsage(task, agent, filteredContext);
|
|
49
|
+
|
|
50
|
+
// STEP 6: Return agent with filtered context
|
|
51
|
+
return {
|
|
52
|
+
agent,
|
|
53
|
+
context: filteredContext,
|
|
54
|
+
taskAnalysis,
|
|
55
|
+
routing: {
|
|
56
|
+
reason: taskAnalysis.reason,
|
|
57
|
+
confidence: taskAnalysis.confidence,
|
|
58
|
+
alternativeAgents: taskAnalysis.alternatives
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Analyze task to determine what type of expertise is needed
|
|
65
|
+
*/
|
|
66
|
+
analyzeTask(task) {
|
|
67
|
+
const description = task.description?.toLowerCase() || '';
|
|
68
|
+
const type = task.type?.toLowerCase() || '';
|
|
69
|
+
|
|
70
|
+
// Keywords for different domains
|
|
71
|
+
const patterns = {
|
|
72
|
+
frontend: [
|
|
73
|
+
'component', 'ui', 'react', 'vue', 'angular', 'style',
|
|
74
|
+
'css', 'layout', 'responsive', 'user interface', 'frontend'
|
|
75
|
+
],
|
|
76
|
+
backend: [
|
|
77
|
+
'api', 'server', 'endpoint', 'route', 'middleware',
|
|
78
|
+
'auth', 'authentication', 'jwt', 'session', 'backend'
|
|
79
|
+
],
|
|
80
|
+
database: [
|
|
81
|
+
'database', 'query', 'migration', 'schema', 'model',
|
|
82
|
+
'sql', 'postgres', 'mysql', 'mongo', 'index'
|
|
83
|
+
],
|
|
84
|
+
devops: [
|
|
85
|
+
'deploy', 'docker', 'kubernetes', 'ci/cd', 'pipeline',
|
|
86
|
+
'build', 'ship', 'release', 'production'
|
|
87
|
+
],
|
|
88
|
+
qa: [
|
|
89
|
+
'test', 'bug', 'error', 'fix', 'debug', 'issue',
|
|
90
|
+
'quality', 'coverage', 'unit test', 'integration'
|
|
91
|
+
],
|
|
92
|
+
architecture: [
|
|
93
|
+
'design', 'architecture', 'pattern', 'structure',
|
|
94
|
+
'refactor', 'organize', 'plan', 'feature'
|
|
95
|
+
]
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Detect primary domain
|
|
99
|
+
let detectedDomain = 'generalist';
|
|
100
|
+
let confidence = 0;
|
|
101
|
+
let matchedKeywords = [];
|
|
102
|
+
|
|
103
|
+
for (const [domain, keywords] of Object.entries(patterns)) {
|
|
104
|
+
const matches = keywords.filter(keyword =>
|
|
105
|
+
description.includes(keyword) || type.includes(keyword)
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
if (matches.length > confidence) {
|
|
109
|
+
confidence = matches.length;
|
|
110
|
+
detectedDomain = domain;
|
|
111
|
+
matchedKeywords = matches;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Detect technology stack
|
|
116
|
+
const techStack = this.detectTechnology(task, description);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
domain: detectedDomain,
|
|
120
|
+
confidence: confidence > 0 ? (confidence / 3) : 0.3, // confidence score
|
|
121
|
+
matchedKeywords,
|
|
122
|
+
techStack,
|
|
123
|
+
reason: `Detected ${detectedDomain} task based on: ${matchedKeywords.join(', ')}`,
|
|
124
|
+
alternatives: this.getSimilarDomains(detectedDomain)
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Detect specific technologies mentioned or implied
|
|
130
|
+
*/
|
|
131
|
+
detectTechnology(task, description) {
|
|
132
|
+
const technologies = {
|
|
133
|
+
languages: [],
|
|
134
|
+
frameworks: [],
|
|
135
|
+
databases: [],
|
|
136
|
+
tools: []
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Language detection
|
|
140
|
+
const languages = [
|
|
141
|
+
'javascript', 'typescript', 'python', 'ruby', 'go',
|
|
142
|
+
'rust', 'java', 'csharp', 'php', 'elixir', 'swift'
|
|
143
|
+
];
|
|
144
|
+
|
|
145
|
+
const frameworks = [
|
|
146
|
+
'react', 'vue', 'angular', 'express', 'django', 'rails',
|
|
147
|
+
'spring', 'laravel', 'phoenix', 'gin', 'fastapi', 'nextjs'
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
const databases = [
|
|
151
|
+
'postgres', 'mysql', 'mongodb', 'redis', 'elasticsearch',
|
|
152
|
+
'dynamodb', 'firebase', 'supabase', 'sqlite'
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
// Check for each technology
|
|
156
|
+
languages.forEach(lang => {
|
|
157
|
+
if (description.includes(lang)) {
|
|
158
|
+
technologies.languages.push(lang);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
frameworks.forEach(fw => {
|
|
163
|
+
if (description.includes(fw)) {
|
|
164
|
+
technologies.frameworks.push(fw);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
databases.forEach(db => {
|
|
169
|
+
if (description.includes(db)) {
|
|
170
|
+
technologies.databases.push(db);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
return technologies;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Assign the best agent for the task
|
|
179
|
+
* Creates a new one if needed
|
|
180
|
+
*/
|
|
181
|
+
async assignAgent(taskAnalysis, context) {
|
|
182
|
+
const { domain, techStack } = taskAnalysis;
|
|
183
|
+
|
|
184
|
+
// Check cache first
|
|
185
|
+
const cacheKey = `${domain}-${techStack.languages.join('-')}`;
|
|
186
|
+
if (this.agentCache.has(cacheKey)) {
|
|
187
|
+
return this.agentCache.get(cacheKey);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Generate specialized agent based on detection
|
|
191
|
+
const agent = await this.generateSpecializedAgent(domain, techStack, context);
|
|
192
|
+
|
|
193
|
+
// Cache for reuse
|
|
194
|
+
this.agentCache.set(cacheKey, agent);
|
|
195
|
+
|
|
196
|
+
return agent;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Generate a specialized agent for the detected domain and tech
|
|
201
|
+
*/
|
|
202
|
+
async generateSpecializedAgent(domain, techStack, context) {
|
|
203
|
+
// Map domain to agent type
|
|
204
|
+
const agentTypes = {
|
|
205
|
+
frontend: 'frontend-specialist',
|
|
206
|
+
backend: 'backend-specialist',
|
|
207
|
+
database: 'database-specialist',
|
|
208
|
+
devops: 'devops-specialist',
|
|
209
|
+
qa: 'qa-specialist',
|
|
210
|
+
architecture: 'architect',
|
|
211
|
+
generalist: 'full-stack'
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const agentType = agentTypes[domain] || 'full-stack';
|
|
215
|
+
|
|
216
|
+
// Generate with detected technologies
|
|
217
|
+
const config = {
|
|
218
|
+
domain,
|
|
219
|
+
techStack,
|
|
220
|
+
projectContext: context.projectSummary || '',
|
|
221
|
+
bestPractices: await this.getBestPractices(domain, techStack)
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
return this.agentGenerator.generateDynamicAgent(agentType, config);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Get best practices for the domain and tech stack
|
|
229
|
+
*/
|
|
230
|
+
async getBestPractices(domain, techStack) {
|
|
231
|
+
const practices = [];
|
|
232
|
+
|
|
233
|
+
// Domain-specific best practices
|
|
234
|
+
const domainPractices = {
|
|
235
|
+
frontend: [
|
|
236
|
+
'Component composition over inheritance',
|
|
237
|
+
'State management patterns',
|
|
238
|
+
'Responsive design principles',
|
|
239
|
+
'Accessibility standards',
|
|
240
|
+
'Performance optimization (lazy loading, memoization)'
|
|
241
|
+
],
|
|
242
|
+
backend: [
|
|
243
|
+
'RESTful API design principles',
|
|
244
|
+
'Authentication and authorization patterns',
|
|
245
|
+
'Error handling and logging',
|
|
246
|
+
'Rate limiting and caching',
|
|
247
|
+
'Database connection pooling'
|
|
248
|
+
],
|
|
249
|
+
database: [
|
|
250
|
+
'Normalization principles',
|
|
251
|
+
'Index optimization',
|
|
252
|
+
'Query performance tuning',
|
|
253
|
+
'Transaction management',
|
|
254
|
+
'Backup and recovery strategies'
|
|
255
|
+
],
|
|
256
|
+
devops: [
|
|
257
|
+
'CI/CD pipeline best practices',
|
|
258
|
+
'Container orchestration',
|
|
259
|
+
'Infrastructure as Code',
|
|
260
|
+
'Monitoring and alerting',
|
|
261
|
+
'Security scanning'
|
|
262
|
+
],
|
|
263
|
+
qa: [
|
|
264
|
+
'Test pyramid (unit, integration, e2e)',
|
|
265
|
+
'Test coverage standards',
|
|
266
|
+
'Mocking and stubbing',
|
|
267
|
+
'Performance testing',
|
|
268
|
+
'Security testing'
|
|
269
|
+
]
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
practices.push(...(domainPractices[domain] || []));
|
|
273
|
+
|
|
274
|
+
// Technology-specific practices
|
|
275
|
+
if (techStack.languages.includes('javascript') || techStack.languages.includes('typescript')) {
|
|
276
|
+
practices.push('ES6+ features', 'Async/await patterns', 'Module system');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (techStack.frameworks.includes('react')) {
|
|
280
|
+
practices.push('Hooks patterns', 'Context API', 'Component lifecycle');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (techStack.languages.includes('ruby')) {
|
|
284
|
+
practices.push('Ruby idioms', 'Metaprogramming carefully', 'Convention over configuration');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (techStack.languages.includes('go')) {
|
|
288
|
+
practices.push('Goroutines and channels', 'Error handling patterns', 'Interface design');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return practices;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Filter context to only what's relevant for this agent
|
|
296
|
+
*/
|
|
297
|
+
async filterContextForAgent(agent, fullContext, taskAnalysis) {
|
|
298
|
+
const { domain } = taskAnalysis;
|
|
299
|
+
|
|
300
|
+
// Define what each agent type should see
|
|
301
|
+
const contextPatterns = {
|
|
302
|
+
frontend: {
|
|
303
|
+
include: ['components', 'views', 'styles', 'pages', 'layouts'],
|
|
304
|
+
exclude: ['node_modules', 'dist', 'build', 'migrations'],
|
|
305
|
+
extensions: ['.jsx', '.tsx', '.vue', '.css', '.scss', '.styled.js']
|
|
306
|
+
},
|
|
307
|
+
backend: {
|
|
308
|
+
include: ['routes', 'controllers', 'services', 'middleware', 'api'],
|
|
309
|
+
exclude: ['node_modules', 'dist', 'public', 'styles'],
|
|
310
|
+
extensions: ['.js', '.ts', '.py', '.rb', '.go', '.java']
|
|
311
|
+
},
|
|
312
|
+
database: {
|
|
313
|
+
include: ['models', 'migrations', 'schemas', 'seeds', 'queries'],
|
|
314
|
+
exclude: ['node_modules', 'public', 'styles', 'components'],
|
|
315
|
+
extensions: ['.sql', '.js', '.ts', '.rb', '.py']
|
|
316
|
+
},
|
|
317
|
+
devops: {
|
|
318
|
+
include: ['.github', '.gitlab', 'docker', 'k8s', 'terraform'],
|
|
319
|
+
exclude: ['node_modules', 'src', 'public'],
|
|
320
|
+
extensions: ['.yml', '.yaml', '.dockerfile', '.sh', '.tf']
|
|
321
|
+
},
|
|
322
|
+
qa: {
|
|
323
|
+
include: ['tests', 'spec', '__tests__', 'test'],
|
|
324
|
+
exclude: ['node_modules', 'dist', 'build'],
|
|
325
|
+
extensions: ['.test.js', '.spec.js', '.test.ts', '.spec.ts']
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
const pattern = contextPatterns[domain] || {
|
|
330
|
+
include: [],
|
|
331
|
+
exclude: ['node_modules', 'dist', 'build'],
|
|
332
|
+
extensions: []
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
// Filter the context based on patterns
|
|
336
|
+
const filtered = {
|
|
337
|
+
...fullContext,
|
|
338
|
+
files: this.filterFiles(fullContext.files || [], pattern),
|
|
339
|
+
relevantOnly: true,
|
|
340
|
+
filterApplied: domain
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
return filtered;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Filter files based on patterns
|
|
348
|
+
*/
|
|
349
|
+
filterFiles(files, pattern) {
|
|
350
|
+
return files.filter(file => {
|
|
351
|
+
// Check if file should be excluded
|
|
352
|
+
for (const exclude of pattern.exclude) {
|
|
353
|
+
if (file.includes(exclude)) return false;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Check if file matches include patterns
|
|
357
|
+
if (pattern.include.length > 0) {
|
|
358
|
+
let matches = false;
|
|
359
|
+
for (const include of pattern.include) {
|
|
360
|
+
if (file.includes(include)) {
|
|
361
|
+
matches = true;
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (!matches) return false;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Check extensions if specified
|
|
369
|
+
if (pattern.extensions.length > 0) {
|
|
370
|
+
let hasValidExtension = false;
|
|
371
|
+
for (const ext of pattern.extensions) {
|
|
372
|
+
if (file.endsWith(ext)) {
|
|
373
|
+
hasValidExtension = true;
|
|
374
|
+
break;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (!hasValidExtension) return false;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return true;
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Log agent usage for metrics and optimization
|
|
386
|
+
*/
|
|
387
|
+
logAgentUsage(task, agent, context) {
|
|
388
|
+
const usage = {
|
|
389
|
+
timestamp: new Date().toISOString(),
|
|
390
|
+
task: task.description,
|
|
391
|
+
agent: agent.name,
|
|
392
|
+
domain: agent.domain || 'unknown',
|
|
393
|
+
contextSize: context.files?.length || 0,
|
|
394
|
+
contextReduction: this.calculateContextReduction(context),
|
|
395
|
+
confidence: agent.confidence || 1.0
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
this.usageLog.push(usage);
|
|
399
|
+
|
|
400
|
+
// Also append to a log file for persistence
|
|
401
|
+
this.appendToLogFile(usage);
|
|
402
|
+
|
|
403
|
+
return usage;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Calculate how much context was reduced
|
|
408
|
+
*/
|
|
409
|
+
calculateContextReduction(filteredContext) {
|
|
410
|
+
// This would compare against full context
|
|
411
|
+
// For now, estimate based on filtering
|
|
412
|
+
if (filteredContext.relevantOnly) {
|
|
413
|
+
return '70-90%'; // Typical reduction when filtering
|
|
414
|
+
}
|
|
415
|
+
return '0%';
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Append usage to log file
|
|
420
|
+
*/
|
|
421
|
+
async appendToLogFile(usage) {
|
|
422
|
+
try {
|
|
423
|
+
const logPath = path.join(
|
|
424
|
+
process.env.HOME,
|
|
425
|
+
'.prjct-cli',
|
|
426
|
+
'agent-usage.jsonl'
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
const logEntry = JSON.stringify(usage) + '\n';
|
|
430
|
+
await fs.appendFile(logPath, logEntry);
|
|
431
|
+
} catch (error) {
|
|
432
|
+
// Log errors silently, don't break execution
|
|
433
|
+
console.error('Failed to log agent usage:', error.message);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Get similar domains for fallback
|
|
439
|
+
*/
|
|
440
|
+
getSimilarDomains(domain) {
|
|
441
|
+
const similarities = {
|
|
442
|
+
frontend: ['fullstack', 'ui/ux'],
|
|
443
|
+
backend: ['fullstack', 'api', 'services'],
|
|
444
|
+
database: ['backend', 'data'],
|
|
445
|
+
devops: ['infrastructure', 'platform'],
|
|
446
|
+
qa: ['testing', 'quality'],
|
|
447
|
+
architecture: ['design', 'planning']
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
return similarities[domain] || ['generalist'];
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Get usage statistics
|
|
455
|
+
*/
|
|
456
|
+
getUsageStats() {
|
|
457
|
+
const stats = {
|
|
458
|
+
totalTasks: this.usageLog.length,
|
|
459
|
+
byAgent: {},
|
|
460
|
+
avgContextReduction: '0%',
|
|
461
|
+
mostUsedAgent: null
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
// Calculate stats from usage log
|
|
465
|
+
this.usageLog.forEach(log => {
|
|
466
|
+
stats.byAgent[log.agent] = (stats.byAgent[log.agent] || 0) + 1;
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
// Find most used agent
|
|
470
|
+
let maxUsage = 0;
|
|
471
|
+
for (const [agent, count] of Object.entries(stats.byAgent)) {
|
|
472
|
+
if (count > maxUsage) {
|
|
473
|
+
maxUsage = count;
|
|
474
|
+
stats.mostUsedAgent = agent;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return stats;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
module.exports = MandatoryAgentRouter;
|
|
@@ -1,45 +1,92 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Command Executor
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* WITH MANDATORY AGENT ASSIGNMENT
|
|
4
|
+
* Every task MUST use a specialized agent
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const templateLoader = require('./template-loader')
|
|
8
8
|
const contextBuilder = require('./context-builder')
|
|
9
9
|
const promptBuilder = require('./prompt-builder')
|
|
10
10
|
const toolRegistry = require('./tool-registry')
|
|
11
|
+
const MandatoryAgentRouter = require('./agent-router')
|
|
12
|
+
const ContextFilter = require('./context-filter')
|
|
11
13
|
|
|
12
14
|
class CommandExecutor {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.agentRouter = new MandatoryAgentRouter()
|
|
17
|
+
this.contextFilter = new ContextFilter()
|
|
18
|
+
}
|
|
19
|
+
|
|
13
20
|
/**
|
|
14
|
-
* Execute
|
|
15
|
-
* @param {string} commandName - Command name (e.g., 'now', 'done')
|
|
16
|
-
* @param {Object} params - Command parameters
|
|
17
|
-
* @param {string} projectPath - Project path
|
|
18
|
-
* @returns {Promise<Object>} Execution result
|
|
21
|
+
* Execute command with MANDATORY agent assignment
|
|
19
22
|
*/
|
|
20
23
|
async execute(commandName, params, projectPath) {
|
|
21
24
|
try {
|
|
22
25
|
// 1. Load template
|
|
23
26
|
const template = await templateLoader.load(commandName)
|
|
24
27
|
|
|
25
|
-
// 2. Build context
|
|
26
|
-
const
|
|
28
|
+
// 2. Build FULL context (before filtering)
|
|
29
|
+
const fullContext = await contextBuilder.build(projectPath, params)
|
|
30
|
+
|
|
31
|
+
// 3. Check if command requires agent
|
|
32
|
+
const requiresAgent = template.metadata?.['required-agent'] ||
|
|
33
|
+
this.isTaskCommand(commandName)
|
|
34
|
+
|
|
35
|
+
let context = fullContext
|
|
36
|
+
let assignedAgent = null
|
|
37
|
+
|
|
38
|
+
if (requiresAgent) {
|
|
39
|
+
// 4. MANDATORY: Assign specialized agent
|
|
40
|
+
const task = {
|
|
41
|
+
description: params.task || params.description || commandName,
|
|
42
|
+
type: commandName
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const agentAssignment = await this.agentRouter.executeTask(
|
|
46
|
+
task,
|
|
47
|
+
fullContext,
|
|
48
|
+
projectPath
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
assignedAgent = agentAssignment.agent
|
|
52
|
+
|
|
53
|
+
// 5. Filter context for this specific agent (70-90% reduction)
|
|
54
|
+
const filtered = await this.contextFilter.filterForAgent(
|
|
55
|
+
assignedAgent,
|
|
56
|
+
task,
|
|
57
|
+
projectPath,
|
|
58
|
+
fullContext
|
|
59
|
+
)
|
|
27
60
|
|
|
28
|
-
|
|
61
|
+
context = {
|
|
62
|
+
...filtered,
|
|
63
|
+
agent: assignedAgent,
|
|
64
|
+
originalSize: fullContext.files?.length || 0,
|
|
65
|
+
filteredSize: filtered.files?.length || 0,
|
|
66
|
+
reduction: filtered.metrics?.reductionPercent || 0
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 6. Load state with filtered context
|
|
29
71
|
const state = await contextBuilder.loadState(context)
|
|
30
72
|
|
|
31
|
-
//
|
|
32
|
-
const prompt = promptBuilder.build(template, context, state)
|
|
73
|
+
// 7. Build prompt with agent assignment
|
|
74
|
+
const prompt = promptBuilder.build(template, context, state, assignedAgent)
|
|
75
|
+
|
|
76
|
+
// 8. Log agent usage
|
|
77
|
+
if (assignedAgent) {
|
|
78
|
+
console.log(`🤖 Task assigned to: ${assignedAgent.name}`)
|
|
79
|
+
console.log(`📉 Context reduced by: ${context.reduction}%`)
|
|
80
|
+
}
|
|
33
81
|
|
|
34
|
-
// 5. Execute (in real implementation, this would call Claude)
|
|
35
|
-
// For now, we return structured data that Claude can work with
|
|
36
82
|
return {
|
|
37
83
|
success: true,
|
|
38
84
|
template,
|
|
39
85
|
context,
|
|
40
86
|
state,
|
|
41
87
|
prompt,
|
|
42
|
-
|
|
88
|
+
assignedAgent,
|
|
89
|
+
contextReduction: context.reduction
|
|
43
90
|
}
|
|
44
91
|
} catch (error) {
|
|
45
92
|
return {
|
|
@@ -49,6 +96,14 @@ class CommandExecutor {
|
|
|
49
96
|
}
|
|
50
97
|
}
|
|
51
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Check if command is task-related
|
|
101
|
+
*/
|
|
102
|
+
isTaskCommand(commandName) {
|
|
103
|
+
const taskCommands = ['work', 'now', 'build', 'feature', 'bug', 'done']
|
|
104
|
+
return taskCommands.includes(commandName)
|
|
105
|
+
}
|
|
106
|
+
|
|
52
107
|
/**
|
|
53
108
|
* Execute tool with permission check
|
|
54
109
|
* @param {string} toolName - Tool name
|