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,335 @@
|
|
|
1
|
+
const fs = require('fs').promises
|
|
2
|
+
const pathManager = require('./path-manager')
|
|
3
|
+
const { VERSION } = require('./version')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ConfigManager - Manages prjct.config.json files
|
|
7
|
+
*
|
|
8
|
+
* Key responsibilities:
|
|
9
|
+
* - Read and write prjct.config.json
|
|
10
|
+
* - Validate configuration structure
|
|
11
|
+
* - Create new configurations
|
|
12
|
+
* - Update existing configurations
|
|
13
|
+
*
|
|
14
|
+
* @version 0.2.1
|
|
15
|
+
*/
|
|
16
|
+
class ConfigManager {
|
|
17
|
+
/**
|
|
18
|
+
* Read the project configuration file
|
|
19
|
+
*
|
|
20
|
+
* @param {string} projectPath - Path to the project
|
|
21
|
+
* @returns {Promise<Object|null>} - Configuration object or null if not found
|
|
22
|
+
*/
|
|
23
|
+
async readConfig(projectPath) {
|
|
24
|
+
try {
|
|
25
|
+
const configPath = pathManager.getLocalConfigPath(projectPath)
|
|
26
|
+
const content = await fs.readFile(configPath, 'utf-8')
|
|
27
|
+
return JSON.parse(content)
|
|
28
|
+
} catch {
|
|
29
|
+
return null
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Write the project configuration file
|
|
35
|
+
*
|
|
36
|
+
* @param {string} projectPath - Path to the project
|
|
37
|
+
* @param {Object} config - Configuration object
|
|
38
|
+
* @returns {Promise<void>}
|
|
39
|
+
*/
|
|
40
|
+
async writeConfig(projectPath, config) {
|
|
41
|
+
const configPath = pathManager.getLocalConfigPath(projectPath)
|
|
42
|
+
const configDir = pathManager.getLegacyPrjctPath(projectPath)
|
|
43
|
+
|
|
44
|
+
await fs.mkdir(configDir, { recursive: true })
|
|
45
|
+
|
|
46
|
+
const content = JSON.stringify(config, null, 2)
|
|
47
|
+
await fs.writeFile(configPath, content + '\n', 'utf-8')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Read the global project configuration file
|
|
52
|
+
* Contains authors array and other system data
|
|
53
|
+
*
|
|
54
|
+
* @param {string} projectId - Project identifier
|
|
55
|
+
* @returns {Promise<Object|null>} - Configuration object or null if not found
|
|
56
|
+
*/
|
|
57
|
+
async readGlobalConfig(projectId) {
|
|
58
|
+
try {
|
|
59
|
+
const configPath = pathManager.getGlobalProjectConfigPath(projectId)
|
|
60
|
+
const content = await fs.readFile(configPath, 'utf-8')
|
|
61
|
+
return JSON.parse(content)
|
|
62
|
+
} catch {
|
|
63
|
+
return null
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Write the global project configuration file
|
|
69
|
+
*
|
|
70
|
+
* @param {string} projectId - Project identifier
|
|
71
|
+
* @param {Object} config - Configuration object
|
|
72
|
+
* @returns {Promise<void>}
|
|
73
|
+
*/
|
|
74
|
+
async writeGlobalConfig(projectId, config) {
|
|
75
|
+
const configPath = pathManager.getGlobalProjectConfigPath(projectId)
|
|
76
|
+
const configDir = pathManager.getGlobalProjectPath(projectId)
|
|
77
|
+
|
|
78
|
+
await fs.mkdir(configDir, { recursive: true })
|
|
79
|
+
|
|
80
|
+
const content = JSON.stringify(config, null, 2)
|
|
81
|
+
await fs.writeFile(configPath, content + '\n', 'utf-8')
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Ensure global config exists, create if not
|
|
86
|
+
*
|
|
87
|
+
* @param {string} projectId - Project identifier
|
|
88
|
+
* @returns {Promise<Object>} - Global configuration
|
|
89
|
+
*/
|
|
90
|
+
async ensureGlobalConfig(projectId) {
|
|
91
|
+
let globalConfig = await this.readGlobalConfig(projectId)
|
|
92
|
+
|
|
93
|
+
if (!globalConfig) {
|
|
94
|
+
const now = new Date().toISOString()
|
|
95
|
+
globalConfig = {
|
|
96
|
+
projectId,
|
|
97
|
+
authors: [],
|
|
98
|
+
version: VERSION,
|
|
99
|
+
lastSync: now,
|
|
100
|
+
}
|
|
101
|
+
await this.writeGlobalConfig(projectId, globalConfig)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return globalConfig
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Create a new project configuration
|
|
109
|
+
*
|
|
110
|
+
* @param {string} projectPath - Path to the project
|
|
111
|
+
* @param {Object} author - Author information {name, email, github}
|
|
112
|
+
* @returns {Promise<Object>} - Created configuration
|
|
113
|
+
*/
|
|
114
|
+
async createConfig(projectPath, author) {
|
|
115
|
+
const projectId = pathManager.generateProjectId(projectPath)
|
|
116
|
+
const globalPath = pathManager.getGlobalProjectPath(projectId)
|
|
117
|
+
const displayPath = pathManager.getDisplayPath(globalPath)
|
|
118
|
+
const now = new Date().toISOString()
|
|
119
|
+
|
|
120
|
+
const localConfig = {
|
|
121
|
+
projectId,
|
|
122
|
+
dataPath: displayPath,
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
await this.writeConfig(projectPath, localConfig)
|
|
126
|
+
|
|
127
|
+
const globalConfig = {
|
|
128
|
+
projectId,
|
|
129
|
+
authors: [
|
|
130
|
+
{
|
|
131
|
+
name: author.name || 'Unknown',
|
|
132
|
+
email: author.email || '',
|
|
133
|
+
github: author.github || '',
|
|
134
|
+
firstContribution: now,
|
|
135
|
+
lastActivity: now,
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
version: VERSION,
|
|
139
|
+
created: now,
|
|
140
|
+
lastSync: now,
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
await this.writeGlobalConfig(projectId, globalConfig)
|
|
144
|
+
|
|
145
|
+
return localConfig
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Update the lastSync timestamp in global config
|
|
150
|
+
*
|
|
151
|
+
* @param {string} projectPath - Path to the project
|
|
152
|
+
* @returns {Promise<void>}
|
|
153
|
+
*/
|
|
154
|
+
async updateLastSync(projectPath) {
|
|
155
|
+
const projectId = await this.getProjectId(projectPath)
|
|
156
|
+
const globalConfig = await this.readGlobalConfig(projectId)
|
|
157
|
+
if (globalConfig) {
|
|
158
|
+
globalConfig.lastSync = new Date().toISOString()
|
|
159
|
+
await this.writeGlobalConfig(projectId, globalConfig)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Validate a local configuration object
|
|
165
|
+
* Local config only contains project metadata (projectId, dataPath)
|
|
166
|
+
* All system data (version, created, lastSync, authors) is in global config
|
|
167
|
+
*
|
|
168
|
+
* @param {Object} config - Configuration to validate
|
|
169
|
+
* @returns {boolean} - True if valid
|
|
170
|
+
*/
|
|
171
|
+
validateConfig(config) {
|
|
172
|
+
if (!config) return false
|
|
173
|
+
if (!config.projectId) return false
|
|
174
|
+
if (!config.dataPath) return false
|
|
175
|
+
|
|
176
|
+
return true
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Check if a project needs migration
|
|
181
|
+
* Migration is needed if:
|
|
182
|
+
* - Has legacy .prjct/ structure
|
|
183
|
+
* - AND either no config exists OR files not yet in global location
|
|
184
|
+
*
|
|
185
|
+
* @param {string} projectPath - Path to the project
|
|
186
|
+
* @returns {Promise<boolean>} - True if migration needed
|
|
187
|
+
*/
|
|
188
|
+
async needsMigration(projectPath) {
|
|
189
|
+
const hasLegacy = await pathManager.hasLegacyStructure(projectPath)
|
|
190
|
+
if (!hasLegacy) return false
|
|
191
|
+
|
|
192
|
+
const hasConfig = await pathManager.hasConfig(projectPath)
|
|
193
|
+
|
|
194
|
+
if (!hasConfig) return true
|
|
195
|
+
|
|
196
|
+
const config = await this.readConfig(projectPath)
|
|
197
|
+
if (!config || !config.projectId) return true
|
|
198
|
+
|
|
199
|
+
const globalPath = pathManager.getGlobalProjectPath(config.projectId)
|
|
200
|
+
const fs = require('fs').promises
|
|
201
|
+
const path = require('path')
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
const coreFiles = await fs.readdir(path.join(globalPath, 'core'))
|
|
205
|
+
return coreFiles.length === 0
|
|
206
|
+
} catch {
|
|
207
|
+
return true
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Get the project ID from config, or generate it if config doesn't exist
|
|
213
|
+
*
|
|
214
|
+
* @param {string} projectPath - Path to the project
|
|
215
|
+
* @returns {Promise<string>} - Project ID
|
|
216
|
+
*/
|
|
217
|
+
async getProjectId(projectPath) {
|
|
218
|
+
const config = await this.readConfig(projectPath)
|
|
219
|
+
if (config && config.projectId) {
|
|
220
|
+
return config.projectId
|
|
221
|
+
}
|
|
222
|
+
return pathManager.generateProjectId(projectPath)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Find an author in the authors array by github username
|
|
227
|
+
* Reads from GLOBAL config
|
|
228
|
+
*
|
|
229
|
+
* @param {string} projectId - Project identifier
|
|
230
|
+
* @param {string} githubUsername - GitHub username to search for
|
|
231
|
+
* @returns {Promise<Object|null>} - Author object or null if not found
|
|
232
|
+
*/
|
|
233
|
+
async findAuthor(projectId, githubUsername) {
|
|
234
|
+
const globalConfig = await this.readGlobalConfig(projectId)
|
|
235
|
+
if (!globalConfig || !globalConfig.authors) return null
|
|
236
|
+
|
|
237
|
+
return globalConfig.authors.find(a => a.github === githubUsername) || null
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Add a new author to the authors array
|
|
242
|
+
* Writes to GLOBAL config
|
|
243
|
+
*
|
|
244
|
+
* @param {string} projectId - Project identifier
|
|
245
|
+
* @param {Object} author - Author information {name, email, github}
|
|
246
|
+
* @returns {Promise<void>}
|
|
247
|
+
*/
|
|
248
|
+
async addAuthor(projectId, author) {
|
|
249
|
+
const globalConfig = await this.ensureGlobalConfig(projectId)
|
|
250
|
+
|
|
251
|
+
const exists = globalConfig.authors.some(a => a.github === author.github)
|
|
252
|
+
if (exists) return
|
|
253
|
+
|
|
254
|
+
const now = new Date().toISOString()
|
|
255
|
+
globalConfig.authors.push({
|
|
256
|
+
name: author.name || 'Unknown',
|
|
257
|
+
email: author.email || '',
|
|
258
|
+
github: author.github || '',
|
|
259
|
+
firstContribution: now,
|
|
260
|
+
lastActivity: now,
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
globalConfig.lastSync = now
|
|
264
|
+
await this.writeGlobalConfig(projectId, globalConfig)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Update author's last activity timestamp
|
|
269
|
+
* Updates GLOBAL config
|
|
270
|
+
*
|
|
271
|
+
* @param {string} projectId - Project identifier
|
|
272
|
+
* @param {string} githubUsername - GitHub username
|
|
273
|
+
* @returns {Promise<void>}
|
|
274
|
+
*/
|
|
275
|
+
async updateAuthorActivity(projectId, githubUsername) {
|
|
276
|
+
const globalConfig = await this.readGlobalConfig(projectId)
|
|
277
|
+
if (!globalConfig || !globalConfig.authors) return
|
|
278
|
+
|
|
279
|
+
const author = globalConfig.authors.find(a => a.github === githubUsername)
|
|
280
|
+
if (author) {
|
|
281
|
+
author.lastActivity = new Date().toISOString()
|
|
282
|
+
globalConfig.lastSync = author.lastActivity
|
|
283
|
+
await this.writeGlobalConfig(projectId, globalConfig)
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Get current author for session (detect or get from global config)
|
|
289
|
+
*
|
|
290
|
+
* @param {string} projectPath - Path to the project
|
|
291
|
+
* @returns {Promise<string>} - GitHub username of current author
|
|
292
|
+
*/
|
|
293
|
+
async getCurrentAuthor(projectPath) {
|
|
294
|
+
const authorDetector = require('./author-detector')
|
|
295
|
+
const author = await authorDetector.detect()
|
|
296
|
+
|
|
297
|
+
const projectId = await this.getProjectId(projectPath)
|
|
298
|
+
await this.addAuthor(projectId, author)
|
|
299
|
+
|
|
300
|
+
return author.github || author.name || 'Unknown'
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Check if config exists and is valid
|
|
305
|
+
*
|
|
306
|
+
* @param {string} projectPath - Path to the project
|
|
307
|
+
* @returns {Promise<boolean>} - True if valid config exists
|
|
308
|
+
*/
|
|
309
|
+
async isConfigured(projectPath) {
|
|
310
|
+
const config = await this.readConfig(projectPath)
|
|
311
|
+
return this.validateConfig(config)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Get configuration with defaults
|
|
316
|
+
* Returns LOCAL config only (projectId, dataPath)
|
|
317
|
+
*
|
|
318
|
+
* @param {string} projectPath - Path to the project
|
|
319
|
+
* @returns {Promise<Object>} - Configuration with defaults
|
|
320
|
+
*/
|
|
321
|
+
async getConfigWithDefaults(projectPath) {
|
|
322
|
+
const config = await this.readConfig(projectPath)
|
|
323
|
+
if (config) {
|
|
324
|
+
return config
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const projectId = pathManager.generateProjectId(projectPath)
|
|
328
|
+
return {
|
|
329
|
+
projectId,
|
|
330
|
+
dataPath: pathManager.getDisplayPath(pathManager.getGlobalProjectPath(projectId)),
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
module.exports = new ConfigManager()
|