@claude-sessions/core 0.3.5 → 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.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
  };
@@ -196,6 +196,17 @@ var isInvalidApiKeyMessage = (msg) => {
196
196
  const text = extractTextContent(msg.message);
197
197
  return text.includes("Invalid API key");
198
198
  };
199
+ var ERROR_SESSION_PATTERNS = [
200
+ "API Error",
201
+ "authentication_error",
202
+ "Invalid API key",
203
+ "OAuth token has expired",
204
+ "Please run /login"
205
+ ];
206
+ var isErrorSessionTitle = (title) => {
207
+ if (!title) return false;
208
+ return ERROR_SESSION_PATTERNS.some((pattern) => title.includes(pattern));
209
+ };
199
210
  var isContinuationSummary = (msg) => {
200
211
  if (msg.isCompactSummary === true) return true;
201
212
  if (msg.type !== "user") return false;
@@ -287,46 +298,127 @@ var findLinkedAgents = (projectName, sessionId) => Effect.gen(function* () {
287
298
  }
288
299
  return linkedAgents;
289
300
  });
290
- var findOrphanAgents = (projectName) => Effect.gen(function* () {
301
+ var findOrphanAgentsWithPaths = (projectName) => Effect.gen(function* () {
291
302
  const projectPath = path2.join(getSessionsDir(), projectName);
292
303
  const files = yield* Effect.tryPromise(() => fs2.readdir(projectPath));
293
304
  const sessionIds = new Set(
294
305
  files.filter((f) => !f.startsWith("agent-") && f.endsWith(".jsonl")).map((f) => f.replace(".jsonl", ""))
295
306
  );
296
- const agentFiles = files.filter((f) => f.startsWith("agent-") && f.endsWith(".jsonl"));
297
307
  const orphanAgents = [];
298
- 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) {
299
329
  const filePath = path2.join(projectPath, agentFile);
300
- const content = yield* Effect.tryPromise(() => fs2.readFile(filePath, "utf-8"));
301
- const firstLine = content.split("\n")[0];
302
- if (firstLine) {
303
- try {
304
- const parsed = JSON.parse(firstLine);
305
- if (parsed.sessionId && !sessionIds.has(parsed.sessionId)) {
306
- orphanAgents.push({
307
- agentId: agentFile.replace(".jsonl", ""),
308
- sessionId: parsed.sessionId
309
- });
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
+ }
310
355
  }
311
- } catch {
312
356
  }
313
357
  }
314
358
  }
315
359
  return orphanAgents;
316
360
  });
361
+ var findOrphanAgents = (projectName) => Effect.gen(function* () {
362
+ const orphans = yield* findOrphanAgentsWithPaths(projectName);
363
+ return orphans.map(({ agentId, sessionId }) => ({ agentId, sessionId }));
364
+ });
317
365
  var deleteOrphanAgents = (projectName) => Effect.gen(function* () {
318
366
  const projectPath = path2.join(getSessionsDir(), projectName);
319
- const orphans = yield* findOrphanAgents(projectName);
320
- const backupDir = path2.join(projectPath, ".bak");
321
- yield* Effect.tryPromise(() => fs2.mkdir(backupDir, { recursive: true }));
367
+ const orphans = yield* findOrphanAgentsWithPaths(projectName);
322
368
  const deletedAgents = [];
369
+ const backedUpAgents = [];
370
+ const cleanedFolders = [];
371
+ let backupDirCreated = false;
372
+ const foldersToCheck = /* @__PURE__ */ new Set();
323
373
  for (const orphan of orphans) {
324
- const agentPath = path2.join(projectPath, `${orphan.agentId}.jsonl`);
325
- const agentBackupPath = path2.join(backupDir, `${orphan.agentId}.jsonl`);
326
- yield* Effect.tryPromise(() => fs2.rename(agentPath, agentBackupPath));
327
- 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
+ }
328
411
  }
329
- 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
+ };
330
422
  });
331
423
  var loadAgentMessages = (projectName, _sessionId, agentId) => Effect.gen(function* () {
332
424
  const projectPath = path2.join(getSessionsDir(), projectName);
@@ -677,8 +769,8 @@ var deleteSession = (projectName, sessionId) => Effect3.gen(function* () {
677
769
  const projectPath = path4.join(sessionsDir, projectName);
678
770
  const filePath = path4.join(projectPath, `${sessionId}.jsonl`);
679
771
  const linkedAgents = yield* findLinkedAgents(projectName, sessionId);
680
- const stat2 = yield* Effect3.tryPromise(() => fs4.stat(filePath));
681
- if (stat2.size === 0) {
772
+ const stat3 = yield* Effect3.tryPromise(() => fs4.stat(filePath));
773
+ if (stat3.size === 0) {
682
774
  yield* Effect3.tryPromise(() => fs4.unlink(filePath));
683
775
  const agentBackupDir2 = path4.join(projectPath, ".bak");
684
776
  yield* Effect3.tryPromise(() => fs4.mkdir(agentBackupDir2, { recursive: true }));
@@ -854,6 +946,137 @@ var getSessionFiles = (projectName, sessionId) => Effect3.gen(function* () {
854
946
  totalChanges: fileChanges.length
855
947
  };
856
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
+ });
857
1080
  var moveSession = (sourceProject, sessionId, targetProject) => Effect3.gen(function* () {
858
1081
  const sessionsDir = getSessionsDir();
859
1082
  const sourcePath = path4.join(sessionsDir, sourceProject);
@@ -1061,7 +1284,7 @@ var clearSessions = (options) => Effect3.gen(function* () {
1061
1284
  clearEmpty = true,
1062
1285
  clearInvalid = true,
1063
1286
  skipWithTodos = true,
1064
- clearOrphanAgents = false,
1287
+ clearOrphanAgents = true,
1065
1288
  clearOrphanTodos = false
1066
1289
  } = options;
1067
1290
  const projects = yield* listProjects;
@@ -1385,12 +1608,18 @@ var loadProjectTreeData = (projectName) => Effect3.gen(function* () {
1385
1608
  const dateB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
1386
1609
  return dateB - dateA;
1387
1610
  });
1611
+ const filteredSessions = sortedSessions.filter((s) => {
1612
+ if (isErrorSessionTitle(s.title)) return false;
1613
+ if (isErrorSessionTitle(s.customTitle)) return false;
1614
+ if (isErrorSessionTitle(s.currentSummary)) return false;
1615
+ return true;
1616
+ });
1388
1617
  return {
1389
1618
  name: project.name,
1390
1619
  displayName: project.displayName,
1391
1620
  path: project.path,
1392
- sessionCount: sessions.length,
1393
- sessions: sortedSessions
1621
+ sessionCount: filteredSessions.length,
1622
+ sessions: filteredSessions
1394
1623
  };
1395
1624
  });
1396
1625
  var updateSessionSummary = (projectName, sessionId, newSummary) => Effect3.gen(function* () {
@@ -1414,8 +1643,194 @@ var updateSessionSummary = (projectName, sessionId, newSummary) => Effect3.gen(f
1414
1643
  yield* Effect3.tryPromise(() => fs4.writeFile(filePath, newContent, "utf-8"));
1415
1644
  return { success: true };
1416
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
+ }
1417
1830
  export {
1831
+ analyzeSession,
1418
1832
  clearSessions,
1833
+ compressSession,
1419
1834
  createLogger,
1420
1835
  deleteLinkedTodos,
1421
1836
  deleteMessage,
@@ -1423,6 +1838,7 @@ export {
1423
1838
  deleteOrphanTodos,
1424
1839
  deleteSession,
1425
1840
  displayPathToFolderName,
1841
+ extractProjectKnowledge,
1426
1842
  extractTextContent,
1427
1843
  extractTitle,
1428
1844
  findLinkedAgents,
@@ -1457,6 +1873,7 @@ export {
1457
1873
  setLogger,
1458
1874
  sortProjects,
1459
1875
  splitSession,
1876
+ summarizeSession,
1460
1877
  updateSessionSummary
1461
1878
  };
1462
1879
  //# sourceMappingURL=index.js.map