prjct-cli 0.4.7 โ 0.4.8
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 +23 -0
- package/core/commands.js +310 -39
- package/package.json +1 -1
- package/scripts/post-install.js +0 -28
package/CHANGELOG.md
CHANGED
|
@@ -15,6 +15,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
15
15
|
- Cross-platform file operations
|
|
16
16
|
- Windows Terminal integration
|
|
17
17
|
|
|
18
|
+
## [0.4.8] - 2025-10-02
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- **Intelligent Project Initialization** - `prjct init` now works seamlessly in all scenarios
|
|
22
|
+
- Preserves existing project IDs when re-initializing
|
|
23
|
+
- Automatically merges local data with global structure
|
|
24
|
+
- Smart fusion of markdown files (now.md, next.md, shipped.md, ideas.md)
|
|
25
|
+
- Chronological merging of memory/context.jsonl entries
|
|
26
|
+
- Cleans up local `.prjct/` directory, keeping only `prjct.config.json`
|
|
27
|
+
- Multiple developers can initialize the same project without conflicts
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
- **Auto-Migration Removed** - Disabled intrusive automatic migration
|
|
31
|
+
- Migration no longer runs automatically during `prjct init` or any command
|
|
32
|
+
- Users can run `prjct migrate-all` manually if needed
|
|
33
|
+
- Removes blocking behavior that prevented normal usage
|
|
34
|
+
- Cleaner, non-intrusive installation and update experience
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
- **`prjct init` Never Blocks** - Removed "already initialized" error
|
|
38
|
+
- Always allows re-initialization with intelligent data fusion
|
|
39
|
+
- Global architecture enables multi-developer workflows
|
|
40
|
+
|
|
18
41
|
## [0.4.7] - 2025-10-02
|
|
19
42
|
|
|
20
43
|
### Fixed
|
package/core/commands.js
CHANGED
|
@@ -192,8 +192,6 @@ class PrjctCommands {
|
|
|
192
192
|
|
|
193
193
|
this.agent = new Agent()
|
|
194
194
|
|
|
195
|
-
await this.checkAndRunAutoMigration()
|
|
196
|
-
|
|
197
195
|
// Check for updates in background (non-blocking)
|
|
198
196
|
this.checkAndNotifyUpdates().catch(() => {
|
|
199
197
|
// Fail silently - don't interrupt workflow
|
|
@@ -316,41 +314,46 @@ class PrjctCommands {
|
|
|
316
314
|
async init(projectPath = process.cwd()) {
|
|
317
315
|
try {
|
|
318
316
|
await this.initializeAgent()
|
|
319
|
-
|
|
320
|
-
if (await configManager.isConfigured(projectPath)) {
|
|
321
|
-
return {
|
|
322
|
-
success: false,
|
|
323
|
-
message: this.agent.formatResponse('Project already initialized!', 'warning'),
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
317
|
const author = await authorDetector.detect()
|
|
328
318
|
|
|
329
|
-
|
|
330
|
-
|
|
319
|
+
// Check if local config exists
|
|
320
|
+
const isConfigured = await configManager.isConfigured(projectPath)
|
|
321
|
+
let projectId
|
|
322
|
+
let existingLocalData = null
|
|
323
|
+
let fusionPerformed = false
|
|
331
324
|
|
|
332
|
-
if (
|
|
333
|
-
|
|
334
|
-
const
|
|
335
|
-
|
|
325
|
+
if (isConfigured) {
|
|
326
|
+
// Use existing ID from local config
|
|
327
|
+
const existingConfig = await configManager.readConfig(projectPath)
|
|
328
|
+
projectId = existingConfig.projectId
|
|
329
|
+
console.log(`๐ Using existing project ID: ${projectId}`)
|
|
336
330
|
|
|
331
|
+
// Check if there's local data to merge
|
|
332
|
+
const localPrjctPath = path.join(projectPath, '.prjct')
|
|
337
333
|
try {
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
})
|
|
343
|
-
migrationPerformed = migrationResult.success
|
|
334
|
+
const localFiles = await fs.readdir(localPrjctPath)
|
|
335
|
+
if (localFiles.length > 1) { // More than just prjct.config.json
|
|
336
|
+
existingLocalData = await this.collectLocalData(localPrjctPath)
|
|
337
|
+
}
|
|
344
338
|
} catch (error) {
|
|
345
|
-
|
|
339
|
+
// No local data to merge
|
|
346
340
|
}
|
|
341
|
+
} else {
|
|
342
|
+
// Generate new ID based on path hash
|
|
343
|
+
const config = await configManager.createConfig(projectPath, author)
|
|
344
|
+
projectId = config.projectId
|
|
345
|
+
console.log(`โจ Created new project ID: ${projectId}`)
|
|
347
346
|
}
|
|
348
347
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
348
|
+
// Ensure global structure exists
|
|
349
|
+
await pathManager.ensureProjectStructure(projectId)
|
|
350
|
+
const globalPath = pathManager.getGlobalProjectPath(projectId)
|
|
351
|
+
|
|
352
|
+
// Check if global data exists
|
|
353
|
+
const globalExists = await this.checkGlobalDataExists(globalPath)
|
|
353
354
|
|
|
355
|
+
if (!globalExists) {
|
|
356
|
+
// Create fresh global structure
|
|
354
357
|
const files = {
|
|
355
358
|
'core/now.md': '# NOW\n\nNo current task. Use `/p:now` to set focus.\n',
|
|
356
359
|
'core/next.md': '# NEXT\n\n## Priority Queue\n\n',
|
|
@@ -362,22 +365,25 @@ class PrjctCommands {
|
|
|
362
365
|
'memory/context.jsonl': '',
|
|
363
366
|
}
|
|
364
367
|
|
|
365
|
-
const globalPath = pathManager.getGlobalProjectPath(projectId)
|
|
366
368
|
for (const [filePath, content] of Object.entries(files)) {
|
|
367
369
|
await this.agent.writeFile(path.join(globalPath, filePath), content)
|
|
368
370
|
}
|
|
371
|
+
console.log('โ
Created global structure')
|
|
372
|
+
} else {
|
|
373
|
+
console.log('โ
Using existing global data')
|
|
369
374
|
}
|
|
370
375
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
376
|
+
// Merge local data if exists
|
|
377
|
+
if (existingLocalData) {
|
|
378
|
+
console.log('๐ Merging local data with global...')
|
|
379
|
+
await this.mergeProjectData(existingLocalData, globalPath)
|
|
380
|
+
fusionPerformed = true
|
|
381
|
+
}
|
|
374
382
|
|
|
375
|
-
|
|
383
|
+
// Clean up local data (keep only prjct.config.json)
|
|
384
|
+
await this.cleanupLocalData(projectPath)
|
|
376
385
|
|
|
377
|
-
const
|
|
378
|
-
const editorsInstalled = installResult.success
|
|
379
|
-
? `\n๐ค Commands installed to: ${installResult.message.split('Editors: ')[1]?.split('\n')[0] || 'selected editors'}`
|
|
380
|
-
: ''
|
|
386
|
+
const projectInfo = await this.detectProjectType(projectPath)
|
|
381
387
|
|
|
382
388
|
let analysisMessage = ''
|
|
383
389
|
const hasExistingCode = await this.detectExistingCode(projectPath)
|
|
@@ -403,13 +409,14 @@ class PrjctCommands {
|
|
|
403
409
|
}
|
|
404
410
|
|
|
405
411
|
const displayPath = pathManager.getDisplayPath(globalPath)
|
|
412
|
+
const fusionMessage = fusionPerformed ? '\n๐ Merged local data with global' : ''
|
|
406
413
|
const message =
|
|
407
414
|
`Initializing prjct v${VERSION} for ${this.agentInfo.name}...\n` +
|
|
408
|
-
`โ
|
|
409
|
-
|
|
415
|
+
`โ
Project ID: ${projectId}\n` +
|
|
416
|
+
`โ
Global data: ${displayPath}\n` +
|
|
410
417
|
`๐ค Author: ${authorDetector.formatAuthor(author)}\n` +
|
|
411
418
|
`๐ Project: ${projectInfo}` +
|
|
412
|
-
|
|
419
|
+
fusionMessage +
|
|
413
420
|
analysisMessage +
|
|
414
421
|
`\n\nReady! Start with ${this.agentInfo.config.commandPrefix}now "your first task"`
|
|
415
422
|
|
|
@@ -426,6 +433,270 @@ class PrjctCommands {
|
|
|
426
433
|
}
|
|
427
434
|
}
|
|
428
435
|
|
|
436
|
+
/**
|
|
437
|
+
* Check if global data exists for a project
|
|
438
|
+
*
|
|
439
|
+
* @param {string} globalPath - Global project path
|
|
440
|
+
* @returns {Promise<boolean>} True if global data exists
|
|
441
|
+
* @private
|
|
442
|
+
*/
|
|
443
|
+
async checkGlobalDataExists(globalPath) {
|
|
444
|
+
try {
|
|
445
|
+
const nowFile = path.join(globalPath, 'core', 'now.md')
|
|
446
|
+
await fs.access(nowFile)
|
|
447
|
+
return true
|
|
448
|
+
} catch {
|
|
449
|
+
return false
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Collect all data from local .prjct directory
|
|
455
|
+
*
|
|
456
|
+
* @param {string} localPrjctPath - Path to local .prjct directory
|
|
457
|
+
* @returns {Promise<Object>} Object with local data organized by category
|
|
458
|
+
* @private
|
|
459
|
+
*/
|
|
460
|
+
async collectLocalData(localPrjctPath) {
|
|
461
|
+
const data = {
|
|
462
|
+
core: {},
|
|
463
|
+
progress: {},
|
|
464
|
+
planning: {},
|
|
465
|
+
memory: {},
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
try {
|
|
469
|
+
// Collect core files
|
|
470
|
+
const coreFiles = ['now.md', 'next.md', 'context.md']
|
|
471
|
+
for (const file of coreFiles) {
|
|
472
|
+
try {
|
|
473
|
+
const filePath = path.join(localPrjctPath, file)
|
|
474
|
+
data.core[file] = await this.agent.readFile(filePath)
|
|
475
|
+
} catch {
|
|
476
|
+
// File doesn't exist locally
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Collect progress files
|
|
481
|
+
const progressFiles = ['shipped.md', 'metrics.md']
|
|
482
|
+
for (const file of progressFiles) {
|
|
483
|
+
try {
|
|
484
|
+
const filePath = path.join(localPrjctPath, file)
|
|
485
|
+
data.progress[file] = await this.agent.readFile(filePath)
|
|
486
|
+
} catch {
|
|
487
|
+
// File doesn't exist locally
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Collect planning files
|
|
492
|
+
const planningFiles = ['ideas.md', 'roadmap.md']
|
|
493
|
+
for (const file of planningFiles) {
|
|
494
|
+
try {
|
|
495
|
+
const filePath = path.join(localPrjctPath, file)
|
|
496
|
+
data.planning[file] = await this.agent.readFile(filePath)
|
|
497
|
+
} catch {
|
|
498
|
+
// File doesn't exist locally
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Collect memory
|
|
503
|
+
try {
|
|
504
|
+
const memoryPath = path.join(localPrjctPath, 'memory.jsonl')
|
|
505
|
+
data.memory['context.jsonl'] = await this.agent.readFile(memoryPath)
|
|
506
|
+
} catch {
|
|
507
|
+
// Memory doesn't exist locally
|
|
508
|
+
}
|
|
509
|
+
} catch (error) {
|
|
510
|
+
console.error('[prjct] Error collecting local data:', error.message)
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
return data
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Merge local project data with global data
|
|
518
|
+
*
|
|
519
|
+
* @param {Object} localData - Local data collected from .prjct
|
|
520
|
+
* @param {string} globalPath - Global project path
|
|
521
|
+
* @returns {Promise<void>}
|
|
522
|
+
* @private
|
|
523
|
+
*/
|
|
524
|
+
async mergeProjectData(localData, globalPath) {
|
|
525
|
+
try {
|
|
526
|
+
// Merge core files
|
|
527
|
+
for (const [filename, localContent] of Object.entries(localData.core)) {
|
|
528
|
+
if (!localContent) continue
|
|
529
|
+
|
|
530
|
+
const globalFilePath = path.join(globalPath, 'core', filename)
|
|
531
|
+
let globalContent = ''
|
|
532
|
+
try {
|
|
533
|
+
globalContent = await this.agent.readFile(globalFilePath)
|
|
534
|
+
} catch {
|
|
535
|
+
// Global file doesn't exist yet
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const merged = this.intelligentMerge(localContent, globalContent, filename)
|
|
539
|
+
await this.agent.writeFile(globalFilePath, merged)
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Merge progress files
|
|
543
|
+
for (const [filename, localContent] of Object.entries(localData.progress)) {
|
|
544
|
+
if (!localContent) continue
|
|
545
|
+
|
|
546
|
+
const globalFilePath = path.join(globalPath, 'progress', filename)
|
|
547
|
+
let globalContent = ''
|
|
548
|
+
try {
|
|
549
|
+
globalContent = await this.agent.readFile(globalFilePath)
|
|
550
|
+
} catch {
|
|
551
|
+
// Global file doesn't exist yet
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const merged = this.intelligentMerge(localContent, globalContent, filename)
|
|
555
|
+
await this.agent.writeFile(globalFilePath, merged)
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Merge planning files
|
|
559
|
+
for (const [filename, localContent] of Object.entries(localData.planning)) {
|
|
560
|
+
if (!localContent) continue
|
|
561
|
+
|
|
562
|
+
const globalFilePath = path.join(globalPath, 'planning', filename)
|
|
563
|
+
let globalContent = ''
|
|
564
|
+
try {
|
|
565
|
+
globalContent = await this.agent.readFile(globalFilePath)
|
|
566
|
+
} catch {
|
|
567
|
+
// Global file doesn't exist yet
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const merged = this.intelligentMerge(localContent, globalContent, filename)
|
|
571
|
+
await this.agent.writeFile(globalFilePath, merged)
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Merge memory (chronologically)
|
|
575
|
+
if (localData.memory['context.jsonl']) {
|
|
576
|
+
const globalMemoryPath = path.join(globalPath, 'memory', 'context.jsonl')
|
|
577
|
+
let globalMemory = ''
|
|
578
|
+
try {
|
|
579
|
+
globalMemory = await this.agent.readFile(globalMemoryPath)
|
|
580
|
+
} catch {
|
|
581
|
+
// Global memory doesn't exist yet
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const merged = this.mergeMemory(localData.memory['context.jsonl'], globalMemory)
|
|
585
|
+
await this.agent.writeFile(globalMemoryPath, merged)
|
|
586
|
+
}
|
|
587
|
+
} catch (error) {
|
|
588
|
+
console.error('[prjct] Error merging data:', error.message)
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Intelligent merge of markdown content
|
|
594
|
+
*
|
|
595
|
+
* @param {string} localContent - Content from local file
|
|
596
|
+
* @param {string} globalContent - Content from global file
|
|
597
|
+
* @param {string} filename - Name of the file being merged
|
|
598
|
+
* @returns {string} Merged content
|
|
599
|
+
* @private
|
|
600
|
+
*/
|
|
601
|
+
intelligentMerge(localContent, globalContent, filename) {
|
|
602
|
+
// If global is empty, use local
|
|
603
|
+
if (!globalContent || globalContent.trim() === '' || globalContent.includes('No current task')) {
|
|
604
|
+
return localContent
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// If local is empty, use global
|
|
608
|
+
if (!localContent || localContent.trim() === '') {
|
|
609
|
+
return globalContent
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// For now.md, prefer local (most recent)
|
|
613
|
+
if (filename === 'now.md') {
|
|
614
|
+
return localContent
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// For shipped.md, next.md, ideas.md, roadmap.md - combine unique entries
|
|
618
|
+
const localLines = localContent.split('\n').filter((l) => l.trim())
|
|
619
|
+
const globalLines = globalContent.split('\n').filter((l) => l.trim())
|
|
620
|
+
|
|
621
|
+
// Keep header from global
|
|
622
|
+
const header = globalLines[0] || localLines[0]
|
|
623
|
+
|
|
624
|
+
// Combine unique content (skip headers and empty lines)
|
|
625
|
+
const allLines = [...globalLines.slice(1), ...localLines.slice(1)]
|
|
626
|
+
const uniqueLines = [...new Set(allLines)]
|
|
627
|
+
|
|
628
|
+
return [header, '', ...uniqueLines].join('\n')
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Merge memory.jsonl files chronologically
|
|
633
|
+
*
|
|
634
|
+
* @param {string} localMemory - Local memory content
|
|
635
|
+
* @param {string} globalMemory - Global memory content
|
|
636
|
+
* @returns {string} Merged memory sorted by timestamp
|
|
637
|
+
* @private
|
|
638
|
+
*/
|
|
639
|
+
mergeMemory(localMemory, globalMemory) {
|
|
640
|
+
if (!globalMemory) return localMemory
|
|
641
|
+
if (!localMemory) return globalMemory
|
|
642
|
+
|
|
643
|
+
const localEntries = localMemory.split('\n').filter((l) => l.trim())
|
|
644
|
+
const globalEntries = globalMemory.split('\n').filter((l) => l.trim())
|
|
645
|
+
|
|
646
|
+
const allEntries = [...globalEntries, ...localEntries].map((entry) => {
|
|
647
|
+
try {
|
|
648
|
+
return JSON.parse(entry)
|
|
649
|
+
} catch {
|
|
650
|
+
return null
|
|
651
|
+
}
|
|
652
|
+
}).filter(Boolean)
|
|
653
|
+
|
|
654
|
+
// Sort by timestamp
|
|
655
|
+
allEntries.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp))
|
|
656
|
+
|
|
657
|
+
// Remove duplicates
|
|
658
|
+
const unique = []
|
|
659
|
+
const seen = new Set()
|
|
660
|
+
for (const entry of allEntries) {
|
|
661
|
+
const key = `${entry.timestamp}-${entry.action}`
|
|
662
|
+
if (!seen.has(key)) {
|
|
663
|
+
seen.add(key)
|
|
664
|
+
unique.push(entry)
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
return unique.map((e) => JSON.stringify(e)).join('\n')
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Clean up local .prjct directory, keeping only prjct.config.json
|
|
673
|
+
*
|
|
674
|
+
* @param {string} projectPath - Project path
|
|
675
|
+
* @returns {Promise<void>}
|
|
676
|
+
* @private
|
|
677
|
+
*/
|
|
678
|
+
async cleanupLocalData(projectPath) {
|
|
679
|
+
try {
|
|
680
|
+
const localPrjctPath = path.join(projectPath, '.prjct')
|
|
681
|
+
const files = await fs.readdir(localPrjctPath, { withFileTypes: true })
|
|
682
|
+
|
|
683
|
+
for (const file of files) {
|
|
684
|
+
if (file.name === 'prjct.config.json') continue
|
|
685
|
+
|
|
686
|
+
const filePath = path.join(localPrjctPath, file.name)
|
|
687
|
+
if (file.isDirectory()) {
|
|
688
|
+
await fs.rm(filePath, { recursive: true, force: true })
|
|
689
|
+
} else {
|
|
690
|
+
await fs.unlink(filePath)
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
console.log('๐งน Cleaned up local data - only prjct.config.json remains')
|
|
695
|
+
} catch (error) {
|
|
696
|
+
console.error('[prjct] Warning: Could not clean up local data:', error.message)
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
429
700
|
/**
|
|
430
701
|
* Set or view current task
|
|
431
702
|
*
|
package/package.json
CHANGED
package/scripts/post-install.js
CHANGED
|
@@ -81,34 +81,6 @@ async function main() {
|
|
|
81
81
|
// Update version in config
|
|
82
82
|
await editorsConfig.updateVersion(currentVersion)
|
|
83
83
|
|
|
84
|
-
// Auto-migrate legacy projects (v0.1.0 โ v0.4.4)
|
|
85
|
-
try {
|
|
86
|
-
console.log(chalk.cyan('\n๐ Checking for legacy projects to migrate...\n'))
|
|
87
|
-
|
|
88
|
-
const migrator = require('../core/migrator')
|
|
89
|
-
|
|
90
|
-
// Quick scan of common directories (not entire home directory)
|
|
91
|
-
const summary = await migrator.migrateAll({
|
|
92
|
-
deepScan: false, // Only scan common dirs (fast)
|
|
93
|
-
removeLegacy: false, // Keep legacy dirs for safety
|
|
94
|
-
cleanupLegacy: true, // Remove old dirs but keep config
|
|
95
|
-
dryRun: false, // Actually perform migration
|
|
96
|
-
interactive: false // No user prompts during npm install
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
if (summary.successfullyMigrated > 0) {
|
|
100
|
-
console.log(chalk.green(`โ
Migrated ${summary.successfullyMigrated} legacy project(s) to new structure`))
|
|
101
|
-
console.log(chalk.gray(` Data location: ~/.prjct-cli/projects/`))
|
|
102
|
-
} else if (summary.totalFound > 0 && summary.alreadyMigrated === summary.totalFound) {
|
|
103
|
-
console.log(chalk.gray('โน๏ธ All projects already using new structure'))
|
|
104
|
-
}
|
|
105
|
-
} catch (migrationError) {
|
|
106
|
-
// Don't block install if migration fails
|
|
107
|
-
if (process.env.DEBUG) {
|
|
108
|
-
console.error(chalk.yellow('[post-install] Migration warning:'), migrationError.message)
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
84
|
console.log(chalk.cyan(`\nโจ prjct-cli ${currentVersion} is ready!\n`))
|
|
113
85
|
|
|
114
86
|
} catch (error) {
|