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.
- package/CHANGELOG.md +220 -7
- package/CLAUDE.md +476 -55
- package/README.md +48 -55
- package/bin/prjct +170 -225
- package/core/agentic/command-executor.js +113 -0
- package/core/agentic/context-builder.js +85 -0
- package/core/agentic/prompt-builder.js +86 -0
- package/core/agentic/template-loader.js +104 -0
- package/core/agentic/tool-registry.js +117 -0
- package/core/command-registry.js +597 -0
- package/core/commands.js +2046 -2028
- package/core/domain/agent-generator.js +118 -0
- package/core/domain/analyzer.js +211 -0
- package/core/domain/architect-session.js +300 -0
- package/core/{agents → infrastructure/agents}/claude-agent.js +16 -13
- package/core/{author-detector.js → infrastructure/author-detector.js} +3 -1
- package/core/{capability-installer.js → infrastructure/capability-installer.js} +3 -6
- package/core/{command-installer.js → infrastructure/command-installer.js} +4 -2
- package/core/{config-manager.js → infrastructure/config-manager.js} +4 -4
- package/core/{editors-config.js → infrastructure/editors-config.js} +2 -10
- package/core/{migrator.js → infrastructure/migrator.js} +34 -19
- package/core/{path-manager.js → infrastructure/path-manager.js} +20 -44
- package/core/{session-manager.js → infrastructure/session-manager.js} +45 -105
- package/core/{update-checker.js → infrastructure/update-checker.js} +67 -67
- package/core/{animations-simple.js → utils/animations.js} +3 -23
- package/core/utils/date-helper.js +238 -0
- package/core/utils/file-helper.js +327 -0
- package/core/utils/jsonl-helper.js +206 -0
- package/core/{project-capabilities.js → utils/project-capabilities.js} +21 -22
- package/core/utils/session-helper.js +277 -0
- package/core/{version.js → utils/version.js} +1 -1
- package/package.json +5 -12
- package/templates/agents/AGENTS.md +151 -99
- package/templates/analysis/analyze.md +84 -0
- package/templates/commands/analyze.md +37 -233
- package/templates/commands/bug.md +79 -0
- package/templates/commands/build.md +44 -0
- package/templates/commands/cleanup.md +24 -84
- package/templates/commands/design.md +20 -95
- package/templates/commands/done.md +17 -180
- package/templates/commands/feature.md +113 -0
- package/templates/commands/fix.md +58 -66
- package/templates/commands/git.md +35 -57
- package/templates/commands/help.md +18 -52
- package/templates/commands/idea.md +18 -34
- package/templates/commands/init.md +65 -257
- package/templates/commands/next.md +20 -60
- package/templates/commands/now.md +21 -23
- package/templates/commands/progress.md +40 -73
- package/templates/commands/recap.md +52 -75
- package/templates/commands/roadmap.md +30 -85
- package/templates/commands/ship.md +93 -126
- package/templates/commands/status.md +42 -0
- package/templates/commands/sync.md +19 -205
- package/templates/commands/task.md +19 -79
- package/templates/commands/test.md +25 -71
- package/templates/commands/workflow.md +20 -210
- package/core/agent-generator.js +0 -516
- package/core/analyzer.js +0 -600
- package/core/animations.js +0 -277
- package/core/git-integration.js +0 -401
- package/core/workflow-engine.js +0 -213
- package/core/workflow-prompts.js +0 -192
- package/core/workflow-rules.js +0 -147
- package/scripts/post-install.js +0 -121
- package/scripts/preuninstall.js +0 -94
- package/scripts/verify-installation.sh +0 -158
- package/templates/agents/be.template.md +0 -42
- package/templates/agents/data.template.md +0 -41
- package/templates/agents/devops.template.md +0 -41
- package/templates/agents/fe.template.md +0 -42
- package/templates/agents/mobile.template.md +0 -41
- package/templates/agents/pm.template.md +0 -84
- package/templates/agents/qa.template.md +0 -54
- package/templates/agents/scribe.template.md +0 -95
- package/templates/agents/security.template.md +0 -41
- package/templates/agents/ux.template.md +0 -49
- package/templates/commands/context.md +0 -105
- package/templates/commands/stuck.md +0 -48
- package/templates/examples/natural-language-examples.md +0 -532
- /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()
|