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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fs = require('fs').promises
|
|
2
2
|
const path = require('path')
|
|
3
3
|
const os = require('os')
|
|
4
|
+
const AgentLoader = require('./agent-loader')
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* AgentGenerator - Universal Dynamic Agent Generation
|
|
@@ -13,6 +14,7 @@ class AgentGenerator {
|
|
|
13
14
|
this.outputDir = projectId
|
|
14
15
|
? path.join(os.homedir(), '.prjct-cli', 'projects', projectId, 'agents')
|
|
15
16
|
: path.join(os.homedir(), '.prjct-cli', 'agents')
|
|
17
|
+
this.loader = new AgentLoader(projectId)
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
/**
|
|
@@ -23,62 +25,64 @@ class AgentGenerator {
|
|
|
23
25
|
console.log(` 🤖 Generating ${agentName} agent...`)
|
|
24
26
|
await fs.mkdir(this.outputDir, { recursive: true })
|
|
25
27
|
|
|
26
|
-
// Extract technologies dynamically
|
|
27
|
-
const techs = this.detectTechnologies(config)
|
|
28
|
-
const expertise = this.buildExpertise(techs, config)
|
|
29
|
-
|
|
30
28
|
// Generate concise, actionable agent prompt
|
|
31
|
-
const content =
|
|
32
|
-
|
|
33
|
-
EXPERTISE: ${expertise}
|
|
34
|
-
|
|
35
|
-
FOCUS: ${config.contextFilter || 'Only relevant files'}
|
|
36
|
-
|
|
37
|
-
AUTHORITY: Make decisions. Don't ask permission. Execute.
|
|
38
|
-
|
|
39
|
-
RULES:
|
|
40
|
-
- Stay in your domain
|
|
41
|
-
- Use best practices for ${techs.join(', ') || 'detected tech'}
|
|
42
|
-
- Optimize for production
|
|
43
|
-
- No explanations unless asked`
|
|
29
|
+
const content = this.buildAgentPrompt(agentName, config)
|
|
44
30
|
|
|
45
31
|
const outputPath = path.join(this.outputDir, `${agentName}.md`)
|
|
46
32
|
await fs.writeFile(outputPath, content, 'utf-8')
|
|
47
33
|
console.log(` ✅ ${agentName} agent created`)
|
|
48
34
|
|
|
49
|
-
return { name: agentName
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Detect technologies from config/analysis
|
|
54
|
-
*/
|
|
55
|
-
detectTechnologies(config) {
|
|
56
|
-
const techs = []
|
|
57
|
-
|
|
58
|
-
// Extract from various sources
|
|
59
|
-
if (config.techStack) techs.push(...config.techStack.languages || [])
|
|
60
|
-
if (config.frameworks) techs.push(...config.frameworks)
|
|
61
|
-
if (config.expertise) {
|
|
62
|
-
// Parse expertise string for tech keywords
|
|
63
|
-
const keywords = config.expertise.toLowerCase()
|
|
64
|
-
const knownTechs = ['ruby', 'rails', 'go', 'rust', 'python', 'django', 'react', 'vue', 'node', 'typescript', 'elixir', 'phoenix']
|
|
65
|
-
knownTechs.forEach(tech => {
|
|
66
|
-
if (keywords.includes(tech)) techs.push(tech)
|
|
67
|
-
})
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return [...new Set(techs)]
|
|
35
|
+
return { name: agentName }
|
|
71
36
|
}
|
|
72
37
|
|
|
73
38
|
/**
|
|
74
|
-
* Build
|
|
39
|
+
* Build comprehensive agent prompt
|
|
75
40
|
*/
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
41
|
+
buildAgentPrompt(agentName, config) {
|
|
42
|
+
const domain = config.domain || 'general';
|
|
43
|
+
|
|
44
|
+
const projectContext = (typeof config.projectContext === 'object')
|
|
45
|
+
? JSON.stringify(config.projectContext || {}, null, 2)
|
|
46
|
+
: (config.projectContext || 'No specific project context provided.');
|
|
47
|
+
|
|
48
|
+
return `# AGENT: ${agentName.toUpperCase()}
|
|
49
|
+
Role: ${config.role || agentName}
|
|
50
|
+
|
|
51
|
+
## META-INSTRUCTION
|
|
52
|
+
You are an intelligent agent responsible for this domain.
|
|
53
|
+
Your first task is to ANALYZE the provided PROJECT CONTEXT to determine:
|
|
54
|
+
1. The technology stack being used.
|
|
55
|
+
2. The architectural patterns in place.
|
|
56
|
+
3. The specific best practices relevant to this stack.
|
|
57
|
+
|
|
58
|
+
## DOMAIN AUTHORITY
|
|
59
|
+
You are the owner of the ${domain} domain.
|
|
60
|
+
You have full authority to make technical decisions within this scope.
|
|
61
|
+
|
|
62
|
+
## DYNAMIC STANDARDS
|
|
63
|
+
Instead of following a hardcoded list, you must:
|
|
64
|
+
- **DETECT**: Identify the languages, frameworks, and tools from the file extensions and content.
|
|
65
|
+
- **ADAPT**: Adopt the persona of a Senior Engineer specializing in the detected stack.
|
|
66
|
+
- **ENFORCE**: Apply the idiomatic best practices, naming conventions, and patterns of that stack.
|
|
67
|
+
|
|
68
|
+
## ORCHESTRATION PROTOCOL
|
|
69
|
+
1. **ANALYZE**: Read the context. Determine the stack.
|
|
70
|
+
2. **PLAN**: Create a plan that fits the detected architecture.
|
|
71
|
+
3. **EXECUTE**: Implement using the detected tools and patterns.
|
|
72
|
+
4. **VERIFY**: Ensure code matches the project's existing style.
|
|
73
|
+
|
|
74
|
+
## PROJECT CONTEXT
|
|
75
|
+
${projectContext}
|
|
76
|
+
|
|
77
|
+
## CONTEXT FOCUS
|
|
78
|
+
${config.contextFilter || 'Only relevant files'}
|
|
79
|
+
|
|
80
|
+
## RULES
|
|
81
|
+
- Stay in your domain (${domain})
|
|
82
|
+
- Do not assume a specific stack until you see the code.
|
|
83
|
+
- Optimize for production.
|
|
84
|
+
- No explanations unless asked.
|
|
85
|
+
`;
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
/**
|
|
@@ -124,6 +128,33 @@ RULES:
|
|
|
124
128
|
return []
|
|
125
129
|
}
|
|
126
130
|
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Load an agent from its file
|
|
134
|
+
* CRITICAL: This is how agents are actually used in prompts
|
|
135
|
+
* @param {string} agentName - Name of the agent (without .md extension)
|
|
136
|
+
* @returns {Promise<Object|null>} - Agent object with name, content, role, domain, skills, or null if not found
|
|
137
|
+
*/
|
|
138
|
+
async loadAgent(agentName) {
|
|
139
|
+
return await this.loader.loadAgent(agentName)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Load all agents for the project
|
|
144
|
+
* @returns {Promise<Array<Object>>} - Array of agent objects
|
|
145
|
+
*/
|
|
146
|
+
async loadAllAgents() {
|
|
147
|
+
return await this.loader.loadAllAgents()
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Check if an agent exists
|
|
152
|
+
* @param {string} agentName - Name of the agent
|
|
153
|
+
* @returns {Promise<boolean>} - True if agent file exists
|
|
154
|
+
*/
|
|
155
|
+
async agentExists(agentName) {
|
|
156
|
+
return await this.loader.agentExists(agentName)
|
|
157
|
+
}
|
|
127
158
|
}
|
|
128
159
|
|
|
129
160
|
module.exports = AgentGenerator
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentLoader - Loads agents from project files
|
|
3
|
+
*
|
|
4
|
+
* CRITICAL: This ensures agents generated for a project are actually USED
|
|
5
|
+
*
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs').promises
|
|
10
|
+
const path = require('path')
|
|
11
|
+
const os = require('os')
|
|
12
|
+
|
|
13
|
+
class AgentLoader {
|
|
14
|
+
constructor(projectId = null) {
|
|
15
|
+
this.projectId = projectId
|
|
16
|
+
this.agentsDir = projectId
|
|
17
|
+
? path.join(os.homedir(), '.prjct-cli', 'projects', projectId, 'agents')
|
|
18
|
+
: path.join(os.homedir(), '.prjct-cli', 'agents')
|
|
19
|
+
this.cache = new Map()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Load an agent from its file
|
|
24
|
+
* @param {string} agentName - Name of the agent (without .md extension)
|
|
25
|
+
* @returns {Promise<Object|null>} - Agent object with name and content, or null if not found
|
|
26
|
+
*/
|
|
27
|
+
async loadAgent(agentName) {
|
|
28
|
+
// Check cache first
|
|
29
|
+
const cacheKey = `${this.projectId || 'global'}-${agentName}`
|
|
30
|
+
if (this.cache.has(cacheKey)) {
|
|
31
|
+
return this.cache.get(cacheKey)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const agentPath = path.join(this.agentsDir, `${agentName}.md`)
|
|
36
|
+
const content = await fs.readFile(agentPath, 'utf-8')
|
|
37
|
+
|
|
38
|
+
// Parse agent metadata from content
|
|
39
|
+
const agent = {
|
|
40
|
+
name: agentName,
|
|
41
|
+
content,
|
|
42
|
+
path: agentPath,
|
|
43
|
+
// Extract role if present in content
|
|
44
|
+
role: this.extractRole(content),
|
|
45
|
+
// Extract domain if present
|
|
46
|
+
domain: this.extractDomain(content),
|
|
47
|
+
// Extract skills/technologies mentioned
|
|
48
|
+
skills: this.extractSkills(content),
|
|
49
|
+
// Last modified time
|
|
50
|
+
modified: (await fs.stat(agentPath)).mtime
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Cache it
|
|
54
|
+
this.cache.set(cacheKey, agent)
|
|
55
|
+
|
|
56
|
+
return agent
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (error.code === 'ENOENT') {
|
|
59
|
+
return null // Agent file doesn't exist
|
|
60
|
+
}
|
|
61
|
+
throw error
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Load all agents for the project
|
|
67
|
+
* @returns {Promise<Array<Object>>} - Array of agent objects
|
|
68
|
+
*/
|
|
69
|
+
async loadAllAgents() {
|
|
70
|
+
try {
|
|
71
|
+
const files = await fs.readdir(this.agentsDir)
|
|
72
|
+
const agentFiles = files.filter(f => f.endsWith('.md') && !f.startsWith('.'))
|
|
73
|
+
|
|
74
|
+
const agents = []
|
|
75
|
+
for (const file of agentFiles) {
|
|
76
|
+
const agentName = file.replace('.md', '')
|
|
77
|
+
const agent = await this.loadAgent(agentName)
|
|
78
|
+
if (agent) {
|
|
79
|
+
agents.push(agent)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return agents
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (error.code === 'ENOENT') {
|
|
86
|
+
return [] // Agents directory doesn't exist yet
|
|
87
|
+
}
|
|
88
|
+
throw error
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Check if an agent exists
|
|
94
|
+
* @param {string} agentName - Name of the agent
|
|
95
|
+
* @returns {Promise<boolean>} - True if agent file exists
|
|
96
|
+
*/
|
|
97
|
+
async agentExists(agentName) {
|
|
98
|
+
try {
|
|
99
|
+
const agentPath = path.join(this.agentsDir, `${agentName}.md`)
|
|
100
|
+
await fs.access(agentPath)
|
|
101
|
+
return true
|
|
102
|
+
} catch {
|
|
103
|
+
return false
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Clear cache (useful after agent updates)
|
|
109
|
+
*/
|
|
110
|
+
clearCache() {
|
|
111
|
+
this.cache.clear()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Extract role from agent content
|
|
116
|
+
* @private
|
|
117
|
+
*/
|
|
118
|
+
extractRole(content) {
|
|
119
|
+
const roleMatch = content.match(/Role:\s*(.+)/i)
|
|
120
|
+
return roleMatch ? roleMatch[1].trim() : null
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Extract domain from agent content
|
|
125
|
+
* @private
|
|
126
|
+
*/
|
|
127
|
+
extractDomain(content) {
|
|
128
|
+
const domainMatch = content.match(/DOMAIN AUTHORITY[\s\S]*?the\s+(\w+)\s+domain/i)
|
|
129
|
+
if (domainMatch) {
|
|
130
|
+
return domainMatch[1].toLowerCase()
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Fallback: try to detect from agent name
|
|
134
|
+
const name = this.projectId ? '' : ''
|
|
135
|
+
if (name.includes('frontend')) return 'frontend'
|
|
136
|
+
if (name.includes('backend')) return 'backend'
|
|
137
|
+
if (name.includes('database')) return 'database'
|
|
138
|
+
if (name.includes('devops')) return 'devops'
|
|
139
|
+
if (name.includes('qa')) return 'qa'
|
|
140
|
+
if (name.includes('architect')) return 'architecture'
|
|
141
|
+
|
|
142
|
+
return 'general'
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Extract skills/technologies mentioned in agent content
|
|
147
|
+
* @private
|
|
148
|
+
*/
|
|
149
|
+
extractSkills(content) {
|
|
150
|
+
const skills = []
|
|
151
|
+
|
|
152
|
+
// Look for common technology mentions
|
|
153
|
+
const techKeywords = [
|
|
154
|
+
'React', 'Vue', 'Angular', 'Svelte',
|
|
155
|
+
'Next.js', 'Nuxt', 'SvelteKit',
|
|
156
|
+
'TypeScript', 'JavaScript',
|
|
157
|
+
'Node.js', 'Express', 'Fastify',
|
|
158
|
+
'Python', 'Django', 'Flask', 'FastAPI',
|
|
159
|
+
'Go', 'Rust', 'Ruby', 'Rails',
|
|
160
|
+
'PostgreSQL', 'MySQL', 'MongoDB',
|
|
161
|
+
'Docker', 'Kubernetes', 'Terraform'
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
for (const tech of techKeywords) {
|
|
165
|
+
if (content.includes(tech)) {
|
|
166
|
+
skills.push(tech)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return skills
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Get agents directory path
|
|
175
|
+
* @returns {string} - Path to agents directory
|
|
176
|
+
*/
|
|
177
|
+
getAgentsDir() {
|
|
178
|
+
return this.agentsDir
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
module.exports = AgentLoader
|
|
183
|
+
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentMatcher - Intelligent Agent Matching with Scoring
|
|
3
|
+
*
|
|
4
|
+
* Matches tasks to agents using multi-factor scoring:
|
|
5
|
+
* - Agent skills and capabilities
|
|
6
|
+
* - Historical success rates
|
|
7
|
+
* - Project technologies
|
|
8
|
+
* - Task complexity
|
|
9
|
+
*
|
|
10
|
+
* @version 1.0.0
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
class AgentMatcher {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.historyCache = new Map()
|
|
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
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Score skills match
|
|
107
|
+
*/
|
|
108
|
+
scoreSkillsMatch(agent, taskAnalysis) {
|
|
109
|
+
if (!agent.skills || agent.skills.length === 0) {
|
|
110
|
+
return 0.2 // Generic agent penalty
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const projectTech = taskAnalysis.projectTechnologies || {}
|
|
114
|
+
const allTech = [
|
|
115
|
+
...(projectTech.languages || []),
|
|
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
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Score historical success
|
|
138
|
+
*/
|
|
139
|
+
scoreHistoricalSuccess(agent, taskAnalysis) {
|
|
140
|
+
// TODO: Load from persistent history
|
|
141
|
+
// For now, return neutral score
|
|
142
|
+
const cacheKey = `${agent.name}-${taskAnalysis.primaryDomain}`
|
|
143
|
+
const history = this.historyCache.get(cacheKey)
|
|
144
|
+
|
|
145
|
+
if (history) {
|
|
146
|
+
// Success rate from history
|
|
147
|
+
return history.successRate || 0.5
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return 0.5 // Neutral - no history
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Score complexity match
|
|
155
|
+
*/
|
|
156
|
+
scoreComplexityMatch(agent, taskAnalysis) {
|
|
157
|
+
const taskComplexity = taskAnalysis.complexity || 'medium'
|
|
158
|
+
|
|
159
|
+
// Generic agents are better for simple tasks
|
|
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
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return 0.5 // Neutral
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Record agent success for learning
|
|
176
|
+
*/
|
|
177
|
+
recordSuccess(agent, taskAnalysis, success = true) {
|
|
178
|
+
const cacheKey = `${agent.name}-${taskAnalysis.primaryDomain}`
|
|
179
|
+
const history = this.historyCache.get(cacheKey) || {
|
|
180
|
+
attempts: 0,
|
|
181
|
+
successes: 0,
|
|
182
|
+
successRate: 0.5
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
history.attempts++
|
|
186
|
+
if (success) {
|
|
187
|
+
history.successes++
|
|
188
|
+
}
|
|
189
|
+
history.successRate = history.successes / history.attempts
|
|
190
|
+
|
|
191
|
+
this.historyCache.set(cacheKey, history)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Get match explanation
|
|
196
|
+
*/
|
|
197
|
+
explainMatch(match) {
|
|
198
|
+
if (!match) {
|
|
199
|
+
return 'No suitable agent found'
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const reasons = []
|
|
203
|
+
|
|
204
|
+
if (match.score > 0.8) {
|
|
205
|
+
reasons.push('Excellent match')
|
|
206
|
+
} else if (match.score > 0.6) {
|
|
207
|
+
reasons.push('Good match')
|
|
208
|
+
} else {
|
|
209
|
+
reasons.push('Acceptable match')
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return reasons.join(', ')
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
module.exports = AgentMatcher
|
|
217
|
+
|