dedsession 2.1.0 → 2.3.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 +640 -33
- 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: "2.
|
|
11
|
+
VERSION: "2.3.0",
|
|
12
12
|
};
|
|
13
13
|
const state = {
|
|
14
14
|
loadedContext: null,
|
|
@@ -435,6 +435,13 @@ function listContexts() {
|
|
|
435
435
|
return [];
|
|
436
436
|
}
|
|
437
437
|
}
|
|
438
|
+
// Находит путь к контексту по номеру (для context_smart_focus)
|
|
439
|
+
function findContextByNumber(number) {
|
|
440
|
+
const contexts = listContexts();
|
|
441
|
+
const numInt = parseInt(number, 10);
|
|
442
|
+
const found = contexts.find(c => c.number === numInt);
|
|
443
|
+
return found?.path || null;
|
|
444
|
+
}
|
|
438
445
|
// ============================================================================
|
|
439
446
|
// АДАПТИВНЫЕ ФУНКЦИИ v1.4.3
|
|
440
447
|
// ============================================================================
|
|
@@ -1153,6 +1160,157 @@ function saveSolutions(solutions) {
|
|
|
1153
1160
|
const filePath = path.join(getMemoryPath(), "solutions.json");
|
|
1154
1161
|
fs.writeFileSync(filePath, JSON.stringify(solutions, null, 2), "utf-8");
|
|
1155
1162
|
}
|
|
1163
|
+
/**
|
|
1164
|
+
* Извлекает решения из текста контекста (legacy и новый формат)
|
|
1165
|
+
* Ищет паттерны: "Проблема:", "Решение:", "fix", "исправил", "причина" и т.д.
|
|
1166
|
+
*/
|
|
1167
|
+
function extractSolutionsFromText(text, contextId) {
|
|
1168
|
+
const solutions = [];
|
|
1169
|
+
if (!text || text.length < 50)
|
|
1170
|
+
return solutions;
|
|
1171
|
+
// Паттерн 1: Структурированный формат "Проблема: X / Решение: Y"
|
|
1172
|
+
const structuredPattern = /(?:проблема|problem|issue|баг|bug|ошибка)[::]\s*([^\n]+)[\s\S]*?(?:решение|solution|fix|исправление)[::]\s*([^\n]+(?:\n(?!(?:проблема|problem|issue|баг|bug|ошибка))[^\n]+)*)/gi;
|
|
1173
|
+
let match;
|
|
1174
|
+
while ((match = structuredPattern.exec(text)) !== null) {
|
|
1175
|
+
const problem = match[1].trim();
|
|
1176
|
+
const solutionText = match[2].trim();
|
|
1177
|
+
if (problem.length > 10 && solutionText.length > 10) {
|
|
1178
|
+
solutions.push({
|
|
1179
|
+
problem,
|
|
1180
|
+
solution: solutionText.split("\n").map(s => s.trim()).filter(s => s.length > 0),
|
|
1181
|
+
tags: extractSolutionTags(problem + " " + solutionText),
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
// Паттерн 2: Нумерованный список "1. Проблема: X"
|
|
1186
|
+
const numberedPattern = /\d+\.\s*(?:проблема|problem)[::]?\s*([^\n]+)[\s\S]*?(?:решение|solution|fix)[::]?\s*([^\n]+(?:\n(?!\d+\.)[^\n]+)*)/gi;
|
|
1187
|
+
while ((match = numberedPattern.exec(text)) !== null) {
|
|
1188
|
+
const problem = match[1].trim();
|
|
1189
|
+
const solutionText = match[2].trim();
|
|
1190
|
+
if (problem.length > 10 && solutionText.length > 10) {
|
|
1191
|
+
// Проверяем что не дубликат
|
|
1192
|
+
if (!solutions.some(s => s.problem.toLowerCase() === problem.toLowerCase())) {
|
|
1193
|
+
solutions.push({
|
|
1194
|
+
problem,
|
|
1195
|
+
solution: solutionText.split("\n").map(s => s.trim()).filter(s => s.length > 0),
|
|
1196
|
+
tags: extractSolutionTags(problem + " " + solutionText),
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
// Паттерн 3: "исправил X" / "fix: X" / "fixed X"
|
|
1202
|
+
const fixPattern = /(?:исправил|fixed|fix[::])\s+([^.!?\n]{15,100})/gi;
|
|
1203
|
+
while ((match = fixPattern.exec(text)) !== null) {
|
|
1204
|
+
const fixText = match[1].trim();
|
|
1205
|
+
// Извлекаем контекст вокруг fix
|
|
1206
|
+
const startIdx = Math.max(0, match.index - 200);
|
|
1207
|
+
const context = text.slice(startIdx, match.index);
|
|
1208
|
+
const problemHint = context.match(/(?:проблема|ошибка|баг|issue|bug)[::]?\s*([^\n]{10,80})/i);
|
|
1209
|
+
if (!solutions.some(s => s.solution.some(sol => sol.includes(fixText.slice(0, 30))))) {
|
|
1210
|
+
solutions.push({
|
|
1211
|
+
problem: problemHint ? problemHint[1].trim() : `Исправление в контексте #${contextId}`,
|
|
1212
|
+
solution: [fixText],
|
|
1213
|
+
tags: extractSolutionTags(fixText),
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
// Паттерн 4: "причина: X" с последующим решением
|
|
1218
|
+
const causePattern = /(?:причина|cause|root cause)[::]\s*([^\n]+)[\s\S]{0,300}?(?:решение|solution|fix)[::]\s*([^\n]+)/gi;
|
|
1219
|
+
while ((match = causePattern.exec(text)) !== null) {
|
|
1220
|
+
const cause = match[1].trim();
|
|
1221
|
+
const fix = match[2].trim();
|
|
1222
|
+
if (cause.length > 10 && fix.length > 10) {
|
|
1223
|
+
if (!solutions.some(s => s.problem.includes(cause.slice(0, 30)))) {
|
|
1224
|
+
solutions.push({
|
|
1225
|
+
problem: cause,
|
|
1226
|
+
solution: [fix],
|
|
1227
|
+
tags: extractSolutionTags(cause + " " + fix),
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
return solutions;
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* Извлекает теги из текста решения
|
|
1236
|
+
*/
|
|
1237
|
+
function extractSolutionTags(text) {
|
|
1238
|
+
const tags = [];
|
|
1239
|
+
const lowerText = text.toLowerCase();
|
|
1240
|
+
const tagPatterns = {
|
|
1241
|
+
git: ["git", "commit", "push", "merge", "rebase", "branch"],
|
|
1242
|
+
npm: ["npm", "package.json", "node_modules", "yarn", "pnpm"],
|
|
1243
|
+
typescript: ["typescript", "ts", "type", "interface", "generic"],
|
|
1244
|
+
react: ["react", "component", "hook", "useState", "useEffect"],
|
|
1245
|
+
api: ["api", "endpoint", "fetch", "axios", "request", "response"],
|
|
1246
|
+
cache: ["cache", "кеш", "npx", "очистка"],
|
|
1247
|
+
build: ["build", "сборка", "compile", "webpack", "vite"],
|
|
1248
|
+
ios: ["ios", "swift", "xcode", "cocoapods"],
|
|
1249
|
+
android: ["android", "kotlin", "gradle"],
|
|
1250
|
+
database: ["database", "db", "sql", "mongo", "postgres"],
|
|
1251
|
+
auth: ["auth", "token", "jwt", "login", "password"],
|
|
1252
|
+
performance: ["performance", "оптимизация", "память", "memory"],
|
|
1253
|
+
};
|
|
1254
|
+
for (const [tag, patterns] of Object.entries(tagPatterns)) {
|
|
1255
|
+
if (patterns.some(p => lowerText.includes(p))) {
|
|
1256
|
+
tags.push(tag);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
return tags.slice(0, 5); // Максимум 5 тегов
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Добавляет извлечённые решения в базу
|
|
1263
|
+
*/
|
|
1264
|
+
function addSolutionsToIndex(extracted, contextId) {
|
|
1265
|
+
if (extracted.length === 0)
|
|
1266
|
+
return 0;
|
|
1267
|
+
const solutionsIndex = loadSolutions();
|
|
1268
|
+
let added = 0;
|
|
1269
|
+
for (const ext of extracted) {
|
|
1270
|
+
// Проверяем на дубликаты по проблеме
|
|
1271
|
+
const isDuplicate = solutionsIndex.solutions.some(s => s.problem.toLowerCase().includes(ext.problem.toLowerCase().slice(0, 30)) ||
|
|
1272
|
+
ext.problem.toLowerCase().includes(s.problem.toLowerCase().slice(0, 30)));
|
|
1273
|
+
if (!isDuplicate) {
|
|
1274
|
+
const newId = `sol_${contextId}_${solutionsIndex.solutions.length + 1}`;
|
|
1275
|
+
solutionsIndex.solutions.push({
|
|
1276
|
+
id: newId,
|
|
1277
|
+
problem: ext.problem,
|
|
1278
|
+
solution: ext.solution,
|
|
1279
|
+
sourceContexts: [contextId],
|
|
1280
|
+
codeExample: undefined,
|
|
1281
|
+
});
|
|
1282
|
+
added++;
|
|
1283
|
+
}
|
|
1284
|
+
else {
|
|
1285
|
+
// Добавляем контекст к существующему решению
|
|
1286
|
+
const existing = solutionsIndex.solutions.find(s => s.problem.toLowerCase().includes(ext.problem.toLowerCase().slice(0, 30)));
|
|
1287
|
+
if (existing && !existing.sourceContexts.includes(contextId)) {
|
|
1288
|
+
existing.sourceContexts.push(contextId);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
if (added > 0) {
|
|
1293
|
+
saveSolutions(solutionsIndex);
|
|
1294
|
+
}
|
|
1295
|
+
return added;
|
|
1296
|
+
}
|
|
1297
|
+
// ============================================================================
|
|
1298
|
+
// ПРАВИЛА v2.2: ЛОКАЛЬНЫЕ + ГЛОБАЛЬНЫЕ
|
|
1299
|
+
// ============================================================================
|
|
1300
|
+
/**
|
|
1301
|
+
* Путь к глобальной конфигурации ~/.dedsession/
|
|
1302
|
+
*/
|
|
1303
|
+
function getGlobalConfigPath() {
|
|
1304
|
+
const home = process.env.HOME || process.env.USERPROFILE || "/tmp";
|
|
1305
|
+
const globalPath = path.join(home, ".dedsession");
|
|
1306
|
+
if (!fs.existsSync(globalPath)) {
|
|
1307
|
+
fs.mkdirSync(globalPath, { recursive: true });
|
|
1308
|
+
}
|
|
1309
|
+
return globalPath;
|
|
1310
|
+
}
|
|
1311
|
+
/**
|
|
1312
|
+
* Загрузить ЛОКАЛЬНЫЕ правила (из MD_HISTORY/memory/)
|
|
1313
|
+
*/
|
|
1156
1314
|
function loadRules() {
|
|
1157
1315
|
const filePath = path.join(getMemoryPath(), "rules.json");
|
|
1158
1316
|
try {
|
|
@@ -1163,23 +1321,65 @@ function loadRules() {
|
|
|
1163
1321
|
catch { /* ignore */ }
|
|
1164
1322
|
return { rules: [] };
|
|
1165
1323
|
}
|
|
1324
|
+
/**
|
|
1325
|
+
* Сохранить ЛОКАЛЬНЫЕ правила
|
|
1326
|
+
*/
|
|
1166
1327
|
function saveRules(rules) {
|
|
1167
1328
|
ensureMemoryStructure();
|
|
1168
1329
|
const filePath = path.join(getMemoryPath(), "rules.json");
|
|
1169
1330
|
fs.writeFileSync(filePath, JSON.stringify(rules, null, 2), "utf-8");
|
|
1170
1331
|
}
|
|
1332
|
+
/**
|
|
1333
|
+
* Загрузить ГЛОБАЛЬНЫЕ правила (из ~/.dedsession/)
|
|
1334
|
+
*/
|
|
1335
|
+
function loadGlobalRules() {
|
|
1336
|
+
const filePath = path.join(getGlobalConfigPath(), "global-rules.json");
|
|
1337
|
+
try {
|
|
1338
|
+
if (fs.existsSync(filePath)) {
|
|
1339
|
+
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
catch { /* ignore */ }
|
|
1343
|
+
return { rules: [] };
|
|
1344
|
+
}
|
|
1345
|
+
/**
|
|
1346
|
+
* Сохранить ГЛОБАЛЬНЫЕ правила
|
|
1347
|
+
*/
|
|
1348
|
+
function saveGlobalRules(rules) {
|
|
1349
|
+
const filePath = path.join(getGlobalConfigPath(), "global-rules.json");
|
|
1350
|
+
fs.writeFileSync(filePath, JSON.stringify(rules, null, 2), "utf-8");
|
|
1351
|
+
}
|
|
1352
|
+
/**
|
|
1353
|
+
* Форматирует блок правил (глобальные + локальные)
|
|
1354
|
+
*/
|
|
1171
1355
|
function formatRulesBlock() {
|
|
1172
|
-
const
|
|
1173
|
-
|
|
1356
|
+
const globalRules = loadGlobalRules();
|
|
1357
|
+
const localRules = loadRules();
|
|
1358
|
+
if (globalRules.rules.length === 0 && localRules.rules.length === 0)
|
|
1174
1359
|
return "";
|
|
1175
|
-
let output = "#
|
|
1176
|
-
output += ">
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1360
|
+
let output = "# 🔴 ПРИОРИТЕТНЫЕ ПРАВИЛА\n\n";
|
|
1361
|
+
output += "> Эти правила Claude ОБЯЗАН соблюдать во ВСЕХ контекстах!\n\n";
|
|
1362
|
+
// Глобальные правила
|
|
1363
|
+
if (globalRules.rules.length > 0) {
|
|
1364
|
+
output += "**🌍 Глобальные (все проекты):**\n";
|
|
1365
|
+
for (const rule of globalRules.rules) {
|
|
1366
|
+
output += `- **G${rule.id}.** ${rule.rule}`;
|
|
1367
|
+
if (rule.context)
|
|
1368
|
+
output += ` _(${rule.context})_`;
|
|
1369
|
+
output += "\n";
|
|
1370
|
+
}
|
|
1181
1371
|
output += "\n";
|
|
1182
1372
|
}
|
|
1373
|
+
// Локальные правила
|
|
1374
|
+
if (localRules.rules.length > 0) {
|
|
1375
|
+
output += "**📁 Локальные (этот проект):**\n";
|
|
1376
|
+
for (const rule of localRules.rules) {
|
|
1377
|
+
output += `- **L${rule.id}.** ${rule.rule}`;
|
|
1378
|
+
if (rule.context)
|
|
1379
|
+
output += ` _(${rule.context})_`;
|
|
1380
|
+
output += "\n";
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1183
1383
|
output += "\n---\n\n";
|
|
1184
1384
|
return output;
|
|
1185
1385
|
}
|
|
@@ -1194,6 +1394,14 @@ const PROJECT_PATTERNS = {
|
|
|
1194
1394
|
dedsession: ["dedsession", "mcp", "context", "memory", "session"],
|
|
1195
1395
|
web: ["web", "react", "vue", "angular", "nextjs", "frontend", "html", "css"],
|
|
1196
1396
|
};
|
|
1397
|
+
// Паттерны для определения людей (для context_smart)
|
|
1398
|
+
const PERSON_PATTERNS = {
|
|
1399
|
+
muradbek: ["muradbek", "мурадбек", "мурад", "ibraghimovmuradbek"],
|
|
1400
|
+
vlad: ["vlad", "vlad0sminer", "влад", "stweekmine"],
|
|
1401
|
+
makhach: ["makhach", "махач", "makhach1717"],
|
|
1402
|
+
akhmad: ["akhmad", "ахмад", "blck6rd"],
|
|
1403
|
+
eldad: ["eldad", "эльдар"],
|
|
1404
|
+
};
|
|
1197
1405
|
function detectProject(content, folderName) {
|
|
1198
1406
|
const lowerContent = (content + " " + folderName).toLowerCase();
|
|
1199
1407
|
let bestMatch = null;
|
|
@@ -1210,6 +1418,92 @@ function detectProject(content, folderName) {
|
|
|
1210
1418
|
}
|
|
1211
1419
|
return bestMatch?.project || null;
|
|
1212
1420
|
}
|
|
1421
|
+
// Определяет человека по содержимому (для context_smart)
|
|
1422
|
+
function detectPerson(content) {
|
|
1423
|
+
const lower = content.toLowerCase();
|
|
1424
|
+
for (const [person, patterns] of Object.entries(PERSON_PATTERNS)) {
|
|
1425
|
+
for (const pattern of patterns) {
|
|
1426
|
+
if (lower.includes(pattern)) {
|
|
1427
|
+
return person;
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
return null;
|
|
1432
|
+
}
|
|
1433
|
+
// Парсит HISTORY.md и возвращает последние N записей (для context_smart)
|
|
1434
|
+
function parseHistoryEntries(historyContent, limit = 25) {
|
|
1435
|
+
const entries = [];
|
|
1436
|
+
// Формат записи в HISTORY.md:
|
|
1437
|
+
// ---
|
|
1438
|
+
// #001 | 2026-01-07 | task-name | ✅
|
|
1439
|
+
// **Сводка:** текст сводки...
|
|
1440
|
+
// **Файлы:** список файлов
|
|
1441
|
+
const entryRegex = /#(\d{3})\s*\|\s*(\d{4}-\d{2}-\d{2})\s*\|\s*([^\|]+)\s*\|\s*(✅|⏳|❌)/g;
|
|
1442
|
+
const sections = historyContent.split(/^---$/m);
|
|
1443
|
+
for (const section of sections) {
|
|
1444
|
+
const match = entryRegex.exec(section);
|
|
1445
|
+
if (match) {
|
|
1446
|
+
const [, number, date, name, status] = match;
|
|
1447
|
+
const cleanName = name.trim();
|
|
1448
|
+
// Извлекаем сводку (всё после заголовка записи)
|
|
1449
|
+
const summaryStart = section.indexOf(status) + status.length;
|
|
1450
|
+
const summary = section.slice(summaryStart).trim();
|
|
1451
|
+
// Определяем человека по названию и сводке
|
|
1452
|
+
const person = detectPerson(cleanName + " " + summary);
|
|
1453
|
+
// Извлекаем ключевые слова из названия
|
|
1454
|
+
const keywords = cleanName.split(/[-_\s]+/).filter(w => w.length > 2);
|
|
1455
|
+
entries.push({
|
|
1456
|
+
number,
|
|
1457
|
+
date,
|
|
1458
|
+
name: cleanName,
|
|
1459
|
+
status,
|
|
1460
|
+
summary,
|
|
1461
|
+
person,
|
|
1462
|
+
keywords,
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
// Сбрасываем regex для следующей итерации
|
|
1466
|
+
entryRegex.lastIndex = 0;
|
|
1467
|
+
}
|
|
1468
|
+
// Сортируем по номеру (новые первые) и берём limit
|
|
1469
|
+
return entries
|
|
1470
|
+
.sort((a, b) => parseInt(b.number) - parseInt(a.number))
|
|
1471
|
+
.slice(0, limit);
|
|
1472
|
+
}
|
|
1473
|
+
// Вычисляет релевантность контекста к запросу (для context_smart_focus)
|
|
1474
|
+
function scoreContextRelevance(entry, queryPerson, queryKeywords, memoryIndex) {
|
|
1475
|
+
let score = 0;
|
|
1476
|
+
// +10 за совпадение человека
|
|
1477
|
+
if (queryPerson && entry.person === queryPerson) {
|
|
1478
|
+
score += 10;
|
|
1479
|
+
}
|
|
1480
|
+
// +5 за совпадение ключевого слова в названии
|
|
1481
|
+
for (const kw of queryKeywords) {
|
|
1482
|
+
if (entry.name.toLowerCase().includes(kw)) {
|
|
1483
|
+
score += 5;
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
// +2 за совпадение ключевого слова в сводке
|
|
1487
|
+
for (const kw of queryKeywords) {
|
|
1488
|
+
if (entry.summary.toLowerCase().includes(kw)) {
|
|
1489
|
+
score += 2;
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
// Бонус из Memory v2.0: +3 за совпадение project
|
|
1493
|
+
const contextMeta = memoryIndex.contexts[entry.number];
|
|
1494
|
+
if (contextMeta) {
|
|
1495
|
+
for (const kw of queryKeywords) {
|
|
1496
|
+
if (contextMeta.project && contextMeta.project.toLowerCase().includes(kw)) {
|
|
1497
|
+
score += 3;
|
|
1498
|
+
}
|
|
1499
|
+
// +1 за совпадение в keywords из Memory
|
|
1500
|
+
if (contextMeta.keywords.some(k => k.includes(kw) || kw.includes(k))) {
|
|
1501
|
+
score += 1;
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
return score;
|
|
1506
|
+
}
|
|
1213
1507
|
function extractKeywords(content) {
|
|
1214
1508
|
const keywords = new Set();
|
|
1215
1509
|
// Извлекаем технологии и инструменты
|
|
@@ -1287,6 +1581,7 @@ function migrateToMemorySystem() {
|
|
|
1287
1581
|
migrated: 0,
|
|
1288
1582
|
projects: [],
|
|
1289
1583
|
epics: [],
|
|
1584
|
+
solutions: 0,
|
|
1290
1585
|
errors: [],
|
|
1291
1586
|
};
|
|
1292
1587
|
try {
|
|
@@ -1311,7 +1606,20 @@ function migrateToMemorySystem() {
|
|
|
1311
1606
|
if (fs.existsSync(changesPath)) {
|
|
1312
1607
|
changes = fs.readFileSync(changesPath, "utf-8");
|
|
1313
1608
|
}
|
|
1609
|
+
// v2.2: Читаем problems-solutions если есть
|
|
1610
|
+
const problemsPath = path.join(ctx.path, "08-problems-solutions.md");
|
|
1611
|
+
let problemsSolutions = "";
|
|
1612
|
+
if (fs.existsSync(problemsPath)) {
|
|
1613
|
+
problemsSolutions = fs.readFileSync(problemsPath, "utf-8");
|
|
1614
|
+
}
|
|
1615
|
+
// v2.2: Читаем session-log для legacy решений
|
|
1616
|
+
const sessionLogPath = path.join(ctx.path, "05-session-log.md");
|
|
1617
|
+
let sessionLog = "";
|
|
1618
|
+
if (fs.existsSync(sessionLogPath)) {
|
|
1619
|
+
sessionLog = fs.readFileSync(sessionLogPath, "utf-8");
|
|
1620
|
+
}
|
|
1314
1621
|
const fullContent = readme + "\n" + changes;
|
|
1622
|
+
const solutionsContent = problemsSolutions + "\n" + readme + "\n" + changes + "\n" + sessionLog;
|
|
1315
1623
|
// Определяем проект
|
|
1316
1624
|
const project = detectProject(fullContent, ctx.name);
|
|
1317
1625
|
// Извлекаем ключевые слова
|
|
@@ -1381,6 +1689,12 @@ function migrateToMemorySystem() {
|
|
|
1381
1689
|
keywordsIndex.keywords[keyword].push(contextMeta.id);
|
|
1382
1690
|
}
|
|
1383
1691
|
}
|
|
1692
|
+
// v2.2: Извлекаем решения из контекста
|
|
1693
|
+
const extractedSolutions = extractSolutionsFromText(solutionsContent, contextMeta.id);
|
|
1694
|
+
if (extractedSolutions.length > 0) {
|
|
1695
|
+
const added = addSolutionsToIndex(extractedSolutions, contextMeta.id);
|
|
1696
|
+
result.solutions += added;
|
|
1697
|
+
}
|
|
1384
1698
|
result.migrated++;
|
|
1385
1699
|
}
|
|
1386
1700
|
catch (err) {
|
|
@@ -1774,7 +2088,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
1774
2088
|
},
|
|
1775
2089
|
{
|
|
1776
2090
|
name: "context_save_detailed",
|
|
1777
|
-
description: "📚 ПОДРОБНОЕ сохранение контекста (
|
|
2091
|
+
description: "📚 МАКСИМАЛЬНО ПОДРОБНОЕ сохранение контекста (15 файлов). ОБЯЗАТЕЛЬНО вызывай когда пользователь говорит: 'сохрани контекст подробно', 'подробно сохрани', 'детальное сохранение', 'сохрани всё подробно', '15 файлов'. Создаёт 15 файлов с максимально качественной информацией. Перед вызовом Claude ОБЯЗАН прочитать гайд по заполнению!",
|
|
1778
2092
|
inputSchema: {
|
|
1779
2093
|
type: "object",
|
|
1780
2094
|
properties: {
|
|
@@ -1817,12 +2131,36 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
1817
2131
|
},
|
|
1818
2132
|
problems_solutions: {
|
|
1819
2133
|
type: "string",
|
|
1820
|
-
description: "Для КАЖДОЙ проблемы: симптомы, root cause, решение, как предотвратить. Минимум 2 проблемы!",
|
|
2134
|
+
description: "Для КАЖДОЙ проблемы: симптомы, root cause, решение, как предотвратить. Формат: 'Проблема: X / Решение: Y'. Минимум 2 проблемы!",
|
|
1821
2135
|
},
|
|
1822
2136
|
architecture: {
|
|
1823
2137
|
type: "string",
|
|
1824
2138
|
description: "Структура модулей, потоки данных, интеграции. Можно ASCII-диаграммы.",
|
|
1825
2139
|
},
|
|
2140
|
+
testing: {
|
|
2141
|
+
type: "string",
|
|
2142
|
+
description: "Что тестировали, как тестировали, результаты тестов, покрытие.",
|
|
2143
|
+
},
|
|
2144
|
+
api_docs: {
|
|
2145
|
+
type: "string",
|
|
2146
|
+
description: "API документация: эндпоинты, параметры, примеры запросов/ответов.",
|
|
2147
|
+
},
|
|
2148
|
+
migration: {
|
|
2149
|
+
type: "string",
|
|
2150
|
+
description: "Миграции данных, breaking changes, инструкции по обновлению.",
|
|
2151
|
+
},
|
|
2152
|
+
performance: {
|
|
2153
|
+
type: "string",
|
|
2154
|
+
description: "Оптимизации производительности, метрики до/после, узкие места.",
|
|
2155
|
+
},
|
|
2156
|
+
security: {
|
|
2157
|
+
type: "string",
|
|
2158
|
+
description: "Анализ безопасности, уязвимости, исправления, рекомендации.",
|
|
2159
|
+
},
|
|
2160
|
+
full_context: {
|
|
2161
|
+
type: "string",
|
|
2162
|
+
description: "Полный контекст сессии: всё что не вошло в другие файлы, дополнительные заметки.",
|
|
2163
|
+
},
|
|
1826
2164
|
history_summary: {
|
|
1827
2165
|
type: "string",
|
|
1828
2166
|
description: "⭐ ОБЯЗАТЕЛЬНО: Качественная сводка для HISTORY.md (2-4 предложения). Формат: **Сводка:** суть задачи, что сделано, результат. **Файлы:** список изменённых файлов.",
|
|
@@ -2025,7 +2363,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
2025
2363
|
},
|
|
2026
2364
|
{
|
|
2027
2365
|
name: "rules",
|
|
2028
|
-
description: "🔴 Управление ПРИОРИТЕТНЫМИ ПРАВИЛАМИ. ОБЯЗАТЕЛЬНО вызывай когда пользователь говорит: 'добавь правило', 'правила', 'покажи правила', 'удали правило', 'новое правило'. Правила показываются ПЕРВЫМИ при каждой загрузке контекста и Claude ОБЯЗАН их соблюдать.",
|
|
2366
|
+
description: "🔴 Управление ПРИОРИТЕТНЫМИ ПРАВИЛАМИ. ОБЯЗАТЕЛЬНО вызывай когда пользователь говорит: 'добавь правило', 'правила', 'покажи правила', 'удали правило', 'новое правило', 'глобальное правило'. Правила показываются ПЕРВЫМИ при каждой загрузке контекста и Claude ОБЯЗАН их соблюдать.",
|
|
2029
2367
|
inputSchema: {
|
|
2030
2368
|
type: "object",
|
|
2031
2369
|
properties: {
|
|
@@ -2034,6 +2372,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
2034
2372
|
enum: ["add", "list", "remove"],
|
|
2035
2373
|
description: "Действие: add (добавить), list (показать все), remove (удалить по ID)",
|
|
2036
2374
|
},
|
|
2375
|
+
scope: {
|
|
2376
|
+
type: "string",
|
|
2377
|
+
enum: ["local", "global"],
|
|
2378
|
+
description: "Область: local (только этот проект, по умолчанию), global (все проекты, ~/.dedsession/)",
|
|
2379
|
+
},
|
|
2037
2380
|
rule: {
|
|
2038
2381
|
type: "string",
|
|
2039
2382
|
description: "Текст правила (для action=add)",
|
|
@@ -2050,6 +2393,29 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
2050
2393
|
required: ["action"],
|
|
2051
2394
|
},
|
|
2052
2395
|
},
|
|
2396
|
+
// ========== ТЕСТОВЫЕ КОМАНДЫ SMART CONTEXT ==========
|
|
2397
|
+
{
|
|
2398
|
+
name: "context_smart",
|
|
2399
|
+
description: "🧪 ТЕСТОВАЯ команда. Умная загрузка 25 контекстов с группировкой по людям/темам. ОБЯЗАТЕЛЬНО вызывай когда пользователь говорит: 'умный контекст', 'smart context', 'контекст по людям', 'тестовый контекст'. После обзора опиши задачу для точной подгрузки.",
|
|
2400
|
+
inputSchema: {
|
|
2401
|
+
type: "object",
|
|
2402
|
+
properties: {},
|
|
2403
|
+
},
|
|
2404
|
+
},
|
|
2405
|
+
{
|
|
2406
|
+
name: "context_smart_focus",
|
|
2407
|
+
description: "🧪 ТЕСТОВАЯ команда. Подгружает 2-4 релевантных контекста ПОЛНОСТЬЮ по описанию задачи. Вызывай ПОСЛЕ context_smart или когда пользователь описывает задачу после умного контекста.",
|
|
2408
|
+
inputSchema: {
|
|
2409
|
+
type: "object",
|
|
2410
|
+
properties: {
|
|
2411
|
+
task: {
|
|
2412
|
+
type: "string",
|
|
2413
|
+
description: "Описание задачи в свободном формате: 'доска мурадбека', 'влад скрипты', 'рефакторинг как делали'",
|
|
2414
|
+
},
|
|
2415
|
+
},
|
|
2416
|
+
required: ["task"],
|
|
2417
|
+
},
|
|
2418
|
+
},
|
|
2053
2419
|
],
|
|
2054
2420
|
}));
|
|
2055
2421
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -2203,6 +2569,18 @@ ${args?.changes?.split("\n").map(l => l.trim() ? `- ${l}` : "").join("\n") || "-
|
|
|
2203
2569
|
args?.session_log,
|
|
2204
2570
|
].filter(Boolean).join("\n");
|
|
2205
2571
|
const memoryInfo = addContextToMemory(contextNumber, result.name, taskName, status, fullContent, new Date().toISOString().split("T")[0]);
|
|
2572
|
+
// v2.2: Извлекаем решения при сохранении
|
|
2573
|
+
let solutionsAdded = 0;
|
|
2574
|
+
if (args?.problems_solutions) {
|
|
2575
|
+
const solutionsContent = [
|
|
2576
|
+
args?.problems_solutions,
|
|
2577
|
+
args?.changes,
|
|
2578
|
+
args?.session_log,
|
|
2579
|
+
].filter(Boolean).join("\n");
|
|
2580
|
+
const contextId = String(contextNumber).padStart(3, "0");
|
|
2581
|
+
const extracted = extractSolutionsFromText(solutionsContent, contextId);
|
|
2582
|
+
solutionsAdded = addSolutionsToIndex(extracted, contextId);
|
|
2583
|
+
}
|
|
2206
2584
|
let output = `✅ **Контекст сохранён**\n\n`;
|
|
2207
2585
|
output += `📁 **Папка:** \`${result.name}\`\n`;
|
|
2208
2586
|
output += `📄 **Файлов:** ${result.filesCount}\n`;
|
|
@@ -2214,6 +2592,9 @@ ${args?.changes?.split("\n").map(l => l.trim() ? `- ${l}` : "").join("\n") || "-
|
|
|
2214
2592
|
output += `\n🧠 **Memory v2.0:**\n`;
|
|
2215
2593
|
output += `- Проект: **${memoryInfo.project}**\n`;
|
|
2216
2594
|
output += `- Теги: ${memoryInfo.keywords.slice(0, 5).join(", ") || "-"}\n`;
|
|
2595
|
+
if (solutionsAdded > 0) {
|
|
2596
|
+
output += `- 💡 Решений добавлено: **${solutionsAdded}**\n`;
|
|
2597
|
+
}
|
|
2217
2598
|
}
|
|
2218
2599
|
if (state.loadedContext && state.loadedContext !== result.name) {
|
|
2219
2600
|
output += `\n🔗 **Parent:** ${state.loadedContext}\n`;
|
|
@@ -2260,7 +2641,7 @@ ${args?.changes?.split("\n").map(l => l.trim() ? `- ${l}` : "").join("\n") || "-
|
|
|
2260
2641
|
blocked: "❌ Заблокировано",
|
|
2261
2642
|
};
|
|
2262
2643
|
const status = statusMap[args?.status || "completed"] || "✅ Завершено";
|
|
2263
|
-
// Всегда
|
|
2644
|
+
// Всегда 15 файлов для максимально детального сохранения
|
|
2264
2645
|
const detailedFiles = [
|
|
2265
2646
|
"README.md",
|
|
2266
2647
|
"01-task-overview.md",
|
|
@@ -2272,13 +2653,19 @@ ${args?.changes?.split("\n").map(l => l.trim() ? `- ${l}` : "").join("\n") || "-
|
|
|
2272
2653
|
"07-decisions.md",
|
|
2273
2654
|
"08-problems-solutions.md",
|
|
2274
2655
|
"09-architecture.md",
|
|
2656
|
+
"10-testing.md",
|
|
2657
|
+
"11-api-docs.md",
|
|
2658
|
+
"12-migration.md",
|
|
2659
|
+
"13-performance.md",
|
|
2660
|
+
"14-security.md",
|
|
2661
|
+
"15-full-context.md",
|
|
2275
2662
|
];
|
|
2276
2663
|
// Формируем README
|
|
2277
2664
|
const readme = `# ${taskName}
|
|
2278
2665
|
|
|
2279
2666
|
**Дата:** ${new Date().toISOString().split("T")[0]}
|
|
2280
2667
|
**Статус:** ${status}
|
|
2281
|
-
**Масштаб:** detailed (
|
|
2668
|
+
**Масштаб:** detailed (15 файлов)
|
|
2282
2669
|
|
|
2283
2670
|
## Краткое описание
|
|
2284
2671
|
|
|
@@ -2304,6 +2691,13 @@ ${state.loadedContext ? `**Parent:** ${state.loadedContext}` : "Нет parent к
|
|
|
2304
2691
|
decisions: args?.decisions || "Решения не документированы",
|
|
2305
2692
|
problemsSolutions: args?.problems_solutions || "Проблемы не описаны",
|
|
2306
2693
|
architecture: args?.architecture || "Архитектура не описана",
|
|
2694
|
+
// v2.2: Новые поля для 15 файлов
|
|
2695
|
+
testing: args?.testing || "Тестирование не описано",
|
|
2696
|
+
apiDocs: args?.api_docs || "API не документировано",
|
|
2697
|
+
migration: args?.migration || "Миграции отсутствуют",
|
|
2698
|
+
performance: args?.performance || "Оптимизации не проводились",
|
|
2699
|
+
security: args?.security || "Безопасность не анализировалась",
|
|
2700
|
+
fullContext: args?.full_context || "Полный контекст сессии не записан",
|
|
2307
2701
|
// v1.4.10: Готовая сводка для HISTORY.md
|
|
2308
2702
|
historySummary: args?.history_summary,
|
|
2309
2703
|
}, undefined, detailedFiles);
|
|
@@ -2324,9 +2718,19 @@ ${state.loadedContext ? `**Parent:** ${state.loadedContext}` : "Нет parent к
|
|
|
2324
2718
|
args?.architecture,
|
|
2325
2719
|
].filter(Boolean).join("\n");
|
|
2326
2720
|
const memoryInfo = addContextToMemory(contextNumber, result.name, taskName, status, fullContent, new Date().toISOString().split("T")[0]);
|
|
2721
|
+
// v2.2: Извлекаем решения при сохранении (detailed всегда имеет problems_solutions)
|
|
2722
|
+
let solutionsAdded = 0;
|
|
2723
|
+
const solutionsContent = [
|
|
2724
|
+
args?.problems_solutions,
|
|
2725
|
+
args?.changes,
|
|
2726
|
+
args?.session_log,
|
|
2727
|
+
].filter(Boolean).join("\n");
|
|
2728
|
+
const contextId = String(contextNumber).padStart(3, "0");
|
|
2729
|
+
const extracted = extractSolutionsFromText(solutionsContent, contextId);
|
|
2730
|
+
solutionsAdded = addSolutionsToIndex(extracted, contextId);
|
|
2327
2731
|
let output = `✅ **Контекст сохранён ПОДРОБНО**\n\n`;
|
|
2328
2732
|
output += `📁 **Папка:** \`${result.name}\`\n`;
|
|
2329
|
-
output += `📄 **Файлов:**
|
|
2733
|
+
output += `📄 **Файлов:** 15\n`;
|
|
2330
2734
|
output += `🎯 **Масштаб:** detailed (максимальная детализация)\n`;
|
|
2331
2735
|
output += `📍 **Путь:** \`${result.path}\`\n`;
|
|
2332
2736
|
// v2.0: Показываем Memory info
|
|
@@ -2334,6 +2738,9 @@ ${state.loadedContext ? `**Parent:** ${state.loadedContext}` : "Нет parent к
|
|
|
2334
2738
|
output += `\n🧠 **Memory v2.0:**\n`;
|
|
2335
2739
|
output += `- Проект: **${memoryInfo.project}**\n`;
|
|
2336
2740
|
output += `- Теги: ${memoryInfo.keywords.slice(0, 5).join(", ") || "-"}\n`;
|
|
2741
|
+
if (solutionsAdded > 0) {
|
|
2742
|
+
output += `- 💡 Решений добавлено: **${solutionsAdded}**\n`;
|
|
2743
|
+
}
|
|
2337
2744
|
}
|
|
2338
2745
|
if (state.loadedContext && state.loadedContext !== result.name) {
|
|
2339
2746
|
output += `\n🔗 **Parent:** ${state.loadedContext}\n`;
|
|
@@ -3158,7 +3565,8 @@ MD_HISTORY/
|
|
|
3158
3565
|
output += `📊 Результаты:\n`;
|
|
3159
3566
|
output += `- Проиндексировано контекстов: ${result.migrated}\n`;
|
|
3160
3567
|
output += `- Обнаружено проектов: ${result.projects.length} (${result.projects.join(", ") || "-"})\n`;
|
|
3161
|
-
output += `- Эпиков: ${result.epics.length}\n
|
|
3568
|
+
output += `- Эпиков: ${result.epics.length}\n`;
|
|
3569
|
+
output += `- 💡 Извлечено решений: ${result.solutions}\n\n`;
|
|
3162
3570
|
const stats = getMemoryStats();
|
|
3163
3571
|
output += `## 🌡️ Распределение по температуре\n\n`;
|
|
3164
3572
|
output += `- 🔥 Hot: ${stats.temperatures.hot}\n`;
|
|
@@ -3186,17 +3594,23 @@ MD_HISTORY/
|
|
|
3186
3594
|
// ==================== RULES ====================
|
|
3187
3595
|
case "rules": {
|
|
3188
3596
|
const action = args.action || "list";
|
|
3597
|
+
const scope = args.scope || "local";
|
|
3189
3598
|
const ruleText = args.rule;
|
|
3190
3599
|
const ruleId = args.id;
|
|
3191
3600
|
const ruleContext = args.context;
|
|
3192
|
-
const
|
|
3601
|
+
const isGlobal = scope === "global";
|
|
3602
|
+
const rulesIndex = isGlobal ? loadGlobalRules() : loadRules();
|
|
3603
|
+
const scopeLabel = isGlobal ? "🌍 Глобальное" : "📁 Локальное";
|
|
3604
|
+
const scopePath = isGlobal ? "~/.dedsession/" : "MD_HISTORY/memory/";
|
|
3193
3605
|
switch (action) {
|
|
3194
3606
|
case "add": {
|
|
3195
3607
|
if (!ruleText) {
|
|
3196
3608
|
return {
|
|
3197
3609
|
content: [{
|
|
3198
3610
|
type: "text",
|
|
3199
|
-
text: `⚠️ Укажи текст правила.\n\n
|
|
3611
|
+
text: `⚠️ Укажи текст правила.\n\n**Примеры:**\n` +
|
|
3612
|
+
`- Локальное: \`rules add "Правило для этого проекта"\`\n` +
|
|
3613
|
+
`- Глобальное: \`rules add "Правило для ВСЕХ проектов" scope:global\``
|
|
3200
3614
|
}]
|
|
3201
3615
|
};
|
|
3202
3616
|
}
|
|
@@ -3210,12 +3624,18 @@ MD_HISTORY/
|
|
|
3210
3624
|
context: ruleContext,
|
|
3211
3625
|
};
|
|
3212
3626
|
rulesIndex.rules.push(newRule);
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3627
|
+
if (isGlobal) {
|
|
3628
|
+
saveGlobalRules(rulesIndex);
|
|
3629
|
+
}
|
|
3630
|
+
else {
|
|
3631
|
+
saveRules(rulesIndex);
|
|
3632
|
+
}
|
|
3633
|
+
let output = `# ✅ ${scopeLabel} правило добавлено\n\n`;
|
|
3634
|
+
output += `**ID:** ${isGlobal ? "G" : "L"}${newRule.id}\n`;
|
|
3216
3635
|
output += `**Правило:** ${newRule.rule}\n`;
|
|
3217
3636
|
if (newRule.context)
|
|
3218
3637
|
output += `**Контекст:** ${newRule.context}\n`;
|
|
3638
|
+
output += `**Хранится:** \`${scopePath}\`\n`;
|
|
3219
3639
|
output += `\nТеперь это правило будет показываться при каждой загрузке контекста.\n`;
|
|
3220
3640
|
output += `\n---\n\n`;
|
|
3221
3641
|
output += formatRulesBlock();
|
|
@@ -3226,7 +3646,9 @@ MD_HISTORY/
|
|
|
3226
3646
|
return {
|
|
3227
3647
|
content: [{
|
|
3228
3648
|
type: "text",
|
|
3229
|
-
text: `⚠️ Укажи ID правила для удаления.\n\n
|
|
3649
|
+
text: `⚠️ Укажи ID правила для удаления.\n\n**Примеры:**\n` +
|
|
3650
|
+
`- Локальное: \`rules remove 1\`\n` +
|
|
3651
|
+
`- Глобальное: \`rules remove 1 scope:global\``
|
|
3230
3652
|
}]
|
|
3231
3653
|
};
|
|
3232
3654
|
}
|
|
@@ -3235,36 +3657,221 @@ MD_HISTORY/
|
|
|
3235
3657
|
return {
|
|
3236
3658
|
content: [{
|
|
3237
3659
|
type: "text",
|
|
3238
|
-
text: `⚠️
|
|
3660
|
+
text: `⚠️ ${scopeLabel} правило с ID ${ruleId} не найдено.`
|
|
3239
3661
|
}]
|
|
3240
3662
|
};
|
|
3241
3663
|
}
|
|
3242
3664
|
const removed = rulesIndex.rules.splice(ruleIndex, 1)[0];
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
output += `**ID:** ${removed.id}\n`;
|
|
3246
|
-
output += `**Было:** ${removed.rule}\n`;
|
|
3247
|
-
output += `\n---\n\n`;
|
|
3248
|
-
if (rulesIndex.rules.length > 0) {
|
|
3249
|
-
output += formatRulesBlock();
|
|
3665
|
+
if (isGlobal) {
|
|
3666
|
+
saveGlobalRules(rulesIndex);
|
|
3250
3667
|
}
|
|
3251
3668
|
else {
|
|
3669
|
+
saveRules(rulesIndex);
|
|
3670
|
+
}
|
|
3671
|
+
let output = `# ✅ ${scopeLabel} правило удалено\n\n`;
|
|
3672
|
+
output += `**ID:** ${isGlobal ? "G" : "L"}${removed.id}\n`;
|
|
3673
|
+
output += `**Было:** ${removed.rule}\n`;
|
|
3674
|
+
output += `\n---\n\n`;
|
|
3675
|
+
output += formatRulesBlock();
|
|
3676
|
+
if (loadGlobalRules().rules.length === 0 && loadRules().rules.length === 0) {
|
|
3252
3677
|
output += `_Нет активных правил._\n`;
|
|
3253
3678
|
}
|
|
3254
3679
|
return { content: [{ type: "text", text: output }] };
|
|
3255
3680
|
}
|
|
3256
3681
|
case "list":
|
|
3257
3682
|
default: {
|
|
3258
|
-
|
|
3683
|
+
const globalRules = loadGlobalRules();
|
|
3684
|
+
const localRules = loadRules();
|
|
3685
|
+
if (globalRules.rules.length === 0 && localRules.rules.length === 0) {
|
|
3259
3686
|
let output = `# 🔴 Приоритетные правила\n\n`;
|
|
3260
3687
|
output += `_Нет активных правил._\n\n`;
|
|
3261
3688
|
output += `**Добавить правило:**\n`;
|
|
3262
|
-
output +=
|
|
3689
|
+
output += `- Локальное (этот проект): \`rules add "Текст правила"\`\n`;
|
|
3690
|
+
output += `- Глобальное (все проекты): \`rules add "Текст правила" scope:global\`\n`;
|
|
3263
3691
|
return { content: [{ type: "text", text: output }] };
|
|
3264
3692
|
}
|
|
3265
|
-
|
|
3693
|
+
let output = formatRulesBlock();
|
|
3694
|
+
output += `\n**Управление:**\n`;
|
|
3695
|
+
output += `- Добавить локальное: \`rules add "..."\`\n`;
|
|
3696
|
+
output += `- Добавить глобальное: \`rules add "..." scope:global\`\n`;
|
|
3697
|
+
output += `- Удалить: \`rules remove ID [scope:global]\`\n`;
|
|
3698
|
+
return { content: [{ type: "text", text: output }] };
|
|
3699
|
+
}
|
|
3700
|
+
}
|
|
3701
|
+
}
|
|
3702
|
+
// ========== ТЕСТОВЫЕ HANDLERS SMART CONTEXT ==========
|
|
3703
|
+
case "context_smart": {
|
|
3704
|
+
const history = readHistory();
|
|
3705
|
+
if (!history) {
|
|
3706
|
+
return {
|
|
3707
|
+
content: [{
|
|
3708
|
+
type: "text",
|
|
3709
|
+
text: `❌ **HISTORY.md не найден**\n\nСначала создайте контексты или запустите \`context_migrate\`.`,
|
|
3710
|
+
}],
|
|
3711
|
+
};
|
|
3712
|
+
}
|
|
3713
|
+
const entries = parseHistoryEntries(history, 25);
|
|
3714
|
+
const memoryIndex = loadMemoryIndex();
|
|
3715
|
+
// Группируем по людям
|
|
3716
|
+
const byPerson = {};
|
|
3717
|
+
for (const entry of entries) {
|
|
3718
|
+
const person = entry.person || "other";
|
|
3719
|
+
if (!byPerson[person])
|
|
3720
|
+
byPerson[person] = [];
|
|
3721
|
+
byPerson[person].push(entry);
|
|
3722
|
+
}
|
|
3723
|
+
// Группируем по проектам из Memory v2.0
|
|
3724
|
+
const byProject = {};
|
|
3725
|
+
for (const entry of entries) {
|
|
3726
|
+
const meta = memoryIndex.contexts[entry.number];
|
|
3727
|
+
const project = meta?.project || "unknown";
|
|
3728
|
+
if (!byProject[project])
|
|
3729
|
+
byProject[project] = [];
|
|
3730
|
+
byProject[project].push(entry);
|
|
3731
|
+
}
|
|
3732
|
+
// Формируем компактный вывод
|
|
3733
|
+
let output = `# 🧠 Smart Context — Обзор ${entries.length} контекстов\n\n`;
|
|
3734
|
+
// По людям
|
|
3735
|
+
output += `## 👥 По людям:\n`;
|
|
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 данные)
|
|
3747
|
+
const projectsWithData = Object.entries(byProject).filter(([p]) => p !== "unknown");
|
|
3748
|
+
if (projectsWithData.length > 0) {
|
|
3749
|
+
output += `\n## 📁 По проектам:\n`;
|
|
3750
|
+
for (const [project, list] of projectsWithData) {
|
|
3751
|
+
output += `- **${project}** (${list.length})\n`;
|
|
3752
|
+
}
|
|
3753
|
+
}
|
|
3754
|
+
// Последние 10 с краткими сводками
|
|
3755
|
+
output += `\n## 📋 Последние 10:\n`;
|
|
3756
|
+
output += `| # | Название | Человек | Сводка |\n|---|----------|---------|--------|\n`;
|
|
3757
|
+
for (const entry of entries.slice(0, 10)) {
|
|
3758
|
+
const shortSummary = entry.summary.slice(0, 50).replace(/\n/g, " ") + "...";
|
|
3759
|
+
output += `| ${entry.number} | ${entry.name} | ${entry.person || "-"} | ${shortSummary} |\n`;
|
|
3760
|
+
}
|
|
3761
|
+
// Генерируем динамические примеры на основе реальных данных
|
|
3762
|
+
output += `\n---\n`;
|
|
3763
|
+
output += `💡 **Опиши задачу** в свободном формате, и я подгружу релевантные контексты:\n`;
|
|
3764
|
+
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
|
+
// Пример с проектом (если есть)
|
|
3775
|
+
if (projectsWithData.length > 0) {
|
|
3776
|
+
const [project, list] = projectsWithData[0];
|
|
3777
|
+
const entry = list[0];
|
|
3778
|
+
const keyword = entry.name.split("-").find(w => w.length > 3) || "";
|
|
3779
|
+
examples.push(`"${keyword} ${project}"`);
|
|
3780
|
+
}
|
|
3781
|
+
// Пример из последнего контекста
|
|
3782
|
+
if (entries.length > 0) {
|
|
3783
|
+
const lastEntry = entries[0];
|
|
3784
|
+
const words = lastEntry.name.split("-").filter(w => w.length > 3).slice(0, 2);
|
|
3785
|
+
if (words.length > 0) {
|
|
3786
|
+
examples.push(`"${words.join(" ")}"`);
|
|
3787
|
+
}
|
|
3788
|
+
}
|
|
3789
|
+
// Выводим примеры (максимум 3)
|
|
3790
|
+
for (const example of examples.slice(0, 3)) {
|
|
3791
|
+
output += `- ${example}\n`;
|
|
3792
|
+
}
|
|
3793
|
+
output += `\nClaude автоматически найдёт и загрузит 2-4 релевантных контекста.`;
|
|
3794
|
+
return { content: [{ type: "text", text: output }] };
|
|
3795
|
+
}
|
|
3796
|
+
case "context_smart_focus": {
|
|
3797
|
+
const task = args.task;
|
|
3798
|
+
if (!task) {
|
|
3799
|
+
return {
|
|
3800
|
+
content: [{
|
|
3801
|
+
type: "text",
|
|
3802
|
+
text: `❌ **Укажи описание задачи**\n\nПример: "доска мурадбека", "влад скрипты", "рефакторинг"`,
|
|
3803
|
+
}],
|
|
3804
|
+
};
|
|
3805
|
+
}
|
|
3806
|
+
const history = readHistory();
|
|
3807
|
+
if (!history) {
|
|
3808
|
+
return {
|
|
3809
|
+
content: [{
|
|
3810
|
+
type: "text",
|
|
3811
|
+
text: `❌ **HISTORY.md не найден**`,
|
|
3812
|
+
}],
|
|
3813
|
+
};
|
|
3814
|
+
}
|
|
3815
|
+
// Парсим запрос
|
|
3816
|
+
const queryPerson = detectPerson(task);
|
|
3817
|
+
const queryKeywords = task.toLowerCase()
|
|
3818
|
+
.split(/\s+/)
|
|
3819
|
+
.filter(w => w.length > 2 && !["над", "для", "как", "что", "это", "мы", "работаем"].includes(w));
|
|
3820
|
+
// Загружаем данные
|
|
3821
|
+
const entries = parseHistoryEntries(history, 25);
|
|
3822
|
+
const memoryIndex = loadMemoryIndex();
|
|
3823
|
+
// Скорим контексты
|
|
3824
|
+
const scored = entries.map(entry => ({
|
|
3825
|
+
entry,
|
|
3826
|
+
score: scoreContextRelevance(entry, queryPerson, queryKeywords, memoryIndex),
|
|
3827
|
+
}));
|
|
3828
|
+
// Топ 2-4 по score (минимум score > 0)
|
|
3829
|
+
const top = scored
|
|
3830
|
+
.filter(s => s.score > 0)
|
|
3831
|
+
.sort((a, b) => b.score - a.score)
|
|
3832
|
+
.slice(0, 4);
|
|
3833
|
+
if (top.length === 0) {
|
|
3834
|
+
let output = `❌ **Не найдено релевантных контекстов для:** "${task}"\n\n`;
|
|
3835
|
+
output += `**Искали:**\n`;
|
|
3836
|
+
if (queryPerson)
|
|
3837
|
+
output += `- Человек: ${queryPerson}\n`;
|
|
3838
|
+
output += `- Ключевые слова: ${queryKeywords.join(", ")}\n\n`;
|
|
3839
|
+
output += `**Попробуй:**\n`;
|
|
3840
|
+
output += `- Уточнить имя человека (muradbek, vlad, makhach, akhmad)\n`;
|
|
3841
|
+
output += `- Использовать ключевые слова из названий контекстов\n`;
|
|
3842
|
+
return { content: [{ type: "text", text: output }] };
|
|
3843
|
+
}
|
|
3844
|
+
// Загружаем контексты полностью
|
|
3845
|
+
let output = `# 🎯 Фокус: "${task}"\n\n`;
|
|
3846
|
+
output += `**Найдено:** ${top.length} релевантных контекстов\n`;
|
|
3847
|
+
if (queryPerson)
|
|
3848
|
+
output += `**Человек:** ${queryPerson}\n`;
|
|
3849
|
+
output += `**Ключевые слова:** ${queryKeywords.join(", ")}\n\n`;
|
|
3850
|
+
output += `---\n\n`;
|
|
3851
|
+
for (const { entry, score } of top) {
|
|
3852
|
+
output += `## #${entry.number}: ${entry.name} (score: ${score})\n\n`;
|
|
3853
|
+
// Загружаем полный контекст
|
|
3854
|
+
const contextPath = findContextByNumber(entry.number);
|
|
3855
|
+
if (contextPath) {
|
|
3856
|
+
// README.md
|
|
3857
|
+
const readmePath = path.join(contextPath, "README.md");
|
|
3858
|
+
if (fs.existsSync(readmePath)) {
|
|
3859
|
+
const readme = fs.readFileSync(readmePath, "utf-8");
|
|
3860
|
+
output += readme + "\n\n";
|
|
3861
|
+
}
|
|
3862
|
+
// 03-changes.md (если есть)
|
|
3863
|
+
const changesPath = path.join(contextPath, "03-changes.md");
|
|
3864
|
+
if (fs.existsSync(changesPath)) {
|
|
3865
|
+
const changes = fs.readFileSync(changesPath, "utf-8");
|
|
3866
|
+
output += `### 🔧 Изменения:\n${changes}\n\n`;
|
|
3867
|
+
}
|
|
3868
|
+
// Обновляем accessCount в Memory v2.0
|
|
3869
|
+
updateContextAccess(entry.number);
|
|
3266
3870
|
}
|
|
3871
|
+
output += `---\n\n`;
|
|
3267
3872
|
}
|
|
3873
|
+
output += `💡 **Контексты загружены.** Теперь можешь работать над задачей!`;
|
|
3874
|
+
return { content: [{ type: "text", text: output }] };
|
|
3268
3875
|
}
|
|
3269
3876
|
default:
|
|
3270
3877
|
return { content: [{ type: "text", text: `❌ Неизвестный tool: ${name}` }] };
|