@massu/core 1.10.8 → 1.12.1

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 (178) hide show
  1. package/CHANGELOG.md +1334 -0
  2. package/dist/adapter.js +6 -4
  3. package/dist/cli.js +581 -448
  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 +67 -19
  16. package/dist/hooks/pre-compact.js +28 -3
  17. package/dist/hooks/pre-delete-check.js +53 -27
  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 +31 -6
  23. package/dist/hooks/session-start.js +8709 -8459
  24. package/dist/hooks/user-prompt.js +25 -1
  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/framework-supports.ts +0 -85
  110. package/src/lib/gitToplevel.ts +0 -22
  111. package/src/lib/hook-registry.ts +0 -43
  112. package/src/lib/installLock.ts +0 -66
  113. package/src/lib/memory-path.ts +0 -49
  114. package/src/lib/pidLiveness.ts +0 -67
  115. package/src/lib/settings-local.ts +0 -110
  116. package/src/lib/sql-table-names.ts +0 -71
  117. package/src/license.ts +0 -547
  118. package/src/lsp/auto-detect.ts +0 -98
  119. package/src/lsp/client.ts +0 -776
  120. package/src/lsp/enrich.ts +0 -127
  121. package/src/lsp/types.ts +0 -221
  122. package/src/memory-db.ts +0 -1614
  123. package/src/memory-file-ingest.ts +0 -128
  124. package/src/memory-tools.ts +0 -424
  125. package/src/middleware-tree.ts +0 -71
  126. package/src/observability-tools.ts +0 -343
  127. package/src/observation-extractor.ts +0 -418
  128. package/src/page-deps.ts +0 -285
  129. package/src/permissions.ts +0 -422
  130. package/src/prompt-analyzer.ts +0 -332
  131. package/src/python/coupling-detector.ts +0 -125
  132. package/src/python/domain-enforcer.ts +0 -84
  133. package/src/python/impact-analyzer.ts +0 -96
  134. package/src/python/import-parser.ts +0 -244
  135. package/src/python/import-resolver.ts +0 -136
  136. package/src/python/migration-indexer.ts +0 -116
  137. package/src/python/migration-parser.ts +0 -332
  138. package/src/python/model-indexer.ts +0 -71
  139. package/src/python/model-parser.ts +0 -279
  140. package/src/python/route-indexer.ts +0 -59
  141. package/src/python/route-parser.ts +0 -317
  142. package/src/python-tools.ts +0 -630
  143. package/src/regression-detector.ts +0 -319
  144. package/src/rules.ts +0 -57
  145. package/src/schema-mapper.ts +0 -232
  146. package/src/security/adapter-origin.ts +0 -130
  147. package/src/security/adapter-verifier.ts +0 -319
  148. package/src/security/atomic-write.ts +0 -164
  149. package/src/security/fetcher.ts +0 -200
  150. package/src/security/install-tracking.ts +0 -319
  151. package/src/security/license-pubkey.generated.ts +0 -30
  152. package/src/security/license-response-verifier.ts +0 -149
  153. package/src/security/local-fingerprint.ts +0 -225
  154. package/src/security/manifest-cache.ts +0 -333
  155. package/src/security/manifest-schema.ts +0 -129
  156. package/src/security/registry-pubkey.generated.ts +0 -35
  157. package/src/security/telemetry.ts +0 -320
  158. package/src/security-scorer.ts +0 -405
  159. package/src/security-utils.ts +0 -133
  160. package/src/sentinel-db.ts +0 -580
  161. package/src/sentinel-scanner.ts +0 -406
  162. package/src/sentinel-tools.ts +0 -513
  163. package/src/sentinel-types.ts +0 -140
  164. package/src/server-dispatch.ts +0 -225
  165. package/src/server.ts +0 -107
  166. package/src/session-archiver.ts +0 -111
  167. package/src/session-state-generator.ts +0 -174
  168. package/src/team-knowledge.ts +0 -407
  169. package/src/tool-db-needs.ts +0 -232
  170. package/src/tools.ts +0 -1206
  171. package/src/transcript-parser.ts +0 -458
  172. package/src/trpc-index.ts +0 -215
  173. package/src/validate-features-runner.ts +0 -107
  174. package/src/validation-engine.ts +0 -358
  175. package/src/watch/daemon.ts +0 -385
  176. package/src/watch/lockfile-detector.ts +0 -65
  177. package/src/watch/paths.ts +0 -279
  178. package/src/watch/state.ts +0 -178
@@ -1,467 +0,0 @@
1
- #!/usr/bin/env node
2
- // Copyright (c) 2026 Massu. All rights reserved.
3
- // Licensed under BSL 1.1 - see LICENSE file for details.
4
-
5
- // ============================================================
6
- // P3-003: Stop (Session End) Hook
7
- // Generates session summary and archives CURRENT.md.
8
- // Dependencies: P1-002, P5-001, P5-002
9
- // ============================================================
10
-
11
- import { getMemoryDb, endSession, addSummary, createSession, addConversationTurn, addToolCallDetail, getLastProcessedLine, setLastProcessedLine } from '../memory-db.ts';
12
- import { generateCurrentMd } from '../session-state-generator.ts';
13
- import { archiveAndRegenerate } from '../session-archiver.ts';
14
- import { parseTranscriptFrom, estimateTokens } from '../transcript-parser.ts';
15
- import { syncToCloud, drainSyncQueue } from '../cloud-sync.ts';
16
- import { calculateQualityScore, storeQualityScore, backfillQualityScores } from '../analytics.ts';
17
- import { extractTokenUsage, calculateCost, storeSessionCost } from '../cost-tracker.ts';
18
- import { analyzeSessionPrompts } from '../prompt-analyzer.ts';
19
- import type { SyncPayload } from '../cloud-sync.ts';
20
- import type { SessionSummary } from '../memory-db.ts';
21
- import type { TranscriptEntry, TranscriptContentBlock } from '../transcript-parser.ts';
22
-
23
- interface HookInput {
24
- session_id: string;
25
- transcript_path: string;
26
- cwd: string;
27
- hook_event_name: string;
28
- }
29
-
30
- async function main(): Promise<void> {
31
- try {
32
- const input = await readStdin();
33
- const hookInput = JSON.parse(input) as HookInput;
34
- const { session_id } = hookInput;
35
-
36
- const db = getMemoryDb();
37
- try {
38
- // Ensure session exists
39
- createSession(db, session_id);
40
-
41
- // 1. Get all observations for this session
42
- const observations = db.prepare(
43
- 'SELECT * FROM observations WHERE session_id = ? ORDER BY created_at_epoch ASC'
44
- ).all(session_id) as Array<Record<string, unknown>>;
45
-
46
- // 2. Get user prompts
47
- const prompts = db.prepare(
48
- 'SELECT prompt_text FROM user_prompts WHERE session_id = ? ORDER BY prompt_number ASC'
49
- ).all(session_id) as Array<{ prompt_text: string }>;
50
-
51
- // 3. Generate structured summary from observations
52
- const summary = buildSummaryFromObservations(observations, prompts);
53
-
54
- // 4. Insert summary
55
- addSummary(db, session_id, summary);
56
-
57
- // 4.5. Capture conversation turns and tool call details from transcript (P2-002)
58
- try {
59
- await captureConversationData(db, session_id, hookInput.transcript_path);
60
- } catch (_captureErr) {
61
- // Best-effort: never block session end
62
- }
63
-
64
- // 4.6. Calculate and store quality score
65
- try {
66
- const { score, breakdown } = calculateQualityScore(db, session_id);
67
- if (score !== 50) {
68
- storeQualityScore(db, session_id, score, breakdown);
69
- }
70
- backfillQualityScores(db);
71
- } catch (_qualityErr) {
72
- // Best-effort: never block session end
73
- }
74
-
75
- // 4.7. Calculate and store session cost
76
- try {
77
- const { entries } = await parseTranscriptFrom(hookInput.transcript_path, 0);
78
- const tokenUsage = extractTokenUsage(entries);
79
- const cost = calculateCost(tokenUsage);
80
-
81
- storeSessionCost(db, session_id, tokenUsage, cost);
82
- } catch (_costErr) {
83
- // Best-effort: never block session end
84
- }
85
-
86
- // 4.8. Analyze prompt effectiveness
87
- try {
88
- analyzeSessionPrompts(db, session_id);
89
- } catch (_promptErr) {
90
- // Best-effort: never block session end
91
- }
92
-
93
- // 5. Mark session as completed
94
- endSession(db, session_id, 'completed');
95
-
96
- // 6. Auto-generate CURRENT.md and archive old one
97
- archiveAndRegenerate(db, session_id);
98
-
99
- // 7. Cloud sync (if enabled)
100
- // Order: drain pending queue first, then sync current session
101
- try {
102
- // 7a. Drain pending sync queue
103
- await drainSyncQueue(db);
104
-
105
- // 7b. Sync current session data
106
- const syncPayload = buildSyncPayload(session_id, observations, summary);
107
- const result = await syncToCloud(db, syncPayload);
108
- if (!result.success && result.error) {
109
- // Payload already enqueued by syncToCloud on failure
110
- }
111
- } catch (_syncErr) {
112
- // Non-blocking: sync failure never blocks session end
113
- }
114
- } finally {
115
- db.close();
116
- }
117
- } catch (_e) {
118
- // Best-effort: never block Claude Code
119
- }
120
- process.exit(0);
121
- }
122
-
123
- /**
124
- * Build a sync payload from the current session data.
125
- */
126
- function buildSyncPayload(
127
- sessionId: string,
128
- observations: Array<Record<string, unknown>>,
129
- summary: SessionSummary
130
- ): SyncPayload {
131
- return {
132
- sessions: [{
133
- local_session_id: sessionId,
134
- summary: summary.request ?? undefined,
135
- started_at: undefined, // Will be filled from session data if available
136
- ended_at: new Date().toISOString(),
137
- turns: 0,
138
- tokens_used: 0,
139
- estimated_cost: 0,
140
- tools_used: [],
141
- }],
142
- observations: observations.map((o, idx) => ({
143
- local_observation_id: `${sessionId}_obs_${idx}`,
144
- session_id: sessionId,
145
- type: o.type as string,
146
- content: (o.title as string) + (o.detail ? `: ${o.detail}` : ''),
147
- importance: (o.importance as number) ?? 3,
148
- file_path: undefined,
149
- })),
150
- };
151
- }
152
-
153
- function buildSummaryFromObservations(
154
- observations: Array<Record<string, unknown>>,
155
- prompts: Array<{ prompt_text: string }>
156
- ): SessionSummary {
157
- // request = first user prompt
158
- const request = prompts[0]?.prompt_text?.slice(0, 500) ?? undefined;
159
-
160
- // investigated = discovery observations
161
- const discoveries = observations
162
- .filter(o => o.type === 'discovery')
163
- .map(o => (o.title as string))
164
- .join('; ');
165
-
166
- // decisions = decision observations
167
- const decisions = observations
168
- .filter(o => o.type === 'decision')
169
- .map(o => `- ${o.title}`)
170
- .join('\n');
171
-
172
- // completed = feature/bugfix/refactor observations
173
- const completed = observations
174
- .filter(o => ['feature', 'bugfix', 'refactor'].includes(o.type as string))
175
- .map(o => `- ${o.title}`)
176
- .join('\n');
177
-
178
- // failed_attempts = failed_attempt observations
179
- const failedAttempts = observations
180
- .filter(o => o.type === 'failed_attempt')
181
- .map(o => `- ${o.title}`)
182
- .join('\n');
183
-
184
- // next_steps = observations from last 10% if no completion markers
185
- const lastTenPercent = observations.slice(Math.floor(observations.length * 0.9));
186
- const hasCompletion = completed.length > 0;
187
- const nextSteps = hasCompletion ? undefined : lastTenPercent
188
- .map(o => `- [${o.type}] ${o.title}`)
189
- .join('\n');
190
-
191
- // files created/modified
192
- const filesCreated: string[] = [];
193
- const filesModified: string[] = [];
194
- for (const o of observations) {
195
- if (o.type !== 'file_change') continue;
196
- const files = safeParseJson(o.files_involved as string, []) as string[];
197
- const title = o.title as string;
198
- if (title.startsWith('Created') || title.startsWith('Created/wrote')) {
199
- filesCreated.push(...files);
200
- } else if (title.startsWith('Edited')) {
201
- filesModified.push(...files);
202
- }
203
- }
204
-
205
- // verification results
206
- const verificationResults: Record<string, string> = {};
207
- for (const o of observations) {
208
- if (o.type !== 'vr_check') continue;
209
- const vrType = o.vr_type as string;
210
- const passed = (o.title as string).includes('PASS');
211
- if (vrType) verificationResults[vrType] = passed ? 'PASS' : 'FAIL';
212
- }
213
-
214
- // plan progress
215
- const planProgress: Record<string, string> = {};
216
- for (const o of observations) {
217
- if (!o.plan_item) continue;
218
- planProgress[o.plan_item as string] = 'in_progress';
219
- }
220
-
221
- return {
222
- request,
223
- investigated: discoveries || undefined,
224
- decisions: decisions || undefined,
225
- completed: completed || undefined,
226
- failedAttempts: failedAttempts || undefined,
227
- nextSteps,
228
- filesCreated: [...new Set(filesCreated)],
229
- filesModified: [...new Set(filesModified)],
230
- verificationResults,
231
- planProgress,
232
- };
233
- }
234
-
235
- function safeParseJson(json: string, fallback: unknown): unknown {
236
- try {
237
- return JSON.parse(json);
238
- } catch (_e) {
239
- return fallback;
240
- }
241
- }
242
-
243
- /**
244
- * Capture conversation turns and tool call details from the JSONL transcript.
245
- * Uses incremental parsing to only process new lines since last invocation.
246
- * P2-002 + P2-003: Stop hook conversation capture with state tracking.
247
- */
248
- async function captureConversationData(
249
- db: import('better-sqlite3').Database,
250
- sessionId: string,
251
- transcriptPath: string
252
- ): Promise<void> {
253
- if (!transcriptPath) return;
254
-
255
- // P2-003: Incremental parsing - only process new lines
256
- const lastLine = getLastProcessedLine(db, sessionId);
257
- const { entries, totalLines } = await parseTranscriptFrom(transcriptPath, lastLine);
258
-
259
- if (entries.length === 0) {
260
- setLastProcessedLine(db, sessionId, totalLines);
261
- return;
262
- }
263
-
264
- // Group entries into turns (user prompt -> assistant response(s) with tool calls)
265
- const turns = groupEntriesIntoTurns(entries);
266
-
267
- // Use a transaction for batch insert (P4-002: performance safeguard)
268
- const insertTurns = db.transaction(() => {
269
- // Determine starting turn number (continue from existing turns)
270
- const existingMax = db.prepare(
271
- 'SELECT MAX(turn_number) as max_turn FROM conversation_turns WHERE session_id = ?'
272
- ).get(sessionId) as { max_turn: number | null };
273
- let turnNumber = (existingMax.max_turn ?? 0) + 1;
274
-
275
- for (const turn of turns) {
276
- const toolCallSummaries = turn.toolCalls.map(tc => ({
277
- name: tc.toolName,
278
- input_summary: summarizeToolInput(tc.toolName, tc.input).slice(0, 200),
279
- is_error: tc.isError ?? false,
280
- }));
281
-
282
- // P4-001: assistant_response capped at 10000 chars
283
- const assistantText = turn.assistantText?.slice(0, 10000) ?? null;
284
-
285
- addConversationTurn(
286
- db, sessionId, turnNumber,
287
- turn.userPrompt,
288
- assistantText,
289
- toolCallSummaries.length > 0 ? JSON.stringify(toolCallSummaries) : null,
290
- turn.toolCalls.length,
291
- estimateTokens(turn.userPrompt),
292
- assistantText ? estimateTokens(assistantText) : 0
293
- );
294
-
295
- // Insert tool call details for this turn (all tools, no filtering)
296
- for (const tc of turn.toolCalls) {
297
- const inputStr = JSON.stringify(tc.input);
298
- const outputStr = tc.result ?? '';
299
- const files = extractFilesFromToolCall(tc.toolName, tc.input);
300
-
301
- addToolCallDetail(
302
- db, sessionId, turnNumber,
303
- tc.toolName,
304
- summarizeToolInput(tc.toolName, tc.input),
305
- inputStr.length,
306
- outputStr.length,
307
- !(tc.isError ?? false),
308
- files.length > 0 ? files : undefined
309
- );
310
- }
311
-
312
- turnNumber++;
313
- }
314
- });
315
-
316
- insertTurns();
317
-
318
- // Update last processed line
319
- setLastProcessedLine(db, sessionId, totalLines);
320
- }
321
-
322
- interface ConversationTurn {
323
- userPrompt: string;
324
- assistantText: string | null;
325
- toolCalls: Array<{
326
- toolName: string;
327
- toolUseId: string;
328
- input: Record<string, unknown>;
329
- result?: string;
330
- isError?: boolean;
331
- }>;
332
- }
333
-
334
- /**
335
- * Group transcript entries into conversation turns.
336
- * A turn starts with a user message and includes all subsequent assistant messages
337
- * and tool calls until the next user message.
338
- */
339
- function groupEntriesIntoTurns(entries: TranscriptEntry[]): ConversationTurn[] {
340
- const turns: ConversationTurn[] = [];
341
- let currentTurn: ConversationTurn | null = null;
342
- const toolUseMap = new Map<string, { toolName: string; toolUseId: string; input: Record<string, unknown>; result?: string; isError?: boolean }>();
343
-
344
- for (const entry of entries) {
345
- if (entry.type === 'user' && entry.message && !entry.isMeta) {
346
- // Start a new turn
347
- if (currentTurn) {
348
- turns.push(currentTurn);
349
- }
350
- const text = getTextFromBlocks(entry.message.content);
351
- if (text.trim()) {
352
- currentTurn = {
353
- userPrompt: text.trim(),
354
- assistantText: null,
355
- toolCalls: [],
356
- };
357
- }
358
- } else if (entry.type === 'assistant' && entry.message && currentTurn) {
359
- // Add assistant text
360
- const text = getTextFromBlocks(entry.message.content);
361
- if (text.trim()) {
362
- currentTurn.assistantText = currentTurn.assistantText
363
- ? currentTurn.assistantText + '\n' + text.trim()
364
- : text.trim();
365
- }
366
-
367
- // Extract tool calls from this assistant message
368
- for (const block of entry.message.content) {
369
- if (block.type === 'tool_use') {
370
- const tc = {
371
- toolName: (block as { name: string }).name,
372
- toolUseId: (block as { id: string }).id,
373
- input: (block as { input: Record<string, unknown> }).input ?? {},
374
- };
375
- currentTurn.toolCalls.push(tc);
376
- toolUseMap.set(tc.toolUseId, tc);
377
- } else if (block.type === 'tool_result') {
378
- const toolUseId = (block as { tool_use_id: string }).tool_use_id;
379
- const existing = toolUseMap.get(toolUseId);
380
- if (existing) {
381
- existing.result = getToolResultFromBlock(block);
382
- existing.isError = (block as { is_error?: boolean }).is_error ?? false;
383
- }
384
- }
385
- }
386
- }
387
- }
388
-
389
- // Push the last turn
390
- if (currentTurn) {
391
- turns.push(currentTurn);
392
- }
393
-
394
- return turns;
395
- }
396
-
397
- function getTextFromBlocks(content: TranscriptContentBlock[]): string {
398
- return content
399
- .filter((block): block is { type: 'text'; text: string } => block.type === 'text')
400
- .map(block => block.text)
401
- .join('\n');
402
- }
403
-
404
- function getToolResultFromBlock(block: TranscriptContentBlock): string {
405
- const content = (block as { content: string | TranscriptContentBlock[] }).content;
406
- if (typeof content === 'string') return content;
407
- if (Array.isArray(content)) {
408
- return content
409
- .filter((b): b is { type: 'text'; text: string } => typeof b === 'object' && b !== null && b.type === 'text')
410
- .map(b => b.text)
411
- .join('\n');
412
- }
413
- return '';
414
- }
415
-
416
- /**
417
- * Create a concise summary of tool input for the tool_input_summary column.
418
- */
419
- function summarizeToolInput(toolName: string, input: Record<string, unknown>): string {
420
- switch (toolName) {
421
- case 'Read':
422
- return `Read ${input.file_path ?? ''}`;
423
- case 'Write':
424
- return `Write ${input.file_path ?? ''}`;
425
- case 'Edit':
426
- return `Edit ${input.file_path ?? ''}`;
427
- case 'Bash':
428
- return `$ ${(input.command as string ?? '').slice(0, 200)}`;
429
- case 'Grep':
430
- return `Grep "${input.pattern ?? ''}" in ${input.path ?? '.'}`;
431
- case 'Glob':
432
- return `Glob "${input.pattern ?? ''}" in ${input.path ?? '.'}`;
433
- case 'Task':
434
- return `Task: ${(input.description as string ?? '').slice(0, 100)}`;
435
- case 'WebFetch':
436
- return `Fetch ${input.url ?? ''}`;
437
- case 'WebSearch':
438
- return `Search "${input.query ?? ''}"`;
439
- default:
440
- return `${toolName}: ${JSON.stringify(input).slice(0, 200)}`;
441
- }
442
- }
443
-
444
- /**
445
- * Extract file paths from a tool call input.
446
- */
447
- function extractFilesFromToolCall(toolName: string, input: Record<string, unknown>): string[] {
448
- const filePath = input.file_path as string | undefined;
449
- if (filePath) return [filePath];
450
-
451
- const path = input.path as string | undefined;
452
- if (path && !path.startsWith('.') && toolName !== 'Grep') return [path];
453
-
454
- return [];
455
- }
456
-
457
- function readStdin(): Promise<string> {
458
- return new Promise((resolve) => {
459
- let data = '';
460
- process.stdin.setEncoding('utf-8');
461
- process.stdin.on('data', (chunk: string) => { data += chunk; });
462
- process.stdin.on('end', () => resolve(data));
463
- setTimeout(() => resolve(data), 5000);
464
- });
465
- }
466
-
467
- main();