@hir4ta/mneme 0.22.0 → 0.22.3
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/.claude-plugin/plugin.json +1 -1
- package/README.ja.md +7 -1
- package/README.md +1 -1
- package/dist/lib/incremental-save.js +2 -2
- package/dist/lib/prompt-search.js +1 -64
- package/dist/lib/search-core.js +1 -64
- package/dist/lib/session-finalize.js +21 -10
- package/dist/public/assets/index-DZyzcWMg.js +351 -0
- package/dist/public/assets/index-g8Lvi94K.css +1 -0
- package/dist/public/assets/{react-force-graph-2d-Dlcfvz01.js → react-force-graph-2d-CAP2m3Y0.js} +1 -1
- package/dist/public/index.html +2 -2
- package/dist/server.js +190 -243
- package/dist/servers/db-server.js +140 -436
- package/dist/servers/search-server.js +3 -95
- package/package.json +1 -1
- package/scripts/export-weekly-knowledge-html.ts +15 -14
- package/servers/db-server.ts +180 -515
- package/servers/search-server.ts +3 -46
- package/skills/save/SKILL.md +20 -21
- package/dist/public/assets/index-Bvl_IrPy.css +0 -1
- package/dist/public/assets/index-k5JYSPV6.js +0 -351
|
@@ -30355,66 +30355,6 @@ function searchSessions(mnemeDir, keywords, limit = 5) {
|
|
|
30355
30355
|
});
|
|
30356
30356
|
return results.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
30357
30357
|
}
|
|
30358
|
-
function searchUnits(mnemeDir, keywords, limit = 5) {
|
|
30359
|
-
const unitsPath = path3.join(mnemeDir, "units", "units.json");
|
|
30360
|
-
const results = [];
|
|
30361
|
-
const pattern = new RegExp(keywords.map(escapeRegex2).join("|"), "i");
|
|
30362
|
-
if (!fs3.existsSync(unitsPath)) return results;
|
|
30363
|
-
try {
|
|
30364
|
-
const cards = JSON.parse(fs3.readFileSync(unitsPath, "utf-8"));
|
|
30365
|
-
const items = (cards.items || []).filter(
|
|
30366
|
-
(item) => item.status === "approved"
|
|
30367
|
-
);
|
|
30368
|
-
for (const item of items) {
|
|
30369
|
-
let score = 0;
|
|
30370
|
-
const matchedFields = [];
|
|
30371
|
-
const titleScore = fieldScore(item.title, pattern, 3);
|
|
30372
|
-
if (titleScore > 0) {
|
|
30373
|
-
score += titleScore;
|
|
30374
|
-
matchedFields.push("title");
|
|
30375
|
-
}
|
|
30376
|
-
const summaryScore = fieldScore(item.summary, pattern, 2);
|
|
30377
|
-
if (summaryScore > 0) {
|
|
30378
|
-
score += summaryScore;
|
|
30379
|
-
matchedFields.push("summary");
|
|
30380
|
-
}
|
|
30381
|
-
if (item.tags?.some((tag) => pattern.test(tag))) {
|
|
30382
|
-
score += 1;
|
|
30383
|
-
matchedFields.push("tags");
|
|
30384
|
-
}
|
|
30385
|
-
if (item.sourceType && pattern.test(item.sourceType)) {
|
|
30386
|
-
score += 1;
|
|
30387
|
-
matchedFields.push("sourceType");
|
|
30388
|
-
}
|
|
30389
|
-
if (score === 0 && keywords.length <= 2) {
|
|
30390
|
-
const titleWords = (item.title || "").toLowerCase().split(/\s+/);
|
|
30391
|
-
const tagWords = item.tags || [];
|
|
30392
|
-
for (const keyword of keywords) {
|
|
30393
|
-
if (titleWords.some((w) => isFuzzyMatch(keyword, w))) {
|
|
30394
|
-
score += 1;
|
|
30395
|
-
matchedFields.push("title~fuzzy");
|
|
30396
|
-
}
|
|
30397
|
-
if (tagWords.some((t) => isFuzzyMatch(keyword, t))) {
|
|
30398
|
-
score += 0.5;
|
|
30399
|
-
matchedFields.push("tags~fuzzy");
|
|
30400
|
-
}
|
|
30401
|
-
}
|
|
30402
|
-
}
|
|
30403
|
-
if (score > 0) {
|
|
30404
|
-
results.push({
|
|
30405
|
-
type: "unit",
|
|
30406
|
-
id: item.id,
|
|
30407
|
-
title: item.title || item.id,
|
|
30408
|
-
snippet: item.summary || "",
|
|
30409
|
-
score,
|
|
30410
|
-
matchedFields
|
|
30411
|
-
});
|
|
30412
|
-
}
|
|
30413
|
-
}
|
|
30414
|
-
} catch {
|
|
30415
|
-
}
|
|
30416
|
-
return results.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
30417
|
-
}
|
|
30418
30358
|
function normalizeRequestedTypes(types) {
|
|
30419
30359
|
const normalized = /* @__PURE__ */ new Set();
|
|
30420
30360
|
for (const type of types) {
|
|
@@ -30428,7 +30368,7 @@ function searchKnowledge(options) {
|
|
|
30428
30368
|
mnemeDir,
|
|
30429
30369
|
projectPath,
|
|
30430
30370
|
database = null,
|
|
30431
|
-
types = ["session", "
|
|
30371
|
+
types = ["session", "interaction"],
|
|
30432
30372
|
limit = 10,
|
|
30433
30373
|
offset = 0
|
|
30434
30374
|
} = options;
|
|
@@ -30445,9 +30385,6 @@ function searchKnowledge(options) {
|
|
|
30445
30385
|
if (normalizedTypes.has("session")) {
|
|
30446
30386
|
results.push(...searchSessions(mnemeDir, expandedKeywords, fetchLimit));
|
|
30447
30387
|
}
|
|
30448
|
-
if (normalizedTypes.has("unit")) {
|
|
30449
|
-
results.push(...searchUnits(mnemeDir, expandedKeywords, fetchLimit));
|
|
30450
|
-
}
|
|
30451
30388
|
if (normalizedTypes.has("interaction")) {
|
|
30452
30389
|
results.push(
|
|
30453
30390
|
...searchInteractions(
|
|
@@ -30473,7 +30410,6 @@ var LIST_LIMIT_MIN = 1;
|
|
|
30473
30410
|
var LIST_LIMIT_MAX = 200;
|
|
30474
30411
|
var INTERACTION_OFFSET_MIN = 0;
|
|
30475
30412
|
var QUERY_MAX_LENGTH = 500;
|
|
30476
|
-
var UNIT_LIMIT_MAX = 500;
|
|
30477
30413
|
var SEARCH_EVAL_DEFAULT_LIMIT = 5;
|
|
30478
30414
|
function ok(text) {
|
|
30479
30415
|
return { content: [{ type: "text", text }] };
|
|
@@ -30512,56 +30448,12 @@ function listJsonFiles(dir) {
|
|
|
30512
30448
|
return entry.isFile() && entry.name.endsWith(".json") ? [fullPath] : [];
|
|
30513
30449
|
});
|
|
30514
30450
|
}
|
|
30515
|
-
function readUnits() {
|
|
30516
|
-
const unitsPath = path4.join(getMnemeDir(), "units", "units.json");
|
|
30517
|
-
const parsed = readJsonFile(unitsPath);
|
|
30518
|
-
if (!parsed || !Array.isArray(parsed.items)) {
|
|
30519
|
-
return {
|
|
30520
|
-
schemaVersion: 1,
|
|
30521
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
30522
|
-
items: []
|
|
30523
|
-
};
|
|
30524
|
-
}
|
|
30525
|
-
return parsed;
|
|
30526
|
-
}
|
|
30527
|
-
function writeUnits(doc) {
|
|
30528
|
-
const unitsPath = path4.join(getMnemeDir(), "units", "units.json");
|
|
30529
|
-
fs4.mkdirSync(path4.dirname(unitsPath), { recursive: true });
|
|
30530
|
-
fs4.writeFileSync(unitsPath, JSON.stringify(doc, null, 2));
|
|
30531
|
-
}
|
|
30532
30451
|
function readRuleItems(ruleType) {
|
|
30533
30452
|
const filePath = path4.join(getMnemeDir(), "rules", `${ruleType}.json`);
|
|
30534
30453
|
const parsed = readJsonFile(filePath);
|
|
30535
30454
|
const items = parsed?.items ?? parsed?.rules;
|
|
30536
30455
|
return Array.isArray(items) ? items : [];
|
|
30537
30456
|
}
|
|
30538
|
-
function readAuditEntries(options = {}) {
|
|
30539
|
-
const auditDir = path4.join(getMnemeDir(), "audit");
|
|
30540
|
-
if (!fs4.existsSync(auditDir)) return [];
|
|
30541
|
-
const files = fs4.readdirSync(auditDir).filter((name) => name.endsWith(".jsonl")).sort();
|
|
30542
|
-
const fromTime = options.from ? new Date(options.from).getTime() : null;
|
|
30543
|
-
const toTime = options.to ? new Date(options.to).getTime() : null;
|
|
30544
|
-
const entries = [];
|
|
30545
|
-
for (const name of files) {
|
|
30546
|
-
const fullPath = path4.join(auditDir, name);
|
|
30547
|
-
const lines = fs4.readFileSync(fullPath, "utf-8").split("\n");
|
|
30548
|
-
for (const line of lines) {
|
|
30549
|
-
if (!line.trim()) continue;
|
|
30550
|
-
try {
|
|
30551
|
-
const parsed = JSON.parse(line);
|
|
30552
|
-
const ts = new Date(parsed.timestamp).getTime();
|
|
30553
|
-
if (fromTime !== null && ts < fromTime) continue;
|
|
30554
|
-
if (toTime !== null && ts > toTime) continue;
|
|
30555
|
-
if (options.entity && parsed.entity !== options.entity) continue;
|
|
30556
|
-
entries.push(parsed);
|
|
30557
|
-
} catch {
|
|
30558
|
-
}
|
|
30559
|
-
}
|
|
30560
|
-
}
|
|
30561
|
-
return entries.sort(
|
|
30562
|
-
(a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
|
|
30563
|
-
);
|
|
30564
|
-
}
|
|
30565
30457
|
function readSessionsById() {
|
|
30566
30458
|
const sessionsDir = path4.join(getMnemeDir(), "sessions");
|
|
30567
30459
|
const map2 = /* @__PURE__ */ new Map();
|
|
@@ -30573,78 +30465,6 @@ function readSessionsById() {
|
|
|
30573
30465
|
}
|
|
30574
30466
|
return map2;
|
|
30575
30467
|
}
|
|
30576
|
-
function inferUnitPriority(unit) {
|
|
30577
|
-
if (unit.sourceType === "rule") {
|
|
30578
|
-
const [ruleFile, ruleId] = unit.sourceId.split(":", 2);
|
|
30579
|
-
if ((ruleFile === "dev-rules" || ruleFile === "review-guidelines") && ruleId) {
|
|
30580
|
-
const rule = readRuleItems(ruleFile).find((item) => item.id === ruleId);
|
|
30581
|
-
const priority = typeof rule?.priority === "string" ? rule.priority.toLowerCase() : "";
|
|
30582
|
-
if (priority === "p0" || priority === "p1" || priority === "p2") {
|
|
30583
|
-
return priority;
|
|
30584
|
-
}
|
|
30585
|
-
}
|
|
30586
|
-
}
|
|
30587
|
-
const text = `${unit.title} ${unit.summary} ${unit.tags.join(" ")}`.toLowerCase();
|
|
30588
|
-
if (/(security|auth|token|secret|password|injection|xss|csrf|compliance|outage|data[- ]?loss)/.test(
|
|
30589
|
-
text
|
|
30590
|
-
)) {
|
|
30591
|
-
return "p0";
|
|
30592
|
-
}
|
|
30593
|
-
if (/(crash|error|correct|reliab|timeout|retry|integrity)/.test(text)) {
|
|
30594
|
-
return "p1";
|
|
30595
|
-
}
|
|
30596
|
-
return "p2";
|
|
30597
|
-
}
|
|
30598
|
-
function extractChangedFilesFromDiff(diffText) {
|
|
30599
|
-
const files = /* @__PURE__ */ new Set();
|
|
30600
|
-
const lines = diffText.split("\n");
|
|
30601
|
-
for (const line of lines) {
|
|
30602
|
-
if (!line.startsWith("diff --git ")) continue;
|
|
30603
|
-
const parts = line.split(" ");
|
|
30604
|
-
if (parts.length >= 4) {
|
|
30605
|
-
const bPath = parts[3].replace(/^b\//, "");
|
|
30606
|
-
if (bPath) files.add(bPath);
|
|
30607
|
-
}
|
|
30608
|
-
}
|
|
30609
|
-
return Array.from(files);
|
|
30610
|
-
}
|
|
30611
|
-
function scoreUnitAgainstDiff(unit, diffText, changedFiles) {
|
|
30612
|
-
const reasons = [];
|
|
30613
|
-
let score = 0;
|
|
30614
|
-
const corpus = `${unit.title} ${unit.summary}`.toLowerCase();
|
|
30615
|
-
const diffLower = diffText.toLowerCase();
|
|
30616
|
-
for (const tag of unit.tags) {
|
|
30617
|
-
if (!tag) continue;
|
|
30618
|
-
const tagLower = tag.toLowerCase();
|
|
30619
|
-
if (diffLower.includes(tagLower)) {
|
|
30620
|
-
score += 3;
|
|
30621
|
-
reasons.push(`tag:${tag}`);
|
|
30622
|
-
}
|
|
30623
|
-
}
|
|
30624
|
-
const keywords = corpus.split(/[^a-zA-Z0-9_-]+/).filter((token) => token.length >= 5).slice(0, 20);
|
|
30625
|
-
for (const token of keywords) {
|
|
30626
|
-
if (diffLower.includes(token)) {
|
|
30627
|
-
score += 1;
|
|
30628
|
-
reasons.push(`keyword:${token}`);
|
|
30629
|
-
}
|
|
30630
|
-
}
|
|
30631
|
-
for (const filePath of changedFiles) {
|
|
30632
|
-
const lower = filePath.toLowerCase();
|
|
30633
|
-
if (corpus.includes("test") && lower.includes("test")) {
|
|
30634
|
-
score += 1;
|
|
30635
|
-
reasons.push("path:test");
|
|
30636
|
-
}
|
|
30637
|
-
if ((corpus.includes("api") || unit.tags.includes("api")) && (lower.includes("api") || lower.includes("route"))) {
|
|
30638
|
-
score += 1;
|
|
30639
|
-
reasons.push("path:api");
|
|
30640
|
-
}
|
|
30641
|
-
if ((corpus.includes("db") || corpus.includes("sql")) && (lower.includes("db") || lower.includes("prisma") || lower.includes("migration"))) {
|
|
30642
|
-
score += 1;
|
|
30643
|
-
reasons.push("path:db");
|
|
30644
|
-
}
|
|
30645
|
-
}
|
|
30646
|
-
return { score, reasons: Array.from(new Set(reasons)) };
|
|
30647
|
-
}
|
|
30648
30468
|
var db = null;
|
|
30649
30469
|
function getDb() {
|
|
30650
30470
|
if (db) return db;
|
|
@@ -31069,6 +30889,14 @@ async function saveInteractions(claudeSessionId, mnemeSessionId) {
|
|
|
31069
30889
|
...interaction,
|
|
31070
30890
|
id: `int-${String(idx + 1).padStart(3, "0")}`
|
|
31071
30891
|
}));
|
|
30892
|
+
if (finalInteractions.length === 0) {
|
|
30893
|
+
return {
|
|
30894
|
+
success: true,
|
|
30895
|
+
savedCount: 0,
|
|
30896
|
+
mergedFromBackup: backupInteractions.length,
|
|
30897
|
+
message: "No interactions to save (transcript may have no text user messages). Existing data preserved."
|
|
30898
|
+
};
|
|
30899
|
+
}
|
|
31072
30900
|
try {
|
|
31073
30901
|
const deleteStmt = database.prepare(
|
|
31074
30902
|
"DELETE FROM interactions WHERE session_id = ?"
|
|
@@ -31235,25 +31063,6 @@ function runSearchBenchmark(limit = SEARCH_EVAL_DEFAULT_LIMIT) {
|
|
|
31235
31063
|
details
|
|
31236
31064
|
};
|
|
31237
31065
|
}
|
|
31238
|
-
function buildUnitGraph(units) {
|
|
31239
|
-
const approved = units.filter((unit) => unit.status === "approved");
|
|
31240
|
-
const edges = [];
|
|
31241
|
-
for (let i = 0; i < approved.length; i++) {
|
|
31242
|
-
for (let j = i + 1; j < approved.length; j++) {
|
|
31243
|
-
const shared = approved[i].tags.filter(
|
|
31244
|
-
(tag) => approved[j].tags.includes(tag)
|
|
31245
|
-
);
|
|
31246
|
-
if (shared.length > 0) {
|
|
31247
|
-
edges.push({
|
|
31248
|
-
source: approved[i].id,
|
|
31249
|
-
target: approved[j].id,
|
|
31250
|
-
weight: shared.length
|
|
31251
|
-
});
|
|
31252
|
-
}
|
|
31253
|
-
}
|
|
31254
|
-
}
|
|
31255
|
-
return { nodes: approved, edges };
|
|
31256
|
-
}
|
|
31257
31066
|
var server = new McpServer({
|
|
31258
31067
|
name: "mneme-db",
|
|
31259
31068
|
version: "0.1.0"
|
|
@@ -31378,120 +31187,139 @@ server.registerTool(
|
|
|
31378
31187
|
}
|
|
31379
31188
|
);
|
|
31380
31189
|
server.registerTool(
|
|
31381
|
-
"
|
|
31190
|
+
"mneme_update_session_summary",
|
|
31382
31191
|
{
|
|
31383
|
-
description: "
|
|
31192
|
+
description: "Update session JSON file with summary data. MUST be called during /mneme:save Phase 3 to persist session metadata. Creates the session file if it does not exist (e.g. when SessionStart hook was skipped).",
|
|
31384
31193
|
inputSchema: {
|
|
31385
|
-
claudeSessionId: external_exports3.string().min(8).describe("Full Claude Code session UUID (36 chars)")
|
|
31194
|
+
claudeSessionId: external_exports3.string().min(8).describe("Full Claude Code session UUID (36 chars)"),
|
|
31195
|
+
title: external_exports3.string().describe("Session title"),
|
|
31196
|
+
summary: external_exports3.object({
|
|
31197
|
+
goal: external_exports3.string().describe("What the session aimed to accomplish"),
|
|
31198
|
+
outcome: external_exports3.string().describe("What was actually accomplished"),
|
|
31199
|
+
description: external_exports3.string().optional().describe("Detailed description of the session")
|
|
31200
|
+
}).describe("Session summary object"),
|
|
31201
|
+
tags: external_exports3.array(external_exports3.string()).optional().describe("Semantic tags for the session"),
|
|
31202
|
+
sessionType: external_exports3.string().optional().describe(
|
|
31203
|
+
"Session type (e.g. implementation, research, bugfix, refactor)"
|
|
31204
|
+
)
|
|
31386
31205
|
}
|
|
31387
31206
|
},
|
|
31388
|
-
async ({ claudeSessionId }) => {
|
|
31207
|
+
async ({ claudeSessionId, title, summary, tags, sessionType }) => {
|
|
31389
31208
|
if (!claudeSessionId.trim()) {
|
|
31390
31209
|
return fail("claudeSessionId must not be empty.");
|
|
31391
31210
|
}
|
|
31392
|
-
const
|
|
31393
|
-
|
|
31394
|
-
|
|
31395
|
-
|
|
31211
|
+
const projectPath = getProjectPath();
|
|
31212
|
+
const sessionsDir = path4.join(projectPath, ".mneme", "sessions");
|
|
31213
|
+
const shortId = claudeSessionId.slice(0, 8);
|
|
31214
|
+
let sessionFile = null;
|
|
31215
|
+
const searchDir = (dir) => {
|
|
31216
|
+
if (!fs4.existsSync(dir)) return null;
|
|
31217
|
+
for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
|
|
31218
|
+
const fullPath = path4.join(dir, entry.name);
|
|
31219
|
+
if (entry.isDirectory()) {
|
|
31220
|
+
const result = searchDir(fullPath);
|
|
31221
|
+
if (result) return result;
|
|
31222
|
+
} else if (entry.name === `${shortId}.json`) {
|
|
31223
|
+
return fullPath;
|
|
31224
|
+
}
|
|
31225
|
+
}
|
|
31226
|
+
return null;
|
|
31396
31227
|
};
|
|
31397
|
-
|
|
31398
|
-
)
|
|
31399
|
-
|
|
31400
|
-
|
|
31401
|
-
|
|
31402
|
-
|
|
31403
|
-
|
|
31404
|
-
|
|
31405
|
-
|
|
31406
|
-
|
|
31407
|
-
|
|
31408
|
-
|
|
31409
|
-
|
|
31410
|
-
|
|
31411
|
-
|
|
31412
|
-
|
|
31413
|
-
|
|
31414
|
-
|
|
31415
|
-
{
|
|
31416
|
-
|
|
31417
|
-
|
|
31228
|
+
sessionFile = searchDir(sessionsDir);
|
|
31229
|
+
if (!sessionFile) {
|
|
31230
|
+
const now = /* @__PURE__ */ new Date();
|
|
31231
|
+
const yearMonth = path4.join(
|
|
31232
|
+
sessionsDir,
|
|
31233
|
+
String(now.getFullYear()),
|
|
31234
|
+
String(now.getMonth() + 1).padStart(2, "0")
|
|
31235
|
+
);
|
|
31236
|
+
if (!fs4.existsSync(yearMonth)) {
|
|
31237
|
+
fs4.mkdirSync(yearMonth, { recursive: true });
|
|
31238
|
+
}
|
|
31239
|
+
sessionFile = path4.join(yearMonth, `${shortId}.json`);
|
|
31240
|
+
const initial = {
|
|
31241
|
+
id: shortId,
|
|
31242
|
+
sessionId: claudeSessionId,
|
|
31243
|
+
createdAt: now.toISOString(),
|
|
31244
|
+
title: "",
|
|
31245
|
+
tags: [],
|
|
31246
|
+
context: {
|
|
31247
|
+
projectDir: projectPath,
|
|
31248
|
+
projectName: path4.basename(projectPath)
|
|
31418
31249
|
},
|
|
31419
|
-
|
|
31420
|
-
|
|
31421
|
-
|
|
31422
|
-
|
|
31423
|
-
|
|
31424
|
-
|
|
31425
|
-
|
|
31426
|
-
|
|
31427
|
-
{
|
|
31428
|
-
description: "Update unit status (approve/reject/pending) in bulk or single item.",
|
|
31429
|
-
inputSchema: {
|
|
31430
|
-
unitIds: external_exports3.array(external_exports3.string().min(1)).min(1).describe("Target unit IDs"),
|
|
31431
|
-
status: external_exports3.enum(["pending", "approved", "rejected"]).describe("New status"),
|
|
31432
|
-
reviewedBy: external_exports3.string().optional().describe("Reviewer name (optional)")
|
|
31433
|
-
}
|
|
31434
|
-
},
|
|
31435
|
-
async ({ unitIds, status, reviewedBy }) => {
|
|
31436
|
-
const doc = readUnits();
|
|
31437
|
-
const target = new Set(unitIds);
|
|
31438
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
31439
|
-
let updated = 0;
|
|
31440
|
-
doc.items = doc.items.map((item) => {
|
|
31441
|
-
if (!target.has(item.id)) return item;
|
|
31442
|
-
updated += 1;
|
|
31443
|
-
return {
|
|
31444
|
-
...item,
|
|
31445
|
-
status,
|
|
31446
|
-
reviewedAt: now,
|
|
31447
|
-
reviewedBy,
|
|
31448
|
-
updatedAt: now
|
|
31250
|
+
metrics: {
|
|
31251
|
+
userMessages: 0,
|
|
31252
|
+
assistantResponses: 0,
|
|
31253
|
+
thinkingBlocks: 0,
|
|
31254
|
+
toolUsage: []
|
|
31255
|
+
},
|
|
31256
|
+
files: [],
|
|
31257
|
+
status: null
|
|
31449
31258
|
};
|
|
31450
|
-
|
|
31451
|
-
|
|
31452
|
-
|
|
31453
|
-
|
|
31454
|
-
|
|
31455
|
-
|
|
31456
|
-
|
|
31457
|
-
|
|
31458
|
-
|
|
31459
|
-
)
|
|
31460
|
-
|
|
31461
|
-
);
|
|
31462
|
-
|
|
31463
|
-
|
|
31464
|
-
|
|
31465
|
-
|
|
31466
|
-
|
|
31467
|
-
|
|
31468
|
-
|
|
31259
|
+
fs4.writeFileSync(sessionFile, JSON.stringify(initial, null, 2));
|
|
31260
|
+
}
|
|
31261
|
+
const data = readJsonFile(sessionFile) ?? {};
|
|
31262
|
+
data.title = title;
|
|
31263
|
+
data.summary = summary;
|
|
31264
|
+
data.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
31265
|
+
if (tags) data.tags = tags;
|
|
31266
|
+
if (sessionType) data.sessionType = sessionType;
|
|
31267
|
+
const transcriptPath = getTranscriptPath(claudeSessionId);
|
|
31268
|
+
if (transcriptPath) {
|
|
31269
|
+
try {
|
|
31270
|
+
const parsed = await parseTranscript(transcriptPath);
|
|
31271
|
+
data.metrics = {
|
|
31272
|
+
userMessages: parsed.metrics.userMessages,
|
|
31273
|
+
assistantResponses: parsed.metrics.assistantResponses,
|
|
31274
|
+
thinkingBlocks: parsed.metrics.thinkingBlocks,
|
|
31275
|
+
toolUsage: parsed.toolUsage
|
|
31276
|
+
};
|
|
31277
|
+
if (parsed.files.length > 0) {
|
|
31278
|
+
data.files = parsed.files;
|
|
31279
|
+
}
|
|
31280
|
+
} catch {
|
|
31281
|
+
}
|
|
31469
31282
|
}
|
|
31470
|
-
|
|
31471
|
-
|
|
31472
|
-
|
|
31473
|
-
|
|
31474
|
-
|
|
31475
|
-
|
|
31476
|
-
|
|
31477
|
-
|
|
31478
|
-
|
|
31479
|
-
|
|
31480
|
-
|
|
31481
|
-
|
|
31482
|
-
|
|
31483
|
-
|
|
31283
|
+
const ctx = data.context;
|
|
31284
|
+
if (ctx && !ctx.repository) {
|
|
31285
|
+
try {
|
|
31286
|
+
const { execSync } = await import("node:child_process");
|
|
31287
|
+
const cwd = ctx.projectDir || projectPath;
|
|
31288
|
+
const branch = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
31289
|
+
encoding: "utf8",
|
|
31290
|
+
cwd
|
|
31291
|
+
}).trim();
|
|
31292
|
+
if (branch) ctx.branch = branch;
|
|
31293
|
+
const remoteUrl = execSync("git remote get-url origin", {
|
|
31294
|
+
encoding: "utf8",
|
|
31295
|
+
cwd
|
|
31296
|
+
}).trim();
|
|
31297
|
+
const repoMatch = remoteUrl.match(/[:/]([^/]+\/[^/]+?)(\.git)?$/);
|
|
31298
|
+
if (repoMatch) ctx.repository = repoMatch[1].replace(/\.git$/, "");
|
|
31299
|
+
const userName = execSync("git config user.name", {
|
|
31300
|
+
encoding: "utf8",
|
|
31301
|
+
cwd
|
|
31302
|
+
}).trim();
|
|
31303
|
+
const userEmail = execSync("git config user.email", {
|
|
31304
|
+
encoding: "utf8",
|
|
31305
|
+
cwd
|
|
31306
|
+
}).trim();
|
|
31307
|
+
if (userName)
|
|
31308
|
+
ctx.user = {
|
|
31309
|
+
name: userName,
|
|
31310
|
+
...userEmail ? { email: userEmail } : {}
|
|
31311
|
+
};
|
|
31312
|
+
} catch {
|
|
31313
|
+
}
|
|
31314
|
+
}
|
|
31315
|
+
fs4.writeFileSync(sessionFile, JSON.stringify(data, null, 2));
|
|
31316
|
+
markSessionCommitted(claudeSessionId);
|
|
31484
31317
|
return ok(
|
|
31485
31318
|
JSON.stringify(
|
|
31486
31319
|
{
|
|
31487
|
-
|
|
31488
|
-
|
|
31489
|
-
|
|
31490
|
-
title: item.unit.title,
|
|
31491
|
-
score: item.score,
|
|
31492
|
-
reasons: item.reasons,
|
|
31493
|
-
source: `${item.unit.sourceType}:${item.unit.sourceId}`
|
|
31494
|
-
}))
|
|
31320
|
+
success: true,
|
|
31321
|
+
sessionFile: sessionFile.replace(projectPath, "."),
|
|
31322
|
+
shortId
|
|
31495
31323
|
},
|
|
31496
31324
|
null,
|
|
31497
31325
|
2
|
|
@@ -31500,33 +31328,22 @@ server.registerTool(
|
|
|
31500
31328
|
}
|
|
31501
31329
|
);
|
|
31502
31330
|
server.registerTool(
|
|
31503
|
-
"
|
|
31331
|
+
"mneme_mark_session_committed",
|
|
31504
31332
|
{
|
|
31505
|
-
description: "
|
|
31333
|
+
description: "Mark a session as committed (saved with /mneme:save). This prevents the session's interactions from being deleted on SessionEnd. Call this after successfully saving session data.",
|
|
31506
31334
|
inputSchema: {
|
|
31507
|
-
|
|
31508
|
-
diff: external_exports3.string().min(1).describe("Unified diff text")
|
|
31335
|
+
claudeSessionId: external_exports3.string().min(8).describe("Full Claude Code session UUID (36 chars)")
|
|
31509
31336
|
}
|
|
31510
31337
|
},
|
|
31511
|
-
async ({
|
|
31512
|
-
|
|
31513
|
-
|
|
31514
|
-
|
|
31515
|
-
const
|
|
31516
|
-
return
|
|
31517
|
-
JSON.stringify(
|
|
31518
|
-
|
|
31519
|
-
|
|
31520
|
-
title: unit.title,
|
|
31521
|
-
score: scored.score,
|
|
31522
|
-
reasons: scored.reasons,
|
|
31523
|
-
priority: inferUnitPriority(unit),
|
|
31524
|
-
changedFiles
|
|
31525
|
-
},
|
|
31526
|
-
null,
|
|
31527
|
-
2
|
|
31528
|
-
)
|
|
31529
|
-
);
|
|
31338
|
+
async ({ claudeSessionId }) => {
|
|
31339
|
+
if (!claudeSessionId.trim()) {
|
|
31340
|
+
return fail("claudeSessionId must not be empty.");
|
|
31341
|
+
}
|
|
31342
|
+
const success2 = markSessionCommitted(claudeSessionId);
|
|
31343
|
+
return {
|
|
31344
|
+
...ok(JSON.stringify({ success: success2, claudeSessionId }, null, 2)),
|
|
31345
|
+
isError: !success2
|
|
31346
|
+
};
|
|
31530
31347
|
}
|
|
31531
31348
|
);
|
|
31532
31349
|
server.registerTool(
|
|
@@ -31688,61 +31505,6 @@ server.registerTool(
|
|
|
31688
31505
|
);
|
|
31689
31506
|
}
|
|
31690
31507
|
);
|
|
31691
|
-
server.registerTool(
|
|
31692
|
-
"mneme_graph_insights",
|
|
31693
|
-
{
|
|
31694
|
-
description: "Compute graph insights from approved units: central units, tag communities, orphan units.",
|
|
31695
|
-
inputSchema: {
|
|
31696
|
-
limit: external_exports3.number().int().min(LIST_LIMIT_MIN).max(100).optional().describe("Limit for ranked outputs (default: 10)")
|
|
31697
|
-
}
|
|
31698
|
-
},
|
|
31699
|
-
async ({ limit }) => {
|
|
31700
|
-
const k = limit ?? 10;
|
|
31701
|
-
const units = readUnits().items.filter(
|
|
31702
|
-
(item) => item.status === "approved"
|
|
31703
|
-
);
|
|
31704
|
-
const graph = buildUnitGraph(units);
|
|
31705
|
-
const degree = /* @__PURE__ */ new Map();
|
|
31706
|
-
for (const unit of graph.nodes) degree.set(unit.id, 0);
|
|
31707
|
-
for (const edge of graph.edges) {
|
|
31708
|
-
degree.set(edge.source, (degree.get(edge.source) || 0) + 1);
|
|
31709
|
-
degree.set(edge.target, (degree.get(edge.target) || 0) + 1);
|
|
31710
|
-
}
|
|
31711
|
-
const topCentral = graph.nodes.map((unit) => ({
|
|
31712
|
-
id: unit.id,
|
|
31713
|
-
title: unit.title,
|
|
31714
|
-
degree: degree.get(unit.id) || 0
|
|
31715
|
-
})).sort((a, b) => b.degree - a.degree).slice(0, k);
|
|
31716
|
-
const tagCounts = /* @__PURE__ */ new Map();
|
|
31717
|
-
for (const unit of graph.nodes) {
|
|
31718
|
-
for (const tag of unit.tags) {
|
|
31719
|
-
tagCounts.set(tag, (tagCounts.get(tag) || 0) + 1);
|
|
31720
|
-
}
|
|
31721
|
-
}
|
|
31722
|
-
const communities = Array.from(tagCounts.entries()).map(([tag, count]) => ({
|
|
31723
|
-
tag,
|
|
31724
|
-
count
|
|
31725
|
-
})).sort((a, b) => b.count - a.count).slice(0, k);
|
|
31726
|
-
const orphans = graph.nodes.filter((unit) => (degree.get(unit.id) || 0) === 0).map((unit) => ({
|
|
31727
|
-
id: unit.id,
|
|
31728
|
-
title: unit.title,
|
|
31729
|
-
tags: unit.tags
|
|
31730
|
-
})).slice(0, k);
|
|
31731
|
-
return ok(
|
|
31732
|
-
JSON.stringify(
|
|
31733
|
-
{
|
|
31734
|
-
approvedUnits: graph.nodes.length,
|
|
31735
|
-
edges: graph.edges.length,
|
|
31736
|
-
topCentral,
|
|
31737
|
-
tagCommunities: communities,
|
|
31738
|
-
orphanUnits: orphans
|
|
31739
|
-
},
|
|
31740
|
-
null,
|
|
31741
|
-
2
|
|
31742
|
-
)
|
|
31743
|
-
);
|
|
31744
|
-
}
|
|
31745
|
-
);
|
|
31746
31508
|
server.registerTool(
|
|
31747
31509
|
"mneme_search_eval",
|
|
31748
31510
|
{
|
|
@@ -31778,64 +31540,6 @@ server.registerTool(
|
|
|
31778
31540
|
return ok(JSON.stringify(payload, null, 2));
|
|
31779
31541
|
}
|
|
31780
31542
|
);
|
|
31781
|
-
server.registerTool(
|
|
31782
|
-
"mneme_audit_query",
|
|
31783
|
-
{
|
|
31784
|
-
description: "Query unit-related audit logs and summarize change history.",
|
|
31785
|
-
inputSchema: {
|
|
31786
|
-
from: external_exports3.string().optional().describe("Start ISO date/time"),
|
|
31787
|
-
to: external_exports3.string().optional().describe("End ISO date/time"),
|
|
31788
|
-
targetId: external_exports3.string().optional().describe("Filter by target unit ID"),
|
|
31789
|
-
summaryMode: external_exports3.enum(["changes", "actors", "target"]).optional().describe(
|
|
31790
|
-
"changes=list, actors=aggregate by actor, target=single target history"
|
|
31791
|
-
)
|
|
31792
|
-
}
|
|
31793
|
-
},
|
|
31794
|
-
async ({ from, to, targetId, summaryMode }) => {
|
|
31795
|
-
const entries = readAuditEntries({ from, to, entity: "unit" }).filter(
|
|
31796
|
-
(entry) => targetId ? entry.targetId === targetId : true
|
|
31797
|
-
);
|
|
31798
|
-
if ((summaryMode || "changes") === "actors") {
|
|
31799
|
-
const byActor = /* @__PURE__ */ new Map();
|
|
31800
|
-
for (const entry of entries) {
|
|
31801
|
-
const actor = entry.actor || "unknown";
|
|
31802
|
-
byActor.set(actor, (byActor.get(actor) || 0) + 1);
|
|
31803
|
-
}
|
|
31804
|
-
return ok(
|
|
31805
|
-
JSON.stringify(
|
|
31806
|
-
{
|
|
31807
|
-
total: entries.length,
|
|
31808
|
-
actors: Array.from(byActor.entries()).map(([actor, count]) => ({ actor, count })).sort((a, b) => b.count - a.count)
|
|
31809
|
-
},
|
|
31810
|
-
null,
|
|
31811
|
-
2
|
|
31812
|
-
)
|
|
31813
|
-
);
|
|
31814
|
-
}
|
|
31815
|
-
if ((summaryMode || "changes") === "target" && targetId) {
|
|
31816
|
-
return ok(
|
|
31817
|
-
JSON.stringify(
|
|
31818
|
-
{
|
|
31819
|
-
targetId,
|
|
31820
|
-
history: entries
|
|
31821
|
-
},
|
|
31822
|
-
null,
|
|
31823
|
-
2
|
|
31824
|
-
)
|
|
31825
|
-
);
|
|
31826
|
-
}
|
|
31827
|
-
return ok(
|
|
31828
|
-
JSON.stringify(
|
|
31829
|
-
{
|
|
31830
|
-
total: entries.length,
|
|
31831
|
-
changes: entries
|
|
31832
|
-
},
|
|
31833
|
-
null,
|
|
31834
|
-
2
|
|
31835
|
-
)
|
|
31836
|
-
);
|
|
31837
|
-
}
|
|
31838
|
-
);
|
|
31839
31543
|
async function main() {
|
|
31840
31544
|
const transport = new StdioServerTransport();
|
|
31841
31545
|
await server.connect(transport);
|