prjct-cli 0.10.5 → 0.10.8
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 +83 -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 +20 -11
- package/core/agentic/context-filter.js +118 -313
- package/core/agentic/prompt-builder.js +79 -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/project-analysis.md +78 -0
- package/templates/global/CLAUDE.md +137 -135
- package/core/domain/tech-detector.js +0 -365
|
@@ -1,365 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TechDetector - Dynamic Technology Detection
|
|
3
|
-
*
|
|
4
|
-
* NO HARDCODING - Detects technologies from actual project files
|
|
5
|
-
* Works with ANY technology stack (Elixir, Phoenix, Svelte, etc.)
|
|
6
|
-
*
|
|
7
|
-
* @version 1.0.0
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const fs = require('fs').promises
|
|
11
|
-
const path = require('path')
|
|
12
|
-
|
|
13
|
-
class TechDetector {
|
|
14
|
-
constructor(projectPath) {
|
|
15
|
-
this.projectPath = projectPath
|
|
16
|
-
this.cache = null
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Detect all technologies in the project
|
|
21
|
-
* Returns structured data about languages, frameworks, tools
|
|
22
|
-
* @returns {Promise<Object>} - { languages: [], frameworks: [], tools: [], packageManagers: [] }
|
|
23
|
-
*/
|
|
24
|
-
async detectAll() {
|
|
25
|
-
if (this.cache) {
|
|
26
|
-
return this.cache
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const detected = {
|
|
30
|
-
languages: [],
|
|
31
|
-
frameworks: [],
|
|
32
|
-
tools: [],
|
|
33
|
-
packageManagers: [],
|
|
34
|
-
databases: [],
|
|
35
|
-
buildTools: [],
|
|
36
|
-
testFrameworks: []
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Detect from package managers (most reliable source)
|
|
40
|
-
await this.detectFromPackageJson(detected)
|
|
41
|
-
await this.detectFromCargoToml(detected)
|
|
42
|
-
await this.detectFromGoMod(detected)
|
|
43
|
-
await this.detectFromRequirements(detected)
|
|
44
|
-
await this.detectFromGemfile(detected)
|
|
45
|
-
await this.detectFromMixExs(detected) // Elixir
|
|
46
|
-
await this.detectFromPomXml(detected) // Maven/Java
|
|
47
|
-
await this.detectFromComposerJson(detected) // PHP
|
|
48
|
-
|
|
49
|
-
// Detect from config files
|
|
50
|
-
await this.detectFromConfigFiles(detected)
|
|
51
|
-
|
|
52
|
-
// Detect from directory structure
|
|
53
|
-
await this.detectFromStructure(detected)
|
|
54
|
-
|
|
55
|
-
// Cache result
|
|
56
|
-
this.cache = detected
|
|
57
|
-
return detected
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Detect from package.json (Node.js/JavaScript/TypeScript)
|
|
62
|
-
*/
|
|
63
|
-
async detectFromPackageJson(detected) {
|
|
64
|
-
try {
|
|
65
|
-
const packagePath = path.join(this.projectPath, 'package.json')
|
|
66
|
-
const content = await fs.readFile(packagePath, 'utf-8')
|
|
67
|
-
const pkg = JSON.parse(content)
|
|
68
|
-
|
|
69
|
-
detected.packageManagers.push('npm')
|
|
70
|
-
|
|
71
|
-
// Language
|
|
72
|
-
if (pkg.dependencies?.typescript || pkg.devDependencies?.typescript) {
|
|
73
|
-
detected.languages.push('TypeScript')
|
|
74
|
-
} else {
|
|
75
|
-
detected.languages.push('JavaScript')
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Collect all dependencies (no hardcoding - just list them)
|
|
79
|
-
const allDeps = {
|
|
80
|
-
...(pkg.dependencies || {}),
|
|
81
|
-
...(pkg.devDependencies || {})
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Frameworks and tools are in dependencies - let Claude decide what's important
|
|
85
|
-
// We just collect the names, no assumptions
|
|
86
|
-
const depNames = Object.keys(allDeps)
|
|
87
|
-
|
|
88
|
-
// Common patterns (but not hardcoded - just helpers)
|
|
89
|
-
for (const dep of depNames) {
|
|
90
|
-
// Frontend frameworks
|
|
91
|
-
if (['react', 'vue', 'angular', 'svelte'].includes(dep.toLowerCase())) {
|
|
92
|
-
detected.frameworks.push(dep)
|
|
93
|
-
}
|
|
94
|
-
// Meta-frameworks
|
|
95
|
-
else if (['next', 'nuxt', 'sveltekit', 'remix'].includes(dep.toLowerCase())) {
|
|
96
|
-
detected.frameworks.push(dep)
|
|
97
|
-
}
|
|
98
|
-
// Backend frameworks
|
|
99
|
-
else if (['express', 'fastify', 'koa', 'hapi', 'nest'].includes(dep.toLowerCase())) {
|
|
100
|
-
detected.frameworks.push(dep)
|
|
101
|
-
}
|
|
102
|
-
// Build tools
|
|
103
|
-
else if (['vite', 'webpack', 'rollup', 'esbuild', 'parcel'].includes(dep.toLowerCase())) {
|
|
104
|
-
detected.buildTools.push(dep)
|
|
105
|
-
}
|
|
106
|
-
// Test frameworks
|
|
107
|
-
else if (['jest', 'vitest', 'mocha', 'jasmine', 'cypress', 'playwright'].includes(dep.toLowerCase())) {
|
|
108
|
-
detected.testFrameworks.push(dep)
|
|
109
|
-
}
|
|
110
|
-
// Databases
|
|
111
|
-
else if (['mongoose', 'sequelize', 'prisma', 'typeorm'].includes(dep.toLowerCase())) {
|
|
112
|
-
detected.databases.push(dep)
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Also check for yarn/pnpm
|
|
117
|
-
if (await this.fileExists('yarn.lock')) {
|
|
118
|
-
detected.packageManagers.push('yarn')
|
|
119
|
-
}
|
|
120
|
-
if (await this.fileExists('pnpm-lock.yaml')) {
|
|
121
|
-
detected.packageManagers.push('pnpm')
|
|
122
|
-
}
|
|
123
|
-
} catch (error) {
|
|
124
|
-
// File doesn't exist or invalid JSON - skip
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Detect from Cargo.toml (Rust)
|
|
130
|
-
*/
|
|
131
|
-
async detectFromCargoToml(detected) {
|
|
132
|
-
try {
|
|
133
|
-
const cargoPath = path.join(this.projectPath, 'Cargo.toml')
|
|
134
|
-
await fs.readFile(cargoPath, 'utf-8')
|
|
135
|
-
|
|
136
|
-
detected.languages.push('Rust')
|
|
137
|
-
detected.packageManagers.push('Cargo')
|
|
138
|
-
} catch (error) {
|
|
139
|
-
// File doesn't exist - skip
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Detect from go.mod (Go)
|
|
145
|
-
*/
|
|
146
|
-
async detectFromGoMod(detected) {
|
|
147
|
-
try {
|
|
148
|
-
const goModPath = path.join(this.projectPath, 'go.mod')
|
|
149
|
-
await fs.readFile(goModPath, 'utf-8')
|
|
150
|
-
|
|
151
|
-
detected.languages.push('Go')
|
|
152
|
-
detected.packageManagers.push('Go Modules')
|
|
153
|
-
} catch (error) {
|
|
154
|
-
// File doesn't exist - skip
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Detect from requirements.txt (Python)
|
|
160
|
-
*/
|
|
161
|
-
async detectFromRequirements(detected) {
|
|
162
|
-
try {
|
|
163
|
-
const reqPath = path.join(this.projectPath, 'requirements.txt')
|
|
164
|
-
const content = await fs.readFile(reqPath, 'utf-8')
|
|
165
|
-
|
|
166
|
-
detected.languages.push('Python')
|
|
167
|
-
detected.packageManagers.push('pip')
|
|
168
|
-
|
|
169
|
-
// Detect common frameworks
|
|
170
|
-
const lines = content.split('\n').map(l => l.trim().toLowerCase())
|
|
171
|
-
if (lines.some(l => l.includes('django'))) detected.frameworks.push('Django')
|
|
172
|
-
if (lines.some(l => l.includes('flask'))) detected.frameworks.push('Flask')
|
|
173
|
-
if (lines.some(l => l.includes('fastapi'))) detected.frameworks.push('FastAPI')
|
|
174
|
-
} catch (error) {
|
|
175
|
-
// File doesn't exist - skip
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Detect from Gemfile (Ruby)
|
|
181
|
-
*/
|
|
182
|
-
async detectFromGemfile(detected) {
|
|
183
|
-
try {
|
|
184
|
-
const gemfilePath = path.join(this.projectPath, 'Gemfile')
|
|
185
|
-
const content = await fs.readFile(gemfilePath, 'utf-8')
|
|
186
|
-
|
|
187
|
-
detected.languages.push('Ruby')
|
|
188
|
-
detected.packageManagers.push('Bundler')
|
|
189
|
-
|
|
190
|
-
if (content.includes('rails')) {
|
|
191
|
-
detected.frameworks.push('Rails')
|
|
192
|
-
}
|
|
193
|
-
} catch (error) {
|
|
194
|
-
// File doesn't exist - skip
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Detect from mix.exs (Elixir)
|
|
200
|
-
*/
|
|
201
|
-
async detectFromMixExs(detected) {
|
|
202
|
-
try {
|
|
203
|
-
const mixPath = path.join(this.projectPath, 'mix.exs')
|
|
204
|
-
const content = await fs.readFile(mixPath, 'utf-8')
|
|
205
|
-
|
|
206
|
-
detected.languages.push('Elixir')
|
|
207
|
-
detected.packageManagers.push('Mix')
|
|
208
|
-
|
|
209
|
-
if (content.includes('phoenix')) {
|
|
210
|
-
detected.frameworks.push('Phoenix')
|
|
211
|
-
}
|
|
212
|
-
} catch (error) {
|
|
213
|
-
// File doesn't exist - skip
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Detect from pom.xml (Maven/Java)
|
|
219
|
-
*/
|
|
220
|
-
async detectFromPomXml(detected) {
|
|
221
|
-
try {
|
|
222
|
-
const pomPath = path.join(this.projectPath, 'pom.xml')
|
|
223
|
-
await fs.readFile(pomPath, 'utf-8')
|
|
224
|
-
|
|
225
|
-
detected.languages.push('Java')
|
|
226
|
-
detected.packageManagers.push('Maven')
|
|
227
|
-
} catch (error) {
|
|
228
|
-
// File doesn't exist - skip
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Detect from composer.json (PHP)
|
|
234
|
-
*/
|
|
235
|
-
async detectFromComposerJson(detected) {
|
|
236
|
-
try {
|
|
237
|
-
const composerPath = path.join(this.projectPath, 'composer.json')
|
|
238
|
-
const content = await fs.readFile(composerPath, 'utf-8')
|
|
239
|
-
const composer = JSON.parse(content)
|
|
240
|
-
|
|
241
|
-
detected.languages.push('PHP')
|
|
242
|
-
detected.packageManagers.push('Composer')
|
|
243
|
-
|
|
244
|
-
const allDeps = {
|
|
245
|
-
...(composer.require || {}),
|
|
246
|
-
...(composer['require-dev'] || {})
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (Object.keys(allDeps).some(d => d.includes('laravel'))) {
|
|
250
|
-
detected.frameworks.push('Laravel')
|
|
251
|
-
}
|
|
252
|
-
if (Object.keys(allDeps).some(d => d.includes('symfony'))) {
|
|
253
|
-
detected.frameworks.push('Symfony')
|
|
254
|
-
}
|
|
255
|
-
} catch (error) {
|
|
256
|
-
// File doesn't exist - skip
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Detect from config files (Docker, etc.)
|
|
262
|
-
*/
|
|
263
|
-
async detectFromConfigFiles(detected) {
|
|
264
|
-
// Docker
|
|
265
|
-
if (await this.fileExists('Dockerfile')) {
|
|
266
|
-
detected.tools.push('Docker')
|
|
267
|
-
}
|
|
268
|
-
if (await this.fileExists('docker-compose.yml') || await this.fileExists('docker-compose.yaml')) {
|
|
269
|
-
detected.tools.push('Docker Compose')
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Kubernetes
|
|
273
|
-
if (await this.fileExists('k8s') || await this.fileExists('kubernetes')) {
|
|
274
|
-
detected.tools.push('Kubernetes')
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// Terraform
|
|
278
|
-
if (await this.fileExists('.tf') || await this.findFiles('*.tf')) {
|
|
279
|
-
detected.tools.push('Terraform')
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* Detect from directory structure
|
|
285
|
-
*/
|
|
286
|
-
async detectFromStructure(_detected) {
|
|
287
|
-
try {
|
|
288
|
-
const entries = await fs.readdir(this.projectPath, { withFileTypes: true })
|
|
289
|
-
const dirs = entries.filter(e => e.isDirectory()).map(e => e.name)
|
|
290
|
-
|
|
291
|
-
// Common patterns (but not assumptions - just hints)
|
|
292
|
-
// Could analyze structure here in the future
|
|
293
|
-
if (dirs.includes('src') && dirs.includes('lib')) {
|
|
294
|
-
// Could be Elixir, but don't assume
|
|
295
|
-
}
|
|
296
|
-
} catch (error) {
|
|
297
|
-
// Can't read directory - skip
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* Get a summary string of detected technologies
|
|
303
|
-
* Useful for agent generation
|
|
304
|
-
*/
|
|
305
|
-
async getSummary() {
|
|
306
|
-
const tech = await this.detectAll()
|
|
307
|
-
const parts = []
|
|
308
|
-
|
|
309
|
-
if (tech.languages.length > 0) {
|
|
310
|
-
parts.push(`Languages: ${tech.languages.join(', ')}`)
|
|
311
|
-
}
|
|
312
|
-
if (tech.frameworks.length > 0) {
|
|
313
|
-
parts.push(`Frameworks: ${tech.frameworks.join(', ')}`)
|
|
314
|
-
}
|
|
315
|
-
if (tech.tools.length > 0) {
|
|
316
|
-
parts.push(`Tools: ${tech.tools.join(', ')}`)
|
|
317
|
-
}
|
|
318
|
-
if (tech.databases.length > 0) {
|
|
319
|
-
parts.push(`Databases: ${tech.databases.join(', ')}`)
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
return parts.join(' | ')
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* Helper: Check if file exists
|
|
327
|
-
*/
|
|
328
|
-
async fileExists(filename) {
|
|
329
|
-
try {
|
|
330
|
-
await fs.access(path.join(this.projectPath, filename))
|
|
331
|
-
return true
|
|
332
|
-
} catch {
|
|
333
|
-
return false
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Helper: Find files matching pattern
|
|
339
|
-
*/
|
|
340
|
-
async findFiles(pattern) {
|
|
341
|
-
try {
|
|
342
|
-
const { exec } = require('child_process')
|
|
343
|
-
const { promisify } = require('util')
|
|
344
|
-
const execAsync = promisify(exec)
|
|
345
|
-
|
|
346
|
-
const { stdout } = await execAsync(
|
|
347
|
-
`find . -name "${pattern}" -type f ! -path "*/node_modules/*" ! -path "*/.git/*" | head -1`,
|
|
348
|
-
{ cwd: this.projectPath }
|
|
349
|
-
)
|
|
350
|
-
return stdout.trim().length > 0
|
|
351
|
-
} catch {
|
|
352
|
-
return false
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* Clear cache (useful after project changes)
|
|
358
|
-
*/
|
|
359
|
-
clearCache() {
|
|
360
|
-
this.cache = null
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
module.exports = TechDetector
|
|
365
|
-
|