prjct-cli 0.60.0 → 0.60.1

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 CHANGED
@@ -1,5 +1,43 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.60.1] - 2026-02-05
4
+
5
+ ### Bug Fixes
6
+
7
+ - improve sync output with summary-first format (PRJ-100) (#100)
8
+
9
+
10
+ ## [0.60.1] - 2026-02-05
11
+
12
+ ### Improved
13
+
14
+ - **Sync output UX (PRJ-100)**: Summary-first format with key metrics prominent, ~50% less output
15
+
16
+ ### Implementation Details
17
+
18
+ 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").
19
+
20
+ ### Learnings
21
+
22
+ - `syncMetrics.compressionRate` is a decimal (0-1), not percentage - multiply by 100
23
+ - Summary-first output pattern improves scannability
24
+ - Conditional metric display reduces noise for low-value data
25
+
26
+ ### Test Plan
27
+
28
+ #### For QA
29
+ 1. Run `prjct sync --yes` on any project
30
+ 2. Verify output shows success line first with timing: `✅ Synced {project} ({time}s)`
31
+ 3. Verify single-line metrics: `{files} files → {context} context | {agents} agents`
32
+ 4. Verify compression rate only shows if > 10%
33
+ 5. Verify pluralization is correct ("1 skill" not "1 skills")
34
+
35
+ #### For Users
36
+ - Sync output is now more scannable - key metrics appear first instead of buried at bottom
37
+ - Run `p. sync` as usual - new format is automatic
38
+ - No breaking changes
39
+
40
+
3
41
  ## [0.60.0] - 2026-02-05
4
42
 
5
43
  ### Features
@@ -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
- // Update global config
464
- const globalConfigResult = await commandInstaller.installGlobalConfig()
465
- if (globalConfigResult.success) {
466
- console.log(`📝 Updated ${pathManager.getDisplayPath(globalConfigResult.path!)}`)
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
- // Format output
470
- console.log(`🔄 Project synced to prjct v${result.cliVersion}\n`)
471
-
472
- console.log('📊 Project Stats')
473
- console.log(`├── Files: ~${result.stats.fileCount}`)
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
- // Show AI Tools generated (multi-agent output)
490
- if (result.aiTools && result.aiTools.length > 0) {
491
- const successTools = result.aiTools.filter((t) => t.success)
492
- console.log(`🤖 AI Tools Context (${successTools.length})`)
493
- for (const tool of result.aiTools) {
494
- const status = tool.success ? '✓' : '✗'
495
- console.log(`├── ${status} ${tool.outputFile} (${tool.toolId})`)
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
- const workflowAgents = result.agents.filter((a) => a.type === 'workflow').map((a) => a.name)
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
- console.log('📦 Skills Configured')
509
- for (const skill of result.skills) {
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('⚠️ You have uncommitted changes\n')
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,
@@ -18093,72 +18093,53 @@ ${formatFullDiff(diff)}`);
18093
18093
  }
18094
18094
  /**
18095
18095
  * Display sync results (extracted to avoid duplication)
18096
+ *
18097
+ * UX Design (PRJ-100):
18098
+ * - Summary first: success + key metrics on first lines
18099
+ * - Scannable: single-line metrics, minimal vertical space
18100
+ * - Changes focused: show what changed, not everything that exists
18101
+ * - Next steps prominent: clear call to action at bottom
18096
18102
  */
18097
18103
  async showSyncResult(result, startTime) {
18098
- const globalConfigResult = await command_installer_default.installGlobalConfig();
18099
- if (globalConfigResult.success) {
18100
- console.log(`\u{1F4DD} Updated ${path_manager_default.getDisplayPath(globalConfigResult.path)}`);
18101
- }
18102
- console.log(`\u{1F504} Project synced to prjct v${result.cliVersion}
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}
18104
+ const elapsed = Date.now() - startTime;
18105
+ const contextFilesCount = result.contextFiles.length + (result.aiTools?.filter((t) => t.success).length || 0);
18106
+ const agentCount = result.agents.length;
18107
+ const domainAgentCount = result.agents.filter((a) => a.type === "domain").length;
18108
+ await command_installer_default.installGlobalConfig();
18109
+ console.log(`\u2705 Synced ${result.stats.name || "project"} (${(elapsed / 1e3).toFixed(1)}s)
18109
18110
  `);
18110
- console.log("\u{1F33F} Git Status");
18111
- console.log(`\u251C\u2500\u2500 Branch: ${result.git.branch}`);
18112
- console.log(`\u251C\u2500\u2500 Uncommitted: ${result.git.hasChanges ? "Yes" : "Clean"}`);
18113
- console.log(`\u2514\u2500\u2500 Recent: ${result.git.weeklyCommits} commits this week
18111
+ const compressionPct = result.syncMetrics?.compressionRate ? Math.round(result.syncMetrics.compressionRate * 100) : 0;
18112
+ const metricsLine = [
18113
+ `${result.stats.fileCount} files \u2192 ${contextFilesCount} context`,
18114
+ `${agentCount} agents`,
18115
+ compressionPct > 10 ? `${compressionPct}% reduction` : null
18116
+ ].filter(Boolean).join(" | ");
18117
+ console.log(metricsLine);
18118
+ const framework = result.stats.frameworks.length > 0 ? ` (${result.stats.frameworks[0]})` : "";
18119
+ console.log(`Stack: ${result.stats.ecosystem}${framework} | Branch: ${result.git.branch}
18114
18120
  `);
18115
- console.log("\u{1F4C1} Context Updated");
18116
- for (const file of result.contextFiles) {
18117
- console.log(`\u251C\u2500\u2500 ${file}`);
18121
+ console.log("Generated:");
18122
+ if (result.contextFiles.length > 0) {
18123
+ console.log(` \u2713 ${result.contextFiles.length} context files`);
18118
18124
  }
18119
- console.log("");
18120
- if (result.aiTools && result.aiTools.length > 0) {
18121
- const successTools = result.aiTools.filter((t) => t.success);
18122
- console.log(`\u{1F916} AI Tools Context (${successTools.length})`);
18123
- for (const tool of result.aiTools) {
18124
- const status = tool.success ? "\u2713" : "\u2717";
18125
- console.log(`\u251C\u2500\u2500 ${status} ${tool.outputFile} (${tool.toolId})`);
18126
- }
18127
- console.log("");
18125
+ const successTools = result.aiTools?.filter((t) => t.success) || [];
18126
+ if (successTools.length > 0) {
18127
+ const toolNames = successTools.map((t) => t.toolId).join(", ");
18128
+ console.log(` \u2713 AI tools: ${toolNames}`);
18129
+ }
18130
+ if (agentCount > 0) {
18131
+ const agentSummary = domainAgentCount > 0 ? `${agentCount} agents (${domainAgentCount} domain)` : `${agentCount} agents`;
18132
+ console.log(` \u2713 ${agentSummary}`);
18128
18133
  }
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
18134
  if (result.skills.length > 0) {
18136
- console.log("\u{1F4E6} Skills Configured");
18137
- for (const skill of result.skills) {
18138
- console.log(`\u251C\u2500\u2500 ${skill.agent}.md \u2192 ${skill.skill}`);
18139
- }
18140
- console.log("");
18135
+ const skillWord = result.skills.length === 1 ? "skill" : "skills";
18136
+ console.log(` \u2713 ${result.skills.length} ${skillWord}`);
18141
18137
  }
18138
+ console.log("");
18142
18139
  if (result.git.hasChanges) {
18143
- console.log("\u26A0\uFE0F You have uncommitted changes\n");
18144
- } else {
18145
- console.log("\u2728 Repository is clean!\n");
18140
+ console.log("\u26A0\uFE0F Uncommitted changes detected\n");
18146
18141
  }
18147
18142
  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
18143
  return {
18163
18144
  success: true,
18164
18145
  data: result,
@@ -26431,7 +26412,7 @@ var require_package = __commonJS({
26431
26412
  "package.json"(exports, module) {
26432
26413
  module.exports = {
26433
26414
  name: "prjct-cli",
26434
- version: "0.60.0",
26415
+ version: "0.60.1",
26435
26416
  description: "Context layer for AI agents. Project context for Claude Code, Gemini CLI, and more.",
26436
26417
  main: "core/index.ts",
26437
26418
  bin: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prjct-cli",
3
- "version": "0.60.0",
3
+ "version": "0.60.1",
4
4
  "description": "Context layer for AI agents. Project context for Claude Code, Gemini CLI, and more.",
5
5
  "main": "core/index.ts",
6
6
  "bin": {