@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.
@@ -29947,11 +29947,11 @@ var StdioServerTransport = class {
29947
29947
  }
29948
29948
  };
29949
29949
 
29950
- // servers/db-utils.ts
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-types.ts
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-utils.ts
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-queries.ts
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-save.ts
30238
+ // servers/db/save.ts
30239
30239
  import * as os2 from "node:os";
30240
30240
 
30241
- // servers/db-transcript.ts
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-save.ts
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-benchmark.ts
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-core.ts
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-helpers.ts
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/fuzzy-search.ts
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 = fs3.readFileSync(filePath, "utf-8");
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 (!fs3.existsSync(dir)) return results;
30609
- const items = fs3.readdirSync(dir, { withFileTypes: true });
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 = path3.join(dir, item.name);
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-search.ts
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 = path4.join(mnemeDir, "tags.json");
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 = path4.join(mnemeDir, "sessions");
30688
- if (fs4.existsSync(sessionsDir)) {
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 || path4.basename(file2, ".json"),
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 = path4.join(mnemeDir, "decisions");
30712
- if (fs4.existsSync(decisionsDir)) {
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 || path4.basename(file2, ".json"),
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 = path4.join(mnemeDir, "patterns");
30736
- if (fs4.existsSync(patternsDir)) {
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: `${path4.basename(file2, ".json")}-${p.type || "unknown"}`,
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-search.js") || process.argv[1]?.endsWith("fuzzy-search.ts");
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-helpers.ts
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 = path5.join(mnemeDir, "tags.json");
30822
- if (!fs5.existsSync(tagsPath)) return null;
31008
+ const tagsPath = path6.join(mnemeDir, "tags.json");
31009
+ if (!fs6.existsSync(tagsPath)) return null;
30823
31010
  try {
30824
- return JSON.parse(fs5.readFileSync(tagsPath, "utf-8"));
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 (!fs5.existsSync(dir)) return;
30848
- const entries = fs5.readdirSync(dir, { withFileTypes: true });
31034
+ if (!fs6.existsSync(dir)) return;
31035
+ const entries = fs6.readdirSync(dir, { withFileTypes: true });
30849
31036
  for (const entry of entries) {
30850
- const fullPath = path5.join(dir, entry.name);
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-core.ts
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 = path6.join(mnemeDir, "sessions");
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
- fs6.readFileSync(filePath, "utf-8")
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-benchmark.ts
31220
+ // servers/db/benchmark.ts
31034
31221
  function runSearchBenchmark(limit) {
31035
- const queryPath = path7.join(
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-server-tools.ts
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 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.",
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 one session or a resume-chain using sessions metadata and interactions.",
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 (required fields, priority, clarity, duplicates).",
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 and emit regression summary. Intended for CI and save-time quality checks.",
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 tracked in mneme's local database with session counts and last activity",
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 across all projects: total counts, per-project breakdown, recent activity",
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 ALL projects (not just current). Uses FTS5 for fast full-text search.",
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 conversation interactions from Claude Code transcript to SQLite. Use this during /mneme:save to persist the conversation history. Reads the transcript file directly and extracts user/assistant messages.",
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(