@claude-sessions/core 0.3.6 → 0.3.7

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/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { M as MessagePayload, a as Message, P as Project, T as TodoItem, F as FileChange, b as MoveSessionResult, S as SearchResult, c as SummaryInfo, A as AgentInfo } from './types-C2fzbmg9.js';
2
- export { i as CleanupPreview, h as ClearSessionsResult, C as ContentItem, D as DeleteSessionResult, k as ProjectTreeData, R as RenameSessionResult, l as ResumeSessionOptions, m as ResumeSessionResult, f as SessionFilesSummary, d as SessionMeta, e as SessionTodos, j as SessionTreeData, g as SplitSessionResult } from './types-C2fzbmg9.js';
1
+ import { M as MessagePayload, a as Message, P as Project, T as TodoItem, F as FileChange, C as CompressSessionOptions, S as SummarizeSessionOptions, b as ConversationLine, c as MoveSessionResult, d as SearchResult, e as SummaryInfo, A as AgentInfo } from './types-Cz8chaYQ.js';
2
+ export { l as CleanupPreview, k as ClearSessionsResult, s as CompressSessionResult, f as ContentItem, D as DeleteSessionResult, t as ProjectKnowledge, n as ProjectTreeData, R as RenameSessionResult, o as ResumeSessionOptions, p as ResumeSessionResult, r as SessionAnalysis, i as SessionFilesSummary, g as SessionMeta, h as SessionTodos, m as SessionTreeData, j as SplitSessionResult, u as SummarizeSessionResult, q as ToolUsageStats } from './types-Cz8chaYQ.js';
3
3
  import * as effect_Cause from 'effect/Cause';
4
4
  import { Effect } from 'effect';
5
5
 
@@ -11,7 +11,9 @@ interface FileSystem {
11
11
  readFileSync: (path: string, encoding: 'utf-8') => string;
12
12
  readdirSync: (path: string) => string[];
13
13
  }
14
- /** Get Claude sessions directory (~/.claude/projects) */
14
+ /** Get Claude sessions directory (~/.claude/projects)
15
+ * Can be overridden with CLAUDE_SESSIONS_DIR environment variable for testing
16
+ */
15
17
  declare const getSessionsDir: () => string;
16
18
  /** Get Claude todos directory (~/.claude/todos) */
17
19
  declare const getTodosDir: () => string;
@@ -94,6 +96,11 @@ declare const findOrphanAgents: (projectName: string) => Effect.Effect<{
94
96
  declare const deleteOrphanAgents: (projectName: string) => Effect.Effect<{
95
97
  success: boolean;
96
98
  deletedAgents: string[];
99
+ backedUpAgents: string[];
100
+ cleanedFolders: string[];
101
+ deletedCount: number;
102
+ backedUpCount: number;
103
+ cleanedFolderCount: number;
97
104
  count: number;
98
105
  }, effect_Cause.UnknownException, never>;
99
106
  declare const loadAgentMessages: (projectName: string, _sessionId: string, // Reserved for future validation
@@ -168,6 +175,34 @@ declare const getSessionFiles: (projectName: string, sessionId: string) => Effec
168
175
  files: FileChange[];
169
176
  totalChanges: number;
170
177
  }, effect_Cause.UnknownException, never>;
178
+ declare const analyzeSession: (projectName: string, sessionId: string) => Effect.Effect<{
179
+ sessionId: string;
180
+ projectName: string;
181
+ durationMinutes: number;
182
+ stats: {
183
+ totalMessages: number;
184
+ userMessages: number;
185
+ assistantMessages: number;
186
+ summaryCount: number;
187
+ snapshotCount: number;
188
+ };
189
+ toolUsage: {
190
+ name: string;
191
+ count: number;
192
+ errorCount: number;
193
+ }[];
194
+ filesChanged: string[];
195
+ patterns: {
196
+ type: string;
197
+ description: string;
198
+ count: number;
199
+ }[];
200
+ milestones: {
201
+ timestamp?: string;
202
+ description: string;
203
+ messageUuid?: string;
204
+ }[];
205
+ }, effect_Cause.UnknownException, never>;
171
206
  declare const moveSession: (sourceProject: string, sessionId: string, targetProject: string) => Effect.Effect<MoveSessionResult, Error>;
172
207
  declare const splitSession: (projectName: string, sessionId: string, splitAtMessageUuid: string) => Effect.Effect<{
173
208
  success: false;
@@ -277,6 +312,37 @@ declare const loadProjectTreeData: (projectName: string) => Effect.Effect<{
277
312
  declare const updateSessionSummary: (projectName: string, sessionId: string, newSummary: string) => Effect.Effect<{
278
313
  success: boolean;
279
314
  }, effect_Cause.UnknownException, never>;
315
+ declare const compressSession: (projectName: string, sessionId: string, options?: CompressSessionOptions) => Effect.Effect<{
316
+ success: true;
317
+ originalSize: number;
318
+ compressedSize: number;
319
+ removedSnapshots: number;
320
+ truncatedOutputs: number;
321
+ }, effect_Cause.UnknownException, never>;
322
+ declare const extractProjectKnowledge: (projectName: string, sessionIds?: string[]) => Effect.Effect<{
323
+ projectName: string;
324
+ patterns: never[];
325
+ hotFiles: {
326
+ path: string;
327
+ modifyCount: number;
328
+ lastModified: string | undefined;
329
+ }[];
330
+ workflows: {
331
+ sequence: string[];
332
+ count: number;
333
+ }[];
334
+ decisions: {
335
+ context: string;
336
+ decision: string;
337
+ sessionId: string;
338
+ }[];
339
+ }, effect_Cause.UnknownException, never>;
340
+ declare const summarizeSession: (projectName: string, sessionId: string, options?: SummarizeSessionOptions) => Effect.Effect<{
341
+ sessionId: string;
342
+ projectName: string;
343
+ lines: ConversationLine[];
344
+ formatted: string;
345
+ }, effect_Cause.UnknownException, never>;
280
346
 
281
347
  /**
282
348
  * Simple logger abstraction for Claude Sessions
@@ -312,4 +378,4 @@ declare const getLogger: () => Logger;
312
378
  */
313
379
  declare const createLogger: (namespace: string) => Logger;
314
380
 
315
- export { AgentInfo, FileChange, type Logger, Message, MessagePayload, MoveSessionResult, Project, SearchResult, SummaryInfo, TodoItem, clearSessions, createLogger, deleteLinkedTodos, deleteMessage, deleteOrphanAgents, deleteOrphanTodos, deleteSession, displayPathToFolderName, extractTextContent, extractTitle, findLinkedAgents, findLinkedTodos, findOrphanAgents, findOrphanTodos, findProjectByWorkspacePath, folderNameToDisplayPath, folderNameToPath, getDisplayTitle, getLogger, getRealPathFromSession, getSessionFiles, getSessionsDir, getTodosDir, isContinuationSummary, isInvalidApiKeyMessage, listProjects, listSessions, loadAgentMessages, loadProjectTreeData, loadSessionTreeData, maskHomePath, moveSession, pathToFolderName, previewCleanup, readSession, renameSession, restoreMessage, searchSessions, sessionHasTodos, setLogger, sortProjects, splitSession, updateSessionSummary };
381
+ export { AgentInfo, CompressSessionOptions, ConversationLine, FileChange, type Logger, Message, MessagePayload, MoveSessionResult, Project, SearchResult, SummarizeSessionOptions, SummaryInfo, TodoItem, analyzeSession, clearSessions, compressSession, createLogger, deleteLinkedTodos, deleteMessage, deleteOrphanAgents, deleteOrphanTodos, deleteSession, displayPathToFolderName, extractProjectKnowledge, extractTextContent, extractTitle, findLinkedAgents, findLinkedTodos, findOrphanAgents, findOrphanTodos, findProjectByWorkspacePath, folderNameToDisplayPath, folderNameToPath, getDisplayTitle, getLogger, getRealPathFromSession, getSessionFiles, getSessionsDir, getTodosDir, isContinuationSummary, isInvalidApiKeyMessage, listProjects, listSessions, loadAgentMessages, loadProjectTreeData, loadSessionTreeData, maskHomePath, moveSession, pathToFolderName, previewCleanup, readSession, renameSession, restoreMessage, searchSessions, sessionHasTodos, setLogger, sortProjects, splitSession, summarizeSession, updateSessionSummary };
package/dist/index.js CHANGED
@@ -24,7 +24,7 @@ var createLogger = (namespace) => ({
24
24
 
25
25
  // src/paths.ts
26
26
  var log = createLogger("paths");
27
- var getSessionsDir = () => path.join(os.homedir(), ".claude", "projects");
27
+ var getSessionsDir = () => process.env.CLAUDE_SESSIONS_DIR || path.join(os.homedir(), ".claude", "projects");
28
28
  var getTodosDir = () => path.join(os.homedir(), ".claude", "todos");
29
29
  var extractCwdFromContent = (content) => {
30
30
  const lines = content.split("\n").filter((l) => l.trim());
@@ -80,22 +80,22 @@ var pathToFolderName = (absolutePath) => {
80
80
  return convertNonAscii(absolutePath).replace(/^\//g, "-").replace(/\/\./g, "--").replace(/\//g, "-").replace(/\./g, "-");
81
81
  };
82
82
  var tryGetCwdFromFile = (filePath, fileSystem = fs, logger2 = log) => {
83
- const basename2 = path.basename(filePath);
83
+ const basename3 = path.basename(filePath);
84
84
  try {
85
85
  const content = fileSystem.readFileSync(filePath, "utf-8");
86
86
  const cwd = extractCwdFromContent(content);
87
87
  if (cwd === null) {
88
88
  const lines = content.split("\n").filter((l) => l.trim());
89
89
  if (lines.length === 0) {
90
- logger2.debug(`tryGetCwdFromFile: ${basename2} -> empty file`);
90
+ logger2.debug(`tryGetCwdFromFile: ${basename3} -> empty file`);
91
91
  } else {
92
- logger2.debug(`tryGetCwdFromFile: ${basename2} -> no cwd found in ${lines.length} lines`);
92
+ logger2.debug(`tryGetCwdFromFile: ${basename3} -> no cwd found in ${lines.length} lines`);
93
93
  }
94
94
  return null;
95
95
  }
96
96
  return cwd;
97
97
  } catch (e) {
98
- logger2.warn(`tryGetCwdFromFile: ${basename2} -> read error: ${e}`);
98
+ logger2.warn(`tryGetCwdFromFile: ${basename3} -> read error: ${e}`);
99
99
  return null;
100
100
  }
101
101
  };
@@ -298,46 +298,127 @@ var findLinkedAgents = (projectName, sessionId) => Effect.gen(function* () {
298
298
  }
299
299
  return linkedAgents;
300
300
  });
301
- var findOrphanAgents = (projectName) => Effect.gen(function* () {
301
+ var findOrphanAgentsWithPaths = (projectName) => Effect.gen(function* () {
302
302
  const projectPath = path2.join(getSessionsDir(), projectName);
303
303
  const files = yield* Effect.tryPromise(() => fs2.readdir(projectPath));
304
304
  const sessionIds = new Set(
305
305
  files.filter((f) => !f.startsWith("agent-") && f.endsWith(".jsonl")).map((f) => f.replace(".jsonl", ""))
306
306
  );
307
- const agentFiles = files.filter((f) => f.startsWith("agent-") && f.endsWith(".jsonl"));
308
307
  const orphanAgents = [];
309
- for (const agentFile of agentFiles) {
308
+ const checkAgentFile = async (filePath) => {
309
+ try {
310
+ const content = await fs2.readFile(filePath, "utf-8");
311
+ const lines = content.split("\n").filter((l) => l.trim());
312
+ const firstLine = lines[0];
313
+ if (!firstLine) return null;
314
+ const parsed = JSON.parse(firstLine);
315
+ if (parsed.sessionId && !sessionIds.has(parsed.sessionId)) {
316
+ const fileName = path2.basename(filePath);
317
+ return {
318
+ agentId: fileName.replace(".jsonl", ""),
319
+ sessionId: parsed.sessionId,
320
+ lineCount: lines.length
321
+ };
322
+ }
323
+ } catch {
324
+ }
325
+ return null;
326
+ };
327
+ const rootAgentFiles = files.filter((f) => f.startsWith("agent-") && f.endsWith(".jsonl"));
328
+ for (const agentFile of rootAgentFiles) {
310
329
  const filePath = path2.join(projectPath, agentFile);
311
- const content = yield* Effect.tryPromise(() => fs2.readFile(filePath, "utf-8"));
312
- const firstLine = content.split("\n")[0];
313
- if (firstLine) {
314
- try {
315
- const parsed = JSON.parse(firstLine);
316
- if (parsed.sessionId && !sessionIds.has(parsed.sessionId)) {
317
- orphanAgents.push({
318
- agentId: agentFile.replace(".jsonl", ""),
319
- sessionId: parsed.sessionId
320
- });
330
+ const orphan = yield* Effect.tryPromise(() => checkAgentFile(filePath));
331
+ if (orphan) {
332
+ orphanAgents.push({ ...orphan, filePath });
333
+ }
334
+ }
335
+ for (const entry of files) {
336
+ const entryPath = path2.join(projectPath, entry);
337
+ const stat3 = yield* Effect.tryPromise(() => fs2.stat(entryPath).catch(() => null));
338
+ if (stat3?.isDirectory() && !entry.startsWith(".")) {
339
+ const subagentsPath = path2.join(entryPath, "subagents");
340
+ const subagentsExists = yield* Effect.tryPromise(
341
+ () => fs2.stat(subagentsPath).then(() => true).catch(() => false)
342
+ );
343
+ if (subagentsExists) {
344
+ const subagentFiles = yield* Effect.tryPromise(
345
+ () => fs2.readdir(subagentsPath).catch(() => [])
346
+ );
347
+ for (const subagentFile of subagentFiles) {
348
+ if (subagentFile.startsWith("agent-") && subagentFile.endsWith(".jsonl")) {
349
+ const filePath = path2.join(subagentsPath, subagentFile);
350
+ const orphan = yield* Effect.tryPromise(() => checkAgentFile(filePath));
351
+ if (orphan) {
352
+ orphanAgents.push({ ...orphan, filePath });
353
+ }
354
+ }
321
355
  }
322
- } catch {
323
356
  }
324
357
  }
325
358
  }
326
359
  return orphanAgents;
327
360
  });
361
+ var findOrphanAgents = (projectName) => Effect.gen(function* () {
362
+ const orphans = yield* findOrphanAgentsWithPaths(projectName);
363
+ return orphans.map(({ agentId, sessionId }) => ({ agentId, sessionId }));
364
+ });
328
365
  var deleteOrphanAgents = (projectName) => Effect.gen(function* () {
329
366
  const projectPath = path2.join(getSessionsDir(), projectName);
330
- const orphans = yield* findOrphanAgents(projectName);
331
- const backupDir = path2.join(projectPath, ".bak");
332
- yield* Effect.tryPromise(() => fs2.mkdir(backupDir, { recursive: true }));
367
+ const orphans = yield* findOrphanAgentsWithPaths(projectName);
333
368
  const deletedAgents = [];
369
+ const backedUpAgents = [];
370
+ const cleanedFolders = [];
371
+ let backupDirCreated = false;
372
+ const foldersToCheck = /* @__PURE__ */ new Set();
334
373
  for (const orphan of orphans) {
335
- const agentPath = path2.join(projectPath, `${orphan.agentId}.jsonl`);
336
- const agentBackupPath = path2.join(backupDir, `${orphan.agentId}.jsonl`);
337
- yield* Effect.tryPromise(() => fs2.rename(agentPath, agentBackupPath));
338
- deletedAgents.push(orphan.agentId);
374
+ const parentDir = path2.dirname(orphan.filePath);
375
+ if (parentDir.endsWith("/subagents") || parentDir.endsWith("\\subagents")) {
376
+ foldersToCheck.add(parentDir);
377
+ }
378
+ if (orphan.lineCount <= 2) {
379
+ yield* Effect.tryPromise(() => fs2.unlink(orphan.filePath));
380
+ deletedAgents.push(orphan.agentId);
381
+ } else {
382
+ if (!backupDirCreated) {
383
+ const backupDir2 = path2.join(projectPath, ".bak");
384
+ yield* Effect.tryPromise(() => fs2.mkdir(backupDir2, { recursive: true }));
385
+ backupDirCreated = true;
386
+ }
387
+ const backupDir = path2.join(projectPath, ".bak");
388
+ const agentBackupPath = path2.join(backupDir, `${orphan.agentId}.jsonl`);
389
+ yield* Effect.tryPromise(() => fs2.rename(orphan.filePath, agentBackupPath));
390
+ backedUpAgents.push(orphan.agentId);
391
+ }
392
+ }
393
+ for (const subagentsDir of foldersToCheck) {
394
+ const isEmpty = yield* Effect.tryPromise(async () => {
395
+ const files = await fs2.readdir(subagentsDir);
396
+ return files.length === 0;
397
+ });
398
+ if (isEmpty) {
399
+ yield* Effect.tryPromise(() => fs2.rmdir(subagentsDir));
400
+ cleanedFolders.push(subagentsDir);
401
+ const sessionDir = path2.dirname(subagentsDir);
402
+ const sessionDirEmpty = yield* Effect.tryPromise(async () => {
403
+ const files = await fs2.readdir(sessionDir);
404
+ return files.length === 0;
405
+ });
406
+ if (sessionDirEmpty) {
407
+ yield* Effect.tryPromise(() => fs2.rmdir(sessionDir));
408
+ cleanedFolders.push(sessionDir);
409
+ }
410
+ }
339
411
  }
340
- return { success: true, deletedAgents, count: deletedAgents.length };
412
+ return {
413
+ success: true,
414
+ deletedAgents,
415
+ backedUpAgents,
416
+ cleanedFolders,
417
+ deletedCount: deletedAgents.length,
418
+ backedUpCount: backedUpAgents.length,
419
+ cleanedFolderCount: cleanedFolders.length,
420
+ count: deletedAgents.length + backedUpAgents.length
421
+ };
341
422
  });
342
423
  var loadAgentMessages = (projectName, _sessionId, agentId) => Effect.gen(function* () {
343
424
  const projectPath = path2.join(getSessionsDir(), projectName);
@@ -688,8 +769,8 @@ var deleteSession = (projectName, sessionId) => Effect3.gen(function* () {
688
769
  const projectPath = path4.join(sessionsDir, projectName);
689
770
  const filePath = path4.join(projectPath, `${sessionId}.jsonl`);
690
771
  const linkedAgents = yield* findLinkedAgents(projectName, sessionId);
691
- const stat2 = yield* Effect3.tryPromise(() => fs4.stat(filePath));
692
- if (stat2.size === 0) {
772
+ const stat3 = yield* Effect3.tryPromise(() => fs4.stat(filePath));
773
+ if (stat3.size === 0) {
693
774
  yield* Effect3.tryPromise(() => fs4.unlink(filePath));
694
775
  const agentBackupDir2 = path4.join(projectPath, ".bak");
695
776
  yield* Effect3.tryPromise(() => fs4.mkdir(agentBackupDir2, { recursive: true }));
@@ -865,6 +946,137 @@ var getSessionFiles = (projectName, sessionId) => Effect3.gen(function* () {
865
946
  totalChanges: fileChanges.length
866
947
  };
867
948
  });
949
+ var analyzeSession = (projectName, sessionId) => Effect3.gen(function* () {
950
+ const messages = yield* readSession(projectName, sessionId);
951
+ let userMessages = 0;
952
+ let assistantMessages = 0;
953
+ let summaryCount = 0;
954
+ let snapshotCount = 0;
955
+ const toolUsageMap = /* @__PURE__ */ new Map();
956
+ const filesChanged = /* @__PURE__ */ new Set();
957
+ const patterns = [];
958
+ const milestones = [];
959
+ let firstTimestamp;
960
+ let lastTimestamp;
961
+ for (const msg of messages) {
962
+ if (msg.timestamp) {
963
+ if (!firstTimestamp) firstTimestamp = msg.timestamp;
964
+ lastTimestamp = msg.timestamp;
965
+ }
966
+ if (msg.type === "user") {
967
+ userMessages++;
968
+ const content = typeof msg.content === "string" ? msg.content : "";
969
+ if (content.toLowerCase().includes("commit") || content.toLowerCase().includes("\uC644\uB8CC")) {
970
+ milestones.push({
971
+ timestamp: msg.timestamp,
972
+ description: `User checkpoint: ${content.slice(0, 50)}...`,
973
+ messageUuid: msg.uuid
974
+ });
975
+ }
976
+ } else if (msg.type === "assistant") {
977
+ assistantMessages++;
978
+ if (msg.message?.content && Array.isArray(msg.message.content)) {
979
+ for (const item of msg.message.content) {
980
+ if (item && typeof item === "object" && "type" in item && item.type === "tool_use") {
981
+ const toolUse = item;
982
+ const toolName = toolUse.name ?? "unknown";
983
+ const existing = toolUsageMap.get(toolName) ?? { count: 0, errorCount: 0 };
984
+ existing.count++;
985
+ toolUsageMap.set(toolName, existing);
986
+ if ((toolName === "Write" || toolName === "Edit") && toolUse.input?.file_path) {
987
+ filesChanged.add(toolUse.input.file_path);
988
+ }
989
+ }
990
+ }
991
+ }
992
+ } else if (msg.type === "summary") {
993
+ summaryCount++;
994
+ if (msg.summary) {
995
+ milestones.push({
996
+ timestamp: msg.timestamp,
997
+ description: `Summary: ${msg.summary.slice(0, 100)}...`,
998
+ messageUuid: msg.uuid
999
+ });
1000
+ }
1001
+ } else if (msg.type === "file-history-snapshot") {
1002
+ snapshotCount++;
1003
+ const snapshot = msg;
1004
+ if (snapshot.snapshot?.trackedFileBackups) {
1005
+ for (const filePath of Object.keys(snapshot.snapshot.trackedFileBackups)) {
1006
+ filesChanged.add(filePath);
1007
+ }
1008
+ }
1009
+ }
1010
+ }
1011
+ for (const msg of messages) {
1012
+ if (msg.type === "user" && msg.content && Array.isArray(msg.content)) {
1013
+ for (const item of msg.content) {
1014
+ if (item && typeof item === "object" && "type" in item && item.type === "tool_result" && "is_error" in item && item.is_error) {
1015
+ const toolResultItem = item;
1016
+ const toolUseId = toolResultItem.tool_use_id;
1017
+ if (toolUseId) {
1018
+ for (const prevMsg of messages) {
1019
+ if (prevMsg.message?.content && Array.isArray(prevMsg.message.content)) {
1020
+ for (const prevItem of prevMsg.message.content) {
1021
+ if (prevItem && typeof prevItem === "object" && "type" in prevItem && prevItem.type === "tool_use" && "id" in prevItem && prevItem.id === toolUseId) {
1022
+ const toolName = prevItem.name ?? "unknown";
1023
+ const existing = toolUsageMap.get(toolName);
1024
+ if (existing) {
1025
+ existing.errorCount++;
1026
+ }
1027
+ }
1028
+ }
1029
+ }
1030
+ }
1031
+ }
1032
+ }
1033
+ }
1034
+ }
1035
+ }
1036
+ let durationMinutes = 0;
1037
+ if (firstTimestamp && lastTimestamp) {
1038
+ const first = new Date(firstTimestamp).getTime();
1039
+ const last = new Date(lastTimestamp).getTime();
1040
+ durationMinutes = Math.round((last - first) / 1e3 / 60);
1041
+ }
1042
+ const toolUsageArray = Array.from(toolUsageMap.entries()).map(([name, stats]) => ({
1043
+ name,
1044
+ count: stats.count,
1045
+ errorCount: stats.errorCount
1046
+ }));
1047
+ for (const tool of toolUsageArray) {
1048
+ if (tool.count >= 3 && tool.errorCount / tool.count > 0.3) {
1049
+ patterns.push({
1050
+ type: "high_error_rate",
1051
+ description: `${tool.name} had ${tool.errorCount}/${tool.count} errors (${Math.round(tool.errorCount / tool.count * 100)}%)`,
1052
+ count: tool.errorCount
1053
+ });
1054
+ }
1055
+ }
1056
+ if (snapshotCount > 10) {
1057
+ patterns.push({
1058
+ type: "many_snapshots",
1059
+ description: `${snapshotCount} file-history-snapshots could be compressed`,
1060
+ count: snapshotCount
1061
+ });
1062
+ }
1063
+ return {
1064
+ sessionId,
1065
+ projectName,
1066
+ durationMinutes,
1067
+ stats: {
1068
+ totalMessages: messages.length,
1069
+ userMessages,
1070
+ assistantMessages,
1071
+ summaryCount,
1072
+ snapshotCount
1073
+ },
1074
+ toolUsage: toolUsageArray.sort((a, b) => b.count - a.count),
1075
+ filesChanged: Array.from(filesChanged),
1076
+ patterns,
1077
+ milestones
1078
+ };
1079
+ });
868
1080
  var moveSession = (sourceProject, sessionId, targetProject) => Effect3.gen(function* () {
869
1081
  const sessionsDir = getSessionsDir();
870
1082
  const sourcePath = path4.join(sessionsDir, sourceProject);
@@ -1072,7 +1284,7 @@ var clearSessions = (options) => Effect3.gen(function* () {
1072
1284
  clearEmpty = true,
1073
1285
  clearInvalid = true,
1074
1286
  skipWithTodos = true,
1075
- clearOrphanAgents = false,
1287
+ clearOrphanAgents = true,
1076
1288
  clearOrphanTodos = false
1077
1289
  } = options;
1078
1290
  const projects = yield* listProjects;
@@ -1431,8 +1643,194 @@ var updateSessionSummary = (projectName, sessionId, newSummary) => Effect3.gen(f
1431
1643
  yield* Effect3.tryPromise(() => fs4.writeFile(filePath, newContent, "utf-8"));
1432
1644
  return { success: true };
1433
1645
  });
1646
+ var compressSession = (projectName, sessionId, options = {}) => Effect3.gen(function* () {
1647
+ const { keepSnapshots = "first_last", maxToolOutputLength = 5e3 } = options;
1648
+ const filePath = path4.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
1649
+ const content = yield* Effect3.tryPromise(() => fs4.readFile(filePath, "utf-8"));
1650
+ const originalSize = Buffer.byteLength(content, "utf-8");
1651
+ const lines = content.trim().split("\n").filter(Boolean);
1652
+ const messages = lines.map((line) => JSON.parse(line));
1653
+ let removedSnapshots = 0;
1654
+ let truncatedOutputs = 0;
1655
+ const snapshotIndices = [];
1656
+ messages.forEach((msg, idx) => {
1657
+ if (msg.type === "file-history-snapshot") {
1658
+ snapshotIndices.push(idx);
1659
+ }
1660
+ });
1661
+ const filteredMessages = messages.filter((msg, idx) => {
1662
+ if (msg.type === "file-history-snapshot") {
1663
+ if (keepSnapshots === "none") {
1664
+ removedSnapshots++;
1665
+ return false;
1666
+ }
1667
+ if (keepSnapshots === "first_last") {
1668
+ const isFirst = idx === snapshotIndices[0];
1669
+ const isLast = idx === snapshotIndices[snapshotIndices.length - 1];
1670
+ if (!isFirst && !isLast) {
1671
+ removedSnapshots++;
1672
+ return false;
1673
+ }
1674
+ }
1675
+ }
1676
+ return true;
1677
+ });
1678
+ for (const msg of filteredMessages) {
1679
+ if (msg.type === "user" && Array.isArray(msg.content)) {
1680
+ for (const item of msg.content) {
1681
+ if (item.type === "tool_result" && typeof item.content === "string") {
1682
+ if (maxToolOutputLength > 0 && item.content.length > maxToolOutputLength) {
1683
+ item.content = item.content.slice(0, maxToolOutputLength) + "\n... [truncated]";
1684
+ truncatedOutputs++;
1685
+ }
1686
+ }
1687
+ }
1688
+ }
1689
+ }
1690
+ const newContent = filteredMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
1691
+ const compressedSize = Buffer.byteLength(newContent, "utf-8");
1692
+ yield* Effect3.tryPromise(() => fs4.writeFile(filePath, newContent, "utf-8"));
1693
+ return {
1694
+ success: true,
1695
+ originalSize,
1696
+ compressedSize,
1697
+ removedSnapshots,
1698
+ truncatedOutputs
1699
+ };
1700
+ });
1701
+ var extractProjectKnowledge = (projectName, sessionIds) => Effect3.gen(function* () {
1702
+ const sessionsDir = getSessionsDir();
1703
+ const projectDir = path4.join(sessionsDir, projectName);
1704
+ let targetSessionIds = sessionIds;
1705
+ if (!targetSessionIds) {
1706
+ const files = yield* Effect3.tryPromise(() => fs4.readdir(projectDir));
1707
+ targetSessionIds = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-")).map((f) => f.replace(".jsonl", ""));
1708
+ }
1709
+ const fileModifyCount = /* @__PURE__ */ new Map();
1710
+ const toolSequences = [];
1711
+ const decisions = [];
1712
+ for (const sessionId of targetSessionIds) {
1713
+ try {
1714
+ const messages = yield* readSession(projectName, sessionId);
1715
+ for (const msg of messages) {
1716
+ if (msg.type === "file-history-snapshot") {
1717
+ const snapshot = msg;
1718
+ if (snapshot.snapshot?.trackedFileBackups) {
1719
+ for (const filePath of Object.keys(snapshot.snapshot.trackedFileBackups)) {
1720
+ const existing = fileModifyCount.get(filePath) ?? { count: 0 };
1721
+ existing.count++;
1722
+ existing.lastModified = snapshot.snapshot.timestamp;
1723
+ fileModifyCount.set(filePath, existing);
1724
+ }
1725
+ }
1726
+ }
1727
+ if (msg.type === "assistant" && msg.message?.content && Array.isArray(msg.message.content)) {
1728
+ const tools = [];
1729
+ for (const item of msg.message.content) {
1730
+ if (item && typeof item === "object" && "type" in item && item.type === "tool_use") {
1731
+ const toolUse = item;
1732
+ if (toolUse.name) tools.push(toolUse.name);
1733
+ }
1734
+ }
1735
+ if (tools.length > 1) {
1736
+ toolSequences.push(tools);
1737
+ }
1738
+ }
1739
+ if (msg.type === "summary" && msg.summary) {
1740
+ decisions.push({
1741
+ context: "Session summary",
1742
+ decision: msg.summary.slice(0, 200),
1743
+ sessionId
1744
+ });
1745
+ }
1746
+ }
1747
+ } catch {
1748
+ continue;
1749
+ }
1750
+ }
1751
+ const hotFiles = Array.from(fileModifyCount.entries()).map(([filePath, data]) => ({
1752
+ path: filePath,
1753
+ modifyCount: data.count,
1754
+ lastModified: data.lastModified
1755
+ })).sort((a, b) => b.modifyCount - a.modifyCount).slice(0, 20);
1756
+ const workflowMap = /* @__PURE__ */ new Map();
1757
+ for (const seq of toolSequences) {
1758
+ const key = seq.join(" -> ");
1759
+ workflowMap.set(key, (workflowMap.get(key) ?? 0) + 1);
1760
+ }
1761
+ const workflows = Array.from(workflowMap.entries()).filter(([, count]) => count >= 2).map(([sequence, count]) => ({
1762
+ sequence: sequence.split(" -> "),
1763
+ count
1764
+ })).sort((a, b) => b.count - a.count).slice(0, 10);
1765
+ return {
1766
+ projectName,
1767
+ patterns: [],
1768
+ hotFiles,
1769
+ workflows,
1770
+ decisions: decisions.slice(0, 20)
1771
+ };
1772
+ });
1773
+ var summarizeSession = (projectName, sessionId, options = {}) => Effect3.gen(function* () {
1774
+ const { limit = 50, maxLength = 100 } = options;
1775
+ const messages = yield* readSession(projectName, sessionId);
1776
+ const lines = [];
1777
+ let count = 0;
1778
+ for (const msg of messages) {
1779
+ if (count >= limit) break;
1780
+ if (msg.type === "user" || msg.type === "human") {
1781
+ let timeStr;
1782
+ if (msg.timestamp) {
1783
+ try {
1784
+ const dt = new Date(msg.timestamp);
1785
+ timeStr = dt.toLocaleString("ko-KR", {
1786
+ month: "2-digit",
1787
+ day: "2-digit",
1788
+ hour: "2-digit",
1789
+ minute: "2-digit",
1790
+ hour12: false
1791
+ });
1792
+ } catch {
1793
+ }
1794
+ }
1795
+ const text = extractTextContent(msg.message);
1796
+ if (text) {
1797
+ const truncated = truncateText(text, maxLength);
1798
+ lines.push({ role: "user", content: truncated, timestamp: timeStr });
1799
+ count++;
1800
+ }
1801
+ } else if (msg.type === "assistant") {
1802
+ const text = extractTextContent(msg.message);
1803
+ if (text) {
1804
+ const truncated = truncateText(text, maxLength);
1805
+ lines.push({ role: "assistant", content: truncated });
1806
+ count++;
1807
+ }
1808
+ }
1809
+ }
1810
+ const formatted = lines.map((line) => {
1811
+ if (line.role === "user") {
1812
+ return line.timestamp ? `user [${line.timestamp}]: ${line.content}` : `user: ${line.content}`;
1813
+ }
1814
+ return `assistant: ${line.content}`;
1815
+ }).join("\n");
1816
+ return {
1817
+ sessionId,
1818
+ projectName,
1819
+ lines,
1820
+ formatted
1821
+ };
1822
+ });
1823
+ function truncateText(text, maxLen) {
1824
+ const cleaned = text.replace(/\n/g, " ");
1825
+ if (cleaned.length > maxLen) {
1826
+ return cleaned.slice(0, maxLen) + "...";
1827
+ }
1828
+ return cleaned;
1829
+ }
1434
1830
  export {
1831
+ analyzeSession,
1435
1832
  clearSessions,
1833
+ compressSession,
1436
1834
  createLogger,
1437
1835
  deleteLinkedTodos,
1438
1836
  deleteMessage,
@@ -1440,6 +1838,7 @@ export {
1440
1838
  deleteOrphanTodos,
1441
1839
  deleteSession,
1442
1840
  displayPathToFolderName,
1841
+ extractProjectKnowledge,
1443
1842
  extractTextContent,
1444
1843
  extractTitle,
1445
1844
  findLinkedAgents,
@@ -1474,6 +1873,7 @@ export {
1474
1873
  setLogger,
1475
1874
  sortProjects,
1476
1875
  splitSession,
1876
+ summarizeSession,
1477
1877
  updateSessionSummary
1478
1878
  };
1479
1879
  //# sourceMappingURL=index.js.map