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
|
@@ -15,6 +15,7 @@ const TaskAnalyzer = require('../domain/task-analyzer');
|
|
|
15
15
|
const AgentMatcher = require('../domain/agent-matcher');
|
|
16
16
|
const SmartCache = require('../domain/smart-cache');
|
|
17
17
|
const AgentValidator = require('../domain/agent-validator');
|
|
18
|
+
const log = require('../utils/logger');
|
|
18
19
|
|
|
19
20
|
class MandatoryAgentRouter {
|
|
20
21
|
constructor() {
|
|
@@ -90,119 +91,24 @@ class MandatoryAgentRouter {
|
|
|
90
91
|
|
|
91
92
|
/**
|
|
92
93
|
* Analyze task to determine what type of expertise is needed
|
|
93
|
-
*
|
|
94
|
-
*
|
|
94
|
+
*
|
|
95
|
+
* 100% AGENTIC: Delegates to TaskAnalyzer which uses templates.
|
|
96
|
+
* NO hardcoded patterns or keyword lists.
|
|
95
97
|
*/
|
|
96
98
|
async analyzeTask(task, projectPath = null) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
// Get project technologies for better matching
|
|
101
|
-
let projectTech = null
|
|
102
|
-
if (projectPath) {
|
|
103
|
-
try {
|
|
104
|
-
const TechDetector = require('../domain/tech-detector');
|
|
105
|
-
const detector = new TechDetector(projectPath);
|
|
106
|
-
projectTech = await detector.detectAll();
|
|
107
|
-
} catch (error) {
|
|
108
|
-
// If detection fails, continue with keyword-based analysis
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Semantic patterns - broader, more flexible
|
|
113
|
-
const patterns = {
|
|
114
|
-
frontend: [
|
|
115
|
-
'component', 'ui', 'user interface', 'frontend', 'client',
|
|
116
|
-
'style', 'css', 'layout', 'responsive', 'design',
|
|
117
|
-
'page', 'view', 'template', 'render', 'display'
|
|
118
|
-
],
|
|
119
|
-
backend: [
|
|
120
|
-
'api', 'server', 'endpoint', 'route', 'middleware',
|
|
121
|
-
'auth', 'authentication', 'authorization', 'jwt', 'session',
|
|
122
|
-
'backend', 'service', 'controller', 'handler'
|
|
123
|
-
],
|
|
124
|
-
database: [
|
|
125
|
-
'database', 'db', 'query', 'migration', 'schema', 'model',
|
|
126
|
-
'sql', 'data', 'table', 'collection', 'index', 'relation'
|
|
127
|
-
],
|
|
128
|
-
devops: [
|
|
129
|
-
'deploy', 'deployment', 'docker', 'kubernetes', 'k8s',
|
|
130
|
-
'ci/cd', 'pipeline', 'build', 'ship', 'release',
|
|
131
|
-
'production', 'infrastructure', 'container', 'orchestration'
|
|
132
|
-
],
|
|
133
|
-
qa: [
|
|
134
|
-
'test', 'testing', 'bug', 'error', 'fix', 'debug', 'issue',
|
|
135
|
-
'quality', 'coverage', 'unit test', 'integration test',
|
|
136
|
-
'e2e', 'spec', 'assertion', 'validation'
|
|
137
|
-
],
|
|
138
|
-
architecture: [
|
|
139
|
-
'design', 'architecture', 'pattern', 'structure',
|
|
140
|
-
'refactor', 'refactoring', 'organize', 'plan',
|
|
141
|
-
'feature', 'system', 'module', 'component design'
|
|
142
|
-
]
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
// If we have project tech, enhance patterns with actual technologies
|
|
146
|
-
if (projectTech) {
|
|
147
|
-
// Add detected frontend frameworks to frontend patterns
|
|
148
|
-
const frontendTech = [
|
|
149
|
-
...projectTech.frameworks.filter(f => ['react', 'vue', 'angular', 'svelte', 'next', 'nuxt'].includes(f.toLowerCase())),
|
|
150
|
-
...projectTech.buildTools.filter(t => ['vite', 'webpack'].includes(t.toLowerCase()))
|
|
151
|
-
];
|
|
152
|
-
if (frontendTech.length > 0) {
|
|
153
|
-
patterns.frontend.push(...frontendTech.map(t => t.toLowerCase()));
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Add detected backend frameworks to backend patterns
|
|
157
|
-
const backendTech = projectTech.frameworks.filter(f =>
|
|
158
|
-
['express', 'fastify', 'django', 'flask', 'rails', 'phoenix'].includes(f.toLowerCase())
|
|
159
|
-
);
|
|
160
|
-
if (backendTech.length > 0) {
|
|
161
|
-
patterns.backend.push(...backendTech.map(t => t.toLowerCase()));
|
|
162
|
-
}
|
|
99
|
+
// Use TaskAnalyzer for semantic analysis (template-driven)
|
|
100
|
+
if (this.taskAnalyzer) {
|
|
101
|
+
return await this.taskAnalyzer.analyzeTask(task);
|
|
163
102
|
}
|
|
164
103
|
|
|
165
|
-
//
|
|
166
|
-
const { detectedDomain, matchedKeywords, confidence } = this.detectDomain(description, type, patterns);
|
|
167
|
-
|
|
104
|
+
// Fallback: Return minimal analysis, let Claude decide in prompt
|
|
168
105
|
return {
|
|
169
|
-
domain:
|
|
170
|
-
confidence:
|
|
171
|
-
matchedKeywords,
|
|
172
|
-
reason: `Detected ${detectedDomain} task based on: ${matchedKeywords.join(', ')}`,
|
|
173
|
-
alternatives: this.getSimilarDomains(detectedDomain),
|
|
174
|
-
projectTechnologies: projectTech
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Detect domain based on patterns
|
|
180
|
-
*/
|
|
181
|
-
detectDomain(description, type, patterns) {
|
|
182
|
-
// Simple domain detection based on keywords
|
|
183
|
-
const matches = Object.entries(patterns).map(([domain, keywords]) => {
|
|
184
|
-
const found = keywords.filter(keyword =>
|
|
185
|
-
description.includes(keyword) || type.includes(keyword)
|
|
186
|
-
);
|
|
187
|
-
return { domain, keywords: found, count: found.length };
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
// Sort by count descending
|
|
191
|
-
const sorted = matches.sort((a, b) => b.count - a.count);
|
|
192
|
-
const bestMatch = sorted[0];
|
|
193
|
-
|
|
194
|
-
if (bestMatch && bestMatch.count > 0) {
|
|
195
|
-
return {
|
|
196
|
-
detectedDomain: bestMatch.domain,
|
|
197
|
-
matchedKeywords: bestMatch.keywords,
|
|
198
|
-
confidence: bestMatch.count
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return {
|
|
203
|
-
detectedDomain: 'generalist',
|
|
106
|
+
domain: 'generalist',
|
|
107
|
+
confidence: 0.5,
|
|
204
108
|
matchedKeywords: [],
|
|
205
|
-
|
|
109
|
+
reason: 'Using generalist - Claude will analyze task in context',
|
|
110
|
+
alternatives: ['full-stack'],
|
|
111
|
+
projectTechnologies: null
|
|
206
112
|
};
|
|
207
113
|
}
|
|
208
114
|
|
|
@@ -282,7 +188,7 @@ class MandatoryAgentRouter {
|
|
|
282
188
|
// Validate after generation
|
|
283
189
|
const postValidation = this.agentValidator.validateAfterGeneration(agent);
|
|
284
190
|
if (!postValidation.valid) {
|
|
285
|
-
|
|
191
|
+
log.warn(`Agent validation issues: ${postValidation.issues.join(', ')}`);
|
|
286
192
|
}
|
|
287
193
|
|
|
288
194
|
// Cache for reuse
|
|
@@ -293,28 +199,27 @@ class MandatoryAgentRouter {
|
|
|
293
199
|
|
|
294
200
|
/**
|
|
295
201
|
* Build expertise string from tech stack
|
|
202
|
+
*
|
|
203
|
+
* 100% AGENTIC: No hardcoded framework lists.
|
|
204
|
+
* Returns ALL tech, Claude decides what's relevant.
|
|
296
205
|
*/
|
|
297
206
|
buildExpertiseFromTech(projectTech, domain) {
|
|
298
207
|
const parts = []
|
|
299
208
|
|
|
209
|
+
// Include ALL languages - no filtering
|
|
300
210
|
if (projectTech.languages && projectTech.languages.length > 0) {
|
|
301
211
|
parts.push(projectTech.languages.join(', '))
|
|
302
212
|
}
|
|
303
213
|
|
|
214
|
+
// Include ALL frameworks - Claude decides relevance
|
|
215
|
+
// NO hardcoded lists like ['react', 'vue', 'angular']
|
|
304
216
|
if (projectTech.frameworks && projectTech.frameworks.length > 0) {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
return ['express', 'fastify', 'django', 'flask', 'rails', 'phoenix'].some(tech => fLower.includes(tech))
|
|
312
|
-
}
|
|
313
|
-
return true
|
|
314
|
-
})
|
|
315
|
-
if (relevantFrameworks.length > 0) {
|
|
316
|
-
parts.push(relevantFrameworks.join(', '))
|
|
317
|
-
}
|
|
217
|
+
parts.push(projectTech.frameworks.join(', '))
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Include tools if present
|
|
221
|
+
if (projectTech.tools && projectTech.tools.length > 0) {
|
|
222
|
+
parts.push(projectTech.tools.join(', '))
|
|
318
223
|
}
|
|
319
224
|
|
|
320
225
|
return parts.join(', ') || `${domain} development`
|
|
@@ -375,51 +280,23 @@ class MandatoryAgentRouter {
|
|
|
375
280
|
|
|
376
281
|
/**
|
|
377
282
|
* Filter context to only what's relevant for this agent
|
|
283
|
+
*
|
|
284
|
+
* 100% AGENTIC: No hardcoded directory/extension lists.
|
|
285
|
+
* Only excludes universal noise (node_modules, .git, dist).
|
|
286
|
+
* Claude decides relevance based on task.
|
|
378
287
|
*/
|
|
379
288
|
async filterContextForAgent(agent, fullContext, taskAnalysis) {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
// Define what each agent type should see
|
|
383
|
-
const contextPatterns = {
|
|
384
|
-
frontend: {
|
|
385
|
-
include: ['components', 'views', 'styles', 'pages', 'layouts'],
|
|
386
|
-
exclude: ['node_modules', 'dist', 'build', 'migrations'],
|
|
387
|
-
extensions: ['.jsx', '.tsx', '.vue', '.css', '.scss', '.styled.js']
|
|
388
|
-
},
|
|
389
|
-
backend: {
|
|
390
|
-
include: ['routes', 'controllers', 'services', 'middleware', 'api'],
|
|
391
|
-
exclude: ['node_modules', 'dist', 'public', 'styles'],
|
|
392
|
-
extensions: ['.js', '.ts', '.py', '.rb', '.go', '.java']
|
|
393
|
-
},
|
|
394
|
-
database: {
|
|
395
|
-
include: ['models', 'migrations', 'schemas', 'seeds', 'queries'],
|
|
396
|
-
exclude: ['node_modules', 'public', 'styles', 'components'],
|
|
397
|
-
extensions: ['.sql', '.js', '.ts', '.rb', '.py']
|
|
398
|
-
},
|
|
399
|
-
devops: {
|
|
400
|
-
include: ['.github', '.gitlab', 'docker', 'k8s', 'terraform'],
|
|
401
|
-
exclude: ['node_modules', 'src', 'public'],
|
|
402
|
-
extensions: ['.yml', '.yaml', '.dockerfile', '.sh', '.tf']
|
|
403
|
-
},
|
|
404
|
-
qa: {
|
|
405
|
-
include: ['tests', 'spec', '__tests__', 'test'],
|
|
406
|
-
exclude: ['node_modules', 'dist', 'build'],
|
|
407
|
-
extensions: ['.test.js', '.spec.js', '.test.ts', '.spec.ts']
|
|
408
|
-
}
|
|
409
|
-
};
|
|
289
|
+
// Universal exclusions that apply to ALL projects
|
|
290
|
+
const universalExclusions = ['node_modules', '.git', 'dist', 'build', '.next', 'target', 'vendor'];
|
|
410
291
|
|
|
411
|
-
|
|
412
|
-
include: [],
|
|
413
|
-
exclude: ['node_modules', 'dist', 'build'],
|
|
414
|
-
extensions: []
|
|
415
|
-
};
|
|
416
|
-
|
|
417
|
-
// Filter the context based on patterns
|
|
292
|
+
// Filter only universal noise - let Claude decide the rest
|
|
418
293
|
const filtered = {
|
|
419
294
|
...fullContext,
|
|
420
|
-
files:
|
|
421
|
-
|
|
422
|
-
|
|
295
|
+
files: (fullContext.files || []).filter(file =>
|
|
296
|
+
!universalExclusions.some(exc => file.includes(exc))
|
|
297
|
+
),
|
|
298
|
+
relevantOnly: false, // Claude decides relevance, not us
|
|
299
|
+
filterApplied: 'universal-only'
|
|
423
300
|
};
|
|
424
301
|
|
|
425
302
|
return filtered;
|
|
@@ -498,25 +375,20 @@ class MandatoryAgentRouter {
|
|
|
498
375
|
const logEntry = JSON.stringify(usage) + '\n';
|
|
499
376
|
await fs.appendFile(logPath, logEntry);
|
|
500
377
|
} catch (error) {
|
|
501
|
-
|
|
502
|
-
console.error('Failed to log agent usage:', error.message);
|
|
378
|
+
log.error('Failed to log agent usage:', error.message);
|
|
503
379
|
}
|
|
504
380
|
}
|
|
505
381
|
|
|
506
382
|
/**
|
|
507
383
|
* Get similar domains for fallback
|
|
384
|
+
*
|
|
385
|
+
* 100% AGENTIC: Returns generic fallback.
|
|
386
|
+
* Claude determines domain relationships based on context.
|
|
508
387
|
*/
|
|
509
388
|
getSimilarDomains(domain) {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
database: ['backend', 'data'],
|
|
514
|
-
devops: ['infrastructure', 'platform'],
|
|
515
|
-
qa: ['testing', 'quality'],
|
|
516
|
-
architecture: ['design', 'planning']
|
|
517
|
-
};
|
|
518
|
-
|
|
519
|
-
return similarities[domain] || ['generalist'];
|
|
389
|
+
// No hardcoded domain relationships
|
|
390
|
+
// Claude decides what's similar based on actual project context
|
|
391
|
+
return ['full-stack', 'generalist'];
|
|
520
392
|
}
|
|
521
393
|
|
|
522
394
|
/**
|
|
@@ -168,29 +168,38 @@ class ContextBuilder {
|
|
|
168
168
|
async loadStateForCommand(context, commandName) {
|
|
169
169
|
// Command-specific file requirements
|
|
170
170
|
// Minimizes context window usage
|
|
171
|
+
// CRITICAL: Always include 'analysis' for pattern detection
|
|
171
172
|
const commandFileMap = {
|
|
172
173
|
// Core workflow
|
|
173
|
-
'now': ['now', 'next'],
|
|
174
|
-
'done': ['now', 'next', 'metrics'],
|
|
175
|
-
'next': ['next'],
|
|
174
|
+
'now': ['now', 'next', 'analysis'],
|
|
175
|
+
'done': ['now', 'next', 'metrics', 'analysis'],
|
|
176
|
+
'next': ['next', 'analysis'],
|
|
176
177
|
|
|
177
178
|
// Progress
|
|
178
|
-
'ship': ['now', 'shipped', 'metrics'],
|
|
179
|
-
'recap': ['shipped', 'metrics', 'now'],
|
|
180
|
-
'progress': ['shipped', 'metrics'],
|
|
179
|
+
'ship': ['now', 'shipped', 'metrics', 'analysis'],
|
|
180
|
+
'recap': ['shipped', 'metrics', 'now', 'analysis'],
|
|
181
|
+
'progress': ['shipped', 'metrics', 'analysis'],
|
|
181
182
|
|
|
182
183
|
// Planning
|
|
183
|
-
'idea': ['ideas', 'next'],
|
|
184
|
-
'feature': ['roadmap', 'next', 'ideas'],
|
|
185
|
-
'roadmap': ['roadmap'],
|
|
186
|
-
'spec': ['roadmap', 'next', 'specs'],
|
|
184
|
+
'idea': ['ideas', 'next', 'analysis'],
|
|
185
|
+
'feature': ['roadmap', 'next', 'ideas', 'analysis'],
|
|
186
|
+
'roadmap': ['roadmap', 'analysis'],
|
|
187
|
+
'spec': ['roadmap', 'next', 'specs', 'analysis'],
|
|
187
188
|
|
|
188
189
|
// Analysis
|
|
189
190
|
'analyze': ['analysis', 'context'],
|
|
190
191
|
'sync': ['analysis', 'context', 'now'],
|
|
191
192
|
|
|
193
|
+
// Code modification commands - ALWAYS need analysis for patterns
|
|
194
|
+
'work': ['now', 'next', 'analysis', 'context'],
|
|
195
|
+
'build': ['now', 'next', 'analysis', 'context'],
|
|
196
|
+
'design': ['analysis', 'context'],
|
|
197
|
+
'cleanup': ['analysis', 'context'],
|
|
198
|
+
'fix': ['analysis', 'context'],
|
|
199
|
+
'test': ['analysis', 'context'],
|
|
200
|
+
|
|
192
201
|
// All files (fallback)
|
|
193
|
-
'default':
|
|
202
|
+
'default': ['analysis'] // Always include analysis even for unknown commands
|
|
194
203
|
}
|
|
195
204
|
|
|
196
205
|
const requiredFiles = commandFileMap[commandName] || commandFileMap.default
|