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.
- package/CHANGELOG.md +142 -0
- package/core/__tests__/agentic/memory-system.test.js +263 -0
- package/core/__tests__/agentic/plan-mode.test.js +336 -0
- package/core/agentic/agent-router.js +253 -186
- package/core/agentic/chain-of-thought.js +578 -0
- package/core/agentic/command-executor.js +299 -17
- package/core/agentic/context-builder.js +208 -8
- package/core/agentic/context-filter.js +83 -83
- package/core/agentic/ground-truth.js +591 -0
- package/core/agentic/loop-detector.js +406 -0
- package/core/agentic/memory-system.js +850 -0
- package/core/agentic/parallel-tools.js +366 -0
- package/core/agentic/plan-mode.js +572 -0
- package/core/agentic/prompt-builder.js +127 -2
- package/core/agentic/response-templates.js +290 -0
- package/core/agentic/semantic-compression.js +517 -0
- package/core/agentic/think-blocks.js +657 -0
- package/core/agentic/tool-registry.js +32 -0
- package/core/agentic/validation-rules.js +380 -0
- package/core/command-registry.js +48 -0
- package/core/commands.js +128 -60
- package/core/context-sync.js +183 -0
- package/core/domain/agent-generator.js +77 -46
- package/core/domain/agent-loader.js +183 -0
- package/core/domain/agent-matcher.js +217 -0
- package/core/domain/agent-validator.js +217 -0
- package/core/domain/context-estimator.js +175 -0
- package/core/domain/product-standards.js +92 -0
- package/core/domain/smart-cache.js +157 -0
- package/core/domain/task-analyzer.js +353 -0
- package/core/domain/tech-detector.js +365 -0
- package/package.json +8 -16
- package/templates/commands/done.md +7 -0
- package/templates/commands/feature.md +8 -0
- package/templates/commands/ship.md +8 -0
- package/templates/commands/spec.md +128 -0
- package/templates/global/CLAUDE.md +17 -0
- package/core/__tests__/agentic/agent-router.test.js +0 -398
- package/core/__tests__/agentic/command-executor.test.js +0 -223
- package/core/__tests__/agentic/context-builder.test.js +0 -160
- package/core/__tests__/agentic/context-filter.test.js +0 -494
- package/core/__tests__/agentic/prompt-builder.test.js +0 -212
- package/core/__tests__/agentic/template-loader.test.js +0 -164
- package/core/__tests__/agentic/tool-registry.test.js +0 -243
- package/core/__tests__/domain/agent-generator.test.js +0 -296
- package/core/__tests__/domain/analyzer.test.js +0 -324
- package/core/__tests__/infrastructure/author-detector.test.js +0 -103
- package/core/__tests__/infrastructure/config-manager.test.js +0 -454
- package/core/__tests__/infrastructure/path-manager.test.js +0 -412
- package/core/__tests__/setup.test.js +0 -15
- package/core/__tests__/utils/date-helper.test.js +0 -169
- package/core/__tests__/utils/file-helper.test.js +0 -258
- 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
|
|
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
|
-
*
|
|
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
|
-
//
|
|
2516
|
-
//
|
|
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
|
-
//
|
|
2530
|
-
|
|
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 (
|
|
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: `${
|
|
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
|
-
|
|
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
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
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 (
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
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(
|
|
2615
|
+
agents.push(agentName)
|
|
2564
2616
|
}
|
|
2565
2617
|
|
|
2566
|
-
if
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
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('
|
|
2628
|
+
agents.push('database-specialist')
|
|
2574
2629
|
}
|
|
2575
2630
|
|
|
2576
|
-
if
|
|
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,
|
|
2635
|
+
expertise: tech.tools.filter(t => ['Docker', 'Kubernetes', 'Terraform'].includes(t)).join(', '),
|
|
2580
2636
|
responsibilities: 'Handle containerization, deployment, infrastructure setup',
|
|
2581
|
-
projectContext: {
|
|
2637
|
+
projectContext: {
|
|
2638
|
+
tools: tech.tools
|
|
2639
|
+
},
|
|
2582
2640
|
})
|
|
2583
2641
|
agents.push('devops-specialist')
|
|
2584
2642
|
}
|
|
2585
2643
|
|
|
2586
|
-
//
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
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 =
|
|
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
|
|
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
|
|
39
|
+
* Build comprehensive agent prompt
|
|
75
40
|
*/
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
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
|