claude-memory-layer 1.0.32 → 1.0.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mcp/index.js CHANGED
@@ -10308,12 +10308,12 @@ var tools = [
10308
10308
  projectPath: projectPathProperty,
10309
10309
  refreshLatest: {
10310
10310
  type: "boolean",
10311
- description: "Explicit opt-in: import latest local session history before retrieval. Requires absolute projectPath and mutates project memory (default: false)."
10311
+ description: "Import latest local session history before retrieval. Generic continuation queries auto-refresh when absolute projectPath is supplied and no sessionId filter is set; set false to opt out. Auto-refresh and explicit true both mutate project memory; explicit true also requires absolute projectPath."
10312
10312
  },
10313
10313
  refreshSources: {
10314
10314
  type: "array",
10315
10315
  items: { type: "string", enum: ["claude", "codex", "hermes"] },
10316
- description: "Sources to refresh when refreshLatest is true (default: hermes and codex)"
10316
+ description: "Sources to refresh when refreshLatest is true or auto-refresh runs (default: hermes and codex)"
10317
10317
  },
10318
10318
  refreshSessionLimit: {
10319
10319
  type: "number",
@@ -12667,10 +12667,15 @@ function sqliteClose(db) {
12667
12667
  function toDateFromSQLite(value) {
12668
12668
  if (value instanceof Date)
12669
12669
  return value;
12670
- if (typeof value === "string")
12671
- return new Date(value);
12672
12670
  if (typeof value === "number")
12673
12671
  return new Date(value);
12672
+ if (typeof value === "string") {
12673
+ const trimmed = value.trim();
12674
+ if (/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?$/.test(trimmed)) {
12675
+ return /* @__PURE__ */ new Date(trimmed.replace(" ", "T") + "Z");
12676
+ }
12677
+ return new Date(trimmed);
12678
+ }
12674
12679
  return new Date(String(value));
12675
12680
  }
12676
12681
  function toSQLiteTimestamp(date) {
@@ -12736,6 +12741,13 @@ var MarkdownMirror2 = class {
12736
12741
  };
12737
12742
 
12738
12743
  // src/core/sqlite-event-store.ts
12744
+ function normalizeQueryRewriteKind(value) {
12745
+ const normalized = (value || "").trim().toLowerCase();
12746
+ if (normalized === "follow-up-context" || normalized === "intent-rewrite")
12747
+ return normalized;
12748
+ return "none";
12749
+ }
12750
+ var REWRITTEN_QUERY_REWRITE_KIND_SQL = `LOWER(TRIM(COALESCE(query_rewrite_kind, 'none'))) IN ('follow-up-context', 'intent-rewrite')`;
12739
12751
  var SQLiteEventStore = class {
12740
12752
  db;
12741
12753
  initialized = false;
@@ -12996,6 +13008,8 @@ var SQLiteEventStore = class {
12996
13008
  session_id TEXT,
12997
13009
  project_hash TEXT,
12998
13010
  query_text TEXT NOT NULL,
13011
+ raw_query_text TEXT,
13012
+ query_rewrite_kind TEXT,
12999
13013
  strategy TEXT,
13000
13014
  candidate_event_ids TEXT,
13001
13015
  selected_event_ids TEXT,
@@ -13037,6 +13051,8 @@ var SQLiteEventStore = class {
13037
13051
  CREATE INDEX IF NOT EXISTS idx_helpfulness_event ON memory_helpfulness(event_id);
13038
13052
  CREATE INDEX IF NOT EXISTS idx_helpfulness_session ON memory_helpfulness(session_id);
13039
13053
  CREATE INDEX IF NOT EXISTS idx_helpfulness_score ON memory_helpfulness(helpfulness_score DESC);
13054
+ CREATE INDEX IF NOT EXISTS idx_helpfulness_created_at ON memory_helpfulness(created_at);
13055
+ CREATE INDEX IF NOT EXISTS idx_helpfulness_measured_at ON memory_helpfulness(measured_at);
13040
13056
  CREATE INDEX IF NOT EXISTS idx_retrieval_traces_created_at ON retrieval_traces(created_at DESC);
13041
13057
  CREATE INDEX IF NOT EXISTS idx_retrieval_traces_project_hash ON retrieval_traces(project_hash);
13042
13058
  CREATE INDEX IF NOT EXISTS idx_retrieval_traces_session_id ON retrieval_traces(session_id);
@@ -13070,6 +13086,18 @@ var SQLiteEventStore = class {
13070
13086
  sqliteExec(this.db, `ALTER TABLE retrieval_traces ADD COLUMN candidate_details_json TEXT;`);
13071
13087
  } catch {
13072
13088
  }
13089
+ try {
13090
+ sqliteExec(this.db, `ALTER TABLE retrieval_traces ADD COLUMN raw_query_text TEXT;`);
13091
+ } catch {
13092
+ }
13093
+ try {
13094
+ sqliteExec(this.db, `ALTER TABLE retrieval_traces ADD COLUMN query_rewrite_kind TEXT;`);
13095
+ } catch {
13096
+ }
13097
+ try {
13098
+ sqliteExec(this.db, `CREATE INDEX IF NOT EXISTS idx_retrieval_traces_query_rewrite_kind ON retrieval_traces(query_rewrite_kind);`);
13099
+ } catch {
13100
+ }
13073
13101
  const tableInfo = sqliteAll(this.db, "PRAGMA table_info(events)", []);
13074
13102
  const columnNames = tableInfo.map((col) => col.name);
13075
13103
  if (!columnNames.includes("access_count")) {
@@ -13855,8 +13883,11 @@ var SQLiteEventStore = class {
13855
13883
  /**
13856
13884
  * Get helpfulness statistics for dashboard
13857
13885
  */
13858
- async getHelpfulnessStats() {
13886
+ async getHelpfulnessStats(since) {
13859
13887
  await this.initialize();
13888
+ const sinceIso = since?.toISOString();
13889
+ const evaluatedWhere = sinceIso ? `WHERE measured_at IS NOT NULL AND datetime(created_at) >= datetime(?)` : `WHERE measured_at IS NOT NULL`;
13890
+ const totalWhere = sinceIso ? `WHERE datetime(created_at) >= datetime(?)` : ``;
13860
13891
  const stats = sqliteGet(
13861
13892
  this.db,
13862
13893
  `SELECT
@@ -13866,11 +13897,13 @@ var SQLiteEventStore = class {
13866
13897
  SUM(CASE WHEN helpfulness_score >= 0.4 AND helpfulness_score < 0.7 THEN 1 ELSE 0 END) as neutral,
13867
13898
  SUM(CASE WHEN helpfulness_score < 0.4 THEN 1 ELSE 0 END) as unhelpful
13868
13899
  FROM memory_helpfulness
13869
- WHERE measured_at IS NOT NULL`
13900
+ ${evaluatedWhere}`,
13901
+ sinceIso ? [sinceIso] : []
13870
13902
  );
13871
13903
  const totalRow = sqliteGet(
13872
13904
  this.db,
13873
- `SELECT COUNT(*) as total FROM memory_helpfulness`
13905
+ `SELECT COUNT(*) as total FROM memory_helpfulness ${totalWhere}`,
13906
+ sinceIso ? [sinceIso] : []
13874
13907
  );
13875
13908
  return {
13876
13909
  avgScore: Math.round((stats?.avg_score || 0) * 100) / 100,
@@ -13969,18 +14002,21 @@ var SQLiteEventStore = class {
13969
14002
  async recordRetrievalTrace(input) {
13970
14003
  await this.initialize();
13971
14004
  const traceId = randomUUID5();
14005
+ const queryRewriteKind = normalizeQueryRewriteKind(input.queryRewriteKind);
13972
14006
  sqliteRun(
13973
14007
  this.db,
13974
14008
  `INSERT INTO retrieval_traces (
13975
- trace_id, session_id, project_hash, query_text, strategy,
14009
+ trace_id, session_id, project_hash, query_text, raw_query_text, query_rewrite_kind, strategy,
13976
14010
  candidate_event_ids, selected_event_ids, candidate_details_json, selected_details_json,
13977
14011
  candidate_count, selected_count, confidence, fallback_trace
13978
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
14012
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
13979
14013
  [
13980
14014
  traceId,
13981
14015
  input.sessionId || null,
13982
14016
  input.projectHash || null,
13983
14017
  input.queryText,
14018
+ input.rawQueryText || null,
14019
+ queryRewriteKind,
13984
14020
  input.strategy || null,
13985
14021
  JSON.stringify(input.candidateEventIds || []),
13986
14022
  JSON.stringify(input.selectedEventIds || []),
@@ -14006,6 +14042,8 @@ var SQLiteEventStore = class {
14006
14042
  sessionId: row.session_id || void 0,
14007
14043
  projectHash: row.project_hash || void 0,
14008
14044
  queryText: row.query_text,
14045
+ rawQueryText: row.raw_query_text || void 0,
14046
+ queryRewriteKind: normalizeQueryRewriteKind(row.query_rewrite_kind),
14009
14047
  strategy: row.strategy || void 0,
14010
14048
  candidateEventIds: row.candidate_event_ids ? JSON.parse(row.candidate_event_ids) : [],
14011
14049
  selectedEventIds: row.selected_event_ids ? JSON.parse(row.selected_event_ids) : [],
@@ -14032,6 +14070,11 @@ var SQLiteEventStore = class {
14032
14070
  COUNT(*) as total_queries,
14033
14071
  AVG(candidate_count) as avg_candidate_count,
14034
14072
  AVG(selected_count) as avg_selected_count,
14073
+ SUM(CASE WHEN ${REWRITTEN_QUERY_REWRITE_KIND_SQL} THEN 1 ELSE 0 END) as rewritten_queries,
14074
+ SUM(CASE WHEN ${REWRITTEN_QUERY_REWRITE_KIND_SQL} AND selected_count > 0 THEN 1 ELSE 0 END) as rewritten_queries_with_selection,
14075
+ SUM(CASE WHEN NOT (${REWRITTEN_QUERY_REWRITE_KIND_SQL}) AND selected_count > 0 THEN 1 ELSE 0 END) as raw_queries_with_selection,
14076
+ AVG(CASE WHEN ${REWRITTEN_QUERY_REWRITE_KIND_SQL} THEN selected_count END) as avg_selected_count_for_rewritten_queries,
14077
+ AVG(CASE WHEN NOT (${REWRITTEN_QUERY_REWRITE_KIND_SQL}) THEN selected_count END) as avg_selected_count_for_raw_queries,
14035
14078
  CASE
14036
14079
  WHEN SUM(candidate_count) > 0 THEN (SUM(selected_count) * 1.0 / SUM(candidate_count))
14037
14080
  ELSE 0
@@ -14039,15 +14082,41 @@ var SQLiteEventStore = class {
14039
14082
  FROM retrieval_traces`,
14040
14083
  []
14041
14084
  );
14085
+ const totalQueries = Number(row?.total_queries || 0);
14086
+ const rewrittenQueries = Number(row?.rewritten_queries || 0);
14087
+ const rawQueries = Math.max(0, totalQueries - rewrittenQueries);
14088
+ const rewrittenQueriesWithSelection = Number(row?.rewritten_queries_with_selection || 0);
14089
+ const rawQueriesWithSelection = Number(row?.raw_queries_with_selection || 0);
14042
14090
  return {
14043
- totalQueries: Number(row?.total_queries || 0),
14091
+ totalQueries,
14044
14092
  avgCandidateCount: Number(row?.avg_candidate_count || 0),
14045
14093
  avgSelectedCount: Number(row?.avg_selected_count || 0),
14046
- selectionRate: Number(row?.selection_rate || 0)
14094
+ selectionRate: Number(row?.selection_rate || 0),
14095
+ rewrittenQueries,
14096
+ rewriteRate: totalQueries > 0 ? rewrittenQueries / totalQueries : 0,
14097
+ rewrittenQueriesWithSelection,
14098
+ rawQueriesWithSelection,
14099
+ rewrittenSelectionRate: rewrittenQueries > 0 ? rewrittenQueriesWithSelection / rewrittenQueries : 0,
14100
+ rawSelectionRate: rawQueries > 0 ? rawQueriesWithSelection / rawQueries : 0,
14101
+ avgSelectedCountForRewrittenQueries: Number(row?.avg_selected_count_for_rewritten_queries || 0),
14102
+ avgSelectedCountForRawQueries: Number(row?.avg_selected_count_for_raw_queries || 0)
14047
14103
  };
14048
14104
  } catch (err) {
14049
14105
  if (err?.message?.includes("no such table")) {
14050
- return { totalQueries: 0, avgCandidateCount: 0, avgSelectedCount: 0, selectionRate: 0 };
14106
+ return {
14107
+ totalQueries: 0,
14108
+ avgCandidateCount: 0,
14109
+ avgSelectedCount: 0,
14110
+ selectionRate: 0,
14111
+ rewrittenQueries: 0,
14112
+ rewriteRate: 0,
14113
+ rewrittenQueriesWithSelection: 0,
14114
+ rawQueriesWithSelection: 0,
14115
+ rewrittenSelectionRate: 0,
14116
+ rawSelectionRate: 0,
14117
+ avgSelectedCountForRewrittenQueries: 0,
14118
+ avgSelectedCountForRawQueries: 0
14119
+ };
14051
14120
  }
14052
14121
  throw err;
14053
14122
  }
@@ -14869,8 +14938,55 @@ var LOW_SIGNAL_CONTEXT_PATTERNS = [
14869
14938
  ];
14870
14939
  var CONTINUATION_QUERY_PATTERNS = [
14871
14940
  /^\s*(?:continue|resume|next|what(?:'s| is)? next|next\s+(?:step|task|action)|recommended\s+(?:next\s+)?(?:step|task|action)|what should (?:we|i) do next)\??\s*$/i,
14872
- /^\s*(?:이어서(?:\s*진행해줘)?|계속(?:\s*해줘)?|다음\s*(?:단계|작업|추천\s*작업|추천|할\s*일)?(?:은|는)?(?:\s*뭐야)?\??|추천\s*작업(?:은|는)?(?:\s*뭐야)?\??|진행해줘)\s*$/i
14941
+ /^\s*(?:응\s*)?(?:이어서(?:\s*진행(?:해줘)?)?|계속(?:\s*해줘)?|다음\s*(?:단계|작업|추천\s*작업|추천|할\s*일)?(?:은|는)?(?:\s*(?:뭐야|진행(?:해줘)?))?\??|남은\s*(?:추가(?:로)?\s*)?(?:(?:할\s*만한\s*)?(?:작업|일)|할\s*일)?(?:은|는)?\s*(?:있어|있나|있나요|뭐야)\??|추천\s*작업(?:은|는)?(?:\s*뭐야)?\??|진행해줘)\s*$/i
14942
+ ];
14943
+ var SHORT_REPAIR_FOLLOW_UP_PATTERNS = [
14944
+ /^\s*(?:fix\s+(?:it|that)|repair\s+(?:it|that)|resolve\s+(?:it|that)|that\s+bug|same\s+issue)\s*$/i,
14945
+ /^\s*(?:그거|그것|이거|이것)?\s*(?:고쳐줘|수정해줘|해결해줘|처리해줘)\s*$/i
14946
+ ];
14947
+ var CURRENT_STATE_QUERY_PATTERNS = [
14948
+ /\bcurrent\b.*\b(?:state|status|deployment|blocker|pr|pull request)\b/i,
14949
+ /\b(?:still|as current|current)\b.*\b(?:unresolved|open|pending|not completed)\b/i,
14950
+ /\b(?:old|obsolete|stale|resolved|already resolved)\b.*\b(?:current|still|unresolved|open|state|status)\b/i,
14951
+ /(?:현재|아직|이전|오래된|해결된).*(?:상태|미해결|열린|블로커|PR|풀리퀘스트)/i
14873
14952
  ];
14953
+ var STALE_CONTENT_PATTERNS = [
14954
+ /\b(?:obsolete|superseded|outdated)\b/i,
14955
+ /\bstale\s+(?:operational\s+)?state\b/i,
14956
+ /\bstale\s+after\b/i,
14957
+ /\bno\s+longer\s+(?:valid|current|applies?)\b/i,
14958
+ /\bearlier\s+(?:pull request|pr)\b[\s\S]{0,160}\b(?:open|not completed|had not completed)\b/i,
14959
+ /\bshould\s+not\s+be\s+injected\s+as\s+current\s+context\b/i,
14960
+ /(?:오래된|더 이상 유효하지|현재 상태가 아님)/i
14961
+ ];
14962
+ var CONTINUATION_EXPANSION = "current next step plan roadmap status validation replay rerank memory usefulness continuation";
14963
+ var REPAIR_FOLLOW_UP_EXPANSION = "review blocker fix pattern dashboard error state metrics bucket validation sanitize rerun unresolved";
14964
+ var RETRIEVAL_PRIVACY_DECISION_EXPANSION = "retrieval telemetry privacy public api dashboard dashboards rawQueryText queryText raw query text expose safe trace metadata trace id reason strategy rewrite kind aggregate count counts candidate selected public panel";
14965
+ var DECISION_RECALL_TERMS = /* @__PURE__ */ new Set([
14966
+ "decide",
14967
+ "decided",
14968
+ "decision",
14969
+ "agreed",
14970
+ "policy",
14971
+ "constraint"
14972
+ ]);
14973
+ var RETRIEVAL_PRIVACY_SURFACE_TERMS = /* @__PURE__ */ new Set([
14974
+ "retrieval",
14975
+ "dashboard",
14976
+ "telemetry",
14977
+ "trace"
14978
+ ]);
14979
+ var DECISION_TOPIC_WEAK_TERMS = /* @__PURE__ */ new Set([
14980
+ "api",
14981
+ "dashboard",
14982
+ "retrieval",
14983
+ "trace",
14984
+ "telemetry",
14985
+ "query",
14986
+ "raw",
14987
+ "count",
14988
+ "counts"
14989
+ ]);
14874
14990
  var GENERIC_TECHNICAL_TERMS = /* @__PURE__ */ new Set([
14875
14991
  "api",
14876
14992
  "cli",
@@ -14888,6 +15004,87 @@ var GENERIC_TECHNICAL_TERMS = /* @__PURE__ */ new Set([
14888
15004
  "db",
14889
15005
  "sql"
14890
15006
  ]);
15007
+ var LOW_INFORMATION_QUERY_TERMS = /* @__PURE__ */ new Set([
15008
+ "the",
15009
+ "and",
15010
+ "or",
15011
+ "for",
15012
+ "from",
15013
+ "with",
15014
+ "without",
15015
+ "about",
15016
+ "what",
15017
+ "when",
15018
+ "where",
15019
+ "which",
15020
+ "who",
15021
+ "why",
15022
+ "how",
15023
+ "did",
15024
+ "does",
15025
+ "do",
15026
+ "we",
15027
+ "i",
15028
+ "in",
15029
+ "to",
15030
+ "of",
15031
+ "on",
15032
+ "as",
15033
+ "be",
15034
+ "was",
15035
+ "were",
15036
+ "decide",
15037
+ "decided",
15038
+ "decision",
15039
+ "agreed",
15040
+ "policy",
15041
+ "constraint",
15042
+ "showing",
15043
+ "can",
15044
+ "you",
15045
+ "me",
15046
+ "show",
15047
+ "tell",
15048
+ "please",
15049
+ "should",
15050
+ "would",
15051
+ "could",
15052
+ "this",
15053
+ "that",
15054
+ "these",
15055
+ "those",
15056
+ "use",
15057
+ "using",
15058
+ "treat",
15059
+ "continue",
15060
+ "resume",
15061
+ "next",
15062
+ "step",
15063
+ "task",
15064
+ "action",
15065
+ "current",
15066
+ "state",
15067
+ "status",
15068
+ "old",
15069
+ "already",
15070
+ "still",
15071
+ "near",
15072
+ "today",
15073
+ "\uC751",
15074
+ "\uADF8\uAC70",
15075
+ "\uADF8\uAC83",
15076
+ "\uC774\uAC70",
15077
+ "\uC774\uAC83",
15078
+ "\uB2E4\uC74C",
15079
+ "\uB2E8\uACC4",
15080
+ "\uC9C4\uD589",
15081
+ "\uC9C4\uD589\uD574\uC918",
15082
+ "\uACC4\uC18D",
15083
+ "\uC774\uC5B4\uC11C",
15084
+ "\uACE0\uCCD0\uC918",
15085
+ "\uC218\uC815\uD574\uC918",
15086
+ "\uD574\uACB0\uD574\uC918"
15087
+ ]);
14891
15088
  function isCommandArtifactQuery(query) {
14892
15089
  const trimmed = query.trim();
14893
15090
  if (!trimmed)
@@ -14933,6 +15130,60 @@ function isGenericContinuationQuery(query) {
14933
15130
  return false;
14934
15131
  return !/[A-Za-z0-9_-]+\.[A-Za-z0-9]+/.test(trimmed) && !/(?:^|\s)(?:feat|fix|chore|refactor|docs)\/[A-Za-z0-9._-]+/.test(trimmed) && !/[A-Za-z]:?[\\/]|\/Users\/|\.\/|\.\.\//.test(trimmed);
14935
15132
  }
15133
+ function isShortRepairFollowUpQuery(query) {
15134
+ const trimmed = query.trim();
15135
+ if (!trimmed)
15136
+ return false;
15137
+ if (extractTechnicalQueryTerms(trimmed).length > 0)
15138
+ return false;
15139
+ const tokens = trimmed.match(/[A-Za-z0-9가-힣#._/-]+/g) ?? [];
15140
+ if (tokens.length > 8)
15141
+ return false;
15142
+ return SHORT_REPAIR_FOLLOW_UP_PATTERNS.some((pattern) => pattern.test(trimmed));
15143
+ }
15144
+ function isCurrentStateQuery(query) {
15145
+ const trimmed = query.trim();
15146
+ if (!trimmed)
15147
+ return false;
15148
+ return CURRENT_STATE_QUERY_PATTERNS.some((pattern) => pattern.test(trimmed));
15149
+ }
15150
+ function isStaleOrSupersededContent(content) {
15151
+ const trimmed = content.trim();
15152
+ if (!trimmed)
15153
+ return false;
15154
+ return STALE_CONTENT_PATTERNS.some((pattern) => pattern.test(trimmed));
15155
+ }
15156
+ function buildRetrievalQualityQuery(query) {
15157
+ const trimmed = query.trim();
15158
+ if (!trimmed)
15159
+ return query;
15160
+ if (isRetrievalPrivacyDecisionQuery(trimmed)) {
15161
+ return `${trimmed} ${RETRIEVAL_PRIVACY_DECISION_EXPANSION}`;
15162
+ }
15163
+ if (isGenericContinuationQuery(trimmed)) {
15164
+ return `${trimmed} ${CONTINUATION_EXPANSION}`;
15165
+ }
15166
+ if (isShortRepairFollowUpQuery(trimmed)) {
15167
+ return `${trimmed} ${REPAIR_FOLLOW_UP_EXPANSION}`;
15168
+ }
15169
+ return query;
15170
+ }
15171
+ function isRetrievalPrivacyDecisionQuery(query) {
15172
+ const trimmed = query.trim();
15173
+ if (!trimmed)
15174
+ return false;
15175
+ const terms = new Set(tokenizeQualityText(trimmed));
15176
+ const hasDecisionSignal = hasAnyTerm(terms, DECISION_RECALL_TERMS) || /(?:결정|정책|원칙)/i.test(trimmed);
15177
+ if (!hasDecisionSignal)
15178
+ return false;
15179
+ const hasRawQuerySignal = terms.has("raw") && terms.has("query");
15180
+ const hasPrivacySignal = terms.has("privacy") || terms.has("expose") || terms.has("redacted");
15181
+ const hasRetrievalSurface = hasAnyTerm(terms, RETRIEVAL_PRIVACY_SURFACE_TERMS) || terms.has("api") && terms.has("query");
15182
+ const hasQuerySurface = terms.has("query") && (terms.has("dashboard") || terms.has("trace") || terms.has("telemetry") || terms.has("api"));
15183
+ const hasKoreanRetrievalSurface = /(?:검색|리트리벌|retrieval|대시보드|트레이스|텔레메트리|telemetry)/i.test(trimmed);
15184
+ const hasKoreanPrivacySurface = /(?:원문|쿼리|프라이버시|개인정보|노출|트레이스|메타데이터)/i.test(trimmed);
15185
+ return (hasRetrievalSurface || hasKoreanRetrievalSurface && hasKoreanPrivacySurface) && (hasRawQuerySignal || hasPrivacySignal || hasQuerySurface || hasKoreanPrivacySurface);
15186
+ }
14936
15187
  function extractTechnicalQueryTerms(query) {
14937
15188
  const matches = query.match(/[A-Za-z][A-Za-z0-9_.:-]{2,}/g) ?? [];
14938
15189
  const terms = matches.filter((term) => {
@@ -14950,9 +15201,87 @@ function hasTechnicalTermOverlap(query, content) {
14950
15201
  const normalizedContent = content.toLowerCase();
14951
15202
  return terms.some((term) => normalizedContent.includes(term));
14952
15203
  }
15204
+ function hasDiscriminativeTermOverlap(query, content) {
15205
+ const queryTerms = extractDiscriminativeQueryTerms(query);
15206
+ const contentTerms = new Set(tokenizeQualityText(content));
15207
+ if (isRetrievalPrivacyDecisionQuery(query) && hasRetrievalPrivacyDecisionContent(contentTerms)) {
15208
+ return true;
15209
+ }
15210
+ if (shouldRequireDecisionTopicOverlap(query)) {
15211
+ const topicTerms = queryTerms.filter((term) => !DECISION_TOPIC_WEAK_TERMS.has(term));
15212
+ if (topicTerms.length > 0) {
15213
+ return topicTerms.some((term) => contentTerms.has(term));
15214
+ }
15215
+ }
15216
+ if (queryTerms.length < 3)
15217
+ return true;
15218
+ const requiredHits = queryTerms.length >= 3 ? 2 : 1;
15219
+ let hits = 0;
15220
+ for (const term of queryTerms) {
15221
+ if (contentTerms.has(term))
15222
+ hits += 1;
15223
+ if (hits >= requiredHits)
15224
+ return true;
15225
+ }
15226
+ return false;
15227
+ }
14953
15228
  function shouldApplyTechnicalGuard(query) {
14954
15229
  return extractTechnicalQueryTerms(query).length > 0;
14955
15230
  }
15231
+ function hasAnyTerm(terms, expectedTerms) {
15232
+ let found = false;
15233
+ expectedTerms.forEach((term) => {
15234
+ if (terms.has(term))
15235
+ found = true;
15236
+ });
15237
+ return found;
15238
+ }
15239
+ function shouldRequireDecisionTopicOverlap(query) {
15240
+ if (isRetrievalPrivacyDecisionQuery(query))
15241
+ return false;
15242
+ const trimmed = query.trim();
15243
+ if (!trimmed)
15244
+ return false;
15245
+ const terms = new Set(tokenizeQualityText(trimmed));
15246
+ return hasAnyTerm(terms, DECISION_RECALL_TERMS) || /(?:결정|정책|원칙)/i.test(trimmed);
15247
+ }
15248
+ function extractDiscriminativeQueryTerms(query) {
15249
+ const seen = /* @__PURE__ */ new Set();
15250
+ const terms = [];
15251
+ for (const token of tokenizeQualityText(query)) {
15252
+ if (LOW_INFORMATION_QUERY_TERMS.has(token))
15253
+ continue;
15254
+ if (GENERIC_TECHNICAL_TERMS.has(token))
15255
+ continue;
15256
+ if (seen.has(token))
15257
+ continue;
15258
+ seen.add(token);
15259
+ terms.push(token);
15260
+ }
15261
+ return terms;
15262
+ }
15263
+ function hasRetrievalPrivacyDecisionContent(contentTerms) {
15264
+ const hasDashboardTraceMetadata = contentTerms.has("dashboard") && (contentTerms.has("trace") || contentTerms.has("metadata")) && (contentTerms.has("safe") || contentTerms.has("strategy") || contentTerms.has("rewrite") || contentTerms.has("candidate") || contentTerms.has("selected") || contentTerms.has("count") || contentTerms.has("reason"));
15265
+ const hasRawQueryPrivacyPolicy = contentTerms.has("retrieval") && (contentTerms.has("privacy") || contentTerms.has("expose") || contentTerms.has("raw") && contentTerms.has("query") && (contentTerms.has("dashboard") || contentTerms.has("telemetry") || contentTerms.has("api") || contentTerms.has("public")));
15266
+ return hasDashboardTraceMetadata || hasRawQueryPrivacyPolicy;
15267
+ }
15268
+ function tokenizeQualityText(text) {
15269
+ return text.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase().replace(/[^A-Za-z0-9가-힣\s_.:-]/g, " ").split(/\s+/).flatMap((token) => token.split(/(?=[._:-])|(?<=[._:-])/g)).map((token) => normalizeQualityToken(token.replace(/^[._:-]+|[._:-]+$/g, ""))).filter((token) => token.length >= 2);
15270
+ }
15271
+ function normalizeQualityToken(token) {
15272
+ if (token === "apis")
15273
+ return "api";
15274
+ if (token === "ids")
15275
+ return "id";
15276
+ if (LOW_INFORMATION_QUERY_TERMS.has(token) || GENERIC_TECHNICAL_TERMS.has(token))
15277
+ return token;
15278
+ if (token.length > 4 && token.endsWith("ies"))
15279
+ return `${token.slice(0, -3)}y`;
15280
+ if (token.length > 3 && token.endsWith("s") && !token.endsWith("ss") && !token.endsWith("us") && !token.endsWith("is")) {
15281
+ return token.slice(0, -1);
15282
+ }
15283
+ return token;
15284
+ }
14956
15285
 
14957
15286
  // src/core/retriever.ts
14958
15287
  var DEFAULT_OPTIONS = {
@@ -15005,6 +15334,7 @@ var Retriever = class {
15005
15334
  const opts = { ...DEFAULT_OPTIONS, ...options };
15006
15335
  const sessionFilter = opts.scope?.sessionId ?? opts.sessionId;
15007
15336
  const fallbackTrace = [];
15337
+ const qualityQuery = buildRetrievalQualityQuery(query);
15008
15338
  if (isCommandArtifactQuery(query)) {
15009
15339
  fallbackTrace.push("guard:command-artifact-query");
15010
15340
  const emptyMatch = this.matcher.matchSearchResults([], () => 0);
@@ -15021,6 +15351,7 @@ var Retriever = class {
15021
15351
  const fallbackEnabled = (opts.strategy ?? "auto") === "auto";
15022
15352
  const primaryStrategy = opts.strategy === "auto" ? "fast" : opts.strategy || "fast";
15023
15353
  let current = await this.runStage(query, {
15354
+ qualityQuery,
15024
15355
  strategy: primaryStrategy,
15025
15356
  topK: opts.topK,
15026
15357
  minScore: opts.minScore,
@@ -15038,6 +15369,7 @@ var Retriever = class {
15038
15369
  fallbackTrace.push(`stage:primary:${primaryStrategy}`);
15039
15370
  if (fallbackEnabled && this.shouldFallback(current.matchResult, current.results) && primaryStrategy !== "deep") {
15040
15371
  current = await this.runStage(query, {
15372
+ qualityQuery,
15041
15373
  strategy: "deep",
15042
15374
  topK: opts.topK,
15043
15375
  minScore: opts.minScore,
@@ -15055,6 +15387,7 @@ var Retriever = class {
15055
15387
  }
15056
15388
  if (fallbackEnabled && this.shouldFallback(current.matchResult, current.results)) {
15057
15389
  current = await this.runStage(query, {
15390
+ qualityQuery,
15058
15391
  strategy: "deep",
15059
15392
  topK: opts.topK,
15060
15393
  minScore: Math.max(0.5, opts.minScore - 0.15),
@@ -15071,11 +15404,21 @@ var Retriever = class {
15071
15404
  fallbackTrace.push("fallback:scope-expanded");
15072
15405
  }
15073
15406
  if (fallbackEnabled && this.shouldFallback(current.matchResult, current.results)) {
15074
- const summary = await this.buildSummaryFallback(query, opts.topK);
15407
+ const summary = await this.buildSummaryFallback(qualityQuery, opts.topK);
15408
+ const scopedSummary = await this.applyScopeFilters(summary, {
15409
+ scope: opts.scope,
15410
+ projectScopeMode: opts.projectScopeMode,
15411
+ projectHash: opts.projectHash,
15412
+ allowedProjectHashes: opts.allowedProjectHashes
15413
+ });
15414
+ const filteredSummary = this.applyQualityFilters(scopedSummary, {
15415
+ query,
15416
+ minScore: opts.minScore
15417
+ });
15075
15418
  current = {
15076
- results: summary,
15077
- candidateResults: summary,
15078
- matchResult: this.matcher.matchSearchResults(summary, () => 0)
15419
+ results: filteredSummary,
15420
+ candidateResults: filteredSummary,
15421
+ matchResult: this.matcher.matchSearchResults(filteredSummary, () => 0)
15079
15422
  };
15080
15423
  fallbackTrace.push("fallback:summary");
15081
15424
  }
@@ -15100,7 +15443,10 @@ var Retriever = class {
15100
15443
  semanticScore: r.semanticScore,
15101
15444
  lexicalScore: r.lexicalScore,
15102
15445
  recencyScore: r.recencyScore
15103
- }))
15446
+ })),
15447
+ rawQueryText: current.queryRewriteKind ? query : void 0,
15448
+ effectiveQueryText: current.effectiveQueryText,
15449
+ queryRewriteKind: current.queryRewriteKind
15104
15450
  };
15105
15451
  }
15106
15452
  async retrieveUnified(query, options = {}) {
@@ -15138,8 +15484,11 @@ var Retriever = class {
15138
15484
  }
15139
15485
  }
15140
15486
  async runStage(query, input) {
15141
- let rerankQuery = query;
15142
- let initialResults = await this.searchByStrategy(query, {
15487
+ const searchQuery = input.qualityQuery ?? query;
15488
+ let rerankQuery = searchQuery;
15489
+ let effectiveQueryText;
15490
+ let queryRewriteKind;
15491
+ let initialResults = await this.searchByStrategy(searchQuery, {
15143
15492
  strategy: input.strategy,
15144
15493
  topK: input.topK,
15145
15494
  minScore: input.minScore,
@@ -15147,9 +15496,12 @@ var Retriever = class {
15147
15496
  });
15148
15497
  if (input.intentRewrite && input.strategy === "deep" && this.queryRewriter) {
15149
15498
  const rewritten = (await this.queryRewriter(query))?.trim();
15150
- if (rewritten && rewritten !== query) {
15151
- rerankQuery = `${query} ${rewritten}`;
15152
- const rewrittenResults = await this.searchByStrategy(rewritten, {
15499
+ const normalizedQuery = query.trim();
15500
+ if (rewritten && rewritten !== normalizedQuery) {
15501
+ effectiveQueryText = `${normalizedQuery} ${rewritten}`.trim();
15502
+ queryRewriteKind = "intent-rewrite";
15503
+ rerankQuery = buildRetrievalQualityQuery(effectiveQueryText);
15504
+ const rewrittenResults = await this.searchByStrategy(buildRetrievalQualityQuery(rewritten), {
15153
15505
  strategy: "deep",
15154
15506
  topK: input.topK,
15155
15507
  minScore: Math.max(0.5, input.minScore - 0.1),
@@ -15176,10 +15528,14 @@ var Retriever = class {
15176
15528
  });
15177
15529
  const top = qualityFiltered.slice(0, input.topK);
15178
15530
  const matchResult = this.matcher.matchSearchResults(top, () => 0);
15179
- return { results: top, candidateResults: qualityFiltered, matchResult };
15531
+ return { results: top, candidateResults: qualityFiltered, matchResult, effectiveQueryText, queryRewriteKind };
15180
15532
  }
15181
15533
  applyQualityFilters(results, options) {
15182
15534
  let filtered = [...results];
15535
+ if (isCurrentStateQuery(options.query)) {
15536
+ filtered = filtered.filter((result) => !isStaleOrSupersededContent(result.content));
15537
+ }
15538
+ filtered = filtered.filter((result) => hasDiscriminativeTermOverlap(options.query, result.content));
15183
15539
  if (shouldApplyTechnicalGuard(options.query)) {
15184
15540
  filtered = filtered.filter((result) => hasTechnicalTermOverlap(options.query, result.content));
15185
15541
  }
@@ -15487,7 +15843,21 @@ _Context:_ ${sessionContext}`;
15487
15843
  });
15488
15844
  }
15489
15845
  tokenize(text) {
15490
- return text.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, " ").split(/\s+/).filter((t) => t.length >= 2).slice(0, 64);
15846
+ return text.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, " ").split(/\s+/).map((token) => this.normalizeToken(token)).filter((t) => t.length >= 2).slice(0, 64);
15847
+ }
15848
+ normalizeToken(token) {
15849
+ if (token === "apis")
15850
+ return "api";
15851
+ if (token === "ids")
15852
+ return "id";
15853
+ if (token === "does")
15854
+ return token;
15855
+ if (token.length > 4 && token.endsWith("ies"))
15856
+ return `${token.slice(0, -3)}y`;
15857
+ if (token.length > 3 && token.endsWith("s") && !token.endsWith("ss") && !token.endsWith("us") && !token.endsWith("is") && !token.endsWith("ps")) {
15858
+ return token.slice(0, -1);
15859
+ }
15860
+ return token;
15491
15861
  }
15492
15862
  keywordOverlap(a, b) {
15493
15863
  if (a.length === 0 || b.length === 0)
@@ -15550,9 +15920,9 @@ var RetrievalAnalyticsService = class {
15550
15920
  await this.deps.initialize();
15551
15921
  return this.deps.retrievalStore.getHelpfulMemories(limit);
15552
15922
  }
15553
- async getHelpfulnessStats() {
15923
+ async getHelpfulnessStats(since) {
15554
15924
  await this.deps.initialize();
15555
- return this.deps.retrievalStore.getHelpfulnessStats();
15925
+ return this.deps.retrievalStore.getHelpfulnessStats(since);
15556
15926
  }
15557
15927
  /**
15558
15928
  * Extract topic keywords from event content (markdown headings and key terms).
@@ -15977,7 +16347,9 @@ var RetrievalOrchestrator = class {
15977
16347
  await this.deps.traceStore.recordRetrievalTrace({
15978
16348
  sessionId: options?.sessionId,
15979
16349
  projectHash: projectHash || void 0,
15980
- queryText: query,
16350
+ queryText: result.effectiveQueryText || query,
16351
+ rawQueryText: result.rawQueryText || (result.queryRewriteKind ? query : void 0),
16352
+ queryRewriteKind: result.queryRewriteKind || "none",
15981
16353
  strategy: options?.strategy || "auto",
15982
16354
  candidateEventIds,
15983
16355
  selectedEventIds,
@@ -17992,8 +18364,8 @@ var MemoryService = class {
17992
18364
  /**
17993
18365
  * Get helpfulness statistics for dashboard
17994
18366
  */
17995
- async getHelpfulnessStats() {
17996
- return this.retrievalAnalyticsService.getHelpfulnessStats();
18367
+ async getHelpfulnessStats(since) {
18368
+ return this.retrievalAnalyticsService.getHelpfulnessStats(since);
17997
18369
  }
17998
18370
  /**
17999
18371
  * Mark a consolidated memory as accessed
@@ -20214,8 +20586,10 @@ async function handleMemContextPack(memoryService, args) {
20214
20586
  const sessionId = optionalString(args.sessionId);
20215
20587
  const projectPath = optionalString(args.projectPath);
20216
20588
  const genericContinuationQuery = isGenericContinuationQuery(query);
20589
+ const explicitFreshnessRefresh = args.refreshLatest === true;
20590
+ const autoFreshnessRefresh = args.refreshLatest !== false && !explicitFreshnessRefresh && genericContinuationQuery && sessionId === void 0 && projectPath !== void 0 && path14.isAbsolute(projectPath);
20217
20591
  const retrievalTopK = Math.min(topK * 3, 12);
20218
- const freshnessRun = args.refreshLatest === true ? await runLatestImport(memoryService, {
20592
+ const freshnessRun = explicitFreshnessRefresh || autoFreshnessRefresh ? await runLatestImport(memoryService, {
20219
20593
  projectPath: projectPath || "",
20220
20594
  sources: sourceListArg(args.refreshSources),
20221
20595
  sessionLimit: numberArg(args.refreshSessionLimit, 1, 1, 10),
@@ -20251,8 +20625,9 @@ async function handleMemContextPack(memoryService, args) {
20251
20625
  `- Recent sessions shown: ${Math.min(sessionLimit, sessions.length)}`
20252
20626
  ];
20253
20627
  if (freshnessRun) {
20628
+ const refreshMode = autoFreshnessRefresh ? "auto" : "attempted";
20254
20629
  lines.push(
20255
- `- Freshness refresh: attempted before retrieval (${freshnessRun.sources.join(", ")})`,
20630
+ `- Freshness refresh: ${refreshMode} before retrieval (${freshnessRun.sources.join(", ")})`,
20256
20631
  `- Refresh limits: sessions=${freshnessRun.sessionLimit} messages=${freshnessRun.messageLimit} force=${freshnessRun.force ? "yes" : "no"} embeddings=${freshnessRun.processEmbeddings ? `processed ${freshnessRun.embeddingsProcessed ?? 0}` : "skipped"}`
20257
20632
  );
20258
20633
  }