@hir4ta/mneme 0.23.0 → 0.23.2
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 +1 -1
- package/README.md +1 -1
- package/dist/lib/save/index.js +784 -0
- package/dist/lib/search/prompt.js +672 -0
- package/dist/lib/session/finalize.js +1001 -0
- package/dist/lib/session/init.js +419 -0
- package/dist/lib/session-finalize.js +8 -8
- package/dist/lib/session-init.js +5 -5
- package/dist/server.js +6 -12
- package/dist/servers/db-server.js +239 -239
- package/dist/servers/search-server.js +8 -8
- package/hooks/lib/common.sh +16 -6
- package/package.json +2 -2
- package/scripts/search-benchmark.ts +1 -1
- package/servers/{db-benchmark.ts → db/benchmark.ts} +3 -3
- package/servers/{db-queries.ts → db/queries.ts} +2 -7
- package/servers/{db-save.ts → db/save.ts} +3 -3
- package/servers/{db-session-summary.ts → db/session-summary.ts} +5 -7
- package/servers/{db-server-tools.ts → db/tools.ts} +9 -14
- package/servers/{db-transcript.ts → db/transcript.ts} +2 -2
- package/servers/{db-utils.ts → db/utils.ts} +1 -5
- package/servers/db-server.ts +10 -15
- package/servers/search-server.ts +2 -3
- package/skills/harvest/SKILL.md +1 -0
- package/skills/report/SKILL.md +3 -1
- package/skills/resume/SKILL.md +1 -0
- package/skills/save/SKILL.md +1 -0
- package/skills/search/SKILL.md +1 -0
- /package/servers/{db-types.ts → db/types.ts} +0 -0
|
@@ -29947,11 +29947,11 @@ var StdioServerTransport = class {
|
|
|
29947
29947
|
}
|
|
29948
29948
|
};
|
|
29949
29949
|
|
|
29950
|
-
// servers/db
|
|
29950
|
+
// servers/db/utils.ts
|
|
29951
29951
|
import * as fs from "node:fs";
|
|
29952
29952
|
import * as path from "node:path";
|
|
29953
29953
|
|
|
29954
|
-
// servers/db
|
|
29954
|
+
// servers/db/types.ts
|
|
29955
29955
|
var { DatabaseSync } = await import("node:sqlite");
|
|
29956
29956
|
var LIST_LIMIT_MIN = 1;
|
|
29957
29957
|
var LIST_LIMIT_MAX = 200;
|
|
@@ -29968,7 +29968,7 @@ function fail(message) {
|
|
|
29968
29968
|
};
|
|
29969
29969
|
}
|
|
29970
29970
|
|
|
29971
|
-
// servers/db
|
|
29971
|
+
// servers/db/utils.ts
|
|
29972
29972
|
function getProjectPath() {
|
|
29973
29973
|
return process.env.MNEME_PROJECT_PATH || process.cwd();
|
|
29974
29974
|
}
|
|
@@ -30030,7 +30030,7 @@ function getDb() {
|
|
|
30030
30030
|
}
|
|
30031
30031
|
}
|
|
30032
30032
|
|
|
30033
|
-
// servers/db
|
|
30033
|
+
// servers/db/queries.ts
|
|
30034
30034
|
function mapSearchRow(row) {
|
|
30035
30035
|
return {
|
|
30036
30036
|
sessionId: row.session_id,
|
|
@@ -30235,10 +30235,10 @@ function crossProjectSearch(query, options = {}) {
|
|
|
30235
30235
|
}
|
|
30236
30236
|
}
|
|
30237
30237
|
|
|
30238
|
-
// servers/db
|
|
30238
|
+
// servers/db/save.ts
|
|
30239
30239
|
import * as os2 from "node:os";
|
|
30240
30240
|
|
|
30241
|
-
// servers/db
|
|
30241
|
+
// servers/db/transcript.ts
|
|
30242
30242
|
import * as fs2 from "node:fs";
|
|
30243
30243
|
import * as os from "node:os";
|
|
30244
30244
|
import * as path2 from "node:path";
|
|
@@ -30368,7 +30368,7 @@ function buildInteractions(userMessages, assistantMessages) {
|
|
|
30368
30368
|
return interactions;
|
|
30369
30369
|
}
|
|
30370
30370
|
|
|
30371
|
-
// servers/db
|
|
30371
|
+
// servers/db/save.ts
|
|
30372
30372
|
async function saveInteractions(claudeSessionId, mnemeSessionId) {
|
|
30373
30373
|
const transcriptPath = getTranscriptPath(claudeSessionId);
|
|
30374
30374
|
if (!transcriptPath) {
|
|
@@ -30577,27 +30577,214 @@ function markSessionCommitted(claudeSessionId) {
|
|
|
30577
30577
|
}
|
|
30578
30578
|
}
|
|
30579
30579
|
|
|
30580
|
-
// servers/db-
|
|
30580
|
+
// servers/db/session-summary.ts
|
|
30581
|
+
import * as fs3 from "node:fs";
|
|
30582
|
+
import * as path3 from "node:path";
|
|
30583
|
+
async function updateSessionSummary(params) {
|
|
30584
|
+
const {
|
|
30585
|
+
claudeSessionId,
|
|
30586
|
+
title,
|
|
30587
|
+
summary,
|
|
30588
|
+
tags,
|
|
30589
|
+
sessionType,
|
|
30590
|
+
plan,
|
|
30591
|
+
discussions,
|
|
30592
|
+
errors,
|
|
30593
|
+
handoff,
|
|
30594
|
+
references
|
|
30595
|
+
} = params;
|
|
30596
|
+
const projectPath = getProjectPath();
|
|
30597
|
+
const sessionsDir = path3.join(projectPath, ".mneme", "sessions");
|
|
30598
|
+
const shortId = claudeSessionId.slice(0, 8);
|
|
30599
|
+
let sessionFile = null;
|
|
30600
|
+
const searchDir = (dir) => {
|
|
30601
|
+
if (!fs3.existsSync(dir)) return null;
|
|
30602
|
+
for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
|
|
30603
|
+
const fullPath = path3.join(dir, entry.name);
|
|
30604
|
+
if (entry.isDirectory()) {
|
|
30605
|
+
const result = searchDir(fullPath);
|
|
30606
|
+
if (result) return result;
|
|
30607
|
+
} else if (entry.name === `${shortId}.json`) {
|
|
30608
|
+
return fullPath;
|
|
30609
|
+
}
|
|
30610
|
+
}
|
|
30611
|
+
return null;
|
|
30612
|
+
};
|
|
30613
|
+
sessionFile = searchDir(sessionsDir);
|
|
30614
|
+
if (sessionFile) {
|
|
30615
|
+
const existingData = readJsonFile(sessionFile);
|
|
30616
|
+
if (existingData?.sessionId && existingData.sessionId !== claudeSessionId) {
|
|
30617
|
+
sessionFile = null;
|
|
30618
|
+
}
|
|
30619
|
+
}
|
|
30620
|
+
if (!sessionFile) {
|
|
30621
|
+
const now = /* @__PURE__ */ new Date();
|
|
30622
|
+
const yearMonth = path3.join(
|
|
30623
|
+
sessionsDir,
|
|
30624
|
+
String(now.getFullYear()),
|
|
30625
|
+
String(now.getMonth() + 1).padStart(2, "0")
|
|
30626
|
+
);
|
|
30627
|
+
if (!fs3.existsSync(yearMonth)) fs3.mkdirSync(yearMonth, { recursive: true });
|
|
30628
|
+
sessionFile = path3.join(yearMonth, `${shortId}.json`);
|
|
30629
|
+
const initial = {
|
|
30630
|
+
id: shortId,
|
|
30631
|
+
sessionId: claudeSessionId,
|
|
30632
|
+
createdAt: now.toISOString(),
|
|
30633
|
+
title: "",
|
|
30634
|
+
tags: [],
|
|
30635
|
+
context: {
|
|
30636
|
+
projectDir: projectPath,
|
|
30637
|
+
projectName: path3.basename(projectPath)
|
|
30638
|
+
},
|
|
30639
|
+
metrics: {
|
|
30640
|
+
userMessages: 0,
|
|
30641
|
+
assistantResponses: 0,
|
|
30642
|
+
thinkingBlocks: 0,
|
|
30643
|
+
toolUsage: []
|
|
30644
|
+
},
|
|
30645
|
+
files: [],
|
|
30646
|
+
status: null
|
|
30647
|
+
};
|
|
30648
|
+
fs3.writeFileSync(sessionFile, JSON.stringify(initial, null, 2));
|
|
30649
|
+
}
|
|
30650
|
+
const data = readJsonFile(sessionFile) ?? {};
|
|
30651
|
+
data.title = title;
|
|
30652
|
+
data.summary = summary;
|
|
30653
|
+
data.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
30654
|
+
if (tags) data.tags = tags;
|
|
30655
|
+
if (sessionType) data.sessionType = sessionType;
|
|
30656
|
+
if (plan) data.plan = plan;
|
|
30657
|
+
if (discussions && discussions.length > 0) data.discussions = discussions;
|
|
30658
|
+
if (errors && errors.length > 0) data.errors = errors;
|
|
30659
|
+
if (handoff) data.handoff = handoff;
|
|
30660
|
+
if (references && references.length > 0) data.references = references;
|
|
30661
|
+
const transcriptPath = getTranscriptPath(claudeSessionId);
|
|
30662
|
+
if (transcriptPath) {
|
|
30663
|
+
try {
|
|
30664
|
+
const parsed = await parseTranscript(transcriptPath);
|
|
30665
|
+
data.metrics = {
|
|
30666
|
+
userMessages: parsed.metrics.userMessages,
|
|
30667
|
+
assistantResponses: parsed.metrics.assistantResponses,
|
|
30668
|
+
thinkingBlocks: parsed.metrics.thinkingBlocks,
|
|
30669
|
+
toolUsage: parsed.toolUsage
|
|
30670
|
+
};
|
|
30671
|
+
if (parsed.files.length > 0) data.files = parsed.files;
|
|
30672
|
+
} catch {
|
|
30673
|
+
}
|
|
30674
|
+
}
|
|
30675
|
+
const ctx = data.context;
|
|
30676
|
+
if (ctx && !ctx.repository) {
|
|
30677
|
+
try {
|
|
30678
|
+
const { execSync } = await import("node:child_process");
|
|
30679
|
+
const cwd = ctx.projectDir || projectPath;
|
|
30680
|
+
const git = (cmd) => execSync(cmd, { encoding: "utf8", cwd }).trim();
|
|
30681
|
+
const branch = git("git rev-parse --abbrev-ref HEAD");
|
|
30682
|
+
if (branch) ctx.branch = branch;
|
|
30683
|
+
const remoteUrl = git("git remote get-url origin");
|
|
30684
|
+
const repoMatch = remoteUrl.match(/[:/]([^/]+\/[^/]+?)(\.git)?$/);
|
|
30685
|
+
if (repoMatch) ctx.repository = repoMatch[1].replace(/\.git$/, "");
|
|
30686
|
+
const userName = git("git config user.name");
|
|
30687
|
+
const userEmail = git("git config user.email");
|
|
30688
|
+
if (userName)
|
|
30689
|
+
ctx.user = {
|
|
30690
|
+
name: userName,
|
|
30691
|
+
...userEmail ? { email: userEmail } : {}
|
|
30692
|
+
};
|
|
30693
|
+
} catch {
|
|
30694
|
+
}
|
|
30695
|
+
}
|
|
30696
|
+
fs3.writeFileSync(sessionFile, JSON.stringify(data, null, 2));
|
|
30697
|
+
markSessionCommitted(claudeSessionId);
|
|
30698
|
+
return {
|
|
30699
|
+
success: true,
|
|
30700
|
+
sessionFile: sessionFile.replace(projectPath, "."),
|
|
30701
|
+
shortId
|
|
30702
|
+
};
|
|
30703
|
+
}
|
|
30704
|
+
function registerSessionSummaryTool(server2) {
|
|
30705
|
+
server2.registerTool(
|
|
30706
|
+
"mneme_update_session_summary",
|
|
30707
|
+
{
|
|
30708
|
+
description: "Update session JSON with summary data for /mneme:save Phase 3.",
|
|
30709
|
+
inputSchema: {
|
|
30710
|
+
claudeSessionId: external_exports3.string().min(8).describe("Full Claude Code session UUID (36 chars)"),
|
|
30711
|
+
title: external_exports3.string().describe("Session title"),
|
|
30712
|
+
summary: external_exports3.object({
|
|
30713
|
+
goal: external_exports3.string().describe("What the session aimed to accomplish"),
|
|
30714
|
+
outcome: external_exports3.string().describe("What was actually accomplished"),
|
|
30715
|
+
description: external_exports3.string().optional().describe("Detailed description of the session")
|
|
30716
|
+
}).describe("Session summary object"),
|
|
30717
|
+
tags: external_exports3.array(external_exports3.string()).optional().describe("Semantic tags for the session"),
|
|
30718
|
+
sessionType: external_exports3.string().optional().describe(
|
|
30719
|
+
"Session type (e.g. implementation, research, bugfix, refactor)"
|
|
30720
|
+
),
|
|
30721
|
+
plan: external_exports3.object({
|
|
30722
|
+
goals: external_exports3.array(external_exports3.string()).optional().describe("Session goals"),
|
|
30723
|
+
tasks: external_exports3.array(external_exports3.string()).optional().describe('Task list (prefix with "[x] " for completed)'),
|
|
30724
|
+
remaining: external_exports3.array(external_exports3.string()).optional().describe("Remaining tasks")
|
|
30725
|
+
}).optional().describe("Session plan with tasks and progress"),
|
|
30726
|
+
discussions: external_exports3.array(
|
|
30727
|
+
external_exports3.object({
|
|
30728
|
+
topic: external_exports3.string().describe("Discussion topic"),
|
|
30729
|
+
decision: external_exports3.string().describe("Final decision"),
|
|
30730
|
+
reasoning: external_exports3.string().optional().describe("Reasoning"),
|
|
30731
|
+
alternatives: external_exports3.array(external_exports3.string()).optional().describe("Considered alternatives")
|
|
30732
|
+
})
|
|
30733
|
+
).optional().describe("Design discussions and decisions made during session"),
|
|
30734
|
+
errors: external_exports3.array(
|
|
30735
|
+
external_exports3.object({
|
|
30736
|
+
error: external_exports3.string().describe("Error message or description"),
|
|
30737
|
+
context: external_exports3.string().optional().describe("Where/when it occurred"),
|
|
30738
|
+
solution: external_exports3.string().optional().describe("How it was resolved"),
|
|
30739
|
+
files: external_exports3.array(external_exports3.string()).optional().describe("Related file paths")
|
|
30740
|
+
})
|
|
30741
|
+
).optional().describe("Errors encountered and their solutions"),
|
|
30742
|
+
handoff: external_exports3.object({
|
|
30743
|
+
stoppedReason: external_exports3.string().optional().describe("Why the session stopped"),
|
|
30744
|
+
notes: external_exports3.array(external_exports3.string()).optional().describe("Important notes for next session"),
|
|
30745
|
+
nextSteps: external_exports3.array(external_exports3.string()).optional().describe("What to do next")
|
|
30746
|
+
}).optional().describe("Handoff context for session continuity"),
|
|
30747
|
+
references: external_exports3.array(
|
|
30748
|
+
external_exports3.object({
|
|
30749
|
+
type: external_exports3.string().optional().describe('Reference type: "doc", "file", "url"'),
|
|
30750
|
+
url: external_exports3.string().optional().describe("URL if external"),
|
|
30751
|
+
path: external_exports3.string().optional().describe("File path if local"),
|
|
30752
|
+
title: external_exports3.string().optional().describe("Title or label"),
|
|
30753
|
+
description: external_exports3.string().optional().describe("Brief description")
|
|
30754
|
+
})
|
|
30755
|
+
).optional().describe("Documents and resources referenced during session")
|
|
30756
|
+
}
|
|
30757
|
+
},
|
|
30758
|
+
async (params) => {
|
|
30759
|
+
if (!params.claudeSessionId.trim())
|
|
30760
|
+
return fail("claudeSessionId must not be empty.");
|
|
30761
|
+
const result = await updateSessionSummary(params);
|
|
30762
|
+
return ok(JSON.stringify(result, null, 2));
|
|
30763
|
+
}
|
|
30764
|
+
);
|
|
30765
|
+
}
|
|
30766
|
+
|
|
30767
|
+
// servers/db/benchmark.ts
|
|
30768
|
+
import * as path8 from "node:path";
|
|
30769
|
+
|
|
30770
|
+
// lib/search/core.ts
|
|
30771
|
+
import * as fs7 from "node:fs";
|
|
30581
30772
|
import * as path7 from "node:path";
|
|
30582
30773
|
|
|
30583
|
-
// lib/search
|
|
30774
|
+
// lib/search/helpers.ts
|
|
30584
30775
|
import * as fs6 from "node:fs";
|
|
30585
30776
|
import * as path6 from "node:path";
|
|
30586
30777
|
|
|
30587
|
-
// lib/search
|
|
30778
|
+
// lib/search/fuzzy.ts
|
|
30588
30779
|
import * as fs5 from "node:fs";
|
|
30589
30780
|
import * as path5 from "node:path";
|
|
30590
30781
|
|
|
30591
|
-
// lib/
|
|
30782
|
+
// lib/utils.ts
|
|
30592
30783
|
import * as fs4 from "node:fs";
|
|
30593
30784
|
import * as path4 from "node:path";
|
|
30594
|
-
|
|
30595
|
-
// lib/utils.ts
|
|
30596
|
-
import * as fs3 from "node:fs";
|
|
30597
|
-
import * as path3 from "node:path";
|
|
30598
30785
|
function safeReadJson(filePath, fallback) {
|
|
30599
30786
|
try {
|
|
30600
|
-
const content =
|
|
30787
|
+
const content = fs4.readFileSync(filePath, "utf-8");
|
|
30601
30788
|
return JSON.parse(content);
|
|
30602
30789
|
} catch {
|
|
30603
30790
|
return fallback;
|
|
@@ -30605,10 +30792,10 @@ function safeReadJson(filePath, fallback) {
|
|
|
30605
30792
|
}
|
|
30606
30793
|
function findJsonFiles(dir) {
|
|
30607
30794
|
const results = [];
|
|
30608
|
-
if (!
|
|
30609
|
-
const items =
|
|
30795
|
+
if (!fs4.existsSync(dir)) return results;
|
|
30796
|
+
const items = fs4.readdirSync(dir, { withFileTypes: true });
|
|
30610
30797
|
for (const item of items) {
|
|
30611
|
-
const fullPath =
|
|
30798
|
+
const fullPath = path4.join(dir, item.name);
|
|
30612
30799
|
if (item.isDirectory()) {
|
|
30613
30800
|
results.push(...findJsonFiles(fullPath));
|
|
30614
30801
|
} else if (item.name.endsWith(".json")) {
|
|
@@ -30618,7 +30805,7 @@ function findJsonFiles(dir) {
|
|
|
30618
30805
|
return results;
|
|
30619
30806
|
}
|
|
30620
30807
|
|
|
30621
|
-
// lib/fuzzy
|
|
30808
|
+
// lib/search/fuzzy.ts
|
|
30622
30809
|
function levenshtein(a, b) {
|
|
30623
30810
|
const matrix = [];
|
|
30624
30811
|
for (let i = 0; i <= a.length; i++) {
|
|
@@ -30680,12 +30867,12 @@ async function search(options) {
|
|
|
30680
30867
|
} = options;
|
|
30681
30868
|
const startTime = Date.now();
|
|
30682
30869
|
const results = [];
|
|
30683
|
-
const tagsPath =
|
|
30870
|
+
const tagsPath = path5.join(mnemeDir, "tags.json");
|
|
30684
30871
|
const tagsData = safeReadJson(tagsPath, { tags: [] });
|
|
30685
30872
|
const expandedQueries = expandAliases(query, tagsData.tags);
|
|
30686
30873
|
if (targets.includes("sessions")) {
|
|
30687
|
-
const sessionsDir =
|
|
30688
|
-
if (
|
|
30874
|
+
const sessionsDir = path5.join(mnemeDir, "sessions");
|
|
30875
|
+
if (fs5.existsSync(sessionsDir)) {
|
|
30689
30876
|
const files = findJsonFiles(sessionsDir);
|
|
30690
30877
|
for (const file2 of files) {
|
|
30691
30878
|
if (Date.now() - startTime > timeout) break;
|
|
@@ -30698,7 +30885,7 @@ async function search(options) {
|
|
|
30698
30885
|
if (score > 0) {
|
|
30699
30886
|
results.push({
|
|
30700
30887
|
type: "session",
|
|
30701
|
-
id: session.id ||
|
|
30888
|
+
id: session.id || path5.basename(file2, ".json"),
|
|
30702
30889
|
score,
|
|
30703
30890
|
title: session.title || "Untitled",
|
|
30704
30891
|
highlights: []
|
|
@@ -30708,8 +30895,8 @@ async function search(options) {
|
|
|
30708
30895
|
}
|
|
30709
30896
|
}
|
|
30710
30897
|
if (targets.includes("decisions")) {
|
|
30711
|
-
const decisionsDir =
|
|
30712
|
-
if (
|
|
30898
|
+
const decisionsDir = path5.join(mnemeDir, "decisions");
|
|
30899
|
+
if (fs5.existsSync(decisionsDir)) {
|
|
30713
30900
|
const files = findJsonFiles(decisionsDir);
|
|
30714
30901
|
for (const file2 of files) {
|
|
30715
30902
|
if (Date.now() - startTime > timeout) break;
|
|
@@ -30722,7 +30909,7 @@ async function search(options) {
|
|
|
30722
30909
|
if (score > 0) {
|
|
30723
30910
|
results.push({
|
|
30724
30911
|
type: "decision",
|
|
30725
|
-
id: decision.id ||
|
|
30912
|
+
id: decision.id || path5.basename(file2, ".json"),
|
|
30726
30913
|
score,
|
|
30727
30914
|
title: decision.title || "Untitled",
|
|
30728
30915
|
highlights: []
|
|
@@ -30732,8 +30919,8 @@ async function search(options) {
|
|
|
30732
30919
|
}
|
|
30733
30920
|
}
|
|
30734
30921
|
if (targets.includes("patterns")) {
|
|
30735
|
-
const patternsDir =
|
|
30736
|
-
if (
|
|
30922
|
+
const patternsDir = path5.join(mnemeDir, "patterns");
|
|
30923
|
+
if (fs5.existsSync(patternsDir)) {
|
|
30737
30924
|
const files = findJsonFiles(patternsDir);
|
|
30738
30925
|
for (const file2 of files) {
|
|
30739
30926
|
if (Date.now() - startTime > timeout) break;
|
|
@@ -30748,7 +30935,7 @@ async function search(options) {
|
|
|
30748
30935
|
if (score > 0) {
|
|
30749
30936
|
results.push({
|
|
30750
30937
|
type: "pattern",
|
|
30751
|
-
id: `${
|
|
30938
|
+
id: `${path5.basename(file2, ".json")}-${p.type || "unknown"}`,
|
|
30752
30939
|
score,
|
|
30753
30940
|
title: p.description || "Untitled pattern",
|
|
30754
30941
|
highlights: []
|
|
@@ -30780,7 +30967,7 @@ function scoreDocument(doc, queries, fields) {
|
|
|
30780
30967
|
}
|
|
30781
30968
|
return totalScore;
|
|
30782
30969
|
}
|
|
30783
|
-
var isMain = process.argv[1]?.endsWith("fuzzy
|
|
30970
|
+
var isMain = process.argv[1]?.endsWith("fuzzy.js") || process.argv[1]?.endsWith("fuzzy.ts");
|
|
30784
30971
|
if (isMain && process.argv.length > 2) {
|
|
30785
30972
|
const args = process.argv.slice(2);
|
|
30786
30973
|
const queryIndex = args.indexOf("--query");
|
|
@@ -30797,7 +30984,7 @@ if (isMain && process.argv.length > 2) {
|
|
|
30797
30984
|
});
|
|
30798
30985
|
}
|
|
30799
30986
|
|
|
30800
|
-
// lib/search
|
|
30987
|
+
// lib/search/helpers.ts
|
|
30801
30988
|
function escapeRegex2(value) {
|
|
30802
30989
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
30803
30990
|
}
|
|
@@ -30818,10 +31005,10 @@ function isFuzzyMatch(word, target, maxDistance = 2) {
|
|
|
30818
31005
|
return distance <= threshold;
|
|
30819
31006
|
}
|
|
30820
31007
|
function loadTags(mnemeDir) {
|
|
30821
|
-
const tagsPath =
|
|
30822
|
-
if (!
|
|
31008
|
+
const tagsPath = path6.join(mnemeDir, "tags.json");
|
|
31009
|
+
if (!fs6.existsSync(tagsPath)) return null;
|
|
30823
31010
|
try {
|
|
30824
|
-
return JSON.parse(
|
|
31011
|
+
return JSON.parse(fs6.readFileSync(tagsPath, "utf-8"));
|
|
30825
31012
|
} catch {
|
|
30826
31013
|
return null;
|
|
30827
31014
|
}
|
|
@@ -30844,10 +31031,10 @@ function expandKeywordsWithAliases(keywords, tags) {
|
|
|
30844
31031
|
return Array.from(expanded);
|
|
30845
31032
|
}
|
|
30846
31033
|
function walkJsonFiles(dir, callback) {
|
|
30847
|
-
if (!
|
|
30848
|
-
const entries =
|
|
31034
|
+
if (!fs6.existsSync(dir)) return;
|
|
31035
|
+
const entries = fs6.readdirSync(dir, { withFileTypes: true });
|
|
30849
31036
|
for (const entry of entries) {
|
|
30850
|
-
const fullPath =
|
|
31037
|
+
const fullPath = path6.join(dir, entry.name);
|
|
30851
31038
|
if (entry.isDirectory()) {
|
|
30852
31039
|
walkJsonFiles(fullPath, callback);
|
|
30853
31040
|
continue;
|
|
@@ -30858,7 +31045,7 @@ function walkJsonFiles(dir, callback) {
|
|
|
30858
31045
|
}
|
|
30859
31046
|
}
|
|
30860
31047
|
|
|
30861
|
-
// lib/search
|
|
31048
|
+
// lib/search/core.ts
|
|
30862
31049
|
function searchInteractions(keywords, projectPath, database, limit = 5) {
|
|
30863
31050
|
if (!database) return [];
|
|
30864
31051
|
try {
|
|
@@ -30917,13 +31104,13 @@ function searchInteractions(keywords, projectPath, database, limit = 5) {
|
|
|
30917
31104
|
}
|
|
30918
31105
|
}
|
|
30919
31106
|
function searchSessions(mnemeDir, keywords, limit = 5) {
|
|
30920
|
-
const sessionsDir =
|
|
31107
|
+
const sessionsDir = path7.join(mnemeDir, "sessions");
|
|
30921
31108
|
const results = [];
|
|
30922
31109
|
const pattern = new RegExp(keywords.map(escapeRegex2).join("|"), "i");
|
|
30923
31110
|
walkJsonFiles(sessionsDir, (filePath) => {
|
|
30924
31111
|
try {
|
|
30925
31112
|
const session = JSON.parse(
|
|
30926
|
-
|
|
31113
|
+
fs7.readFileSync(filePath, "utf-8")
|
|
30927
31114
|
);
|
|
30928
31115
|
const title = session.title || session.summary?.title || "";
|
|
30929
31116
|
let score = 0;
|
|
@@ -31030,9 +31217,9 @@ function searchKnowledge(options) {
|
|
|
31030
31217
|
}).slice(safeOffset, safeOffset + limit);
|
|
31031
31218
|
}
|
|
31032
31219
|
|
|
31033
|
-
// servers/db
|
|
31220
|
+
// servers/db/benchmark.ts
|
|
31034
31221
|
function runSearchBenchmark(limit) {
|
|
31035
|
-
const queryPath =
|
|
31222
|
+
const queryPath = path8.join(
|
|
31036
31223
|
getProjectPath(),
|
|
31037
31224
|
"scripts",
|
|
31038
31225
|
"search-benchmark.queries.json"
|
|
@@ -31155,12 +31342,12 @@ function lintRules(ruleType) {
|
|
|
31155
31342
|
};
|
|
31156
31343
|
}
|
|
31157
31344
|
|
|
31158
|
-
// servers/db
|
|
31345
|
+
// servers/db/tools.ts
|
|
31159
31346
|
function registerExtendedTools(server2) {
|
|
31160
31347
|
server2.registerTool(
|
|
31161
31348
|
"mneme_mark_session_committed",
|
|
31162
31349
|
{
|
|
31163
|
-
description: "Mark
|
|
31350
|
+
description: "Mark session as committed to prevent cleanup on SessionEnd.",
|
|
31164
31351
|
inputSchema: {
|
|
31165
31352
|
claudeSessionId: external_exports3.string().min(8).describe("Full Claude Code session UUID (36 chars)")
|
|
31166
31353
|
}
|
|
@@ -31178,7 +31365,7 @@ function registerExtendedTools(server2) {
|
|
|
31178
31365
|
server2.registerTool(
|
|
31179
31366
|
"mneme_session_timeline",
|
|
31180
31367
|
{
|
|
31181
|
-
description: "Build timeline for
|
|
31368
|
+
description: "Build timeline for a session or its resume-chain.",
|
|
31182
31369
|
inputSchema: {
|
|
31183
31370
|
sessionId: external_exports3.string().min(1).describe("Session ID (short or full)"),
|
|
31184
31371
|
includeChain: external_exports3.boolean().optional().describe(
|
|
@@ -31237,7 +31424,7 @@ function registerExtendedTools(server2) {
|
|
|
31237
31424
|
server2.registerTool(
|
|
31238
31425
|
"mneme_rule_linter",
|
|
31239
31426
|
{
|
|
31240
|
-
description: "Lint rules for schema and quality
|
|
31427
|
+
description: "Lint rules for schema and quality.",
|
|
31241
31428
|
inputSchema: {
|
|
31242
31429
|
ruleType: external_exports3.enum(["dev-rules", "review-guidelines", "all"]).optional().describe("Rule set to lint (default: all)")
|
|
31243
31430
|
}
|
|
@@ -31249,7 +31436,7 @@ function registerExtendedTools(server2) {
|
|
|
31249
31436
|
server2.registerTool(
|
|
31250
31437
|
"mneme_search_eval",
|
|
31251
31438
|
{
|
|
31252
|
-
description: "Run/compare search benchmark
|
|
31439
|
+
description: "Run/compare search benchmark for quality checks.",
|
|
31253
31440
|
inputSchema: {
|
|
31254
31441
|
mode: external_exports3.enum(["run", "compare", "regression"]).optional().describe(
|
|
31255
31442
|
"run=single, compare=against baseline, regression=threshold check"
|
|
@@ -31283,193 +31470,6 @@ function registerExtendedTools(server2) {
|
|
|
31283
31470
|
);
|
|
31284
31471
|
}
|
|
31285
31472
|
|
|
31286
|
-
// servers/db-session-summary.ts
|
|
31287
|
-
import * as fs7 from "node:fs";
|
|
31288
|
-
import * as path8 from "node:path";
|
|
31289
|
-
async function updateSessionSummary(params) {
|
|
31290
|
-
const {
|
|
31291
|
-
claudeSessionId,
|
|
31292
|
-
title,
|
|
31293
|
-
summary,
|
|
31294
|
-
tags,
|
|
31295
|
-
sessionType,
|
|
31296
|
-
plan,
|
|
31297
|
-
discussions,
|
|
31298
|
-
errors,
|
|
31299
|
-
handoff,
|
|
31300
|
-
references
|
|
31301
|
-
} = params;
|
|
31302
|
-
const projectPath = getProjectPath();
|
|
31303
|
-
const sessionsDir = path8.join(projectPath, ".mneme", "sessions");
|
|
31304
|
-
const shortId = claudeSessionId.slice(0, 8);
|
|
31305
|
-
let sessionFile = null;
|
|
31306
|
-
const searchDir = (dir) => {
|
|
31307
|
-
if (!fs7.existsSync(dir)) return null;
|
|
31308
|
-
for (const entry of fs7.readdirSync(dir, { withFileTypes: true })) {
|
|
31309
|
-
const fullPath = path8.join(dir, entry.name);
|
|
31310
|
-
if (entry.isDirectory()) {
|
|
31311
|
-
const result = searchDir(fullPath);
|
|
31312
|
-
if (result) return result;
|
|
31313
|
-
} else if (entry.name === `${shortId}.json`) {
|
|
31314
|
-
return fullPath;
|
|
31315
|
-
}
|
|
31316
|
-
}
|
|
31317
|
-
return null;
|
|
31318
|
-
};
|
|
31319
|
-
sessionFile = searchDir(sessionsDir);
|
|
31320
|
-
if (sessionFile) {
|
|
31321
|
-
const existingData = readJsonFile(sessionFile);
|
|
31322
|
-
if (existingData?.sessionId && existingData.sessionId !== claudeSessionId) {
|
|
31323
|
-
sessionFile = null;
|
|
31324
|
-
}
|
|
31325
|
-
}
|
|
31326
|
-
if (!sessionFile) {
|
|
31327
|
-
const now = /* @__PURE__ */ new Date();
|
|
31328
|
-
const yearMonth = path8.join(
|
|
31329
|
-
sessionsDir,
|
|
31330
|
-
String(now.getFullYear()),
|
|
31331
|
-
String(now.getMonth() + 1).padStart(2, "0")
|
|
31332
|
-
);
|
|
31333
|
-
if (!fs7.existsSync(yearMonth)) fs7.mkdirSync(yearMonth, { recursive: true });
|
|
31334
|
-
sessionFile = path8.join(yearMonth, `${shortId}.json`);
|
|
31335
|
-
const initial = {
|
|
31336
|
-
id: shortId,
|
|
31337
|
-
sessionId: claudeSessionId,
|
|
31338
|
-
createdAt: now.toISOString(),
|
|
31339
|
-
title: "",
|
|
31340
|
-
tags: [],
|
|
31341
|
-
context: {
|
|
31342
|
-
projectDir: projectPath,
|
|
31343
|
-
projectName: path8.basename(projectPath)
|
|
31344
|
-
},
|
|
31345
|
-
metrics: {
|
|
31346
|
-
userMessages: 0,
|
|
31347
|
-
assistantResponses: 0,
|
|
31348
|
-
thinkingBlocks: 0,
|
|
31349
|
-
toolUsage: []
|
|
31350
|
-
},
|
|
31351
|
-
files: [],
|
|
31352
|
-
status: null
|
|
31353
|
-
};
|
|
31354
|
-
fs7.writeFileSync(sessionFile, JSON.stringify(initial, null, 2));
|
|
31355
|
-
}
|
|
31356
|
-
const data = readJsonFile(sessionFile) ?? {};
|
|
31357
|
-
data.title = title;
|
|
31358
|
-
data.summary = summary;
|
|
31359
|
-
data.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
31360
|
-
if (tags) data.tags = tags;
|
|
31361
|
-
if (sessionType) data.sessionType = sessionType;
|
|
31362
|
-
if (plan) data.plan = plan;
|
|
31363
|
-
if (discussions && discussions.length > 0) data.discussions = discussions;
|
|
31364
|
-
if (errors && errors.length > 0) data.errors = errors;
|
|
31365
|
-
if (handoff) data.handoff = handoff;
|
|
31366
|
-
if (references && references.length > 0) data.references = references;
|
|
31367
|
-
const transcriptPath = getTranscriptPath(claudeSessionId);
|
|
31368
|
-
if (transcriptPath) {
|
|
31369
|
-
try {
|
|
31370
|
-
const parsed = await parseTranscript(transcriptPath);
|
|
31371
|
-
data.metrics = {
|
|
31372
|
-
userMessages: parsed.metrics.userMessages,
|
|
31373
|
-
assistantResponses: parsed.metrics.assistantResponses,
|
|
31374
|
-
thinkingBlocks: parsed.metrics.thinkingBlocks,
|
|
31375
|
-
toolUsage: parsed.toolUsage
|
|
31376
|
-
};
|
|
31377
|
-
if (parsed.files.length > 0) data.files = parsed.files;
|
|
31378
|
-
} catch {
|
|
31379
|
-
}
|
|
31380
|
-
}
|
|
31381
|
-
const ctx = data.context;
|
|
31382
|
-
if (ctx && !ctx.repository) {
|
|
31383
|
-
try {
|
|
31384
|
-
const { execSync } = await import("node:child_process");
|
|
31385
|
-
const cwd = ctx.projectDir || projectPath;
|
|
31386
|
-
const git = (cmd) => execSync(cmd, { encoding: "utf8", cwd }).trim();
|
|
31387
|
-
const branch = git("git rev-parse --abbrev-ref HEAD");
|
|
31388
|
-
if (branch) ctx.branch = branch;
|
|
31389
|
-
const remoteUrl = git("git remote get-url origin");
|
|
31390
|
-
const repoMatch = remoteUrl.match(/[:/]([^/]+\/[^/]+?)(\.git)?$/);
|
|
31391
|
-
if (repoMatch) ctx.repository = repoMatch[1].replace(/\.git$/, "");
|
|
31392
|
-
const userName = git("git config user.name");
|
|
31393
|
-
const userEmail = git("git config user.email");
|
|
31394
|
-
if (userName)
|
|
31395
|
-
ctx.user = {
|
|
31396
|
-
name: userName,
|
|
31397
|
-
...userEmail ? { email: userEmail } : {}
|
|
31398
|
-
};
|
|
31399
|
-
} catch {
|
|
31400
|
-
}
|
|
31401
|
-
}
|
|
31402
|
-
fs7.writeFileSync(sessionFile, JSON.stringify(data, null, 2));
|
|
31403
|
-
markSessionCommitted(claudeSessionId);
|
|
31404
|
-
return {
|
|
31405
|
-
success: true,
|
|
31406
|
-
sessionFile: sessionFile.replace(projectPath, "."),
|
|
31407
|
-
shortId
|
|
31408
|
-
};
|
|
31409
|
-
}
|
|
31410
|
-
function registerSessionSummaryTool(server2) {
|
|
31411
|
-
server2.registerTool(
|
|
31412
|
-
"mneme_update_session_summary",
|
|
31413
|
-
{
|
|
31414
|
-
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).",
|
|
31415
|
-
inputSchema: {
|
|
31416
|
-
claudeSessionId: external_exports3.string().min(8).describe("Full Claude Code session UUID (36 chars)"),
|
|
31417
|
-
title: external_exports3.string().describe("Session title"),
|
|
31418
|
-
summary: external_exports3.object({
|
|
31419
|
-
goal: external_exports3.string().describe("What the session aimed to accomplish"),
|
|
31420
|
-
outcome: external_exports3.string().describe("What was actually accomplished"),
|
|
31421
|
-
description: external_exports3.string().optional().describe("Detailed description of the session")
|
|
31422
|
-
}).describe("Session summary object"),
|
|
31423
|
-
tags: external_exports3.array(external_exports3.string()).optional().describe("Semantic tags for the session"),
|
|
31424
|
-
sessionType: external_exports3.string().optional().describe(
|
|
31425
|
-
"Session type (e.g. implementation, research, bugfix, refactor)"
|
|
31426
|
-
),
|
|
31427
|
-
plan: external_exports3.object({
|
|
31428
|
-
goals: external_exports3.array(external_exports3.string()).optional().describe("Session goals"),
|
|
31429
|
-
tasks: external_exports3.array(external_exports3.string()).optional().describe('Task list (prefix with "[x] " for completed)'),
|
|
31430
|
-
remaining: external_exports3.array(external_exports3.string()).optional().describe("Remaining tasks")
|
|
31431
|
-
}).optional().describe("Session plan with tasks and progress"),
|
|
31432
|
-
discussions: external_exports3.array(
|
|
31433
|
-
external_exports3.object({
|
|
31434
|
-
topic: external_exports3.string().describe("Discussion topic"),
|
|
31435
|
-
decision: external_exports3.string().describe("Final decision"),
|
|
31436
|
-
reasoning: external_exports3.string().optional().describe("Reasoning"),
|
|
31437
|
-
alternatives: external_exports3.array(external_exports3.string()).optional().describe("Considered alternatives")
|
|
31438
|
-
})
|
|
31439
|
-
).optional().describe("Design discussions and decisions made during session"),
|
|
31440
|
-
errors: external_exports3.array(
|
|
31441
|
-
external_exports3.object({
|
|
31442
|
-
error: external_exports3.string().describe("Error message or description"),
|
|
31443
|
-
context: external_exports3.string().optional().describe("Where/when it occurred"),
|
|
31444
|
-
solution: external_exports3.string().optional().describe("How it was resolved"),
|
|
31445
|
-
files: external_exports3.array(external_exports3.string()).optional().describe("Related file paths")
|
|
31446
|
-
})
|
|
31447
|
-
).optional().describe("Errors encountered and their solutions"),
|
|
31448
|
-
handoff: external_exports3.object({
|
|
31449
|
-
stoppedReason: external_exports3.string().optional().describe("Why the session stopped"),
|
|
31450
|
-
notes: external_exports3.array(external_exports3.string()).optional().describe("Important notes for next session"),
|
|
31451
|
-
nextSteps: external_exports3.array(external_exports3.string()).optional().describe("What to do next")
|
|
31452
|
-
}).optional().describe("Handoff context for session continuity"),
|
|
31453
|
-
references: external_exports3.array(
|
|
31454
|
-
external_exports3.object({
|
|
31455
|
-
type: external_exports3.string().optional().describe('Reference type: "doc", "file", "url"'),
|
|
31456
|
-
url: external_exports3.string().optional().describe("URL if external"),
|
|
31457
|
-
path: external_exports3.string().optional().describe("File path if local"),
|
|
31458
|
-
title: external_exports3.string().optional().describe("Title or label"),
|
|
31459
|
-
description: external_exports3.string().optional().describe("Brief description")
|
|
31460
|
-
})
|
|
31461
|
-
).optional().describe("Documents and resources referenced during session")
|
|
31462
|
-
}
|
|
31463
|
-
},
|
|
31464
|
-
async (params) => {
|
|
31465
|
-
if (!params.claudeSessionId.trim())
|
|
31466
|
-
return fail("claudeSessionId must not be empty.");
|
|
31467
|
-
const result = await updateSessionSummary(params);
|
|
31468
|
-
return ok(JSON.stringify(result, null, 2));
|
|
31469
|
-
}
|
|
31470
|
-
);
|
|
31471
|
-
}
|
|
31472
|
-
|
|
31473
31473
|
// servers/db-server.ts
|
|
31474
31474
|
var server = new McpServer({
|
|
31475
31475
|
name: "mneme-db",
|
|
@@ -31478,7 +31478,7 @@ var server = new McpServer({
|
|
|
31478
31478
|
server.registerTool(
|
|
31479
31479
|
"mneme_list_projects",
|
|
31480
31480
|
{
|
|
31481
|
-
description: "List all projects
|
|
31481
|
+
description: "List all projects with session counts and last activity",
|
|
31482
31482
|
inputSchema: {}
|
|
31483
31483
|
},
|
|
31484
31484
|
async () => {
|
|
@@ -31529,7 +31529,7 @@ server.registerTool(
|
|
|
31529
31529
|
server.registerTool(
|
|
31530
31530
|
"mneme_stats",
|
|
31531
31531
|
{
|
|
31532
|
-
description: "Get statistics
|
|
31532
|
+
description: "Get statistics: total counts, per-project breakdown, recent activity",
|
|
31533
31533
|
inputSchema: {}
|
|
31534
31534
|
},
|
|
31535
31535
|
async () => {
|
|
@@ -31541,7 +31541,7 @@ server.registerTool(
|
|
|
31541
31541
|
server.registerTool(
|
|
31542
31542
|
"mneme_cross_project_search",
|
|
31543
31543
|
{
|
|
31544
|
-
description: "Search interactions across
|
|
31544
|
+
description: "Search interactions across all projects via FTS5.",
|
|
31545
31545
|
inputSchema: {
|
|
31546
31546
|
query: external_exports3.string().max(QUERY_MAX_LENGTH).describe("Search query"),
|
|
31547
31547
|
limit: external_exports3.number().int().min(LIST_LIMIT_MIN).max(LIST_LIMIT_MAX).optional().describe(
|
|
@@ -31561,7 +31561,7 @@ server.registerTool(
|
|
|
31561
31561
|
server.registerTool(
|
|
31562
31562
|
"mneme_save_interactions",
|
|
31563
31563
|
{
|
|
31564
|
-
description: "Save
|
|
31564
|
+
description: "Save transcript interactions to SQLite for /mneme:save.",
|
|
31565
31565
|
inputSchema: {
|
|
31566
31566
|
claudeSessionId: external_exports3.string().min(8).describe("Full Claude Code session UUID (36 chars)"),
|
|
31567
31567
|
mnemeSessionId: external_exports3.string().optional().describe(
|