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 +57 -0
- package/core/agentic/memory-system.ts +216 -0
- package/core/commands/analysis.ts +167 -10
- package/core/infrastructure/config-manager.ts +24 -0
- package/core/services/memory-service.ts +21 -0
- package/core/types/config.ts +6 -0
- package/core/types/memory.ts +70 -0
- package/core/utils/output.ts +2 -2
- package/dist/bin/prjct.mjs +316 -14
- package/package.json +1 -1
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 -
|
|
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
|
|
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} -
|
|
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()
|
package/core/types/config.ts
CHANGED
|
@@ -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
|
/**
|
package/core/types/memory.ts
CHANGED
|
@@ -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
|
*/
|
package/core/utils/output.ts
CHANGED
|
@@ -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('│')
|
|
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('│')
|
|
241
|
+
console.log(`${chalk.dim('│')} ${line.padEnd(maxLen)} ${chalk.dim('│')}`)
|
|
242
242
|
}
|
|
243
243
|
console.log(chalk.dim(`└${border}┘`))
|
|
244
244
|
return this
|
package/dist/bin/prjct.mjs
CHANGED
|
@@ -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")
|
|
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")
|
|
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 -
|
|
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
|
|
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
|
|
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} -
|
|
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.
|
|
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: {
|