prjct-cli 0.10.6 → 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 +70 -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/core/domain/tech-detector.js +0 -365
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
allowed-tools: [Read, Glob, Bash]
|
|
3
|
+
description: 'Analyze project technology stack - Claude reads and decides'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Project Analysis Instructions
|
|
7
|
+
|
|
8
|
+
## Objective
|
|
9
|
+
|
|
10
|
+
Determine the technology stack by READING actual files, not assuming.
|
|
11
|
+
|
|
12
|
+
## Step 1: Read Dependency Files
|
|
13
|
+
|
|
14
|
+
Read these files if they exist:
|
|
15
|
+
|
|
16
|
+
- `package.json` → Node.js/JavaScript/TypeScript project
|
|
17
|
+
- `Cargo.toml` → Rust project
|
|
18
|
+
- `go.mod` → Go project
|
|
19
|
+
- `requirements.txt` or `pyproject.toml` → Python project
|
|
20
|
+
- `Gemfile` → Ruby project
|
|
21
|
+
- `mix.exs` → Elixir project
|
|
22
|
+
- `pom.xml` or `build.gradle` → Java project
|
|
23
|
+
- `composer.json` → PHP project
|
|
24
|
+
|
|
25
|
+
**Extract actual dependencies** - don't guess frameworks.
|
|
26
|
+
|
|
27
|
+
## Step 2: Examine Directory Structure
|
|
28
|
+
|
|
29
|
+
List the root directory to identify:
|
|
30
|
+
|
|
31
|
+
- Source directories (src/, lib/, app/, cmd/)
|
|
32
|
+
- Test directories (tests/, spec/, __tests__/)
|
|
33
|
+
- Config directories (.github/, .gitlab/, .vscode/)
|
|
34
|
+
- Build outputs (dist/, build/, target/)
|
|
35
|
+
|
|
36
|
+
## Step 3: Check Configuration Files
|
|
37
|
+
|
|
38
|
+
Look for:
|
|
39
|
+
|
|
40
|
+
- `tsconfig.json` → TypeScript configuration
|
|
41
|
+
- `Dockerfile` → Docker usage
|
|
42
|
+
- `docker-compose.yml` → Multi-container setup
|
|
43
|
+
- `.eslintrc`, `.prettierrc` → Linting/formatting
|
|
44
|
+
- `jest.config.js`, `vitest.config.ts` → Testing setup
|
|
45
|
+
- `.env*` files → Environment configuration
|
|
46
|
+
|
|
47
|
+
## Step 4: Determine Stack
|
|
48
|
+
|
|
49
|
+
Based on what you READ (not assumed):
|
|
50
|
+
|
|
51
|
+
1. **Languages**: Identify from file extensions and configs
|
|
52
|
+
2. **Frameworks**: Extract from actual dependencies
|
|
53
|
+
3. **Tools**: Identify from config files present
|
|
54
|
+
4. **Databases**: Look for DB clients in dependencies
|
|
55
|
+
5. **Testing**: Identify test frameworks from configs/deps
|
|
56
|
+
|
|
57
|
+
## Output Format
|
|
58
|
+
|
|
59
|
+
Provide analysis in this structure:
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"languages": ["actual languages found"],
|
|
64
|
+
"frameworks": ["actual frameworks from deps"],
|
|
65
|
+
"tools": ["tools with config files present"],
|
|
66
|
+
"databases": ["database clients in deps"],
|
|
67
|
+
"testing": ["test frameworks found"],
|
|
68
|
+
"notes": "any observations about the project"
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Rules
|
|
73
|
+
|
|
74
|
+
- **NO hardcoded assumptions** - React doesn't mean "frontend"
|
|
75
|
+
- **READ before deciding** - Don't guess based on filenames
|
|
76
|
+
- **Any stack works** - Elixir, Rust, Go, Python, etc.
|
|
77
|
+
- **Be specific** - Include versions when available
|
|
78
|
+
- **Note uncertainty** - If unclear, say so
|
|
@@ -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
|
-
|