prjct-cli 0.52.0 → 0.54.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.
@@ -5,11 +5,12 @@
5
5
  import fs from 'node:fs/promises'
6
6
  import path from 'node:path'
7
7
  import prompts from 'prompts'
8
+ import memorySystem from '../agentic/memory-system'
8
9
  import { generateContext } from '../context/generator'
9
10
  import analyzer from '../domain/analyzer'
10
11
  import commandInstaller from '../infrastructure/command-installer'
11
12
  import { formatCost } from '../schemas/metrics'
12
- import { syncService } from '../services'
13
+ import { memoryService, syncService } from '../services'
13
14
  import { formatDiffPreview, formatFullDiff, generateSyncDiff } from '../services/diff-generator'
14
15
  import { metricsStorage } from '../storage/metrics-storage'
15
16
  import type { AnalyzeOptions, CommandResult, ProjectContext } from '../types'
@@ -439,13 +440,17 @@ export class AnalysisCommands extends PrjctCommandsBase {
439
440
  }
440
441
 
441
442
  /**
442
- * /p:stats - Value dashboard showing accumulated savings and impact
443
+ * /p:stats - Session summary and value dashboard
443
444
  *
444
445
  * Displays:
446
+ * - Session activity (tasks completed, features shipped today)
447
+ * - Patterns learned (from memory system)
445
448
  * - Token savings (total, compression rate, estimated cost)
446
449
  * - Performance metrics (sync count, avg duration)
447
450
  * - Agent usage breakdown
448
451
  * - 30-day trend visualization
452
+ *
453
+ * @see PRJ-89
449
454
  */
450
455
  async stats(
451
456
  projectPath: string = process.cwd(),
@@ -464,9 +469,17 @@ export class AnalysisCommands extends PrjctCommandsBase {
464
469
  const summary = await metricsStorage.getSummary(projectId)
465
470
  const dailyStats = await metricsStorage.getDailyStats(projectId, 30)
466
471
 
472
+ // Get session activity (today's events)
473
+ const sessionActivity = await this._getSessionActivity(projectId)
474
+
475
+ // Get learned patterns
476
+ const patternsSummary = await memorySystem.getPatternsSummary(projectId)
477
+
467
478
  // JSON output mode
468
479
  if (options.json) {
469
480
  const jsonOutput = {
481
+ session: sessionActivity,
482
+ patterns: patternsSummary,
470
483
  totalTokensSaved: summary.totalTokensSaved,
471
484
  estimatedCostSaved: summary.estimatedCostSaved,
472
485
  compressionRate: summary.compressionRate,
@@ -485,8 +498,6 @@ export class AnalysisCommands extends PrjctCommandsBase {
485
498
  const globalPath = pathManager.getGlobalProjectPath(projectId)
486
499
  let projectName = 'Unknown'
487
500
  try {
488
- const fs = require('node:fs/promises')
489
- const path = require('node:path')
490
501
  const projectJson = JSON.parse(
491
502
  await fs.readFile(path.join(globalPath, 'project.json'), 'utf-8')
492
503
  )
@@ -508,13 +519,40 @@ export class AnalysisCommands extends PrjctCommandsBase {
508
519
  // ASCII Dashboard
509
520
  console.log('')
510
521
  console.log('╭─────────────────────────────────────────────────╮')
511
- console.log('│ 📊 prjct-cli Value Dashboard │')
522
+ console.log('│ 📊 prjct-cli Stats Dashboard │')
512
523
  console.log(
513
524
  `│ Project: ${projectName.padEnd(20).slice(0, 20)} | Since: ${firstSyncDate.padEnd(12).slice(0, 12)} │`
514
525
  )
515
526
  console.log('╰─────────────────────────────────────────────────╯')
516
527
  console.log('')
517
528
 
529
+ // Session Activity Section (PRJ-89)
530
+ console.log("🎯 TODAY'S ACTIVITY")
531
+ if (sessionActivity.sessionDuration) {
532
+ console.log(` Duration: ${sessionActivity.sessionDuration}`)
533
+ }
534
+ console.log(` Tasks completed: ${sessionActivity.tasksCompleted}`)
535
+ console.log(` Features shipped: ${sessionActivity.featuresShipped}`)
536
+ if (sessionActivity.agentsUsed.length > 0) {
537
+ const agentStr = sessionActivity.agentsUsed
538
+ .slice(0, 3)
539
+ .map((a) => `${a.name} (${a.count}×)`)
540
+ .join(', ')
541
+ console.log(` Agents used: ${agentStr}`)
542
+ }
543
+ console.log('')
544
+
545
+ // Learned Patterns Section (PRJ-89)
546
+ if (patternsSummary.decisions > 0 || patternsSummary.preferences > 0) {
547
+ console.log('🧠 PATTERNS LEARNED')
548
+ console.log(
549
+ ` Decisions: ${patternsSummary.learnedDecisions} confirmed (${patternsSummary.decisions} total)`
550
+ )
551
+ console.log(` Preferences: ${patternsSummary.preferences} saved`)
552
+ console.log(` Workflows: ${patternsSummary.workflows} tracked`)
553
+ console.log('')
554
+ }
555
+
518
556
  // Token Savings Section
519
557
  console.log('💰 TOKEN SAVINGS')
520
558
  console.log(` Total saved: ${this._formatTokens(summary.totalTokensSaved)} tokens`)
@@ -532,7 +570,7 @@ export class AnalysisCommands extends PrjctCommandsBase {
532
570
 
533
571
  // Agent Usage Section
534
572
  if (summary.topAgents.length > 0) {
535
- console.log('🤖 AGENT USAGE')
573
+ console.log('🤖 AGENT USAGE (all time)')
536
574
  const totalUsage = summary.topAgents.reduce((sum, a) => sum + a.usageCount, 0)
537
575
  for (const agent of summary.topAgents) {
538
576
  const pct = totalUsage > 0 ? ((agent.usageCount / totalUsage) * 100).toFixed(0) : 0
@@ -568,7 +606,9 @@ export class AnalysisCommands extends PrjctCommandsBase {
568
606
  summary,
569
607
  dailyStats,
570
608
  projectName,
571
- firstSyncDate
609
+ firstSyncDate,
610
+ sessionActivity,
611
+ patternsSummary
572
612
  )
573
613
  console.log(markdown)
574
614
  return { success: true, data: { markdown } }
@@ -576,7 +616,7 @@ export class AnalysisCommands extends PrjctCommandsBase {
576
616
 
577
617
  return {
578
618
  success: true,
579
- data: summary,
619
+ data: { ...summary, session: sessionActivity, patterns: patternsSummary },
580
620
  }
581
621
  } catch (error) {
582
622
  console.error('❌ Error:', (error as Error).message)
@@ -584,6 +624,76 @@ export class AnalysisCommands extends PrjctCommandsBase {
584
624
  }
585
625
  }
586
626
 
627
+ /**
628
+ * Get session activity stats from today's events
629
+ * @see PRJ-89
630
+ */
631
+ private async _getSessionActivity(projectId: string): Promise<{
632
+ sessionDuration: string | null
633
+ tasksCompleted: number
634
+ featuresShipped: number
635
+ agentsUsed: { name: string; count: number }[]
636
+ }> {
637
+ try {
638
+ // Get today's events from memory
639
+ const recentHistory = await memoryService.getRecentEvents(projectId, 100)
640
+
641
+ const today = new Date().toISOString().split('T')[0]
642
+ const todayEvents = recentHistory.filter((e) => {
643
+ const ts = (e.timestamp || e.ts) as string | undefined
644
+ return ts?.startsWith(today)
645
+ })
646
+
647
+ // Calculate session duration (time between first and last event today)
648
+ let sessionDuration: string | null = null
649
+ if (todayEvents.length >= 2) {
650
+ const timestamps = todayEvents
651
+ .map((e) => new Date((e.timestamp || e.ts) as string).getTime())
652
+ .filter((t) => !Number.isNaN(t))
653
+ .sort((a, b) => a - b)
654
+
655
+ if (timestamps.length >= 2) {
656
+ const durationMs = timestamps[timestamps.length - 1] - timestamps[0]
657
+ sessionDuration = dateHelper.formatDuration(durationMs)
658
+ }
659
+ }
660
+
661
+ // Count tasks completed today
662
+ const tasksCompleted = todayEvents.filter((e) => e.action === 'task_completed').length
663
+
664
+ // Count features shipped today
665
+ const featuresShipped = todayEvents.filter((e) => e.action === 'feature_shipped').length
666
+
667
+ // Count agent usage from sync events
668
+ const agentCounts = new Map<string, number>()
669
+ for (const event of todayEvents) {
670
+ if (event.action === 'sync' && Array.isArray(event.subagents)) {
671
+ for (const agent of event.subagents as string[]) {
672
+ agentCounts.set(agent, (agentCounts.get(agent) || 0) + 1)
673
+ }
674
+ }
675
+ }
676
+
677
+ const agentsUsed = Array.from(agentCounts.entries())
678
+ .map(([name, count]) => ({ name, count }))
679
+ .sort((a, b) => b.count - a.count)
680
+
681
+ return {
682
+ sessionDuration,
683
+ tasksCompleted,
684
+ featuresShipped,
685
+ agentsUsed,
686
+ }
687
+ } catch {
688
+ return {
689
+ sessionDuration: null,
690
+ tasksCompleted: 0,
691
+ featuresShipped: 0,
692
+ agentsUsed: [],
693
+ }
694
+ }
695
+ }
696
+
587
697
  // =========== Stats Helper Methods ===========
588
698
 
589
699
  private _formatTokens(tokens: number): string {
@@ -631,15 +741,62 @@ export class AnalysisCommands extends PrjctCommandsBase {
631
741
  },
632
742
  _dailyStats: { date: string; tokensSaved: number; syncs: number }[],
633
743
  projectName: string,
634
- firstSyncDate: string
744
+ firstSyncDate: string,
745
+ sessionActivity?: {
746
+ sessionDuration: string | null
747
+ tasksCompleted: number
748
+ featuresShipped: number
749
+ agentsUsed: { name: string; count: number }[]
750
+ },
751
+ patternsSummary?: {
752
+ decisions: number
753
+ learnedDecisions: number
754
+ workflows: number
755
+ preferences: number
756
+ }
635
757
  ): string {
636
758
  const lines: string[] = []
637
759
 
638
- lines.push(`# ${projectName} - Value Dashboard`)
760
+ lines.push(`# ${projectName} - Stats Dashboard`)
639
761
  lines.push('')
640
762
  lines.push(`_Generated: ${new Date().toLocaleString()} | Tracking since: ${firstSyncDate}_`)
641
763
  lines.push('')
642
764
 
765
+ // Session Activity (PRJ-89)
766
+ if (sessionActivity) {
767
+ lines.push("## 🎯 Today's Activity")
768
+ lines.push('')
769
+ lines.push(`| Metric | Value |`)
770
+ lines.push(`|--------|-------|`)
771
+ if (sessionActivity.sessionDuration) {
772
+ lines.push(`| Duration | ${sessionActivity.sessionDuration} |`)
773
+ }
774
+ lines.push(`| Tasks completed | ${sessionActivity.tasksCompleted} |`)
775
+ lines.push(`| Features shipped | ${sessionActivity.featuresShipped} |`)
776
+ if (sessionActivity.agentsUsed.length > 0) {
777
+ const agentStr = sessionActivity.agentsUsed
778
+ .slice(0, 3)
779
+ .map((a) => `${a.name} (${a.count}×)`)
780
+ .join(', ')
781
+ lines.push(`| Agents used | ${agentStr} |`)
782
+ }
783
+ lines.push('')
784
+ }
785
+
786
+ // Patterns Learned (PRJ-89)
787
+ if (patternsSummary && (patternsSummary.decisions > 0 || patternsSummary.preferences > 0)) {
788
+ lines.push('## 🧠 Patterns Learned')
789
+ lines.push('')
790
+ lines.push(`| Type | Count |`)
791
+ lines.push(`|------|-------|`)
792
+ lines.push(
793
+ `| Decisions | ${patternsSummary.learnedDecisions} confirmed (${patternsSummary.decisions} total) |`
794
+ )
795
+ lines.push(`| Preferences | ${patternsSummary.preferences} |`)
796
+ lines.push(`| Workflows | ${patternsSummary.workflows} |`)
797
+ lines.push('')
798
+ }
799
+
643
800
  lines.push('## 💰 Token Savings')
644
801
  lines.push('')
645
802
  lines.push(`| Metric | Value |`)
@@ -148,6 +148,7 @@ class ConfigManager {
148
148
  const localConfig: LocalConfig = {
149
149
  projectId,
150
150
  dataPath: displayPath,
151
+ showMetrics: true, // PRJ-70: default to true for new projects
151
152
  }
152
153
 
153
154
  await this.writeConfig(projectPath, localConfig)
@@ -318,6 +319,29 @@ class ConfigManager {
318
319
  return this.validateConfig(config)
319
320
  }
320
321
 
322
+ /**
323
+ * Get showMetrics setting from config.
324
+ * Defaults to true for new or existing projects without the setting.
325
+ * @see PRJ-70
326
+ */
327
+ async getShowMetrics(projectPath: string): Promise<boolean> {
328
+ const config = await this.readConfig(projectPath)
329
+ // Default to true if not set
330
+ return config?.showMetrics ?? true
331
+ }
332
+
333
+ /**
334
+ * Set showMetrics setting in config.
335
+ * @see PRJ-70
336
+ */
337
+ async setShowMetrics(projectPath: string, showMetrics: boolean): Promise<void> {
338
+ const config = await this.readConfig(projectPath)
339
+ if (config) {
340
+ config.showMetrics = showMetrics
341
+ await this.writeConfig(projectPath, config)
342
+ }
343
+ }
344
+
321
345
  /**
322
346
  * Get configuration with defaults
323
347
  * Returns LOCAL config only (projectId, dataPath)
@@ -112,6 +112,27 @@ export class MemoryService {
112
112
  }
113
113
  }
114
114
  }
115
+
116
+ /**
117
+ * Get recent events by projectId (for stats dashboard)
118
+ * @see PRJ-89
119
+ */
120
+ async getRecentEvents(
121
+ projectId: string,
122
+ limit: number = 100
123
+ ): Promise<Record<string, unknown>[]> {
124
+ try {
125
+ const memoryPath = pathManager.getFilePath(projectId, 'memory', 'context.jsonl')
126
+ const entries = await jsonlHelper.readJsonLines<Record<string, unknown>>(memoryPath)
127
+ return entries.slice(-limit)
128
+ } catch (error) {
129
+ // ENOENT or parse error - return empty
130
+ if (!isNotFoundError(error) && !(error instanceof SyntaxError)) {
131
+ console.error(`Memory read error: ${(error as Error).message}`)
132
+ }
133
+ return []
134
+ }
135
+ }
115
136
  }
116
137
 
117
138
  export const memoryService = new MemoryService()
@@ -12,6 +12,12 @@ import type { IntegrationsConfig } from './integrations'
12
12
  export interface LocalConfig {
13
13
  projectId: string
14
14
  dataPath: string
15
+ /**
16
+ * Whether to show metrics in command output.
17
+ * Defaults to true for new projects.
18
+ * @see PRJ-70
19
+ */
20
+ showMetrics?: boolean
15
21
  }
16
22
 
17
23
  /**
@@ -75,6 +75,76 @@ export interface MemoryQuery {
75
75
  since?: string
76
76
  }
77
77
 
78
+ /**
79
+ * Domain types for task context.
80
+ * @see PRJ-107
81
+ */
82
+ export type TaskDomain =
83
+ | 'frontend'
84
+ | 'backend'
85
+ | 'devops'
86
+ | 'docs'
87
+ | 'testing'
88
+ | 'database'
89
+ | 'general'
90
+
91
+ /**
92
+ * Enhanced query parameters for selective memory retrieval.
93
+ * @see PRJ-107
94
+ */
95
+ export interface RelevantMemoryQuery {
96
+ /** Task domain for context-aware retrieval */
97
+ taskDomain?: TaskDomain
98
+ /** Task description for keyword matching */
99
+ taskDescription?: string
100
+ /** Command being executed */
101
+ commandName?: string
102
+ /** Maximum results to return */
103
+ maxResults?: number
104
+ /** Minimum relevance score threshold (0-100) */
105
+ minRelevance?: number
106
+ }
107
+
108
+ /**
109
+ * Memory with relevance score attached.
110
+ * @see PRJ-107
111
+ */
112
+ export interface ScoredMemory extends Memory {
113
+ /** Relevance score (0-100) */
114
+ relevanceScore: number
115
+ /** Breakdown of score components */
116
+ scoreBreakdown?: {
117
+ domainMatch: number
118
+ tagMatch: number
119
+ recency: number
120
+ confidence: number
121
+ keywords: number
122
+ userTriggered: number
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Result of selective memory retrieval with metrics.
128
+ * @see PRJ-107
129
+ */
130
+ export interface MemoryRetrievalResult {
131
+ /** Relevant memories sorted by score */
132
+ memories: ScoredMemory[]
133
+ /** Retrieval metrics */
134
+ metrics: {
135
+ /** Total memories in database */
136
+ totalMemories: number
137
+ /** Memories that passed threshold */
138
+ memoriesConsidered: number
139
+ /** Memories returned */
140
+ memoriesReturned: number
141
+ /** Filtering ratio (returned/total) */
142
+ filteringRatio: number
143
+ /** Average relevance score of returned memories */
144
+ avgRelevanceScore: number
145
+ }
146
+ }
147
+
78
148
  /**
79
149
  * Memory database structure.
80
150
  */
@@ -235,10 +235,10 @@ const out: Output = {
235
235
  const border = '─'.repeat(maxLen + 2)
236
236
 
237
237
  console.log(chalk.dim(`┌${border}┐`))
238
- console.log(chalk.dim('│') + ` ${chalk.bold(title.padEnd(maxLen))} ` + chalk.dim('│'))
238
+ console.log(`${chalk.dim('│')} ${chalk.bold(title.padEnd(maxLen))} ${chalk.dim('│')}`)
239
239
  console.log(chalk.dim(`├${border}┤`))
240
240
  for (const line of lines) {
241
- console.log(chalk.dim('│') + ` ${line.padEnd(maxLen)} ` + chalk.dim('│'))
241
+ console.log(`${chalk.dim('│')} ${line.padEnd(maxLen)} ${chalk.dim('│')}`)
242
242
  }
243
243
  console.log(chalk.dim(`└${border}┘`))
244
244
  return this