prjct-cli 0.8.6 → 0.9.1

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.
Files changed (47) hide show
  1. package/CHANGELOG.md +216 -0
  2. package/CLAUDE.md +34 -0
  3. package/core/agentic/agent-router.js +482 -0
  4. package/core/agentic/command-executor.js +70 -15
  5. package/core/agentic/context-builder.js +4 -3
  6. package/core/agentic/context-filter.js +545 -0
  7. package/core/agentic/prompt-builder.js +48 -38
  8. package/core/agentic/tool-registry.js +35 -0
  9. package/core/command-registry.js +104 -164
  10. package/core/commands.js +84 -0
  11. package/core/domain/agent-generator.js +55 -44
  12. package/core/domain/architecture-generator.js +561 -0
  13. package/core/domain/task-stack.js +496 -0
  14. package/core/infrastructure/legacy-installer-detector.js +546 -0
  15. package/core/infrastructure/session-manager.js +14 -2
  16. package/core/infrastructure/setup.js +29 -11
  17. package/core/utils/jsonl-helper.js +137 -0
  18. package/package.json +1 -1
  19. package/scripts/install.sh +45 -8
  20. package/scripts/postinstall.js +5 -5
  21. package/templates/agents/AGENTS.md +3 -3
  22. package/templates/commands/analyze.md +10 -53
  23. package/templates/commands/ask.md +25 -338
  24. package/templates/commands/bug.md +11 -70
  25. package/templates/commands/build.md +8 -35
  26. package/templates/commands/cleanup.md +9 -32
  27. package/templates/commands/dash.md +241 -0
  28. package/templates/commands/design.md +5 -28
  29. package/templates/commands/done.md +6 -20
  30. package/templates/commands/feature.md +12 -225
  31. package/templates/commands/help.md +26 -313
  32. package/templates/commands/idea.md +7 -25
  33. package/templates/commands/init.md +15 -191
  34. package/templates/commands/migrate-all.md +25 -84
  35. package/templates/commands/next.md +6 -26
  36. package/templates/commands/now.md +6 -25
  37. package/templates/commands/pause.md +18 -0
  38. package/templates/commands/progress.md +5 -50
  39. package/templates/commands/recap.md +5 -54
  40. package/templates/commands/resume.md +97 -0
  41. package/templates/commands/ship.md +14 -135
  42. package/templates/commands/status.md +7 -32
  43. package/templates/commands/suggest.md +36 -495
  44. package/templates/commands/sync.md +7 -24
  45. package/templates/commands/work.md +44 -0
  46. package/templates/commands/workflow.md +3 -25
  47. package/templates/planning-methodology.md +195 -0
@@ -0,0 +1,482 @@
1
+ /**
2
+ * Mandatory Agent Router
3
+ *
4
+ * CRITICAL: Ensures EVERY task is executed by a specialized agent
5
+ * No task can run without an assigned expert agent
6
+ *
7
+ * @version 1.0.0
8
+ */
9
+
10
+ const fs = require('fs').promises;
11
+ const path = require('path');
12
+ const AgentGenerator = require('../domain/agent-generator');
13
+
14
+ class MandatoryAgentRouter {
15
+ constructor() {
16
+ this.agentGenerator = new AgentGenerator();
17
+ this.agentCache = new Map();
18
+ this.usageLog = [];
19
+ }
20
+
21
+ /**
22
+ * Main entry point - ALL tasks MUST go through here
23
+ * @throws {Error} If no agent can be assigned
24
+ */
25
+ async executeTask(task, context, projectPath) {
26
+ // STEP 1: Analyze task to determine required expertise
27
+ const taskAnalysis = this.analyzeTask(task);
28
+
29
+ // STEP 2: Select or generate specialized agent (MANDATORY)
30
+ const agent = await this.assignAgent(taskAnalysis, context);
31
+
32
+ // STEP 3: Validate agent assignment
33
+ if (!agent || !agent.name) {
34
+ throw new Error(
35
+ `CRITICAL: No agent assigned for task "${task.description}".
36
+ System requires ALL tasks to use specialized agents.`
37
+ );
38
+ }
39
+
40
+ // STEP 4: Filter context for this specific agent
41
+ const filteredContext = await this.filterContextForAgent(
42
+ agent,
43
+ context,
44
+ taskAnalysis
45
+ );
46
+
47
+ // STEP 5: Log agent usage for tracking
48
+ this.logAgentUsage(task, agent, filteredContext);
49
+
50
+ // STEP 6: Return agent with filtered context
51
+ return {
52
+ agent,
53
+ context: filteredContext,
54
+ taskAnalysis,
55
+ routing: {
56
+ reason: taskAnalysis.reason,
57
+ confidence: taskAnalysis.confidence,
58
+ alternativeAgents: taskAnalysis.alternatives
59
+ }
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Analyze task to determine what type of expertise is needed
65
+ */
66
+ analyzeTask(task) {
67
+ const description = task.description?.toLowerCase() || '';
68
+ const type = task.type?.toLowerCase() || '';
69
+
70
+ // Keywords for different domains
71
+ const patterns = {
72
+ frontend: [
73
+ 'component', 'ui', 'react', 'vue', 'angular', 'style',
74
+ 'css', 'layout', 'responsive', 'user interface', 'frontend'
75
+ ],
76
+ backend: [
77
+ 'api', 'server', 'endpoint', 'route', 'middleware',
78
+ 'auth', 'authentication', 'jwt', 'session', 'backend'
79
+ ],
80
+ database: [
81
+ 'database', 'query', 'migration', 'schema', 'model',
82
+ 'sql', 'postgres', 'mysql', 'mongo', 'index'
83
+ ],
84
+ devops: [
85
+ 'deploy', 'docker', 'kubernetes', 'ci/cd', 'pipeline',
86
+ 'build', 'ship', 'release', 'production'
87
+ ],
88
+ qa: [
89
+ 'test', 'bug', 'error', 'fix', 'debug', 'issue',
90
+ 'quality', 'coverage', 'unit test', 'integration'
91
+ ],
92
+ architecture: [
93
+ 'design', 'architecture', 'pattern', 'structure',
94
+ 'refactor', 'organize', 'plan', 'feature'
95
+ ]
96
+ };
97
+
98
+ // Detect primary domain
99
+ let detectedDomain = 'generalist';
100
+ let confidence = 0;
101
+ let matchedKeywords = [];
102
+
103
+ for (const [domain, keywords] of Object.entries(patterns)) {
104
+ const matches = keywords.filter(keyword =>
105
+ description.includes(keyword) || type.includes(keyword)
106
+ );
107
+
108
+ if (matches.length > confidence) {
109
+ confidence = matches.length;
110
+ detectedDomain = domain;
111
+ matchedKeywords = matches;
112
+ }
113
+ }
114
+
115
+ // Detect technology stack
116
+ const techStack = this.detectTechnology(task, description);
117
+
118
+ return {
119
+ domain: detectedDomain,
120
+ confidence: confidence > 0 ? (confidence / 3) : 0.3, // confidence score
121
+ matchedKeywords,
122
+ techStack,
123
+ reason: `Detected ${detectedDomain} task based on: ${matchedKeywords.join(', ')}`,
124
+ alternatives: this.getSimilarDomains(detectedDomain)
125
+ };
126
+ }
127
+
128
+ /**
129
+ * Detect specific technologies mentioned or implied
130
+ */
131
+ detectTechnology(task, description) {
132
+ const technologies = {
133
+ languages: [],
134
+ frameworks: [],
135
+ databases: [],
136
+ tools: []
137
+ };
138
+
139
+ // Language detection
140
+ const languages = [
141
+ 'javascript', 'typescript', 'python', 'ruby', 'go',
142
+ 'rust', 'java', 'csharp', 'php', 'elixir', 'swift'
143
+ ];
144
+
145
+ const frameworks = [
146
+ 'react', 'vue', 'angular', 'express', 'django', 'rails',
147
+ 'spring', 'laravel', 'phoenix', 'gin', 'fastapi', 'nextjs'
148
+ ];
149
+
150
+ const databases = [
151
+ 'postgres', 'mysql', 'mongodb', 'redis', 'elasticsearch',
152
+ 'dynamodb', 'firebase', 'supabase', 'sqlite'
153
+ ];
154
+
155
+ // Check for each technology
156
+ languages.forEach(lang => {
157
+ if (description.includes(lang)) {
158
+ technologies.languages.push(lang);
159
+ }
160
+ });
161
+
162
+ frameworks.forEach(fw => {
163
+ if (description.includes(fw)) {
164
+ technologies.frameworks.push(fw);
165
+ }
166
+ });
167
+
168
+ databases.forEach(db => {
169
+ if (description.includes(db)) {
170
+ technologies.databases.push(db);
171
+ }
172
+ });
173
+
174
+ return technologies;
175
+ }
176
+
177
+ /**
178
+ * Assign the best agent for the task
179
+ * Creates a new one if needed
180
+ */
181
+ async assignAgent(taskAnalysis, context) {
182
+ const { domain, techStack } = taskAnalysis;
183
+
184
+ // Check cache first
185
+ const cacheKey = `${domain}-${techStack.languages.join('-')}`;
186
+ if (this.agentCache.has(cacheKey)) {
187
+ return this.agentCache.get(cacheKey);
188
+ }
189
+
190
+ // Generate specialized agent based on detection
191
+ const agent = await this.generateSpecializedAgent(domain, techStack, context);
192
+
193
+ // Cache for reuse
194
+ this.agentCache.set(cacheKey, agent);
195
+
196
+ return agent;
197
+ }
198
+
199
+ /**
200
+ * Generate a specialized agent for the detected domain and tech
201
+ */
202
+ async generateSpecializedAgent(domain, techStack, context) {
203
+ // Map domain to agent type
204
+ const agentTypes = {
205
+ frontend: 'frontend-specialist',
206
+ backend: 'backend-specialist',
207
+ database: 'database-specialist',
208
+ devops: 'devops-specialist',
209
+ qa: 'qa-specialist',
210
+ architecture: 'architect',
211
+ generalist: 'full-stack'
212
+ };
213
+
214
+ const agentType = agentTypes[domain] || 'full-stack';
215
+
216
+ // Generate with detected technologies
217
+ const config = {
218
+ domain,
219
+ techStack,
220
+ projectContext: context.projectSummary || '',
221
+ bestPractices: await this.getBestPractices(domain, techStack)
222
+ };
223
+
224
+ return this.agentGenerator.generateDynamicAgent(agentType, config);
225
+ }
226
+
227
+ /**
228
+ * Get best practices for the domain and tech stack
229
+ */
230
+ async getBestPractices(domain, techStack) {
231
+ const practices = [];
232
+
233
+ // Domain-specific best practices
234
+ const domainPractices = {
235
+ frontend: [
236
+ 'Component composition over inheritance',
237
+ 'State management patterns',
238
+ 'Responsive design principles',
239
+ 'Accessibility standards',
240
+ 'Performance optimization (lazy loading, memoization)'
241
+ ],
242
+ backend: [
243
+ 'RESTful API design principles',
244
+ 'Authentication and authorization patterns',
245
+ 'Error handling and logging',
246
+ 'Rate limiting and caching',
247
+ 'Database connection pooling'
248
+ ],
249
+ database: [
250
+ 'Normalization principles',
251
+ 'Index optimization',
252
+ 'Query performance tuning',
253
+ 'Transaction management',
254
+ 'Backup and recovery strategies'
255
+ ],
256
+ devops: [
257
+ 'CI/CD pipeline best practices',
258
+ 'Container orchestration',
259
+ 'Infrastructure as Code',
260
+ 'Monitoring and alerting',
261
+ 'Security scanning'
262
+ ],
263
+ qa: [
264
+ 'Test pyramid (unit, integration, e2e)',
265
+ 'Test coverage standards',
266
+ 'Mocking and stubbing',
267
+ 'Performance testing',
268
+ 'Security testing'
269
+ ]
270
+ };
271
+
272
+ practices.push(...(domainPractices[domain] || []));
273
+
274
+ // Technology-specific practices
275
+ if (techStack.languages.includes('javascript') || techStack.languages.includes('typescript')) {
276
+ practices.push('ES6+ features', 'Async/await patterns', 'Module system');
277
+ }
278
+
279
+ if (techStack.frameworks.includes('react')) {
280
+ practices.push('Hooks patterns', 'Context API', 'Component lifecycle');
281
+ }
282
+
283
+ if (techStack.languages.includes('ruby')) {
284
+ practices.push('Ruby idioms', 'Metaprogramming carefully', 'Convention over configuration');
285
+ }
286
+
287
+ if (techStack.languages.includes('go')) {
288
+ practices.push('Goroutines and channels', 'Error handling patterns', 'Interface design');
289
+ }
290
+
291
+ return practices;
292
+ }
293
+
294
+ /**
295
+ * Filter context to only what's relevant for this agent
296
+ */
297
+ async filterContextForAgent(agent, fullContext, taskAnalysis) {
298
+ const { domain } = taskAnalysis;
299
+
300
+ // Define what each agent type should see
301
+ const contextPatterns = {
302
+ frontend: {
303
+ include: ['components', 'views', 'styles', 'pages', 'layouts'],
304
+ exclude: ['node_modules', 'dist', 'build', 'migrations'],
305
+ extensions: ['.jsx', '.tsx', '.vue', '.css', '.scss', '.styled.js']
306
+ },
307
+ backend: {
308
+ include: ['routes', 'controllers', 'services', 'middleware', 'api'],
309
+ exclude: ['node_modules', 'dist', 'public', 'styles'],
310
+ extensions: ['.js', '.ts', '.py', '.rb', '.go', '.java']
311
+ },
312
+ database: {
313
+ include: ['models', 'migrations', 'schemas', 'seeds', 'queries'],
314
+ exclude: ['node_modules', 'public', 'styles', 'components'],
315
+ extensions: ['.sql', '.js', '.ts', '.rb', '.py']
316
+ },
317
+ devops: {
318
+ include: ['.github', '.gitlab', 'docker', 'k8s', 'terraform'],
319
+ exclude: ['node_modules', 'src', 'public'],
320
+ extensions: ['.yml', '.yaml', '.dockerfile', '.sh', '.tf']
321
+ },
322
+ qa: {
323
+ include: ['tests', 'spec', '__tests__', 'test'],
324
+ exclude: ['node_modules', 'dist', 'build'],
325
+ extensions: ['.test.js', '.spec.js', '.test.ts', '.spec.ts']
326
+ }
327
+ };
328
+
329
+ const pattern = contextPatterns[domain] || {
330
+ include: [],
331
+ exclude: ['node_modules', 'dist', 'build'],
332
+ extensions: []
333
+ };
334
+
335
+ // Filter the context based on patterns
336
+ const filtered = {
337
+ ...fullContext,
338
+ files: this.filterFiles(fullContext.files || [], pattern),
339
+ relevantOnly: true,
340
+ filterApplied: domain
341
+ };
342
+
343
+ return filtered;
344
+ }
345
+
346
+ /**
347
+ * Filter files based on patterns
348
+ */
349
+ filterFiles(files, pattern) {
350
+ return files.filter(file => {
351
+ // Check if file should be excluded
352
+ for (const exclude of pattern.exclude) {
353
+ if (file.includes(exclude)) return false;
354
+ }
355
+
356
+ // Check if file matches include patterns
357
+ if (pattern.include.length > 0) {
358
+ let matches = false;
359
+ for (const include of pattern.include) {
360
+ if (file.includes(include)) {
361
+ matches = true;
362
+ break;
363
+ }
364
+ }
365
+ if (!matches) return false;
366
+ }
367
+
368
+ // Check extensions if specified
369
+ if (pattern.extensions.length > 0) {
370
+ let hasValidExtension = false;
371
+ for (const ext of pattern.extensions) {
372
+ if (file.endsWith(ext)) {
373
+ hasValidExtension = true;
374
+ break;
375
+ }
376
+ }
377
+ if (!hasValidExtension) return false;
378
+ }
379
+
380
+ return true;
381
+ });
382
+ }
383
+
384
+ /**
385
+ * Log agent usage for metrics and optimization
386
+ */
387
+ logAgentUsage(task, agent, context) {
388
+ const usage = {
389
+ timestamp: new Date().toISOString(),
390
+ task: task.description,
391
+ agent: agent.name,
392
+ domain: agent.domain || 'unknown',
393
+ contextSize: context.files?.length || 0,
394
+ contextReduction: this.calculateContextReduction(context),
395
+ confidence: agent.confidence || 1.0
396
+ };
397
+
398
+ this.usageLog.push(usage);
399
+
400
+ // Also append to a log file for persistence
401
+ this.appendToLogFile(usage);
402
+
403
+ return usage;
404
+ }
405
+
406
+ /**
407
+ * Calculate how much context was reduced
408
+ */
409
+ calculateContextReduction(filteredContext) {
410
+ // This would compare against full context
411
+ // For now, estimate based on filtering
412
+ if (filteredContext.relevantOnly) {
413
+ return '70-90%'; // Typical reduction when filtering
414
+ }
415
+ return '0%';
416
+ }
417
+
418
+ /**
419
+ * Append usage to log file
420
+ */
421
+ async appendToLogFile(usage) {
422
+ try {
423
+ const logPath = path.join(
424
+ process.env.HOME,
425
+ '.prjct-cli',
426
+ 'agent-usage.jsonl'
427
+ );
428
+
429
+ const logEntry = JSON.stringify(usage) + '\n';
430
+ await fs.appendFile(logPath, logEntry);
431
+ } catch (error) {
432
+ // Log errors silently, don't break execution
433
+ console.error('Failed to log agent usage:', error.message);
434
+ }
435
+ }
436
+
437
+ /**
438
+ * Get similar domains for fallback
439
+ */
440
+ getSimilarDomains(domain) {
441
+ const similarities = {
442
+ frontend: ['fullstack', 'ui/ux'],
443
+ backend: ['fullstack', 'api', 'services'],
444
+ database: ['backend', 'data'],
445
+ devops: ['infrastructure', 'platform'],
446
+ qa: ['testing', 'quality'],
447
+ architecture: ['design', 'planning']
448
+ };
449
+
450
+ return similarities[domain] || ['generalist'];
451
+ }
452
+
453
+ /**
454
+ * Get usage statistics
455
+ */
456
+ getUsageStats() {
457
+ const stats = {
458
+ totalTasks: this.usageLog.length,
459
+ byAgent: {},
460
+ avgContextReduction: '0%',
461
+ mostUsedAgent: null
462
+ };
463
+
464
+ // Calculate stats from usage log
465
+ this.usageLog.forEach(log => {
466
+ stats.byAgent[log.agent] = (stats.byAgent[log.agent] || 0) + 1;
467
+ });
468
+
469
+ // Find most used agent
470
+ let maxUsage = 0;
471
+ for (const [agent, count] of Object.entries(stats.byAgent)) {
472
+ if (count > maxUsage) {
473
+ maxUsage = count;
474
+ stats.mostUsedAgent = agent;
475
+ }
476
+ }
477
+
478
+ return stats;
479
+ }
480
+ }
481
+
482
+ module.exports = MandatoryAgentRouter;
@@ -1,45 +1,92 @@
1
1
  /**
2
2
  * Command Executor
3
- * Executes commands using templates - Claude decides everything
4
- * ZERO if/else business logic
3
+ * WITH MANDATORY AGENT ASSIGNMENT
4
+ * Every task MUST use a specialized agent
5
5
  */
6
6
 
7
7
  const templateLoader = require('./template-loader')
8
8
  const contextBuilder = require('./context-builder')
9
9
  const promptBuilder = require('./prompt-builder')
10
10
  const toolRegistry = require('./tool-registry')
11
+ const MandatoryAgentRouter = require('./agent-router')
12
+ const ContextFilter = require('./context-filter')
11
13
 
12
14
  class CommandExecutor {
15
+ constructor() {
16
+ this.agentRouter = new MandatoryAgentRouter()
17
+ this.contextFilter = new ContextFilter()
18
+ }
19
+
13
20
  /**
14
- * Execute a command agentically
15
- * @param {string} commandName - Command name (e.g., 'now', 'done')
16
- * @param {Object} params - Command parameters
17
- * @param {string} projectPath - Project path
18
- * @returns {Promise<Object>} Execution result
21
+ * Execute command with MANDATORY agent assignment
19
22
  */
20
23
  async execute(commandName, params, projectPath) {
21
24
  try {
22
25
  // 1. Load template
23
26
  const template = await templateLoader.load(commandName)
24
27
 
25
- // 2. Build context
26
- const context = await contextBuilder.build(projectPath, params)
28
+ // 2. Build FULL context (before filtering)
29
+ const fullContext = await contextBuilder.build(projectPath, params)
30
+
31
+ // 3. Check if command requires agent
32
+ const requiresAgent = template.metadata?.['required-agent'] ||
33
+ this.isTaskCommand(commandName)
34
+
35
+ let context = fullContext
36
+ let assignedAgent = null
37
+
38
+ if (requiresAgent) {
39
+ // 4. MANDATORY: Assign specialized agent
40
+ const task = {
41
+ description: params.task || params.description || commandName,
42
+ type: commandName
43
+ }
44
+
45
+ const agentAssignment = await this.agentRouter.executeTask(
46
+ task,
47
+ fullContext,
48
+ projectPath
49
+ )
50
+
51
+ assignedAgent = agentAssignment.agent
52
+
53
+ // 5. Filter context for this specific agent (70-90% reduction)
54
+ const filtered = await this.contextFilter.filterForAgent(
55
+ assignedAgent,
56
+ task,
57
+ projectPath,
58
+ fullContext
59
+ )
27
60
 
28
- // 3. Load current state
61
+ context = {
62
+ ...filtered,
63
+ agent: assignedAgent,
64
+ originalSize: fullContext.files?.length || 0,
65
+ filteredSize: filtered.files?.length || 0,
66
+ reduction: filtered.metrics?.reductionPercent || 0
67
+ }
68
+ }
69
+
70
+ // 6. Load state with filtered context
29
71
  const state = await contextBuilder.loadState(context)
30
72
 
31
- // 4. Build prompt for Claude
32
- const prompt = promptBuilder.build(template, context, state)
73
+ // 7. Build prompt with agent assignment
74
+ const prompt = promptBuilder.build(template, context, state, assignedAgent)
75
+
76
+ // 8. Log agent usage
77
+ if (assignedAgent) {
78
+ console.log(`🤖 Task assigned to: ${assignedAgent.name}`)
79
+ console.log(`📉 Context reduced by: ${context.reduction}%`)
80
+ }
33
81
 
34
- // 5. Execute (in real implementation, this would call Claude)
35
- // For now, we return structured data that Claude can work with
36
82
  return {
37
83
  success: true,
38
84
  template,
39
85
  context,
40
86
  state,
41
87
  prompt,
42
- // In production: result from Claude's execution
88
+ assignedAgent,
89
+ contextReduction: context.reduction
43
90
  }
44
91
  } catch (error) {
45
92
  return {
@@ -49,6 +96,14 @@ class CommandExecutor {
49
96
  }
50
97
  }
51
98
 
99
+ /**
100
+ * Check if command is task-related
101
+ */
102
+ isTaskCommand(commandName) {
103
+ const taskCommands = ['work', 'now', 'build', 'feature', 'bug', 'done']
104
+ return taskCommands.includes(commandName)
105
+ }
106
+
52
107
  /**
53
108
  * Execute tool with permission check
54
109
  * @param {string} toolName - Tool name
@@ -41,9 +41,10 @@ class ContextBuilder {
41
41
  // Command parameters
42
42
  params: commandParams,
43
43
 
44
- // Timestamps
45
- timestamp: new Date().toISOString(),
46
- date: new Date().toLocaleString(),
44
+ // System timestamps (ALWAYS use these, NEVER generate timestamps)
45
+ // LLM does not know current date/time - these are from system clock
46
+ timestamp: new Date().toISOString(), // ISO format: "2025-10-07T14:30:00.000Z"
47
+ date: new Date().toISOString().split('T')[0], // YYYY-MM-DD: "2025-10-07"
47
48
  }
48
49
  }
49
50