prjct-cli 0.44.1 → 0.45.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 +39 -0
- package/bin/prjct.ts +14 -0
- package/core/commands/analysis.ts +239 -0
- package/core/commands/command-data.ts +21 -4
- package/core/commands/commands.ts +4 -0
- package/core/commands/register.ts +1 -0
- package/core/context-tools/files-tool.ts +584 -0
- package/core/context-tools/imports-tool.ts +423 -0
- package/core/context-tools/index.ts +458 -0
- package/core/context-tools/recent-tool.ts +313 -0
- package/core/context-tools/signatures-tool.ts +510 -0
- package/core/context-tools/summary-tool.ts +309 -0
- package/core/context-tools/token-counter.ts +279 -0
- package/core/context-tools/types.ts +253 -0
- package/core/index.ts +4 -0
- package/core/schemas/metrics.ts +143 -0
- package/core/services/sync-service.ts +99 -0
- package/core/storage/index.ts +4 -0
- package/core/storage/metrics-storage.ts +315 -0
- package/core/types/index.ts +3 -0
- package/core/types/storage.ts +49 -0
- package/dist/bin/prjct.mjs +5362 -2825
- package/dist/core/infrastructure/command-installer.js +10 -32
- package/dist/core/infrastructure/setup.js +10 -32
- package/package.json +1 -1
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Tools Types
|
|
3
|
+
*
|
|
4
|
+
* Shared interfaces for all context filtering tools.
|
|
5
|
+
* These tools are designed for AI agents to efficiently explore
|
|
6
|
+
* codebases WITHOUT consuming tokens for filtering.
|
|
7
|
+
*
|
|
8
|
+
* @module context-tools/types
|
|
9
|
+
* @version 1.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// =============================================================================
|
|
13
|
+
// Token Measurement Types
|
|
14
|
+
// =============================================================================
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Cost savings breakdown by model
|
|
18
|
+
*/
|
|
19
|
+
export interface CostBreakdown {
|
|
20
|
+
model: string
|
|
21
|
+
inputSaved: number // $ saved on input tokens
|
|
22
|
+
outputPotential: number // $ potential savings on output (estimated)
|
|
23
|
+
total: number // Combined savings
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Token measurement result
|
|
28
|
+
*/
|
|
29
|
+
export interface TokenMetrics {
|
|
30
|
+
tokens: {
|
|
31
|
+
original: number
|
|
32
|
+
filtered: number
|
|
33
|
+
saved: number
|
|
34
|
+
}
|
|
35
|
+
compression: number // 0-1 (e.g., 0.90 = 90% reduction)
|
|
36
|
+
cost: {
|
|
37
|
+
saved: number // $ saved (using default model)
|
|
38
|
+
formatted: string // Human-readable (e.g., "$0.02")
|
|
39
|
+
byModel: CostBreakdown[] // Breakdown by popular models
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// =============================================================================
|
|
44
|
+
// Files Tool Types
|
|
45
|
+
// =============================================================================
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Relevance score reasons
|
|
49
|
+
*/
|
|
50
|
+
export type ScoreReason =
|
|
51
|
+
| `keyword:${string}` // Matched keyword in path
|
|
52
|
+
| `domain:${string}` // Matched domain pattern
|
|
53
|
+
| `recent:${string}` // Recently modified (e.g., "3d" = 3 days)
|
|
54
|
+
| `import:${number}` // Import distance from entry point
|
|
55
|
+
| `extension:${string}` // File extension match
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* File with relevance score
|
|
59
|
+
*/
|
|
60
|
+
export interface ScoredFile {
|
|
61
|
+
path: string
|
|
62
|
+
score: number // 0-1
|
|
63
|
+
reasons: ScoreReason[]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Files tool output
|
|
68
|
+
*/
|
|
69
|
+
export interface FilesToolOutput {
|
|
70
|
+
files: ScoredFile[]
|
|
71
|
+
metrics: {
|
|
72
|
+
filesScanned: number
|
|
73
|
+
filesReturned: number
|
|
74
|
+
scanDuration: number // ms
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// =============================================================================
|
|
79
|
+
// Signatures Tool Types
|
|
80
|
+
// =============================================================================
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Code signature types
|
|
84
|
+
*/
|
|
85
|
+
export type SignatureType =
|
|
86
|
+
| 'function'
|
|
87
|
+
| 'method'
|
|
88
|
+
| 'class'
|
|
89
|
+
| 'interface'
|
|
90
|
+
| 'type'
|
|
91
|
+
| 'enum'
|
|
92
|
+
| 'const'
|
|
93
|
+
| 'variable'
|
|
94
|
+
| 'export'
|
|
95
|
+
| 'import'
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Extracted code signature
|
|
99
|
+
*/
|
|
100
|
+
export interface CodeSignature {
|
|
101
|
+
type: SignatureType
|
|
102
|
+
name: string
|
|
103
|
+
signature: string // Full signature string (e.g., "(token: string) => Promise<User>")
|
|
104
|
+
exported: boolean
|
|
105
|
+
line: number
|
|
106
|
+
docstring?: string
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Signatures tool output
|
|
111
|
+
*/
|
|
112
|
+
export interface SignaturesToolOutput {
|
|
113
|
+
file: string
|
|
114
|
+
language: string
|
|
115
|
+
signatures: CodeSignature[]
|
|
116
|
+
fallback: boolean // True if full file was returned (no grammar)
|
|
117
|
+
fallbackReason?: string
|
|
118
|
+
metrics: TokenMetrics
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// =============================================================================
|
|
122
|
+
// Imports Tool Types
|
|
123
|
+
// =============================================================================
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Import relationship
|
|
127
|
+
*/
|
|
128
|
+
export interface ImportRelation {
|
|
129
|
+
source: string // Import path (e.g., "./types", "lodash")
|
|
130
|
+
resolved: string | null // Resolved file path (null for external)
|
|
131
|
+
isExternal: boolean
|
|
132
|
+
importedNames?: string[] // Named imports
|
|
133
|
+
isDefault?: boolean
|
|
134
|
+
isNamespace?: boolean // import * as X
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* File that imports the target
|
|
139
|
+
*/
|
|
140
|
+
export interface ImportedBy {
|
|
141
|
+
file: string
|
|
142
|
+
importedNames?: string[]
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Dependency tree node
|
|
147
|
+
*/
|
|
148
|
+
export interface DependencyNode {
|
|
149
|
+
file: string
|
|
150
|
+
imports: DependencyNode[]
|
|
151
|
+
depth: number
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Imports tool output
|
|
156
|
+
*/
|
|
157
|
+
export interface ImportsToolOutput {
|
|
158
|
+
file: string
|
|
159
|
+
imports: ImportRelation[]
|
|
160
|
+
importedBy: ImportedBy[]
|
|
161
|
+
dependencyTree?: DependencyNode
|
|
162
|
+
metrics: {
|
|
163
|
+
totalImports: number
|
|
164
|
+
externalImports: number
|
|
165
|
+
internalImports: number
|
|
166
|
+
importedByCount: number
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// =============================================================================
|
|
171
|
+
// Recent Tool Types
|
|
172
|
+
// =============================================================================
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Hot file from git analysis
|
|
176
|
+
*/
|
|
177
|
+
export interface HotFile {
|
|
178
|
+
path: string
|
|
179
|
+
changes: number // Number of commits touching this file
|
|
180
|
+
heatScore: number // 0-1 normalized score
|
|
181
|
+
lastChanged: string // Human-readable (e.g., "2h ago", "3d ago")
|
|
182
|
+
lastChangedAt: string // ISO timestamp
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Recent tool output
|
|
187
|
+
*/
|
|
188
|
+
export interface RecentToolOutput {
|
|
189
|
+
hotFiles: HotFile[]
|
|
190
|
+
branchOnlyFiles: string[] // Files only changed in current branch
|
|
191
|
+
metrics: {
|
|
192
|
+
commitsAnalyzed: number
|
|
193
|
+
totalFilesChanged: number
|
|
194
|
+
filesReturned: number
|
|
195
|
+
analysisWindow: string // e.g., "30 commits", "main..HEAD"
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// =============================================================================
|
|
200
|
+
// Summary Tool Types
|
|
201
|
+
// =============================================================================
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Public API entry
|
|
205
|
+
*/
|
|
206
|
+
export interface PublicAPIEntry {
|
|
207
|
+
name: string
|
|
208
|
+
type: SignatureType
|
|
209
|
+
signature: string
|
|
210
|
+
description?: string // From JSDoc/docstring
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Summary tool output
|
|
215
|
+
*/
|
|
216
|
+
export interface SummaryToolOutput {
|
|
217
|
+
file: string
|
|
218
|
+
purpose: string // Short description of file purpose
|
|
219
|
+
publicAPI: PublicAPIEntry[]
|
|
220
|
+
dependencies: string[] // Key dependencies
|
|
221
|
+
metrics: TokenMetrics
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// =============================================================================
|
|
225
|
+
// Context Tool Usage Tracking
|
|
226
|
+
// =============================================================================
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Tool usage record for metrics
|
|
230
|
+
*/
|
|
231
|
+
export interface ContextToolUsage {
|
|
232
|
+
tool: 'files' | 'signatures' | 'imports' | 'recent' | 'summary'
|
|
233
|
+
timestamp: string
|
|
234
|
+
inputArgs: string
|
|
235
|
+
tokensSaved: number
|
|
236
|
+
compressionRate: number
|
|
237
|
+
duration: number // ms
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// =============================================================================
|
|
241
|
+
// Main Tool Result Type
|
|
242
|
+
// =============================================================================
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Union type for all tool outputs
|
|
246
|
+
*/
|
|
247
|
+
export type ContextToolOutput =
|
|
248
|
+
| { tool: 'files'; result: FilesToolOutput }
|
|
249
|
+
| { tool: 'signatures'; result: SignaturesToolOutput }
|
|
250
|
+
| { tool: 'imports'; result: ImportsToolOutput }
|
|
251
|
+
| { tool: 'recent'; result: RecentToolOutput }
|
|
252
|
+
| { tool: 'summary'; result: SummaryToolOutput }
|
|
253
|
+
| { tool: 'error'; result: { error: string; code: string } }
|
package/core/index.ts
CHANGED
|
@@ -111,6 +111,10 @@ async function main(): Promise<void> {
|
|
|
111
111
|
ship: (p) => commands.ship(p),
|
|
112
112
|
// Analytics
|
|
113
113
|
dash: (p) => commands.dash(p || 'default'),
|
|
114
|
+
stats: () => commands.stats(process.cwd(), {
|
|
115
|
+
json: options.json === true,
|
|
116
|
+
export: options.export === true,
|
|
117
|
+
}),
|
|
114
118
|
help: (p) => commands.help(p || ''),
|
|
115
119
|
// Maintenance
|
|
116
120
|
recover: () => commands.recover(),
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metrics Schema
|
|
3
|
+
*
|
|
4
|
+
* Defines the structure for metrics.json - value dashboard metrics.
|
|
5
|
+
* Tracks token savings, sync performance, and usage trends.
|
|
6
|
+
*
|
|
7
|
+
* Uses Zod for runtime validation and TypeScript type inference.
|
|
8
|
+
* @version 1.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { z } from 'zod'
|
|
12
|
+
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// Zod Schemas - Source of Truth
|
|
15
|
+
// =============================================================================
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Daily stats for trend analysis
|
|
19
|
+
*/
|
|
20
|
+
export const DailyStatsSchema = z.object({
|
|
21
|
+
date: z.string(), // YYYY-MM-DD
|
|
22
|
+
tokensSaved: z.number(), // Tokens saved that day
|
|
23
|
+
syncs: z.number(), // Number of syncs
|
|
24
|
+
avgCompressionRate: z.number(), // Average compression rate (0-1)
|
|
25
|
+
totalDuration: z.number(), // Total sync time in ms
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Agent usage tracking
|
|
30
|
+
*/
|
|
31
|
+
export const AgentUsageSchema = z.object({
|
|
32
|
+
agentName: z.string(), // e.g., "backend", "frontend"
|
|
33
|
+
usageCount: z.number(), // Times invoked
|
|
34
|
+
tokensSaved: z.number(), // Tokens saved by this agent
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Main metrics JSON structure
|
|
39
|
+
*/
|
|
40
|
+
export const MetricsJsonSchema = z.object({
|
|
41
|
+
// Token metrics
|
|
42
|
+
totalTokensSaved: z.number(),
|
|
43
|
+
avgCompressionRate: z.number(), // 0-1 (e.g., 0.63 = 63% reduction)
|
|
44
|
+
|
|
45
|
+
// Sync metrics
|
|
46
|
+
syncCount: z.number(),
|
|
47
|
+
watchTriggers: z.number(), // Auto-syncs from watch mode
|
|
48
|
+
avgSyncDuration: z.number(), // Average in ms
|
|
49
|
+
totalSyncDuration: z.number(), // Total in ms
|
|
50
|
+
|
|
51
|
+
// Agent usage
|
|
52
|
+
agentUsage: z.array(AgentUsageSchema),
|
|
53
|
+
|
|
54
|
+
// Time series for trends
|
|
55
|
+
dailyStats: z.array(DailyStatsSchema),
|
|
56
|
+
|
|
57
|
+
// Metadata
|
|
58
|
+
firstSync: z.string(), // ISO8601 - when tracking started
|
|
59
|
+
lastUpdated: z.string(), // ISO8601
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// =============================================================================
|
|
63
|
+
// Inferred Types
|
|
64
|
+
// =============================================================================
|
|
65
|
+
|
|
66
|
+
export type DailyStats = z.infer<typeof DailyStatsSchema>
|
|
67
|
+
export type AgentUsage = z.infer<typeof AgentUsageSchema>
|
|
68
|
+
export type MetricsJson = z.infer<typeof MetricsJsonSchema>
|
|
69
|
+
|
|
70
|
+
// =============================================================================
|
|
71
|
+
// Validation Helpers
|
|
72
|
+
// =============================================================================
|
|
73
|
+
|
|
74
|
+
/** Parse and validate metrics.json content */
|
|
75
|
+
export const parseMetrics = (data: unknown): MetricsJson => MetricsJsonSchema.parse(data)
|
|
76
|
+
|
|
77
|
+
/** Safe parse with error result */
|
|
78
|
+
export const safeParseMetrics = (data: unknown) => MetricsJsonSchema.safeParse(data)
|
|
79
|
+
|
|
80
|
+
// =============================================================================
|
|
81
|
+
// Defaults
|
|
82
|
+
// =============================================================================
|
|
83
|
+
|
|
84
|
+
export const DEFAULT_METRICS: MetricsJson = {
|
|
85
|
+
totalTokensSaved: 0,
|
|
86
|
+
avgCompressionRate: 0,
|
|
87
|
+
syncCount: 0,
|
|
88
|
+
watchTriggers: 0,
|
|
89
|
+
avgSyncDuration: 0,
|
|
90
|
+
totalSyncDuration: 0,
|
|
91
|
+
agentUsage: [],
|
|
92
|
+
dailyStats: [],
|
|
93
|
+
firstSync: '',
|
|
94
|
+
lastUpdated: '',
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// =============================================================================
|
|
98
|
+
// Cost Calculation Constants (January 2026 Pricing)
|
|
99
|
+
// =============================================================================
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Token costs per 1000 tokens (INPUT pricing)
|
|
103
|
+
* Source: https://docs.anthropic.com/en/docs/about-claude/models
|
|
104
|
+
*
|
|
105
|
+
* Used for estimating cost savings from context compression
|
|
106
|
+
*/
|
|
107
|
+
export const TOKEN_COSTS = {
|
|
108
|
+
// Current models (2026)
|
|
109
|
+
'claude-opus-4.5': 0.005, // $5/M input - flagship
|
|
110
|
+
'claude-sonnet-4.5': 0.003, // $3/M input - balanced
|
|
111
|
+
'claude-haiku-4.5': 0.001, // $1/M input - fastest
|
|
112
|
+
// Legacy models
|
|
113
|
+
'claude-opus-4': 0.015, // $15/M input
|
|
114
|
+
'claude-sonnet-4': 0.003, // $3/M input
|
|
115
|
+
'claude-3-opus': 0.015, // $15/M input (deprecated)
|
|
116
|
+
'claude-3-sonnet': 0.003, // $3/M input (deprecated)
|
|
117
|
+
// Other providers
|
|
118
|
+
'gpt-4o': 0.0025, // $2.50/M input
|
|
119
|
+
'gpt-4': 0.01, // $10/M input (legacy)
|
|
120
|
+
'gemini-pro': 0.00125, // $1.25/M input
|
|
121
|
+
// Default: Claude Sonnet (most common for Claude Code)
|
|
122
|
+
default: 0.003, // $3/M input
|
|
123
|
+
} as const
|
|
124
|
+
|
|
125
|
+
export type ModelName = keyof typeof TOKEN_COSTS
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Calculate estimated cost saved based on tokens
|
|
129
|
+
*/
|
|
130
|
+
export function estimateCostSaved(tokens: number, model: ModelName = 'default'): number {
|
|
131
|
+
const costPer1k = TOKEN_COSTS[model] || TOKEN_COSTS.default
|
|
132
|
+
return (tokens / 1000) * costPer1k
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Format cost as currency string
|
|
137
|
+
*/
|
|
138
|
+
export function formatCost(cost: number): string {
|
|
139
|
+
if (cost < 0.01) {
|
|
140
|
+
return `$${(cost * 100).toFixed(2)}¢`
|
|
141
|
+
}
|
|
142
|
+
return `$${cost.toFixed(2)}`
|
|
143
|
+
}
|
|
@@ -22,6 +22,7 @@ import { promisify } from 'util'
|
|
|
22
22
|
import pathManager from '../infrastructure/path-manager'
|
|
23
23
|
import configManager from '../infrastructure/config-manager'
|
|
24
24
|
import dateHelper from '../utils/date-helper'
|
|
25
|
+
import { metricsStorage } from '../storage/metrics-storage'
|
|
25
26
|
import {
|
|
26
27
|
generateAIToolContexts,
|
|
27
28
|
DEFAULT_AI_TOOLS,
|
|
@@ -91,6 +92,13 @@ interface AIToolResult {
|
|
|
91
92
|
success: boolean
|
|
92
93
|
}
|
|
93
94
|
|
|
95
|
+
interface SyncMetrics {
|
|
96
|
+
duration: number // Sync duration in ms
|
|
97
|
+
originalSize: number // Estimated tokens before compression
|
|
98
|
+
filteredSize: number // Actual tokens in context files
|
|
99
|
+
compressionRate: number // Percentage saved
|
|
100
|
+
}
|
|
101
|
+
|
|
94
102
|
interface SyncResult {
|
|
95
103
|
success: boolean
|
|
96
104
|
projectId: string
|
|
@@ -103,6 +111,7 @@ interface SyncResult {
|
|
|
103
111
|
skills: { agent: string; skill: string }[]
|
|
104
112
|
contextFiles: string[]
|
|
105
113
|
aiTools: AIToolResult[]
|
|
114
|
+
syncMetrics?: SyncMetrics
|
|
106
115
|
error?: string
|
|
107
116
|
}
|
|
108
117
|
|
|
@@ -129,6 +138,7 @@ class SyncService {
|
|
|
129
138
|
*/
|
|
130
139
|
async sync(projectPath: string = process.cwd(), options: SyncOptions = {}): Promise<SyncResult> {
|
|
131
140
|
this.projectPath = projectPath
|
|
141
|
+
const startTime = Date.now()
|
|
132
142
|
|
|
133
143
|
// Resolve AI tools: supports 'auto', 'all', or specific list
|
|
134
144
|
let aiToolIds: string[]
|
|
@@ -217,6 +227,15 @@ class SyncService {
|
|
|
217
227
|
// 8. Log to memory
|
|
218
228
|
await this.logToMemory(git, stats)
|
|
219
229
|
|
|
230
|
+
// 9. Record metrics for value dashboard
|
|
231
|
+
const duration = Date.now() - startTime
|
|
232
|
+
const syncMetrics = await this.recordSyncMetrics(
|
|
233
|
+
stats,
|
|
234
|
+
contextFiles,
|
|
235
|
+
agents,
|
|
236
|
+
duration
|
|
237
|
+
)
|
|
238
|
+
|
|
220
239
|
return {
|
|
221
240
|
success: true,
|
|
222
241
|
projectId: this.projectId,
|
|
@@ -233,6 +252,7 @@ class SyncService {
|
|
|
233
252
|
outputFile: r.outputFile,
|
|
234
253
|
success: r.success,
|
|
235
254
|
})),
|
|
255
|
+
syncMetrics,
|
|
236
256
|
}
|
|
237
257
|
} catch (error) {
|
|
238
258
|
return {
|
|
@@ -1067,6 +1087,85 @@ ${
|
|
|
1067
1087
|
await fs.appendFile(memoryPath, JSON.stringify(event) + '\n', 'utf-8')
|
|
1068
1088
|
}
|
|
1069
1089
|
|
|
1090
|
+
// ==========================================================================
|
|
1091
|
+
// METRICS RECORDING
|
|
1092
|
+
// ==========================================================================
|
|
1093
|
+
|
|
1094
|
+
/**
|
|
1095
|
+
* Record sync metrics for the value dashboard
|
|
1096
|
+
*
|
|
1097
|
+
* Calculates token savings by comparing:
|
|
1098
|
+
* - Original: Estimated tokens if we sent all source files
|
|
1099
|
+
* - Filtered: Actual tokens in generated context files
|
|
1100
|
+
*
|
|
1101
|
+
* Token estimation: ~4 chars per token (industry standard)
|
|
1102
|
+
*/
|
|
1103
|
+
private async recordSyncMetrics(
|
|
1104
|
+
stats: ProjectStats,
|
|
1105
|
+
contextFiles: string[],
|
|
1106
|
+
agents: AgentInfo[],
|
|
1107
|
+
duration: number
|
|
1108
|
+
): Promise<SyncMetrics> {
|
|
1109
|
+
const CHARS_PER_TOKEN = 4
|
|
1110
|
+
|
|
1111
|
+
// Calculate filtered size (actual context files generated)
|
|
1112
|
+
let filteredChars = 0
|
|
1113
|
+
for (const file of contextFiles) {
|
|
1114
|
+
try {
|
|
1115
|
+
const filePath = path.join(this.globalPath, file)
|
|
1116
|
+
const content = await fs.readFile(filePath, 'utf-8')
|
|
1117
|
+
filteredChars += content.length
|
|
1118
|
+
} catch {
|
|
1119
|
+
// File might not exist, skip
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
// Also count agent files
|
|
1124
|
+
for (const agent of agents) {
|
|
1125
|
+
try {
|
|
1126
|
+
const agentPath = path.join(this.globalPath, 'agents', `${agent.name}.md`)
|
|
1127
|
+
const content = await fs.readFile(agentPath, 'utf-8')
|
|
1128
|
+
filteredChars += content.length
|
|
1129
|
+
} catch {
|
|
1130
|
+
// Skip if not found
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
const filteredSize = Math.floor(filteredChars / CHARS_PER_TOKEN)
|
|
1135
|
+
|
|
1136
|
+
// Estimate original size (what it would take without prjct)
|
|
1137
|
+
// Conservative estimate: avg 500 tokens per source file
|
|
1138
|
+
// Plus overhead for manually creating context
|
|
1139
|
+
const avgTokensPerFile = 500
|
|
1140
|
+
const originalSize = stats.fileCount * avgTokensPerFile
|
|
1141
|
+
|
|
1142
|
+
// Calculate compression rate
|
|
1143
|
+
const compressionRate = originalSize > 0
|
|
1144
|
+
? Math.max(0, (originalSize - filteredSize) / originalSize)
|
|
1145
|
+
: 0
|
|
1146
|
+
|
|
1147
|
+
// Record to storage
|
|
1148
|
+
try {
|
|
1149
|
+
await metricsStorage.recordSync(this.projectId!, {
|
|
1150
|
+
originalSize,
|
|
1151
|
+
filteredSize,
|
|
1152
|
+
duration,
|
|
1153
|
+
isWatch: false,
|
|
1154
|
+
agents: agents.filter(a => a.type === 'domain').map(a => a.name),
|
|
1155
|
+
})
|
|
1156
|
+
} catch (error) {
|
|
1157
|
+
// Non-blocking - metrics are nice to have
|
|
1158
|
+
console.error('Warning: Failed to record metrics:', (error as Error).message)
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
return {
|
|
1162
|
+
duration,
|
|
1163
|
+
originalSize,
|
|
1164
|
+
filteredSize,
|
|
1165
|
+
compressionRate,
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1070
1169
|
// ==========================================================================
|
|
1071
1170
|
// HELPERS
|
|
1072
1171
|
// ==========================================================================
|
package/core/storage/index.ts
CHANGED
|
@@ -40,6 +40,7 @@ export { stateStorage } from './state-storage'
|
|
|
40
40
|
export { queueStorage } from './queue-storage'
|
|
41
41
|
export { ideasStorage } from './ideas-storage'
|
|
42
42
|
export { shippedStorage } from './shipped-storage'
|
|
43
|
+
export { metricsStorage } from './metrics-storage'
|
|
43
44
|
|
|
44
45
|
// ========== GRANULAR STORAGE (Legacy) ==========
|
|
45
46
|
export { getStorage, default } from './storage'
|
|
@@ -53,4 +54,7 @@ export type {
|
|
|
53
54
|
IdeaPriority,
|
|
54
55
|
ShippedFeature,
|
|
55
56
|
ShippedJson,
|
|
57
|
+
DailyStats,
|
|
58
|
+
AgentUsage,
|
|
59
|
+
MetricsJson,
|
|
56
60
|
} from '../types'
|