@rlabs-inc/memory 0.5.13 → 0.6.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/package.json +2 -1
- package/src/server/index.ts +18 -0
- package/src/utils/logger.ts +321 -270
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rlabs-inc/memory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "AI Memory System - Consciousness continuity through intelligent memory curation and retrieval",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"@anthropic-ai/claude-agent-sdk": "^0.2.12",
|
|
43
43
|
"@huggingface/transformers": "^3.4.1",
|
|
44
44
|
"@rlabs-inc/fsdb": "^1.0.1",
|
|
45
|
+
"@rlabs-inc/prism": "^0.4.0",
|
|
45
46
|
"@rlabs-inc/signals": "^1.0.0"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
package/src/server/index.ts
CHANGED
|
@@ -183,6 +183,15 @@ export async function createServer(config: ServerConfig = {}) {
|
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
// Verbose: show the full payload that gets injected into AI context
|
|
187
|
+
if (result.formatted) {
|
|
188
|
+
logger.logInjectedPayload(
|
|
189
|
+
result.formatted,
|
|
190
|
+
result.primer ? 'primer' : (mode === 'action_items' ? 'action_items' : 'memories'),
|
|
191
|
+
result.memories.length
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
|
|
186
195
|
return Response.json({
|
|
187
196
|
success: true,
|
|
188
197
|
session_id: body.session_id,
|
|
@@ -359,6 +368,15 @@ export async function createServer(config: ServerConfig = {}) {
|
|
|
359
368
|
filesWritten: managementResult.filesWritten,
|
|
360
369
|
},
|
|
361
370
|
})
|
|
371
|
+
|
|
372
|
+
// Notify @rlabs-inc/mind (fire-and-forget, never blocks memory pipeline)
|
|
373
|
+
if (process.env.MEMORY_MIND_WEBHOOK_URL) {
|
|
374
|
+
fetch(process.env.MEMORY_MIND_WEBHOOK_URL, {
|
|
375
|
+
method: 'POST',
|
|
376
|
+
headers: { 'Content-Type': 'application/json' },
|
|
377
|
+
body: JSON.stringify({ projectId: body.project_id, sessionNumber, curationResult: result, managementResult }),
|
|
378
|
+
}).catch(() => {})
|
|
379
|
+
}
|
|
362
380
|
} catch (error) {
|
|
363
381
|
logger.error(`Management failed: ${error}`)
|
|
364
382
|
}
|
package/src/utils/logger.ts
CHANGED
|
@@ -1,44 +1,35 @@
|
|
|
1
1
|
// ============================================================================
|
|
2
2
|
// LOGGER - Beautiful console output for the memory system
|
|
3
|
-
//
|
|
3
|
+
// Powered by @rlabs-inc/prism for terminal rendering
|
|
4
4
|
// ============================================================================
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
type Style = Parameters<typeof styleText>[0]
|
|
9
|
-
|
|
10
|
-
const style = (format: Style, text: string): string => styleText(format, text)
|
|
6
|
+
import { s, log, box, table, badge, divider as prismDivider, kv, writeln } from '@rlabs-inc/prism'
|
|
11
7
|
|
|
12
8
|
/**
|
|
13
9
|
* Format a timestamp (HH:MM:SS)
|
|
14
10
|
*/
|
|
15
|
-
function
|
|
16
|
-
return
|
|
11
|
+
function ts(): string {
|
|
12
|
+
return s.dim(new Date().toISOString().slice(11, 19))
|
|
17
13
|
}
|
|
18
14
|
|
|
19
15
|
/**
|
|
20
16
|
* Format a short session ID
|
|
21
17
|
*/
|
|
22
18
|
function shortId(id: string): string {
|
|
23
|
-
return
|
|
19
|
+
return s.dim(id.slice(0, 8) + '...')
|
|
24
20
|
}
|
|
25
21
|
|
|
26
22
|
/**
|
|
27
|
-
*
|
|
23
|
+
* Emoji map for quick visual scanning of context types
|
|
28
24
|
*/
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
warning: '⚠',
|
|
38
|
-
info: 'ℹ',
|
|
39
|
-
bullet: '•',
|
|
40
|
-
fire: '🔥',
|
|
41
|
-
target: '🎯',
|
|
25
|
+
const emojiMap: Record<string, string> = {
|
|
26
|
+
breakthrough: '💡', decision: '⚖️', personal: '💜', technical: '🔧',
|
|
27
|
+
technical_state: '📍', unresolved: '❓', preference: '⚙️', workflow: '🔄',
|
|
28
|
+
architectural: '🏗️', debugging: '🐛', philosophy: '🌀', todo: '🎯',
|
|
29
|
+
implementation: '⚡', problem_solution: '✅', project_context: '📦',
|
|
30
|
+
milestone: '🏆', general: '📝', project_state: '📍', pending_task: '⏳',
|
|
31
|
+
work_in_progress: '🔨', system_feedback: '📣', project_milestone: '🏆',
|
|
32
|
+
architectural_insight: '🏛️', architectural_direction: '🧭',
|
|
42
33
|
}
|
|
43
34
|
|
|
44
35
|
/**
|
|
@@ -68,81 +59,79 @@ export const logger = {
|
|
|
68
59
|
* Debug message (only shown in verbose mode)
|
|
69
60
|
*/
|
|
70
61
|
debug(message: string, prefix?: string) {
|
|
71
|
-
if (_verbose)
|
|
72
|
-
|
|
73
|
-
console.log(`${style('dim', `🔍 ${pfx}${message}`)}`)
|
|
74
|
-
}
|
|
62
|
+
if (!_verbose) return
|
|
63
|
+
log.debug(prefix ? `[${prefix}] ${message}` : message)
|
|
75
64
|
},
|
|
76
65
|
|
|
77
66
|
/**
|
|
78
67
|
* Info message
|
|
79
68
|
*/
|
|
80
69
|
info(message: string) {
|
|
81
|
-
|
|
70
|
+
log.info(message, { timestamp: true })
|
|
82
71
|
},
|
|
83
72
|
|
|
84
73
|
/**
|
|
85
74
|
* Success message
|
|
86
75
|
*/
|
|
87
76
|
success(message: string) {
|
|
88
|
-
|
|
77
|
+
log.success(message, { timestamp: true })
|
|
89
78
|
},
|
|
90
79
|
|
|
91
80
|
/**
|
|
92
81
|
* Warning message
|
|
93
82
|
*/
|
|
94
83
|
warn(message: string) {
|
|
95
|
-
|
|
84
|
+
log.warn(message, { timestamp: true })
|
|
96
85
|
},
|
|
97
86
|
|
|
98
87
|
/**
|
|
99
88
|
* Error message
|
|
100
89
|
*/
|
|
101
90
|
error(message: string) {
|
|
102
|
-
|
|
91
|
+
log.error(message, { timestamp: true })
|
|
103
92
|
},
|
|
104
93
|
|
|
105
94
|
/**
|
|
106
95
|
* Memory event (curation, storage)
|
|
107
96
|
*/
|
|
108
97
|
memory(message: string) {
|
|
109
|
-
|
|
98
|
+
writeln(`${ts()} ${s.magenta('🧠')} ${message}`)
|
|
110
99
|
},
|
|
111
100
|
|
|
112
101
|
/**
|
|
113
102
|
* Injection event (memories surfaced)
|
|
114
103
|
*/
|
|
115
104
|
inject(message: string) {
|
|
116
|
-
|
|
105
|
+
writeln(`${ts()} ${s.cyan('✨')} ${message}`)
|
|
117
106
|
},
|
|
118
107
|
|
|
119
108
|
/**
|
|
120
109
|
* Session event
|
|
121
110
|
*/
|
|
122
111
|
session(message: string) {
|
|
123
|
-
|
|
112
|
+
writeln(`${ts()} ${s.blue('📅')} ${message}`)
|
|
124
113
|
},
|
|
125
114
|
|
|
126
115
|
/**
|
|
127
116
|
* Primer shown
|
|
128
117
|
*/
|
|
129
118
|
primer(message: string) {
|
|
130
|
-
|
|
119
|
+
writeln(`${ts()} ${s.yellow('📖')} ${message}`)
|
|
131
120
|
},
|
|
132
121
|
|
|
133
122
|
/**
|
|
134
123
|
* Divider line
|
|
135
124
|
*/
|
|
136
125
|
divider() {
|
|
137
|
-
|
|
126
|
+
writeln(prismDivider())
|
|
138
127
|
},
|
|
139
128
|
|
|
140
129
|
/**
|
|
141
130
|
* Request received (incoming)
|
|
142
131
|
*/
|
|
143
132
|
request(method: string, path: string, projectId?: string) {
|
|
144
|
-
const proj = projectId ?
|
|
145
|
-
|
|
133
|
+
const proj = projectId ? s.dim(` [${projectId}]`) : ''
|
|
134
|
+
writeln(`${ts()} ${s.dim('→')} ${s.cyan(method)} ${path}${proj}`)
|
|
146
135
|
},
|
|
147
136
|
|
|
148
137
|
/**
|
|
@@ -155,34 +144,34 @@ export const logger = {
|
|
|
155
144
|
semantic_tags?: string[]
|
|
156
145
|
action_required?: boolean
|
|
157
146
|
}>) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
147
|
+
writeln()
|
|
148
|
+
writeln(`${ts()} ${s.magenta('🧠')} ${s.bold.magenta(`CURATED ${memories.length} MEMORIES`)}`)
|
|
149
|
+
writeln()
|
|
161
150
|
|
|
162
151
|
memories.forEach((m, i) => {
|
|
163
|
-
const
|
|
164
|
-
const type =
|
|
165
|
-
const
|
|
152
|
+
const num = s.dim(`${i + 1}.`)
|
|
153
|
+
const type = badge(m.context_type.toUpperCase(), { color: s.cyan, variant: 'bracket' })
|
|
154
|
+
const importance = s.yellow(`${(m.importance_weight * 100).toFixed(0)}%`)
|
|
166
155
|
|
|
167
|
-
|
|
156
|
+
writeln(` ${num} ${type} ${importance}`)
|
|
168
157
|
|
|
169
|
-
// Content
|
|
170
|
-
const
|
|
171
|
-
? m.content.slice(0, 70) +
|
|
158
|
+
// Content: full in verbose, truncated in normal
|
|
159
|
+
const content = !_verbose && m.content.length > 70
|
|
160
|
+
? m.content.slice(0, 70) + s.dim('...')
|
|
172
161
|
: m.content
|
|
173
|
-
|
|
162
|
+
writeln(` ${content}`)
|
|
174
163
|
|
|
175
164
|
// Tags
|
|
176
165
|
if (m.semantic_tags?.length) {
|
|
177
|
-
const tags = m.semantic_tags.slice(0, 4).join(
|
|
178
|
-
|
|
166
|
+
const tags = m.semantic_tags.slice(0, _verbose ? 8 : 4).join(s.dim(', '))
|
|
167
|
+
writeln(` ${s.dim('tags:')} ${tags}`)
|
|
179
168
|
}
|
|
180
169
|
|
|
181
170
|
// Special flags
|
|
182
171
|
if (m.action_required) {
|
|
183
|
-
|
|
172
|
+
writeln(` ${s.red('⚡ ACTION REQUIRED')}`)
|
|
184
173
|
}
|
|
185
|
-
|
|
174
|
+
writeln()
|
|
186
175
|
})
|
|
187
176
|
},
|
|
188
177
|
|
|
@@ -194,88 +183,79 @@ export const logger = {
|
|
|
194
183
|
score: number // signals.count / 6
|
|
195
184
|
context_type: string
|
|
196
185
|
}>, query: string) {
|
|
197
|
-
const queryPreview = query.length > 40
|
|
186
|
+
const queryPreview = !_verbose && query.length > 40
|
|
198
187
|
? query.slice(0, 40) + '...'
|
|
199
188
|
: query
|
|
200
189
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
architectural: '🏗️', debugging: '🐛', philosophy: '🌀', todo: '🎯',
|
|
206
|
-
implementation: '⚡', problem_solution: '✅', project_context: '📦',
|
|
207
|
-
milestone: '🏆', general: '📝', project_state: '📍', pending_task: '⏳',
|
|
208
|
-
work_in_progress: '🔨', system_feedback: '📣', project_milestone: '🏆',
|
|
209
|
-
architectural_insight: '🏛️', architectural_direction: '🧭',
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
console.log()
|
|
213
|
-
console.log(`${timestamp()} ${style('cyan', sym.sparkles)} ${style('bold', `SURFACING ${memories.length} MEMORIES`)}`)
|
|
214
|
-
console.log(` ${style('dim', 'query:')} "${queryPreview}"`)
|
|
215
|
-
console.log()
|
|
190
|
+
writeln()
|
|
191
|
+
writeln(`${ts()} ${s.cyan('✨')} ${s.bold(`SURFACING ${memories.length} MEMORIES`)}`)
|
|
192
|
+
writeln(` ${s.dim('query:')} "${queryPreview}"`)
|
|
193
|
+
writeln()
|
|
216
194
|
|
|
217
195
|
if (memories.length === 0) {
|
|
218
|
-
|
|
219
|
-
|
|
196
|
+
writeln(` ${s.dim('(no relevant memories for this context)')}`)
|
|
197
|
+
writeln()
|
|
220
198
|
return
|
|
221
199
|
}
|
|
222
200
|
|
|
223
201
|
memories.forEach((m, i) => {
|
|
224
|
-
// Convert score back to signal count (score is count/6)
|
|
225
202
|
const signalCount = Math.round(m.score * 6)
|
|
226
|
-
const
|
|
203
|
+
const signalBadge = badge(`${signalCount}sig`, { color: s.green, variant: 'bracket' })
|
|
227
204
|
const emoji = emojiMap[m.context_type?.toLowerCase()] ?? '📝'
|
|
228
|
-
const num =
|
|
205
|
+
const num = s.dim(`${i + 1}.`)
|
|
229
206
|
|
|
230
|
-
|
|
231
|
-
|
|
207
|
+
// Content: full in verbose, truncated in normal
|
|
208
|
+
const content = !_verbose && m.content.length > 55
|
|
209
|
+
? m.content.slice(0, 55) + s.dim('...')
|
|
232
210
|
: m.content
|
|
233
211
|
|
|
234
|
-
|
|
235
|
-
|
|
212
|
+
writeln(` ${num} ${signalBadge} ${emoji}`)
|
|
213
|
+
writeln(` ${content}`)
|
|
236
214
|
})
|
|
237
|
-
|
|
215
|
+
writeln()
|
|
238
216
|
},
|
|
239
217
|
|
|
240
218
|
/**
|
|
241
219
|
* Log server startup
|
|
242
220
|
*/
|
|
243
221
|
startup(port: number, host: string, mode: string) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
222
|
+
const info = kv([
|
|
223
|
+
['url', s.cyan(`http://${host}:${port}`)],
|
|
224
|
+
['storage', mode],
|
|
225
|
+
['engine', 'TypeScript + fsdb'],
|
|
226
|
+
['verbose', _verbose ? s.green('on') : s.dim('off')],
|
|
227
|
+
], { keyColor: s.dim, indent: 1 })
|
|
228
|
+
|
|
229
|
+
writeln()
|
|
230
|
+
writeln(box(info, {
|
|
231
|
+
border: 'double',
|
|
232
|
+
borderColor: 'magenta',
|
|
233
|
+
title: '🧠 Memory Server',
|
|
234
|
+
titleColor: s.bold,
|
|
235
|
+
}))
|
|
236
|
+
writeln()
|
|
255
237
|
},
|
|
256
238
|
|
|
257
239
|
/**
|
|
258
240
|
* Log session start
|
|
259
241
|
*/
|
|
260
242
|
logSessionStart(sessionId: string, projectId: string, isNew: boolean) {
|
|
261
|
-
const status = isNew
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
console.log(` ${style('dim', 'status:')} ${status}`)
|
|
269
|
-
console.log()
|
|
243
|
+
const status = isNew ? s.green('new session') : s.blue('continuing')
|
|
244
|
+
|
|
245
|
+
writeln()
|
|
246
|
+
writeln(`${ts()} ${s.blue('📅')} ${s.bold('SESSION')} ${shortId(sessionId)}`)
|
|
247
|
+
writeln(` ${s.dim('project:')} ${projectId}`)
|
|
248
|
+
writeln(` ${s.dim('status:')} ${status}`)
|
|
249
|
+
writeln()
|
|
270
250
|
},
|
|
271
251
|
|
|
272
252
|
/**
|
|
273
253
|
* Log curation start
|
|
274
254
|
*/
|
|
275
255
|
logCurationStart(sessionId: string, trigger: string) {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
256
|
+
writeln()
|
|
257
|
+
writeln(`${ts()} ${s.magenta('🧠')} ${s.bold('CURATING')} ${shortId(sessionId)}`)
|
|
258
|
+
writeln(` ${s.dim('trigger:')} ${trigger}`)
|
|
279
259
|
},
|
|
280
260
|
|
|
281
261
|
/**
|
|
@@ -283,31 +263,31 @@ export const logger = {
|
|
|
283
263
|
*/
|
|
284
264
|
logCurationComplete(memoriesCount: number, summary?: string) {
|
|
285
265
|
if (memoriesCount > 0) {
|
|
286
|
-
|
|
266
|
+
writeln(` ${s.dim('memories:')} ${s.green(String(memoriesCount))} extracted`)
|
|
287
267
|
if (summary) {
|
|
288
|
-
const
|
|
268
|
+
const text = !_verbose && summary.length > 50
|
|
289
269
|
? summary.slice(0, 50) + '...'
|
|
290
270
|
: summary
|
|
291
|
-
|
|
271
|
+
writeln(` ${s.dim('summary:')} ${text}`)
|
|
292
272
|
}
|
|
293
273
|
} else {
|
|
294
|
-
|
|
274
|
+
writeln(` ${s.dim('result:')} no memories to extract`)
|
|
295
275
|
}
|
|
296
|
-
|
|
276
|
+
writeln()
|
|
297
277
|
},
|
|
298
278
|
|
|
299
279
|
/**
|
|
300
280
|
* Log management agent starting
|
|
301
281
|
*/
|
|
302
282
|
logManagementStart(memoriesCount: number) {
|
|
303
|
-
|
|
304
|
-
|
|
283
|
+
writeln(`${ts()} ${s.blue('🔧')} ${s.bold('MANAGEMENT AGENT')}`)
|
|
284
|
+
writeln(` ${s.dim('processing:')} ${memoriesCount} new memories`)
|
|
305
285
|
},
|
|
306
286
|
|
|
307
287
|
/**
|
|
308
288
|
* Log management agent results
|
|
309
|
-
*
|
|
310
|
-
*
|
|
289
|
+
* Verbose: full details beautifully formatted
|
|
290
|
+
* Normal: compact summary
|
|
311
291
|
*/
|
|
312
292
|
logManagementComplete(result: {
|
|
313
293
|
success: boolean
|
|
@@ -322,88 +302,75 @@ export const logger = {
|
|
|
322
302
|
fullReport?: string
|
|
323
303
|
error?: string
|
|
324
304
|
}) {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
if (action.startsWith('
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
else if (action.startsWith('NO_ACTION')) icon = style('dim', ' ◦')
|
|
341
|
-
|
|
342
|
-
const text = truncate && action.length > 70 ? action.slice(0, 67) + '...' : action
|
|
343
|
-
return `${icon} ${style('dim', text)}`
|
|
305
|
+
const actionIcon = (action: string): string => {
|
|
306
|
+
if (action.startsWith('READ OK')) return s.dim('📖')
|
|
307
|
+
if (action.startsWith('READ FAILED')) return s.red('❌')
|
|
308
|
+
if (action.startsWith('WRITE OK')) return s.green('✏️')
|
|
309
|
+
if (action.startsWith('WRITE FAILED')) return s.red('❌')
|
|
310
|
+
if (action.startsWith('RECEIVED')) return s.cyan('📥')
|
|
311
|
+
if (action.startsWith('CREATED')) return s.green('✨')
|
|
312
|
+
if (action.startsWith('UPDATED')) return s.blue('📝')
|
|
313
|
+
if (action.startsWith('SUPERSEDED')) return s.yellow('🔄')
|
|
314
|
+
if (action.startsWith('RESOLVED')) return s.green('✅')
|
|
315
|
+
if (action.startsWith('LINKED')) return s.cyan('🔗')
|
|
316
|
+
if (action.startsWith('PRIMER')) return s.magenta('💜')
|
|
317
|
+
if (action.startsWith('SKIPPED')) return s.dim('⏭️')
|
|
318
|
+
if (action.startsWith('NO_ACTION')) return s.dim('◦')
|
|
319
|
+
return '•'
|
|
344
320
|
}
|
|
345
321
|
|
|
346
322
|
if (result.success) {
|
|
347
|
-
|
|
323
|
+
writeln(` ${s.green('✓')} ${s.bold('Completed')}`)
|
|
348
324
|
|
|
349
325
|
if (_verbose) {
|
|
350
|
-
//
|
|
351
|
-
// VERBOSE MODE: Show everything beautifully formatted
|
|
352
|
-
// ═══════════════════════════════════════════════════════════════
|
|
353
|
-
|
|
354
|
-
// File I/O stats section
|
|
355
|
-
console.log(` ${style('dim', '─'.repeat(50))}`)
|
|
356
|
-
console.log(` ${style('cyan', '📊')} ${style('bold', 'Statistics')}`)
|
|
357
|
-
|
|
326
|
+
// ── VERBOSE: Full details ──
|
|
358
327
|
const filesRead = result.filesRead ?? 0
|
|
359
328
|
const filesWritten = result.filesWritten ?? 0
|
|
360
|
-
console.log(` ${style('dim', 'Files read:')} ${filesRead > 0 ? style('green', String(filesRead)) : style('dim', '0')}`)
|
|
361
|
-
console.log(` ${style('dim', 'Files written:')} ${filesWritten > 0 ? style('green', String(filesWritten)) : style('dim', '0')}`)
|
|
362
|
-
|
|
363
|
-
// Memory changes section
|
|
364
329
|
const superseded = result.superseded ?? 0
|
|
365
330
|
const resolved = result.resolved ?? 0
|
|
366
331
|
const linked = result.linked ?? 0
|
|
367
|
-
console.log(` ${style('dim', 'Superseded:')} ${superseded > 0 ? style('yellow', String(superseded)) : style('dim', '0')}`)
|
|
368
|
-
console.log(` ${style('dim', 'Resolved:')} ${resolved > 0 ? style('green', String(resolved)) : style('dim', '0')}`)
|
|
369
|
-
console.log(` ${style('dim', 'Linked:')} ${linked > 0 ? style('cyan', String(linked)) : style('dim', '0')}`)
|
|
370
|
-
console.log(` ${style('dim', 'Primer:')} ${result.primerUpdated ? style('magenta', 'updated') : style('dim', 'unchanged')}`)
|
|
371
332
|
|
|
372
|
-
|
|
333
|
+
writeln(` ${s.dim('─'.repeat(50))}`)
|
|
334
|
+
writeln(` ${s.cyan('📊')} ${s.bold('Statistics')}`)
|
|
335
|
+
|
|
336
|
+
const stats = kv([
|
|
337
|
+
['Files read', filesRead > 0 ? s.green(String(filesRead)) : s.dim('0')],
|
|
338
|
+
['Files written', filesWritten > 0 ? s.green(String(filesWritten)) : s.dim('0')],
|
|
339
|
+
['Superseded', superseded > 0 ? s.yellow(String(superseded)) : s.dim('0')],
|
|
340
|
+
['Resolved', resolved > 0 ? s.green(String(resolved)) : s.dim('0')],
|
|
341
|
+
['Linked', linked > 0 ? s.cyan(String(linked)) : s.dim('0')],
|
|
342
|
+
['Primer', result.primerUpdated ? s.magenta('updated') : s.dim('unchanged')],
|
|
343
|
+
], { keyColor: s.dim, indent: 4 })
|
|
344
|
+
writeln(stats)
|
|
345
|
+
|
|
346
|
+
// Actions - no truncation in verbose
|
|
373
347
|
if (result.actions && result.actions.length > 0) {
|
|
374
|
-
|
|
375
|
-
|
|
348
|
+
writeln(` ${s.dim('─'.repeat(50))}`)
|
|
349
|
+
writeln(` ${s.cyan('🎬')} ${s.bold('Actions')} ${s.dim(`(${result.actions.length} total)`)}`)
|
|
376
350
|
for (const action of result.actions) {
|
|
377
|
-
|
|
351
|
+
writeln(` ${actionIcon(action)} ${s.dim(action)}`)
|
|
378
352
|
}
|
|
379
353
|
}
|
|
380
354
|
|
|
381
|
-
// Full report
|
|
355
|
+
// Full report
|
|
382
356
|
if (result.fullReport) {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
const
|
|
386
|
-
for (const line of reportLines) {
|
|
387
|
-
// Highlight section headers
|
|
357
|
+
writeln(` ${s.dim('─'.repeat(50))}`)
|
|
358
|
+
writeln(` ${s.cyan('📋')} ${s.bold('Full Report')}`)
|
|
359
|
+
for (const line of result.fullReport.split('\n')) {
|
|
388
360
|
if (line.includes('===')) {
|
|
389
|
-
|
|
361
|
+
writeln(` ${s.bold(line)}`)
|
|
390
362
|
} else if (line.match(/^[A-Z_]+:/)) {
|
|
391
|
-
|
|
392
|
-
console.log(` ${style('cyan', line)}`)
|
|
363
|
+
writeln(` ${s.cyan(line)}`)
|
|
393
364
|
} else {
|
|
394
|
-
|
|
365
|
+
writeln(` ${s.dim(line)}`)
|
|
395
366
|
}
|
|
396
367
|
}
|
|
397
368
|
}
|
|
398
369
|
|
|
399
|
-
|
|
370
|
+
writeln(` ${s.dim('─'.repeat(50))}`)
|
|
400
371
|
|
|
401
372
|
} else {
|
|
402
|
-
//
|
|
403
|
-
// NORMAL MODE: Compact summary
|
|
404
|
-
// ═══════════════════════════════════════════════════════════════
|
|
405
|
-
|
|
406
|
-
// Memory change stats (one line)
|
|
373
|
+
// ── NORMAL: Compact summary ──
|
|
407
374
|
const stats: string[] = []
|
|
408
375
|
if (result.superseded && result.superseded > 0) stats.push(`${result.superseded} superseded`)
|
|
409
376
|
if (result.resolved && result.resolved > 0) stats.push(`${result.resolved} resolved`)
|
|
@@ -411,43 +378,39 @@ export const logger = {
|
|
|
411
378
|
if (result.primerUpdated) stats.push('primer updated')
|
|
412
379
|
|
|
413
380
|
if (stats.length > 0) {
|
|
414
|
-
|
|
381
|
+
writeln(` ${s.dim('changes:')} ${stats.join(s.dim(', '))}`)
|
|
415
382
|
} else {
|
|
416
|
-
|
|
383
|
+
writeln(` ${s.dim('changes:')} none (memories are current)`)
|
|
417
384
|
}
|
|
418
385
|
|
|
419
|
-
// Show limited actions
|
|
420
386
|
if (result.actions && result.actions.length > 0) {
|
|
421
|
-
|
|
387
|
+
writeln(` ${s.dim('actions:')}`)
|
|
422
388
|
for (const action of result.actions.slice(0, 10)) {
|
|
423
|
-
|
|
389
|
+
const text = action.length > 70 ? action.slice(0, 67) + '...' : action
|
|
390
|
+
writeln(` ${actionIcon(action)} ${s.dim(text)}`)
|
|
424
391
|
}
|
|
425
392
|
if (result.actions.length > 10) {
|
|
426
|
-
|
|
393
|
+
writeln(` ${s.dim(` ... and ${result.actions.length - 10} more actions`)}`)
|
|
427
394
|
}
|
|
428
395
|
}
|
|
429
396
|
}
|
|
430
397
|
|
|
431
398
|
} else {
|
|
432
|
-
//
|
|
433
|
-
|
|
434
|
-
// ═══════════════════════════════════════════════════════════════
|
|
435
|
-
console.log(` ${style('yellow', sym.warning)} ${style('bold', 'Failed')}`)
|
|
399
|
+
// ── ERROR: Always show details ──
|
|
400
|
+
writeln(` ${s.yellow('⚠')} ${s.bold('Failed')}`)
|
|
436
401
|
if (result.error) {
|
|
437
|
-
|
|
402
|
+
writeln(` ${s.red('error:')} ${result.error}`)
|
|
438
403
|
}
|
|
439
404
|
|
|
440
|
-
// Show full report on error (always, for debugging)
|
|
441
405
|
if (result.fullReport) {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
console.log(` ${style('dim', line)}`)
|
|
406
|
+
writeln(` ${s.dim('─'.repeat(50))}`)
|
|
407
|
+
writeln(` ${s.red('📋')} ${s.bold('Error Report:')}`)
|
|
408
|
+
for (const line of result.fullReport.split('\n')) {
|
|
409
|
+
writeln(` ${s.dim(line)}`)
|
|
447
410
|
}
|
|
448
411
|
}
|
|
449
412
|
}
|
|
450
|
-
|
|
413
|
+
writeln()
|
|
451
414
|
},
|
|
452
415
|
|
|
453
416
|
/**
|
|
@@ -464,13 +427,12 @@ export const logger = {
|
|
|
464
427
|
durationMs?: number
|
|
465
428
|
selectedMemories: Array<{
|
|
466
429
|
content: string
|
|
467
|
-
reasoning: string
|
|
430
|
+
reasoning: string
|
|
468
431
|
signalCount: number
|
|
469
432
|
importance_weight: number
|
|
470
433
|
context_type: string
|
|
471
434
|
semantic_tags: string[]
|
|
472
435
|
isGlobal: boolean
|
|
473
|
-
// Activation signals
|
|
474
436
|
signals: {
|
|
475
437
|
trigger: boolean
|
|
476
438
|
triggerStrength: number
|
|
@@ -486,76 +448,114 @@ export const logger = {
|
|
|
486
448
|
}) {
|
|
487
449
|
const { totalMemories, currentMessage, alreadyInjected, preFiltered, globalCount, projectCount, finalCount, durationMs, selectedMemories } = params
|
|
488
450
|
|
|
489
|
-
const timeStr = durationMs !== undefined ?
|
|
451
|
+
const timeStr = durationMs !== undefined ? s.cyan(`${durationMs.toFixed(1)}ms`) : ''
|
|
490
452
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
console.log(` ${style('dim', 'total:')} ${totalMemories} → ${style('dim', 'filtered:')} ${preFiltered} → ${style('dim', 'candidates:')} ${totalMemories - preFiltered}`)
|
|
494
|
-
console.log(` ${style('dim', 'already injected:')} ${alreadyInjected}`)
|
|
453
|
+
writeln()
|
|
454
|
+
writeln(`${ts()} ${s.magenta('🧠')} ${s.bold('RETRIEVAL')} ${timeStr}`)
|
|
495
455
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
:
|
|
499
|
-
|
|
500
|
-
|
|
456
|
+
// Pipeline summary
|
|
457
|
+
const pipeline = kv([
|
|
458
|
+
['total', `${totalMemories} → ${s.dim('filtered:')} ${preFiltered} → ${s.dim('candidates:')} ${totalMemories - preFiltered}`],
|
|
459
|
+
['already injected', String(alreadyInjected)],
|
|
460
|
+
['message', `"${!_verbose && currentMessage.length > 60 ? currentMessage.slice(0, 60) + '...' : currentMessage}"`],
|
|
461
|
+
], { keyColor: s.dim, indent: 3 })
|
|
462
|
+
writeln(pipeline)
|
|
463
|
+
writeln()
|
|
501
464
|
|
|
502
465
|
// Selection summary
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
466
|
+
writeln(` ${s.cyan('Global:')} ${globalCount} candidates → max 2 selected`)
|
|
467
|
+
writeln(` ${s.cyan('Project:')} ${projectCount} candidates`)
|
|
468
|
+
writeln(` ${s.green('Final:')} ${finalCount} memories selected`)
|
|
469
|
+
writeln()
|
|
507
470
|
|
|
508
471
|
if (selectedMemories.length === 0) {
|
|
509
|
-
|
|
510
|
-
|
|
472
|
+
writeln(` ${s.dim('📭 No relevant memories for this context')}`)
|
|
473
|
+
writeln()
|
|
511
474
|
return
|
|
512
475
|
}
|
|
513
476
|
|
|
514
|
-
//
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
console.log(` ${num} [${signalsStr} • ${imp}] ${type}${scope}`)
|
|
527
|
-
|
|
528
|
-
// Content preview
|
|
529
|
-
const preview = m.content.length > 60
|
|
530
|
-
? m.content.slice(0, 60) + style('dim', '...')
|
|
531
|
-
: m.content
|
|
532
|
-
console.log(` ${style('white', preview)}`)
|
|
533
|
-
|
|
534
|
-
// Show which signals fired with their strengths
|
|
535
|
-
const firedSignals: string[] = []
|
|
536
|
-
if (m.signals.trigger) {
|
|
537
|
-
firedSignals.push(`trigger:${(m.signals.triggerStrength * 100).toFixed(0)}%`)
|
|
538
|
-
}
|
|
539
|
-
if (m.signals.tags) {
|
|
540
|
-
firedSignals.push(`tags:${m.signals.tagCount}`)
|
|
541
|
-
}
|
|
542
|
-
if (m.signals.domain) firedSignals.push('domain')
|
|
543
|
-
if (m.signals.feature) firedSignals.push('feature')
|
|
544
|
-
if (m.signals.content) firedSignals.push('content')
|
|
545
|
-
if (m.signals.vector) {
|
|
546
|
-
firedSignals.push(`vector:${(m.signals.vectorSimilarity * 100).toFixed(0)}%`)
|
|
477
|
+
// ── Verbose: Table view ──
|
|
478
|
+
if (_verbose) {
|
|
479
|
+
const formatSignals = (sig: typeof selectedMemories[0]['signals']): string => {
|
|
480
|
+
const parts: string[] = []
|
|
481
|
+
if (sig.trigger) parts.push(`trig:${(sig.triggerStrength * 100).toFixed(0)}%`)
|
|
482
|
+
if (sig.tags) parts.push(`tags:${sig.tagCount}`)
|
|
483
|
+
if (sig.domain) parts.push('dom')
|
|
484
|
+
if (sig.feature) parts.push('feat')
|
|
485
|
+
if (sig.content) parts.push('content')
|
|
486
|
+
if (sig.vector) parts.push(`vec:${(sig.vectorSimilarity * 100).toFixed(0)}%`)
|
|
487
|
+
return parts.join(', ')
|
|
547
488
|
}
|
|
548
489
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
490
|
+
const rows = selectedMemories.map((m, i) => ({
|
|
491
|
+
'#': String(i + 1),
|
|
492
|
+
sig: String(m.signalCount),
|
|
493
|
+
imp: `${(m.importance_weight * 100).toFixed(0)}%`,
|
|
494
|
+
type: m.context_type.toUpperCase(),
|
|
495
|
+
scope: m.isGlobal ? 'G' : 'P',
|
|
496
|
+
signals: formatSignals(m.signals),
|
|
497
|
+
}))
|
|
498
|
+
|
|
499
|
+
writeln(table(rows, {
|
|
500
|
+
columns: [
|
|
501
|
+
{ key: '#', align: 'right' as const, width: 4 },
|
|
502
|
+
{ key: 'sig', align: 'center' as const, width: 5 },
|
|
503
|
+
{ key: 'imp', align: 'right' as const, width: 6 },
|
|
504
|
+
{ key: 'type', label: 'type', minWidth: 12 },
|
|
505
|
+
{ key: 'scope', align: 'center' as const, width: 7 },
|
|
506
|
+
{ key: 'signals', minWidth: 20 },
|
|
507
|
+
],
|
|
508
|
+
border: 'rounded',
|
|
509
|
+
borderColor: 'magenta',
|
|
510
|
+
}))
|
|
511
|
+
writeln()
|
|
512
|
+
|
|
513
|
+
// Full content for each memory in verbose
|
|
514
|
+
selectedMemories.forEach((m, i) => {
|
|
515
|
+
const emoji = emojiMap[m.context_type?.toLowerCase()] ?? '📝'
|
|
516
|
+
writeln(` ${s.dim(`${i + 1}.`)} ${emoji} ${m.content}`)
|
|
517
|
+
writeln()
|
|
518
|
+
})
|
|
552
519
|
|
|
553
|
-
|
|
554
|
-
|
|
520
|
+
} else {
|
|
521
|
+
// ── Normal: Compact list ──
|
|
522
|
+
writeln(s.dim(' ─'.repeat(30)))
|
|
523
|
+
writeln(` ${s.bold('SELECTION DETAILS')}`)
|
|
524
|
+
writeln()
|
|
525
|
+
|
|
526
|
+
selectedMemories.forEach((m, i) => {
|
|
527
|
+
const num = s.dim(`${i + 1}.`)
|
|
528
|
+
const signalsStr = s.green(`${m.signalCount} signals`)
|
|
529
|
+
const imp = s.magenta(`imp:${(m.importance_weight * 100).toFixed(0)}%`)
|
|
530
|
+
const type = s.yellow(m.context_type.toUpperCase())
|
|
531
|
+
const scope = m.isGlobal ? s.blue(' [G]') : ''
|
|
532
|
+
|
|
533
|
+
writeln(` ${num} [${signalsStr} • ${imp}] ${type}${scope}`)
|
|
534
|
+
|
|
535
|
+
const preview = m.content.length > 60
|
|
536
|
+
? m.content.slice(0, 60) + s.dim('...')
|
|
537
|
+
: m.content
|
|
538
|
+
writeln(` ${preview}`)
|
|
539
|
+
|
|
540
|
+
// Signal details
|
|
541
|
+
const firedSignals: string[] = []
|
|
542
|
+
if (m.signals.trigger) firedSignals.push(`trigger:${(m.signals.triggerStrength * 100).toFixed(0)}%`)
|
|
543
|
+
if (m.signals.tags) firedSignals.push(`tags:${m.signals.tagCount}`)
|
|
544
|
+
if (m.signals.domain) firedSignals.push('domain')
|
|
545
|
+
if (m.signals.feature) firedSignals.push('feature')
|
|
546
|
+
if (m.signals.content) firedSignals.push('content')
|
|
547
|
+
if (m.signals.vector) firedSignals.push(`vector:${(m.signals.vectorSimilarity * 100).toFixed(0)}%`)
|
|
548
|
+
|
|
549
|
+
if (firedSignals.length > 0) {
|
|
550
|
+
writeln(` ${s.cyan('signals:')} ${firedSignals.join(', ')}`)
|
|
551
|
+
}
|
|
552
|
+
writeln()
|
|
553
|
+
})
|
|
554
|
+
}
|
|
555
555
|
},
|
|
556
556
|
|
|
557
557
|
/**
|
|
558
|
-
* Log score distribution for diagnostics
|
|
558
|
+
* Log score distribution for diagnostics
|
|
559
559
|
*/
|
|
560
560
|
logScoreDistribution(params: {
|
|
561
561
|
totalCandidates: number
|
|
@@ -581,46 +581,74 @@ export const logger = {
|
|
|
581
581
|
}) {
|
|
582
582
|
const { totalCandidates, passedGatekeeper, rejectedByGatekeeper, buckets, stats, signalBreakdown } = params
|
|
583
583
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
584
|
+
writeln()
|
|
585
|
+
writeln(s.dim(' ─'.repeat(30)))
|
|
586
|
+
writeln(` ${s.bold('ACTIVATION SIGNALS')}`)
|
|
587
|
+
writeln()
|
|
588
588
|
|
|
589
589
|
// Gatekeeper stats
|
|
590
590
|
const passRate = totalCandidates > 0 ? ((passedGatekeeper / totalCandidates) * 100).toFixed(0) : '0'
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
591
|
+
writeln(` ${s.dim('Activated:')} ${s.green(String(passedGatekeeper))}/${totalCandidates} (${passRate}%)`)
|
|
592
|
+
writeln(` ${s.dim('Rejected:')} ${rejectedByGatekeeper} (< 2 signals)`)
|
|
593
|
+
writeln()
|
|
594
594
|
|
|
595
595
|
// Signal breakdown
|
|
596
596
|
if (signalBreakdown && signalBreakdown.total > 0) {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
597
|
+
if (_verbose) {
|
|
598
|
+
// Verbose: table view
|
|
599
|
+
const signals = [
|
|
600
|
+
{ signal: 'trigger', count: signalBreakdown.trigger },
|
|
601
|
+
{ signal: 'tags', count: signalBreakdown.tags },
|
|
602
|
+
{ signal: 'domain', count: signalBreakdown.domain },
|
|
603
|
+
{ signal: 'feature', count: signalBreakdown.feature },
|
|
604
|
+
{ signal: 'content', count: signalBreakdown.content },
|
|
605
|
+
{ signal: 'files', count: signalBreakdown.files },
|
|
606
|
+
{ signal: 'vector', count: signalBreakdown.vector },
|
|
607
|
+
].filter(sig => sig.count > 0).map(sig => ({
|
|
608
|
+
signal: sig.signal,
|
|
609
|
+
count: String(sig.count),
|
|
610
|
+
pct: `${((sig.count / signalBreakdown.total) * 100).toFixed(0)}%`,
|
|
611
|
+
}))
|
|
612
|
+
|
|
613
|
+
writeln(table(signals, {
|
|
614
|
+
border: 'rounded',
|
|
615
|
+
borderColor: 'cyan',
|
|
616
|
+
columns: [
|
|
617
|
+
{ key: 'signal', label: 'Signal' },
|
|
618
|
+
{ key: 'count', label: 'Count', align: 'right' as const },
|
|
619
|
+
{ key: 'pct', label: '%', align: 'right' as const },
|
|
620
|
+
],
|
|
621
|
+
}))
|
|
622
|
+
} else {
|
|
623
|
+
// Normal: bar chart
|
|
624
|
+
writeln(` ${s.cyan('Signal Breakdown:')}`)
|
|
625
|
+
const signals = [
|
|
626
|
+
{ name: 'trigger', count: signalBreakdown.trigger },
|
|
627
|
+
{ name: 'tags', count: signalBreakdown.tags },
|
|
628
|
+
{ name: 'domain', count: signalBreakdown.domain },
|
|
629
|
+
{ name: 'feature', count: signalBreakdown.feature },
|
|
630
|
+
{ name: 'content', count: signalBreakdown.content },
|
|
631
|
+
{ name: 'files', count: signalBreakdown.files },
|
|
632
|
+
{ name: 'vector', count: signalBreakdown.vector },
|
|
633
|
+
]
|
|
634
|
+
for (const sig of signals) {
|
|
635
|
+
const pct = ((sig.count / signalBreakdown.total) * 100).toFixed(0)
|
|
636
|
+
const bar = '█'.repeat(Math.round(sig.count / signalBreakdown.total * 20))
|
|
637
|
+
writeln(` ${sig.name.padEnd(8)} ${bar.padEnd(20)} ${sig.count} (${pct}%)`)
|
|
638
|
+
}
|
|
611
639
|
}
|
|
612
|
-
|
|
640
|
+
writeln()
|
|
613
641
|
}
|
|
614
642
|
|
|
615
643
|
// Stats
|
|
616
644
|
if (stats.max > 0) {
|
|
617
|
-
|
|
618
|
-
|
|
645
|
+
writeln(` ${s.cyan('Signals:')} min=${stats.min} max=${stats.max} mean=${stats.mean}`)
|
|
646
|
+
writeln()
|
|
619
647
|
}
|
|
620
648
|
|
|
621
649
|
// Histogram by signal count
|
|
622
650
|
if (Object.keys(buckets).length > 0) {
|
|
623
|
-
|
|
651
|
+
writeln(` ${s.bold('Distribution:')}`)
|
|
624
652
|
const maxBucketCount = Math.max(...Object.values(buckets), 1)
|
|
625
653
|
const bucketOrder = ['2 signals', '3 signals', '4 signals', '5 signals', '6 signals', '7 signals']
|
|
626
654
|
|
|
@@ -628,14 +656,37 @@ export const logger = {
|
|
|
628
656
|
const count = buckets[bucket] ?? 0
|
|
629
657
|
if (count > 0 || bucket === '2 signals') {
|
|
630
658
|
const barLen = Math.round((count / maxBucketCount) * 25)
|
|
631
|
-
const bar = '█'.repeat(barLen) +
|
|
659
|
+
const bar = '█'.repeat(barLen) + s.dim('░'.repeat(25 - barLen))
|
|
632
660
|
const countStr = count.toString().padStart(3)
|
|
633
|
-
|
|
661
|
+
writeln(` ${s.dim(bucket.padEnd(10))} ${bar} ${s.cyan(countStr)}`)
|
|
634
662
|
}
|
|
635
663
|
}
|
|
636
|
-
|
|
664
|
+
writeln()
|
|
637
665
|
}
|
|
638
666
|
},
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Log the full injected payload (verbose only)
|
|
670
|
+
* Shows exactly what gets sent to the AI's context
|
|
671
|
+
*/
|
|
672
|
+
logInjectedPayload(payload: string, type: 'primer' | 'memories' | 'action_items', count?: number) {
|
|
673
|
+
if (!_verbose) return
|
|
674
|
+
|
|
675
|
+
const titles: Record<string, string> = {
|
|
676
|
+
primer: '📖 Injected Payload (Session Primer)',
|
|
677
|
+
memories: `✨ Injected Payload (${count ?? 0} memor${count === 1 ? 'y' : 'ies'})`,
|
|
678
|
+
action_items: `🎯 Injected Payload (${count ?? 0} action item${count === 1 ? '' : 's'})`,
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
writeln()
|
|
682
|
+
writeln(box(payload, {
|
|
683
|
+
border: 'rounded',
|
|
684
|
+
borderColor: 'cyan',
|
|
685
|
+
title: titles[type],
|
|
686
|
+
titleColor: s.bold,
|
|
687
|
+
}))
|
|
688
|
+
writeln()
|
|
689
|
+
},
|
|
639
690
|
}
|
|
640
691
|
|
|
641
692
|
export default logger
|