prjct-cli 0.9.2 → 0.10.2

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 (53) hide show
  1. package/CHANGELOG.md +142 -0
  2. package/core/__tests__/agentic/memory-system.test.js +263 -0
  3. package/core/__tests__/agentic/plan-mode.test.js +336 -0
  4. package/core/agentic/agent-router.js +253 -186
  5. package/core/agentic/chain-of-thought.js +578 -0
  6. package/core/agentic/command-executor.js +299 -17
  7. package/core/agentic/context-builder.js +208 -8
  8. package/core/agentic/context-filter.js +83 -83
  9. package/core/agentic/ground-truth.js +591 -0
  10. package/core/agentic/loop-detector.js +406 -0
  11. package/core/agentic/memory-system.js +850 -0
  12. package/core/agentic/parallel-tools.js +366 -0
  13. package/core/agentic/plan-mode.js +572 -0
  14. package/core/agentic/prompt-builder.js +127 -2
  15. package/core/agentic/response-templates.js +290 -0
  16. package/core/agentic/semantic-compression.js +517 -0
  17. package/core/agentic/think-blocks.js +657 -0
  18. package/core/agentic/tool-registry.js +32 -0
  19. package/core/agentic/validation-rules.js +380 -0
  20. package/core/command-registry.js +48 -0
  21. package/core/commands.js +128 -60
  22. package/core/context-sync.js +183 -0
  23. package/core/domain/agent-generator.js +77 -46
  24. package/core/domain/agent-loader.js +183 -0
  25. package/core/domain/agent-matcher.js +217 -0
  26. package/core/domain/agent-validator.js +217 -0
  27. package/core/domain/context-estimator.js +175 -0
  28. package/core/domain/product-standards.js +92 -0
  29. package/core/domain/smart-cache.js +157 -0
  30. package/core/domain/task-analyzer.js +353 -0
  31. package/core/domain/tech-detector.js +365 -0
  32. package/package.json +8 -16
  33. package/templates/commands/done.md +7 -0
  34. package/templates/commands/feature.md +8 -0
  35. package/templates/commands/ship.md +8 -0
  36. package/templates/commands/spec.md +128 -0
  37. package/templates/global/CLAUDE.md +17 -0
  38. package/core/__tests__/agentic/agent-router.test.js +0 -398
  39. package/core/__tests__/agentic/command-executor.test.js +0 -223
  40. package/core/__tests__/agentic/context-builder.test.js +0 -160
  41. package/core/__tests__/agentic/context-filter.test.js +0 -494
  42. package/core/__tests__/agentic/prompt-builder.test.js +0 -212
  43. package/core/__tests__/agentic/template-loader.test.js +0 -164
  44. package/core/__tests__/agentic/tool-registry.test.js +0 -243
  45. package/core/__tests__/domain/agent-generator.test.js +0 -296
  46. package/core/__tests__/domain/analyzer.test.js +0 -324
  47. package/core/__tests__/infrastructure/author-detector.test.js +0 -103
  48. package/core/__tests__/infrastructure/config-manager.test.js +0 -454
  49. package/core/__tests__/infrastructure/path-manager.test.js +0 -412
  50. package/core/__tests__/setup.test.js +0 -15
  51. package/core/__tests__/utils/date-helper.test.js +0 -169
  52. package/core/__tests__/utils/file-helper.test.js +0 -258
  53. package/core/__tests__/utils/jsonl-helper.test.js +0 -387
package/core/commands.js CHANGED
@@ -21,6 +21,7 @@ const path = require('path')
21
21
  const commandExecutor = require('./agentic/command-executor')
22
22
  const contextBuilder = require('./agentic/context-builder')
23
23
  const toolRegistry = require('./agentic/tool-registry')
24
+ const memorySystem = require('./agentic/memory-system')
24
25
  const pathManager = require('./infrastructure/path-manager')
25
26
  const configManager = require('./infrastructure/config-manager')
26
27
  const authorDetector = require('./infrastructure/author-detector')
@@ -306,6 +307,7 @@ class PrjctCommands {
306
307
  const globalPath = pathManager.getGlobalProjectPath(projectId)
307
308
 
308
309
  // Create base files
310
+ // P1.1: Added patterns.json for Layered Memory System
309
311
  const baseFiles = {
310
312
  'core/now.md': '# NOW\n\nNo current task. Use `/p:now` to set focus.\n',
311
313
  'core/next.md': '# NEXT\n\n## Priority Queue\n\n',
@@ -314,7 +316,15 @@ class PrjctCommands {
314
316
  'progress/metrics.md': '# METRICS\n\n',
315
317
  'planning/ideas.md': '# IDEAS 💡\n\n## Brain Dump\n\n',
316
318
  'planning/roadmap.md': '# ROADMAP\n\n',
319
+ 'planning/specs/.gitkeep': '# Specs directory - created by /p:spec\n',
317
320
  'memory/context.jsonl': '',
321
+ 'memory/patterns.json': JSON.stringify({
322
+ version: 1,
323
+ decisions: {},
324
+ preferences: {},
325
+ workflows: {},
326
+ counters: {}
327
+ }, null, 2),
318
328
  }
319
329
 
320
330
  for (const [filePath, content] of Object.entries(baseFiles)) {
@@ -693,6 +703,27 @@ class PrjctCommands {
693
703
  timestamp: dateHelper.getTimestamp(),
694
704
  })
695
705
 
706
+ // P1.1: Learn patterns from this ship
707
+ const config = await configManager.getConfig(projectPath)
708
+ const projectId = config.projectId
709
+
710
+ // Record shipping workflow patterns
711
+ await memorySystem.learnDecision(projectId, 'commit_footer', 'prjct', 'ship')
712
+
713
+ // Track if tests were run (for quick_ship pattern learning)
714
+ if (testResult.success) {
715
+ await memorySystem.recordDecision(projectId, 'test_before_ship', 'true', 'ship')
716
+ }
717
+
718
+ // Record workflow if it's a quick ship (small changes)
719
+ const isQuickShip = !lintResult.success || !testResult.success
720
+ if (isQuickShip) {
721
+ await memorySystem.recordWorkflow(projectId, 'quick_ship', {
722
+ description: 'Ship without full checks',
723
+ feature_type: feature.toLowerCase().includes('doc') ? 'docs' : 'other'
724
+ })
725
+ }
726
+
696
727
  console.log('\n🎉 Feature shipped successfully!\n')
697
728
  console.log('💡 Recommendation: Compact conversation now')
698
729
  console.log(' (Keeps context clean for next feature)\n')
@@ -1133,7 +1164,6 @@ class PrjctCommands {
1133
1164
  */
1134
1165
  async _cleanupMemory(projectPath) {
1135
1166
  const projectId = await configManager.getProjectId(projectPath)
1136
- const globalPath = pathManager.getGlobalProjectPath(projectId)
1137
1167
 
1138
1168
  console.log('📊 Analyzing disk usage...\n')
1139
1169
 
@@ -2317,8 +2347,14 @@ Agent: ${agent}
2317
2347
  gitCommits: analysisData.gitStats.totalCommits,
2318
2348
  })
2319
2349
 
2350
+ // Generate dynamic context for Claude
2351
+ const contextSync = require('./context-sync')
2352
+ const projectId = await configManager.getProjectId(projectPath)
2353
+ await contextSync.generateLocalContext(projectPath, projectId)
2354
+
2320
2355
  console.log('✅ Analysis complete!\n')
2321
- console.log('📄 Full report: analysis/repo-summary.md\n')
2356
+ console.log('📄 Full report: analysis/repo-summary.md')
2357
+ console.log('📝 Context: ~/.prjct-cli/projects/' + projectId + '/CLAUDE.md\n')
2322
2358
  console.log('Next steps:')
2323
2359
  console.log('• /p:sync → Generate agents based on stack')
2324
2360
  console.log('• /p:feature → Add a new feature')
@@ -2474,7 +2510,7 @@ Agent: ${agent}
2474
2510
  const AgentGenerator = require('./domain/agent-generator')
2475
2511
  const generator = new AgentGenerator(projectId)
2476
2512
 
2477
- const generatedAgents = await this._generateAgentsFromAnalysis(summaryContent, generator)
2513
+ const generatedAgents = await this._generateAgentsFromAnalysis(summaryContent, generator, projectPath)
2478
2514
 
2479
2515
  // Step 4: Log to memory
2480
2516
  await this.logToMemory(projectPath, 'agents_generated', {
@@ -2483,11 +2519,16 @@ Agent: ${agent}
2483
2519
  count: generatedAgents.length,
2484
2520
  })
2485
2521
 
2522
+ // Generate dynamic context for Claude
2523
+ const contextSync = require('./context-sync')
2524
+ await contextSync.generateLocalContext(projectPath, projectId)
2525
+
2486
2526
  console.log('\n✅ Sync complete!\n')
2487
2527
  console.log(`🤖 Agents Generated: ${generatedAgents.length}`)
2488
2528
  generatedAgents.forEach((agent) => {
2489
2529
  console.log(` • ${agent}`)
2490
2530
  })
2531
+ console.log('📝 Context: ~/.prjct-cli/projects/' + projectId + '/CLAUDE.md')
2491
2532
  console.log('\n📋 Based on: analysis/repo-summary.md')
2492
2533
  console.log('💡 See templates/agents/AGENTS.md for reference\n')
2493
2534
  console.log('Next steps:')
@@ -2506,91 +2547,118 @@ Agent: ${agent}
2506
2547
 
2507
2548
  /**
2508
2549
  * Generate agents dynamically from analysis summary
2509
- * Claude decides based on what technologies are detected
2550
+ * 100% DYNAMIC - Uses TechDetector, NO HARDCODING
2551
+ * Claude decides based on actual detected technologies
2510
2552
  * @private
2511
2553
  */
2512
- async _generateAgentsFromAnalysis(summaryContent, generator) {
2554
+ async _generateAgentsFromAnalysis(summaryContent, generator, projectPath) {
2513
2555
  const agents = []
2556
+ const TechDetector = require('./domain/tech-detector')
2557
+ const detector = new TechDetector(projectPath)
2558
+ const tech = await detector.detectAll()
2514
2559
 
2515
- // Parse summary to identify technologies
2516
- // Simple detection based on sections (NOT predetermined patterns)
2517
-
2518
- // Detect languages/frameworks from summary
2519
- const hasJavaScript =
2520
- summaryContent.includes('JavaScript') || summaryContent.includes('TypeScript')
2521
- const hasNextJS = summaryContent.includes('Next.js')
2522
- const hasVite = summaryContent.includes('Vite')
2523
- const hasReact = summaryContent.includes('react')
2524
- const hasRust = summaryContent.includes('Rust')
2525
- const hasGo = summaryContent.includes('Go')
2526
- const hasPython = summaryContent.includes('Python')
2527
- const hasDocker = summaryContent.includes('Docker')
2560
+ // Generate agents based on ACTUAL detected technologies
2561
+ // No assumptions, no hardcoding - just what we found
2528
2562
 
2529
- // Generate agents based on detected stack
2530
- // Each agent is specific to THIS project's stack
2563
+ // Frontend agents - if we have frontend frameworks
2564
+ const frontendFrameworks = tech.frameworks.filter(f =>
2565
+ ['react', 'vue', 'angular', 'svelte', 'next', 'nuxt', 'sveltekit', 'remix'].includes(f.toLowerCase())
2566
+ )
2567
+ const frontendBuildTools = tech.buildTools.filter(t =>
2568
+ ['vite', 'webpack', 'rollup', 'esbuild'].includes(t.toLowerCase())
2569
+ )
2531
2570
 
2532
- if (hasJavaScript || hasNextJS || hasVite) {
2571
+ if (frontendFrameworks.length > 0 || frontendBuildTools.length > 0 || tech.languages.includes('JavaScript') || tech.languages.includes('TypeScript')) {
2572
+ const frameworkList = frontendFrameworks.length > 0
2573
+ ? frontendFrameworks.join(', ')
2574
+ : (frontendBuildTools.length > 0 ? frontendBuildTools.join(', ') : 'JavaScript/TypeScript')
2575
+
2533
2576
  await generator.generateDynamicAgent('frontend-specialist', {
2534
2577
  role: 'Frontend Development Specialist',
2535
- expertise: `${hasNextJS ? 'Next.js' : hasVite ? 'Vite' : 'JavaScript/TypeScript'}, ${hasReact ? 'React' : 'Modern JavaScript frameworks'}`,
2536
- responsibilities:
2537
- 'Handle UI components, state management, routing, and frontend architecture',
2578
+ expertise: `${frameworkList}, ${tech.languages.filter(l => ['JavaScript', 'TypeScript'].includes(l)).join(' or ') || 'Modern JavaScript'}`,
2579
+ responsibilities: 'Handle UI components, state management, routing, and frontend architecture',
2538
2580
  projectContext: {
2539
- detectedFramework: hasNextJS ? 'Next.js' : hasVite ? 'Vite + React' : 'JavaScript',
2581
+ detectedFrameworks: frontendFrameworks,
2582
+ buildTools: frontendBuildTools,
2583
+ languages: tech.languages.filter(l => ['JavaScript', 'TypeScript'].includes(l))
2540
2584
  },
2541
2585
  })
2542
2586
  agents.push('frontend-specialist')
2543
2587
  }
2544
2588
 
2545
- if (hasRust) {
2546
- await generator.generateDynamicAgent('rust-developer', {
2547
- role: 'Rust Development Specialist',
2548
- expertise: 'Rust, Cargo, performance optimization, memory safety',
2549
- responsibilities:
2550
- 'Handle Rust codebase, performance-critical components, systems programming',
2551
- projectContext: { language: 'Rust' },
2552
- })
2553
- agents.push('rust-developer')
2554
- }
2589
+ // Backend agents - if we have backend frameworks or languages
2590
+ const backendFrameworks = tech.frameworks.filter(f =>
2591
+ ['express', 'fastify', 'koa', 'hapi', 'nest', 'django', 'flask', 'fastapi', 'rails', 'phoenix', 'laravel'].includes(f.toLowerCase())
2592
+ )
2593
+ const backendLanguages = tech.languages.filter(l =>
2594
+ ['Go', 'Rust', 'Python', 'Ruby', 'Elixir', 'Java', 'PHP'].includes(l)
2595
+ )
2555
2596
 
2556
- if (hasGo) {
2557
- await generator.generateDynamicAgent('go-developer', {
2558
- role: 'Go Development Specialist',
2559
- expertise: 'Go, Go modules, concurrency, backend services',
2560
- responsibilities: 'Handle Go codebase, backend services, API development',
2561
- projectContext: { language: 'Go' },
2597
+ if (backendFrameworks.length > 0 || backendLanguages.length > 0) {
2598
+ const agentName = backendLanguages.length > 0
2599
+ ? `${backendLanguages[0].toLowerCase()}-developer`
2600
+ : 'backend-specialist'
2601
+
2602
+ const expertise = backendFrameworks.length > 0
2603
+ ? backendFrameworks.join(', ')
2604
+ : backendLanguages.join(', ')
2605
+
2606
+ await generator.generateDynamicAgent(agentName, {
2607
+ role: `${backendLanguages[0] || 'Backend'} Development Specialist`,
2608
+ expertise,
2609
+ responsibilities: 'Handle backend services, API development, server logic',
2610
+ projectContext: {
2611
+ detectedFrameworks: backendFrameworks,
2612
+ languages: backendLanguages
2613
+ },
2562
2614
  })
2563
- agents.push('go-developer')
2615
+ agents.push(agentName)
2564
2616
  }
2565
2617
 
2566
- if (hasPython) {
2567
- await generator.generateDynamicAgent('python-developer', {
2568
- role: 'Python Development Specialist',
2569
- expertise: 'Python, pip, Django/Flask, data processing',
2570
- responsibilities: 'Handle Python codebase, backend logic, data processing',
2571
- projectContext: { language: 'Python' },
2618
+ // Database specialist - if we have database tools
2619
+ if (tech.databases.length > 0) {
2620
+ await generator.generateDynamicAgent('database-specialist', {
2621
+ role: 'Database Specialist',
2622
+ expertise: tech.databases.join(', '),
2623
+ responsibilities: 'Handle database design, queries, migrations, data modeling',
2624
+ projectContext: {
2625
+ databases: tech.databases
2626
+ },
2572
2627
  })
2573
- agents.push('python-developer')
2628
+ agents.push('database-specialist')
2574
2629
  }
2575
2630
 
2576
- if (hasDocker) {
2631
+ // DevOps specialist - if we have DevOps tools
2632
+ if (tech.tools.some(t => ['Docker', 'Kubernetes', 'Terraform'].includes(t))) {
2577
2633
  await generator.generateDynamicAgent('devops-specialist', {
2578
2634
  role: 'DevOps & Infrastructure Specialist',
2579
- expertise: 'Docker, Docker Compose, containerization, deployment',
2635
+ expertise: tech.tools.filter(t => ['Docker', 'Kubernetes', 'Terraform'].includes(t)).join(', '),
2580
2636
  responsibilities: 'Handle containerization, deployment, infrastructure setup',
2581
- projectContext: { hasDocker: true },
2637
+ projectContext: {
2638
+ tools: tech.tools
2639
+ },
2582
2640
  })
2583
2641
  agents.push('devops-specialist')
2584
2642
  }
2585
2643
 
2586
- // Always generate a QA specialist if we have code
2587
- await generator.generateDynamicAgent('qa-specialist', {
2588
- role: 'Quality Assurance Specialist',
2589
- expertise: 'Testing frameworks, test automation, quality metrics',
2590
- responsibilities: 'Handle testing strategy, test creation, quality assurance',
2591
- projectContext: { role: 'QA' },
2592
- })
2593
- agents.push('qa-specialist')
2644
+ // QA specialist - always generate if we have test frameworks or any code
2645
+ if (tech.testFrameworks.length > 0 || tech.languages.length > 0) {
2646
+ const testExpertise = tech.testFrameworks.length > 0
2647
+ ? tech.testFrameworks.join(', ')
2648
+ : 'Testing frameworks, test automation'
2649
+
2650
+ await generator.generateDynamicAgent('qa-specialist', {
2651
+ role: 'Quality Assurance Specialist',
2652
+ expertise: testExpertise,
2653
+ responsibilities: 'Handle testing strategy, test creation, quality assurance',
2654
+ projectContext: {
2655
+ testFrameworks: tech.testFrameworks,
2656
+ languages: tech.languages,
2657
+ role: 'QA'
2658
+ },
2659
+ })
2660
+ agents.push('qa-specialist')
2661
+ }
2594
2662
 
2595
2663
  return agents
2596
2664
  }
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Context Sync - Generates dynamic project context for Claude
3
+ *
4
+ * Creates ~/.prjct-cli/projects/{id}/CLAUDE.md with:
5
+ * - Stack info
6
+ * - Available agents (dynamic, varies per project)
7
+ * - Current task
8
+ * - Priority queue
9
+ *
10
+ * Called by: /p:sync, /p:analyze, /p:init
11
+ */
12
+
13
+ const fs = require('fs').promises
14
+ const path = require('path')
15
+ const os = require('os')
16
+
17
+ /**
18
+ * Generate project context file for Claude
19
+ * 100% DYNAMIC - reads whatever agents exist
20
+ *
21
+ * @param {string} projectPath - Local project path
22
+ * @param {string} projectId - Project ID from config
23
+ * @returns {Promise<{agents: string[], stack: string|null, currentTask: string|null}>}
24
+ */
25
+ async function generateLocalContext(projectPath, projectId) {
26
+ const globalPath = path.join(os.homedir(), '.prjct-cli/projects', projectId)
27
+
28
+ // 1. Read ALL agents that exist (dynamic - varies per project)
29
+ const agentsDir = path.join(globalPath, 'agents')
30
+ let agentFiles = []
31
+ try {
32
+ agentFiles = await fs.readdir(agentsDir)
33
+ agentFiles = agentFiles.filter(f => f.endsWith('.md'))
34
+ } catch {
35
+ // No agents directory yet
36
+ }
37
+
38
+ // 2. Read core files (may not exist)
39
+ const readSafe = async (p) => {
40
+ try {
41
+ return await fs.readFile(p, 'utf-8')
42
+ } catch {
43
+ return null
44
+ }
45
+ }
46
+
47
+ const [repoSummary, now, next, roadmap] = await Promise.all([
48
+ readSafe(path.join(globalPath, 'analysis/repo-summary.md')),
49
+ readSafe(path.join(globalPath, 'core/now.md')),
50
+ readSafe(path.join(globalPath, 'core/next.md')),
51
+ readSafe(path.join(globalPath, 'planning/roadmap.md'))
52
+ ])
53
+
54
+ // 3. Extract stack from repo-summary
55
+ const stack = extractStack(repoSummary)
56
+
57
+ // 4. Generate agent list with clean names
58
+ const agents = agentFiles.map(f => f.replace('.md', ''))
59
+
60
+ // 5. Extract current task
61
+ const currentTask = extractCurrentTask(now)
62
+
63
+ // 6. Extract top 3 from next.md
64
+ const nextTasks = extractTopTasks(next, 3)
65
+
66
+ // 7. Extract active features from roadmap
67
+ const activeFeatures = extractActiveFeatures(roadmap)
68
+
69
+ // 8. Generate CLAUDE.md content
70
+ const content = `# Project Context
71
+ <!-- Auto-generated by /p:sync - DO NOT EDIT -->
72
+ <!-- projectId: ${projectId} -->
73
+ <!-- Last sync: ${new Date().toISOString()} -->
74
+
75
+ ## Stack
76
+ ${stack || 'Run /p:analyze to detect stack'}
77
+
78
+ ## Available Agents
79
+ ${agents.length ? agents.map(a => `- ${a}`).join('\n') : 'None. Run /p:sync to generate.'}
80
+
81
+ ## Current Task
82
+ ${currentTask || 'None. Use /p:now to start.'}
83
+
84
+ ## Next Up
85
+ ${nextTasks.length ? nextTasks.map((t, i) => `${i + 1}. ${t}`).join('\n') : 'Empty queue.'}
86
+
87
+ ## Active Features
88
+ ${activeFeatures.length ? activeFeatures.map(f => `- ${f}`).join('\n') : 'None in progress.'}
89
+
90
+ ## Data Location
91
+ All project data: ~/.prjct-cli/projects/${projectId}/
92
+ - agents/ - Read for detailed patterns and code examples
93
+ - analysis/repo-summary.md - Full technical analysis
94
+ - planning/roadmap.md - Features and roadmap
95
+ `
96
+
97
+ // 9. Write to global storage (NOT in repo)
98
+ const contextPath = path.join(globalPath, 'CLAUDE.md')
99
+ await fs.writeFile(contextPath, content, 'utf-8')
100
+
101
+ return { agents, stack, currentTask }
102
+ }
103
+
104
+ /**
105
+ * Extract stack from repo-summary
106
+ */
107
+ function extractStack(summary) {
108
+ if (!summary) return null
109
+
110
+ // Try multiple patterns
111
+ const patterns = [
112
+ /\*\*(?:Stack|Framework|Language)\*\*[:\s|]+([^\n|]+)/i,
113
+ /\| \*\*Type\*\* \| ([^\|]+) \|/i,
114
+ /## Tech Stack[\s\S]*?### (?:Frontend|Backend)\n- \*\*([^*]+)\*\*/i
115
+ ]
116
+
117
+ for (const pattern of patterns) {
118
+ const match = summary.match(pattern)
119
+ if (match) return match[1].trim()
120
+ }
121
+
122
+ return null
123
+ }
124
+
125
+ /**
126
+ * Extract current task from now.md
127
+ */
128
+ function extractCurrentTask(now) {
129
+ if (!now) return null
130
+
131
+ // Skip headers and empty lines, get first content line
132
+ const lines = now.split('\n')
133
+ for (const line of lines) {
134
+ const trimmed = line.trim()
135
+ if (trimmed && !trimmed.startsWith('#') && !trimmed.startsWith('<!--')) {
136
+ return trimmed
137
+ }
138
+ }
139
+ return null
140
+ }
141
+
142
+ /**
143
+ * Extract top N tasks from next.md
144
+ */
145
+ function extractTopTasks(next, count) {
146
+ if (!next) return []
147
+
148
+ return next
149
+ .split('\n')
150
+ .filter(l => l.match(/^[-*\d.]\s/))
151
+ .slice(0, count)
152
+ .map(l => l.replace(/^[-*\d.]\s+/, '').trim())
153
+ .filter(Boolean)
154
+ }
155
+
156
+ /**
157
+ * Extract active features from roadmap.md
158
+ */
159
+ function extractActiveFeatures(roadmap) {
160
+ if (!roadmap) return []
161
+
162
+ const features = []
163
+ const lines = roadmap.split('\n')
164
+ let inActive = false
165
+
166
+ for (const line of lines) {
167
+ if (line.includes('Active') || line.includes('In Progress')) {
168
+ inActive = true
169
+ continue
170
+ }
171
+ if (line.startsWith('## ') && inActive) {
172
+ break // End of active section
173
+ }
174
+ if (inActive && line.match(/^- \[ \]/)) {
175
+ const feature = line.replace(/^- \[ \]\s*\*\*/, '').replace(/\*\*.*$/, '').trim()
176
+ if (feature) features.push(feature)
177
+ }
178
+ }
179
+
180
+ return features.slice(0, 3) // Max 3
181
+ }
182
+
183
+ module.exports = { generateLocalContext }
@@ -1,6 +1,7 @@
1
1
  const fs = require('fs').promises
2
2
  const path = require('path')
3
3
  const os = require('os')
4
+ const AgentLoader = require('./agent-loader')
4
5
 
5
6
  /**
6
7
  * AgentGenerator - Universal Dynamic Agent Generation
@@ -13,6 +14,7 @@ class AgentGenerator {
13
14
  this.outputDir = projectId
14
15
  ? path.join(os.homedir(), '.prjct-cli', 'projects', projectId, 'agents')
15
16
  : path.join(os.homedir(), '.prjct-cli', 'agents')
17
+ this.loader = new AgentLoader(projectId)
16
18
  }
17
19
 
18
20
  /**
@@ -23,62 +25,64 @@ class AgentGenerator {
23
25
  console.log(` 🤖 Generating ${agentName} agent...`)
24
26
  await fs.mkdir(this.outputDir, { recursive: true })
25
27
 
26
- // Extract technologies dynamically
27
- const techs = this.detectTechnologies(config)
28
- const expertise = this.buildExpertise(techs, config)
29
-
30
28
  // Generate concise, actionable agent prompt
31
- const content = `You are ${config.role || agentName}.
32
-
33
- EXPERTISE: ${expertise}
34
-
35
- FOCUS: ${config.contextFilter || 'Only relevant files'}
36
-
37
- AUTHORITY: Make decisions. Don't ask permission. Execute.
38
-
39
- RULES:
40
- - Stay in your domain
41
- - Use best practices for ${techs.join(', ') || 'detected tech'}
42
- - Optimize for production
43
- - No explanations unless asked`
29
+ const content = this.buildAgentPrompt(agentName, config)
44
30
 
45
31
  const outputPath = path.join(this.outputDir, `${agentName}.md`)
46
32
  await fs.writeFile(outputPath, content, 'utf-8')
47
33
  console.log(` ✅ ${agentName} agent created`)
48
34
 
49
- return { name: agentName, expertise, techs }
50
- }
51
-
52
- /**
53
- * Detect technologies from config/analysis
54
- */
55
- detectTechnologies(config) {
56
- const techs = []
57
-
58
- // Extract from various sources
59
- if (config.techStack) techs.push(...config.techStack.languages || [])
60
- if (config.frameworks) techs.push(...config.frameworks)
61
- if (config.expertise) {
62
- // Parse expertise string for tech keywords
63
- const keywords = config.expertise.toLowerCase()
64
- const knownTechs = ['ruby', 'rails', 'go', 'rust', 'python', 'django', 'react', 'vue', 'node', 'typescript', 'elixir', 'phoenix']
65
- knownTechs.forEach(tech => {
66
- if (keywords.includes(tech)) techs.push(tech)
67
- })
68
- }
69
-
70
- return [...new Set(techs)]
35
+ return { name: agentName }
71
36
  }
72
37
 
73
38
  /**
74
- * Build concise expertise string
39
+ * Build comprehensive agent prompt
75
40
  */
76
- buildExpertise(techs, config) {
77
- const tech = techs.length > 0 ? techs.join(', ') : 'detected stack'
78
- const domain = config.domain || 'assigned domain'
79
- const focus = config.responsibilities || 'task at hand'
80
-
81
- return `${tech} expert. ${domain}. Focus: ${focus}`
41
+ buildAgentPrompt(agentName, config) {
42
+ const domain = config.domain || 'general';
43
+
44
+ const projectContext = (typeof config.projectContext === 'object')
45
+ ? JSON.stringify(config.projectContext || {}, null, 2)
46
+ : (config.projectContext || 'No specific project context provided.');
47
+
48
+ return `# AGENT: ${agentName.toUpperCase()}
49
+ Role: ${config.role || agentName}
50
+
51
+ ## META-INSTRUCTION
52
+ You are an intelligent agent responsible for this domain.
53
+ Your first task is to ANALYZE the provided PROJECT CONTEXT to determine:
54
+ 1. The technology stack being used.
55
+ 2. The architectural patterns in place.
56
+ 3. The specific best practices relevant to this stack.
57
+
58
+ ## DOMAIN AUTHORITY
59
+ You are the owner of the ${domain} domain.
60
+ You have full authority to make technical decisions within this scope.
61
+
62
+ ## DYNAMIC STANDARDS
63
+ Instead of following a hardcoded list, you must:
64
+ - **DETECT**: Identify the languages, frameworks, and tools from the file extensions and content.
65
+ - **ADAPT**: Adopt the persona of a Senior Engineer specializing in the detected stack.
66
+ - **ENFORCE**: Apply the idiomatic best practices, naming conventions, and patterns of that stack.
67
+
68
+ ## ORCHESTRATION PROTOCOL
69
+ 1. **ANALYZE**: Read the context. Determine the stack.
70
+ 2. **PLAN**: Create a plan that fits the detected architecture.
71
+ 3. **EXECUTE**: Implement using the detected tools and patterns.
72
+ 4. **VERIFY**: Ensure code matches the project's existing style.
73
+
74
+ ## PROJECT CONTEXT
75
+ ${projectContext}
76
+
77
+ ## CONTEXT FOCUS
78
+ ${config.contextFilter || 'Only relevant files'}
79
+
80
+ ## RULES
81
+ - Stay in your domain (${domain})
82
+ - Do not assume a specific stack until you see the code.
83
+ - Optimize for production.
84
+ - No explanations unless asked.
85
+ `;
82
86
  }
83
87
 
84
88
  /**
@@ -124,6 +128,33 @@ RULES:
124
128
  return []
125
129
  }
126
130
  }
131
+
132
+ /**
133
+ * Load an agent from its file
134
+ * CRITICAL: This is how agents are actually used in prompts
135
+ * @param {string} agentName - Name of the agent (without .md extension)
136
+ * @returns {Promise<Object|null>} - Agent object with name, content, role, domain, skills, or null if not found
137
+ */
138
+ async loadAgent(agentName) {
139
+ return await this.loader.loadAgent(agentName)
140
+ }
141
+
142
+ /**
143
+ * Load all agents for the project
144
+ * @returns {Promise<Array<Object>>} - Array of agent objects
145
+ */
146
+ async loadAllAgents() {
147
+ return await this.loader.loadAllAgents()
148
+ }
149
+
150
+ /**
151
+ * Check if an agent exists
152
+ * @param {string} agentName - Name of the agent
153
+ * @returns {Promise<boolean>} - True if agent file exists
154
+ */
155
+ async agentExists(agentName) {
156
+ return await this.loader.agentExists(agentName)
157
+ }
127
158
  }
128
159
 
129
160
  module.exports = AgentGenerator