@rlabs-inc/memory 0.3.5 → 0.3.6
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/README.md +123 -30
- package/dist/index.js +803 -179
- package/dist/index.mjs +803 -179
- package/dist/server/index.js +36774 -2643
- package/dist/server/index.mjs +1034 -185
- package/package.json +3 -2
- package/skills/memory-management.md +686 -0
- package/src/cli/commands/migrate.ts +423 -0
- package/src/cli/commands/serve.ts +88 -0
- package/src/cli/index.ts +21 -0
- package/src/core/curator.ts +151 -17
- package/src/core/engine.ts +159 -11
- package/src/core/manager.ts +484 -0
- package/src/core/retrieval.ts +547 -420
- package/src/core/store.ts +383 -8
- package/src/server/index.ts +108 -8
- package/src/types/memory.ts +142 -0
- package/src/types/schema.ts +80 -7
- package/src/utils/logger.ts +310 -46
package/src/utils/logger.ts
CHANGED
|
@@ -41,10 +41,29 @@ const sym = {
|
|
|
41
41
|
target: '🎯',
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Logger configuration
|
|
46
|
+
*/
|
|
47
|
+
let _verbose = false
|
|
48
|
+
|
|
44
49
|
/**
|
|
45
50
|
* Logger with beautiful styled output
|
|
46
51
|
*/
|
|
47
52
|
export const logger = {
|
|
53
|
+
/**
|
|
54
|
+
* Set verbose mode
|
|
55
|
+
*/
|
|
56
|
+
setVerbose(enabled: boolean) {
|
|
57
|
+
_verbose = enabled
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Check if verbose mode is enabled
|
|
62
|
+
*/
|
|
63
|
+
isVerbose(): boolean {
|
|
64
|
+
return _verbose
|
|
65
|
+
},
|
|
66
|
+
|
|
48
67
|
/**
|
|
49
68
|
* Info message
|
|
50
69
|
*/
|
|
@@ -159,11 +178,11 @@ export const logger = {
|
|
|
159
178
|
},
|
|
160
179
|
|
|
161
180
|
/**
|
|
162
|
-
* Log retrieved memories
|
|
181
|
+
* Log retrieved memories (Activation Signal Algorithm)
|
|
163
182
|
*/
|
|
164
183
|
logRetrievedMemories(memories: Array<{
|
|
165
184
|
content: string
|
|
166
|
-
score: number
|
|
185
|
+
score: number // signals.count / 6
|
|
167
186
|
context_type: string
|
|
168
187
|
}>, query: string) {
|
|
169
188
|
const queryPreview = query.length > 40
|
|
@@ -176,7 +195,9 @@ export const logger = {
|
|
|
176
195
|
technical_state: '📍', unresolved: '❓', preference: '⚙️', workflow: '🔄',
|
|
177
196
|
architectural: '🏗️', debugging: '🐛', philosophy: '🌀', todo: '🎯',
|
|
178
197
|
implementation: '⚡', problem_solution: '✅', project_context: '📦',
|
|
179
|
-
milestone: '🏆', general: '📝',
|
|
198
|
+
milestone: '🏆', general: '📝', project_state: '📍', pending_task: '⏳',
|
|
199
|
+
work_in_progress: '🔨', system_feedback: '📣', project_milestone: '🏆',
|
|
200
|
+
architectural_insight: '🏛️', architectural_direction: '🧭',
|
|
180
201
|
}
|
|
181
202
|
|
|
182
203
|
console.log()
|
|
@@ -191,7 +212,9 @@ export const logger = {
|
|
|
191
212
|
}
|
|
192
213
|
|
|
193
214
|
memories.forEach((m, i) => {
|
|
194
|
-
|
|
215
|
+
// Convert score back to signal count (score is count/6)
|
|
216
|
+
const signalCount = Math.round(m.score * 6)
|
|
217
|
+
const signalStr = style('green', `${signalCount}sig`)
|
|
195
218
|
const emoji = emojiMap[m.context_type?.toLowerCase()] ?? '📝'
|
|
196
219
|
const num = style('dim', `${i + 1}.`)
|
|
197
220
|
|
|
@@ -199,7 +222,7 @@ export const logger = {
|
|
|
199
222
|
? m.content.slice(0, 55) + style('dim', '...')
|
|
200
223
|
: m.content
|
|
201
224
|
|
|
202
|
-
console.log(` ${num} [${
|
|
225
|
+
console.log(` ${num} [${signalStr}] ${emoji}`)
|
|
203
226
|
console.log(` ${preview}`)
|
|
204
227
|
})
|
|
205
228
|
console.log()
|
|
@@ -265,53 +288,211 @@ export const logger = {
|
|
|
265
288
|
},
|
|
266
289
|
|
|
267
290
|
/**
|
|
268
|
-
* Log
|
|
291
|
+
* Log management agent starting
|
|
292
|
+
*/
|
|
293
|
+
logManagementStart(memoriesCount: number) {
|
|
294
|
+
console.log(`${timestamp()} ${style('blue', '🔧')} ${style('bold', 'MANAGEMENT AGENT')}`)
|
|
295
|
+
console.log(` ${style('dim', 'processing:')} ${memoriesCount} new memories`)
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Log management agent results
|
|
300
|
+
* In verbose mode: shows all details beautifully formatted
|
|
301
|
+
* In normal mode: shows compact summary
|
|
302
|
+
*/
|
|
303
|
+
logManagementComplete(result: {
|
|
304
|
+
success: boolean
|
|
305
|
+
superseded?: number
|
|
306
|
+
resolved?: number
|
|
307
|
+
linked?: number
|
|
308
|
+
filesRead?: number
|
|
309
|
+
filesWritten?: number
|
|
310
|
+
primerUpdated?: boolean
|
|
311
|
+
actions?: string[]
|
|
312
|
+
summary?: string
|
|
313
|
+
fullReport?: string
|
|
314
|
+
error?: string
|
|
315
|
+
}) {
|
|
316
|
+
// Helper to format action with icon
|
|
317
|
+
const formatAction = (action: string, truncate = true): string => {
|
|
318
|
+
let icon = ' •'
|
|
319
|
+
if (action.startsWith('READ OK')) icon = style('dim', ' 📖')
|
|
320
|
+
else if (action.startsWith('READ FAILED')) icon = style('red', ' ❌')
|
|
321
|
+
else if (action.startsWith('WRITE OK')) icon = style('green', ' ✏️')
|
|
322
|
+
else if (action.startsWith('WRITE FAILED')) icon = style('red', ' ❌')
|
|
323
|
+
else if (action.startsWith('RECEIVED')) icon = style('cyan', ' 📥')
|
|
324
|
+
else if (action.startsWith('CREATED')) icon = style('green', ' ✨')
|
|
325
|
+
else if (action.startsWith('UPDATED')) icon = style('blue', ' 📝')
|
|
326
|
+
else if (action.startsWith('SUPERSEDED')) icon = style('yellow', ' 🔄')
|
|
327
|
+
else if (action.startsWith('RESOLVED')) icon = style('green', ' ✅')
|
|
328
|
+
else if (action.startsWith('LINKED')) icon = style('cyan', ' 🔗')
|
|
329
|
+
else if (action.startsWith('PRIMER')) icon = style('magenta', ' 💜')
|
|
330
|
+
else if (action.startsWith('SKIPPED')) icon = style('dim', ' ⏭️')
|
|
331
|
+
else if (action.startsWith('NO_ACTION')) icon = style('dim', ' ◦')
|
|
332
|
+
|
|
333
|
+
const text = truncate && action.length > 70 ? action.slice(0, 67) + '...' : action
|
|
334
|
+
return `${icon} ${style('dim', text)}`
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (result.success) {
|
|
338
|
+
console.log(` ${style('green', sym.check)} ${style('bold', 'Completed')}`)
|
|
339
|
+
|
|
340
|
+
if (_verbose) {
|
|
341
|
+
// ═══════════════════════════════════════════════════════════════
|
|
342
|
+
// VERBOSE MODE: Show everything beautifully formatted
|
|
343
|
+
// ═══════════════════════════════════════════════════════════════
|
|
344
|
+
|
|
345
|
+
// File I/O stats section
|
|
346
|
+
console.log(` ${style('dim', '─'.repeat(50))}`)
|
|
347
|
+
console.log(` ${style('cyan', '📊')} ${style('bold', 'Statistics')}`)
|
|
348
|
+
|
|
349
|
+
const filesRead = result.filesRead ?? 0
|
|
350
|
+
const filesWritten = result.filesWritten ?? 0
|
|
351
|
+
console.log(` ${style('dim', 'Files read:')} ${filesRead > 0 ? style('green', String(filesRead)) : style('dim', '0')}`)
|
|
352
|
+
console.log(` ${style('dim', 'Files written:')} ${filesWritten > 0 ? style('green', String(filesWritten)) : style('dim', '0')}`)
|
|
353
|
+
|
|
354
|
+
// Memory changes section
|
|
355
|
+
const superseded = result.superseded ?? 0
|
|
356
|
+
const resolved = result.resolved ?? 0
|
|
357
|
+
const linked = result.linked ?? 0
|
|
358
|
+
console.log(` ${style('dim', 'Superseded:')} ${superseded > 0 ? style('yellow', String(superseded)) : style('dim', '0')}`)
|
|
359
|
+
console.log(` ${style('dim', 'Resolved:')} ${resolved > 0 ? style('green', String(resolved)) : style('dim', '0')}`)
|
|
360
|
+
console.log(` ${style('dim', 'Linked:')} ${linked > 0 ? style('cyan', String(linked)) : style('dim', '0')}`)
|
|
361
|
+
console.log(` ${style('dim', 'Primer:')} ${result.primerUpdated ? style('magenta', 'updated') : style('dim', 'unchanged')}`)
|
|
362
|
+
|
|
363
|
+
// Actions section - show ALL actions in verbose mode
|
|
364
|
+
if (result.actions && result.actions.length > 0) {
|
|
365
|
+
console.log(` ${style('dim', '─'.repeat(50))}`)
|
|
366
|
+
console.log(` ${style('cyan', '🎬')} ${style('bold', 'Actions')} ${style('dim', `(${result.actions.length} total)`)}`)
|
|
367
|
+
for (const action of result.actions) {
|
|
368
|
+
console.log(` ${formatAction(action, false)}`) // No truncation in verbose
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Full report section
|
|
373
|
+
if (result.fullReport) {
|
|
374
|
+
console.log(` ${style('dim', '─'.repeat(50))}`)
|
|
375
|
+
console.log(` ${style('cyan', '📋')} ${style('bold', 'Full Report')}`)
|
|
376
|
+
const reportLines = result.fullReport.split('\n')
|
|
377
|
+
for (const line of reportLines) {
|
|
378
|
+
// Highlight section headers
|
|
379
|
+
if (line.includes('===')) {
|
|
380
|
+
console.log(` ${style('bold', line)}`)
|
|
381
|
+
} else if (line.match(/^[A-Z_]+:/)) {
|
|
382
|
+
// Highlight stat lines like "memories_processed: 5"
|
|
383
|
+
console.log(` ${style('cyan', line)}`)
|
|
384
|
+
} else {
|
|
385
|
+
console.log(` ${style('dim', line)}`)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
console.log(` ${style('dim', '─'.repeat(50))}`)
|
|
391
|
+
|
|
392
|
+
} else {
|
|
393
|
+
// ═══════════════════════════════════════════════════════════════
|
|
394
|
+
// NORMAL MODE: Compact summary
|
|
395
|
+
// ═══════════════════════════════════════════════════════════════
|
|
396
|
+
|
|
397
|
+
// Memory change stats (one line)
|
|
398
|
+
const stats: string[] = []
|
|
399
|
+
if (result.superseded && result.superseded > 0) stats.push(`${result.superseded} superseded`)
|
|
400
|
+
if (result.resolved && result.resolved > 0) stats.push(`${result.resolved} resolved`)
|
|
401
|
+
if (result.linked && result.linked > 0) stats.push(`${result.linked} linked`)
|
|
402
|
+
if (result.primerUpdated) stats.push('primer updated')
|
|
403
|
+
|
|
404
|
+
if (stats.length > 0) {
|
|
405
|
+
console.log(` ${style('dim', 'changes:')} ${stats.join(style('dim', ', '))}`)
|
|
406
|
+
} else {
|
|
407
|
+
console.log(` ${style('dim', 'changes:')} none (memories are current)`)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Show limited actions
|
|
411
|
+
if (result.actions && result.actions.length > 0) {
|
|
412
|
+
console.log(` ${style('dim', 'actions:')}`)
|
|
413
|
+
for (const action of result.actions.slice(0, 10)) {
|
|
414
|
+
console.log(` ${formatAction(action, true)}`)
|
|
415
|
+
}
|
|
416
|
+
if (result.actions.length > 10) {
|
|
417
|
+
console.log(` ${style('dim', ` ... and ${result.actions.length - 10} more actions`)}`)
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
} else {
|
|
423
|
+
// ═══════════════════════════════════════════════════════════════
|
|
424
|
+
// ERROR: Always show error details
|
|
425
|
+
// ═══════════════════════════════════════════════════════════════
|
|
426
|
+
console.log(` ${style('yellow', sym.warning)} ${style('bold', 'Failed')}`)
|
|
427
|
+
if (result.error) {
|
|
428
|
+
console.log(` ${style('red', 'error:')} ${result.error}`)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Show full report on error (always, for debugging)
|
|
432
|
+
if (result.fullReport) {
|
|
433
|
+
console.log(` ${style('dim', '─'.repeat(50))}`)
|
|
434
|
+
console.log(` ${style('red', '📋')} ${style('bold', 'Error Report:')}`)
|
|
435
|
+
const reportLines = result.fullReport.split('\n')
|
|
436
|
+
for (const line of reportLines) {
|
|
437
|
+
console.log(` ${style('dim', line)}`)
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
console.log()
|
|
442
|
+
},
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Log memory retrieval scoring details (Activation Signal Algorithm)
|
|
269
446
|
*/
|
|
270
447
|
logRetrievalScoring(params: {
|
|
271
448
|
totalMemories: number
|
|
272
449
|
currentMessage: string
|
|
273
450
|
alreadyInjected: number
|
|
274
|
-
|
|
275
|
-
|
|
451
|
+
preFiltered: number
|
|
452
|
+
globalCount: number
|
|
453
|
+
projectCount: number
|
|
276
454
|
finalCount: number
|
|
455
|
+
durationMs?: number
|
|
277
456
|
selectedMemories: Array<{
|
|
278
457
|
content: string
|
|
279
|
-
reasoning: string
|
|
280
|
-
|
|
281
|
-
relevance_score: number
|
|
458
|
+
reasoning: string // "Activated: trigger:67%, tags:2, content (3 signals)"
|
|
459
|
+
signalCount: number
|
|
282
460
|
importance_weight: number
|
|
283
461
|
context_type: string
|
|
284
462
|
semantic_tags: string[]
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
463
|
+
isGlobal: boolean
|
|
464
|
+
// Activation signals
|
|
465
|
+
signals: {
|
|
466
|
+
trigger: boolean
|
|
467
|
+
triggerStrength: number
|
|
468
|
+
tags: boolean
|
|
469
|
+
tagCount: number
|
|
470
|
+
domain: boolean
|
|
471
|
+
feature: boolean
|
|
472
|
+
content: boolean
|
|
473
|
+
vector: boolean
|
|
474
|
+
vectorSimilarity: number
|
|
296
475
|
}
|
|
297
476
|
}>
|
|
298
477
|
}) {
|
|
299
|
-
const { totalMemories, currentMessage, alreadyInjected,
|
|
478
|
+
const { totalMemories, currentMessage, alreadyInjected, preFiltered, globalCount, projectCount, finalCount, durationMs, selectedMemories } = params
|
|
479
|
+
|
|
480
|
+
const timeStr = durationMs !== undefined ? style('cyan', `${durationMs.toFixed(1)}ms`) : ''
|
|
300
481
|
|
|
301
482
|
console.log()
|
|
302
|
-
console.log(`${timestamp()} ${style('magenta', sym.brain)} ${style('bold', '
|
|
303
|
-
console.log(` ${style('dim', 'candidates:')} ${totalMemories}
|
|
483
|
+
console.log(`${timestamp()} ${style('magenta', sym.brain)} ${style('bold', 'RETRIEVAL')} ${timeStr}`)
|
|
484
|
+
console.log(` ${style('dim', 'total:')} ${totalMemories} → ${style('dim', 'filtered:')} ${preFiltered} → ${style('dim', 'candidates:')} ${totalMemories - preFiltered}`)
|
|
304
485
|
console.log(` ${style('dim', 'already injected:')} ${alreadyInjected}`)
|
|
305
486
|
|
|
306
487
|
const msgPreview = currentMessage.length > 60
|
|
307
488
|
? currentMessage.slice(0, 60) + '...'
|
|
308
489
|
: currentMessage
|
|
309
|
-
console.log(` ${style('dim', '
|
|
490
|
+
console.log(` ${style('dim', 'message:')} "${msgPreview}"`)
|
|
310
491
|
console.log()
|
|
311
492
|
|
|
312
|
-
//
|
|
313
|
-
console.log(` ${style('cyan', '
|
|
314
|
-
console.log(` ${style('cyan', '
|
|
493
|
+
// Selection summary
|
|
494
|
+
console.log(` ${style('cyan', 'Global:')} ${globalCount} candidates → max 2 selected`)
|
|
495
|
+
console.log(` ${style('cyan', 'Project:')} ${projectCount} candidates`)
|
|
315
496
|
console.log(` ${style('green', 'Final:')} ${finalCount} memories selected`)
|
|
316
497
|
console.log()
|
|
317
498
|
|
|
@@ -328,11 +509,12 @@ export const logger = {
|
|
|
328
509
|
|
|
329
510
|
selectedMemories.forEach((m, i) => {
|
|
330
511
|
const num = style('dim', `${i + 1}.`)
|
|
331
|
-
const
|
|
332
|
-
const
|
|
512
|
+
const signalsStr = style('green', `${m.signalCount} signals`)
|
|
513
|
+
const imp = style('magenta', `imp:${(m.importance_weight * 100).toFixed(0)}%`)
|
|
333
514
|
const type = style('yellow', m.context_type.toUpperCase())
|
|
515
|
+
const scope = m.isGlobal ? style('blue', ' [G]') : ''
|
|
334
516
|
|
|
335
|
-
console.log(` ${num} [${
|
|
517
|
+
console.log(` ${num} [${signalsStr} • ${imp}] ${type}${scope}`)
|
|
336
518
|
|
|
337
519
|
// Content preview
|
|
338
520
|
const preview = m.content.length > 60
|
|
@@ -340,27 +522,109 @@ export const logger = {
|
|
|
340
522
|
: m.content
|
|
341
523
|
console.log(` ${style('white', preview)}`)
|
|
342
524
|
|
|
343
|
-
//
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
.
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
.
|
|
350
|
-
|
|
351
|
-
if (
|
|
352
|
-
|
|
525
|
+
// Show which signals fired with their strengths
|
|
526
|
+
const firedSignals: string[] = []
|
|
527
|
+
if (m.signals.trigger) {
|
|
528
|
+
firedSignals.push(`trigger:${(m.signals.triggerStrength * 100).toFixed(0)}%`)
|
|
529
|
+
}
|
|
530
|
+
if (m.signals.tags) {
|
|
531
|
+
firedSignals.push(`tags:${m.signals.tagCount}`)
|
|
532
|
+
}
|
|
533
|
+
if (m.signals.domain) firedSignals.push('domain')
|
|
534
|
+
if (m.signals.feature) firedSignals.push('feature')
|
|
535
|
+
if (m.signals.content) firedSignals.push('content')
|
|
536
|
+
if (m.signals.vector) {
|
|
537
|
+
firedSignals.push(`vector:${(m.signals.vectorSimilarity * 100).toFixed(0)}%`)
|
|
353
538
|
}
|
|
354
539
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
const tags = m.semantic_tags.slice(0, 3).join(', ')
|
|
358
|
-
console.log(` ${style('dim', 'tags:')} ${tags}`)
|
|
540
|
+
if (firedSignals.length > 0) {
|
|
541
|
+
console.log(` ${style('cyan', 'signals:')} ${firedSignals.join(', ')}`)
|
|
359
542
|
}
|
|
360
543
|
|
|
361
544
|
console.log()
|
|
362
545
|
})
|
|
363
546
|
},
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Log score distribution for diagnostics (supports both old and new algorithm)
|
|
550
|
+
*/
|
|
551
|
+
logScoreDistribution(params: {
|
|
552
|
+
totalCandidates: number
|
|
553
|
+
passedGatekeeper: number
|
|
554
|
+
rejectedByGatekeeper: number
|
|
555
|
+
buckets: Record<string, number>
|
|
556
|
+
stats: { min: number; max: number; mean: number; stdev: number; spread: number }
|
|
557
|
+
percentiles: Record<string, number>
|
|
558
|
+
relevanceStats?: { min: number; max: number; spread: number }
|
|
559
|
+
triggerAnalysis?: { perfect: number; zero: number; total: number }
|
|
560
|
+
top5Spread?: number
|
|
561
|
+
compressionWarning: boolean
|
|
562
|
+
signalBreakdown?: {
|
|
563
|
+
trigger: number
|
|
564
|
+
tags: number
|
|
565
|
+
domain: number
|
|
566
|
+
feature: number
|
|
567
|
+
content: number
|
|
568
|
+
vector: number
|
|
569
|
+
total: number
|
|
570
|
+
}
|
|
571
|
+
}) {
|
|
572
|
+
const { totalCandidates, passedGatekeeper, rejectedByGatekeeper, buckets, stats, signalBreakdown } = params
|
|
573
|
+
|
|
574
|
+
console.log()
|
|
575
|
+
console.log(style('dim', ' ─'.repeat(30)))
|
|
576
|
+
console.log(` ${style('bold', 'ACTIVATION SIGNALS')}`)
|
|
577
|
+
console.log()
|
|
578
|
+
|
|
579
|
+
// Gatekeeper stats
|
|
580
|
+
const passRate = totalCandidates > 0 ? ((passedGatekeeper / totalCandidates) * 100).toFixed(0) : '0'
|
|
581
|
+
console.log(` ${style('dim', 'Activated:')} ${style('green', String(passedGatekeeper))}/${totalCandidates} (${passRate}%)`)
|
|
582
|
+
console.log(` ${style('dim', 'Rejected:')} ${rejectedByGatekeeper} (< 2 signals)`)
|
|
583
|
+
console.log()
|
|
584
|
+
|
|
585
|
+
// Signal breakdown
|
|
586
|
+
if (signalBreakdown && signalBreakdown.total > 0) {
|
|
587
|
+
console.log(` ${style('cyan', 'Signal Breakdown:')}`)
|
|
588
|
+
const signals = [
|
|
589
|
+
{ name: 'trigger', count: signalBreakdown.trigger },
|
|
590
|
+
{ name: 'tags', count: signalBreakdown.tags },
|
|
591
|
+
{ name: 'domain', count: signalBreakdown.domain },
|
|
592
|
+
{ name: 'feature', count: signalBreakdown.feature },
|
|
593
|
+
{ name: 'content', count: signalBreakdown.content },
|
|
594
|
+
{ name: 'vector', count: signalBreakdown.vector },
|
|
595
|
+
]
|
|
596
|
+
for (const sig of signals) {
|
|
597
|
+
const pct = ((sig.count / signalBreakdown.total) * 100).toFixed(0)
|
|
598
|
+
const bar = '█'.repeat(Math.round(sig.count / signalBreakdown.total * 20))
|
|
599
|
+
console.log(` ${sig.name.padEnd(8)} ${bar.padEnd(20)} ${sig.count} (${pct}%)`)
|
|
600
|
+
}
|
|
601
|
+
console.log()
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Stats
|
|
605
|
+
if (stats.max > 0) {
|
|
606
|
+
console.log(` ${style('cyan', 'Signals:')} min=${stats.min} max=${stats.max} mean=${stats.mean}`)
|
|
607
|
+
console.log()
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// Histogram by signal count
|
|
611
|
+
if (Object.keys(buckets).length > 0) {
|
|
612
|
+
console.log(` ${style('bold', 'Distribution:')}`)
|
|
613
|
+
const maxBucketCount = Math.max(...Object.values(buckets), 1)
|
|
614
|
+
const bucketOrder = ['2 signals', '3 signals', '4 signals', '5 signals', '6 signals']
|
|
615
|
+
|
|
616
|
+
for (const bucket of bucketOrder) {
|
|
617
|
+
const count = buckets[bucket] ?? 0
|
|
618
|
+
if (count > 0 || bucket === '2 signals') {
|
|
619
|
+
const barLen = Math.round((count / maxBucketCount) * 25)
|
|
620
|
+
const bar = '█'.repeat(barLen) + style('dim', '░'.repeat(25 - barLen))
|
|
621
|
+
const countStr = count.toString().padStart(3)
|
|
622
|
+
console.log(` ${style('dim', bucket.padEnd(10))} ${bar} ${style('cyan', countStr)}`)
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
console.log()
|
|
626
|
+
}
|
|
627
|
+
},
|
|
364
628
|
}
|
|
365
629
|
|
|
366
630
|
export default logger
|