prjct-cli 0.10.6 → 0.10.9
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 +107 -0
- package/core/__tests__/agentic/prompt-builder.test.js +244 -0
- package/core/__tests__/utils/output.test.js +163 -0
- package/core/agentic/agent-router.js +45 -173
- package/core/agentic/context-builder.js +25 -15
- package/core/agentic/context-filter.js +118 -313
- package/core/agentic/prompt-builder.js +90 -46
- package/core/commands.js +121 -637
- package/core/domain/agent-generator.js +55 -4
- package/core/domain/analyzer.js +122 -0
- package/core/domain/context-estimator.js +32 -53
- package/core/domain/smart-cache.js +2 -1
- package/core/domain/task-analyzer.js +75 -146
- package/core/domain/task-stack.js +2 -1
- package/core/utils/logger.js +64 -0
- package/core/utils/output.js +54 -0
- package/package.json +1 -1
- package/templates/agentic/agent-routing.md +78 -0
- package/templates/agentic/context-filtering.md +77 -0
- package/templates/analysis/patterns.md +206 -0
- package/templates/analysis/project-analysis.md +78 -0
- package/templates/commands/sync.md +61 -7
- package/core/domain/tech-detector.js +0 -365
|
@@ -2,6 +2,7 @@ const fs = require('fs').promises
|
|
|
2
2
|
const path = require('path')
|
|
3
3
|
const os = require('os')
|
|
4
4
|
const AgentLoader = require('./agent-loader')
|
|
5
|
+
const log = require('../utils/logger')
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* AgentGenerator - Universal Dynamic Agent Generation
|
|
@@ -22,7 +23,7 @@ class AgentGenerator {
|
|
|
22
23
|
* Universal - works with ANY technology stack
|
|
23
24
|
*/
|
|
24
25
|
async generateDynamicAgent(agentName, config) {
|
|
25
|
-
|
|
26
|
+
log.debug(`Generating ${agentName} agent...`)
|
|
26
27
|
await fs.mkdir(this.outputDir, { recursive: true })
|
|
27
28
|
|
|
28
29
|
// Generate concise, actionable agent prompt
|
|
@@ -30,11 +31,61 @@ class AgentGenerator {
|
|
|
30
31
|
|
|
31
32
|
const outputPath = path.join(this.outputDir, `${agentName}.md`)
|
|
32
33
|
await fs.writeFile(outputPath, content, 'utf-8')
|
|
33
|
-
|
|
34
|
+
log.debug(`${agentName} agent created`)
|
|
34
35
|
|
|
35
36
|
return { name: agentName }
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Generate agents from tech analysis - 100% AGENTIC
|
|
41
|
+
* Claude decides what agents are needed based on actual project tech
|
|
42
|
+
* NO HARDCODED LISTS - reads analysis and decides
|
|
43
|
+
*/
|
|
44
|
+
async generateAgentsFromTech(context) {
|
|
45
|
+
const { detectedTech, analysisSummary, projectPath } = context
|
|
46
|
+
const agents = []
|
|
47
|
+
|
|
48
|
+
// Read agent generation template - Claude will use this to decide
|
|
49
|
+
const templatePath = path.join(__dirname, '../../templates/agents/AGENTS.md')
|
|
50
|
+
let agentTemplate = ''
|
|
51
|
+
try {
|
|
52
|
+
agentTemplate = await fs.readFile(templatePath, 'utf-8')
|
|
53
|
+
} catch {
|
|
54
|
+
// Fallback if template doesn't exist
|
|
55
|
+
agentTemplate = 'Generate agents based on detected technologies'
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Build context for Claude to decide
|
|
59
|
+
// Pass full tech info, let Claude categorize and decide what agents are needed
|
|
60
|
+
const techSummary = {
|
|
61
|
+
languages: detectedTech.languages || [],
|
|
62
|
+
frameworks: detectedTech.frameworks || [],
|
|
63
|
+
buildTools: detectedTech.buildTools || [],
|
|
64
|
+
testFrameworks: detectedTech.testFrameworks || [],
|
|
65
|
+
databases: detectedTech.databases || [],
|
|
66
|
+
tools: detectedTech.tools || [],
|
|
67
|
+
allDependencies: detectedTech.allDependencies || []
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// For now, generate basic agents based on what we detected
|
|
71
|
+
// But this should be replaced with Claude decision-making
|
|
72
|
+
// TODO: Make this fully agentic by having Claude read the template and decide
|
|
73
|
+
|
|
74
|
+
// Temporary: Generate agents for detected domains (agentic, not hardcoded)
|
|
75
|
+
// Claude will eventually decide this based on template
|
|
76
|
+
if (techSummary.languages.length > 0 || techSummary.frameworks.length > 0) {
|
|
77
|
+
// Generate a general development agent
|
|
78
|
+
await this.generateDynamicAgent('developer', {
|
|
79
|
+
role: 'Development Specialist',
|
|
80
|
+
domain: 'general',
|
|
81
|
+
projectContext: techSummary
|
|
82
|
+
})
|
|
83
|
+
agents.push('developer')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return agents.map(name => ({ name }))
|
|
87
|
+
}
|
|
88
|
+
|
|
38
89
|
/**
|
|
39
90
|
* Build comprehensive agent prompt
|
|
40
91
|
*/
|
|
@@ -104,11 +155,11 @@ ${config.contextFilter || 'Only relevant files'}
|
|
|
104
155
|
const filePath = path.join(this.outputDir, file)
|
|
105
156
|
await fs.unlink(filePath)
|
|
106
157
|
removed.push(type)
|
|
107
|
-
|
|
158
|
+
log.debug(`${type} agent removed`)
|
|
108
159
|
}
|
|
109
160
|
}
|
|
110
161
|
} catch (error) {
|
|
111
|
-
|
|
162
|
+
log.error('Agent cleanup failed:', error.message)
|
|
112
163
|
}
|
|
113
164
|
|
|
114
165
|
return removed
|
package/core/domain/analyzer.js
CHANGED
|
@@ -81,6 +81,128 @@ class CodebaseAnalyzer {
|
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Read Gemfile if it exists (Ruby)
|
|
86
|
+
* @returns {Promise<string|null>}
|
|
87
|
+
*/
|
|
88
|
+
async readGemfile() {
|
|
89
|
+
try {
|
|
90
|
+
const gemfilePath = path.join(this.projectPath, 'Gemfile')
|
|
91
|
+
return await fs.readFile(gemfilePath, 'utf-8')
|
|
92
|
+
} catch {
|
|
93
|
+
return null
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Read mix.exs if it exists (Elixir)
|
|
99
|
+
* @returns {Promise<string|null>}
|
|
100
|
+
*/
|
|
101
|
+
async readMixExs() {
|
|
102
|
+
try {
|
|
103
|
+
const mixPath = path.join(this.projectPath, 'mix.exs')
|
|
104
|
+
return await fs.readFile(mixPath, 'utf-8')
|
|
105
|
+
} catch {
|
|
106
|
+
return null
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Read pom.xml if it exists (Java/Maven)
|
|
112
|
+
* @returns {Promise<string|null>}
|
|
113
|
+
*/
|
|
114
|
+
async readPomXml() {
|
|
115
|
+
try {
|
|
116
|
+
const pomPath = path.join(this.projectPath, 'pom.xml')
|
|
117
|
+
return await fs.readFile(pomPath, 'utf-8')
|
|
118
|
+
} catch {
|
|
119
|
+
return null
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Read composer.json if it exists (PHP)
|
|
125
|
+
* @returns {Promise<Object|null>}
|
|
126
|
+
*/
|
|
127
|
+
async readComposerJson() {
|
|
128
|
+
try {
|
|
129
|
+
const composerPath = path.join(this.projectPath, 'composer.json')
|
|
130
|
+
const content = await fs.readFile(composerPath, 'utf-8')
|
|
131
|
+
return JSON.parse(content)
|
|
132
|
+
} catch {
|
|
133
|
+
return null
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Read pyproject.toml if it exists (Python)
|
|
139
|
+
* @returns {Promise<string|null>}
|
|
140
|
+
*/
|
|
141
|
+
async readPyprojectToml() {
|
|
142
|
+
try {
|
|
143
|
+
const pyprojectPath = path.join(this.projectPath, 'pyproject.toml')
|
|
144
|
+
return await fs.readFile(pyprojectPath, 'utf-8')
|
|
145
|
+
} catch {
|
|
146
|
+
return null
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get all file extensions in project with counts
|
|
152
|
+
* I/O PURO - Solo cuenta, no interpreta
|
|
153
|
+
* @returns {Promise<Object>} - {'.js': 45, '.ts': 23, ...}
|
|
154
|
+
*/
|
|
155
|
+
async getFileExtensions() {
|
|
156
|
+
try {
|
|
157
|
+
const { stdout } = await exec(
|
|
158
|
+
'find . -type f ! -path "*/node_modules/*" ! -path "*/.git/*" ! -path "*/dist/*" ! -path "*/.next/*" | sed "s/.*\\./\\./" | sort | uniq -c | sort -rn',
|
|
159
|
+
{ cwd: this.projectPath }
|
|
160
|
+
)
|
|
161
|
+
const extensions = {}
|
|
162
|
+
stdout.trim().split('\n').filter(Boolean).forEach(line => {
|
|
163
|
+
const match = line.trim().match(/^\s*(\d+)\s+(\.\w+)$/)
|
|
164
|
+
if (match) {
|
|
165
|
+
extensions[match[2]] = parseInt(match[1])
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
return extensions
|
|
169
|
+
} catch {
|
|
170
|
+
return {}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* List all config files in project root
|
|
176
|
+
* I/O PURO - Solo lista, no categoriza
|
|
177
|
+
* @returns {Promise<string[]>}
|
|
178
|
+
*/
|
|
179
|
+
async listConfigFiles() {
|
|
180
|
+
try {
|
|
181
|
+
const entries = await fs.readdir(this.projectPath)
|
|
182
|
+
const configPatterns = [
|
|
183
|
+
/^package\.json$/,
|
|
184
|
+
/^Cargo\.toml$/,
|
|
185
|
+
/^go\.mod$/,
|
|
186
|
+
/^requirements\.txt$/,
|
|
187
|
+
/^Gemfile$/,
|
|
188
|
+
/^mix\.exs$/,
|
|
189
|
+
/^pom\.xml$/,
|
|
190
|
+
/^composer\.json$/,
|
|
191
|
+
/^pyproject\.toml$/,
|
|
192
|
+
/^tsconfig.*\.json$/,
|
|
193
|
+
/^\..*rc(\.json|\.js|\.cjs)?$/,
|
|
194
|
+
/^Dockerfile$/,
|
|
195
|
+
/^docker-compose.*\.ya?ml$/,
|
|
196
|
+
/^\.env.*$/,
|
|
197
|
+
]
|
|
198
|
+
return entries.filter(entry =>
|
|
199
|
+
configPatterns.some(pattern => pattern.test(entry))
|
|
200
|
+
)
|
|
201
|
+
} catch {
|
|
202
|
+
return []
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
84
206
|
/**
|
|
85
207
|
* List all directories in project root
|
|
86
208
|
* @returns {Promise<string[]>}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
const path = require('path')
|
|
11
11
|
const { glob } = require('glob')
|
|
12
|
+
const log = require('../utils/logger')
|
|
12
13
|
|
|
13
14
|
class ContextEstimator {
|
|
14
15
|
/**
|
|
@@ -50,66 +51,44 @@ class ContextEstimator {
|
|
|
50
51
|
|
|
51
52
|
/**
|
|
52
53
|
* Get file patterns for a domain
|
|
54
|
+
*
|
|
55
|
+
* 100% AGENTIC: Uses REAL project data, not hardcoded patterns.
|
|
56
|
+
* No domain-specific assumptions or language→extension mapping.
|
|
53
57
|
*/
|
|
54
|
-
getPatternsForDomain(domain,
|
|
58
|
+
getPatternsForDomain(domain, projectData) {
|
|
55
59
|
const patterns = {
|
|
56
60
|
include: [],
|
|
57
61
|
extensions: [],
|
|
58
|
-
exclude: ['node_modules', 'dist', 'build', '.git']
|
|
62
|
+
exclude: ['node_modules', 'dist', 'build', '.git', '.next', 'target', 'vendor', 'coverage']
|
|
59
63
|
}
|
|
60
64
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
//
|
|
67
|
-
if (projectTech.frameworks) {
|
|
68
|
-
if (projectTech.frameworks.some(f => f.toLowerCase().includes('next'))) {
|
|
69
|
-
patterns.include.push('pages', 'app', 'components')
|
|
70
|
-
}
|
|
71
|
-
if (projectTech.frameworks.some(f => f.toLowerCase().includes('react'))) {
|
|
72
|
-
patterns.extensions.push('.tsx', '.jsx')
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
break
|
|
76
|
-
|
|
77
|
-
case 'backend':
|
|
78
|
-
patterns.include = ['src', 'lib', 'api', 'routes', 'controllers', 'services', 'app']
|
|
79
|
-
patterns.extensions = ['.js', '.ts', '.py', '.rb', '.go', '.rs']
|
|
80
|
-
|
|
81
|
-
// Framework-specific
|
|
82
|
-
if (projectTech.frameworks) {
|
|
83
|
-
if (projectTech.frameworks.some(f => f.toLowerCase().includes('express'))) {
|
|
84
|
-
patterns.include.push('routes', 'middleware', 'controllers')
|
|
85
|
-
}
|
|
86
|
-
if (projectTech.frameworks.some(f => f.toLowerCase().includes('django'))) {
|
|
87
|
-
patterns.include.push('views', 'urls', 'models')
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
break
|
|
91
|
-
|
|
92
|
-
case 'database':
|
|
93
|
-
patterns.include = ['migrations', 'models', 'schemas', 'db', 'database']
|
|
94
|
-
patterns.extensions = ['.sql', '.js', '.ts', '.rb', '.py']
|
|
95
|
-
break
|
|
96
|
-
|
|
97
|
-
case 'qa':
|
|
98
|
-
patterns.include = ['tests', '__tests__', 'spec', 'test']
|
|
99
|
-
patterns.extensions = ['.test.js', '.test.ts', '.spec.js', '.spec.ts']
|
|
100
|
-
break
|
|
101
|
-
|
|
102
|
-
case 'devops':
|
|
103
|
-
patterns.include = ['.github', '.gitlab', 'docker', 'k8s', 'kubernetes', 'terraform']
|
|
104
|
-
patterns.extensions = ['.yml', '.yaml', '.dockerfile', '.sh', '.tf']
|
|
105
|
-
break
|
|
106
|
-
|
|
107
|
-
default:
|
|
108
|
-
// General - include common source directories
|
|
109
|
-
patterns.include = ['src', 'lib', 'app']
|
|
110
|
-
patterns.extensions = ['.js', '.ts', '.jsx', '.tsx', '.py', '.rb']
|
|
65
|
+
// Use REAL extensions from project (if provided in projectData)
|
|
66
|
+
if (projectData && projectData.extensions) {
|
|
67
|
+
// projectData.extensions is {'.js': 45, '.ts': 23, ...}
|
|
68
|
+
patterns.extensions = Object.keys(projectData.extensions)
|
|
69
|
+
.filter(ext => ext.startsWith('.'))
|
|
70
|
+
.slice(0, 15); // Top 15 extensions
|
|
111
71
|
}
|
|
112
72
|
|
|
73
|
+
// Use REAL directories from project (if provided in projectData)
|
|
74
|
+
if (projectData && projectData.directories) {
|
|
75
|
+
patterns.include = projectData.directories.filter(dir =>
|
|
76
|
+
!patterns.exclude.includes(dir)
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// If no real data available, use minimal universal fallback
|
|
81
|
+
if (patterns.extensions.length === 0) {
|
|
82
|
+
patterns.extensions = ['*']; // All files
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (patterns.include.length === 0) {
|
|
86
|
+
patterns.include = ['.']; // Root directory
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// NO domain-specific hardcoding
|
|
90
|
+
// Claude decides what files matter based on actual analysis
|
|
91
|
+
|
|
113
92
|
return patterns
|
|
114
93
|
}
|
|
115
94
|
|
|
@@ -157,7 +136,7 @@ class ContextEstimator {
|
|
|
157
136
|
// Remove duplicates and sort
|
|
158
137
|
return [...new Set(files)].sort()
|
|
159
138
|
} catch (error) {
|
|
160
|
-
|
|
139
|
+
log.error('Error finding files:', error.message)
|
|
161
140
|
return []
|
|
162
141
|
}
|
|
163
142
|
}
|
|
@@ -12,6 +12,7 @@ const fs = require('fs').promises
|
|
|
12
12
|
const path = require('path')
|
|
13
13
|
const os = require('os')
|
|
14
14
|
const crypto = require('crypto')
|
|
15
|
+
const log = require('../utils/logger')
|
|
15
16
|
|
|
16
17
|
class SmartCache {
|
|
17
18
|
constructor(projectId = null) {
|
|
@@ -69,7 +70,7 @@ class SmartCache {
|
|
|
69
70
|
|
|
70
71
|
// Persist to disk (async, don't wait)
|
|
71
72
|
this.persistToDisk().catch(err => {
|
|
72
|
-
|
|
73
|
+
log.error('Cache persist error:', err.message)
|
|
73
74
|
})
|
|
74
75
|
}
|
|
75
76
|
|