prjct-cli 0.4.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 (71) hide show
  1. package/CHANGELOG.md +312 -0
  2. package/CLAUDE.md +300 -0
  3. package/LICENSE +21 -0
  4. package/README.md +424 -0
  5. package/bin/prjct +214 -0
  6. package/core/agent-detector.js +249 -0
  7. package/core/agents/claude-agent.js +250 -0
  8. package/core/agents/codex-agent.js +256 -0
  9. package/core/agents/terminal-agent.js +465 -0
  10. package/core/analyzer.js +596 -0
  11. package/core/animations-simple.js +240 -0
  12. package/core/animations.js +277 -0
  13. package/core/author-detector.js +218 -0
  14. package/core/capability-installer.js +190 -0
  15. package/core/command-installer.js +775 -0
  16. package/core/commands.js +2050 -0
  17. package/core/config-manager.js +335 -0
  18. package/core/migrator.js +784 -0
  19. package/core/path-manager.js +324 -0
  20. package/core/project-capabilities.js +144 -0
  21. package/core/session-manager.js +439 -0
  22. package/core/version.js +107 -0
  23. package/core/workflow-engine.js +213 -0
  24. package/core/workflow-prompts.js +192 -0
  25. package/core/workflow-rules.js +147 -0
  26. package/package.json +80 -0
  27. package/scripts/install.sh +433 -0
  28. package/scripts/verify-installation.sh +158 -0
  29. package/templates/agents/AGENTS.md +164 -0
  30. package/templates/commands/analyze.md +125 -0
  31. package/templates/commands/cleanup.md +102 -0
  32. package/templates/commands/context.md +105 -0
  33. package/templates/commands/design.md +113 -0
  34. package/templates/commands/done.md +44 -0
  35. package/templates/commands/fix.md +87 -0
  36. package/templates/commands/git.md +79 -0
  37. package/templates/commands/help.md +72 -0
  38. package/templates/commands/idea.md +50 -0
  39. package/templates/commands/init.md +237 -0
  40. package/templates/commands/next.md +74 -0
  41. package/templates/commands/now.md +35 -0
  42. package/templates/commands/progress.md +92 -0
  43. package/templates/commands/recap.md +86 -0
  44. package/templates/commands/roadmap.md +107 -0
  45. package/templates/commands/ship.md +41 -0
  46. package/templates/commands/stuck.md +48 -0
  47. package/templates/commands/task.md +97 -0
  48. package/templates/commands/test.md +94 -0
  49. package/templates/commands/workflow.md +224 -0
  50. package/templates/examples/natural-language-examples.md +320 -0
  51. package/templates/mcp-config.json +8 -0
  52. package/templates/workflows/analyze.md +159 -0
  53. package/templates/workflows/cleanup.md +73 -0
  54. package/templates/workflows/context.md +72 -0
  55. package/templates/workflows/design.md +88 -0
  56. package/templates/workflows/done.md +20 -0
  57. package/templates/workflows/fix.md +201 -0
  58. package/templates/workflows/git.md +192 -0
  59. package/templates/workflows/help.md +13 -0
  60. package/templates/workflows/idea.md +22 -0
  61. package/templates/workflows/init.md +80 -0
  62. package/templates/workflows/natural-language-handler.md +183 -0
  63. package/templates/workflows/next.md +44 -0
  64. package/templates/workflows/now.md +19 -0
  65. package/templates/workflows/progress.md +113 -0
  66. package/templates/workflows/recap.md +66 -0
  67. package/templates/workflows/roadmap.md +95 -0
  68. package/templates/workflows/ship.md +18 -0
  69. package/templates/workflows/stuck.md +25 -0
  70. package/templates/workflows/task.md +109 -0
  71. package/templates/workflows/test.md +243 -0
@@ -0,0 +1,218 @@
1
+ const { promisify } = require('util')
2
+ const { exec: execCallback } = require('child_process')
3
+ const exec = promisify(execCallback)
4
+
5
+ /**
6
+ * AuthorDetector - Detects author information from multiple sources
7
+ *
8
+ * Detection priority:
9
+ * 1. GitHub CLI (gh api user) - Most reliable for GitHub username
10
+ * 2. Git config (user.name and user.email)
11
+ * 3. Manual prompt (fallback)
12
+ *
13
+ * @version 0.2.0
14
+ */
15
+ class AuthorDetector {
16
+ /**
17
+ * Execute a shell command safely
18
+ *
19
+ * @param {string} command - Command to execute
20
+ * @returns {Promise<{success: boolean, output: string}>}
21
+ * @private
22
+ */
23
+ async execCommand(command) {
24
+ try {
25
+ const { stdout } = await exec(command, { timeout: 5000 })
26
+ return {
27
+ success: true,
28
+ output: stdout.trim(),
29
+ }
30
+ } catch (error) {
31
+ return {
32
+ success: false,
33
+ output: '',
34
+ }
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Detect GitHub username using GitHub CLI
40
+ *
41
+ * @returns {Promise<string|null>} - GitHub username or null
42
+ */
43
+ async detectGitHubUsername() {
44
+ let result = await this.execCommand('gh api user --jq .login')
45
+ if (result.success && result.output) {
46
+ return result.output
47
+ }
48
+
49
+ result = await this.execCommand('git config --global github.user')
50
+ if (result.success && result.output) {
51
+ return result.output
52
+ }
53
+
54
+ return null
55
+ }
56
+
57
+ /**
58
+ * Detect git config name
59
+ *
60
+ * @returns {Promise<string|null>} - Git name or null
61
+ */
62
+ async detectGitName() {
63
+ const result = await this.execCommand('git config user.name')
64
+ return result.success && result.output ? result.output : null
65
+ }
66
+
67
+ /**
68
+ * Detect git config email
69
+ *
70
+ * @returns {Promise<string|null>} - Git email or null
71
+ */
72
+ async detectGitEmail() {
73
+ const result = await this.execCommand('git config user.email')
74
+ return result.success && result.output ? result.output : null
75
+ }
76
+
77
+ /**
78
+ * Detect author information from all available sources
79
+ *
80
+ * @returns {Promise<Object>} - Author information {name, email, github}
81
+ */
82
+ async detect() {
83
+ const author = {
84
+ name: null,
85
+ email: null,
86
+ github: null,
87
+ }
88
+
89
+ author.github = await this.detectGitHubUsername()
90
+
91
+ author.name = await this.detectGitName()
92
+ author.email = await this.detectGitEmail()
93
+
94
+ if (!author.name && author.github) {
95
+ author.name = author.github
96
+ }
97
+
98
+ if (!author.name) {
99
+ author.name = 'Unknown'
100
+ }
101
+
102
+ return author
103
+ }
104
+
105
+ /**
106
+ * Detect and format author for memory logs
107
+ * Returns just the GitHub username or git name
108
+ *
109
+ * @returns {Promise<string>} - Author identifier for logs
110
+ */
111
+ async detectAuthorForLogs() {
112
+ const author = await this.detect()
113
+
114
+ if (author.github) {
115
+ return author.github
116
+ }
117
+
118
+ if (author.name && author.name !== 'Unknown') {
119
+ return author.name
120
+ }
121
+
122
+ return 'unknown'
123
+ }
124
+
125
+ /**
126
+ * Check if GitHub CLI is available
127
+ *
128
+ * @returns {Promise<boolean>} - True if gh command is available
129
+ */
130
+ async isGitHubCLIAvailable() {
131
+ const result = await this.execCommand('gh --version')
132
+ return result.success
133
+ }
134
+
135
+ /**
136
+ * Check if git is configured
137
+ *
138
+ * @returns {Promise<boolean>} - True if git name and email are set
139
+ */
140
+ async isGitConfigured() {
141
+ const name = await this.detectGitName()
142
+ const email = await this.detectGitEmail()
143
+ return !!(name && email)
144
+ }
145
+
146
+ /**
147
+ * Get configuration status and recommendations
148
+ *
149
+ * @returns {Promise<Object>} - Status and recommendations
150
+ */
151
+ async getConfigStatus() {
152
+ const hasGitHub = await this.isGitHubCLIAvailable()
153
+ const hasGit = await this.isGitConfigured()
154
+ const author = await this.detect()
155
+
156
+ return {
157
+ hasGitHub,
158
+ hasGit,
159
+ author,
160
+ isComplete: !!(author.github || (author.name !== 'Unknown' && author.email)),
161
+ recommendations: this._getRecommendations(hasGitHub, hasGit, author),
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Generate recommendations based on detected configuration
167
+ *
168
+ * @param {boolean} hasGitHub - GitHub CLI available
169
+ * @param {boolean} hasGit - Git configured
170
+ * @param {Object} author - Detected author
171
+ * @returns {string[]} - Array of recommendations
172
+ * @private
173
+ */
174
+ _getRecommendations(hasGitHub, hasGit, author) {
175
+ const recommendations = []
176
+
177
+ if (!hasGitHub && !author.github) {
178
+ recommendations.push('Install GitHub CLI (gh) for better collaboration support: https://cli.github.com/')
179
+ }
180
+
181
+ if (!hasGit) {
182
+ recommendations.push('Configure git user: git config --global user.name "Your Name"')
183
+ recommendations.push('Configure git email: git config --global user.email "your@email.com"')
184
+ }
185
+
186
+ if (author.github && !author.email) {
187
+ recommendations.push('Consider setting your git email for better tracking')
188
+ }
189
+
190
+ return recommendations
191
+ }
192
+
193
+ /**
194
+ * Format author information for display
195
+ *
196
+ * @param {Object} author - Author object
197
+ * @returns {string} - Formatted author string
198
+ */
199
+ formatAuthor(author) {
200
+ const parts = []
201
+
202
+ if (author.name && author.name !== 'Unknown') {
203
+ parts.push(author.name)
204
+ }
205
+
206
+ if (author.github) {
207
+ parts.push(`@${author.github}`)
208
+ }
209
+
210
+ if (author.email) {
211
+ parts.push(`<${author.email}>`)
212
+ }
213
+
214
+ return parts.join(' ') || 'Unknown'
215
+ }
216
+ }
217
+
218
+ module.exports = new AuthorDetector()
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Capability Installer
3
+ * Handles installation of missing tools and tracks them as workflow tasks
4
+ */
5
+
6
+ const { exec } = require('child_process')
7
+ const { promisify } = require('util')
8
+ const execAsync = promisify(exec)
9
+
10
+ class CapabilityInstaller {
11
+ /**
12
+ * Install capability and create tracking task
13
+ */
14
+ async install(capability, recommendation, _dataPath) {
15
+ const command = recommendation.install
16
+ const startTime = Date.now()
17
+
18
+ try {
19
+ // Execute installation command
20
+ const { stdout, stderr } = await execAsync(command)
21
+
22
+ const duration = Date.now() - startTime
23
+ const durationMin = Math.round(duration / 1000 / 60 * 10) / 10
24
+
25
+ return {
26
+ success: true,
27
+ capability,
28
+ command,
29
+ duration: durationMin,
30
+ output: stdout,
31
+ errors: stderr || null,
32
+ }
33
+ } catch (error) {
34
+ return {
35
+ success: false,
36
+ capability,
37
+ command,
38
+ error: error.message,
39
+ }
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Create configuration for installed tool
45
+ */
46
+ async configure(capability, projectPath) {
47
+ const configs = {
48
+ test: () => this.configureTest(projectPath),
49
+ design: () => this.configureDesign(projectPath),
50
+ docs: () => this.configureDocs(projectPath),
51
+ }
52
+
53
+ if (configs[capability]) {
54
+ return await configs[capability]()
55
+ }
56
+
57
+ return { configured: false }
58
+ }
59
+
60
+ /**
61
+ * Configure test framework
62
+ */
63
+ async configureTest(projectPath) {
64
+ const fs = require('fs').promises
65
+ const path = require('path')
66
+
67
+ // Check if package.json exists
68
+ const pkgPath = path.join(projectPath, 'package.json')
69
+ const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'))
70
+
71
+ const hasVitest = pkg.devDependencies?.vitest
72
+ const hasJest = pkg.devDependencies?.jest
73
+
74
+ if (hasVitest) {
75
+ // Create vitest.config.js
76
+ const config = `import { defineConfig } from 'vitest/config'
77
+
78
+ export default defineConfig({
79
+ test: {
80
+ globals: true,
81
+ environment: 'jsdom',
82
+ setupFiles: './test/setup.js'
83
+ }
84
+ })
85
+ `
86
+ await fs.writeFile(path.join(projectPath, 'vitest.config.js'), config)
87
+
88
+ // Add test script
89
+ pkg.scripts = pkg.scripts || {}
90
+ pkg.scripts.test = 'vitest'
91
+ pkg.scripts['test:ui'] = 'vitest --ui'
92
+ await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2))
93
+
94
+ return { configured: true, framework: 'vitest' }
95
+ }
96
+
97
+ if (hasJest) {
98
+ // Create jest.config.js
99
+ const config = `module.exports = {
100
+ testEnvironment: 'jsdom',
101
+ setupFilesAfterEnv: ['<rootDir>/test/setup.js'],
102
+ moduleNameMapper: {
103
+ '\\\\.(css|less|scss|sass)$': 'identity-obj-proxy'
104
+ }
105
+ }
106
+ `
107
+ await fs.writeFile(path.join(projectPath, 'jest.config.js'), config)
108
+
109
+ // Add test script
110
+ pkg.scripts = pkg.scripts || {}
111
+ pkg.scripts.test = 'jest'
112
+ pkg.scripts['test:watch'] = 'jest --watch'
113
+ await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2))
114
+
115
+ return { configured: true, framework: 'jest' }
116
+ }
117
+
118
+ return { configured: false }
119
+ }
120
+
121
+ /**
122
+ * Configure design system
123
+ */
124
+ async configureDesign(projectPath) {
125
+ const fs = require('fs').promises
126
+ const path = require('path')
127
+
128
+ const pkgPath = path.join(projectPath, 'package.json')
129
+ const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'))
130
+
131
+ if (pkg.devDependencies?.storybook) {
132
+ // Storybook auto-configures itself during init
133
+ return { configured: true, tool: 'storybook' }
134
+ }
135
+
136
+ return { configured: false }
137
+ }
138
+
139
+ /**
140
+ * Configure documentation
141
+ */
142
+ async configureDocs(projectPath) {
143
+ const fs = require('fs').promises
144
+ const path = require('path')
145
+
146
+ const pkgPath = path.join(projectPath, 'package.json')
147
+ const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'))
148
+
149
+ if (pkg.devDependencies?.jsdoc) {
150
+ // Create jsdoc.json
151
+ const config = {
152
+ source: {
153
+ include: ['src'],
154
+ includePattern: '.+\\\\.js(doc|x)?$',
155
+ excludePattern: '(node_modules|docs)',
156
+ },
157
+ opts: {
158
+ destination: './docs',
159
+ recurse: true,
160
+ },
161
+ }
162
+
163
+ await fs.writeFile(
164
+ path.join(projectPath, 'jsdoc.json'),
165
+ JSON.stringify(config, null, 2),
166
+ )
167
+
168
+ // Add docs script
169
+ pkg.scripts = pkg.scripts || {}
170
+ pkg.scripts.docs = 'jsdoc -c jsdoc.json'
171
+ await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2))
172
+
173
+ return { configured: true, tool: 'jsdoc' }
174
+ }
175
+
176
+ return { configured: false }
177
+ }
178
+
179
+ /**
180
+ * Verify installation succeeded
181
+ */
182
+ async verify(capability, projectPath) {
183
+ const caps = require('./project-capabilities')
184
+ const detected = await caps.detect(projectPath)
185
+
186
+ return detected[capability] === true
187
+ }
188
+ }
189
+
190
+ module.exports = new CapabilityInstaller()