prjct-cli 0.60.0 → 0.60.2
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 +73 -0
- package/core/agentic/orchestrator-executor.ts +31 -33
- package/core/commands/analysis.ts +72 -64
- package/core/domain/agent-loader.ts +8 -8
- package/dist/bin/prjct.mjs +62 -83
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,78 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.60.2] - 2026-02-05
|
|
4
|
+
|
|
5
|
+
### Performance
|
|
6
|
+
|
|
7
|
+
- parallelize agent/skill loading with Promise.all (PRJ-110) (#101)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## [0.60.2] - 2026-02-05
|
|
11
|
+
|
|
12
|
+
### Performance
|
|
13
|
+
|
|
14
|
+
- **Parallel agent/skill loading (PRJ-110)**: Agent and skill loading now uses `Promise.all` for parallel I/O
|
|
15
|
+
|
|
16
|
+
### Implementation Details
|
|
17
|
+
|
|
18
|
+
Refactored `loadAgents()` and `loadSkills()` in `core/agentic/orchestrator-executor.ts` to use `Promise.all` with map instead of sequential for loops. Also parallelized `loadAllAgents()` in `core/domain/agent-loader.ts`. Pattern: collect items → map to async promises → Promise.all → filter nulls with type guard.
|
|
19
|
+
|
|
20
|
+
### Learnings
|
|
21
|
+
|
|
22
|
+
- Use `Promise.all(items.map(async (item) => ...))` for parallel async operations
|
|
23
|
+
- Return null for failed items, then filter - can't push to array in parallel
|
|
24
|
+
- Collect unique items first (deduplication), then parallelize reads
|
|
25
|
+
|
|
26
|
+
### Test Plan
|
|
27
|
+
|
|
28
|
+
#### For QA
|
|
29
|
+
1. Run `prjct sync --yes` - verify agents load successfully
|
|
30
|
+
2. Run `p. task "test"` - verify orchestrator works
|
|
31
|
+
3. Check no errors in agent/skill loading output
|
|
32
|
+
|
|
33
|
+
#### For Users
|
|
34
|
+
- Agent and skill loading is now faster (parallel I/O)
|
|
35
|
+
- No changes needed - improvement is automatic
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
## [0.60.1] - 2026-02-05
|
|
39
|
+
|
|
40
|
+
### Bug Fixes
|
|
41
|
+
|
|
42
|
+
- improve sync output with summary-first format (PRJ-100) (#100)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## [0.60.1] - 2026-02-05
|
|
46
|
+
|
|
47
|
+
### Improved
|
|
48
|
+
|
|
49
|
+
- **Sync output UX (PRJ-100)**: Summary-first format with key metrics prominent, ~50% less output
|
|
50
|
+
|
|
51
|
+
### Implementation Details
|
|
52
|
+
|
|
53
|
+
Refactored `showSyncResult()` in `core/commands/analysis.ts` to show success line with timing immediately, followed by single-line metrics (files → context | agents | reduction). Removed redundant bottom summary section. Fixed compressionRate calculation (was decimal, now percentage). Added conditional display for low-value metrics (only show reduction if >10%). Fixed pluralization ("1 skill" not "1 skills").
|
|
54
|
+
|
|
55
|
+
### Learnings
|
|
56
|
+
|
|
57
|
+
- `syncMetrics.compressionRate` is a decimal (0-1), not percentage - multiply by 100
|
|
58
|
+
- Summary-first output pattern improves scannability
|
|
59
|
+
- Conditional metric display reduces noise for low-value data
|
|
60
|
+
|
|
61
|
+
### Test Plan
|
|
62
|
+
|
|
63
|
+
#### For QA
|
|
64
|
+
1. Run `prjct sync --yes` on any project
|
|
65
|
+
2. Verify output shows success line first with timing: `✅ Synced {project} ({time}s)`
|
|
66
|
+
3. Verify single-line metrics: `{files} files → {context} context | {agents} agents`
|
|
67
|
+
4. Verify compression rate only shows if > 10%
|
|
68
|
+
5. Verify pluralization is correct ("1 skill" not "1 skills")
|
|
69
|
+
|
|
70
|
+
#### For Users
|
|
71
|
+
- Sync output is now more scannable - key metrics appear first instead of buried at bottom
|
|
72
|
+
- Run `p. sync` as usual - new format is automatic
|
|
73
|
+
- No breaking changes
|
|
74
|
+
|
|
75
|
+
|
|
3
76
|
## [0.60.0] - 2026-02-05
|
|
4
77
|
|
|
5
78
|
### Features
|
|
@@ -341,13 +341,15 @@ export class OrchestratorExecutor {
|
|
|
341
341
|
*
|
|
342
342
|
* Reads agent markdown files from {globalPath}/agents/
|
|
343
343
|
* and extracts their content and skills from frontmatter.
|
|
344
|
+
*
|
|
345
|
+
* Uses parallel file reads for performance (PRJ-110).
|
|
344
346
|
*/
|
|
345
347
|
async loadAgents(domains: string[], projectId: string): Promise<LoadedAgent[]> {
|
|
346
348
|
const globalPath = pathManager.getGlobalProjectPath(projectId)
|
|
347
349
|
const agentsDir = path.join(globalPath, 'agents')
|
|
348
|
-
const agents: LoadedAgent[] = []
|
|
349
350
|
|
|
350
|
-
|
|
351
|
+
// Load all domain agents in parallel
|
|
352
|
+
const agentPromises = domains.map(async (domain): Promise<LoadedAgent | null> => {
|
|
351
353
|
// Try exact match first, then variations
|
|
352
354
|
const possibleNames = [`${domain}.md`, `${domain}-agent.md`, `prjct-${domain}.md`]
|
|
353
355
|
|
|
@@ -357,21 +359,22 @@ export class OrchestratorExecutor {
|
|
|
357
359
|
const content = await fs.readFile(filePath, 'utf-8')
|
|
358
360
|
const { frontmatter, body } = this.parseAgentFile(content)
|
|
359
361
|
|
|
360
|
-
|
|
362
|
+
return {
|
|
361
363
|
name: fileName.replace('.md', ''),
|
|
362
364
|
domain,
|
|
363
365
|
content: body,
|
|
364
366
|
skills: frontmatter.skills || [],
|
|
365
367
|
filePath,
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
//
|
|
369
|
-
|
|
370
|
-
} catch {}
|
|
368
|
+
}
|
|
369
|
+
} catch {
|
|
370
|
+
// Try next variation
|
|
371
|
+
}
|
|
371
372
|
}
|
|
372
|
-
|
|
373
|
+
return null
|
|
374
|
+
})
|
|
373
375
|
|
|
374
|
-
|
|
376
|
+
const results = await Promise.all(agentPromises)
|
|
377
|
+
return results.filter((agent): agent is LoadedAgent => agent !== null)
|
|
375
378
|
}
|
|
376
379
|
|
|
377
380
|
/**
|
|
@@ -400,51 +403,46 @@ export class OrchestratorExecutor {
|
|
|
400
403
|
* Load skills from agent frontmatter
|
|
401
404
|
*
|
|
402
405
|
* Skills are stored in ~/.claude/skills/{name}.md
|
|
406
|
+
*
|
|
407
|
+
* Uses parallel file reads for performance (PRJ-110).
|
|
403
408
|
*/
|
|
404
409
|
async loadSkills(agents: LoadedAgent[]): Promise<LoadedSkill[]> {
|
|
405
410
|
const skillsDir = path.join(os.homedir(), '.claude', 'skills')
|
|
406
|
-
const skills: LoadedSkill[] = []
|
|
407
|
-
const loadedSkillNames = new Set<string>()
|
|
408
411
|
|
|
412
|
+
// Collect unique skill names from all agents
|
|
413
|
+
const uniqueSkillNames = new Set<string>()
|
|
409
414
|
for (const agent of agents) {
|
|
410
415
|
for (const skillName of agent.skills) {
|
|
411
|
-
|
|
412
|
-
|
|
416
|
+
uniqueSkillNames.add(skillName)
|
|
417
|
+
}
|
|
418
|
+
}
|
|
413
419
|
|
|
420
|
+
// Load all skills in parallel
|
|
421
|
+
const skillPromises = Array.from(uniqueSkillNames).map(
|
|
422
|
+
async (skillName): Promise<LoadedSkill | null> => {
|
|
414
423
|
// Check both patterns: flat file and subdirectory (ecosystem standard)
|
|
415
424
|
const flatPath = path.join(skillsDir, `${skillName}.md`)
|
|
416
425
|
const subdirPath = path.join(skillsDir, skillName, 'SKILL.md')
|
|
417
426
|
|
|
418
|
-
let content: string | null = null
|
|
419
|
-
let resolvedPath = flatPath
|
|
420
|
-
|
|
421
427
|
// Prefer subdirectory format (ecosystem standard)
|
|
422
428
|
try {
|
|
423
|
-
content = await fs.readFile(subdirPath, 'utf-8')
|
|
424
|
-
|
|
429
|
+
const content = await fs.readFile(subdirPath, 'utf-8')
|
|
430
|
+
return { name: skillName, content, filePath: subdirPath }
|
|
425
431
|
} catch {
|
|
426
432
|
// Fall back to flat file
|
|
427
433
|
try {
|
|
428
|
-
content = await fs.readFile(flatPath, 'utf-8')
|
|
429
|
-
|
|
434
|
+
const content = await fs.readFile(flatPath, 'utf-8')
|
|
435
|
+
return { name: skillName, content, filePath: flatPath }
|
|
430
436
|
} catch {
|
|
431
437
|
// Skill not found - not an error, just skip
|
|
432
|
-
|
|
438
|
+
return null
|
|
433
439
|
}
|
|
434
440
|
}
|
|
435
|
-
|
|
436
|
-
if (content) {
|
|
437
|
-
skills.push({
|
|
438
|
-
name: skillName,
|
|
439
|
-
content,
|
|
440
|
-
filePath: resolvedPath,
|
|
441
|
-
})
|
|
442
|
-
loadedSkillNames.add(skillName)
|
|
443
|
-
}
|
|
444
441
|
}
|
|
445
|
-
|
|
442
|
+
)
|
|
446
443
|
|
|
447
|
-
|
|
444
|
+
const results = await Promise.all(skillPromises)
|
|
445
|
+
return results.filter((skill): skill is LoadedSkill => skill !== null)
|
|
448
446
|
}
|
|
449
447
|
|
|
450
448
|
/**
|
|
@@ -455,89 +455,97 @@ export class AnalysisCommands extends PrjctCommandsBase {
|
|
|
455
455
|
|
|
456
456
|
/**
|
|
457
457
|
* Display sync results (extracted to avoid duplication)
|
|
458
|
+
*
|
|
459
|
+
* UX Design (PRJ-100):
|
|
460
|
+
* - Summary first: success + key metrics on first lines
|
|
461
|
+
* - Scannable: single-line metrics, minimal vertical space
|
|
462
|
+
* - Changes focused: show what changed, not everything that exists
|
|
463
|
+
* - Next steps prominent: clear call to action at bottom
|
|
458
464
|
*/
|
|
459
465
|
private async showSyncResult(
|
|
460
466
|
result: Awaited<ReturnType<typeof syncService.sync>>,
|
|
461
467
|
startTime: number
|
|
462
468
|
): Promise<CommandResult> {
|
|
463
|
-
|
|
464
|
-
const
|
|
465
|
-
|
|
466
|
-
|
|
469
|
+
const elapsed = Date.now() - startTime
|
|
470
|
+
const contextFilesCount =
|
|
471
|
+
result.contextFiles.length + (result.aiTools?.filter((t) => t.success).length || 0)
|
|
472
|
+
const agentCount = result.agents.length
|
|
473
|
+
const domainAgentCount = result.agents.filter((a) => a.type === 'domain').length
|
|
474
|
+
|
|
475
|
+
// Update global config (silent - don't clutter output)
|
|
476
|
+
await commandInstaller.installGlobalConfig()
|
|
477
|
+
|
|
478
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
479
|
+
// SUCCESS LINE - Immediate confirmation with timing
|
|
480
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
481
|
+
console.log(`✅ Synced ${result.stats.name || 'project'} (${(elapsed / 1000).toFixed(1)}s)\n`)
|
|
482
|
+
|
|
483
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
484
|
+
// KEY METRICS - Single scannable line
|
|
485
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
486
|
+
// Only show compression rate if meaningful (> 10%)
|
|
487
|
+
const compressionPct = result.syncMetrics?.compressionRate
|
|
488
|
+
? Math.round(result.syncMetrics.compressionRate * 100)
|
|
489
|
+
: 0
|
|
490
|
+
const metricsLine = [
|
|
491
|
+
`${result.stats.fileCount} files → ${contextFilesCount} context`,
|
|
492
|
+
`${agentCount} agents`,
|
|
493
|
+
compressionPct > 10 ? `${compressionPct}% reduction` : null,
|
|
494
|
+
]
|
|
495
|
+
.filter(Boolean)
|
|
496
|
+
.join(' | ')
|
|
497
|
+
console.log(metricsLine)
|
|
498
|
+
|
|
499
|
+
// Stack and branch info
|
|
500
|
+
const framework = result.stats.frameworks.length > 0 ? ` (${result.stats.frameworks[0]})` : ''
|
|
501
|
+
console.log(`Stack: ${result.stats.ecosystem}${framework} | Branch: ${result.git.branch}\n`)
|
|
502
|
+
|
|
503
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
504
|
+
// CHANGES SECTION - What was generated/updated
|
|
505
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
506
|
+
console.log('Generated:')
|
|
507
|
+
|
|
508
|
+
// Context files (condensed)
|
|
509
|
+
if (result.contextFiles.length > 0) {
|
|
510
|
+
console.log(` ✓ ${result.contextFiles.length} context files`)
|
|
467
511
|
}
|
|
468
512
|
|
|
469
|
-
//
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
console.log(`├── Commits: ${result.git.commits}`)
|
|
475
|
-
console.log(`├── Version: ${result.stats.version}`)
|
|
476
|
-
console.log(`└── Stack: ${result.stats.ecosystem}\n`)
|
|
477
|
-
|
|
478
|
-
console.log('🌿 Git Status')
|
|
479
|
-
console.log(`├── Branch: ${result.git.branch}`)
|
|
480
|
-
console.log(`├── Uncommitted: ${result.git.hasChanges ? 'Yes' : 'Clean'}`)
|
|
481
|
-
console.log(`└── Recent: ${result.git.weeklyCommits} commits this week\n`)
|
|
482
|
-
|
|
483
|
-
console.log('📁 Context Updated')
|
|
484
|
-
for (const file of result.contextFiles) {
|
|
485
|
-
console.log(`├── ${file}`)
|
|
513
|
+
// AI tools
|
|
514
|
+
const successTools = result.aiTools?.filter((t) => t.success) || []
|
|
515
|
+
if (successTools.length > 0) {
|
|
516
|
+
const toolNames = successTools.map((t) => t.toolId).join(', ')
|
|
517
|
+
console.log(` ✓ AI tools: ${toolNames}`)
|
|
486
518
|
}
|
|
487
|
-
console.log('')
|
|
488
519
|
|
|
489
|
-
//
|
|
490
|
-
if (
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
}
|
|
497
|
-
console.log('')
|
|
520
|
+
// Agents (show count with breakdown)
|
|
521
|
+
if (agentCount > 0) {
|
|
522
|
+
const agentSummary =
|
|
523
|
+
domainAgentCount > 0
|
|
524
|
+
? `${agentCount} agents (${domainAgentCount} domain)`
|
|
525
|
+
: `${agentCount} agents`
|
|
526
|
+
console.log(` ✓ ${agentSummary}`)
|
|
498
527
|
}
|
|
499
528
|
|
|
500
|
-
|
|
501
|
-
const domainAgents = result.agents.filter((a) => a.type === 'domain').map((a) => a.name)
|
|
502
|
-
|
|
503
|
-
console.log(`🤖 Agents Regenerated (${result.agents.length})`)
|
|
504
|
-
console.log(`├── Workflow: ${workflowAgents.join(', ')}`)
|
|
505
|
-
console.log(`└── Domain: ${domainAgents.join(', ') || 'none'}\n`)
|
|
506
|
-
|
|
529
|
+
// Skills
|
|
507
530
|
if (result.skills.length > 0) {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
console.log(`├── ${skill.agent}.md → ${skill.skill}`)
|
|
511
|
-
}
|
|
512
|
-
console.log('')
|
|
531
|
+
const skillWord = result.skills.length === 1 ? 'skill' : 'skills'
|
|
532
|
+
console.log(` ✓ ${result.skills.length} ${skillWord}`)
|
|
513
533
|
}
|
|
514
534
|
|
|
535
|
+
console.log('')
|
|
536
|
+
|
|
537
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
538
|
+
// STATUS INDICATOR - Repository state
|
|
539
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
515
540
|
if (result.git.hasChanges) {
|
|
516
|
-
console.log('⚠️
|
|
517
|
-
} else {
|
|
518
|
-
console.log('✨ Repository is clean!\n')
|
|
541
|
+
console.log('⚠️ Uncommitted changes detected\n')
|
|
519
542
|
}
|
|
520
543
|
|
|
544
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
545
|
+
// NEXT STEPS - Clear call to action
|
|
546
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
521
547
|
showNextSteps('sync')
|
|
522
548
|
|
|
523
|
-
// Summary metrics
|
|
524
|
-
const elapsed = Date.now() - startTime
|
|
525
|
-
const contextFilesCount =
|
|
526
|
-
result.contextFiles.length + (result.aiTools?.filter((t) => t.success).length || 0)
|
|
527
|
-
const agentCount = result.agents.length
|
|
528
|
-
|
|
529
|
-
console.log('─'.repeat(45))
|
|
530
|
-
console.log('📊 Sync Summary')
|
|
531
|
-
console.log(
|
|
532
|
-
` Stack: ${result.stats.ecosystem} (${result.stats.frameworks.join(', ') || 'no frameworks'})`
|
|
533
|
-
)
|
|
534
|
-
console.log(` Files: ${result.stats.fileCount} analyzed → ${contextFilesCount} context files`)
|
|
535
|
-
console.log(
|
|
536
|
-
` Agents: ${agentCount} (${result.agents.filter((a) => a.type === 'domain').length} domain)`
|
|
537
|
-
)
|
|
538
|
-
console.log(` Time: ${(elapsed / 1000).toFixed(1)}s`)
|
|
539
|
-
console.log('')
|
|
540
|
-
|
|
541
549
|
return {
|
|
542
550
|
success: true,
|
|
543
551
|
data: result,
|
|
@@ -94,22 +94,22 @@ class AgentLoader {
|
|
|
94
94
|
|
|
95
95
|
/**
|
|
96
96
|
* Load all agents for the project
|
|
97
|
+
*
|
|
98
|
+
* Uses parallel file reads for performance (PRJ-110).
|
|
97
99
|
*/
|
|
98
100
|
async loadAllAgents(): Promise<Agent[]> {
|
|
99
101
|
try {
|
|
100
102
|
const files = await fs.readdir(this.agentsDir)
|
|
101
103
|
const agentFiles = files.filter((f) => f.endsWith('.md') && !f.startsWith('.'))
|
|
102
104
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
+
// Load all agents in parallel
|
|
106
|
+
const agentPromises = agentFiles.map((file) => {
|
|
105
107
|
const agentName = file.replace('.md', '')
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
agents.push(agent)
|
|
109
|
-
}
|
|
110
|
-
}
|
|
108
|
+
return this.loadAgent(agentName)
|
|
109
|
+
})
|
|
111
110
|
|
|
112
|
-
|
|
111
|
+
const results = await Promise.all(agentPromises)
|
|
112
|
+
return results.filter((agent): agent is Agent => agent !== null)
|
|
113
113
|
} catch (error) {
|
|
114
114
|
if (isNotFoundError(error)) {
|
|
115
115
|
return [] // Agents directory doesn't exist yet
|
package/dist/bin/prjct.mjs
CHANGED
|
@@ -11788,31 +11788,33 @@ var init_orchestrator_executor = __esm({
|
|
|
11788
11788
|
*
|
|
11789
11789
|
* Reads agent markdown files from {globalPath}/agents/
|
|
11790
11790
|
* and extracts their content and skills from frontmatter.
|
|
11791
|
+
*
|
|
11792
|
+
* Uses parallel file reads for performance (PRJ-110).
|
|
11791
11793
|
*/
|
|
11792
11794
|
async loadAgents(domains, projectId) {
|
|
11793
11795
|
const globalPath = path_manager_default.getGlobalProjectPath(projectId);
|
|
11794
11796
|
const agentsDir = path23.join(globalPath, "agents");
|
|
11795
|
-
const
|
|
11796
|
-
for (const domain of domains) {
|
|
11797
|
+
const agentPromises = domains.map(async (domain) => {
|
|
11797
11798
|
const possibleNames = [`${domain}.md`, `${domain}-agent.md`, `prjct-${domain}.md`];
|
|
11798
11799
|
for (const fileName of possibleNames) {
|
|
11799
11800
|
const filePath = path23.join(agentsDir, fileName);
|
|
11800
11801
|
try {
|
|
11801
11802
|
const content = await fs24.readFile(filePath, "utf-8");
|
|
11802
11803
|
const { frontmatter, body } = this.parseAgentFile(content);
|
|
11803
|
-
|
|
11804
|
+
return {
|
|
11804
11805
|
name: fileName.replace(".md", ""),
|
|
11805
11806
|
domain,
|
|
11806
11807
|
content: body,
|
|
11807
11808
|
skills: frontmatter.skills || [],
|
|
11808
11809
|
filePath
|
|
11809
|
-
}
|
|
11810
|
-
break;
|
|
11810
|
+
};
|
|
11811
11811
|
} catch {
|
|
11812
11812
|
}
|
|
11813
11813
|
}
|
|
11814
|
-
|
|
11815
|
-
|
|
11814
|
+
return null;
|
|
11815
|
+
});
|
|
11816
|
+
const results = await Promise.all(agentPromises);
|
|
11817
|
+
return results.filter((agent) => agent !== null);
|
|
11816
11818
|
}
|
|
11817
11819
|
/**
|
|
11818
11820
|
* Parse agent markdown file to extract frontmatter and body
|
|
@@ -11832,40 +11834,36 @@ var init_orchestrator_executor = __esm({
|
|
|
11832
11834
|
* Load skills from agent frontmatter
|
|
11833
11835
|
*
|
|
11834
11836
|
* Skills are stored in ~/.claude/skills/{name}.md
|
|
11837
|
+
*
|
|
11838
|
+
* Uses parallel file reads for performance (PRJ-110).
|
|
11835
11839
|
*/
|
|
11836
11840
|
async loadSkills(agents) {
|
|
11837
11841
|
const skillsDir = path23.join(os8.homedir(), ".claude", "skills");
|
|
11838
|
-
const
|
|
11839
|
-
const loadedSkillNames = /* @__PURE__ */ new Set();
|
|
11842
|
+
const uniqueSkillNames = /* @__PURE__ */ new Set();
|
|
11840
11843
|
for (const agent of agents) {
|
|
11841
11844
|
for (const skillName of agent.skills) {
|
|
11842
|
-
|
|
11845
|
+
uniqueSkillNames.add(skillName);
|
|
11846
|
+
}
|
|
11847
|
+
}
|
|
11848
|
+
const skillPromises = Array.from(uniqueSkillNames).map(
|
|
11849
|
+
async (skillName) => {
|
|
11843
11850
|
const flatPath = path23.join(skillsDir, `${skillName}.md`);
|
|
11844
11851
|
const subdirPath = path23.join(skillsDir, skillName, "SKILL.md");
|
|
11845
|
-
let content = null;
|
|
11846
|
-
let resolvedPath = flatPath;
|
|
11847
11852
|
try {
|
|
11848
|
-
content = await fs24.readFile(subdirPath, "utf-8");
|
|
11849
|
-
|
|
11853
|
+
const content = await fs24.readFile(subdirPath, "utf-8");
|
|
11854
|
+
return { name: skillName, content, filePath: subdirPath };
|
|
11850
11855
|
} catch {
|
|
11851
11856
|
try {
|
|
11852
|
-
content = await fs24.readFile(flatPath, "utf-8");
|
|
11853
|
-
|
|
11857
|
+
const content = await fs24.readFile(flatPath, "utf-8");
|
|
11858
|
+
return { name: skillName, content, filePath: flatPath };
|
|
11854
11859
|
} catch {
|
|
11855
|
-
|
|
11860
|
+
return null;
|
|
11856
11861
|
}
|
|
11857
11862
|
}
|
|
11858
|
-
if (content) {
|
|
11859
|
-
skills.push({
|
|
11860
|
-
name: skillName,
|
|
11861
|
-
content,
|
|
11862
|
-
filePath: resolvedPath
|
|
11863
|
-
});
|
|
11864
|
-
loadedSkillNames.add(skillName);
|
|
11865
|
-
}
|
|
11866
11863
|
}
|
|
11867
|
-
|
|
11868
|
-
|
|
11864
|
+
);
|
|
11865
|
+
const results = await Promise.all(skillPromises);
|
|
11866
|
+
return results.filter((skill) => skill !== null);
|
|
11869
11867
|
}
|
|
11870
11868
|
/**
|
|
11871
11869
|
* Determine if task should be fragmented into subtasks
|
|
@@ -18093,72 +18091,53 @@ ${formatFullDiff(diff)}`);
|
|
|
18093
18091
|
}
|
|
18094
18092
|
/**
|
|
18095
18093
|
* Display sync results (extracted to avoid duplication)
|
|
18094
|
+
*
|
|
18095
|
+
* UX Design (PRJ-100):
|
|
18096
|
+
* - Summary first: success + key metrics on first lines
|
|
18097
|
+
* - Scannable: single-line metrics, minimal vertical space
|
|
18098
|
+
* - Changes focused: show what changed, not everything that exists
|
|
18099
|
+
* - Next steps prominent: clear call to action at bottom
|
|
18096
18100
|
*/
|
|
18097
18101
|
async showSyncResult(result, startTime) {
|
|
18098
|
-
const
|
|
18099
|
-
|
|
18100
|
-
|
|
18101
|
-
|
|
18102
|
-
|
|
18103
|
-
|
|
18104
|
-
console.log("\u{1F4CA} Project Stats");
|
|
18105
|
-
console.log(`\u251C\u2500\u2500 Files: ~${result.stats.fileCount}`);
|
|
18106
|
-
console.log(`\u251C\u2500\u2500 Commits: ${result.git.commits}`);
|
|
18107
|
-
console.log(`\u251C\u2500\u2500 Version: ${result.stats.version}`);
|
|
18108
|
-
console.log(`\u2514\u2500\u2500 Stack: ${result.stats.ecosystem}
|
|
18102
|
+
const elapsed = Date.now() - startTime;
|
|
18103
|
+
const contextFilesCount = result.contextFiles.length + (result.aiTools?.filter((t) => t.success).length || 0);
|
|
18104
|
+
const agentCount = result.agents.length;
|
|
18105
|
+
const domainAgentCount = result.agents.filter((a) => a.type === "domain").length;
|
|
18106
|
+
await command_installer_default.installGlobalConfig();
|
|
18107
|
+
console.log(`\u2705 Synced ${result.stats.name || "project"} (${(elapsed / 1e3).toFixed(1)}s)
|
|
18109
18108
|
`);
|
|
18110
|
-
|
|
18111
|
-
|
|
18112
|
-
|
|
18113
|
-
|
|
18109
|
+
const compressionPct = result.syncMetrics?.compressionRate ? Math.round(result.syncMetrics.compressionRate * 100) : 0;
|
|
18110
|
+
const metricsLine = [
|
|
18111
|
+
`${result.stats.fileCount} files \u2192 ${contextFilesCount} context`,
|
|
18112
|
+
`${agentCount} agents`,
|
|
18113
|
+
compressionPct > 10 ? `${compressionPct}% reduction` : null
|
|
18114
|
+
].filter(Boolean).join(" | ");
|
|
18115
|
+
console.log(metricsLine);
|
|
18116
|
+
const framework = result.stats.frameworks.length > 0 ? ` (${result.stats.frameworks[0]})` : "";
|
|
18117
|
+
console.log(`Stack: ${result.stats.ecosystem}${framework} | Branch: ${result.git.branch}
|
|
18114
18118
|
`);
|
|
18115
|
-
console.log("
|
|
18116
|
-
|
|
18117
|
-
console.log(
|
|
18119
|
+
console.log("Generated:");
|
|
18120
|
+
if (result.contextFiles.length > 0) {
|
|
18121
|
+
console.log(` \u2713 ${result.contextFiles.length} context files`);
|
|
18118
18122
|
}
|
|
18119
|
-
|
|
18120
|
-
if (
|
|
18121
|
-
const
|
|
18122
|
-
console.log(
|
|
18123
|
-
|
|
18124
|
-
|
|
18125
|
-
|
|
18126
|
-
}
|
|
18127
|
-
console.log("");
|
|
18123
|
+
const successTools = result.aiTools?.filter((t) => t.success) || [];
|
|
18124
|
+
if (successTools.length > 0) {
|
|
18125
|
+
const toolNames = successTools.map((t) => t.toolId).join(", ");
|
|
18126
|
+
console.log(` \u2713 AI tools: ${toolNames}`);
|
|
18127
|
+
}
|
|
18128
|
+
if (agentCount > 0) {
|
|
18129
|
+
const agentSummary = domainAgentCount > 0 ? `${agentCount} agents (${domainAgentCount} domain)` : `${agentCount} agents`;
|
|
18130
|
+
console.log(` \u2713 ${agentSummary}`);
|
|
18128
18131
|
}
|
|
18129
|
-
const workflowAgents = result.agents.filter((a) => a.type === "workflow").map((a) => a.name);
|
|
18130
|
-
const domainAgents = result.agents.filter((a) => a.type === "domain").map((a) => a.name);
|
|
18131
|
-
console.log(`\u{1F916} Agents Regenerated (${result.agents.length})`);
|
|
18132
|
-
console.log(`\u251C\u2500\u2500 Workflow: ${workflowAgents.join(", ")}`);
|
|
18133
|
-
console.log(`\u2514\u2500\u2500 Domain: ${domainAgents.join(", ") || "none"}
|
|
18134
|
-
`);
|
|
18135
18132
|
if (result.skills.length > 0) {
|
|
18136
|
-
|
|
18137
|
-
|
|
18138
|
-
console.log(`\u251C\u2500\u2500 ${skill.agent}.md \u2192 ${skill.skill}`);
|
|
18139
|
-
}
|
|
18140
|
-
console.log("");
|
|
18133
|
+
const skillWord = result.skills.length === 1 ? "skill" : "skills";
|
|
18134
|
+
console.log(` \u2713 ${result.skills.length} ${skillWord}`);
|
|
18141
18135
|
}
|
|
18136
|
+
console.log("");
|
|
18142
18137
|
if (result.git.hasChanges) {
|
|
18143
|
-
console.log("\u26A0\uFE0F
|
|
18144
|
-
} else {
|
|
18145
|
-
console.log("\u2728 Repository is clean!\n");
|
|
18138
|
+
console.log("\u26A0\uFE0F Uncommitted changes detected\n");
|
|
18146
18139
|
}
|
|
18147
18140
|
showNextSteps("sync");
|
|
18148
|
-
const elapsed = Date.now() - startTime;
|
|
18149
|
-
const contextFilesCount = result.contextFiles.length + (result.aiTools?.filter((t) => t.success).length || 0);
|
|
18150
|
-
const agentCount = result.agents.length;
|
|
18151
|
-
console.log("\u2500".repeat(45));
|
|
18152
|
-
console.log("\u{1F4CA} Sync Summary");
|
|
18153
|
-
console.log(
|
|
18154
|
-
` Stack: ${result.stats.ecosystem} (${result.stats.frameworks.join(", ") || "no frameworks"})`
|
|
18155
|
-
);
|
|
18156
|
-
console.log(` Files: ${result.stats.fileCount} analyzed \u2192 ${contextFilesCount} context files`);
|
|
18157
|
-
console.log(
|
|
18158
|
-
` Agents: ${agentCount} (${result.agents.filter((a) => a.type === "domain").length} domain)`
|
|
18159
|
-
);
|
|
18160
|
-
console.log(` Time: ${(elapsed / 1e3).toFixed(1)}s`);
|
|
18161
|
-
console.log("");
|
|
18162
18141
|
return {
|
|
18163
18142
|
success: true,
|
|
18164
18143
|
data: result,
|
|
@@ -26431,7 +26410,7 @@ var require_package = __commonJS({
|
|
|
26431
26410
|
"package.json"(exports, module) {
|
|
26432
26411
|
module.exports = {
|
|
26433
26412
|
name: "prjct-cli",
|
|
26434
|
-
version: "0.60.
|
|
26413
|
+
version: "0.60.2",
|
|
26435
26414
|
description: "Context layer for AI agents. Project context for Claude Code, Gemini CLI, and more.",
|
|
26436
26415
|
main: "core/index.ts",
|
|
26437
26416
|
bin: {
|