@massu/core 1.10.7 → 1.12.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.
Files changed (177) hide show
  1. package/CHANGELOG.md +1303 -0
  2. package/dist/adapter.js +6 -4
  3. package/dist/cli.js +559 -400
  4. package/dist/detect/adapters/.bundle-shasums.json +4 -7
  5. package/dist/detect/adapters/rails.js +6 -4
  6. package/dist/detect/adapters/spring.js +6 -4
  7. package/dist/detect/adapters/tree-sitter-loader.js +6 -4
  8. package/dist/hooks/auto-learning-pipeline.js +24 -1
  9. package/dist/hooks/classify-failure.js +32 -3
  10. package/dist/hooks/cost-tracker.js +24 -0
  11. package/dist/hooks/fix-detector.js +97 -1
  12. package/dist/hooks/incident-pipeline.js +30 -1
  13. package/dist/hooks/intent-suggester.js +6 -3
  14. package/dist/hooks/post-edit-context.js +24 -1
  15. package/dist/hooks/post-tool-use.js +66 -18
  16. package/dist/hooks/pre-compact.js +27 -2
  17. package/dist/hooks/pre-delete-check.js +48 -22
  18. package/dist/hooks/pre-tool-use-gate.js +962 -0
  19. package/dist/hooks/quality-event.js +24 -0
  20. package/dist/hooks/rule-enforcement-pipeline.js +24 -1
  21. package/dist/hooks/security-gate.js +29 -16
  22. package/dist/hooks/session-end.js +24 -0
  23. package/dist/hooks/session-start.js +8710 -8460
  24. package/dist/hooks/user-prompt.js +24 -0
  25. package/package.json +15 -18
  26. package/commands/README.md +0 -134
  27. package/src/adapter.ts +0 -121
  28. package/src/adr-generator.ts +0 -292
  29. package/src/analytics.ts +0 -373
  30. package/src/audit-trail.ts +0 -450
  31. package/src/backfill-sessions.ts +0 -182
  32. package/src/changelog-generator.ts +0 -178
  33. package/src/cli.ts +0 -246
  34. package/src/cloud-sync.ts +0 -234
  35. package/src/commands/adapters.ts +0 -824
  36. package/src/commands/changelog.ts +0 -165
  37. package/src/commands/config-check-drift.ts +0 -133
  38. package/src/commands/config-refresh.ts +0 -396
  39. package/src/commands/config-upgrade.ts +0 -127
  40. package/src/commands/doctor.ts +0 -485
  41. package/src/commands/hook-runner.ts +0 -145
  42. package/src/commands/init.ts +0 -1823
  43. package/src/commands/install-commands.ts +0 -692
  44. package/src/commands/install-hooks.ts +0 -27
  45. package/src/commands/permissions.ts +0 -150
  46. package/src/commands/refresh-log.ts +0 -37
  47. package/src/commands/show-template.ts +0 -65
  48. package/src/commands/template-engine.ts +0 -301
  49. package/src/commands/watch.ts +0 -430
  50. package/src/config.ts +0 -765
  51. package/src/cost-tracker.ts +0 -355
  52. package/src/db.ts +0 -370
  53. package/src/dependency-scorer.ts +0 -337
  54. package/src/detect/adapters/aspnet.ts +0 -4
  55. package/src/detect/adapters/discover.ts +0 -469
  56. package/src/detect/adapters/file-sampler.ts +0 -225
  57. package/src/detect/adapters/go-chi.ts +0 -4
  58. package/src/detect/adapters/index.ts +0 -51
  59. package/src/detect/adapters/nextjs-trpc.ts +0 -166
  60. package/src/detect/adapters/parse-guard.ts +0 -133
  61. package/src/detect/adapters/phoenix.ts +0 -4
  62. package/src/detect/adapters/python-django.ts +0 -208
  63. package/src/detect/adapters/python-fastapi.ts +0 -223
  64. package/src/detect/adapters/python-flask.ts +0 -235
  65. package/src/detect/adapters/query-helpers.ts +0 -170
  66. package/src/detect/adapters/rails.ts +0 -4
  67. package/src/detect/adapters/runner.ts +0 -284
  68. package/src/detect/adapters/spring.ts +0 -4
  69. package/src/detect/adapters/swift-swiftui.ts +0 -171
  70. package/src/detect/adapters/tree-sitter-loader.ts +0 -517
  71. package/src/detect/adapters/types.ts +0 -191
  72. package/src/detect/codebase-introspector.ts +0 -210
  73. package/src/detect/domain-inferrer.ts +0 -197
  74. package/src/detect/drift.ts +0 -199
  75. package/src/detect/framework-detector.ts +0 -307
  76. package/src/detect/index.ts +0 -209
  77. package/src/detect/manifest-registry.ts +0 -261
  78. package/src/detect/migrate.ts +0 -362
  79. package/src/detect/monorepo-detector.ts +0 -348
  80. package/src/detect/package-detector.ts +0 -828
  81. package/src/detect/passthrough.ts +0 -108
  82. package/src/detect/regex-fallback.ts +0 -449
  83. package/src/detect/source-dir-detector.ts +0 -297
  84. package/src/detect/vr-command-map.ts +0 -167
  85. package/src/docs-map.json +0 -100
  86. package/src/docs-tools.ts +0 -519
  87. package/src/domains.ts +0 -182
  88. package/src/hooks/auto-learning-pipeline.ts +0 -228
  89. package/src/hooks/classify-failure.ts +0 -259
  90. package/src/hooks/cost-tracker.ts +0 -66
  91. package/src/hooks/fix-detector.ts +0 -186
  92. package/src/hooks/incident-pipeline.ts +0 -190
  93. package/src/hooks/intent-suggester.ts +0 -131
  94. package/src/hooks/post-edit-context.ts +0 -91
  95. package/src/hooks/post-tool-use.ts +0 -413
  96. package/src/hooks/pre-compact.ts +0 -168
  97. package/src/hooks/pre-delete-check.ts +0 -243
  98. package/src/hooks/quality-event.ts +0 -127
  99. package/src/hooks/rule-enforcement-pipeline.ts +0 -159
  100. package/src/hooks/security-gate.ts +0 -153
  101. package/src/hooks/session-end.ts +0 -467
  102. package/src/hooks/session-start.ts +0 -436
  103. package/src/hooks/user-prompt.ts +0 -157
  104. package/src/import-resolver.ts +0 -226
  105. package/src/knowledge-db.ts +0 -176
  106. package/src/knowledge-indexer.ts +0 -708
  107. package/src/knowledge-tools.ts +0 -1414
  108. package/src/lib/fileLock.ts +0 -203
  109. package/src/lib/gitToplevel.ts +0 -22
  110. package/src/lib/hook-registry.ts +0 -43
  111. package/src/lib/installLock.ts +0 -66
  112. package/src/lib/memory-path.ts +0 -49
  113. package/src/lib/pidLiveness.ts +0 -67
  114. package/src/lib/settings-local.ts +0 -110
  115. package/src/lib/sql-table-names.ts +0 -71
  116. package/src/license.ts +0 -547
  117. package/src/lsp/auto-detect.ts +0 -98
  118. package/src/lsp/client.ts +0 -776
  119. package/src/lsp/enrich.ts +0 -127
  120. package/src/lsp/types.ts +0 -221
  121. package/src/memory-db.ts +0 -1614
  122. package/src/memory-file-ingest.ts +0 -128
  123. package/src/memory-tools.ts +0 -424
  124. package/src/middleware-tree.ts +0 -71
  125. package/src/observability-tools.ts +0 -343
  126. package/src/observation-extractor.ts +0 -418
  127. package/src/page-deps.ts +0 -285
  128. package/src/permissions.ts +0 -422
  129. package/src/prompt-analyzer.ts +0 -332
  130. package/src/python/coupling-detector.ts +0 -125
  131. package/src/python/domain-enforcer.ts +0 -84
  132. package/src/python/impact-analyzer.ts +0 -96
  133. package/src/python/import-parser.ts +0 -244
  134. package/src/python/import-resolver.ts +0 -136
  135. package/src/python/migration-indexer.ts +0 -116
  136. package/src/python/migration-parser.ts +0 -332
  137. package/src/python/model-indexer.ts +0 -71
  138. package/src/python/model-parser.ts +0 -279
  139. package/src/python/route-indexer.ts +0 -59
  140. package/src/python/route-parser.ts +0 -317
  141. package/src/python-tools.ts +0 -630
  142. package/src/regression-detector.ts +0 -319
  143. package/src/rules.ts +0 -57
  144. package/src/schema-mapper.ts +0 -232
  145. package/src/security/adapter-origin.ts +0 -130
  146. package/src/security/adapter-verifier.ts +0 -319
  147. package/src/security/atomic-write.ts +0 -164
  148. package/src/security/fetcher.ts +0 -200
  149. package/src/security/install-tracking.ts +0 -319
  150. package/src/security/license-pubkey.generated.ts +0 -30
  151. package/src/security/license-response-verifier.ts +0 -149
  152. package/src/security/local-fingerprint.ts +0 -225
  153. package/src/security/manifest-cache.ts +0 -333
  154. package/src/security/manifest-schema.ts +0 -129
  155. package/src/security/registry-pubkey.generated.ts +0 -35
  156. package/src/security/telemetry.ts +0 -320
  157. package/src/security-scorer.ts +0 -405
  158. package/src/security-utils.ts +0 -133
  159. package/src/sentinel-db.ts +0 -580
  160. package/src/sentinel-scanner.ts +0 -406
  161. package/src/sentinel-tools.ts +0 -513
  162. package/src/sentinel-types.ts +0 -140
  163. package/src/server-dispatch.ts +0 -225
  164. package/src/server.ts +0 -107
  165. package/src/session-archiver.ts +0 -111
  166. package/src/session-state-generator.ts +0 -174
  167. package/src/team-knowledge.ts +0 -407
  168. package/src/tool-db-needs.ts +0 -232
  169. package/src/tools.ts +0 -1205
  170. package/src/transcript-parser.ts +0 -458
  171. package/src/trpc-index.ts +0 -215
  172. package/src/validate-features-runner.ts +0 -107
  173. package/src/validation-engine.ts +0 -358
  174. package/src/watch/daemon.ts +0 -385
  175. package/src/watch/lockfile-detector.ts +0 -65
  176. package/src/watch/paths.ts +0 -279
  177. package/src/watch/state.ts +0 -178
@@ -1,343 +0,0 @@
1
- // Copyright (c) 2026 Massu. All rights reserved.
2
- // Licensed under BSL 1.1 - see LICENSE file for details.
3
-
4
- import type Database from 'better-sqlite3';
5
- import type { ToolDefinition, ToolResult } from './tools.ts';
6
- import {
7
- getConversationTurns,
8
- searchConversationTurns,
9
- getToolPatterns,
10
- getSessionStats,
11
- getObservabilityDbSize,
12
- pruneOldConversationTurns,
13
- } from './memory-db.ts';
14
- import { getConfig } from './config.ts';
15
-
16
- /** Prefix a base tool name with the configured tool prefix. */
17
- function p(baseName: string): string {
18
- return `${getConfig().toolPrefix}_${baseName}`;
19
- }
20
-
21
- // ============================================================
22
- // Observability MCP Tools (P3-001 through P3-004)
23
- // ============================================================
24
-
25
- const OBSERVABILITY_BASE_NAMES = new Set([
26
- 'session_replay',
27
- 'prompt_analysis',
28
- 'tool_patterns',
29
- 'session_stats',
30
- ]);
31
-
32
- export function isObservabilityTool(name: string): boolean {
33
- const prefix = getConfig().toolPrefix + '_';
34
- const baseName = name.startsWith(prefix) ? name.slice(prefix.length) : name;
35
- return OBSERVABILITY_BASE_NAMES.has(baseName);
36
- }
37
-
38
- export function getObservabilityToolDefinitions(): ToolDefinition[] {
39
- return [
40
- // P3-001: session_replay
41
- {
42
- name: p('session_replay'),
43
- description: 'Replay a past session as a chronological conversation with user prompts, assistant responses, and optional tool call details. Requires conversation data captured by the session-end hook.',
44
- inputSchema: {
45
- type: 'object',
46
- properties: {
47
- session_id: { type: 'string', description: 'Session ID to replay' },
48
- turn_from: { type: 'number', description: 'Start from turn N (optional)' },
49
- turn_to: { type: 'number', description: 'End at turn N (optional)' },
50
- include_tool_calls: { type: 'boolean', description: 'Include tool call details in replay (default: false)' },
51
- },
52
- required: ['session_id'],
53
- },
54
- },
55
- // P3-002: prompt_analysis
56
- {
57
- name: p('prompt_analysis'),
58
- description: 'Search and analyze prompts across sessions using FTS5 full-text search on conversation_turns_fts. Find prompts by keyword, filter by date range or complexity (tool call count).',
59
- inputSchema: {
60
- type: 'object',
61
- properties: {
62
- query: { type: 'string', description: 'FTS5 search query within prompts and responses' },
63
- session_id: { type: 'string', description: 'Filter to specific session' },
64
- date_from: { type: 'string', description: 'Start date (ISO format)' },
65
- date_to: { type: 'string', description: 'End date (ISO format)' },
66
- min_tool_calls: { type: 'number', description: 'Filter by turns with N+ tool calls' },
67
- limit: { type: 'number', description: 'Max results (default: 20)' },
68
- },
69
- required: [],
70
- },
71
- },
72
- // P3-003: tool_patterns
73
- {
74
- name: p('tool_patterns'),
75
- description: 'Analyze tool usage patterns across sessions. Shows counts, success rates, average I/O sizes per tool. Can group by tool name, session, or day.',
76
- inputSchema: {
77
- type: 'object',
78
- properties: {
79
- session_id: { type: 'string', description: 'Filter to specific session' },
80
- tool_name: { type: 'string', description: 'Filter to specific tool (Read, Write, Edit, Bash, etc.)' },
81
- date_from: { type: 'string', description: 'Start date (ISO format)' },
82
- group_by: { type: 'string', description: '"tool" | "session" | "day" (default: "tool")' },
83
- },
84
- required: [],
85
- },
86
- },
87
- // P3-004: session_stats
88
- {
89
- name: p('session_stats'),
90
- description: 'Get per-session statistics: turn count, tool call breakdown, token usage, duration. Includes database size monitoring for observability data.',
91
- inputSchema: {
92
- type: 'object',
93
- properties: {
94
- session_id: { type: 'string', description: 'Specific session ID (default: all recent)' },
95
- limit: { type: 'number', description: 'Max sessions to show (default: 10)' },
96
- },
97
- required: [],
98
- },
99
- },
100
- ];
101
- }
102
-
103
- export function handleObservabilityToolCall(
104
- name: string,
105
- args: Record<string, unknown>,
106
- memoryDb: Database.Database
107
- ): ToolResult {
108
- try {
109
- const prefix = getConfig().toolPrefix + '_';
110
- const baseName = name.startsWith(prefix) ? name.slice(prefix.length) : name;
111
-
112
- switch (baseName) {
113
- case 'session_replay':
114
- return handleSessionReplay(args, memoryDb);
115
- case 'prompt_analysis':
116
- return handlePromptAnalysis(args, memoryDb);
117
- case 'tool_patterns':
118
- return handleToolPatterns(args, memoryDb);
119
- case 'session_stats':
120
- return handleSessionStats(args, memoryDb);
121
- default:
122
- return text(`Unknown observability tool: ${name}`);
123
- }
124
- } catch (error) {
125
- return text(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}`);
126
- }
127
- }
128
-
129
- // ============================================================
130
- // Tool Handlers
131
- // ============================================================
132
-
133
- function handleSessionReplay(args: Record<string, unknown>, db: Database.Database): ToolResult {
134
- const sessionId = args.session_id as string;
135
- if (!sessionId) return text('Error: session_id is required');
136
-
137
- const includeToolCalls = (args.include_tool_calls as boolean) ?? false;
138
- const turns = getConversationTurns(db, sessionId, {
139
- turnFrom: args.turn_from as number | undefined,
140
- turnTo: args.turn_to as number | undefined,
141
- includeToolCalls,
142
- });
143
-
144
- if (turns.length === 0) {
145
- return text(`No conversation turns found for session ${sessionId.slice(0, 8)}...\nNote: Conversation data is captured by the session-end hook. Only sessions that have ended will have replay data.`);
146
- }
147
-
148
- const lines = [`## Session Replay: ${sessionId.slice(0, 12)}...`, `Turns: ${turns.length}`, ''];
149
-
150
- for (const turn of turns) {
151
- lines.push(`### Turn ${turn.turn_number}`);
152
- lines.push(`**User**: ${turn.user_prompt.slice(0, 2000)}`);
153
- lines.push('');
154
-
155
- if (turn.assistant_response) {
156
- lines.push(`**Assistant**: ${turn.assistant_response.slice(0, 2000)}`);
157
- lines.push('');
158
- }
159
-
160
- if (includeToolCalls && turn.tool_calls_json) {
161
- try {
162
- const toolCalls = JSON.parse(turn.tool_calls_json) as Array<{ name: string; input_summary: string; is_error: boolean }>;
163
- if (toolCalls.length > 0) {
164
- lines.push(`**Tool Calls** (${toolCalls.length}):`);
165
- for (const tc of toolCalls) {
166
- const status = tc.is_error ? ' [ERROR]' : '';
167
- lines.push(` - ${tc.name}: ${tc.input_summary}${status}`);
168
- }
169
- lines.push('');
170
- }
171
- } catch (_e) {
172
- // Skip invalid JSON
173
- }
174
- }
175
-
176
- if (turn.tool_call_count > 0) {
177
- lines.push(`_${turn.tool_call_count} tool calls | ~${turn.prompt_tokens ?? 0} prompt tokens | ~${turn.response_tokens ?? 0} response tokens_`);
178
- }
179
- lines.push('---');
180
- lines.push('');
181
- }
182
-
183
- return text(lines.join('\n'));
184
- }
185
-
186
- function handlePromptAnalysis(args: Record<string, unknown>, db: Database.Database): ToolResult {
187
- const query = args.query as string | undefined;
188
-
189
- if (!query) {
190
- // No FTS query -- show recent turns summary
191
- const recentTurns = db.prepare(
192
- 'SELECT ct.session_id, ct.turn_number, ct.user_prompt, ct.tool_call_count, ct.response_tokens, ct.created_at FROM conversation_turns ct ORDER BY ct.created_at_epoch DESC LIMIT ?'
193
- ).all((args.limit as number) ?? 20) as Array<{
194
- session_id: string; turn_number: number; user_prompt: string;
195
- tool_call_count: number; response_tokens: number | null; created_at: string;
196
- }>;
197
-
198
- if (recentTurns.length === 0) {
199
- return text('No conversation turns recorded yet.');
200
- }
201
-
202
- const lines = ['## Recent Prompts', ''];
203
- lines.push('| Session | Turn | Prompt (truncated) | Tools | Response Tokens | Date |');
204
- lines.push('|---------|------|--------------------|-------|-----------------|------|');
205
-
206
- for (const t of recentTurns) {
207
- lines.push(`| ${t.session_id.slice(0, 8)}... | ${t.turn_number} | ${t.user_prompt.slice(0, 60).replace(/\|/g, '\\|')} | ${t.tool_call_count} | ${t.response_tokens ?? '-'} | ${t.created_at.split('T')[0]} |`);
208
- }
209
-
210
- return text(lines.join('\n'));
211
- }
212
-
213
- const results = searchConversationTurns(db, query, {
214
- sessionId: args.session_id as string | undefined,
215
- dateFrom: args.date_from as string | undefined,
216
- dateTo: args.date_to as string | undefined,
217
- minToolCalls: args.min_tool_calls as number | undefined,
218
- limit: args.limit as number | undefined,
219
- });
220
-
221
- if (results.length === 0) {
222
- return text(`No prompts found matching "${query}".`);
223
- }
224
-
225
- const lines = [`## Prompt Search: "${query}" (${results.length} results)`, ''];
226
- lines.push('| Session | Turn | Prompt (truncated) | Tools | Response Tokens | Date |');
227
- lines.push('|---------|------|--------------------|-------|-----------------|------|');
228
-
229
- for (const r of results) {
230
- lines.push(`| ${r.session_id.slice(0, 8)}... | ${r.turn_number} | ${r.user_prompt.slice(0, 60).replace(/\|/g, '\\|')} | ${r.tool_call_count} | ${r.response_tokens ?? '-'} | ${r.created_at.split('T')[0]} |`);
231
- }
232
-
233
- return text(lines.join('\n'));
234
- }
235
-
236
- function handleToolPatterns(args: Record<string, unknown>, db: Database.Database): ToolResult {
237
- const groupBy = (args.group_by as 'tool' | 'session' | 'day') ?? 'tool';
238
- const patterns = getToolPatterns(db, {
239
- sessionId: args.session_id as string | undefined,
240
- toolName: args.tool_name as string | undefined,
241
- dateFrom: args.date_from as string | undefined,
242
- groupBy,
243
- });
244
-
245
- if (patterns.length === 0) {
246
- return text('No tool usage data recorded yet.');
247
- }
248
-
249
- const lines = [`## Tool Usage Patterns (grouped by ${groupBy})`, ''];
250
-
251
- switch (groupBy) {
252
- case 'tool':
253
- lines.push('| Tool | Calls | Successes | Failures | Success Rate | Avg Output Size | Avg Input Size |');
254
- lines.push('|------|-------|-----------|----------|--------------|-----------------|----------------|');
255
- for (const p of patterns) {
256
- const total = p.call_count as number;
257
- const successes = p.successes as number;
258
- const failures = p.failures as number;
259
- const rate = total > 0 ? Math.round((successes / total) * 100) : 0;
260
- lines.push(`| ${p.tool_name} | ${total} | ${successes} | ${failures} | ${rate}% | ${Math.round(p.avg_output_size as number ?? 0)} | ${Math.round(p.avg_input_size as number ?? 0)} |`);
261
- }
262
- break;
263
- case 'session':
264
- lines.push('| Session | Calls | Unique Tools | Successes | Failures | Avg Output Size |');
265
- lines.push('|---------|-------|--------------|-----------|----------|-----------------|');
266
- for (const p of patterns) {
267
- lines.push(`| ${(p.session_id as string).slice(0, 8)}... | ${p.call_count} | ${p.unique_tools} | ${p.successes} | ${p.failures} | ${Math.round(p.avg_output_size as number ?? 0)} |`);
268
- }
269
- break;
270
- case 'day':
271
- lines.push('| Day | Calls | Unique Tools | Successes |');
272
- lines.push('|-----|-------|--------------|-----------|');
273
- for (const p of patterns) {
274
- lines.push(`| ${p.day} | ${p.call_count} | ${p.unique_tools} | ${p.successes} |`);
275
- }
276
- break;
277
- }
278
-
279
- return text(lines.join('\n'));
280
- }
281
-
282
- function handleSessionStats(args: Record<string, unknown>, db: Database.Database): ToolResult {
283
- const stats = getSessionStats(db, {
284
- sessionId: args.session_id as string | undefined,
285
- limit: args.limit as number | undefined,
286
- });
287
-
288
- if (stats.length === 0) {
289
- return text('No session stats available.');
290
- }
291
-
292
- const lines = ['## Session Statistics', ''];
293
-
294
- if (args.session_id) {
295
- // Detailed single session view
296
- const s = stats[0];
297
- lines.push(`### Session: ${(s.session_id as string).slice(0, 12)}...`);
298
- lines.push(`- **Status**: ${s.status}`);
299
- lines.push(`- **Started**: ${s.started_at ?? '-'}`);
300
- lines.push(`- **Ended**: ${s.ended_at ?? '-'}`);
301
- lines.push(`- **Turns**: ${s.turn_count}`);
302
- lines.push(`- **Total Tool Calls**: ${s.total_tool_calls}`);
303
- lines.push(`- **Prompt Tokens**: ~${s.total_prompt_tokens}`);
304
- lines.push(`- **Response Tokens**: ~${s.total_response_tokens}`);
305
- lines.push('');
306
-
307
- const breakdown = s.tool_breakdown as Array<Record<string, unknown>> | undefined;
308
- if (breakdown && breakdown.length > 0) {
309
- lines.push('#### Tool Breakdown');
310
- lines.push('| Tool | Calls |');
311
- lines.push('|------|-------|');
312
- for (const tb of breakdown) {
313
- lines.push(`| ${tb.tool_name} | ${tb.count} |`);
314
- }
315
- }
316
- } else {
317
- // Multi-session summary
318
- lines.push('| Session | Status | Turns | Tool Calls | Prompt Tokens | Response Tokens | Started |');
319
- lines.push('|---------|--------|-------|------------|---------------|-----------------|---------|');
320
- for (const s of stats) {
321
- lines.push(`| ${(s.session_id as string).slice(0, 8)}... | ${s.status} | ${s.turn_count} | ${s.total_tool_calls} | ~${s.total_prompt_tokens} | ~${s.total_response_tokens} | ${(s.started_at as string)?.split('T')[0] ?? '-'} |`);
322
- }
323
- }
324
-
325
- // Database size info (P4-001)
326
- lines.push('');
327
- lines.push('### Database Size');
328
- const dbSize = getObservabilityDbSize(db);
329
- lines.push(`- Conversation turns: ${dbSize.conversation_turns_count}`);
330
- lines.push(`- Tool call details: ${dbSize.tool_call_details_count}`);
331
- lines.push(`- Observations: ${dbSize.observations_count}`);
332
- lines.push(`- Database size: ~${dbSize.estimated_size_mb} MB`);
333
-
334
- return text(lines.join('\n'));
335
- }
336
-
337
- // ============================================================
338
- // Helpers
339
- // ============================================================
340
-
341
- function text(content: string): ToolResult {
342
- return { content: [{ type: 'text', text: content }] };
343
- }