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.
- package/CHANGELOG.md +312 -0
- package/CLAUDE.md +300 -0
- package/LICENSE +21 -0
- package/README.md +424 -0
- package/bin/prjct +214 -0
- package/core/agent-detector.js +249 -0
- package/core/agents/claude-agent.js +250 -0
- package/core/agents/codex-agent.js +256 -0
- package/core/agents/terminal-agent.js +465 -0
- package/core/analyzer.js +596 -0
- package/core/animations-simple.js +240 -0
- package/core/animations.js +277 -0
- package/core/author-detector.js +218 -0
- package/core/capability-installer.js +190 -0
- package/core/command-installer.js +775 -0
- package/core/commands.js +2050 -0
- package/core/config-manager.js +335 -0
- package/core/migrator.js +784 -0
- package/core/path-manager.js +324 -0
- package/core/project-capabilities.js +144 -0
- package/core/session-manager.js +439 -0
- package/core/version.js +107 -0
- package/core/workflow-engine.js +213 -0
- package/core/workflow-prompts.js +192 -0
- package/core/workflow-rules.js +147 -0
- package/package.json +80 -0
- package/scripts/install.sh +433 -0
- package/scripts/verify-installation.sh +158 -0
- package/templates/agents/AGENTS.md +164 -0
- package/templates/commands/analyze.md +125 -0
- package/templates/commands/cleanup.md +102 -0
- package/templates/commands/context.md +105 -0
- package/templates/commands/design.md +113 -0
- package/templates/commands/done.md +44 -0
- package/templates/commands/fix.md +87 -0
- package/templates/commands/git.md +79 -0
- package/templates/commands/help.md +72 -0
- package/templates/commands/idea.md +50 -0
- package/templates/commands/init.md +237 -0
- package/templates/commands/next.md +74 -0
- package/templates/commands/now.md +35 -0
- package/templates/commands/progress.md +92 -0
- package/templates/commands/recap.md +86 -0
- package/templates/commands/roadmap.md +107 -0
- package/templates/commands/ship.md +41 -0
- package/templates/commands/stuck.md +48 -0
- package/templates/commands/task.md +97 -0
- package/templates/commands/test.md +94 -0
- package/templates/commands/workflow.md +224 -0
- package/templates/examples/natural-language-examples.md +320 -0
- package/templates/mcp-config.json +8 -0
- package/templates/workflows/analyze.md +159 -0
- package/templates/workflows/cleanup.md +73 -0
- package/templates/workflows/context.md +72 -0
- package/templates/workflows/design.md +88 -0
- package/templates/workflows/done.md +20 -0
- package/templates/workflows/fix.md +201 -0
- package/templates/workflows/git.md +192 -0
- package/templates/workflows/help.md +13 -0
- package/templates/workflows/idea.md +22 -0
- package/templates/workflows/init.md +80 -0
- package/templates/workflows/natural-language-handler.md +183 -0
- package/templates/workflows/next.md +44 -0
- package/templates/workflows/now.md +19 -0
- package/templates/workflows/progress.md +113 -0
- package/templates/workflows/recap.md +66 -0
- package/templates/workflows/roadmap.md +95 -0
- package/templates/workflows/ship.md +18 -0
- package/templates/workflows/stuck.md +25 -0
- package/templates/workflows/task.md +109 -0
- 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()
|