@vheins/local-memory-mcp 0.9.5 → 0.9.8

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.
@@ -3,8 +3,8 @@ import { fileURLToPath } from "url";
3
3
  import path from "path";
4
4
  var __dirname = path.dirname(fileURLToPath(import.meta.url));
5
5
  var pkgVersion = "0.1.0";
6
- if ("0.9.5") {
7
- pkgVersion = "0.9.5";
6
+ if ("0.9.8") {
7
+ pkgVersion = "0.9.8";
8
8
  } else {
9
9
  let searchDir = __dirname;
10
10
  for (let i = 0; i < 5; i++) {
@@ -10,7 +10,7 @@ import {
10
10
  createFileSink,
11
11
  listResources,
12
12
  logger
13
- } from "../chunk-H2NOOJCN.js";
13
+ } from "../chunk-CSEGUZWJ.js";
14
14
 
15
15
  // src/dashboard/server.ts
16
16
  import express from "express";
@@ -55,7 +55,7 @@ import {
55
55
  toContextSlug,
56
56
  updateSessionFromInitialize,
57
57
  updateSessionRoots
58
- } from "../chunk-H2NOOJCN.js";
58
+ } from "../chunk-CSEGUZWJ.js";
59
59
 
60
60
  // src/mcp/server.ts
61
61
  import readline from "readline";
@@ -1901,10 +1901,63 @@ async function handleStandardStore(params, db2, vectors2) {
1901
1901
 
1902
1902
  // src/mcp/tools/standard.search.ts
1903
1903
  var HYBRID_WEIGHTS_STANDARD = {
1904
- similarity: 0.55,
1905
- vector: 0.35,
1906
- usage: 0.1
1904
+ similarity: 0.4,
1905
+ vector: 0.25,
1906
+ keyword: 0.3,
1907
+ usage: 0.05
1907
1908
  };
1909
+ function tokenizeSearchText(value) {
1910
+ return value.toLowerCase().split(/[^a-z0-9]+/g).map((token) => token.trim()).filter((token) => token.length >= 2);
1911
+ }
1912
+ function scoreKeywordRelevance(query, standard) {
1913
+ const queryTokens = Array.from(new Set(tokenizeSearchText(query)));
1914
+ if (queryTokens.length === 0) return 0;
1915
+ const titleText = standard.title.toLowerCase();
1916
+ const contextText = standard.context.toLowerCase();
1917
+ const tagText = standard.tags.join(" ").toLowerCase();
1918
+ const stackText = standard.stack.join(" ").toLowerCase();
1919
+ const contentText = standard.content.toLowerCase();
1920
+ const queryPhrase = query.trim().toLowerCase();
1921
+ let titleMatches = 0;
1922
+ let contextMatches = 0;
1923
+ let tagMatches = 0;
1924
+ let stackMatches = 0;
1925
+ let contentMatches = 0;
1926
+ for (const token of queryTokens) {
1927
+ if (titleText.includes(token)) titleMatches += 1;
1928
+ if (contextText.includes(token)) contextMatches += 1;
1929
+ if (tagText.includes(token)) tagMatches += 1;
1930
+ if (stackText.includes(token)) stackMatches += 1;
1931
+ if (contentText.includes(token)) contentMatches += 1;
1932
+ }
1933
+ const titleCoverage = titleMatches / queryTokens.length;
1934
+ const contextCoverage = contextMatches / queryTokens.length;
1935
+ const tagCoverage = tagMatches / queryTokens.length;
1936
+ const stackCoverage = stackMatches / queryTokens.length;
1937
+ const contentCoverage = contentMatches / queryTokens.length;
1938
+ const exactPhraseBonus = queryPhrase.length >= 6 && (titleText.includes(queryPhrase) || contentText.includes(queryPhrase) || tagText.includes(queryPhrase)) ? 0.2 : 0;
1939
+ return Math.min(
1940
+ 1,
1941
+ titleCoverage * 0.45 + contextCoverage * 0.15 + tagCoverage * 0.15 + stackCoverage * 0.05 + contentCoverage * 0.2 + exactPhraseBonus
1942
+ );
1943
+ }
1944
+ function collectMatchedTerms(query, standard) {
1945
+ const queryTokens = Array.from(new Set(tokenizeSearchText(query)));
1946
+ if (queryTokens.length === 0) return [];
1947
+ const searchableFields = [
1948
+ standard.title.toLowerCase(),
1949
+ standard.context.toLowerCase(),
1950
+ standard.tags.join(" ").toLowerCase(),
1951
+ standard.stack.join(" ").toLowerCase(),
1952
+ standard.content.toLowerCase()
1953
+ ];
1954
+ return queryTokens.filter((token) => searchableFields.some((field) => field.includes(token)));
1955
+ }
1956
+ function toConfidence(finalScore, keywordScore) {
1957
+ if (finalScore >= 0.72 || keywordScore >= 0.85) return "high";
1958
+ if (finalScore >= 0.42 || keywordScore >= 0.45) return "medium";
1959
+ return "low";
1960
+ }
1908
1961
  async function handleStandardSearch(params, db2, vectors2) {
1909
1962
  const validated = StandardSearchSchema.parse(params);
1910
1963
  const searchQuery = expandQuery(validated.query || "", void 0);
@@ -1936,13 +1989,18 @@ async function handleStandardSearch(params, db2, vectors2) {
1936
1989
  if (similarityResults.length > 0) {
1937
1990
  scoredStandards = similarityResults.map((candidate) => {
1938
1991
  const vectorScore = vectorScoreMap.get(candidate.id) ?? 0;
1992
+ const keywordScore = scoreKeywordRelevance(validated.query || "", candidate);
1993
+ const matchedTerms = collectMatchedTerms(validated.query || "", candidate);
1939
1994
  const usageScore = Math.min(1, candidate.hit_count / 10);
1940
- const finalScore = candidate.similarity * HYBRID_WEIGHTS_STANDARD.similarity + vectorScore * HYBRID_WEIGHTS_STANDARD.vector + usageScore * HYBRID_WEIGHTS_STANDARD.usage;
1995
+ const finalScore = candidate.similarity * HYBRID_WEIGHTS_STANDARD.similarity + vectorScore * HYBRID_WEIGHTS_STANDARD.vector + keywordScore * HYBRID_WEIGHTS_STANDARD.keyword + usageScore * HYBRID_WEIGHTS_STANDARD.usage;
1941
1996
  return {
1942
1997
  standard: candidate,
1943
1998
  similarityScore: candidate.similarity,
1944
1999
  vectorScore,
1945
- finalScore
2000
+ keywordScore,
2001
+ matchedTerms,
2002
+ finalScore,
2003
+ confidence: toConfidence(finalScore, keywordScore)
1946
2004
  };
1947
2005
  });
1948
2006
  } else if (vectorResults.length > 0) {
@@ -1951,43 +2009,58 @@ async function handleStandardSearch(params, db2, vectors2) {
1951
2009
  scoredStandards = vectorResults.flatMap((row) => {
1952
2010
  const standard = standardMap.get(row.id);
1953
2011
  if (!standard) return [];
2012
+ const keywordScore = scoreKeywordRelevance(validated.query || "", standard);
2013
+ const matchedTerms = collectMatchedTerms(validated.query || "", standard);
1954
2014
  const usageScore = Math.min(1, standard.hit_count / 10);
2015
+ const finalScore = row.score * HYBRID_WEIGHTS_STANDARD.vector + keywordScore * HYBRID_WEIGHTS_STANDARD.keyword + usageScore * (1 - HYBRID_WEIGHTS_STANDARD.vector - HYBRID_WEIGHTS_STANDARD.keyword);
1955
2016
  return [
1956
2017
  {
1957
2018
  standard,
1958
2019
  similarityScore: 0,
1959
2020
  vectorScore: row.score,
1960
- finalScore: row.score * 0.9 + usageScore * 0.1
2021
+ keywordScore,
2022
+ matchedTerms,
2023
+ finalScore,
2024
+ confidence: toConfidence(finalScore, keywordScore)
1961
2025
  }
1962
2026
  ];
1963
2027
  });
1964
2028
  }
1965
2029
  } catch (error) {
1966
2030
  logger.warn("Standard vector search failed, using similarity only", { error: String(error) });
1967
- scoredStandards = similarityResults.map((candidate) => ({
1968
- standard: candidate,
1969
- similarityScore: candidate.similarity,
1970
- vectorScore: 0,
1971
- finalScore: candidate.similarity * 0.9 + Math.min(1, candidate.hit_count / 10) * 0.1
1972
- }));
2031
+ scoredStandards = similarityResults.map((candidate) => {
2032
+ const keywordScore = scoreKeywordRelevance(validated.query || "", candidate);
2033
+ const matchedTerms = collectMatchedTerms(validated.query || "", candidate);
2034
+ const finalScore = candidate.similarity * (HYBRID_WEIGHTS_STANDARD.similarity + HYBRID_WEIGHTS_STANDARD.vector * 0.5) + keywordScore * HYBRID_WEIGHTS_STANDARD.keyword + Math.min(1, candidate.hit_count / 10) * HYBRID_WEIGHTS_STANDARD.usage;
2035
+ return {
2036
+ standard: candidate,
2037
+ similarityScore: candidate.similarity,
2038
+ vectorScore: 0,
2039
+ keywordScore,
2040
+ matchedTerms,
2041
+ finalScore,
2042
+ confidence: toConfidence(finalScore, keywordScore)
2043
+ };
2044
+ });
1973
2045
  }
1974
2046
  scoredStandards.sort((a, b) => b.finalScore - a.finalScore);
1975
2047
  const threshold = scoredStandards.length <= 5 ? 0.08 : 0.2;
1976
- let results = scoredStandards.filter((candidate) => candidate.finalScore >= threshold).map((candidate) => candidate.standard);
2048
+ let results = scoredStandards.filter((candidate) => candidate.finalScore >= threshold);
1977
2049
  if (results.length === 0 && scoredStandards.length > 0) {
1978
- results = [scoredStandards[0].standard];
2050
+ results = [scoredStandards[0]];
1979
2051
  }
1980
2052
  const paginatedResults = results.slice(validated.offset, validated.offset + validated.limit);
1981
- db2.standards.incrementHitCounts(paginatedResults.map((standard) => standard.id));
2053
+ db2.standards.incrementHitCounts(paginatedResults.map(({ standard }) => standard.id));
1982
2054
  logger.info("[Tool] standard.search - searched coding standards", {
1983
2055
  resultCount: paginatedResults.length,
1984
2056
  stack: validated.stack,
1985
2057
  language: validated.language,
1986
2058
  context: validated.context,
1987
- version: validated.version
2059
+ version: validated.version,
2060
+ topConfidence: paginatedResults[0]?.confidence
1988
2061
  });
1989
- const COLUMNS = ["code", "id", "title", "context", "language", "scope", "tags", "updated_at"];
1990
- const rows = paginatedResults.map((standard) => [
2062
+ const COLUMNS = ["code", "id", "title", "context", "language", "scope", "tags", "confidence", "score", "matched_terms", "updated_at"];
2063
+ const rows = paginatedResults.map(({ standard, confidence, finalScore, matchedTerms }) => [
1991
2064
  standard.code ?? "-",
1992
2065
  standard.id,
1993
2066
  standard.title,
@@ -1995,18 +2068,21 @@ async function handleStandardSearch(params, db2, vectors2) {
1995
2068
  standard.language || "-",
1996
2069
  standard.is_global ? "global" : standard.repo || "-",
1997
2070
  standard.tags.join(", "),
2071
+ confidence,
2072
+ Number(finalScore.toFixed(3)),
2073
+ matchedTerms.join(", "),
1998
2074
  standard.updated_at
1999
2075
  ]);
2000
2076
  let contentSummary;
2001
2077
  if (paginatedResults.length > 0) {
2002
2078
  const parts = [
2003
2079
  "Standards:",
2004
- "- title|context|language|scope",
2080
+ "- code|confidence|matched_terms|title|context|language|scope",
2005
2081
  ...paginatedResults.map(
2006
- (standard) => `- ${standard.title}|${standard.context}|${standard.language || "-"}|${standard.is_global ? "global" : standard.repo || "-"}`
2082
+ ({ standard, confidence, matchedTerms }) => `- ${standard.code ?? "-"}|${confidence}|${matchedTerms.join(", ")}|${standard.title}|${standard.context}|${standard.language || "-"}|${standard.is_global ? "global" : standard.repo || "-"}`
2007
2083
  ),
2008
2084
  "",
2009
- "Use standard-detail with id or code for full content."
2085
+ "Use standard-detail with code for full content."
2010
2086
  ];
2011
2087
  contentSummary = parts.join("\n");
2012
2088
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vheins/local-memory-mcp",
3
- "version": "0.9.5",
3
+ "version": "0.9.8",
4
4
  "description": "MCP Local Memory Service for coding copilot agents",
5
5
  "mcpName": "io.github.vheins/local-memory-mcp",
6
6
  "type": "module",