dedsession 2.3.1 → 3.0.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.
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import * as path from "path";
8
8
  // КОНФИГУРАЦИЯ
9
9
  // ============================================================================
10
10
  const CONFIG = {
11
- VERSION: "2.3.1",
11
+ VERSION: "3.0.1",
12
12
  };
13
13
  const state = {
14
14
  loadedContext: null,
@@ -1084,6 +1084,8 @@ function ensureMemoryStructure() {
1084
1084
  fs.writeFileSync(filePath, JSON.stringify(file.default, null, 2), "utf-8");
1085
1085
  }
1086
1086
  }
1087
+ // v3.0: Создаём _DIGEST/ директорию
1088
+ ensureDigestDir();
1087
1089
  }
1088
1090
  function loadMemoryIndex() {
1089
1091
  const filePath = path.join(getMemoryPath(), "index.json");
@@ -1160,6 +1162,363 @@ function saveSolutions(solutions) {
1160
1162
  const filePath = path.join(getMemoryPath(), "solutions.json");
1161
1163
  fs.writeFileSync(filePath, JSON.stringify(solutions, null, 2), "utf-8");
1162
1164
  }
1165
+ // ============================================================================
1166
+ // PROJECT DIGEST v3.0 — Инфраструктура
1167
+ // ============================================================================
1168
+ function getDigestPath() {
1169
+ return path.join(getMdHistoryPath(), "_DIGEST");
1170
+ }
1171
+ function ensureDigestDir() {
1172
+ const digestPath = getDigestPath();
1173
+ if (!fs.existsSync(digestPath)) {
1174
+ fs.mkdirSync(digestPath, { recursive: true });
1175
+ }
1176
+ }
1177
+ function createEmptyDigest(project) {
1178
+ const today = new Date().toISOString().split("T")[0];
1179
+ return {
1180
+ version: CONFIG.VERSION,
1181
+ project,
1182
+ lastUpdated: today,
1183
+ statistics: {
1184
+ totalContexts: 0,
1185
+ completed: 0,
1186
+ inProgress: 0,
1187
+ blocked: 0,
1188
+ firstDate: today,
1189
+ lastDate: today,
1190
+ },
1191
+ currentState: {
1192
+ description: "",
1193
+ keyFiles: [],
1194
+ techStack: [],
1195
+ activeEpics: [],
1196
+ },
1197
+ recentTasks: [],
1198
+ knownIssues: [],
1199
+ keyDecisions: [],
1200
+ solutions: [],
1201
+ phases: [],
1202
+ };
1203
+ }
1204
+ function loadDigest() {
1205
+ const jsonPath = path.join(getDigestPath(), "digest.json");
1206
+ try {
1207
+ if (fs.existsSync(jsonPath)) {
1208
+ const raw = JSON.parse(fs.readFileSync(jsonPath, "utf-8"));
1209
+ // Schema migration — гарантируем все поля
1210
+ raw.phases = raw.phases || [];
1211
+ raw.solutions = raw.solutions || [];
1212
+ raw.keyDecisions = raw.keyDecisions || [];
1213
+ raw.knownIssues = raw.knownIssues || [];
1214
+ raw.recentTasks = raw.recentTasks || [];
1215
+ raw.currentState = raw.currentState || { description: "", keyFiles: [], techStack: [], activeEpics: [] };
1216
+ raw.currentState.keyFiles = raw.currentState.keyFiles || [];
1217
+ raw.currentState.techStack = raw.currentState.techStack || [];
1218
+ raw.currentState.activeEpics = raw.currentState.activeEpics || [];
1219
+ raw.statistics = raw.statistics || { totalContexts: 0, completed: 0, inProgress: 0, blocked: 0, firstDate: "", lastDate: "" };
1220
+ return raw;
1221
+ }
1222
+ }
1223
+ catch { /* ignore */ }
1224
+ return null;
1225
+ }
1226
+ function saveDigest(digest) {
1227
+ ensureDigestDir();
1228
+ const digestPath = getDigestPath();
1229
+ // Сохраняем JSON
1230
+ fs.writeFileSync(path.join(digestPath, "digest.json"), JSON.stringify(digest, null, 2), "utf-8");
1231
+ // Рендерим все 5 MD файлов
1232
+ fs.writeFileSync(path.join(digestPath, "00-overview.md"), renderOverviewMd(digest), "utf-8");
1233
+ fs.writeFileSync(path.join(digestPath, "01-timeline.md"), renderTimelineMd(digest), "utf-8");
1234
+ fs.writeFileSync(path.join(digestPath, "02-architecture.md"), renderArchitectureMd(digest), "utf-8");
1235
+ fs.writeFileSync(path.join(digestPath, "03-decisions.md"), renderDecisionsMd(digest), "utf-8");
1236
+ fs.writeFileSync(path.join(digestPath, "04-solutions.md"), renderSolutionsMd(digest), "utf-8");
1237
+ }
1238
+ // ============================================================================
1239
+ // PROJECT DIGEST v3.0 — 5 рендеров
1240
+ // ============================================================================
1241
+ function renderOverviewMd(digest) {
1242
+ let md = `# 📦 Project Digest: ${digest.project}\n`;
1243
+ md += `> Обновлено: ${digest.lastUpdated} | Контекстов: ${digest.statistics.totalContexts}\n\n`;
1244
+ md += `## Что это за проект\n`;
1245
+ md += `${digest.currentState.description || "_Описание будет добавлено после digest_refresh_"}\n\n`;
1246
+ md += `## Текущее состояние\n`;
1247
+ md += `- **Контекстов:** ${digest.statistics.totalContexts} (✅ ${digest.statistics.completed} | ⏳ ${digest.statistics.inProgress})\n`;
1248
+ md += `- **Период:** ${digest.statistics.firstDate} — ${digest.statistics.lastDate}\n`;
1249
+ if (digest.currentState.techStack.length > 0) {
1250
+ md += `- **Стек:** ${digest.currentState.techStack.join(", ")}\n`;
1251
+ }
1252
+ if (digest.currentState.keyFiles.length > 0) {
1253
+ md += `- **Ключевые файлы:** ${digest.currentState.keyFiles.slice(0, 10).join(", ")}\n`;
1254
+ }
1255
+ if (digest.currentState.activeEpics.length > 0) {
1256
+ md += `- **Активные эпики:** ${digest.currentState.activeEpics.join(", ")}\n`;
1257
+ }
1258
+ md += "\n";
1259
+ if (digest.phases.length > 0) {
1260
+ md += `## Эволюция проекта\n`;
1261
+ for (const phase of digest.phases) {
1262
+ md += `### ${phase.name} (${phase.dateRange})\n`;
1263
+ md += `${phase.description}\n\n`;
1264
+ }
1265
+ }
1266
+ // В работе сейчас
1267
+ const inProgress = digest.recentTasks.filter(t => t.status === "⏳" || t.status === "⏳ В процессе" || t.status === "in_progress");
1268
+ if (inProgress.length > 0) {
1269
+ md += `## В работе сейчас\n`;
1270
+ for (const task of inProgress.slice(0, 5)) {
1271
+ md += `- **#${task.contextId}: ${task.name}** — ${task.summary}\n`;
1272
+ }
1273
+ md += "\n";
1274
+ }
1275
+ if (digest.knownIssues.length > 0) {
1276
+ md += `## Известные проблемы\n`;
1277
+ for (const issue of digest.knownIssues) {
1278
+ md += `- ${issue}\n`;
1279
+ }
1280
+ md += "\n";
1281
+ }
1282
+ return md;
1283
+ }
1284
+ function renderTimelineMd(digest) {
1285
+ let md = `# 📅 Timeline: последние ${digest.recentTasks.length} задач\n\n`;
1286
+ if (digest.recentTasks.length === 0) {
1287
+ md += "_Нет задач_\n";
1288
+ return md;
1289
+ }
1290
+ // Таблица всех задач
1291
+ md += `| # | Дата | Задача | Статус | Суть |\n`;
1292
+ md += `|---|------|--------|--------|------|\n`;
1293
+ for (const task of digest.recentTasks) {
1294
+ const summary = task.summary || "";
1295
+ const shortSummary = summary.length > 60 ? summary.slice(0, 57) + "..." : summary;
1296
+ md += `| ${task.contextId} | ${task.date} | ${task.name} | ${task.status} | ${shortSummary} |\n`;
1297
+ }
1298
+ md += "\n";
1299
+ // Детали последних 10
1300
+ md += `## Детали последних ${Math.min(10, digest.recentTasks.length)} задач\n\n`;
1301
+ for (const task of digest.recentTasks.slice(0, 10)) {
1302
+ md += `### #${task.contextId}: ${task.name} (${task.date}) ${task.status}\n`;
1303
+ md += `Суть: ${task.summary || ""}\n`;
1304
+ if (task.keyChanges && task.keyChanges.length > 0) {
1305
+ md += `Изменения:\n`;
1306
+ for (const change of task.keyChanges) {
1307
+ md += `- ${change}\n`;
1308
+ }
1309
+ }
1310
+ md += "\n";
1311
+ }
1312
+ return md;
1313
+ }
1314
+ function renderArchitectureMd(digest) {
1315
+ let md = `# 🏗️ Архитектура проекта: ${digest.project}\n\n`;
1316
+ if (digest.currentState.keyFiles.length > 0) {
1317
+ md += `## Ключевые файлы\n`;
1318
+ md += `| Файл | Упоминания |\n`;
1319
+ md += `|------|------------|\n`;
1320
+ for (const file of digest.currentState.keyFiles.slice(0, 30)) {
1321
+ md += `| ${file} | — |\n`;
1322
+ }
1323
+ md += "\n";
1324
+ }
1325
+ if (digest.currentState.techStack.length > 0) {
1326
+ md += `## Стек технологий\n`;
1327
+ for (const tech of digest.currentState.techStack) {
1328
+ md += `- ${tech}\n`;
1329
+ }
1330
+ md += "\n";
1331
+ }
1332
+ return md;
1333
+ }
1334
+ function renderDecisionsMd(digest) {
1335
+ let md = `# 💡 Ключевые решения\n\n`;
1336
+ if (digest.keyDecisions.length === 0) {
1337
+ md += "_Решения будут добавлены при сохранении контекстов с полем decisions_\n";
1338
+ return md;
1339
+ }
1340
+ for (const dec of digest.keyDecisions) {
1341
+ md += `## Решение (${dec.date}, #${dec.contextId})\n`;
1342
+ md += `${dec.decision}\n\n`;
1343
+ }
1344
+ return md;
1345
+ }
1346
+ function renderSolutionsMd(digest) {
1347
+ let md = `# 🔧 Проблемы и решения\n\n`;
1348
+ if (digest.solutions.length === 0) {
1349
+ md += "_Решения будут добавлены при сохранении контекстов с полем problems_solutions_\n";
1350
+ return md;
1351
+ }
1352
+ for (const sol of digest.solutions) {
1353
+ md += `## Проблема (#${sol.contextId})\n`;
1354
+ md += `**Проблема:** ${sol.problem}\n`;
1355
+ md += `**Решение:** ${sol.solution}\n\n`;
1356
+ }
1357
+ return md;
1358
+ }
1359
+ // ============================================================================
1360
+ // PROJECT DIGEST v3.0 — detectProjectFromWorkDir + update
1361
+ // ============================================================================
1362
+ function detectProjectFromWorkDir() {
1363
+ try {
1364
+ const workDir = getWorkingDir();
1365
+ const dirName = path.basename(workDir).toLowerCase();
1366
+ // Проверяем PROJECT_PATTERNS
1367
+ for (const [project, patterns] of Object.entries(PROJECT_PATTERNS)) {
1368
+ for (const pattern of patterns) {
1369
+ if (dirName.includes(pattern)) {
1370
+ return project;
1371
+ }
1372
+ }
1373
+ }
1374
+ // Возвращаем имя директории КАК ЕСТЬ
1375
+ return path.basename(workDir);
1376
+ }
1377
+ catch {
1378
+ return null;
1379
+ }
1380
+ }
1381
+ function extractKeyChangesFromText(changes) {
1382
+ if (!changes)
1383
+ return [];
1384
+ const lines = changes.split("\n")
1385
+ .map(l => l.trim())
1386
+ .filter(l => l.length > 5)
1387
+ .filter(l => l.startsWith("-") || l.startsWith("*") || l.startsWith("•") || /^\d+\./.test(l) || /^[A-Za-zА-Яа-я]/.test(l));
1388
+ return lines
1389
+ .map(l => l.replace(/^[-*•]\s*/, "").replace(/^\d+\.\s*/, ""))
1390
+ .filter(l => l.length > 5)
1391
+ .slice(0, 5);
1392
+ }
1393
+ function extractFilesFromChanges(changes) {
1394
+ if (!changes)
1395
+ return [];
1396
+ const filePatterns = changes.match(/(?:^|\s|`)([\w./\\-]+\.\w{1,10})(?:\s|$|`|,|:|\))/gm);
1397
+ if (!filePatterns)
1398
+ return [];
1399
+ const files = filePatterns
1400
+ .map(m => m.trim().replace(/[`,:)]/g, "").trim())
1401
+ .filter(f => f.includes(".") && !f.startsWith("http") && f.length < 100);
1402
+ return [...new Set(files)].slice(0, 30);
1403
+ }
1404
+ function extractSolutionsFromChanges(text, contextId) {
1405
+ if (!text)
1406
+ return [];
1407
+ const results = [];
1408
+ // Паттерны "Проблема: X / Решение: Y"
1409
+ const pairs = text.match(/(?:проблема|problem|ошибка|баг|bug)[:\s]*([^\n]+)[\s\S]*?(?:решение|solution|фикс|fix)[:\s]*([^\n]+)/gi);
1410
+ if (pairs) {
1411
+ for (const pair of pairs.slice(0, 5)) {
1412
+ const probMatch = pair.match(/(?:проблема|problem|ошибка|баг|bug)[:\s]*([^\n]+)/i);
1413
+ const solMatch = pair.match(/(?:решение|solution|фикс|fix)[:\s]*([^\n]+)/i);
1414
+ if (probMatch && solMatch) {
1415
+ results.push({
1416
+ problem: probMatch[1].trim(),
1417
+ solution: solMatch[1].trim(),
1418
+ contextId,
1419
+ });
1420
+ }
1421
+ }
1422
+ }
1423
+ return results.slice(0, 5);
1424
+ }
1425
+ function extractDecisionsFromText(text, contextId, date) {
1426
+ if (!text)
1427
+ return [];
1428
+ const results = [];
1429
+ // Разбиваем на секции/абзацы
1430
+ const sections = text.split(/\n(?=##?\s|[-*]\s)/);
1431
+ for (const section of sections) {
1432
+ const trimmed = section.trim();
1433
+ if (trimmed.length > 20 && trimmed.length < 500) {
1434
+ results.push({ decision: trimmed, date, contextId });
1435
+ }
1436
+ }
1437
+ return results.slice(0, 5);
1438
+ }
1439
+ function updateDigestOnSave(data) {
1440
+ try {
1441
+ let digest = loadDigest();
1442
+ if (!digest) {
1443
+ digest = createEmptyDigest(data.project);
1444
+ }
1445
+ const today = data.date || new Date().toISOString().split("T")[0];
1446
+ // 1. Проверяем дубликат (если тот же contextId уже есть)
1447
+ const isDuplicate = digest.recentTasks.some(t => t.contextId === data.contextId);
1448
+ // 2. Обновляем statistics (только если новый контекст)
1449
+ if (!isDuplicate) {
1450
+ digest.statistics.totalContexts++;
1451
+ if (data.status.includes("✅") || data.status === "completed") {
1452
+ digest.statistics.completed++;
1453
+ }
1454
+ else if (data.status.includes("⏳") || data.status === "in_progress") {
1455
+ digest.statistics.inProgress++;
1456
+ }
1457
+ else if (data.status.includes("❌") || data.status === "blocked") {
1458
+ digest.statistics.blocked++;
1459
+ }
1460
+ }
1461
+ digest.statistics.lastDate = today;
1462
+ if (!digest.statistics.firstDate || today < digest.statistics.firstDate) {
1463
+ digest.statistics.firstDate = today;
1464
+ }
1465
+ // 3. Prepend/update в recentTasks (лимит 30)
1466
+ const summaryText = data.historySummary || data.summary || "";
1467
+ const statusIcon = data.status.includes("✅") ? "✅" :
1468
+ data.status.includes("⏳") ? "⏳" :
1469
+ data.status.includes("❌") ? "❌" : data.status;
1470
+ // Убираем дубликат если есть
1471
+ digest.recentTasks = digest.recentTasks.filter(t => t.contextId !== data.contextId);
1472
+ digest.recentTasks.unshift({
1473
+ contextId: data.contextId,
1474
+ date: today,
1475
+ name: data.taskName,
1476
+ status: statusIcon,
1477
+ summary: summaryText.slice(0, 200),
1478
+ keyChanges: extractKeyChangesFromText(data.changes || ""),
1479
+ });
1480
+ digest.recentTasks = digest.recentTasks.slice(0, 30);
1481
+ // 3. Извлекаем файлы из changes → обновляем keyFiles
1482
+ const newFiles = extractFilesFromChanges(data.changes || "");
1483
+ for (const f of newFiles) {
1484
+ if (!digest.currentState.keyFiles.includes(f)) {
1485
+ digest.currentState.keyFiles.push(f);
1486
+ }
1487
+ }
1488
+ digest.currentState.keyFiles = digest.currentState.keyFiles.slice(0, 30);
1489
+ // 4. decisions → keyDecisions (лимит 20)
1490
+ if (data.decisions) {
1491
+ const newDecisions = extractDecisionsFromText(data.decisions, data.contextId, today);
1492
+ digest.keyDecisions = [...newDecisions, ...digest.keyDecisions].slice(0, 20);
1493
+ }
1494
+ // 5. problems_solutions → solutions (лимит 20)
1495
+ if (data.problemsSolutions) {
1496
+ const newSolutions = extractSolutionsFromChanges(data.problemsSolutions, data.contextId);
1497
+ digest.solutions = [...newSolutions, ...digest.solutions].slice(0, 20);
1498
+ }
1499
+ // 6. knownIssues для in_progress
1500
+ if (data.status.includes("⏳") || data.status === "in_progress") {
1501
+ const issue = `[#${data.contextId}] ${data.taskName}: ${(data.summary || "").slice(0, 100)}`;
1502
+ if (!digest.knownIssues.some(i => i.includes(`#${data.contextId}`))) {
1503
+ digest.knownIssues.unshift(issue);
1504
+ digest.knownIssues = digest.knownIssues.slice(0, 10);
1505
+ }
1506
+ }
1507
+ // 7. digest_update → обновляет описание
1508
+ if (data.digestUpdate) {
1509
+ digest.currentState.description = data.digestUpdate;
1510
+ }
1511
+ // 8. Обновляем метаданные
1512
+ digest.lastUpdated = today;
1513
+ digest.project = data.project;
1514
+ // 9. Сохраняем
1515
+ saveDigest(digest);
1516
+ return true;
1517
+ }
1518
+ catch {
1519
+ return false;
1520
+ }
1521
+ }
1163
1522
  /**
1164
1523
  * Извлекает решения из текста контекста (legacy и новый формат)
1165
1524
  * Ищет паттерны: "Проблема:", "Решение:", "fix", "исправил", "причина" и т.д.
@@ -2082,6 +2441,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
2082
2441
  type: "string",
2083
2442
  description: "⭐ ВАЖНО: Готовая качественная сводка для HISTORY.md (2-4 предложения). Формат: **Сводка:** что сделано, суть работы, результат. **Файлы:** список изменённых файлов. Если не передать — будет автогенерация низкого качества!",
2084
2443
  },
2444
+ digest_update: {
2445
+ type: "string",
2446
+ description: "(v3.0) Обновление описания/состояния проекта для дайджеста. Если передано — обновляет описание проекта в 00-overview.md",
2447
+ },
2085
2448
  },
2086
2449
  required: ["task_name", "changes", "summary", "session_log"],
2087
2450
  },
@@ -2165,6 +2528,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
2165
2528
  type: "string",
2166
2529
  description: "⭐ ОБЯЗАТЕЛЬНО: Качественная сводка для HISTORY.md (2-4 предложения). Формат: **Сводка:** суть задачи, что сделано, результат. **Файлы:** список изменённых файлов.",
2167
2530
  },
2531
+ digest_update: {
2532
+ type: "string",
2533
+ description: "(v3.0) Обновление описания/состояния проекта для дайджеста. Если передано — обновляет описание проекта в 00-overview.md",
2534
+ },
2168
2535
  },
2169
2536
  required: ["task_name", "task_overview", "analysis", "changes", "summary", "session_log", "code_snippets", "decisions", "problems_solutions", "architecture", "history_summary"],
2170
2537
  },
@@ -2393,6 +2760,68 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
2393
2760
  required: ["action"],
2394
2761
  },
2395
2762
  },
2763
+ // ========== PROJECT DIGEST v3.0 ==========
2764
+ {
2765
+ name: "context_digest",
2766
+ description: "📦 Просмотр дайджеста проекта. ОБЯЗАТЕЛЬНО вызывай когда пользователь говорит: 'дайджест', 'digest', 'обзор проекта'. Загружает и показывает ВСЕ 5 MD файлов из _DIGEST/.",
2767
+ inputSchema: {
2768
+ type: "object",
2769
+ properties: {},
2770
+ },
2771
+ },
2772
+ {
2773
+ name: "context_digest_migrate",
2774
+ description: "📦 Миграция: создать дайджест из существующих контекстов. ОБЯЗАТЕЛЬНО вызывай когда пользователь говорит: 'миграция дайджеста', 'создай дайджест', 'digest migrate'. Сканирует ВСЕ контексты и строит полный дайджест. Безопасен для повторного запуска.",
2775
+ inputSchema: {
2776
+ type: "object",
2777
+ properties: {},
2778
+ },
2779
+ },
2780
+ {
2781
+ name: "context_digest_refresh",
2782
+ description: "🔄 Claude перечитывает и обновляет дайджест. ОБЯЗАТЕЛЬНО вызывай когда пользователь говорит: 'обнови дайджест', 'refresh digest'. Возвращает текущий digest.json + README последних 10 контекстов. Claude анализирует и вызывает context_digest_write.",
2783
+ inputSchema: {
2784
+ type: "object",
2785
+ properties: {},
2786
+ },
2787
+ },
2788
+ {
2789
+ name: "context_digest_write",
2790
+ description: "📝 Запись качественного дайджеста от Claude. Вызывается ПОСЛЕ context_digest_refresh. Claude передаёт проанализированные данные для обновления дайджеста.",
2791
+ inputSchema: {
2792
+ type: "object",
2793
+ properties: {
2794
+ description: {
2795
+ type: "string",
2796
+ description: "Описание проекта: цель, суть, для кого (3-5 предложений)",
2797
+ },
2798
+ tech_stack: {
2799
+ type: "string",
2800
+ description: "Стек технологий через запятую: TypeScript, Node.js, MCP, etc.",
2801
+ },
2802
+ key_files: {
2803
+ type: "string",
2804
+ description: "Ключевые файлы через запятую: src/index.ts, package.json, etc.",
2805
+ },
2806
+ active_epics: {
2807
+ type: "string",
2808
+ description: "Активные эпики через запятую",
2809
+ },
2810
+ known_issues: {
2811
+ type: "string",
2812
+ description: "Известные проблемы, каждая на новой строке",
2813
+ },
2814
+ phases: {
2815
+ type: "string",
2816
+ description: "Фазы эволюции проекта. Формат: 'Название фазы | даты | описание' на каждой строке",
2817
+ },
2818
+ key_decisions: {
2819
+ type: "string",
2820
+ description: "Ключевые решения, каждое на новой строке",
2821
+ },
2822
+ },
2823
+ },
2824
+ },
2396
2825
  // ========== ТЕСТОВЫЕ КОМАНДЫ SMART CONTEXT ==========
2397
2826
  {
2398
2827
  name: "context_smart",
@@ -2456,12 +2885,41 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2456
2885
  output += lastContextMatch[0] + "\n---\n\n";
2457
2886
  }
2458
2887
  }
2459
- output += `## 📊 Статистика\n\n`;
2460
- output += `| Показатель | Значение |\n|------------|----------|\n`;
2461
- output += `| Всего контекстов | ${stats.total} |\n`;
2462
- output += `| За неделю | ${stats.thisWeek} |\n`;
2463
- output += `| Завершено | ${stats.completed} |\n`;
2464
- output += `| В процессе | ${stats.inProgress} |\n\n`;
2888
+ // v3.0: Показываем дайджест если есть
2889
+ const startDigest = loadDigest();
2890
+ if (startDigest) {
2891
+ output += `## 📦 Project Digest: ${startDigest.project}\n\n`;
2892
+ output += `${(startDigest.currentState.description || "_Описание не задано_").slice(0, 500)}\n\n`;
2893
+ output += `| Показатель | Значение |\n|------------|----------|\n`;
2894
+ output += `| Всего контекстов | ${startDigest.statistics.totalContexts} |\n`;
2895
+ output += `| Завершено | ${startDigest.statistics.completed} |\n`;
2896
+ output += `| В процессе | ${startDigest.statistics.inProgress} |\n`;
2897
+ output += `| Период | ${startDigest.statistics.firstDate} — ${startDigest.statistics.lastDate} |\n\n`;
2898
+ // Последние 5 задач из таймлайна
2899
+ if (startDigest.recentTasks.length > 0) {
2900
+ output += `### Последние задачи\n\n`;
2901
+ output += `| # | Дата | Задача | Статус |\n|---|------|--------|--------|\n`;
2902
+ for (const task of startDigest.recentTasks.slice(0, 5)) {
2903
+ output += `| ${task.contextId} | ${task.date} | ${task.name} | ${task.status} |\n`;
2904
+ }
2905
+ output += "\n";
2906
+ }
2907
+ output += `💡 Полный дайджест: \`context_digest\`\n\n`;
2908
+ output += `---\n\n`;
2909
+ }
2910
+ else {
2911
+ output += `## 📊 Статистика\n\n`;
2912
+ output += `| Показатель | Значение |\n|------------|----------|\n`;
2913
+ output += `| Всего контекстов | ${stats.total} |\n`;
2914
+ output += `| За неделю | ${stats.thisWeek} |\n`;
2915
+ output += `| Завершено | ${stats.completed} |\n`;
2916
+ output += `| В процессе | ${stats.inProgress} |\n\n`;
2917
+ // v3.0: Предложить создать дайджест если контексты есть
2918
+ if (stats.total > 0) {
2919
+ output += `<claude-instruction>Project Digest НЕ найден. Предложи пользователю создать дайджест командой "миграция дайджеста" — это даст полную историю проекта при каждой загрузке контекста.</claude-instruction>\n`;
2920
+ output += `> 💡 **Dedsession v3.0:** Создайте Project Digest для мгновенного восстановления контекста: "миграция дайджеста"\n\n`;
2921
+ }
2922
+ }
2465
2923
  // Архитектура проекта если есть
2466
2924
  if (architecture.length > 0) {
2467
2925
  output += `## 🏗️ Архитектура проекта (из MD/)\n\n`;
@@ -2581,6 +3039,23 @@ ${args?.changes?.split("\n").map(l => l.trim() ? `- ${l}` : "").join("\n") || "-
2581
3039
  const extracted = extractSolutionsFromText(solutionsContent, contextId);
2582
3040
  solutionsAdded = addSolutionsToIndex(extracted, contextId);
2583
3041
  }
3042
+ // v3.0: Обновляем дайджест
3043
+ const digestProject = detectProjectFromWorkDir() || memoryInfo.project || "unknown";
3044
+ const digestContextId = String(contextNumber).padStart(3, "0");
3045
+ const digestUpdated = updateDigestOnSave({
3046
+ project: digestProject,
3047
+ contextId: digestContextId,
3048
+ taskName,
3049
+ status,
3050
+ changes: args?.changes,
3051
+ summary: args?.summary,
3052
+ historySummary: args?.history_summary,
3053
+ problemsSolutions: args?.problems_solutions,
3054
+ decisions: args?.decisions,
3055
+ architecture: args?.architecture,
3056
+ date: new Date().toISOString().split("T")[0],
3057
+ digestUpdate: args?.digest_update,
3058
+ });
2584
3059
  let output = `✅ **Контекст сохранён**\n\n`;
2585
3060
  output += `📁 **Папка:** \`${result.name}\`\n`;
2586
3061
  output += `📄 **Файлов:** ${result.filesCount}\n`;
@@ -2596,6 +3071,10 @@ ${args?.changes?.split("\n").map(l => l.trim() ? `- ${l}` : "").join("\n") || "-
2596
3071
  output += `- 💡 Решений добавлено: **${solutionsAdded}**\n`;
2597
3072
  }
2598
3073
  }
3074
+ // v3.0: Дайджест статус
3075
+ if (digestUpdated) {
3076
+ output += `\n📦 **Дайджест обновлён** (${digestProject})\n`;
3077
+ }
2599
3078
  if (state.loadedContext && state.loadedContext !== result.name) {
2600
3079
  output += `\n🔗 **Parent:** ${state.loadedContext}\n`;
2601
3080
  }
@@ -2728,6 +3207,22 @@ ${state.loadedContext ? `**Parent:** ${state.loadedContext}` : "Нет parent к
2728
3207
  const contextId = String(contextNumber).padStart(3, "0");
2729
3208
  const extracted = extractSolutionsFromText(solutionsContent, contextId);
2730
3209
  solutionsAdded = addSolutionsToIndex(extracted, contextId);
3210
+ // v3.0: Обновляем дайджест (detailed передаёт больше данных)
3211
+ const digestProjectD = detectProjectFromWorkDir() || memoryInfo.project || "unknown";
3212
+ const digestUpdatedD = updateDigestOnSave({
3213
+ project: digestProjectD,
3214
+ contextId,
3215
+ taskName,
3216
+ status,
3217
+ changes: args?.changes,
3218
+ summary: args?.summary,
3219
+ historySummary: args?.history_summary,
3220
+ problemsSolutions: args?.problems_solutions,
3221
+ decisions: args?.decisions,
3222
+ architecture: args?.architecture,
3223
+ date: new Date().toISOString().split("T")[0],
3224
+ digestUpdate: args?.digest_update,
3225
+ });
2731
3226
  let output = `✅ **Контекст сохранён ПОДРОБНО**\n\n`;
2732
3227
  output += `📁 **Папка:** \`${result.name}\`\n`;
2733
3228
  output += `📄 **Файлов:** 15\n`;
@@ -2742,6 +3237,10 @@ ${state.loadedContext ? `**Parent:** ${state.loadedContext}` : "Нет parent к
2742
3237
  output += `- 💡 Решений добавлено: **${solutionsAdded}**\n`;
2743
3238
  }
2744
3239
  }
3240
+ // v3.0: Дайджест статус
3241
+ if (digestUpdatedD) {
3242
+ output += `\n📦 **Дайджест обновлён** (${digestProjectD})\n`;
3243
+ }
2745
3244
  if (state.loadedContext && state.loadedContext !== result.name) {
2746
3245
  output += `\n🔗 **Parent:** ${state.loadedContext}\n`;
2747
3246
  }
@@ -2765,7 +3264,7 @@ ${state.loadedContext ? `**Parent:** ${state.loadedContext}` : "Нет parent к
2765
3264
  .map(l => l.startsWith("-") ? l : `- ${l}`)
2766
3265
  .join("\n");
2767
3266
  const commitTitle = `📝 Context: ${contextShortName}`;
2768
- const commitBody = `Статус: ${status} | Файлов: 10\n\nЧто сделано:\n${changeLines}`;
3267
+ const commitBody = `Статус: ${status} | Файлов: 15\n\nЧто сделано:\n${changeLines}`;
2769
3268
  output += `\n\n---\n\n`;
2770
3269
  output += `📋 **Для коммита:**\n\n`;
2771
3270
  output += `**Title:**\n\`\`\`\n${commitTitle}\n\`\`\`\n\n`;
@@ -3006,15 +3505,20 @@ MD_HISTORY/
3006
3505
  if (allContexts.length === 0) {
3007
3506
  return { content: [{ type: "text", text: "📭 Нет сохранённых контекстов" }] };
3008
3507
  }
3508
+ // v3.0: Проверяем наличие дайджеста
3509
+ const digest = loadDigest();
3510
+ const hasDigest = digest !== null;
3009
3511
  // v2.0: Получаем Memory stats
3010
3512
  const memoryStats = getMemoryStats();
3011
3513
  const hasMemory = memoryStats.totalContexts > 0;
3012
3514
  // v1.4.10: Читаем HISTORY.md и учитываем его размер
3013
3515
  const history = readHistory();
3014
3516
  const historySize = history ? history.length : 0;
3517
+ // v3.0: Когда есть дайджест — другой режим работы
3518
+ const contextCount = hasDigest ? Math.min(3, allContexts.length) : Math.min(5, allContexts.length);
3015
3519
  // Определяем стратегию с учётом размера HISTORY.md
3016
3520
  const strategy = determineQuickLoadStrategy(allContexts, historySize);
3017
- const contexts = allContexts.slice(0, strategy.count);
3521
+ const contexts = allContexts.slice(0, contextCount);
3018
3522
  // Функция обрезки текста с ... в конце
3019
3523
  const truncate = (text, maxLen) => {
3020
3524
  if (text.length <= maxLen)
@@ -3024,10 +3528,39 @@ MD_HISTORY/
3024
3528
  let output = `# ⚡ Краткий контекст — Dedsession v${CONFIG.VERSION}\n\n`;
3025
3529
  output += `📁 **Рабочая директория:** \`${workDir}\`\n`;
3026
3530
  output += `📊 **Статистика:** Всего ${stats.total} | ✅ ${stats.completed} | ⏳ ${stats.inProgress}\n`;
3027
- output += `🎯 **Режим:** ${strategy.reason}\n`;
3028
- output += `📦 **Загружено контекстов:** ${contexts.length}\n\n`;
3531
+ if (hasDigest) {
3532
+ output += `📦 **Режим:** Дайджест + ${contextCount} контекстов (v3.0)\n\n`;
3533
+ }
3534
+ else {
3535
+ output += `🎯 **Режим:** ${strategy.reason}\n`;
3536
+ output += `📦 **Загружено контекстов:** ${contexts.length}\n\n`;
3537
+ }
3029
3538
  // v2.1: Правила показываются ПЕРВЫМИ
3030
3539
  output += formatRulesBlock();
3540
+ // v3.0: ДАЙДЖЕСТ ПЕРВЫМ когда есть
3541
+ if (hasDigest) {
3542
+ output += `<claude-instruction>Дайджест содержит полный контекст проекта. Начинай работу сразу без дополнительных вопросов.</claude-instruction>\n\n`;
3543
+ const digestPath = getDigestPath();
3544
+ const digestFiles = ["00-overview.md", "01-timeline.md", "02-architecture.md", "03-decisions.md", "04-solutions.md"];
3545
+ for (const fileName of digestFiles) {
3546
+ const filePath = path.join(digestPath, fileName);
3547
+ if (fs.existsSync(filePath)) {
3548
+ const content = fs.readFileSync(filePath, "utf-8");
3549
+ // Лимит ~4KB на каждый файл дайджеста
3550
+ output += truncate(content, 4 * 1024) + "\n\n---\n\n";
3551
+ }
3552
+ }
3553
+ }
3554
+ else {
3555
+ // v3.0: Баннер для legacy формата + ИНСТРУКЦИЯ для Claude
3556
+ output += `<claude-instruction>ВАЖНО: Project Digest НЕ найден для этого проекта! Ты ОБЯЗАН в своём ответе пользователю ПЕРВЫМ ДЕЛОМ предложить создать дайджест командой "миграция дайджеста" или "digest migrate". Объясни что это даст: полная история проекта вместо последних 5 контекстов, автообновление при каждом сохранении, мгновенный старт новых сессий. Это ключевое улучшение v3.0!</claude-instruction>\n\n`;
3557
+ output += `# ⚡ Project Digest не найден!\n\n`;
3558
+ output += `> **Dedsession v3.0** использует Project Digest — полную память проекта.\n`;
3559
+ output += `> Сейчас загружается legacy формат (только 5 последних контекстов = 13% истории).\n`;
3560
+ output += `>\n`;
3561
+ output += `> **Создайте дайджест:** скажите "миграция дайджеста" или "digest migrate"\n`;
3562
+ output += `> После этого каждый context_save будет автоматически обогащать дайджест.\n\n`;
3563
+ }
3031
3564
  // v2.0: Memory System summary
3032
3565
  if (hasMemory) {
3033
3566
  output += `🧠 **Memory v2.0:** `;
@@ -3049,11 +3582,12 @@ MD_HISTORY/
3049
3582
  else {
3050
3583
  output += `💡 *Memory не инициализирована. Выполни \`context_migrate\` для индексации.*\n\n`;
3051
3584
  }
3052
- // v1.4.10: HISTORY.md с лимитом 10KB
3585
+ // v3.0: HISTORY.md урезанный когда есть дайджест
3586
+ const historyLimit = hasDigest ? 2 * 1024 : 10 * 1024;
3053
3587
  if (history) {
3054
3588
  output += `---\n\n`;
3055
- output += `# 📜 HISTORY.md — Полная история\n\n`;
3056
- output += truncate(history, 10 * 1024);
3589
+ output += `# 📜 HISTORY.md — ${hasDigest ? "Последние записи" : "Полная история"}\n\n`;
3590
+ output += truncate(history, historyLimit);
3057
3591
  output += `\n\n---\n\n`;
3058
3592
  }
3059
3593
  else {
@@ -3061,24 +3595,26 @@ MD_HISTORY/
3061
3595
  output += `💡 *HISTORY.md не найден. Выполни \`context_to_history\` для создания индекса.*\n\n`;
3062
3596
  output += `---\n\n`;
3063
3597
  }
3064
- // v1.4.10: Загружаем контексты с лимитом на каждый
3065
- let contextBudget = strategy.maxPerContext;
3598
+ // v3.0: Загружаем контексты с лимитом на каждый
3599
+ const maxPerCtx = hasDigest
3600
+ ? Math.floor(9 * 1024 / contextCount) // ~3KB per context when digest present
3601
+ : strategy.maxPerContext;
3066
3602
  for (const ctx of contexts) {
3067
3603
  const result = loadContext(ctx.name);
3068
3604
  if (!result.success)
3069
3605
  continue;
3070
3606
  let ctxOutput = `# 📁 #${String(ctx.number).padStart(3, "0")}: ${ctx.title}\n`;
3071
3607
  ctxOutput += `📅 ${ctx.date} | ${ctx.status} | ${ctx.filesCount} файлов\n\n`;
3072
- if (strategy.mode === "readme_only") {
3073
- // Только README (обрезанный)
3608
+ if (hasDigest || strategy.mode === "readme_only") {
3609
+ // Дайджест есть или мало места — только README
3074
3610
  if (result.files["README.md"]) {
3075
- ctxOutput += truncate(result.files["README.md"], contextBudget - 200);
3611
+ ctxOutput += truncate(result.files["README.md"], maxPerCtx - 200);
3076
3612
  }
3077
3613
  }
3078
3614
  else if (strategy.mode === "summary") {
3079
3615
  // README + summary + changes (с лимитами)
3080
3616
  const summaryFiles = ["README.md", "04-summary.md", "03-changes.md"];
3081
- const perFile = Math.floor((contextBudget - 200) / 3);
3617
+ const perFile = Math.floor((maxPerCtx - 200) / 3);
3082
3618
  for (const fileName of summaryFiles) {
3083
3619
  if (result.files[fileName]) {
3084
3620
  ctxOutput += `### ${fileName}\n${truncate(result.files[fileName], perFile)}\n\n`;
@@ -3088,7 +3624,7 @@ MD_HISTORY/
3088
3624
  else {
3089
3625
  // Полная загрузка (с лимитами)
3090
3626
  const fileOrder = ["README.md", "04-summary.md", "03-changes.md", "01-task-overview.md", "02-analysis.md"];
3091
- const perFile = Math.floor((contextBudget - 200) / fileOrder.length);
3627
+ const perFile = Math.floor((maxPerCtx - 200) / fileOrder.length);
3092
3628
  for (const fileName of fileOrder) {
3093
3629
  if (result.files[fileName]) {
3094
3630
  ctxOutput += `### ${fileName}\n${truncate(result.files[fileName], perFile)}\n\n`;
@@ -3097,7 +3633,7 @@ MD_HISTORY/
3097
3633
  }
3098
3634
  output += ctxOutput + `\n---\n\n`;
3099
3635
  }
3100
- output += `\n💡 Загружено ${contexts.length} контекстов. Продолжаем работу?`;
3636
+ output += `\n💡 Загружено ${contexts.length} контекстов${hasDigest ? " + дайджест" : ""}. Продолжаем работу?`;
3101
3637
  return { content: [{ type: "text", text: output }] };
3102
3638
  }
3103
3639
  // ==================== CONTEXT_FULL ====================
@@ -3699,6 +4235,238 @@ MD_HISTORY/
3699
4235
  }
3700
4236
  }
3701
4237
  }
4238
+ // ========== PROJECT DIGEST v3.0 HANDLERS ==========
4239
+ case "context_digest": {
4240
+ const digestPath = getDigestPath();
4241
+ if (!fs.existsSync(path.join(digestPath, "digest.json"))) {
4242
+ return {
4243
+ content: [{
4244
+ type: "text",
4245
+ text: `📦 **Дайджест не найден**\n\nПапка \`_DIGEST/\` пуста или не существует.\n\n**Создайте дайджест:**\n- Скажите "миграция дайджеста" или "digest migrate"\n- Или просто сохраните контекст — дайджест создастся автоматически`,
4246
+ }],
4247
+ };
4248
+ }
4249
+ let output = `# 📦 Project Digest\n\n`;
4250
+ const digestFiles = ["00-overview.md", "01-timeline.md", "02-architecture.md", "03-decisions.md", "04-solutions.md"];
4251
+ for (const fileName of digestFiles) {
4252
+ const filePath = path.join(digestPath, fileName);
4253
+ if (fs.existsSync(filePath)) {
4254
+ const content = fs.readFileSync(filePath, "utf-8");
4255
+ output += content + "\n\n---\n\n";
4256
+ }
4257
+ }
4258
+ return { content: [{ type: "text", text: output }] };
4259
+ }
4260
+ case "context_digest_migrate": {
4261
+ const allContexts = listContexts();
4262
+ const memoryIndex = loadMemoryIndex();
4263
+ const solutionsIdx = loadSolutions();
4264
+ const keywordsIdx = loadKeywords();
4265
+ const projectName = detectProjectFromWorkDir() || "unknown";
4266
+ const digest = createEmptyDigest(projectName);
4267
+ // Сканируем все контексты
4268
+ for (const ctx of allContexts) {
4269
+ const contextId = String(ctx.number).padStart(3, "0");
4270
+ // Читаем README
4271
+ let summary = "";
4272
+ const readmePath = path.join(ctx.path, "README.md");
4273
+ if (fs.existsSync(readmePath)) {
4274
+ const readme = fs.readFileSync(readmePath, "utf-8");
4275
+ const descMatch = readme.match(/## Краткое описание\n\n([^\n#]+)/);
4276
+ summary = descMatch ? descMatch[1].trim() : readme.split("\n").slice(0, 3).join(" ").slice(0, 200);
4277
+ }
4278
+ // Читаем 03-changes.md для файлов
4279
+ let keyChanges = [];
4280
+ const changesPath = path.join(ctx.path, "03-changes.md");
4281
+ if (fs.existsSync(changesPath)) {
4282
+ const changesContent = fs.readFileSync(changesPath, "utf-8");
4283
+ keyChanges = extractKeyChangesFromText(changesContent);
4284
+ const files = extractFilesFromChanges(changesContent);
4285
+ for (const f of files) {
4286
+ if (!digest.currentState.keyFiles.includes(f)) {
4287
+ digest.currentState.keyFiles.push(f);
4288
+ }
4289
+ }
4290
+ }
4291
+ // Статистика
4292
+ digest.statistics.totalContexts++;
4293
+ if (ctx.status.includes("✅"))
4294
+ digest.statistics.completed++;
4295
+ else if (ctx.status.includes("⏳"))
4296
+ digest.statistics.inProgress++;
4297
+ else if (ctx.status.includes("❌"))
4298
+ digest.statistics.blocked++;
4299
+ if (!digest.statistics.firstDate || ctx.date < digest.statistics.firstDate) {
4300
+ digest.statistics.firstDate = ctx.date;
4301
+ }
4302
+ if (!digest.statistics.lastDate || ctx.date > digest.statistics.lastDate) {
4303
+ digest.statistics.lastDate = ctx.date;
4304
+ }
4305
+ // recentTasks (последние 30)
4306
+ digest.recentTasks.push({
4307
+ contextId,
4308
+ date: ctx.date,
4309
+ name: ctx.title,
4310
+ status: ctx.status,
4311
+ summary: summary.slice(0, 200),
4312
+ keyChanges,
4313
+ });
4314
+ }
4315
+ // Сортируем по номеру (новые первые) и обрезаем
4316
+ digest.recentTasks.sort((a, b) => parseInt(b.contextId) - parseInt(a.contextId));
4317
+ digest.recentTasks = digest.recentTasks.slice(0, 30);
4318
+ // Лимит keyFiles
4319
+ digest.currentState.keyFiles = digest.currentState.keyFiles.slice(0, 30);
4320
+ // techStack из keywords
4321
+ const techKeywords = ["typescript", "javascript", "python", "swift", "kotlin", "react", "vue", "node", "mcp", "trello", "obsidian"];
4322
+ for (const [kw] of Object.entries(keywordsIdx.keywords)) {
4323
+ if (techKeywords.includes(kw.toLowerCase())) {
4324
+ digest.currentState.techStack.push(kw);
4325
+ }
4326
+ }
4327
+ // solutions из solutions.json
4328
+ for (const sol of solutionsIdx.solutions.slice(0, 20)) {
4329
+ digest.solutions.push({
4330
+ problem: sol.problem,
4331
+ solution: sol.solution.join("; "),
4332
+ contextId: sol.sourceContexts[0] || "?",
4333
+ });
4334
+ }
4335
+ // Фазы — группировка по месяцам
4336
+ const monthMap = new Map();
4337
+ for (const task of digest.recentTasks) {
4338
+ const month = task.date.slice(0, 7); // YYYY-MM
4339
+ if (!monthMap.has(month)) {
4340
+ monthMap.set(month, { count: 0, tasks: [] });
4341
+ }
4342
+ const m = monthMap.get(month);
4343
+ m.count++;
4344
+ if (m.tasks.length < 3)
4345
+ m.tasks.push(task.name);
4346
+ }
4347
+ for (const [month, data] of monthMap) {
4348
+ digest.phases.push({
4349
+ name: month,
4350
+ dateRange: month,
4351
+ description: `${data.count} задач: ${data.tasks.join(", ")}${data.count > 3 ? " и др." : ""}`,
4352
+ });
4353
+ }
4354
+ // activeEpics из memory
4355
+ const epics = loadEpics();
4356
+ for (const [name, epic] of Object.entries(epics.epics)) {
4357
+ if (epic.status === "in_progress") {
4358
+ digest.currentState.activeEpics.push(name);
4359
+ }
4360
+ }
4361
+ // Сохраняем
4362
+ saveDigest(digest);
4363
+ let output = `✅ **Дайджест создан!**\n\n`;
4364
+ output += `📦 **Проект:** ${projectName}\n`;
4365
+ output += `📊 **Контекстов обработано:** ${allContexts.length}\n`;
4366
+ output += `📁 **Путь:** \`${getDigestPath()}\`\n\n`;
4367
+ output += `**Создано файлов:**\n`;
4368
+ output += `- 00-overview.md\n- 01-timeline.md\n- 02-architecture.md\n- 03-decisions.md\n- 04-solutions.md\n- digest.json\n\n`;
4369
+ output += `---\n\n`;
4370
+ output += `💡 **Следующий шаг:** скажите "обнови дайджест" (refresh) — Claude проанализирует и добавит качественное описание проекта, фазы эволюции и т.д.\n\n`;
4371
+ output += `📦 Дайджест будет автоматически обновляться при каждом \`context_save\`.`;
4372
+ return { content: [{ type: "text", text: output }] };
4373
+ }
4374
+ case "context_digest_refresh": {
4375
+ const digest = loadDigest();
4376
+ if (!digest) {
4377
+ return {
4378
+ content: [{
4379
+ type: "text",
4380
+ text: `📦 **Дайджест не найден**\n\nСначала создайте дайджест: "миграция дайджеста" или "digest migrate"`,
4381
+ }],
4382
+ };
4383
+ }
4384
+ // Собираем README последних 10 контекстов
4385
+ const contexts = listContexts().slice(0, 10);
4386
+ let readmes = "";
4387
+ for (const ctx of contexts) {
4388
+ const readmePath = path.join(ctx.path, "README.md");
4389
+ if (fs.existsSync(readmePath)) {
4390
+ readmes += `\n---\n### #${String(ctx.number).padStart(3, "0")} | ${ctx.date} | ${ctx.title} | ${ctx.status}\n\n`;
4391
+ const content = fs.readFileSync(readmePath, "utf-8");
4392
+ readmes += content.slice(0, 1500) + "\n";
4393
+ }
4394
+ }
4395
+ let output = `# 🔄 Refresh дайджеста\n\n`;
4396
+ output += `## Текущий digest.json:\n\n`;
4397
+ output += `\`\`\`json\n${JSON.stringify(digest, null, 2)}\n\`\`\`\n\n`;
4398
+ output += `---\n\n`;
4399
+ output += `## README последних 10 контекстов:\n\n`;
4400
+ output += readmes;
4401
+ output += `\n\n---\n\n`;
4402
+ output += `<claude-instruction>\n`;
4403
+ output += `Проанализируй digest.json и README контекстов выше.\n`;
4404
+ output += `Затем вызови context_digest_write с параметрами:\n`;
4405
+ output += `- description: описание проекта (3-5 предложений)\n`;
4406
+ output += `- tech_stack: стек через запятую\n`;
4407
+ output += `- key_files: ключевые файлы через запятую\n`;
4408
+ output += `- phases: фазы эволюции (Название | даты | описание на каждой строке)\n`;
4409
+ output += `- key_decisions: ключевые решения\n`;
4410
+ output += `- known_issues: текущие проблемы\n`;
4411
+ output += `- active_epics: активные эпики\n`;
4412
+ output += `</claude-instruction>`;
4413
+ return { content: [{ type: "text", text: output }] };
4414
+ }
4415
+ case "context_digest_write": {
4416
+ let digest = loadDigest();
4417
+ if (!digest) {
4418
+ const projectName = detectProjectFromWorkDir() || "unknown";
4419
+ digest = createEmptyDigest(projectName);
4420
+ }
4421
+ // Обновляем currentState
4422
+ if (args?.description) {
4423
+ digest.currentState.description = args.description;
4424
+ }
4425
+ if (args?.tech_stack) {
4426
+ digest.currentState.techStack = args.tech_stack.split(",").map(s => s.trim()).filter(Boolean);
4427
+ }
4428
+ if (args?.key_files) {
4429
+ digest.currentState.keyFiles = args.key_files.split(",").map(s => s.trim()).filter(Boolean);
4430
+ }
4431
+ if (args?.active_epics) {
4432
+ digest.currentState.activeEpics = args.active_epics.split(",").map(s => s.trim()).filter(Boolean);
4433
+ }
4434
+ // Обновляем known_issues
4435
+ if (args?.known_issues) {
4436
+ digest.knownIssues = args.known_issues.split("\n").map(s => s.trim()).filter(Boolean).slice(0, 10);
4437
+ }
4438
+ // Обновляем phases
4439
+ if (args?.phases) {
4440
+ const phaseLines = args.phases.split("\n").filter(l => l.trim());
4441
+ digest.phases = phaseLines.map(line => {
4442
+ const parts = line.split("|").map(p => p.trim());
4443
+ return {
4444
+ name: parts[0] || "Phase",
4445
+ dateRange: parts[1] || "",
4446
+ description: parts[2] || "",
4447
+ };
4448
+ });
4449
+ }
4450
+ // Обновляем key_decisions
4451
+ if (args?.key_decisions) {
4452
+ const decLines = args.key_decisions.split("\n").filter(l => l.trim());
4453
+ const today = new Date().toISOString().split("T")[0];
4454
+ digest.keyDecisions = decLines.map(line => ({
4455
+ decision: line.trim(),
4456
+ date: today,
4457
+ contextId: "refresh",
4458
+ })).slice(0, 20);
4459
+ }
4460
+ digest.lastUpdated = new Date().toISOString().split("T")[0];
4461
+ // Сохраняем и перерендериваем
4462
+ saveDigest(digest);
4463
+ return {
4464
+ content: [{
4465
+ type: "text",
4466
+ text: `✅ **Дайджест обновлён!**\n\n📦 Проект: **${digest.project}**\n📝 Описание: ${(digest.currentState.description || "").slice(0, 100)}...\n🔧 Стек: ${digest.currentState.techStack.join(", ") || "-"}\n📁 Файлов: ${digest.currentState.keyFiles.length}\n\nВсе 5 MD файлов перерендерены. Проверьте: "дайджест" или "digest"`,
4467
+ }],
4468
+ };
4469
+ }
3702
4470
  // ========== ТЕСТОВЫЕ HANDLERS SMART CONTEXT ==========
3703
4471
  case "context_smart": {
3704
4472
  const history = readHistory();