dedsession 2.3.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +826 -58
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import * as path from "path";
|
|
|
8
8
|
// КОНФИГУРАЦИЯ
|
|
9
9
|
// ============================================================================
|
|
10
10
|
const CONFIG = {
|
|
11
|
-
VERSION: "
|
|
11
|
+
VERSION: "3.0.0",
|
|
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", "исправил", "причина" и т.д.
|
|
@@ -1473,9 +1832,9 @@ function parseHistoryEntries(historyContent, limit = 25) {
|
|
|
1473
1832
|
// Вычисляет релевантность контекста к запросу (для context_smart_focus)
|
|
1474
1833
|
function scoreContextRelevance(entry, queryPerson, queryKeywords, memoryIndex) {
|
|
1475
1834
|
let score = 0;
|
|
1476
|
-
// +
|
|
1835
|
+
// +5 за совпадение человека (было +10, уменьшено для баланса)
|
|
1477
1836
|
if (queryPerson && entry.person === queryPerson) {
|
|
1478
|
-
score +=
|
|
1837
|
+
score += 5;
|
|
1479
1838
|
}
|
|
1480
1839
|
// +5 за совпадение ключевого слова в названии
|
|
1481
1840
|
for (const kw of queryKeywords) {
|
|
@@ -1489,16 +1848,16 @@ function scoreContextRelevance(entry, queryPerson, queryKeywords, memoryIndex) {
|
|
|
1489
1848
|
score += 2;
|
|
1490
1849
|
}
|
|
1491
1850
|
}
|
|
1492
|
-
// Бонус из Memory v2.0: +
|
|
1851
|
+
// Бонус из Memory v2.0: +5 за совпадение project (было +3, увеличено)
|
|
1493
1852
|
const contextMeta = memoryIndex.contexts[entry.number];
|
|
1494
1853
|
if (contextMeta) {
|
|
1495
1854
|
for (const kw of queryKeywords) {
|
|
1496
1855
|
if (contextMeta.project && contextMeta.project.toLowerCase().includes(kw)) {
|
|
1497
|
-
score +=
|
|
1856
|
+
score += 5;
|
|
1498
1857
|
}
|
|
1499
|
-
// +
|
|
1858
|
+
// +3 за совпадение в keywords из Memory (было +1, увеличено для технологий)
|
|
1500
1859
|
if (contextMeta.keywords.some(k => k.includes(kw) || kw.includes(k))) {
|
|
1501
|
-
score +=
|
|
1860
|
+
score += 3;
|
|
1502
1861
|
}
|
|
1503
1862
|
}
|
|
1504
1863
|
}
|
|
@@ -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,36 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2456
2885
|
output += lastContextMatch[0] + "\n---\n\n";
|
|
2457
2886
|
}
|
|
2458
2887
|
}
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
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
|
+
}
|
|
2465
2918
|
// Архитектура проекта если есть
|
|
2466
2919
|
if (architecture.length > 0) {
|
|
2467
2920
|
output += `## 🏗️ Архитектура проекта (из MD/)\n\n`;
|
|
@@ -2581,6 +3034,23 @@ ${args?.changes?.split("\n").map(l => l.trim() ? `- ${l}` : "").join("\n") || "-
|
|
|
2581
3034
|
const extracted = extractSolutionsFromText(solutionsContent, contextId);
|
|
2582
3035
|
solutionsAdded = addSolutionsToIndex(extracted, contextId);
|
|
2583
3036
|
}
|
|
3037
|
+
// v3.0: Обновляем дайджест
|
|
3038
|
+
const digestProject = detectProjectFromWorkDir() || memoryInfo.project || "unknown";
|
|
3039
|
+
const digestContextId = String(contextNumber).padStart(3, "0");
|
|
3040
|
+
const digestUpdated = updateDigestOnSave({
|
|
3041
|
+
project: digestProject,
|
|
3042
|
+
contextId: digestContextId,
|
|
3043
|
+
taskName,
|
|
3044
|
+
status,
|
|
3045
|
+
changes: args?.changes,
|
|
3046
|
+
summary: args?.summary,
|
|
3047
|
+
historySummary: args?.history_summary,
|
|
3048
|
+
problemsSolutions: args?.problems_solutions,
|
|
3049
|
+
decisions: args?.decisions,
|
|
3050
|
+
architecture: args?.architecture,
|
|
3051
|
+
date: new Date().toISOString().split("T")[0],
|
|
3052
|
+
digestUpdate: args?.digest_update,
|
|
3053
|
+
});
|
|
2584
3054
|
let output = `✅ **Контекст сохранён**\n\n`;
|
|
2585
3055
|
output += `📁 **Папка:** \`${result.name}\`\n`;
|
|
2586
3056
|
output += `📄 **Файлов:** ${result.filesCount}\n`;
|
|
@@ -2596,6 +3066,10 @@ ${args?.changes?.split("\n").map(l => l.trim() ? `- ${l}` : "").join("\n") || "-
|
|
|
2596
3066
|
output += `- 💡 Решений добавлено: **${solutionsAdded}**\n`;
|
|
2597
3067
|
}
|
|
2598
3068
|
}
|
|
3069
|
+
// v3.0: Дайджест статус
|
|
3070
|
+
if (digestUpdated) {
|
|
3071
|
+
output += `\n📦 **Дайджест обновлён** (${digestProject})\n`;
|
|
3072
|
+
}
|
|
2599
3073
|
if (state.loadedContext && state.loadedContext !== result.name) {
|
|
2600
3074
|
output += `\n🔗 **Parent:** ${state.loadedContext}\n`;
|
|
2601
3075
|
}
|
|
@@ -2728,6 +3202,22 @@ ${state.loadedContext ? `**Parent:** ${state.loadedContext}` : "Нет parent к
|
|
|
2728
3202
|
const contextId = String(contextNumber).padStart(3, "0");
|
|
2729
3203
|
const extracted = extractSolutionsFromText(solutionsContent, contextId);
|
|
2730
3204
|
solutionsAdded = addSolutionsToIndex(extracted, contextId);
|
|
3205
|
+
// v3.0: Обновляем дайджест (detailed передаёт больше данных)
|
|
3206
|
+
const digestProjectD = detectProjectFromWorkDir() || memoryInfo.project || "unknown";
|
|
3207
|
+
const digestUpdatedD = updateDigestOnSave({
|
|
3208
|
+
project: digestProjectD,
|
|
3209
|
+
contextId,
|
|
3210
|
+
taskName,
|
|
3211
|
+
status,
|
|
3212
|
+
changes: args?.changes,
|
|
3213
|
+
summary: args?.summary,
|
|
3214
|
+
historySummary: args?.history_summary,
|
|
3215
|
+
problemsSolutions: args?.problems_solutions,
|
|
3216
|
+
decisions: args?.decisions,
|
|
3217
|
+
architecture: args?.architecture,
|
|
3218
|
+
date: new Date().toISOString().split("T")[0],
|
|
3219
|
+
digestUpdate: args?.digest_update,
|
|
3220
|
+
});
|
|
2731
3221
|
let output = `✅ **Контекст сохранён ПОДРОБНО**\n\n`;
|
|
2732
3222
|
output += `📁 **Папка:** \`${result.name}\`\n`;
|
|
2733
3223
|
output += `📄 **Файлов:** 15\n`;
|
|
@@ -2742,6 +3232,10 @@ ${state.loadedContext ? `**Parent:** ${state.loadedContext}` : "Нет parent к
|
|
|
2742
3232
|
output += `- 💡 Решений добавлено: **${solutionsAdded}**\n`;
|
|
2743
3233
|
}
|
|
2744
3234
|
}
|
|
3235
|
+
// v3.0: Дайджест статус
|
|
3236
|
+
if (digestUpdatedD) {
|
|
3237
|
+
output += `\n📦 **Дайджест обновлён** (${digestProjectD})\n`;
|
|
3238
|
+
}
|
|
2745
3239
|
if (state.loadedContext && state.loadedContext !== result.name) {
|
|
2746
3240
|
output += `\n🔗 **Parent:** ${state.loadedContext}\n`;
|
|
2747
3241
|
}
|
|
@@ -2765,7 +3259,7 @@ ${state.loadedContext ? `**Parent:** ${state.loadedContext}` : "Нет parent к
|
|
|
2765
3259
|
.map(l => l.startsWith("-") ? l : `- ${l}`)
|
|
2766
3260
|
.join("\n");
|
|
2767
3261
|
const commitTitle = `📝 Context: ${contextShortName}`;
|
|
2768
|
-
const commitBody = `Статус: ${status} | Файлов:
|
|
3262
|
+
const commitBody = `Статус: ${status} | Файлов: 15\n\nЧто сделано:\n${changeLines}`;
|
|
2769
3263
|
output += `\n\n---\n\n`;
|
|
2770
3264
|
output += `📋 **Для коммита:**\n\n`;
|
|
2771
3265
|
output += `**Title:**\n\`\`\`\n${commitTitle}\n\`\`\`\n\n`;
|
|
@@ -3006,15 +3500,20 @@ MD_HISTORY/
|
|
|
3006
3500
|
if (allContexts.length === 0) {
|
|
3007
3501
|
return { content: [{ type: "text", text: "📭 Нет сохранённых контекстов" }] };
|
|
3008
3502
|
}
|
|
3503
|
+
// v3.0: Проверяем наличие дайджеста
|
|
3504
|
+
const digest = loadDigest();
|
|
3505
|
+
const hasDigest = digest !== null;
|
|
3009
3506
|
// v2.0: Получаем Memory stats
|
|
3010
3507
|
const memoryStats = getMemoryStats();
|
|
3011
3508
|
const hasMemory = memoryStats.totalContexts > 0;
|
|
3012
3509
|
// v1.4.10: Читаем HISTORY.md и учитываем его размер
|
|
3013
3510
|
const history = readHistory();
|
|
3014
3511
|
const historySize = history ? history.length : 0;
|
|
3512
|
+
// v3.0: Когда есть дайджест — другой режим работы
|
|
3513
|
+
const contextCount = hasDigest ? Math.min(3, allContexts.length) : Math.min(5, allContexts.length);
|
|
3015
3514
|
// Определяем стратегию с учётом размера HISTORY.md
|
|
3016
3515
|
const strategy = determineQuickLoadStrategy(allContexts, historySize);
|
|
3017
|
-
const contexts = allContexts.slice(0,
|
|
3516
|
+
const contexts = allContexts.slice(0, contextCount);
|
|
3018
3517
|
// Функция обрезки текста с ... в конце
|
|
3019
3518
|
const truncate = (text, maxLen) => {
|
|
3020
3519
|
if (text.length <= maxLen)
|
|
@@ -3024,10 +3523,40 @@ MD_HISTORY/
|
|
|
3024
3523
|
let output = `# ⚡ Краткий контекст — Dedsession v${CONFIG.VERSION}\n\n`;
|
|
3025
3524
|
output += `📁 **Рабочая директория:** \`${workDir}\`\n`;
|
|
3026
3525
|
output += `📊 **Статистика:** Всего ${stats.total} | ✅ ${stats.completed} | ⏳ ${stats.inProgress}\n`;
|
|
3027
|
-
|
|
3028
|
-
|
|
3526
|
+
if (hasDigest) {
|
|
3527
|
+
output += `📦 **Режим:** Дайджест + ${contextCount} контекстов (v3.0)\n\n`;
|
|
3528
|
+
}
|
|
3529
|
+
else {
|
|
3530
|
+
output += `🎯 **Режим:** ${strategy.reason}\n`;
|
|
3531
|
+
output += `📦 **Загружено контекстов:** ${contexts.length}\n\n`;
|
|
3532
|
+
}
|
|
3029
3533
|
// v2.1: Правила показываются ПЕРВЫМИ
|
|
3030
3534
|
output += formatRulesBlock();
|
|
3535
|
+
// v3.0: ДАЙДЖЕСТ ПЕРВЫМ когда есть
|
|
3536
|
+
if (hasDigest) {
|
|
3537
|
+
output += `<claude-instruction>Дайджест содержит полный контекст проекта. Начинай работу сразу без дополнительных вопросов.</claude-instruction>\n\n`;
|
|
3538
|
+
const digestPath = getDigestPath();
|
|
3539
|
+
const digestFiles = ["00-overview.md", "01-timeline.md", "02-architecture.md", "03-decisions.md", "04-solutions.md"];
|
|
3540
|
+
for (const fileName of digestFiles) {
|
|
3541
|
+
const filePath = path.join(digestPath, fileName);
|
|
3542
|
+
if (fs.existsSync(filePath)) {
|
|
3543
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
3544
|
+
// Лимит ~4KB на каждый файл дайджеста
|
|
3545
|
+
output += truncate(content, 4 * 1024) + "\n\n---\n\n";
|
|
3546
|
+
}
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3549
|
+
else {
|
|
3550
|
+
// v3.0: Баннер для legacy формата
|
|
3551
|
+
output += `# ⚡ Обнаружен legacy формат — дайджест не найден!\n\n`;
|
|
3552
|
+
output += `> Вы используете старый формат без Project Digest.\n`;
|
|
3553
|
+
output += `> Создайте дайджест для повышения эффективности в **3-5 раз**:\n`;
|
|
3554
|
+
output += `> - Мгновенное восстановление контекста проекта\n`;
|
|
3555
|
+
output += `> - Полная история вместо последних 5 контекстов\n`;
|
|
3556
|
+
output += `> - Автообновление при каждом сохранении\n`;
|
|
3557
|
+
output += `>\n`;
|
|
3558
|
+
output += `> **Команда:** скажите "миграция дайджеста" или "digest migrate"\n\n`;
|
|
3559
|
+
}
|
|
3031
3560
|
// v2.0: Memory System summary
|
|
3032
3561
|
if (hasMemory) {
|
|
3033
3562
|
output += `🧠 **Memory v2.0:** `;
|
|
@@ -3049,11 +3578,12 @@ MD_HISTORY/
|
|
|
3049
3578
|
else {
|
|
3050
3579
|
output += `💡 *Memory не инициализирована. Выполни \`context_migrate\` для индексации.*\n\n`;
|
|
3051
3580
|
}
|
|
3052
|
-
//
|
|
3581
|
+
// v3.0: HISTORY.md — урезанный когда есть дайджест
|
|
3582
|
+
const historyLimit = hasDigest ? 2 * 1024 : 10 * 1024;
|
|
3053
3583
|
if (history) {
|
|
3054
3584
|
output += `---\n\n`;
|
|
3055
|
-
output += `# 📜 HISTORY.md — Полная
|
|
3056
|
-
output += truncate(history,
|
|
3585
|
+
output += `# 📜 HISTORY.md — ${hasDigest ? "Последние записи" : "Полная история"}\n\n`;
|
|
3586
|
+
output += truncate(history, historyLimit);
|
|
3057
3587
|
output += `\n\n---\n\n`;
|
|
3058
3588
|
}
|
|
3059
3589
|
else {
|
|
@@ -3061,24 +3591,26 @@ MD_HISTORY/
|
|
|
3061
3591
|
output += `💡 *HISTORY.md не найден. Выполни \`context_to_history\` для создания индекса.*\n\n`;
|
|
3062
3592
|
output += `---\n\n`;
|
|
3063
3593
|
}
|
|
3064
|
-
//
|
|
3065
|
-
|
|
3594
|
+
// v3.0: Загружаем контексты с лимитом на каждый
|
|
3595
|
+
const maxPerCtx = hasDigest
|
|
3596
|
+
? Math.floor(9 * 1024 / contextCount) // ~3KB per context when digest present
|
|
3597
|
+
: strategy.maxPerContext;
|
|
3066
3598
|
for (const ctx of contexts) {
|
|
3067
3599
|
const result = loadContext(ctx.name);
|
|
3068
3600
|
if (!result.success)
|
|
3069
3601
|
continue;
|
|
3070
3602
|
let ctxOutput = `# 📁 #${String(ctx.number).padStart(3, "0")}: ${ctx.title}\n`;
|
|
3071
3603
|
ctxOutput += `📅 ${ctx.date} | ${ctx.status} | ${ctx.filesCount} файлов\n\n`;
|
|
3072
|
-
if (strategy.mode === "readme_only") {
|
|
3073
|
-
//
|
|
3604
|
+
if (hasDigest || strategy.mode === "readme_only") {
|
|
3605
|
+
// Дайджест есть или мало места — только README
|
|
3074
3606
|
if (result.files["README.md"]) {
|
|
3075
|
-
ctxOutput += truncate(result.files["README.md"],
|
|
3607
|
+
ctxOutput += truncate(result.files["README.md"], maxPerCtx - 200);
|
|
3076
3608
|
}
|
|
3077
3609
|
}
|
|
3078
3610
|
else if (strategy.mode === "summary") {
|
|
3079
3611
|
// README + summary + changes (с лимитами)
|
|
3080
3612
|
const summaryFiles = ["README.md", "04-summary.md", "03-changes.md"];
|
|
3081
|
-
const perFile = Math.floor((
|
|
3613
|
+
const perFile = Math.floor((maxPerCtx - 200) / 3);
|
|
3082
3614
|
for (const fileName of summaryFiles) {
|
|
3083
3615
|
if (result.files[fileName]) {
|
|
3084
3616
|
ctxOutput += `### ${fileName}\n${truncate(result.files[fileName], perFile)}\n\n`;
|
|
@@ -3088,7 +3620,7 @@ MD_HISTORY/
|
|
|
3088
3620
|
else {
|
|
3089
3621
|
// Полная загрузка (с лимитами)
|
|
3090
3622
|
const fileOrder = ["README.md", "04-summary.md", "03-changes.md", "01-task-overview.md", "02-analysis.md"];
|
|
3091
|
-
const perFile = Math.floor((
|
|
3623
|
+
const perFile = Math.floor((maxPerCtx - 200) / fileOrder.length);
|
|
3092
3624
|
for (const fileName of fileOrder) {
|
|
3093
3625
|
if (result.files[fileName]) {
|
|
3094
3626
|
ctxOutput += `### ${fileName}\n${truncate(result.files[fileName], perFile)}\n\n`;
|
|
@@ -3097,7 +3629,7 @@ MD_HISTORY/
|
|
|
3097
3629
|
}
|
|
3098
3630
|
output += ctxOutput + `\n---\n\n`;
|
|
3099
3631
|
}
|
|
3100
|
-
output += `\n💡 Загружено ${contexts.length}
|
|
3632
|
+
output += `\n💡 Загружено ${contexts.length} контекстов${hasDigest ? " + дайджест" : ""}. Продолжаем работу?`;
|
|
3101
3633
|
return { content: [{ type: "text", text: output }] };
|
|
3102
3634
|
}
|
|
3103
3635
|
// ==================== CONTEXT_FULL ====================
|
|
@@ -3699,6 +4231,238 @@ MD_HISTORY/
|
|
|
3699
4231
|
}
|
|
3700
4232
|
}
|
|
3701
4233
|
}
|
|
4234
|
+
// ========== PROJECT DIGEST v3.0 HANDLERS ==========
|
|
4235
|
+
case "context_digest": {
|
|
4236
|
+
const digestPath = getDigestPath();
|
|
4237
|
+
if (!fs.existsSync(path.join(digestPath, "digest.json"))) {
|
|
4238
|
+
return {
|
|
4239
|
+
content: [{
|
|
4240
|
+
type: "text",
|
|
4241
|
+
text: `📦 **Дайджест не найден**\n\nПапка \`_DIGEST/\` пуста или не существует.\n\n**Создайте дайджест:**\n- Скажите "миграция дайджеста" или "digest migrate"\n- Или просто сохраните контекст — дайджест создастся автоматически`,
|
|
4242
|
+
}],
|
|
4243
|
+
};
|
|
4244
|
+
}
|
|
4245
|
+
let output = `# 📦 Project Digest\n\n`;
|
|
4246
|
+
const digestFiles = ["00-overview.md", "01-timeline.md", "02-architecture.md", "03-decisions.md", "04-solutions.md"];
|
|
4247
|
+
for (const fileName of digestFiles) {
|
|
4248
|
+
const filePath = path.join(digestPath, fileName);
|
|
4249
|
+
if (fs.existsSync(filePath)) {
|
|
4250
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
4251
|
+
output += content + "\n\n---\n\n";
|
|
4252
|
+
}
|
|
4253
|
+
}
|
|
4254
|
+
return { content: [{ type: "text", text: output }] };
|
|
4255
|
+
}
|
|
4256
|
+
case "context_digest_migrate": {
|
|
4257
|
+
const allContexts = listContexts();
|
|
4258
|
+
const memoryIndex = loadMemoryIndex();
|
|
4259
|
+
const solutionsIdx = loadSolutions();
|
|
4260
|
+
const keywordsIdx = loadKeywords();
|
|
4261
|
+
const projectName = detectProjectFromWorkDir() || "unknown";
|
|
4262
|
+
const digest = createEmptyDigest(projectName);
|
|
4263
|
+
// Сканируем все контексты
|
|
4264
|
+
for (const ctx of allContexts) {
|
|
4265
|
+
const contextId = String(ctx.number).padStart(3, "0");
|
|
4266
|
+
// Читаем README
|
|
4267
|
+
let summary = "";
|
|
4268
|
+
const readmePath = path.join(ctx.path, "README.md");
|
|
4269
|
+
if (fs.existsSync(readmePath)) {
|
|
4270
|
+
const readme = fs.readFileSync(readmePath, "utf-8");
|
|
4271
|
+
const descMatch = readme.match(/## Краткое описание\n\n([^\n#]+)/);
|
|
4272
|
+
summary = descMatch ? descMatch[1].trim() : readme.split("\n").slice(0, 3).join(" ").slice(0, 200);
|
|
4273
|
+
}
|
|
4274
|
+
// Читаем 03-changes.md для файлов
|
|
4275
|
+
let keyChanges = [];
|
|
4276
|
+
const changesPath = path.join(ctx.path, "03-changes.md");
|
|
4277
|
+
if (fs.existsSync(changesPath)) {
|
|
4278
|
+
const changesContent = fs.readFileSync(changesPath, "utf-8");
|
|
4279
|
+
keyChanges = extractKeyChangesFromText(changesContent);
|
|
4280
|
+
const files = extractFilesFromChanges(changesContent);
|
|
4281
|
+
for (const f of files) {
|
|
4282
|
+
if (!digest.currentState.keyFiles.includes(f)) {
|
|
4283
|
+
digest.currentState.keyFiles.push(f);
|
|
4284
|
+
}
|
|
4285
|
+
}
|
|
4286
|
+
}
|
|
4287
|
+
// Статистика
|
|
4288
|
+
digest.statistics.totalContexts++;
|
|
4289
|
+
if (ctx.status.includes("✅"))
|
|
4290
|
+
digest.statistics.completed++;
|
|
4291
|
+
else if (ctx.status.includes("⏳"))
|
|
4292
|
+
digest.statistics.inProgress++;
|
|
4293
|
+
else if (ctx.status.includes("❌"))
|
|
4294
|
+
digest.statistics.blocked++;
|
|
4295
|
+
if (!digest.statistics.firstDate || ctx.date < digest.statistics.firstDate) {
|
|
4296
|
+
digest.statistics.firstDate = ctx.date;
|
|
4297
|
+
}
|
|
4298
|
+
if (!digest.statistics.lastDate || ctx.date > digest.statistics.lastDate) {
|
|
4299
|
+
digest.statistics.lastDate = ctx.date;
|
|
4300
|
+
}
|
|
4301
|
+
// recentTasks (последние 30)
|
|
4302
|
+
digest.recentTasks.push({
|
|
4303
|
+
contextId,
|
|
4304
|
+
date: ctx.date,
|
|
4305
|
+
name: ctx.title,
|
|
4306
|
+
status: ctx.status,
|
|
4307
|
+
summary: summary.slice(0, 200),
|
|
4308
|
+
keyChanges,
|
|
4309
|
+
});
|
|
4310
|
+
}
|
|
4311
|
+
// Сортируем по номеру (новые первые) и обрезаем
|
|
4312
|
+
digest.recentTasks.sort((a, b) => parseInt(b.contextId) - parseInt(a.contextId));
|
|
4313
|
+
digest.recentTasks = digest.recentTasks.slice(0, 30);
|
|
4314
|
+
// Лимит keyFiles
|
|
4315
|
+
digest.currentState.keyFiles = digest.currentState.keyFiles.slice(0, 30);
|
|
4316
|
+
// techStack из keywords
|
|
4317
|
+
const techKeywords = ["typescript", "javascript", "python", "swift", "kotlin", "react", "vue", "node", "mcp", "trello", "obsidian"];
|
|
4318
|
+
for (const [kw] of Object.entries(keywordsIdx.keywords)) {
|
|
4319
|
+
if (techKeywords.includes(kw.toLowerCase())) {
|
|
4320
|
+
digest.currentState.techStack.push(kw);
|
|
4321
|
+
}
|
|
4322
|
+
}
|
|
4323
|
+
// solutions из solutions.json
|
|
4324
|
+
for (const sol of solutionsIdx.solutions.slice(0, 20)) {
|
|
4325
|
+
digest.solutions.push({
|
|
4326
|
+
problem: sol.problem,
|
|
4327
|
+
solution: sol.solution.join("; "),
|
|
4328
|
+
contextId: sol.sourceContexts[0] || "?",
|
|
4329
|
+
});
|
|
4330
|
+
}
|
|
4331
|
+
// Фазы — группировка по месяцам
|
|
4332
|
+
const monthMap = new Map();
|
|
4333
|
+
for (const task of digest.recentTasks) {
|
|
4334
|
+
const month = task.date.slice(0, 7); // YYYY-MM
|
|
4335
|
+
if (!monthMap.has(month)) {
|
|
4336
|
+
monthMap.set(month, { count: 0, tasks: [] });
|
|
4337
|
+
}
|
|
4338
|
+
const m = monthMap.get(month);
|
|
4339
|
+
m.count++;
|
|
4340
|
+
if (m.tasks.length < 3)
|
|
4341
|
+
m.tasks.push(task.name);
|
|
4342
|
+
}
|
|
4343
|
+
for (const [month, data] of monthMap) {
|
|
4344
|
+
digest.phases.push({
|
|
4345
|
+
name: month,
|
|
4346
|
+
dateRange: month,
|
|
4347
|
+
description: `${data.count} задач: ${data.tasks.join(", ")}${data.count > 3 ? " и др." : ""}`,
|
|
4348
|
+
});
|
|
4349
|
+
}
|
|
4350
|
+
// activeEpics из memory
|
|
4351
|
+
const epics = loadEpics();
|
|
4352
|
+
for (const [name, epic] of Object.entries(epics.epics)) {
|
|
4353
|
+
if (epic.status === "in_progress") {
|
|
4354
|
+
digest.currentState.activeEpics.push(name);
|
|
4355
|
+
}
|
|
4356
|
+
}
|
|
4357
|
+
// Сохраняем
|
|
4358
|
+
saveDigest(digest);
|
|
4359
|
+
let output = `✅ **Дайджест создан!**\n\n`;
|
|
4360
|
+
output += `📦 **Проект:** ${projectName}\n`;
|
|
4361
|
+
output += `📊 **Контекстов обработано:** ${allContexts.length}\n`;
|
|
4362
|
+
output += `📁 **Путь:** \`${getDigestPath()}\`\n\n`;
|
|
4363
|
+
output += `**Создано файлов:**\n`;
|
|
4364
|
+
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`;
|
|
4365
|
+
output += `---\n\n`;
|
|
4366
|
+
output += `💡 **Следующий шаг:** скажите "обнови дайджест" (refresh) — Claude проанализирует и добавит качественное описание проекта, фазы эволюции и т.д.\n\n`;
|
|
4367
|
+
output += `📦 Дайджест будет автоматически обновляться при каждом \`context_save\`.`;
|
|
4368
|
+
return { content: [{ type: "text", text: output }] };
|
|
4369
|
+
}
|
|
4370
|
+
case "context_digest_refresh": {
|
|
4371
|
+
const digest = loadDigest();
|
|
4372
|
+
if (!digest) {
|
|
4373
|
+
return {
|
|
4374
|
+
content: [{
|
|
4375
|
+
type: "text",
|
|
4376
|
+
text: `📦 **Дайджест не найден**\n\nСначала создайте дайджест: "миграция дайджеста" или "digest migrate"`,
|
|
4377
|
+
}],
|
|
4378
|
+
};
|
|
4379
|
+
}
|
|
4380
|
+
// Собираем README последних 10 контекстов
|
|
4381
|
+
const contexts = listContexts().slice(0, 10);
|
|
4382
|
+
let readmes = "";
|
|
4383
|
+
for (const ctx of contexts) {
|
|
4384
|
+
const readmePath = path.join(ctx.path, "README.md");
|
|
4385
|
+
if (fs.existsSync(readmePath)) {
|
|
4386
|
+
readmes += `\n---\n### #${String(ctx.number).padStart(3, "0")} | ${ctx.date} | ${ctx.title} | ${ctx.status}\n\n`;
|
|
4387
|
+
const content = fs.readFileSync(readmePath, "utf-8");
|
|
4388
|
+
readmes += content.slice(0, 1500) + "\n";
|
|
4389
|
+
}
|
|
4390
|
+
}
|
|
4391
|
+
let output = `# 🔄 Refresh дайджеста\n\n`;
|
|
4392
|
+
output += `## Текущий digest.json:\n\n`;
|
|
4393
|
+
output += `\`\`\`json\n${JSON.stringify(digest, null, 2)}\n\`\`\`\n\n`;
|
|
4394
|
+
output += `---\n\n`;
|
|
4395
|
+
output += `## README последних 10 контекстов:\n\n`;
|
|
4396
|
+
output += readmes;
|
|
4397
|
+
output += `\n\n---\n\n`;
|
|
4398
|
+
output += `<claude-instruction>\n`;
|
|
4399
|
+
output += `Проанализируй digest.json и README контекстов выше.\n`;
|
|
4400
|
+
output += `Затем вызови context_digest_write с параметрами:\n`;
|
|
4401
|
+
output += `- description: описание проекта (3-5 предложений)\n`;
|
|
4402
|
+
output += `- tech_stack: стек через запятую\n`;
|
|
4403
|
+
output += `- key_files: ключевые файлы через запятую\n`;
|
|
4404
|
+
output += `- phases: фазы эволюции (Название | даты | описание на каждой строке)\n`;
|
|
4405
|
+
output += `- key_decisions: ключевые решения\n`;
|
|
4406
|
+
output += `- known_issues: текущие проблемы\n`;
|
|
4407
|
+
output += `- active_epics: активные эпики\n`;
|
|
4408
|
+
output += `</claude-instruction>`;
|
|
4409
|
+
return { content: [{ type: "text", text: output }] };
|
|
4410
|
+
}
|
|
4411
|
+
case "context_digest_write": {
|
|
4412
|
+
let digest = loadDigest();
|
|
4413
|
+
if (!digest) {
|
|
4414
|
+
const projectName = detectProjectFromWorkDir() || "unknown";
|
|
4415
|
+
digest = createEmptyDigest(projectName);
|
|
4416
|
+
}
|
|
4417
|
+
// Обновляем currentState
|
|
4418
|
+
if (args?.description) {
|
|
4419
|
+
digest.currentState.description = args.description;
|
|
4420
|
+
}
|
|
4421
|
+
if (args?.tech_stack) {
|
|
4422
|
+
digest.currentState.techStack = args.tech_stack.split(",").map(s => s.trim()).filter(Boolean);
|
|
4423
|
+
}
|
|
4424
|
+
if (args?.key_files) {
|
|
4425
|
+
digest.currentState.keyFiles = args.key_files.split(",").map(s => s.trim()).filter(Boolean);
|
|
4426
|
+
}
|
|
4427
|
+
if (args?.active_epics) {
|
|
4428
|
+
digest.currentState.activeEpics = args.active_epics.split(",").map(s => s.trim()).filter(Boolean);
|
|
4429
|
+
}
|
|
4430
|
+
// Обновляем known_issues
|
|
4431
|
+
if (args?.known_issues) {
|
|
4432
|
+
digest.knownIssues = args.known_issues.split("\n").map(s => s.trim()).filter(Boolean).slice(0, 10);
|
|
4433
|
+
}
|
|
4434
|
+
// Обновляем phases
|
|
4435
|
+
if (args?.phases) {
|
|
4436
|
+
const phaseLines = args.phases.split("\n").filter(l => l.trim());
|
|
4437
|
+
digest.phases = phaseLines.map(line => {
|
|
4438
|
+
const parts = line.split("|").map(p => p.trim());
|
|
4439
|
+
return {
|
|
4440
|
+
name: parts[0] || "Phase",
|
|
4441
|
+
dateRange: parts[1] || "",
|
|
4442
|
+
description: parts[2] || "",
|
|
4443
|
+
};
|
|
4444
|
+
});
|
|
4445
|
+
}
|
|
4446
|
+
// Обновляем key_decisions
|
|
4447
|
+
if (args?.key_decisions) {
|
|
4448
|
+
const decLines = args.key_decisions.split("\n").filter(l => l.trim());
|
|
4449
|
+
const today = new Date().toISOString().split("T")[0];
|
|
4450
|
+
digest.keyDecisions = decLines.map(line => ({
|
|
4451
|
+
decision: line.trim(),
|
|
4452
|
+
date: today,
|
|
4453
|
+
contextId: "refresh",
|
|
4454
|
+
})).slice(0, 20);
|
|
4455
|
+
}
|
|
4456
|
+
digest.lastUpdated = new Date().toISOString().split("T")[0];
|
|
4457
|
+
// Сохраняем и перерендериваем
|
|
4458
|
+
saveDigest(digest);
|
|
4459
|
+
return {
|
|
4460
|
+
content: [{
|
|
4461
|
+
type: "text",
|
|
4462
|
+
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"`,
|
|
4463
|
+
}],
|
|
4464
|
+
};
|
|
4465
|
+
}
|
|
3702
4466
|
// ========== ТЕСТОВЫЕ HANDLERS SMART CONTEXT ==========
|
|
3703
4467
|
case "context_smart": {
|
|
3704
4468
|
const history = readHistory();
|
|
@@ -3730,55 +4494,51 @@ MD_HISTORY/
|
|
|
3730
4494
|
byProject[project].push(entry);
|
|
3731
4495
|
}
|
|
3732
4496
|
// Формируем компактный вывод
|
|
3733
|
-
let output =
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
for (const [person, list] of Object.entries(byPerson)) {
|
|
3737
|
-
if (person !== "other") {
|
|
3738
|
-
const ids = list.slice(0, 8).map(e => `#${e.number}`).join(", ");
|
|
3739
|
-
const more = list.length > 8 ? ` (+${list.length - 8})` : "";
|
|
3740
|
-
output += `- **${person}** (${list.length}): ${ids}${more}\n`;
|
|
3741
|
-
}
|
|
3742
|
-
}
|
|
3743
|
-
if (byPerson["other"]) {
|
|
3744
|
-
output += `- _другие_ (${byPerson["other"].length})\n`;
|
|
3745
|
-
}
|
|
3746
|
-
// По проектам (если есть Memory v2.0 данные)
|
|
4497
|
+
let output = formatRulesBlock();
|
|
4498
|
+
output += `# 🧠 Smart Context — Обзор ${entries.length} контекстов\n\n`;
|
|
4499
|
+
// По проектам (основной акцент — задачи с кодом)
|
|
3747
4500
|
const projectsWithData = Object.entries(byProject).filter(([p]) => p !== "unknown");
|
|
3748
4501
|
if (projectsWithData.length > 0) {
|
|
3749
|
-
output +=
|
|
4502
|
+
output += `## 📁 По проектам:\n`;
|
|
3750
4503
|
for (const [project, list] of projectsWithData) {
|
|
3751
|
-
|
|
4504
|
+
const ids = list.slice(0, 5).map(e => `#${e.number}`).join(", ");
|
|
4505
|
+
const more = list.length > 5 ? ` (+${list.length - 5})` : "";
|
|
4506
|
+
output += `- **${project}** (${list.length}): ${ids}${more}\n`;
|
|
4507
|
+
}
|
|
4508
|
+
}
|
|
4509
|
+
// По людям (если есть контексты связанные с людьми)
|
|
4510
|
+
const personsWithData = Object.keys(byPerson).filter(p => p !== "other");
|
|
4511
|
+
if (personsWithData.length > 0) {
|
|
4512
|
+
output += `\n## 👥 По людям:\n`;
|
|
4513
|
+
for (const [person, list] of Object.entries(byPerson)) {
|
|
4514
|
+
if (person !== "other") {
|
|
4515
|
+
const ids = list.slice(0, 5).map(e => `#${e.number}`).join(", ");
|
|
4516
|
+
const more = list.length > 5 ? ` (+${list.length - 5})` : "";
|
|
4517
|
+
output += `- **${person}** (${list.length}): ${ids}${more}\n`;
|
|
4518
|
+
}
|
|
3752
4519
|
}
|
|
3753
4520
|
}
|
|
3754
4521
|
// Последние 10 с краткими сводками
|
|
3755
4522
|
output += `\n## 📋 Последние 10:\n`;
|
|
3756
|
-
output += `| # | Название |
|
|
4523
|
+
output += `| # | Название | Проект | Сводка |\n|---|----------|--------|--------|\n`;
|
|
3757
4524
|
for (const entry of entries.slice(0, 10)) {
|
|
4525
|
+
const meta = memoryIndex.contexts[entry.number];
|
|
4526
|
+
const project = meta?.project || "-";
|
|
3758
4527
|
const shortSummary = entry.summary.slice(0, 50).replace(/\n/g, " ") + "...";
|
|
3759
|
-
output += `| ${entry.number} | ${entry.name} | ${
|
|
4528
|
+
output += `| ${entry.number} | ${entry.name} | ${project} | ${shortSummary} |\n`;
|
|
3760
4529
|
}
|
|
3761
4530
|
// Генерируем динамические примеры на основе реальных данных
|
|
3762
4531
|
output += `\n---\n`;
|
|
3763
4532
|
output += `💡 **Опиши задачу** в свободном формате, и я подгружу релевантные контексты:\n`;
|
|
3764
4533
|
const examples = [];
|
|
3765
|
-
// Пример с
|
|
3766
|
-
const persons = Object.keys(byPerson).filter(p => p !== "other");
|
|
3767
|
-
if (persons.length > 0) {
|
|
3768
|
-
const person = persons[0];
|
|
3769
|
-
const personEntry = byPerson[person][0];
|
|
3770
|
-
// Извлекаем ключевое слово из названия
|
|
3771
|
-
const keyword = personEntry.name.split("-").find(w => w.length > 3 && !w.includes(person)) || "";
|
|
3772
|
-
examples.push(`"${keyword ? keyword + " " : ""}${person}"`);
|
|
3773
|
-
}
|
|
3774
|
-
// Пример с проектом (если есть)
|
|
4534
|
+
// Пример с проектом/технологией (основной акцент)
|
|
3775
4535
|
if (projectsWithData.length > 0) {
|
|
3776
4536
|
const [project, list] = projectsWithData[0];
|
|
3777
4537
|
const entry = list[0];
|
|
3778
4538
|
const keyword = entry.name.split("-").find(w => w.length > 3) || "";
|
|
3779
4539
|
examples.push(`"${keyword} ${project}"`);
|
|
3780
4540
|
}
|
|
3781
|
-
// Пример из последнего контекста
|
|
4541
|
+
// Пример из последнего контекста (по ключевым словам)
|
|
3782
4542
|
if (entries.length > 0) {
|
|
3783
4543
|
const lastEntry = entries[0];
|
|
3784
4544
|
const words = lastEntry.name.split("-").filter(w => w.length > 3).slice(0, 2);
|
|
@@ -3786,6 +4546,13 @@ MD_HISTORY/
|
|
|
3786
4546
|
examples.push(`"${words.join(" ")}"`);
|
|
3787
4547
|
}
|
|
3788
4548
|
}
|
|
4549
|
+
// Пример с человеком (если есть контексты с людьми)
|
|
4550
|
+
if (personsWithData.length > 0) {
|
|
4551
|
+
const person = personsWithData[0];
|
|
4552
|
+
const personEntry = byPerson[person][0];
|
|
4553
|
+
const keyword = personEntry.name.split("-").find(w => w.length > 3 && !w.includes(person)) || "";
|
|
4554
|
+
examples.push(`"${keyword ? keyword + " " : ""}${person}"`);
|
|
4555
|
+
}
|
|
3789
4556
|
// Выводим примеры (максимум 3)
|
|
3790
4557
|
for (const example of examples.slice(0, 3)) {
|
|
3791
4558
|
output += `- ${example}\n`;
|
|
@@ -3842,7 +4609,8 @@ MD_HISTORY/
|
|
|
3842
4609
|
return { content: [{ type: "text", text: output }] };
|
|
3843
4610
|
}
|
|
3844
4611
|
// Загружаем контексты полностью
|
|
3845
|
-
let output =
|
|
4612
|
+
let output = formatRulesBlock();
|
|
4613
|
+
output += `# 🎯 Фокус: "${task}"\n\n`;
|
|
3846
4614
|
output += `**Найдено:** ${top.length} релевантных контекстов\n`;
|
|
3847
4615
|
if (queryPerson)
|
|
3848
4616
|
output += `**Человек:** ${queryPerson}\n`;
|