prjct-cli 0.53.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,62 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.54.0] - 2026-01-30
4
+
5
+ ### Features
6
+
7
+ - Add showMetrics config option - PRJ-70 (#82)
8
+ - Selective memory retrieval based on task relevance - PRJ-107 (#81)
9
+ - Add session stats to p. stats command - PRJ-89 (#80)
10
+
11
+
12
+ ## [0.55.1] - 2026-01-30
13
+
14
+ ### Added
15
+
16
+ - **showMetrics config option** (PRJ-70)
17
+ - Add `showMetrics` boolean to `LocalConfig` (prjct.config.json)
18
+ - Defaults to `true` for new projects and existing projects without setting
19
+ - Added `getShowMetrics()` and `setShowMetrics()` to ConfigManager
20
+
21
+
22
+ ## [0.55.0] - 2026-01-30
23
+
24
+ ### Features
25
+
26
+ - Selective memory retrieval based on task relevance - PRJ-107
27
+
28
+
29
+ ## [0.55.0] - 2026-01-30
30
+
31
+ ### Added
32
+
33
+ - **Selective memory retrieval based on task relevance** (PRJ-107)
34
+ - Added `getRelevantMemoriesWithMetrics()` for domain-based filtering
35
+ - Relevance scoring considers: domain match (25pts), tag match (20pts), recency (15pts), confidence (20pts), keywords (15pts), user triggered (5pts)
36
+ - Returns retrieval metrics: total, considered, returned, filtering ratio, avg score
37
+ - New types: `RelevantMemoryQuery`, `ScoredMemory`, `MemoryRetrievalResult`, `TaskDomain`
38
+ - Integrates with PRJ-104 confidence scoring
39
+
40
+
41
+ ## [0.54.0] - 2026-01-30
42
+
43
+ ### Features
44
+
45
+ - Session stats in p. stats command - PRJ-89
46
+
47
+
48
+ ## [0.54.0] - 2026-01-30
49
+
50
+ ### Added
51
+
52
+ - **Session stats in `p. stats` command** (PRJ-89)
53
+ - Shows today's activity: duration, tasks completed, features shipped
54
+ - Displays agents used during the session with frequency
55
+ - Shows learned patterns: decisions, preferences, workflows
56
+ - Enhanced JSON and export modes include session data
57
+ - Added `getRecentEvents()` to memoryService
58
+
59
+
3
60
  ## [0.53.0] - 2026-01-30
4
61
 
5
62
  ### Features
@@ -30,9 +30,13 @@ export type {
30
30
  MemoryContext,
31
31
  MemoryContextParams,
32
32
  MemoryDatabase,
33
+ MemoryRetrievalResult,
33
34
  MemoryTag,
34
35
  Patterns,
35
36
  Preference,
37
+ RelevantMemoryQuery,
38
+ ScoredMemory,
39
+ TaskDomain,
36
40
  Workflow,
37
41
  } from '../types/memory'
38
42
 
@@ -44,9 +48,13 @@ import type {
44
48
  Memory,
45
49
  MemoryContext,
46
50
  MemoryDatabase,
51
+ MemoryRetrievalResult,
47
52
  MemoryTag,
48
53
  Patterns,
49
54
  Preference,
55
+ RelevantMemoryQuery,
56
+ ScoredMemory,
57
+ TaskDomain,
50
58
  Workflow,
51
59
  } from '../types/memory'
52
60
 
@@ -690,6 +698,202 @@ export class SemanticMemories extends CachedStore<MemoryDatabase> {
690
698
  .map(({ _score, ...memory }) => memory as Memory)
691
699
  }
692
700
 
701
+ /**
702
+ * Enhanced memory retrieval with domain-based filtering and metrics.
703
+ * Implements selective memory retrieval based on task relevance.
704
+ * @see PRJ-107
705
+ */
706
+ async getRelevantMemoriesWithMetrics(
707
+ projectId: string,
708
+ query: RelevantMemoryQuery
709
+ ): Promise<MemoryRetrievalResult> {
710
+ const db = await this.load(projectId)
711
+ const totalMemories = db.memories.length
712
+
713
+ if (totalMemories === 0) {
714
+ return {
715
+ memories: [],
716
+ metrics: {
717
+ totalMemories: 0,
718
+ memoriesConsidered: 0,
719
+ memoriesReturned: 0,
720
+ filteringRatio: 0,
721
+ avgRelevanceScore: 0,
722
+ },
723
+ }
724
+ }
725
+
726
+ const maxResults = query.maxResults ?? 10
727
+ const minRelevance = query.minRelevance ?? 10
728
+
729
+ // Score all memories
730
+ const scored: ScoredMemory[] = db.memories.map((memory) => {
731
+ const breakdown = {
732
+ domainMatch: 0,
733
+ tagMatch: 0,
734
+ recency: 0,
735
+ confidence: 0,
736
+ keywords: 0,
737
+ userTriggered: 0,
738
+ }
739
+
740
+ // Domain match scoring (0-25 points)
741
+ if (query.taskDomain) {
742
+ const domainTags = this._getDomainTags(query.taskDomain)
743
+ const matchingTags = (memory.tags || []).filter((tag) => domainTags.includes(tag))
744
+ breakdown.domainMatch = Math.min(25, matchingTags.length * 10)
745
+ }
746
+
747
+ // Tag match from command context (0-20 points)
748
+ if (query.commandName) {
749
+ const commandTags = this._getCommandTags(query.commandName)
750
+ const matchingTags = (memory.tags || []).filter((tag) => commandTags.includes(tag))
751
+ breakdown.tagMatch = Math.min(20, matchingTags.length * 8)
752
+ }
753
+
754
+ // Recency scoring (0-15 points)
755
+ const age = Date.now() - new Date(memory.updatedAt).getTime()
756
+ const daysSinceUpdate = age / (1000 * 60 * 60 * 24)
757
+ breakdown.recency = Math.max(0, Math.round(15 - daysSinceUpdate * 0.5))
758
+
759
+ // Confidence scoring (0-20 points) - PRJ-104 integration
760
+ if (memory.confidence) {
761
+ breakdown.confidence =
762
+ memory.confidence === 'high' ? 20 : memory.confidence === 'medium' ? 12 : 5
763
+ } else if (memory.observationCount) {
764
+ // Fallback to observation count
765
+ breakdown.confidence = Math.min(20, memory.observationCount * 3)
766
+ }
767
+
768
+ // Keyword matching (0-15 points)
769
+ if (query.taskDescription) {
770
+ const keywords = this._extractKeywordsFromText(query.taskDescription)
771
+ let keywordScore = 0
772
+ for (const keyword of keywords) {
773
+ if (memory.content.toLowerCase().includes(keyword)) keywordScore += 2
774
+ if (memory.title.toLowerCase().includes(keyword)) keywordScore += 3
775
+ }
776
+ breakdown.keywords = Math.min(15, keywordScore)
777
+ }
778
+
779
+ // User triggered bonus (0-5 points)
780
+ if (memory.userTriggered) {
781
+ breakdown.userTriggered = 5
782
+ }
783
+
784
+ const relevanceScore =
785
+ breakdown.domainMatch +
786
+ breakdown.tagMatch +
787
+ breakdown.recency +
788
+ breakdown.confidence +
789
+ breakdown.keywords +
790
+ breakdown.userTriggered
791
+
792
+ return {
793
+ ...memory,
794
+ relevanceScore,
795
+ scoreBreakdown: breakdown,
796
+ }
797
+ })
798
+
799
+ // Filter by minimum relevance
800
+ const considered = scored.filter((m) => m.relevanceScore >= minRelevance)
801
+
802
+ // Sort by relevance and take top N
803
+ const sorted = considered.sort((a, b) => b.relevanceScore - a.relevanceScore)
804
+ const returned = sorted.slice(0, maxResults)
805
+
806
+ // Calculate average relevance
807
+ const avgRelevanceScore =
808
+ returned.length > 0
809
+ ? Math.round(returned.reduce((sum, m) => sum + m.relevanceScore, 0) / returned.length)
810
+ : 0
811
+
812
+ return {
813
+ memories: returned,
814
+ metrics: {
815
+ totalMemories,
816
+ memoriesConsidered: considered.length,
817
+ memoriesReturned: returned.length,
818
+ filteringRatio: totalMemories > 0 ? returned.length / totalMemories : 0,
819
+ avgRelevanceScore,
820
+ },
821
+ }
822
+ }
823
+
824
+ /**
825
+ * Map task domain to relevant memory tags.
826
+ * @see PRJ-107
827
+ */
828
+ private _getDomainTags(domain: TaskDomain): MemoryTag[] {
829
+ const domainTagMap: Record<TaskDomain, MemoryTag[]> = {
830
+ frontend: [MEMORY_TAGS.CODE_STYLE, MEMORY_TAGS.FILE_STRUCTURE, MEMORY_TAGS.ARCHITECTURE],
831
+ backend: [
832
+ MEMORY_TAGS.CODE_STYLE,
833
+ MEMORY_TAGS.ARCHITECTURE,
834
+ MEMORY_TAGS.DEPENDENCIES,
835
+ MEMORY_TAGS.TECH_STACK,
836
+ ],
837
+ devops: [MEMORY_TAGS.SHIP_WORKFLOW, MEMORY_TAGS.TEST_BEHAVIOR, MEMORY_TAGS.DEPENDENCIES],
838
+ docs: [MEMORY_TAGS.CODE_STYLE, MEMORY_TAGS.NAMING_CONVENTION],
839
+ testing: [MEMORY_TAGS.TEST_BEHAVIOR, MEMORY_TAGS.CODE_STYLE],
840
+ database: [MEMORY_TAGS.ARCHITECTURE, MEMORY_TAGS.NAMING_CONVENTION],
841
+ general: Object.values(MEMORY_TAGS) as MemoryTag[],
842
+ }
843
+ return domainTagMap[domain] || []
844
+ }
845
+
846
+ /**
847
+ * Map command to relevant memory tags.
848
+ * @see PRJ-107
849
+ */
850
+ private _getCommandTags(commandName: string): MemoryTag[] {
851
+ const commandTags: Record<string, MemoryTag[]> = {
852
+ ship: [MEMORY_TAGS.COMMIT_STYLE, MEMORY_TAGS.SHIP_WORKFLOW, MEMORY_TAGS.TEST_BEHAVIOR],
853
+ feature: [MEMORY_TAGS.ARCHITECTURE, MEMORY_TAGS.CODE_STYLE],
854
+ done: [MEMORY_TAGS.SHIP_WORKFLOW],
855
+ analyze: [MEMORY_TAGS.TECH_STACK, MEMORY_TAGS.ARCHITECTURE],
856
+ spec: [MEMORY_TAGS.ARCHITECTURE, MEMORY_TAGS.CODE_STYLE],
857
+ task: [MEMORY_TAGS.BRANCH_NAMING, MEMORY_TAGS.CODE_STYLE],
858
+ sync: [MEMORY_TAGS.TECH_STACK, MEMORY_TAGS.ARCHITECTURE, MEMORY_TAGS.DEPENDENCIES],
859
+ test: [MEMORY_TAGS.TEST_BEHAVIOR],
860
+ bug: [MEMORY_TAGS.CODE_STYLE, MEMORY_TAGS.TEST_BEHAVIOR],
861
+ }
862
+ return commandTags[commandName] || []
863
+ }
864
+
865
+ /**
866
+ * Extract keywords from text for matching.
867
+ */
868
+ private _extractKeywordsFromText(text: string): string[] {
869
+ const words = text.toLowerCase().split(/\s+/)
870
+ const stopWords = new Set([
871
+ 'the',
872
+ 'a',
873
+ 'an',
874
+ 'is',
875
+ 'are',
876
+ 'to',
877
+ 'for',
878
+ 'and',
879
+ 'or',
880
+ 'in',
881
+ 'on',
882
+ 'at',
883
+ 'by',
884
+ 'with',
885
+ 'from',
886
+ 'as',
887
+ 'it',
888
+ 'this',
889
+ 'that',
890
+ 'be',
891
+ 'have',
892
+ 'has',
893
+ ])
894
+ return words.filter((w) => w.length > 2 && !stopWords.has(w))
895
+ }
896
+
693
897
  private _extractContextTags(context: MemoryContext): string[] {
694
898
  const tags: string[] = []
695
899
 
@@ -863,6 +1067,18 @@ export class MemorySystem {
863
1067
  return this._semanticMemories.getMemoryStats(projectId)
864
1068
  }
865
1069
 
1070
+ /**
1071
+ * Get relevant memories with domain-based filtering and metrics.
1072
+ * Implements selective memory retrieval based on task relevance.
1073
+ * @see PRJ-107
1074
+ */
1075
+ getRelevantMemoriesWithMetrics(
1076
+ projectId: string,
1077
+ query: RelevantMemoryQuery
1078
+ ): Promise<MemoryRetrievalResult> {
1079
+ return this._semanticMemories.getRelevantMemoriesWithMetrics(projectId, query)
1080
+ }
1081
+
866
1082
  // ===========================================================================
867
1083
  // TIER 1: Session Memory
868
1084
  // ===========================================================================
@@ -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
@@ -1595,7 +1595,9 @@ var init_config_manager = __esm({
1595
1595
  const now = getTimestamp();
1596
1596
  const localConfig = {
1597
1597
  projectId,
1598
- dataPath: displayPath
1598
+ dataPath: displayPath,
1599
+ showMetrics: true
1600
+ // PRJ-70: default to true for new projects
1599
1601
  };
1600
1602
  await this.writeConfig(projectPath, localConfig);
1601
1603
  const globalConfig = {
@@ -1734,6 +1736,26 @@ var init_config_manager = __esm({
1734
1736
  const config = await this.readConfig(projectPath);
1735
1737
  return this.validateConfig(config);
1736
1738
  }
1739
+ /**
1740
+ * Get showMetrics setting from config.
1741
+ * Defaults to true for new or existing projects without the setting.
1742
+ * @see PRJ-70
1743
+ */
1744
+ async getShowMetrics(projectPath) {
1745
+ const config = await this.readConfig(projectPath);
1746
+ return config?.showMetrics ?? true;
1747
+ }
1748
+ /**
1749
+ * Set showMetrics setting in config.
1750
+ * @see PRJ-70
1751
+ */
1752
+ async setShowMetrics(projectPath, showMetrics) {
1753
+ const config = await this.readConfig(projectPath);
1754
+ if (config) {
1755
+ config.showMetrics = showMetrics;
1756
+ await this.writeConfig(projectPath, config);
1757
+ }
1758
+ }
1737
1759
  /**
1738
1760
  * Get configuration with defaults
1739
1761
  * Returns LOCAL config only (projectId, dataPath)
@@ -2235,10 +2257,10 @@ var init_output = __esm({
2235
2257
  const maxLen = Math.max(title.length, ...lines.map((l) => l.length));
2236
2258
  const border = "\u2500".repeat(maxLen + 2);
2237
2259
  console.log(chalk2.dim(`\u250C${border}\u2510`));
2238
- console.log(chalk2.dim("\u2502") + ` ${chalk2.bold(title.padEnd(maxLen))} ` + chalk2.dim("\u2502"));
2260
+ console.log(`${chalk2.dim("\u2502")} ${chalk2.bold(title.padEnd(maxLen))} ${chalk2.dim("\u2502")}`);
2239
2261
  console.log(chalk2.dim(`\u251C${border}\u2524`));
2240
2262
  for (const line of lines) {
2241
- console.log(chalk2.dim("\u2502") + ` ${line.padEnd(maxLen)} ` + chalk2.dim("\u2502"));
2263
+ console.log(`${chalk2.dim("\u2502")} ${line.padEnd(maxLen)} ${chalk2.dim("\u2502")}`);
2242
2264
  }
2243
2265
  console.log(chalk2.dim(`\u2514${border}\u2518`));
2244
2266
  return this;
@@ -9064,6 +9086,159 @@ var init_memory_system = __esm({
9064
9086
  });
9065
9087
  return scored.filter((m) => m._score > 0).sort((a, b) => b._score - a._score).slice(0, limit).map(({ _score, ...memory }) => memory);
9066
9088
  }
9089
+ /**
9090
+ * Enhanced memory retrieval with domain-based filtering and metrics.
9091
+ * Implements selective memory retrieval based on task relevance.
9092
+ * @see PRJ-107
9093
+ */
9094
+ async getRelevantMemoriesWithMetrics(projectId, query) {
9095
+ const db = await this.load(projectId);
9096
+ const totalMemories = db.memories.length;
9097
+ if (totalMemories === 0) {
9098
+ return {
9099
+ memories: [],
9100
+ metrics: {
9101
+ totalMemories: 0,
9102
+ memoriesConsidered: 0,
9103
+ memoriesReturned: 0,
9104
+ filteringRatio: 0,
9105
+ avgRelevanceScore: 0
9106
+ }
9107
+ };
9108
+ }
9109
+ const maxResults = query.maxResults ?? 10;
9110
+ const minRelevance = query.minRelevance ?? 10;
9111
+ const scored = db.memories.map((memory) => {
9112
+ const breakdown = {
9113
+ domainMatch: 0,
9114
+ tagMatch: 0,
9115
+ recency: 0,
9116
+ confidence: 0,
9117
+ keywords: 0,
9118
+ userTriggered: 0
9119
+ };
9120
+ if (query.taskDomain) {
9121
+ const domainTags = this._getDomainTags(query.taskDomain);
9122
+ const matchingTags = (memory.tags || []).filter((tag) => domainTags.includes(tag));
9123
+ breakdown.domainMatch = Math.min(25, matchingTags.length * 10);
9124
+ }
9125
+ if (query.commandName) {
9126
+ const commandTags = this._getCommandTags(query.commandName);
9127
+ const matchingTags = (memory.tags || []).filter((tag) => commandTags.includes(tag));
9128
+ breakdown.tagMatch = Math.min(20, matchingTags.length * 8);
9129
+ }
9130
+ const age = Date.now() - new Date(memory.updatedAt).getTime();
9131
+ const daysSinceUpdate = age / (1e3 * 60 * 60 * 24);
9132
+ breakdown.recency = Math.max(0, Math.round(15 - daysSinceUpdate * 0.5));
9133
+ if (memory.confidence) {
9134
+ breakdown.confidence = memory.confidence === "high" ? 20 : memory.confidence === "medium" ? 12 : 5;
9135
+ } else if (memory.observationCount) {
9136
+ breakdown.confidence = Math.min(20, memory.observationCount * 3);
9137
+ }
9138
+ if (query.taskDescription) {
9139
+ const keywords = this._extractKeywordsFromText(query.taskDescription);
9140
+ let keywordScore = 0;
9141
+ for (const keyword of keywords) {
9142
+ if (memory.content.toLowerCase().includes(keyword)) keywordScore += 2;
9143
+ if (memory.title.toLowerCase().includes(keyword)) keywordScore += 3;
9144
+ }
9145
+ breakdown.keywords = Math.min(15, keywordScore);
9146
+ }
9147
+ if (memory.userTriggered) {
9148
+ breakdown.userTriggered = 5;
9149
+ }
9150
+ const relevanceScore = breakdown.domainMatch + breakdown.tagMatch + breakdown.recency + breakdown.confidence + breakdown.keywords + breakdown.userTriggered;
9151
+ return {
9152
+ ...memory,
9153
+ relevanceScore,
9154
+ scoreBreakdown: breakdown
9155
+ };
9156
+ });
9157
+ const considered = scored.filter((m) => m.relevanceScore >= minRelevance);
9158
+ const sorted = considered.sort((a, b) => b.relevanceScore - a.relevanceScore);
9159
+ const returned = sorted.slice(0, maxResults);
9160
+ const avgRelevanceScore = returned.length > 0 ? Math.round(returned.reduce((sum, m) => sum + m.relevanceScore, 0) / returned.length) : 0;
9161
+ return {
9162
+ memories: returned,
9163
+ metrics: {
9164
+ totalMemories,
9165
+ memoriesConsidered: considered.length,
9166
+ memoriesReturned: returned.length,
9167
+ filteringRatio: totalMemories > 0 ? returned.length / totalMemories : 0,
9168
+ avgRelevanceScore
9169
+ }
9170
+ };
9171
+ }
9172
+ /**
9173
+ * Map task domain to relevant memory tags.
9174
+ * @see PRJ-107
9175
+ */
9176
+ _getDomainTags(domain) {
9177
+ const domainTagMap = {
9178
+ frontend: [MEMORY_TAGS.CODE_STYLE, MEMORY_TAGS.FILE_STRUCTURE, MEMORY_TAGS.ARCHITECTURE],
9179
+ backend: [
9180
+ MEMORY_TAGS.CODE_STYLE,
9181
+ MEMORY_TAGS.ARCHITECTURE,
9182
+ MEMORY_TAGS.DEPENDENCIES,
9183
+ MEMORY_TAGS.TECH_STACK
9184
+ ],
9185
+ devops: [MEMORY_TAGS.SHIP_WORKFLOW, MEMORY_TAGS.TEST_BEHAVIOR, MEMORY_TAGS.DEPENDENCIES],
9186
+ docs: [MEMORY_TAGS.CODE_STYLE, MEMORY_TAGS.NAMING_CONVENTION],
9187
+ testing: [MEMORY_TAGS.TEST_BEHAVIOR, MEMORY_TAGS.CODE_STYLE],
9188
+ database: [MEMORY_TAGS.ARCHITECTURE, MEMORY_TAGS.NAMING_CONVENTION],
9189
+ general: Object.values(MEMORY_TAGS)
9190
+ };
9191
+ return domainTagMap[domain] || [];
9192
+ }
9193
+ /**
9194
+ * Map command to relevant memory tags.
9195
+ * @see PRJ-107
9196
+ */
9197
+ _getCommandTags(commandName) {
9198
+ const commandTags = {
9199
+ ship: [MEMORY_TAGS.COMMIT_STYLE, MEMORY_TAGS.SHIP_WORKFLOW, MEMORY_TAGS.TEST_BEHAVIOR],
9200
+ feature: [MEMORY_TAGS.ARCHITECTURE, MEMORY_TAGS.CODE_STYLE],
9201
+ done: [MEMORY_TAGS.SHIP_WORKFLOW],
9202
+ analyze: [MEMORY_TAGS.TECH_STACK, MEMORY_TAGS.ARCHITECTURE],
9203
+ spec: [MEMORY_TAGS.ARCHITECTURE, MEMORY_TAGS.CODE_STYLE],
9204
+ task: [MEMORY_TAGS.BRANCH_NAMING, MEMORY_TAGS.CODE_STYLE],
9205
+ sync: [MEMORY_TAGS.TECH_STACK, MEMORY_TAGS.ARCHITECTURE, MEMORY_TAGS.DEPENDENCIES],
9206
+ test: [MEMORY_TAGS.TEST_BEHAVIOR],
9207
+ bug: [MEMORY_TAGS.CODE_STYLE, MEMORY_TAGS.TEST_BEHAVIOR]
9208
+ };
9209
+ return commandTags[commandName] || [];
9210
+ }
9211
+ /**
9212
+ * Extract keywords from text for matching.
9213
+ */
9214
+ _extractKeywordsFromText(text) {
9215
+ const words = text.toLowerCase().split(/\s+/);
9216
+ const stopWords = /* @__PURE__ */ new Set([
9217
+ "the",
9218
+ "a",
9219
+ "an",
9220
+ "is",
9221
+ "are",
9222
+ "to",
9223
+ "for",
9224
+ "and",
9225
+ "or",
9226
+ "in",
9227
+ "on",
9228
+ "at",
9229
+ "by",
9230
+ "with",
9231
+ "from",
9232
+ "as",
9233
+ "it",
9234
+ "this",
9235
+ "that",
9236
+ "be",
9237
+ "have",
9238
+ "has"
9239
+ ]);
9240
+ return words.filter((w) => w.length > 2 && !stopWords.has(w));
9241
+ }
9067
9242
  _extractContextTags(context2) {
9068
9243
  const tags = [];
9069
9244
  const commandTags = {
@@ -9184,6 +9359,14 @@ Context: ${context2}` : ""}`,
9184
9359
  getMemoryStats(projectId) {
9185
9360
  return this._semanticMemories.getMemoryStats(projectId);
9186
9361
  }
9362
+ /**
9363
+ * Get relevant memories with domain-based filtering and metrics.
9364
+ * Implements selective memory retrieval based on task relevance.
9365
+ * @see PRJ-107
9366
+ */
9367
+ getRelevantMemoriesWithMetrics(projectId, query) {
9368
+ return this._semanticMemories.getRelevantMemoriesWithMetrics(projectId, query);
9369
+ }
9187
9370
  // ===========================================================================
9188
9371
  // TIER 1: Session Memory
9189
9372
  // ===========================================================================
@@ -15163,6 +15346,22 @@ var init_memory_service = __esm({
15163
15346
  }
15164
15347
  }
15165
15348
  }
15349
+ /**
15350
+ * Get recent events by projectId (for stats dashboard)
15351
+ * @see PRJ-89
15352
+ */
15353
+ async getRecentEvents(projectId, limit = 100) {
15354
+ try {
15355
+ const memoryPath = path_manager_default.getFilePath(projectId, "memory", "context.jsonl");
15356
+ const entries = await jsonl_helper_default.readJsonLines(memoryPath);
15357
+ return entries.slice(-limit);
15358
+ } catch (error) {
15359
+ if (!isNotFoundError(error) && !(error instanceof SyntaxError)) {
15360
+ console.error(`Memory read error: ${error.message}`);
15361
+ }
15362
+ return [];
15363
+ }
15364
+ }
15166
15365
  };
15167
15366
  memoryService = new MemoryService();
15168
15367
  }
@@ -16633,6 +16832,7 @@ var AnalysisCommands;
16633
16832
  var init_analysis2 = __esm({
16634
16833
  "core/commands/analysis.ts"() {
16635
16834
  "use strict";
16835
+ init_memory_system();
16636
16836
  init_generator();
16637
16837
  init_analyzer2();
16638
16838
  init_command_installer();
@@ -16972,13 +17172,17 @@ ${formatFullDiff(diff)}`);
16972
17172
  };
16973
17173
  }
16974
17174
  /**
16975
- * /p:stats - Value dashboard showing accumulated savings and impact
17175
+ * /p:stats - Session summary and value dashboard
16976
17176
  *
16977
17177
  * Displays:
17178
+ * - Session activity (tasks completed, features shipped today)
17179
+ * - Patterns learned (from memory system)
16978
17180
  * - Token savings (total, compression rate, estimated cost)
16979
17181
  * - Performance metrics (sync count, avg duration)
16980
17182
  * - Agent usage breakdown
16981
17183
  * - 30-day trend visualization
17184
+ *
17185
+ * @see PRJ-89
16982
17186
  */
16983
17187
  async stats(projectPath = process.cwd(), options = {}) {
16984
17188
  try {
@@ -16990,8 +17194,12 @@ ${formatFullDiff(diff)}`);
16990
17194
  }
16991
17195
  const summary = await metricsStorage.getSummary(projectId);
16992
17196
  const dailyStats = await metricsStorage.getDailyStats(projectId, 30);
17197
+ const sessionActivity = await this._getSessionActivity(projectId);
17198
+ const patternsSummary = await memory_system_default.getPatternsSummary(projectId);
16993
17199
  if (options.json) {
16994
17200
  const jsonOutput = {
17201
+ session: sessionActivity,
17202
+ patterns: patternsSummary,
16995
17203
  totalTokensSaved: summary.totalTokensSaved,
16996
17204
  estimatedCostSaved: summary.estimatedCostSaved,
16997
17205
  compressionRate: summary.compressionRate,
@@ -17008,10 +17216,8 @@ ${formatFullDiff(diff)}`);
17008
17216
  const globalPath = path_manager_default.getGlobalProjectPath(projectId);
17009
17217
  let projectName = "Unknown";
17010
17218
  try {
17011
- const fs47 = __require("node:fs/promises");
17012
- const path56 = __require("node:path");
17013
17219
  const projectJson = JSON.parse(
17014
- await fs47.readFile(path56.join(globalPath, "project.json"), "utf-8")
17220
+ await fs34.readFile(path35.join(globalPath, "project.json"), "utf-8")
17015
17221
  );
17016
17222
  projectName = projectJson.name || "Unknown";
17017
17223
  } catch {
@@ -17024,12 +17230,32 @@ ${formatFullDiff(diff)}`);
17024
17230
  }) : "N/A";
17025
17231
  console.log("");
17026
17232
  console.log("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E");
17027
- console.log("\u2502 \u{1F4CA} prjct-cli Value Dashboard \u2502");
17233
+ console.log("\u2502 \u{1F4CA} prjct-cli Stats Dashboard \u2502");
17028
17234
  console.log(
17029
17235
  `\u2502 Project: ${projectName.padEnd(20).slice(0, 20)} | Since: ${firstSyncDate.padEnd(12).slice(0, 12)} \u2502`
17030
17236
  );
17031
17237
  console.log("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F");
17032
17238
  console.log("");
17239
+ console.log("\u{1F3AF} TODAY'S ACTIVITY");
17240
+ if (sessionActivity.sessionDuration) {
17241
+ console.log(` Duration: ${sessionActivity.sessionDuration}`);
17242
+ }
17243
+ console.log(` Tasks completed: ${sessionActivity.tasksCompleted}`);
17244
+ console.log(` Features shipped: ${sessionActivity.featuresShipped}`);
17245
+ if (sessionActivity.agentsUsed.length > 0) {
17246
+ const agentStr = sessionActivity.agentsUsed.slice(0, 3).map((a) => `${a.name} (${a.count}\xD7)`).join(", ");
17247
+ console.log(` Agents used: ${agentStr}`);
17248
+ }
17249
+ console.log("");
17250
+ if (patternsSummary.decisions > 0 || patternsSummary.preferences > 0) {
17251
+ console.log("\u{1F9E0} PATTERNS LEARNED");
17252
+ console.log(
17253
+ ` Decisions: ${patternsSummary.learnedDecisions} confirmed (${patternsSummary.decisions} total)`
17254
+ );
17255
+ console.log(` Preferences: ${patternsSummary.preferences} saved`);
17256
+ console.log(` Workflows: ${patternsSummary.workflows} tracked`);
17257
+ console.log("");
17258
+ }
17033
17259
  console.log("\u{1F4B0} TOKEN SAVINGS");
17034
17260
  console.log(` Total saved: ${this._formatTokens(summary.totalTokensSaved)} tokens`);
17035
17261
  console.log(
@@ -17042,7 +17268,7 @@ ${formatFullDiff(diff)}`);
17042
17268
  console.log(` Avg sync time: ${this._formatDuration(summary.avgSyncDuration)}`);
17043
17269
  console.log("");
17044
17270
  if (summary.topAgents.length > 0) {
17045
- console.log("\u{1F916} AGENT USAGE");
17271
+ console.log("\u{1F916} AGENT USAGE (all time)");
17046
17272
  const totalUsage = summary.topAgents.reduce((sum, a) => sum + a.usageCount, 0);
17047
17273
  for (const agent of summary.topAgents) {
17048
17274
  const pct = totalUsage > 0 ? (agent.usageCount / totalUsage * 100).toFixed(0) : 0;
@@ -17071,20 +17297,68 @@ ${formatFullDiff(diff)}`);
17071
17297
  summary,
17072
17298
  dailyStats,
17073
17299
  projectName,
17074
- firstSyncDate
17300
+ firstSyncDate,
17301
+ sessionActivity,
17302
+ patternsSummary
17075
17303
  );
17076
17304
  console.log(markdown);
17077
17305
  return { success: true, data: { markdown } };
17078
17306
  }
17079
17307
  return {
17080
17308
  success: true,
17081
- data: summary
17309
+ data: { ...summary, session: sessionActivity, patterns: patternsSummary }
17082
17310
  };
17083
17311
  } catch (error) {
17084
17312
  console.error("\u274C Error:", error.message);
17085
17313
  return { success: false, error: error.message };
17086
17314
  }
17087
17315
  }
17316
+ /**
17317
+ * Get session activity stats from today's events
17318
+ * @see PRJ-89
17319
+ */
17320
+ async _getSessionActivity(projectId) {
17321
+ try {
17322
+ const recentHistory = await memoryService.getRecentEvents(projectId, 100);
17323
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
17324
+ const todayEvents = recentHistory.filter((e) => {
17325
+ const ts = e.timestamp || e.ts;
17326
+ return ts?.startsWith(today);
17327
+ });
17328
+ let sessionDuration = null;
17329
+ if (todayEvents.length >= 2) {
17330
+ const timestamps = todayEvents.map((e) => new Date(e.timestamp || e.ts).getTime()).filter((t) => !Number.isNaN(t)).sort((a, b) => a - b);
17331
+ if (timestamps.length >= 2) {
17332
+ const durationMs = timestamps[timestamps.length - 1] - timestamps[0];
17333
+ sessionDuration = date_helper_default.formatDuration(durationMs);
17334
+ }
17335
+ }
17336
+ const tasksCompleted = todayEvents.filter((e) => e.action === "task_completed").length;
17337
+ const featuresShipped = todayEvents.filter((e) => e.action === "feature_shipped").length;
17338
+ const agentCounts = /* @__PURE__ */ new Map();
17339
+ for (const event of todayEvents) {
17340
+ if (event.action === "sync" && Array.isArray(event.subagents)) {
17341
+ for (const agent of event.subagents) {
17342
+ agentCounts.set(agent, (agentCounts.get(agent) || 0) + 1);
17343
+ }
17344
+ }
17345
+ }
17346
+ const agentsUsed = Array.from(agentCounts.entries()).map(([name, count]) => ({ name, count })).sort((a, b) => b.count - a.count);
17347
+ return {
17348
+ sessionDuration,
17349
+ tasksCompleted,
17350
+ featuresShipped,
17351
+ agentsUsed
17352
+ };
17353
+ } catch {
17354
+ return {
17355
+ sessionDuration: null,
17356
+ tasksCompleted: 0,
17357
+ featuresShipped: 0,
17358
+ agentsUsed: []
17359
+ };
17360
+ }
17361
+ }
17088
17362
  // =========== Stats Helper Methods ===========
17089
17363
  _formatTokens(tokens) {
17090
17364
  if (tokens >= 1e6) {
@@ -17111,12 +17385,40 @@ ${formatFullDiff(diff)}`);
17111
17385
  return chars[idx];
17112
17386
  }).join("");
17113
17387
  }
17114
- _generateStatsMarkdown(summary, _dailyStats, projectName, firstSyncDate) {
17388
+ _generateStatsMarkdown(summary, _dailyStats, projectName, firstSyncDate, sessionActivity, patternsSummary) {
17115
17389
  const lines = [];
17116
- lines.push(`# ${projectName} - Value Dashboard`);
17390
+ lines.push(`# ${projectName} - Stats Dashboard`);
17117
17391
  lines.push("");
17118
17392
  lines.push(`_Generated: ${(/* @__PURE__ */ new Date()).toLocaleString()} | Tracking since: ${firstSyncDate}_`);
17119
17393
  lines.push("");
17394
+ if (sessionActivity) {
17395
+ lines.push("## \u{1F3AF} Today's Activity");
17396
+ lines.push("");
17397
+ lines.push(`| Metric | Value |`);
17398
+ lines.push(`|--------|-------|`);
17399
+ if (sessionActivity.sessionDuration) {
17400
+ lines.push(`| Duration | ${sessionActivity.sessionDuration} |`);
17401
+ }
17402
+ lines.push(`| Tasks completed | ${sessionActivity.tasksCompleted} |`);
17403
+ lines.push(`| Features shipped | ${sessionActivity.featuresShipped} |`);
17404
+ if (sessionActivity.agentsUsed.length > 0) {
17405
+ const agentStr = sessionActivity.agentsUsed.slice(0, 3).map((a) => `${a.name} (${a.count}\xD7)`).join(", ");
17406
+ lines.push(`| Agents used | ${agentStr} |`);
17407
+ }
17408
+ lines.push("");
17409
+ }
17410
+ if (patternsSummary && (patternsSummary.decisions > 0 || patternsSummary.preferences > 0)) {
17411
+ lines.push("## \u{1F9E0} Patterns Learned");
17412
+ lines.push("");
17413
+ lines.push(`| Type | Count |`);
17414
+ lines.push(`|------|-------|`);
17415
+ lines.push(
17416
+ `| Decisions | ${patternsSummary.learnedDecisions} confirmed (${patternsSummary.decisions} total) |`
17417
+ );
17418
+ lines.push(`| Preferences | ${patternsSummary.preferences} |`);
17419
+ lines.push(`| Workflows | ${patternsSummary.workflows} |`);
17420
+ lines.push("");
17421
+ }
17120
17422
  lines.push("## \u{1F4B0} Token Savings");
17121
17423
  lines.push("");
17122
17424
  lines.push(`| Metric | Value |`);
@@ -24714,7 +25016,7 @@ var require_package = __commonJS({
24714
25016
  "package.json"(exports, module) {
24715
25017
  module.exports = {
24716
25018
  name: "prjct-cli",
24717
- version: "0.53.0",
25019
+ version: "0.54.0",
24718
25020
  description: "Context layer for AI agents. Project context for Claude Code, Gemini CLI, and more.",
24719
25021
  main: "core/index.ts",
24720
25022
  bin: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prjct-cli",
3
- "version": "0.53.0",
3
+ "version": "0.54.0",
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": {