prjct-cli 0.9.2 → 0.10.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/CHANGELOG.md +111 -0
- package/core/__tests__/agentic/context-filter.test.js +2 -2
- package/core/__tests__/agentic/prompt-builder.test.js +39 -47
- package/core/__tests__/domain/agent-generator.test.js +29 -36
- package/core/__tests__/domain/agent-loader.test.js +179 -0
- package/core/agentic/agent-router.js +253 -186
- package/core/agentic/command-executor.js +61 -13
- package/core/agentic/context-filter.js +83 -83
- package/core/agentic/prompt-builder.js +51 -1
- package/core/commands.js +85 -59
- package/core/domain/agent-generator.js +77 -46
- package/core/domain/agent-loader.js +183 -0
- package/core/domain/agent-matcher.js +217 -0
- package/core/domain/agent-validator.js +217 -0
- package/core/domain/context-estimator.js +175 -0
- package/core/domain/product-standards.js +92 -0
- package/core/domain/smart-cache.js +157 -0
- package/core/domain/task-analyzer.js +353 -0
- package/core/domain/tech-detector.js +365 -0
- package/package.json +2 -2
|
@@ -10,11 +10,13 @@ const promptBuilder = require('./prompt-builder')
|
|
|
10
10
|
const toolRegistry = require('./tool-registry')
|
|
11
11
|
const MandatoryAgentRouter = require('./agent-router')
|
|
12
12
|
const ContextFilter = require('./context-filter')
|
|
13
|
+
const ContextEstimator = require('../domain/context-estimator')
|
|
13
14
|
|
|
14
15
|
class CommandExecutor {
|
|
15
16
|
constructor() {
|
|
16
17
|
this.agentRouter = new MandatoryAgentRouter()
|
|
17
18
|
this.contextFilter = new ContextFilter()
|
|
19
|
+
this.contextEstimator = null
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
/**
|
|
@@ -25,45 +27,74 @@ class CommandExecutor {
|
|
|
25
27
|
// 1. Load template
|
|
26
28
|
const template = await templateLoader.load(commandName)
|
|
27
29
|
|
|
28
|
-
// 2. Build
|
|
29
|
-
const
|
|
30
|
+
// 2. Build METADATA context only (lazy loading - no file reads yet)
|
|
31
|
+
const metadataContext = await contextBuilder.build(projectPath, params)
|
|
30
32
|
|
|
31
|
-
// 3.
|
|
32
|
-
const requiresAgent = template.metadata?.['required-agent']
|
|
33
|
-
|
|
33
|
+
// 3. CRITICAL: Force agent assignment for ALL task-related commands
|
|
34
|
+
const requiresAgent = template.metadata?.['required-agent'] !== false &&
|
|
35
|
+
(template.metadata?.['required-agent'] === true ||
|
|
36
|
+
this.isTaskCommand(commandName) ||
|
|
37
|
+
this.shouldUseAgent(commandName))
|
|
34
38
|
|
|
35
|
-
let context =
|
|
39
|
+
let context = metadataContext
|
|
36
40
|
let assignedAgent = null
|
|
37
41
|
|
|
42
|
+
// MANDATORY: Assign specialized agent for task commands
|
|
38
43
|
if (requiresAgent) {
|
|
39
|
-
// 4.
|
|
44
|
+
// 4. Create task object for analysis
|
|
40
45
|
const task = {
|
|
41
46
|
description: params.task || params.description || commandName,
|
|
42
47
|
type: commandName
|
|
43
48
|
}
|
|
44
49
|
|
|
50
|
+
// 5. LAZY CONTEXT: Analyze task FIRST, then estimate files needed
|
|
51
|
+
// This avoids reading all files before knowing what we need
|
|
45
52
|
const agentAssignment = await this.agentRouter.executeTask(
|
|
46
53
|
task,
|
|
47
|
-
|
|
54
|
+
metadataContext, // Only metadata, no files yet
|
|
48
55
|
projectPath
|
|
49
56
|
)
|
|
50
57
|
|
|
51
58
|
assignedAgent = agentAssignment.agent
|
|
59
|
+
const taskAnalysis = agentAssignment.taskAnalysis
|
|
60
|
+
|
|
61
|
+
// Validate agent was assigned
|
|
62
|
+
if (!assignedAgent || !assignedAgent.name) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`CRITICAL: Failed to assign agent for command "${commandName}". ` +
|
|
65
|
+
`System requires ALL task commands to use specialized agents.`
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 6. PRE-FILTER: Estimate which files are needed BEFORE reading
|
|
70
|
+
if (!this.contextEstimator) {
|
|
71
|
+
this.contextEstimator = new ContextEstimator()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const estimatedFiles = await this.contextEstimator.estimateFiles(
|
|
75
|
+
taskAnalysis,
|
|
76
|
+
projectPath
|
|
77
|
+
)
|
|
52
78
|
|
|
53
|
-
//
|
|
79
|
+
// 7. Build context ONLY with estimated files (lazy loading)
|
|
54
80
|
const filtered = await this.contextFilter.filterForAgent(
|
|
55
81
|
assignedAgent,
|
|
56
82
|
task,
|
|
57
83
|
projectPath,
|
|
58
|
-
|
|
84
|
+
{
|
|
85
|
+
...metadataContext,
|
|
86
|
+
estimatedFiles, // Pre-filtered file list
|
|
87
|
+
fileCount: estimatedFiles.length
|
|
88
|
+
}
|
|
59
89
|
)
|
|
60
90
|
|
|
61
91
|
context = {
|
|
62
92
|
...filtered,
|
|
63
93
|
agent: assignedAgent,
|
|
64
|
-
originalSize:
|
|
94
|
+
originalSize: estimatedFiles.length, // Estimated, not actual full size
|
|
65
95
|
filteredSize: filtered.files?.length || 0,
|
|
66
|
-
reduction: filtered.metrics?.reductionPercent || 0
|
|
96
|
+
reduction: filtered.metrics?.reductionPercent || 0,
|
|
97
|
+
lazyLoaded: true // Flag indicating lazy loading was used
|
|
67
98
|
}
|
|
68
99
|
}
|
|
69
100
|
|
|
@@ -100,10 +131,27 @@ class CommandExecutor {
|
|
|
100
131
|
* Check if command is task-related
|
|
101
132
|
*/
|
|
102
133
|
isTaskCommand(commandName) {
|
|
103
|
-
const taskCommands = [
|
|
134
|
+
const taskCommands = [
|
|
135
|
+
'work', 'now', 'build', 'feature', 'bug', 'done',
|
|
136
|
+
'task', 'design', 'cleanup', 'fix', 'test'
|
|
137
|
+
]
|
|
104
138
|
return taskCommands.includes(commandName)
|
|
105
139
|
}
|
|
106
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Determine if command should use an agent
|
|
143
|
+
* Expanded list of commands that benefit from agent specialization
|
|
144
|
+
*/
|
|
145
|
+
shouldUseAgent(commandName) {
|
|
146
|
+
// Commands that should ALWAYS use agents
|
|
147
|
+
const agentCommands = [
|
|
148
|
+
'work', 'now', 'build', 'feature', 'bug', 'done',
|
|
149
|
+
'task', 'design', 'cleanup', 'fix', 'test',
|
|
150
|
+
'sync', 'analyze' // These analyze/modify code, need specialization
|
|
151
|
+
]
|
|
152
|
+
return agentCommands.includes(commandName)
|
|
153
|
+
}
|
|
154
|
+
|
|
107
155
|
/**
|
|
108
156
|
* Execute tool with permission check
|
|
109
157
|
* @param {string} toolName - Tool name
|
|
@@ -25,10 +25,31 @@ class ContextFilter {
|
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Main entry point - filters context based on agent and task
|
|
28
|
+
* IMPROVED: Supports pre-estimated files for lazy loading
|
|
28
29
|
*/
|
|
29
30
|
async filterForAgent(agent, task, projectPath, fullContext = {}) {
|
|
30
31
|
const startTime = Date.now();
|
|
31
32
|
|
|
33
|
+
// If files were pre-estimated (lazy loading), use them
|
|
34
|
+
if (fullContext.estimatedFiles && fullContext.estimatedFiles.length > 0) {
|
|
35
|
+
const filteredFiles = fullContext.estimatedFiles;
|
|
36
|
+
|
|
37
|
+
const metrics = this.calculateMetrics(
|
|
38
|
+
fullContext.fileCount || filteredFiles.length,
|
|
39
|
+
filteredFiles.length,
|
|
40
|
+
startTime
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
files: filteredFiles,
|
|
45
|
+
patterns: { preEstimated: true },
|
|
46
|
+
metrics,
|
|
47
|
+
agent: agent.name,
|
|
48
|
+
filtered: true
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Fallback to traditional filtering if no pre-estimation
|
|
32
53
|
// Determine what files this agent needs
|
|
33
54
|
const relevantPatterns = await this.determineRelevantPatterns(
|
|
34
55
|
agent,
|
|
@@ -268,93 +289,29 @@ class ContextFilter {
|
|
|
268
289
|
|
|
269
290
|
/**
|
|
270
291
|
* Detect technologies used in the project
|
|
292
|
+
* NOW USES TechDetector - NO HARDCODING
|
|
271
293
|
*/
|
|
272
294
|
async detectProjectTechnologies(projectPath) {
|
|
273
|
-
const detected = new Set();
|
|
274
|
-
|
|
275
295
|
try {
|
|
276
|
-
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
if (deps.express) detected.add('express');
|
|
292
|
-
if (deps.next) detected.add('nextjs');
|
|
293
|
-
if (deps.fastify) detected.add('fastify');
|
|
294
|
-
|
|
295
|
-
// Language
|
|
296
|
-
if (deps.typescript) detected.add('typescript');
|
|
297
|
-
else detected.add('javascript');
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Check Gemfile for Ruby projects
|
|
301
|
-
const gemfilePath = path.join(projectPath, 'Gemfile');
|
|
302
|
-
if (await this.fileExists(gemfilePath)) {
|
|
303
|
-
detected.add('ruby');
|
|
304
|
-
const gemfile = await fs.readFile(gemfilePath, 'utf8');
|
|
305
|
-
if (gemfile.includes('rails')) detected.add('rails');
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// Check requirements.txt or setup.py for Python
|
|
309
|
-
const requirementsPath = path.join(projectPath, 'requirements.txt');
|
|
310
|
-
const setupPyPath = path.join(projectPath, 'setup.py');
|
|
311
|
-
if (await this.fileExists(requirementsPath) || await this.fileExists(setupPyPath)) {
|
|
312
|
-
detected.add('python');
|
|
313
|
-
|
|
314
|
-
if (await this.fileExists(requirementsPath)) {
|
|
315
|
-
const requirements = await fs.readFile(requirementsPath, 'utf8');
|
|
316
|
-
if (requirements.includes('django')) detected.add('django');
|
|
317
|
-
if (requirements.includes('fastapi')) detected.add('fastapi');
|
|
318
|
-
if (requirements.includes('flask')) detected.add('flask');
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// Check go.mod for Go projects
|
|
323
|
-
const goModPath = path.join(projectPath, 'go.mod');
|
|
324
|
-
if (await this.fileExists(goModPath)) {
|
|
325
|
-
detected.add('go');
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// Check Cargo.toml for Rust
|
|
329
|
-
const cargoPath = path.join(projectPath, 'Cargo.toml');
|
|
330
|
-
if (await this.fileExists(cargoPath)) {
|
|
331
|
-
detected.add('rust');
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// Check mix.exs for Elixir
|
|
335
|
-
const mixPath = path.join(projectPath, 'mix.exs');
|
|
336
|
-
if (await this.fileExists(mixPath)) {
|
|
337
|
-
detected.add('elixir');
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// Check for Java/Maven/Gradle
|
|
341
|
-
const pomPath = path.join(projectPath, 'pom.xml');
|
|
342
|
-
const gradlePath = path.join(projectPath, 'build.gradle');
|
|
343
|
-
if (await this.fileExists(pomPath) || await this.fileExists(gradlePath)) {
|
|
344
|
-
detected.add('java');
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// Check composer.json for PHP
|
|
348
|
-
const composerPath = path.join(projectPath, 'composer.json');
|
|
349
|
-
if (await this.fileExists(composerPath)) {
|
|
350
|
-
detected.add('php');
|
|
351
|
-
}
|
|
352
|
-
|
|
296
|
+
const TechDetector = require('../domain/tech-detector');
|
|
297
|
+
const detector = new TechDetector(projectPath);
|
|
298
|
+
const tech = await detector.detectAll();
|
|
299
|
+
|
|
300
|
+
// Convert to array of all detected technologies
|
|
301
|
+
const all = [
|
|
302
|
+
...tech.languages,
|
|
303
|
+
...tech.frameworks,
|
|
304
|
+
...tech.tools,
|
|
305
|
+
...tech.databases,
|
|
306
|
+
...tech.buildTools,
|
|
307
|
+
...tech.testFrameworks
|
|
308
|
+
];
|
|
309
|
+
|
|
310
|
+
return Array.from(new Set(all)); // Remove duplicates
|
|
353
311
|
} catch (error) {
|
|
354
312
|
console.error('Error detecting technologies:', error.message);
|
|
313
|
+
return [];
|
|
355
314
|
}
|
|
356
|
-
|
|
357
|
-
return Array.from(detected);
|
|
358
315
|
}
|
|
359
316
|
|
|
360
317
|
/**
|
|
@@ -463,13 +420,16 @@ class ContextFilter {
|
|
|
463
420
|
const uniqueFiles = [...new Set(files)].sort();
|
|
464
421
|
|
|
465
422
|
// Limit to reasonable number
|
|
466
|
-
const maxFiles =
|
|
423
|
+
const maxFiles = 300;
|
|
467
424
|
if (uniqueFiles.length > maxFiles) {
|
|
468
425
|
console.log(`Limiting context to ${maxFiles} most relevant files`);
|
|
469
426
|
return uniqueFiles.slice(0, maxFiles);
|
|
470
427
|
}
|
|
471
428
|
|
|
472
|
-
|
|
429
|
+
// Expand context with related files
|
|
430
|
+
const expandedFiles = await this.expandContext(uniqueFiles);
|
|
431
|
+
|
|
432
|
+
return expandedFiles.slice(0, maxFiles);
|
|
473
433
|
|
|
474
434
|
} catch (error) {
|
|
475
435
|
console.error('Error loading files:', error.message);
|
|
@@ -534,6 +494,46 @@ class ContextFilter {
|
|
|
534
494
|
}
|
|
535
495
|
}
|
|
536
496
|
|
|
497
|
+
/**
|
|
498
|
+
* Expand context with related files (tests, styles, etc.)
|
|
499
|
+
*/
|
|
500
|
+
async expandContext(files) {
|
|
501
|
+
const expanded = new Set(files);
|
|
502
|
+
|
|
503
|
+
for (const file of files) {
|
|
504
|
+
const ext = path.extname(file);
|
|
505
|
+
const basename = path.basename(file, ext);
|
|
506
|
+
const dirname = path.dirname(file);
|
|
507
|
+
|
|
508
|
+
// 1. Look for test files
|
|
509
|
+
const testPatterns = [
|
|
510
|
+
path.join(dirname, `${basename}.test${ext}`),
|
|
511
|
+
path.join(dirname, `${basename}.spec${ext}`),
|
|
512
|
+
path.join(dirname, '__tests__', `${basename}.test${ext}`),
|
|
513
|
+
path.join(dirname, 'tests', `${basename}.test${ext}`)
|
|
514
|
+
];
|
|
515
|
+
|
|
516
|
+
// 2. Look for style files (for UI components)
|
|
517
|
+
const stylePatterns = [
|
|
518
|
+
path.join(dirname, `${basename}.css`),
|
|
519
|
+
path.join(dirname, `${basename}.scss`),
|
|
520
|
+
path.join(dirname, `${basename}.module.css`),
|
|
521
|
+
path.join(dirname, `${basename}.module.scss`)
|
|
522
|
+
];
|
|
523
|
+
|
|
524
|
+
// Check if these related files exist
|
|
525
|
+
const potentialFiles = [...testPatterns, ...stylePatterns];
|
|
526
|
+
|
|
527
|
+
for (const potential of potentialFiles) {
|
|
528
|
+
if (!expanded.has(potential) && await this.fileExists(potential)) {
|
|
529
|
+
expanded.add(potential);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return Array.from(expanded).sort();
|
|
535
|
+
}
|
|
536
|
+
|
|
537
537
|
/**
|
|
538
538
|
* Get filter statistics
|
|
539
539
|
*/
|
|
@@ -7,13 +7,45 @@
|
|
|
7
7
|
class PromptBuilder {
|
|
8
8
|
/**
|
|
9
9
|
* Build concise prompt - only essentials
|
|
10
|
+
* CRITICAL: Includes full agent content if agent is provided
|
|
10
11
|
*/
|
|
11
12
|
build(template, context, state, agent = null) {
|
|
12
13
|
const parts = []
|
|
13
14
|
|
|
14
15
|
// Agent assignment (if applicable)
|
|
16
|
+
// CRITICAL: Include full agent content, not just name
|
|
15
17
|
if (agent) {
|
|
16
|
-
parts.push(
|
|
18
|
+
parts.push(`# AGENT ASSIGNMENT\n`)
|
|
19
|
+
parts.push(`Agent: ${agent.name}\n`)
|
|
20
|
+
|
|
21
|
+
// Include role if available
|
|
22
|
+
if (agent.role) {
|
|
23
|
+
parts.push(`Role: ${agent.role}\n`)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Include domain if available
|
|
27
|
+
if (agent.domain) {
|
|
28
|
+
parts.push(`Domain: ${agent.domain}\n`)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Include skills if available
|
|
32
|
+
if (agent.skills && agent.skills.length > 0) {
|
|
33
|
+
parts.push(`Skills: ${agent.skills.join(', ')}\n`)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
parts.push(`\n## AGENT INSTRUCTIONS\n`)
|
|
37
|
+
|
|
38
|
+
// CRITICAL: Include full agent content
|
|
39
|
+
// This is the specialized knowledge for this project
|
|
40
|
+
if (agent.content) {
|
|
41
|
+
parts.push(agent.content)
|
|
42
|
+
parts.push(`\n`)
|
|
43
|
+
} else if (agent.name) {
|
|
44
|
+
// Fallback if content not loaded
|
|
45
|
+
parts.push(`You are the ${agent.name} agent for this project.\n`)
|
|
46
|
+
parts.push(`Apply your specialized expertise to complete the task.\n\n`)
|
|
47
|
+
}
|
|
48
|
+
|
|
17
49
|
parts.push(`CONTEXT: ${context.filteredSize || 'all'} files (${context.reduction || 0}% reduced)\n\n`)
|
|
18
50
|
}
|
|
19
51
|
|
|
@@ -49,6 +81,9 @@ class PromptBuilder {
|
|
|
49
81
|
parts.push('\n')
|
|
50
82
|
}
|
|
51
83
|
|
|
84
|
+
// Enforcement (Strict Mode)
|
|
85
|
+
parts.push(this.buildEnforcement());
|
|
86
|
+
|
|
52
87
|
// Simple execution directive
|
|
53
88
|
parts.push('\nEXECUTE: Follow flow. Use tools. Decide.\n')
|
|
54
89
|
|
|
@@ -91,6 +126,21 @@ class PromptBuilder {
|
|
|
91
126
|
|
|
92
127
|
return parts.join('')
|
|
93
128
|
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Build enforcement section
|
|
132
|
+
* Forces Claude to follow the process strictly
|
|
133
|
+
*/
|
|
134
|
+
buildEnforcement() {
|
|
135
|
+
return `
|
|
136
|
+
## PROCESS ENFORCEMENT
|
|
137
|
+
1. FOLLOW the Flow strictly. Do not skip steps.
|
|
138
|
+
2. USE the allowed tools only.
|
|
139
|
+
3. IF you are stuck, use the "Ask" tool or stop.
|
|
140
|
+
4. DO NOT hallucinate files or commands.
|
|
141
|
+
5. ALWAYS verify your changes.
|
|
142
|
+
`;
|
|
143
|
+
}
|
|
94
144
|
}
|
|
95
145
|
|
|
96
146
|
module.exports = new PromptBuilder()
|
package/core/commands.js
CHANGED
|
@@ -1133,7 +1133,6 @@ class PrjctCommands {
|
|
|
1133
1133
|
*/
|
|
1134
1134
|
async _cleanupMemory(projectPath) {
|
|
1135
1135
|
const projectId = await configManager.getProjectId(projectPath)
|
|
1136
|
-
const globalPath = pathManager.getGlobalProjectPath(projectId)
|
|
1137
1136
|
|
|
1138
1137
|
console.log('📊 Analyzing disk usage...\n')
|
|
1139
1138
|
|
|
@@ -2474,7 +2473,7 @@ Agent: ${agent}
|
|
|
2474
2473
|
const AgentGenerator = require('./domain/agent-generator')
|
|
2475
2474
|
const generator = new AgentGenerator(projectId)
|
|
2476
2475
|
|
|
2477
|
-
const generatedAgents = await this._generateAgentsFromAnalysis(summaryContent, generator)
|
|
2476
|
+
const generatedAgents = await this._generateAgentsFromAnalysis(summaryContent, generator, projectPath)
|
|
2478
2477
|
|
|
2479
2478
|
// Step 4: Log to memory
|
|
2480
2479
|
await this.logToMemory(projectPath, 'agents_generated', {
|
|
@@ -2506,91 +2505,118 @@ Agent: ${agent}
|
|
|
2506
2505
|
|
|
2507
2506
|
/**
|
|
2508
2507
|
* Generate agents dynamically from analysis summary
|
|
2509
|
-
*
|
|
2508
|
+
* 100% DYNAMIC - Uses TechDetector, NO HARDCODING
|
|
2509
|
+
* Claude decides based on actual detected technologies
|
|
2510
2510
|
* @private
|
|
2511
2511
|
*/
|
|
2512
|
-
async _generateAgentsFromAnalysis(summaryContent, generator) {
|
|
2512
|
+
async _generateAgentsFromAnalysis(summaryContent, generator, projectPath) {
|
|
2513
2513
|
const agents = []
|
|
2514
|
+
const TechDetector = require('./domain/tech-detector')
|
|
2515
|
+
const detector = new TechDetector(projectPath)
|
|
2516
|
+
const tech = await detector.detectAll()
|
|
2514
2517
|
|
|
2515
|
-
//
|
|
2516
|
-
//
|
|
2518
|
+
// Generate agents based on ACTUAL detected technologies
|
|
2519
|
+
// No assumptions, no hardcoding - just what we found
|
|
2517
2520
|
|
|
2518
|
-
//
|
|
2519
|
-
const
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
const
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
const hasGo = summaryContent.includes('Go')
|
|
2526
|
-
const hasPython = summaryContent.includes('Python')
|
|
2527
|
-
const hasDocker = summaryContent.includes('Docker')
|
|
2528
|
-
|
|
2529
|
-
// Generate agents based on detected stack
|
|
2530
|
-
// Each agent is specific to THIS project's stack
|
|
2521
|
+
// Frontend agents - if we have frontend frameworks
|
|
2522
|
+
const frontendFrameworks = tech.frameworks.filter(f =>
|
|
2523
|
+
['react', 'vue', 'angular', 'svelte', 'next', 'nuxt', 'sveltekit', 'remix'].includes(f.toLowerCase())
|
|
2524
|
+
)
|
|
2525
|
+
const frontendBuildTools = tech.buildTools.filter(t =>
|
|
2526
|
+
['vite', 'webpack', 'rollup', 'esbuild'].includes(t.toLowerCase())
|
|
2527
|
+
)
|
|
2531
2528
|
|
|
2532
|
-
if (
|
|
2529
|
+
if (frontendFrameworks.length > 0 || frontendBuildTools.length > 0 || tech.languages.includes('JavaScript') || tech.languages.includes('TypeScript')) {
|
|
2530
|
+
const frameworkList = frontendFrameworks.length > 0
|
|
2531
|
+
? frontendFrameworks.join(', ')
|
|
2532
|
+
: (frontendBuildTools.length > 0 ? frontendBuildTools.join(', ') : 'JavaScript/TypeScript')
|
|
2533
|
+
|
|
2533
2534
|
await generator.generateDynamicAgent('frontend-specialist', {
|
|
2534
2535
|
role: 'Frontend Development Specialist',
|
|
2535
|
-
expertise: `${
|
|
2536
|
-
responsibilities:
|
|
2537
|
-
'Handle UI components, state management, routing, and frontend architecture',
|
|
2536
|
+
expertise: `${frameworkList}, ${tech.languages.filter(l => ['JavaScript', 'TypeScript'].includes(l)).join(' or ') || 'Modern JavaScript'}`,
|
|
2537
|
+
responsibilities: 'Handle UI components, state management, routing, and frontend architecture',
|
|
2538
2538
|
projectContext: {
|
|
2539
|
-
|
|
2539
|
+
detectedFrameworks: frontendFrameworks,
|
|
2540
|
+
buildTools: frontendBuildTools,
|
|
2541
|
+
languages: tech.languages.filter(l => ['JavaScript', 'TypeScript'].includes(l))
|
|
2540
2542
|
},
|
|
2541
2543
|
})
|
|
2542
2544
|
agents.push('frontend-specialist')
|
|
2543
2545
|
}
|
|
2544
2546
|
|
|
2545
|
-
if
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
})
|
|
2553
|
-
agents.push('rust-developer')
|
|
2554
|
-
}
|
|
2547
|
+
// Backend agents - if we have backend frameworks or languages
|
|
2548
|
+
const backendFrameworks = tech.frameworks.filter(f =>
|
|
2549
|
+
['express', 'fastify', 'koa', 'hapi', 'nest', 'django', 'flask', 'fastapi', 'rails', 'phoenix', 'laravel'].includes(f.toLowerCase())
|
|
2550
|
+
)
|
|
2551
|
+
const backendLanguages = tech.languages.filter(l =>
|
|
2552
|
+
['Go', 'Rust', 'Python', 'Ruby', 'Elixir', 'Java', 'PHP'].includes(l)
|
|
2553
|
+
)
|
|
2555
2554
|
|
|
2556
|
-
if (
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2555
|
+
if (backendFrameworks.length > 0 || backendLanguages.length > 0) {
|
|
2556
|
+
const agentName = backendLanguages.length > 0
|
|
2557
|
+
? `${backendLanguages[0].toLowerCase()}-developer`
|
|
2558
|
+
: 'backend-specialist'
|
|
2559
|
+
|
|
2560
|
+
const expertise = backendFrameworks.length > 0
|
|
2561
|
+
? backendFrameworks.join(', ')
|
|
2562
|
+
: backendLanguages.join(', ')
|
|
2563
|
+
|
|
2564
|
+
await generator.generateDynamicAgent(agentName, {
|
|
2565
|
+
role: `${backendLanguages[0] || 'Backend'} Development Specialist`,
|
|
2566
|
+
expertise,
|
|
2567
|
+
responsibilities: 'Handle backend services, API development, server logic',
|
|
2568
|
+
projectContext: {
|
|
2569
|
+
detectedFrameworks: backendFrameworks,
|
|
2570
|
+
languages: backendLanguages
|
|
2571
|
+
},
|
|
2562
2572
|
})
|
|
2563
|
-
agents.push(
|
|
2573
|
+
agents.push(agentName)
|
|
2564
2574
|
}
|
|
2565
2575
|
|
|
2566
|
-
if
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2576
|
+
// Database specialist - if we have database tools
|
|
2577
|
+
if (tech.databases.length > 0) {
|
|
2578
|
+
await generator.generateDynamicAgent('database-specialist', {
|
|
2579
|
+
role: 'Database Specialist',
|
|
2580
|
+
expertise: tech.databases.join(', '),
|
|
2581
|
+
responsibilities: 'Handle database design, queries, migrations, data modeling',
|
|
2582
|
+
projectContext: {
|
|
2583
|
+
databases: tech.databases
|
|
2584
|
+
},
|
|
2572
2585
|
})
|
|
2573
|
-
agents.push('
|
|
2586
|
+
agents.push('database-specialist')
|
|
2574
2587
|
}
|
|
2575
2588
|
|
|
2576
|
-
if
|
|
2589
|
+
// DevOps specialist - if we have DevOps tools
|
|
2590
|
+
if (tech.tools.some(t => ['Docker', 'Kubernetes', 'Terraform'].includes(t))) {
|
|
2577
2591
|
await generator.generateDynamicAgent('devops-specialist', {
|
|
2578
2592
|
role: 'DevOps & Infrastructure Specialist',
|
|
2579
|
-
expertise: 'Docker,
|
|
2593
|
+
expertise: tech.tools.filter(t => ['Docker', 'Kubernetes', 'Terraform'].includes(t)).join(', '),
|
|
2580
2594
|
responsibilities: 'Handle containerization, deployment, infrastructure setup',
|
|
2581
|
-
projectContext: {
|
|
2595
|
+
projectContext: {
|
|
2596
|
+
tools: tech.tools
|
|
2597
|
+
},
|
|
2582
2598
|
})
|
|
2583
2599
|
agents.push('devops-specialist')
|
|
2584
2600
|
}
|
|
2585
2601
|
|
|
2586
|
-
//
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2602
|
+
// QA specialist - always generate if we have test frameworks or any code
|
|
2603
|
+
if (tech.testFrameworks.length > 0 || tech.languages.length > 0) {
|
|
2604
|
+
const testExpertise = tech.testFrameworks.length > 0
|
|
2605
|
+
? tech.testFrameworks.join(', ')
|
|
2606
|
+
: 'Testing frameworks, test automation'
|
|
2607
|
+
|
|
2608
|
+
await generator.generateDynamicAgent('qa-specialist', {
|
|
2609
|
+
role: 'Quality Assurance Specialist',
|
|
2610
|
+
expertise: testExpertise,
|
|
2611
|
+
responsibilities: 'Handle testing strategy, test creation, quality assurance',
|
|
2612
|
+
projectContext: {
|
|
2613
|
+
testFrameworks: tech.testFrameworks,
|
|
2614
|
+
languages: tech.languages,
|
|
2615
|
+
role: 'QA'
|
|
2616
|
+
},
|
|
2617
|
+
})
|
|
2618
|
+
agents.push('qa-specialist')
|
|
2619
|
+
}
|
|
2594
2620
|
|
|
2595
2621
|
return agents
|
|
2596
2622
|
}
|