prjct-cli 0.5.1 → 0.7.0

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 (81) hide show
  1. package/CHANGELOG.md +220 -7
  2. package/CLAUDE.md +476 -55
  3. package/README.md +48 -55
  4. package/bin/prjct +170 -225
  5. package/core/agentic/command-executor.js +113 -0
  6. package/core/agentic/context-builder.js +85 -0
  7. package/core/agentic/prompt-builder.js +86 -0
  8. package/core/agentic/template-loader.js +104 -0
  9. package/core/agentic/tool-registry.js +117 -0
  10. package/core/command-registry.js +597 -0
  11. package/core/commands.js +2046 -2028
  12. package/core/domain/agent-generator.js +118 -0
  13. package/core/domain/analyzer.js +211 -0
  14. package/core/domain/architect-session.js +300 -0
  15. package/core/{agents → infrastructure/agents}/claude-agent.js +16 -13
  16. package/core/{author-detector.js → infrastructure/author-detector.js} +3 -1
  17. package/core/{capability-installer.js → infrastructure/capability-installer.js} +3 -6
  18. package/core/{command-installer.js → infrastructure/command-installer.js} +4 -2
  19. package/core/{config-manager.js → infrastructure/config-manager.js} +4 -4
  20. package/core/{editors-config.js → infrastructure/editors-config.js} +2 -10
  21. package/core/{migrator.js → infrastructure/migrator.js} +34 -19
  22. package/core/{path-manager.js → infrastructure/path-manager.js} +20 -44
  23. package/core/{session-manager.js → infrastructure/session-manager.js} +45 -105
  24. package/core/{update-checker.js → infrastructure/update-checker.js} +67 -67
  25. package/core/{animations-simple.js → utils/animations.js} +3 -23
  26. package/core/utils/date-helper.js +238 -0
  27. package/core/utils/file-helper.js +327 -0
  28. package/core/utils/jsonl-helper.js +206 -0
  29. package/core/{project-capabilities.js → utils/project-capabilities.js} +21 -22
  30. package/core/utils/session-helper.js +277 -0
  31. package/core/{version.js → utils/version.js} +1 -1
  32. package/package.json +5 -12
  33. package/templates/agents/AGENTS.md +151 -99
  34. package/templates/analysis/analyze.md +84 -0
  35. package/templates/commands/analyze.md +37 -233
  36. package/templates/commands/bug.md +79 -0
  37. package/templates/commands/build.md +44 -0
  38. package/templates/commands/cleanup.md +24 -84
  39. package/templates/commands/design.md +20 -95
  40. package/templates/commands/done.md +17 -180
  41. package/templates/commands/feature.md +113 -0
  42. package/templates/commands/fix.md +58 -66
  43. package/templates/commands/git.md +35 -57
  44. package/templates/commands/help.md +18 -52
  45. package/templates/commands/idea.md +18 -34
  46. package/templates/commands/init.md +65 -257
  47. package/templates/commands/next.md +20 -60
  48. package/templates/commands/now.md +21 -23
  49. package/templates/commands/progress.md +40 -73
  50. package/templates/commands/recap.md +52 -75
  51. package/templates/commands/roadmap.md +30 -85
  52. package/templates/commands/ship.md +93 -126
  53. package/templates/commands/status.md +42 -0
  54. package/templates/commands/sync.md +19 -205
  55. package/templates/commands/task.md +19 -79
  56. package/templates/commands/test.md +25 -71
  57. package/templates/commands/workflow.md +20 -210
  58. package/core/agent-generator.js +0 -516
  59. package/core/analyzer.js +0 -600
  60. package/core/animations.js +0 -277
  61. package/core/git-integration.js +0 -401
  62. package/core/workflow-engine.js +0 -213
  63. package/core/workflow-prompts.js +0 -192
  64. package/core/workflow-rules.js +0 -147
  65. package/scripts/post-install.js +0 -121
  66. package/scripts/preuninstall.js +0 -94
  67. package/scripts/verify-installation.sh +0 -158
  68. package/templates/agents/be.template.md +0 -42
  69. package/templates/agents/data.template.md +0 -41
  70. package/templates/agents/devops.template.md +0 -41
  71. package/templates/agents/fe.template.md +0 -42
  72. package/templates/agents/mobile.template.md +0 -41
  73. package/templates/agents/pm.template.md +0 -84
  74. package/templates/agents/qa.template.md +0 -54
  75. package/templates/agents/scribe.template.md +0 -95
  76. package/templates/agents/security.template.md +0 -41
  77. package/templates/agents/ux.template.md +0 -49
  78. package/templates/commands/context.md +0 -105
  79. package/templates/commands/stuck.md +0 -48
  80. package/templates/examples/natural-language-examples.md +0 -532
  81. /package/core/{agent-detector.js → infrastructure/agent-detector.js} +0 -0
package/core/analyzer.js DELETED
@@ -1,600 +0,0 @@
1
- const fs = require('fs').promises
2
- const path = require('path')
3
- const { promisify } = require('util')
4
- const { exec: execCallback } = require('child_process')
5
- const exec = promisify(execCallback)
6
-
7
- /**
8
- * CodebaseAnalyzer - Analyzes existing code to sync with .prjct/ state
9
- *
10
- * Detects:
11
- * - Implemented commands in bin/prjct and core/commands.js
12
- * - Completed features from git history and file structure
13
- * - Project structure and technologies
14
- *
15
- * Syncs with:
16
- * - next.md (marks completed tasks)
17
- * - shipped.md (adds detected features)
18
- * - analysis/repo-summary.md (creates detailed report)
19
- */
20
- class CodebaseAnalyzer {
21
- constructor() {
22
- this.projectPath = null
23
- this.analysis = null
24
- }
25
-
26
- /**
27
- * Main analysis entry point
28
- */
29
- async analyzeProject(projectPath = process.cwd()) {
30
- this.projectPath = projectPath
31
-
32
- this.analysis = {
33
- commands: await this.detectImplementedCommands(),
34
- features: await this.detectCompletedFeatures(),
35
- structure: await this.detectProjectStructure(),
36
- gitHistory: await this.scanGitHistory(),
37
- technologies: await this.detectTechnologies(),
38
- }
39
-
40
- return this.analysis
41
- }
42
-
43
- /**
44
- * Detect implemented commands in bin/prjct
45
- */
46
- async detectImplementedCommands() {
47
- const commands = []
48
-
49
- try {
50
- const binPath = path.join(this.projectPath, 'bin', 'prjct')
51
-
52
- // Only try to read bin/prjct if it exists (for prjct-cli projects)
53
- if (await this.fileExists(binPath)) {
54
- const content = await fs.readFile(binPath, 'utf-8')
55
-
56
- const caseMatches = content.matchAll(/case\s+'([^']+)':/g)
57
- for (const match of caseMatches) {
58
- commands.push(match[1])
59
- }
60
- }
61
-
62
- const commandsPath = path.join(this.projectPath, 'core', 'commands.js')
63
- if (await this.fileExists(commandsPath)) {
64
- const commandsContent = await fs.readFile(commandsPath, 'utf-8')
65
- const methodMatches = commandsContent.matchAll(/async\s+(\w+)\s*\(/g)
66
-
67
- for (const match of methodMatches) {
68
- const methodName = match[1]
69
- if (!methodName.startsWith('_') &&
70
- methodName !== 'constructor' &&
71
- methodName !== 'initializeAgent' &&
72
- !commands.includes(methodName)) {
73
- commands.push(methodName)
74
- }
75
- }
76
- }
77
- } catch (error) {
78
- console.error('[analyzer] Error detecting commands:', error.message)
79
- }
80
-
81
- return commands
82
- }
83
-
84
- /**
85
- * Detect completed features from various sources
86
- */
87
- async detectCompletedFeatures() {
88
- const features = []
89
-
90
- const gitFeatures = await this.extractFeaturesFromGit()
91
- features.push(...gitFeatures)
92
-
93
- const packageFeatures = await this.extractFeaturesFromPackageJson()
94
- features.push(...packageFeatures)
95
-
96
- const structureFeatures = await this.extractFeaturesFromStructure()
97
- features.push(...structureFeatures)
98
-
99
- return [...new Set(features)]
100
- }
101
-
102
- /**
103
- * Extract features from git commit history
104
- */
105
- async extractFeaturesFromGit() {
106
- const features = []
107
-
108
- try {
109
- const { stdout } = await exec(
110
- 'git log --all --pretty=format:"%s" --grep="^feat:" --grep="^ship:" --grep="^feature:" -i',
111
- { cwd: this.projectPath },
112
- )
113
-
114
- if (stdout) {
115
- const commits = stdout.split('\n')
116
- for (const commit of commits) {
117
- const match = commit.match(/^(?:feat|ship|feature):\s*(.+)/i)
118
- if (match) {
119
- features.push(match[1].trim())
120
- }
121
- }
122
- }
123
- } catch (error) {
124
- }
125
-
126
- return features
127
- }
128
-
129
- /**
130
- * Extract features from package.json dependencies
131
- */
132
- async extractFeaturesFromPackageJson() {
133
- const features = []
134
-
135
- try {
136
- const packagePath = path.join(this.projectPath, 'package.json')
137
- const content = await fs.readFile(packagePath, 'utf-8')
138
- const pkg = JSON.parse(content)
139
-
140
- const deps = { ...pkg.dependencies, ...pkg.devDependencies }
141
-
142
- const featureMap = {
143
- express: 'REST API server',
144
- next: 'Next.js application',
145
- react: 'React frontend',
146
- vue: 'Vue application',
147
- typescript: 'TypeScript support',
148
- jest: 'Testing framework',
149
- prisma: 'Database ORM',
150
- mongoose: 'MongoDB integration',
151
- stripe: 'Payment processing',
152
- passport: 'Authentication system',
153
- }
154
-
155
- for (const [dep, feature] of Object.entries(featureMap)) {
156
- if (deps[dep]) {
157
- features.push(feature)
158
- }
159
- }
160
- } catch (error) {
161
- }
162
-
163
- return features
164
- }
165
-
166
- /**
167
- * Extract features from directory structure
168
- */
169
- async extractFeaturesFromStructure() {
170
- const features = []
171
-
172
- try {
173
- const entries = await fs.readdir(this.projectPath, { withFileTypes: true })
174
-
175
- const featureDirs = {
176
- auth: 'Authentication system',
177
- api: 'API endpoints',
178
- admin: 'Admin panel',
179
- dashboard: 'Dashboard interface',
180
- payment: 'Payment integration',
181
- notifications: 'Notification system',
182
- chat: 'Chat functionality',
183
- search: 'Search feature',
184
- analytics: 'Analytics tracking',
185
- }
186
-
187
- for (const entry of entries) {
188
- if (entry.isDirectory()) {
189
- const dirName = entry.name.toLowerCase()
190
- if (featureDirs[dirName]) {
191
- features.push(featureDirs[dirName])
192
- }
193
- }
194
- }
195
- } catch (error) {
196
- }
197
-
198
- return features
199
- }
200
-
201
- /**
202
- * Detect project structure and organization
203
- */
204
- async detectProjectStructure() {
205
- const structure = {
206
- hasTests: false,
207
- hasDocs: false,
208
- hasCI: false,
209
- fileCount: 0,
210
- directories: [],
211
- }
212
-
213
- try {
214
- const entries = await fs.readdir(this.projectPath, { withFileTypes: true })
215
-
216
- for (const entry of entries) {
217
- if (entry.isDirectory()) {
218
- structure.directories.push(entry.name)
219
-
220
- if (entry.name === 'test' || entry.name === 'tests' || entry.name === '__tests__') {
221
- structure.hasTests = true
222
- }
223
- if (entry.name === 'docs' || entry.name === 'documentation') {
224
- structure.hasDocs = true
225
- }
226
- }
227
- }
228
-
229
- const ciFiles = ['.github/workflows', '.gitlab-ci.yml', 'jenkins', '.circleci']
230
- for (const ciFile of ciFiles) {
231
- if (await this.fileExists(path.join(this.projectPath, ciFile))) {
232
- structure.hasCI = true
233
- break
234
- }
235
- }
236
-
237
- structure.fileCount = await this.countFiles(this.projectPath)
238
- } catch (error) {
239
- }
240
-
241
- return structure
242
- }
243
-
244
- /**
245
- * Scan git history for insights
246
- */
247
- async scanGitHistory() {
248
- const history = {
249
- totalCommits: 0,
250
- contributors: [],
251
- firstCommit: null,
252
- lastCommit: null,
253
- hasGit: false,
254
- }
255
-
256
- try {
257
- await exec('git rev-parse --git-dir', { cwd: this.projectPath })
258
- history.hasGit = true
259
-
260
- const { stdout: countOut } = await exec('git rev-list --count HEAD', { cwd: this.projectPath })
261
- history.totalCommits = parseInt(countOut.trim()) || 0
262
-
263
- const { stdout: contributorsOut } = await exec(
264
- 'git log --format="%an" | sort -u',
265
- { cwd: this.projectPath },
266
- )
267
- history.contributors = contributorsOut.trim().split('\n').filter(Boolean)
268
-
269
- const { stdout: firstOut } = await exec(
270
- 'git log --reverse --format="%ai" --max-count=1',
271
- { cwd: this.projectPath },
272
- )
273
- history.firstCommit = firstOut.trim()
274
-
275
- const { stdout: lastOut } = await exec(
276
- 'git log --format="%ai" --max-count=1',
277
- { cwd: this.projectPath },
278
- )
279
- history.lastCommit = lastOut.trim()
280
- } catch (error) {
281
- history.hasGit = false
282
- }
283
-
284
- return history
285
- }
286
-
287
- /**
288
- * Detect technologies used in the project
289
- */
290
- async detectTechnologies() {
291
- const technologies = []
292
-
293
- try {
294
- const packagePath = path.join(this.projectPath, 'package.json')
295
- if (await this.fileExists(packagePath)) {
296
- const content = await fs.readFile(packagePath, 'utf-8')
297
- const pkg = JSON.parse(content)
298
-
299
- technologies.push('Node.js')
300
-
301
- const deps = { ...pkg.dependencies, ...pkg.devDependencies }
302
-
303
- if (deps.typescript) technologies.push('TypeScript')
304
- if (deps.react) technologies.push('React')
305
- if (deps.next) technologies.push('Next.js')
306
- if (deps.vue) technologies.push('Vue.js')
307
- if (deps.express) technologies.push('Express')
308
- if (deps.fastify) technologies.push('Fastify')
309
- }
310
-
311
- const entries = await fs.readdir(this.projectPath)
312
-
313
- if (entries.some(f => f.endsWith('.py'))) technologies.push('Python')
314
- if (entries.some(f => f.endsWith('.go'))) technologies.push('Go')
315
- if (entries.some(f => f.endsWith('.rs'))) technologies.push('Rust')
316
- if (entries.some(f => f.endsWith('.rb'))) technologies.push('Ruby')
317
- if (entries.some(f => f.endsWith('.java'))) technologies.push('Java')
318
- } catch (error) {
319
- }
320
-
321
- return [...new Set(technologies)]
322
- }
323
-
324
- /**
325
- * Sync analysis results with .prjct/ files
326
- */
327
- async syncWithPrjctFiles(globalProjectPath) {
328
- const syncResults = {
329
- nextMdUpdated: false,
330
- shippedMdUpdated: false,
331
- tasksMarkedComplete: 0,
332
- featuresAdded: 0,
333
- }
334
-
335
- try {
336
- syncResults.tasksMarkedComplete = await this.updateNextMd(globalProjectPath)
337
- if (syncResults.tasksMarkedComplete > 0) {
338
- syncResults.nextMdUpdated = true
339
- }
340
-
341
- syncResults.featuresAdded = await this.updateShippedMd(globalProjectPath)
342
- if (syncResults.featuresAdded > 0) {
343
- syncResults.shippedMdUpdated = true
344
- }
345
-
346
- await this.createAnalysisReport(globalProjectPath)
347
- } catch (error) {
348
- console.error('[analyzer] Error syncing with .prjct files:', error.message)
349
- }
350
-
351
- return syncResults
352
- }
353
-
354
- /**
355
- * Update next.md by marking completed tasks
356
- */
357
- async updateNextMd(globalProjectPath) {
358
- let tasksMarkedComplete = 0
359
-
360
- try {
361
- const nextPath = path.join(globalProjectPath, 'core', 'next.md')
362
-
363
- if (!(await this.fileExists(nextPath))) {
364
- return 0
365
- }
366
-
367
- const content = await fs.readFile(nextPath, 'utf-8')
368
- const lines = content.split('\n')
369
- const implementedCommands = this.analysis.commands.map(c => c.toLowerCase())
370
-
371
- let modified = false
372
- const newLines = []
373
-
374
- for (const line of lines) {
375
- if (line.startsWith('- ') && !line.includes('✅')) {
376
- const taskText = line.substring(2).toLowerCase()
377
-
378
- const isImplemented = implementedCommands.some(cmd =>
379
- taskText.includes(cmd) || taskText.includes(`/p:${cmd}`),
380
- )
381
-
382
- if (isImplemented) {
383
- newLines.push(line.replace('- ', '- ✅ ') + ' _(auto-detected)_')
384
- tasksMarkedComplete++
385
- modified = true
386
- } else {
387
- newLines.push(line)
388
- }
389
- } else {
390
- newLines.push(line)
391
- }
392
- }
393
-
394
- if (modified) {
395
- await fs.writeFile(nextPath, newLines.join('\n'), 'utf-8')
396
- }
397
- } catch (error) {
398
- console.error('[analyzer] Error updating next.md:', error.message)
399
- }
400
-
401
- return tasksMarkedComplete
402
- }
403
-
404
- /**
405
- * Update shipped.md with detected features
406
- */
407
- async updateShippedMd(globalProjectPath) {
408
- let featuresAdded = 0
409
-
410
- try {
411
- const shippedPath = path.join(globalProjectPath, 'progress', 'shipped.md')
412
-
413
- if (!(await this.fileExists(shippedPath))) {
414
- return 0
415
- }
416
-
417
- let content = await fs.readFile(shippedPath, 'utf-8')
418
-
419
- const now = new Date()
420
- const week = this.getWeekNumber(now)
421
- const year = now.getFullYear()
422
- const weekHeader = `## Week ${week}, ${year}`
423
-
424
- if (!content.includes(weekHeader)) {
425
- content += `\n${weekHeader}\n`
426
- }
427
-
428
- for (const feature of this.analysis.features) {
429
- if (!content.includes(feature)) {
430
- const entry = `- ✅ **${feature}** _(auto-detected on ${now.toLocaleDateString()})_\n`
431
- const insertIndex = content.indexOf(weekHeader) + weekHeader.length + 1
432
- content = content.slice(0, insertIndex) + entry + content.slice(insertIndex)
433
- featuresAdded++
434
- }
435
- }
436
-
437
- if (featuresAdded > 0) {
438
- await fs.writeFile(shippedPath, content, 'utf-8')
439
- }
440
- } catch (error) {
441
- console.error('[analyzer] Error updating shipped.md:', error.message)
442
- }
443
-
444
- return featuresAdded
445
- }
446
-
447
- /**
448
- * Create detailed analysis report
449
- */
450
- async createAnalysisReport(globalProjectPath) {
451
- try {
452
- const analysisDir = path.join(globalProjectPath, 'analysis')
453
- await fs.mkdir(analysisDir, { recursive: true })
454
-
455
- const reportPath = path.join(analysisDir, 'repo-summary.md')
456
- const report = this.generateAnalysisReport()
457
-
458
- await fs.writeFile(reportPath, report, 'utf-8')
459
- } catch (error) {
460
- console.error('[analyzer] Error creating analysis report:', error.message)
461
- }
462
- }
463
-
464
- /**
465
- * Generate formatted analysis report
466
- */
467
- generateAnalysisReport() {
468
- const { commands, features, structure, gitHistory, technologies } = this.analysis
469
-
470
- return `# Project Analysis Report
471
-
472
- **Generated:** ${new Date().toLocaleString()}
473
-
474
- ## 📊 Overview
475
-
476
- - **Technologies:** ${technologies.join(', ') || 'Not detected'}
477
- - **Commands Implemented:** ${commands.length}
478
- - **Features Detected:** ${features.length}
479
- - **Total Files:** ~${structure.fileCount}
480
-
481
- ## 🛠️ Implemented Commands
482
-
483
- ${commands.map(cmd => `- \`/p:${cmd}\``).join('\n') || '_(none detected)_'}
484
-
485
- ## ✨ Completed Features
486
-
487
- ${features.map(f => `- ${f}`).join('\n') || '_(none detected)_'}
488
-
489
- ## 🏗️ Project Structure
490
-
491
- - **Has Tests:** ${structure.hasTests ? '✅' : '❌'}
492
- - **Has Documentation:** ${structure.hasDocs ? '✅' : '❌'}
493
- - **Has CI/CD:** ${structure.hasCI ? '✅' : '❌'}
494
- - **Directories:** ${structure.directories.join(', ')}
495
-
496
- ## 📜 Git History
497
-
498
- ${gitHistory.hasGit
499
- ? `
500
- - **Total Commits:** ${gitHistory.totalCommits}
501
- - **Contributors:** ${gitHistory.contributors.join(', ')}
502
- - **First Commit:** ${gitHistory.firstCommit}
503
- - **Last Commit:** ${gitHistory.lastCommit}
504
- `
505
- : '_Not a git repository_'}
506
-
507
- ## 💡 Recommendations
508
-
509
- ${this.generateRecommendations()}
510
-
511
- ---
512
- _This report was auto-generated by prjct analyze_
513
- `
514
- }
515
-
516
- /**
517
- * Generate recommendations based on analysis
518
- */
519
- generateRecommendations() {
520
- const recommendations = []
521
- const { structure, gitHistory } = this.analysis
522
-
523
- if (!structure.hasTests) {
524
- recommendations.push('- Consider adding tests to improve code quality')
525
- }
526
-
527
- if (!structure.hasDocs) {
528
- recommendations.push('- Add documentation to help onboard new contributors')
529
- }
530
-
531
- if (!structure.hasCI) {
532
- recommendations.push('- Set up CI/CD for automated testing and deployment')
533
- }
534
-
535
- if (gitHistory.hasGit && gitHistory.totalCommits < 10) {
536
- recommendations.push('- Early stage project - focus on core features first')
537
- }
538
-
539
- if (recommendations.length === 0) {
540
- return '- Project is well-structured! Keep up the good work.'
541
- }
542
-
543
- return recommendations.join('\n')
544
- }
545
-
546
- /**
547
- * Helper: Check if file exists
548
- */
549
- async fileExists(filePath) {
550
- try {
551
- await fs.access(filePath)
552
- return true
553
- } catch {
554
- return false
555
- }
556
- }
557
-
558
- /**
559
- * Helper: Count files recursively (with limit for performance)
560
- */
561
- async countFiles(dirPath, maxDepth = 3, currentDepth = 0) {
562
- if (currentDepth > maxDepth) return 0
563
-
564
- let count = 0
565
-
566
- try {
567
- const entries = await fs.readdir(dirPath, { withFileTypes: true })
568
-
569
- for (const entry of entries) {
570
- if (entry.name.startsWith('.') || entry.name === 'node_modules') {
571
- continue
572
- }
573
-
574
- if (entry.isFile()) {
575
- count++
576
- } else if (entry.isDirectory()) {
577
- count += await this.countFiles(
578
- path.join(dirPath, entry.name),
579
- maxDepth,
580
- currentDepth + 1,
581
- )
582
- }
583
- }
584
- } catch {
585
- }
586
-
587
- return count
588
- }
589
-
590
- /**
591
- * Helper: Get week number
592
- */
593
- getWeekNumber(date) {
594
- const firstDayOfYear = new Date(date.getFullYear(), 0, 1)
595
- const pastDaysOfYear = (date - firstDayOfYear) / 86400000
596
- return Math.ceil((pastDaysOfYear + firstDayOfYear.getDay() + 1) / 7)
597
- }
598
- }
599
-
600
- module.exports = new CodebaseAnalyzer()