prjct-cli 0.10.10 → 0.10.12

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 CHANGED
@@ -1,5 +1,93 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.10.12] - 2025-11-29
4
+
5
+ ### Refactored - Mandatory Agent Assignment (100% Agentic)
6
+
7
+ All agent assignment decisions now delegated to Claude via templates. JS code is pure orchestration.
8
+
9
+ - **New Template**: `templates/agent-assignment.md`
10
+ - Claude decides which agent based on task + available agents + context
11
+ - No hardcoded scoring weights or matching algorithms
12
+ - Semantic understanding replaces keyword matching
13
+
14
+ - **Simplified `agent-router.js`**: 419 → 128 lines (69% reduction)
15
+ - Removed: scoring logic, domain mappings, caching algorithms
16
+ - Kept: load agents, build context, log usage (I/O only)
17
+ - Class renamed: `MandatoryAgentRouter` → `AgentRouter`
18
+
19
+ - **Simplified `agent-matcher.js`**: 218 → 103 lines (53% reduction)
20
+ - Removed: multi-factor scoring (40% domain, 30% skills, etc.)
21
+ - Removed: all if/else matching logic
22
+ - Kept: format data, record usage, load history (I/O only)
23
+
24
+ - **Updated commands.js**:
25
+ - `/p:now` - Assigns agent before setting task, shows `[agent]`
26
+ - `/p:feature` - Assigns agent to each task, format: `[agent] [ ] task`
27
+ - `/p:bug` - Assigns agent, shows `→ agent`
28
+ - `/p:build` - Uses async `_assignAgentForTask()`
29
+ - New method: `_assignAgentForTask()` - orchestrates agent assignment
30
+
31
+ ### Architecture Principle
32
+
33
+ **JS = Orchestrator** (load files, build context, format data, I/O)
34
+ **Claude = Decision Maker** (via templates for all logic)
35
+
36
+ No scoring algorithms, no matching weights, no domain mappings in code.
37
+
38
+ ## [0.10.11] - 2025-11-29
39
+
40
+ ### Refactored - 100% Agentic System
41
+
42
+ Complete elimination of procedural keyword-based logic. Claude now decides everything via templates:
43
+
44
+ - **Templates Created (12 new files)**
45
+ - `templates/analysis/complexity.md` - Replaces `_detectComplexity()` keyword lists
46
+ - `templates/analysis/task-breakdown.md` - Replaces `_breakdownFeatureTasks()` hardcoded patterns
47
+ - `templates/analysis/bug-severity.md` - Replaces `_detectBugSeverity()` keyword detection
48
+ - `templates/analysis/health.md` - Replaces `_calculateHealth()` scoring logic
49
+ - `templates/analysis/intent.md` - Replaces `analyzeSemantics()` regex patterns
50
+ - `templates/design/architecture.md` - Architecture design guidance for Claude
51
+ - `templates/design/api.md` - API design guidance for Claude
52
+ - `templates/design/component.md` - Component design guidance for Claude
53
+ - `templates/design/database.md` - Database design guidance for Claude
54
+ - `templates/design/flow.md` - User flow design guidance for Claude
55
+ - `templates/architect/discovery.md` - Discovery phase template
56
+ - `templates/architect/phases.md` - Phase selection template
57
+
58
+ - **commands.js** - Simplified 11 helper methods
59
+ - `_breakdownFeatureTasks()` - Returns placeholder, Claude generates via template
60
+ - `_detectBugSeverity()` - Returns 'medium', Claude assesses via template
61
+ - `_calculateHealth()` - Simple activity check, Claude evaluates via template
62
+ - `_detectComplexity()` - Returns default, Claude analyzes via template
63
+ - `_autoAssignAgent()` - Returns 'generalist', routing via agent-router.js
64
+ - `_generateArchitectureDesign()` - 60 → 2 lines
65
+ - `_generateApiDesign()` - 75 → 2 lines
66
+ - `_generateComponentDesign()` - 75 → 2 lines
67
+ - `_generateDatabaseDesign()` - 65 → 2 lines
68
+ - `_generateFlowDesign()` - 58 → 2 lines
69
+
70
+ - **architecture-generator.js** - Reduced from 561 to 93 lines (83% reduction)
71
+ - Removed all phase-specific generation logic
72
+ - Claude generates content via architect templates
73
+
74
+ - **task-analyzer.js** - Simplified semantic analysis
75
+ - `analyzeSemantics()` - Removed all regex patterns
76
+ - `estimateComplexity()` - Removed keyword lists
77
+ - `detectDomains()` - Returns empty, Claude decides domain
78
+
79
+ ### Fixed
80
+
81
+ - **prompt-builder.test.js** - Updated 3 tests to match compressed format
82
+ - Tests now expect `## FILES:` instead of `AVAILABLE PROJECT FILES`
83
+ - Tests now expect `## PROJECT:` instead of `PROJECT FILES`
84
+
85
+ ### Impact
86
+ - Zero keyword-based logic remaining
87
+ - All decisions delegated to Claude via templates
88
+ - Easier to extend (just add/edit templates)
89
+ - More accurate analysis (semantic understanding vs pattern matching)
90
+
3
91
  ## [0.10.10] - 2025-11-28
4
92
 
5
93
  ### Refactored - Agentic Architecture Optimization
@@ -143,7 +143,9 @@ describe('PromptBuilder', () => {
143
143
 
144
144
  const prompt = builder.build(template, context, state)
145
145
 
146
- expect(prompt).toContain('AVAILABLE PROJECT FILES')
146
+ // OPTIMIZED: New compressed format uses ## FILES:
147
+ expect(prompt).toContain('## FILES:')
148
+ expect(prompt).toContain('3 available')
147
149
  expect(prompt).toContain('file1.js')
148
150
  expect(prompt).toContain('Read')
149
151
  })
@@ -159,7 +161,8 @@ describe('PromptBuilder', () => {
159
161
 
160
162
  const prompt = builder.build(template, context, state)
161
163
 
162
- expect(prompt).toContain('PROJECT FILES')
164
+ // OPTIMIZED: New compressed format uses ## PROJECT:
165
+ expect(prompt).toContain('## PROJECT:')
163
166
  expect(prompt).toContain('/test/project')
164
167
  })
165
168
  })
@@ -188,7 +191,8 @@ describe('PromptBuilder', () => {
188
191
  expect(prompt).toContain('TOOLS:')
189
192
  expect(prompt).toContain('Flow')
190
193
  expect(prompt).toContain('RULES (CRITICAL)')
191
- expect(prompt).toContain('AVAILABLE PROJECT FILES')
194
+ // OPTIMIZED: New compressed format uses ## FILES:
195
+ expect(prompt).toContain('## FILES:')
192
196
  })
193
197
 
194
198
  it('should be concise (under 2000 chars for simple prompt)', () => {
@@ -1,421 +1,128 @@
1
1
  /**
2
- * Mandatory Agent Router
2
+ * Agent Router - Orchestration Only
3
3
  *
4
- * CRITICAL: Ensures EVERY task is executed by a specialized agent
5
- * No task can run without an assigned expert agent
4
+ * AGENTIC: All decisions made by Claude via templates/agent-assignment.md
5
+ * JS only orchestrates: load agents, build context, delegate to Claude
6
6
  *
7
- * @version 1.0.0
7
+ * NO scoring logic, NO matching algorithms, NO hardcoded mappings
8
+ *
9
+ * @version 2.0.0
8
10
  */
9
11
 
10
- const fs = require('fs').promises;
11
- const path = require('path');
12
- const AgentGenerator = require('../domain/agent-generator');
13
- const configManager = require('../infrastructure/config-manager');
14
- const TaskAnalyzer = require('../domain/task-analyzer');
15
- const AgentMatcher = require('../domain/agent-matcher');
16
- const SmartCache = require('../domain/smart-cache');
17
- const AgentValidator = require('../domain/agent-validator');
18
- const log = require('../utils/logger');
12
+ const fs = require('fs').promises
13
+ const path = require('path')
14
+ const configManager = require('../infrastructure/config-manager')
15
+ const pathManager = require('../infrastructure/path-manager')
19
16
 
20
- class MandatoryAgentRouter {
17
+ class AgentRouter {
21
18
  constructor() {
22
- this.agentGenerator = null; // Will be initialized with projectId
23
- this.agentCache = null; // SmartCache instance
24
- this.usageLog = [];
25
- this.projectId = null;
26
- this.taskAnalyzer = null;
27
- this.agentMatcher = new AgentMatcher();
28
- this.agentValidator = new AgentValidator();
19
+ this.projectId = null
20
+ this.agentsPath = null
29
21
  }
30
22
 
31
23
  /**
32
24
  * Initialize with project context
33
- * @param {string} projectPath - Path to the project
25
+ * ORCHESTRATION: Just sets up paths
34
26
  */
35
27
  async initialize(projectPath) {
36
- this.projectId = await configManager.getProjectId(projectPath);
37
- this.agentGenerator = new AgentGenerator(this.projectId);
38
- this.agentCache = new SmartCache(this.projectId);
39
- await this.agentCache.initialize();
40
- this.taskAnalyzer = new TaskAnalyzer(projectPath);
41
- await this.taskAnalyzer.initialize();
28
+ this.projectId = await configManager.getProjectId(projectPath)
29
+ this.agentsPath = pathManager.getPath(this.projectId, 'agents')
42
30
  }
43
31
 
44
32
  /**
45
- * Main entry point - ALL tasks MUST go through here
46
- * @throws {Error} If no agent can be assigned
33
+ * Load all available agents from project
34
+ * ORCHESTRATION: File I/O only, no logic
47
35
  */
48
- async executeTask(task, context, projectPath) {
49
- // Initialize if needed
50
- if (!this.agentGenerator) {
51
- await this.initialize(projectPath);
52
- }
53
-
54
- // STEP 1: Deep semantic task analysis (NEW)
55
- const taskAnalysis = await this.taskAnalyzer.analyzeTask(task);
56
-
57
- // STEP 2: Select or generate specialized agent (MANDATORY) - with intelligent matching
58
- const agent = await this.assignAgent(taskAnalysis, context, projectPath);
59
-
60
- // STEP 3: Validate agent assignment
61
- if (!agent || !agent.name) {
62
- throw new Error(
63
- `CRITICAL: No agent assigned for task "${task.description}".
64
- System requires ALL tasks to use specialized agents.`
65
- );
66
- }
67
-
68
- // STEP 4: Filter context for this specific agent
69
- const filteredContext = await this.filterContextForAgent(
70
- agent,
71
- context,
72
- taskAnalysis
73
- );
74
-
75
- // STEP 5: Log agent usage for tracking and learning
76
- this.logAgentUsage(task, agent, filteredContext);
77
- this.agentMatcher.recordSuccess(agent, taskAnalysis, true); // Learn from assignment
78
-
79
- // STEP 6: Return agent with filtered context
80
- return {
81
- agent,
82
- context: filteredContext,
83
- taskAnalysis,
84
- routing: {
85
- reason: taskAnalysis.reason,
86
- confidence: taskAnalysis.confidence,
87
- alternativeAgents: taskAnalysis.alternatives
36
+ async loadAvailableAgents() {
37
+ try {
38
+ const files = await fs.readdir(this.agentsPath)
39
+ const agents = []
40
+
41
+ for (const file of files) {
42
+ if (file.endsWith('.md')) {
43
+ const name = file.replace('.md', '')
44
+ const content = await fs.readFile(
45
+ path.join(this.agentsPath, file),
46
+ 'utf-8'
47
+ )
48
+ agents.push({ name, content })
49
+ }
88
50
  }
89
- };
90
- }
91
51
 
92
- /**
93
- * Analyze task to determine what type of expertise is needed
94
- *
95
- * 100% AGENTIC: Delegates to TaskAnalyzer which uses templates.
96
- * NO hardcoded patterns or keyword lists.
97
- */
98
- async analyzeTask(task, projectPath = null) {
99
- // Use TaskAnalyzer for semantic analysis (template-driven)
100
- if (this.taskAnalyzer) {
101
- return await this.taskAnalyzer.analyzeTask(task);
52
+ return agents
53
+ } catch {
54
+ return []
102
55
  }
103
-
104
- // Fallback: Return minimal analysis, let Claude decide in prompt
105
- return {
106
- domain: 'generalist',
107
- confidence: 0.5,
108
- matchedKeywords: [],
109
- reason: 'Using generalist - Claude will analyze task in context',
110
- alternatives: ['full-stack'],
111
- projectTechnologies: null
112
- };
113
56
  }
114
57
 
115
58
  /**
116
- * Assign the best agent for the task
117
- * IMPROVED: Uses intelligent matching with scoring
59
+ * Get agent names list
60
+ * ORCHESTRATION: Simple extraction
118
61
  */
119
- async assignAgent(taskAnalysis, context, projectPath, overrideAgent = null) {
120
- // Respect override
121
- if (overrideAgent) {
122
- const existing = await this.agentGenerator.loadAgent(overrideAgent);
123
- if (existing) {
124
- return existing;
125
- }
126
- return this.generateSpecializedAgent(overrideAgent, {}, context);
127
- }
128
-
129
- const primaryDomain = taskAnalysis.primaryDomain;
130
- const projectTech = taskAnalysis.projectTechnologies || {};
131
-
132
- // Generate cache key with tech stack
133
- const techStack = {
134
- languages: projectTech.languages || [],
135
- frameworks: projectTech.frameworks || []
136
- };
137
- const cacheKey = this.agentCache.generateKey(this.projectId, primaryDomain, techStack);
138
-
139
- // Check smart cache first
140
- const cached = await this.agentCache.get(cacheKey);
141
- if (cached) {
142
- return cached;
143
- }
144
-
145
- // STEP 1: Load all existing agents
146
- const allAgents = await this.agentGenerator.loadAllAgents();
147
-
148
- // STEP 2: Use intelligent matching to find best agent
149
- const match = this.agentMatcher.findBestAgent(allAgents, taskAnalysis);
150
-
151
- if (match && match.score > 0.5) {
152
- // Good match found - use it
153
- await this.agentCache.set(cacheKey, match.agent);
154
- return match.agent;
155
- }
156
-
157
- // STEP 3: Try to load domain-specific agent
158
- const agentType = this.getAgentTypeForDomain(primaryDomain);
159
- const existingAgent = await this.agentGenerator.loadAgent(agentType);
160
-
161
- if (existingAgent) {
162
- await this.agentCache.set(cacheKey, existingAgent);
163
- return existingAgent;
164
- }
165
-
166
- // STEP 4: Validate before generating new agent
167
- const config = {
168
- domain: primaryDomain,
169
- projectContext: context.projectSummary || context.projectContext || '',
170
- expertise: this.buildExpertiseFromTech(projectTech, primaryDomain)
171
- };
172
-
173
- const validation = this.agentValidator.validateBeforeGeneration(
174
- agentType,
175
- config,
176
- allAgents
177
- );
178
-
179
- if (!validation.valid && validation.similarAgent) {
180
- // Similar agent exists - use it instead
181
- await this.agentCache.set(cacheKey, validation.similarAgent);
182
- return validation.similarAgent;
183
- }
184
-
185
- // STEP 5: Generate new agent only if validated
186
- const agent = await this.generateSpecializedAgent(primaryDomain, techStack, context);
187
-
188
- // Validate after generation
189
- const postValidation = this.agentValidator.validateAfterGeneration(agent);
190
- if (!postValidation.valid) {
191
- log.warn(`Agent validation issues: ${postValidation.issues.join(', ')}`);
192
- }
193
-
194
- // Cache for reuse
195
- await this.agentCache.set(cacheKey, agent);
196
-
197
- return agent;
62
+ async getAgentNames() {
63
+ const agents = await this.loadAvailableAgents()
64
+ return agents.map(a => a.name)
198
65
  }
199
66
 
200
67
  /**
201
- * Build expertise string from tech stack
202
- *
203
- * 100% AGENTIC: No hardcoded framework lists.
204
- * Returns ALL tech, Claude decides what's relevant.
68
+ * Load specific agent by name
69
+ * ORCHESTRATION: File I/O only
205
70
  */
206
- buildExpertiseFromTech(projectTech, domain) {
207
- const parts = []
208
-
209
- // Include ALL languages - no filtering
210
- if (projectTech.languages && projectTech.languages.length > 0) {
211
- parts.push(projectTech.languages.join(', '))
212
- }
213
-
214
- // Include ALL frameworks - Claude decides relevance
215
- // NO hardcoded lists like ['react', 'vue', 'angular']
216
- if (projectTech.frameworks && projectTech.frameworks.length > 0) {
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(', '))
71
+ async loadAgent(name) {
72
+ try {
73
+ const filePath = path.join(this.agentsPath, `${name}.md`)
74
+ const content = await fs.readFile(filePath, 'utf-8')
75
+ return { name, content }
76
+ } catch {
77
+ return null
223
78
  }
224
-
225
- return parts.join(', ') || `${domain} development`
226
79
  }
227
80
 
228
81
  /**
229
- * Get agent type name for a domain
230
- * @private
231
- */
232
- getAgentTypeForDomain(domain) {
233
- const agentTypes = {
234
- frontend: 'frontend-specialist',
235
- backend: 'backend-specialist',
236
- database: 'database-specialist',
237
- devops: 'devops-specialist',
238
- qa: 'qa-specialist',
239
- architecture: 'architect',
240
- generalist: 'full-stack'
241
- };
242
- return agentTypes[domain] || 'full-stack';
243
- }
244
-
245
- /**
246
- * Find similar agent from existing agents
247
- * DEPRECATED: Now uses AgentMatcher for intelligent matching
248
- * @private
249
- */
250
- findSimilarAgent(allAgents, domain, taskAnalysis) {
251
- // Use AgentMatcher instead
252
- const match = this.agentMatcher.findBestAgent(allAgents, taskAnalysis);
253
- return match ? match.agent : null;
254
- }
255
-
256
- /**
257
- * Generate a specialized agent for the detected domain
258
- * Only called when no existing agent is found
259
- */
260
- async generateSpecializedAgent(domain, techStack, context) {
261
- // Map domain to agent type
262
- const agentType = this.getAgentTypeForDomain(domain);
263
-
264
- // Generate with minimal config - let the Agent figure it out
265
- const config = {
266
- domain,
267
- projectContext: context.projectSummary || context.projectContext || '',
268
- // No hardcoded best practices passed here
269
- };
270
-
271
- // Generate the agent file
272
- await this.agentGenerator.generateDynamicAgent(agentType, config);
273
-
274
- // Load it immediately so we return the full agent object
275
- const agent = await this.agentGenerator.loadAgent(agentType);
276
-
277
- // If loading failed, return minimal object
278
- return agent || { name: agentType, content: '', domain };
279
- }
280
-
281
- /**
282
- * Filter context to only what's relevant for this agent
82
+ * Build context for agent assignment
83
+ * ORCHESTRATION: Data gathering only
283
84
  *
284
- * 100% AGENTIC: No hardcoded directory/extension lists.
285
- * Only excludes universal noise (node_modules, .git, dist).
286
- * Claude decides relevance based on task.
287
- */
288
- async filterContextForAgent(agent, fullContext, taskAnalysis) {
289
- // Universal exclusions that apply to ALL projects
290
- const universalExclusions = ['node_modules', '.git', 'dist', 'build', '.next', 'target', 'vendor'];
291
-
292
- // Filter only universal noise - let Claude decide the rest
293
- const filtered = {
294
- ...fullContext,
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'
300
- };
301
-
302
- return filtered;
303
- }
304
-
305
- /**
306
- * Filter files based on patterns
85
+ * Claude uses this context + templates/agent-assignment.md to decide
307
86
  */
308
- filterFiles(files, pattern) {
309
- return files.filter(file => {
310
- // Check if file should be excluded
311
- const isExcluded = pattern.exclude.some(exclude => file.includes(exclude));
312
- if (isExcluded) return false;
313
-
314
- // Check if file matches include patterns
315
- if (pattern.include.length > 0) {
316
- const isIncluded = pattern.include.some(include => file.includes(include));
317
- if (!isIncluded) return false;
318
- }
319
-
320
- // Check extensions if specified
321
- if (pattern.extensions.length > 0) {
322
- const hasValidExtension = pattern.extensions.some(ext => file.endsWith(ext));
323
- if (!hasValidExtension) return false;
324
- }
87
+ async buildAssignmentContext(task, projectPath) {
88
+ const agents = await this.getAgentNames()
325
89
 
326
- return true;
327
- });
328
- }
329
-
330
- /**
331
- * Log agent usage for metrics and optimization
332
- */
333
- logAgentUsage(task, agent, context) {
334
- const usage = {
335
- timestamp: new Date().toISOString(),
336
- task: task.description,
337
- agent: agent.name,
338
- domain: agent.domain || 'unknown',
339
- contextSize: context.files?.length || 0,
340
- contextReduction: this.calculateContextReduction(context),
341
- confidence: agent.confidence || 1.0
342
- };
343
-
344
- this.usageLog.push(usage);
345
-
346
- // Also append to a log file for persistence
347
- this.appendToLogFile(usage);
348
-
349
- return usage;
350
- }
351
-
352
- /**
353
- * Calculate how much context was reduced
354
- */
355
- calculateContextReduction(filteredContext) {
356
- // This would compare against full context
357
- // For now, estimate based on filtering
358
- if (filteredContext.relevantOnly) {
359
- return '70-90%'; // Typical reduction when filtering
90
+ return {
91
+ task: task.description || task,
92
+ availableAgents: agents,
93
+ projectPath,
94
+ projectId: this.projectId,
95
+ // Claude reads this and decides via template
96
+ _template: 'templates/agent-assignment.md'
360
97
  }
361
- return '0%';
362
98
  }
363
99
 
364
100
  /**
365
- * Append usage to log file
101
+ * Log agent usage
102
+ * ORCHESTRATION: File I/O only
366
103
  */
367
- async appendToLogFile(usage) {
104
+ async logUsage(task, agent, projectPath) {
368
105
  try {
369
106
  const logPath = path.join(
370
107
  process.env.HOME,
371
108
  '.prjct-cli',
109
+ 'projects',
110
+ this.projectId,
372
111
  'agent-usage.jsonl'
373
- );
374
-
375
- const logEntry = JSON.stringify(usage) + '\n';
376
- await fs.appendFile(logPath, logEntry);
377
- } catch (error) {
378
- log.error('Failed to log agent usage:', error.message);
112
+ )
113
+
114
+ const entry = JSON.stringify({
115
+ timestamp: new Date().toISOString(),
116
+ task: typeof task === 'string' ? task : task.description,
117
+ agent: agent.name || agent,
118
+ projectId: this.projectId
119
+ }) + '\n'
120
+
121
+ await fs.appendFile(logPath, entry)
122
+ } catch {
123
+ // Silent fail for logging
379
124
  }
380
125
  }
381
-
382
- /**
383
- * Get similar domains for fallback
384
- *
385
- * 100% AGENTIC: Returns generic fallback.
386
- * Claude determines domain relationships based on context.
387
- */
388
- getSimilarDomains(domain) {
389
- // No hardcoded domain relationships
390
- // Claude decides what's similar based on actual project context
391
- return ['full-stack', 'generalist'];
392
- }
393
-
394
- /**
395
- * Get usage statistics
396
- */
397
- getUsageStats() {
398
- const stats = {
399
- totalTasks: this.usageLog.length,
400
- byAgent: {},
401
- avgContextReduction: '0%',
402
- mostUsedAgent: null
403
- };
404
-
405
- // Calculate stats from usage log
406
- this.usageLog.forEach(log => {
407
- stats.byAgent[log.agent] = (stats.byAgent[log.agent] || 0) + 1;
408
- });
409
-
410
- // Find most used agent
411
- const mostUsed = Object.entries(stats.byAgent).reduce((max, [agent, count]) => {
412
- return count > max.count ? { agent, count } : max;
413
- }, { agent: null, count: 0 });
414
-
415
- stats.mostUsedAgent = mostUsed.agent;
416
-
417
- return stats;
418
- }
419
126
  }
420
127
 
421
- module.exports = MandatoryAgentRouter;
128
+ module.exports = AgentRouter