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.
@@ -1965,10 +1965,15 @@ function sqliteTransaction(db, fn) {
1965
1965
  function toDateFromSQLite(value) {
1966
1966
  if (value instanceof Date)
1967
1967
  return value;
1968
- if (typeof value === "string")
1969
- return new Date(value);
1970
1968
  if (typeof value === "number")
1971
1969
  return new Date(value);
1970
+ if (typeof value === "string") {
1971
+ const trimmed = value.trim();
1972
+ if (/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?$/.test(trimmed)) {
1973
+ return /* @__PURE__ */ new Date(trimmed.replace(" ", "T") + "Z");
1974
+ }
1975
+ return new Date(trimmed);
1976
+ }
1972
1977
  return new Date(String(value));
1973
1978
  }
1974
1979
  function toSQLiteTimestamp(date) {
@@ -2034,6 +2039,13 @@ var MarkdownMirror2 = class {
2034
2039
  };
2035
2040
 
2036
2041
  // src/core/sqlite-event-store.ts
2042
+ function normalizeQueryRewriteKind(value) {
2043
+ const normalized = (value || "").trim().toLowerCase();
2044
+ if (normalized === "follow-up-context" || normalized === "intent-rewrite")
2045
+ return normalized;
2046
+ return "none";
2047
+ }
2048
+ var REWRITTEN_QUERY_REWRITE_KIND_SQL = `LOWER(TRIM(COALESCE(query_rewrite_kind, 'none'))) IN ('follow-up-context', 'intent-rewrite')`;
2037
2049
  var SQLiteEventStore = class {
2038
2050
  db;
2039
2051
  initialized = false;
@@ -2294,6 +2306,8 @@ var SQLiteEventStore = class {
2294
2306
  session_id TEXT,
2295
2307
  project_hash TEXT,
2296
2308
  query_text TEXT NOT NULL,
2309
+ raw_query_text TEXT,
2310
+ query_rewrite_kind TEXT,
2297
2311
  strategy TEXT,
2298
2312
  candidate_event_ids TEXT,
2299
2313
  selected_event_ids TEXT,
@@ -2335,6 +2349,8 @@ var SQLiteEventStore = class {
2335
2349
  CREATE INDEX IF NOT EXISTS idx_helpfulness_event ON memory_helpfulness(event_id);
2336
2350
  CREATE INDEX IF NOT EXISTS idx_helpfulness_session ON memory_helpfulness(session_id);
2337
2351
  CREATE INDEX IF NOT EXISTS idx_helpfulness_score ON memory_helpfulness(helpfulness_score DESC);
2352
+ CREATE INDEX IF NOT EXISTS idx_helpfulness_created_at ON memory_helpfulness(created_at);
2353
+ CREATE INDEX IF NOT EXISTS idx_helpfulness_measured_at ON memory_helpfulness(measured_at);
2338
2354
  CREATE INDEX IF NOT EXISTS idx_retrieval_traces_created_at ON retrieval_traces(created_at DESC);
2339
2355
  CREATE INDEX IF NOT EXISTS idx_retrieval_traces_project_hash ON retrieval_traces(project_hash);
2340
2356
  CREATE INDEX IF NOT EXISTS idx_retrieval_traces_session_id ON retrieval_traces(session_id);
@@ -2368,6 +2384,18 @@ var SQLiteEventStore = class {
2368
2384
  sqliteExec(this.db, `ALTER TABLE retrieval_traces ADD COLUMN candidate_details_json TEXT;`);
2369
2385
  } catch {
2370
2386
  }
2387
+ try {
2388
+ sqliteExec(this.db, `ALTER TABLE retrieval_traces ADD COLUMN raw_query_text TEXT;`);
2389
+ } catch {
2390
+ }
2391
+ try {
2392
+ sqliteExec(this.db, `ALTER TABLE retrieval_traces ADD COLUMN query_rewrite_kind TEXT;`);
2393
+ } catch {
2394
+ }
2395
+ try {
2396
+ sqliteExec(this.db, `CREATE INDEX IF NOT EXISTS idx_retrieval_traces_query_rewrite_kind ON retrieval_traces(query_rewrite_kind);`);
2397
+ } catch {
2398
+ }
2371
2399
  const tableInfo = sqliteAll(this.db, "PRAGMA table_info(events)", []);
2372
2400
  const columnNames = tableInfo.map((col) => col.name);
2373
2401
  if (!columnNames.includes("access_count")) {
@@ -3153,8 +3181,11 @@ var SQLiteEventStore = class {
3153
3181
  /**
3154
3182
  * Get helpfulness statistics for dashboard
3155
3183
  */
3156
- async getHelpfulnessStats() {
3184
+ async getHelpfulnessStats(since) {
3157
3185
  await this.initialize();
3186
+ const sinceIso = since?.toISOString();
3187
+ const evaluatedWhere = sinceIso ? `WHERE measured_at IS NOT NULL AND datetime(created_at) >= datetime(?)` : `WHERE measured_at IS NOT NULL`;
3188
+ const totalWhere = sinceIso ? `WHERE datetime(created_at) >= datetime(?)` : ``;
3158
3189
  const stats = sqliteGet(
3159
3190
  this.db,
3160
3191
  `SELECT
@@ -3164,11 +3195,13 @@ var SQLiteEventStore = class {
3164
3195
  SUM(CASE WHEN helpfulness_score >= 0.4 AND helpfulness_score < 0.7 THEN 1 ELSE 0 END) as neutral,
3165
3196
  SUM(CASE WHEN helpfulness_score < 0.4 THEN 1 ELSE 0 END) as unhelpful
3166
3197
  FROM memory_helpfulness
3167
- WHERE measured_at IS NOT NULL`
3198
+ ${evaluatedWhere}`,
3199
+ sinceIso ? [sinceIso] : []
3168
3200
  );
3169
3201
  const totalRow = sqliteGet(
3170
3202
  this.db,
3171
- `SELECT COUNT(*) as total FROM memory_helpfulness`
3203
+ `SELECT COUNT(*) as total FROM memory_helpfulness ${totalWhere}`,
3204
+ sinceIso ? [sinceIso] : []
3172
3205
  );
3173
3206
  return {
3174
3207
  avgScore: Math.round((stats?.avg_score || 0) * 100) / 100,
@@ -3267,18 +3300,21 @@ var SQLiteEventStore = class {
3267
3300
  async recordRetrievalTrace(input) {
3268
3301
  await this.initialize();
3269
3302
  const traceId = randomUUID();
3303
+ const queryRewriteKind = normalizeQueryRewriteKind(input.queryRewriteKind);
3270
3304
  sqliteRun(
3271
3305
  this.db,
3272
3306
  `INSERT INTO retrieval_traces (
3273
- trace_id, session_id, project_hash, query_text, strategy,
3307
+ trace_id, session_id, project_hash, query_text, raw_query_text, query_rewrite_kind, strategy,
3274
3308
  candidate_event_ids, selected_event_ids, candidate_details_json, selected_details_json,
3275
3309
  candidate_count, selected_count, confidence, fallback_trace
3276
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3310
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3277
3311
  [
3278
3312
  traceId,
3279
3313
  input.sessionId || null,
3280
3314
  input.projectHash || null,
3281
3315
  input.queryText,
3316
+ input.rawQueryText || null,
3317
+ queryRewriteKind,
3282
3318
  input.strategy || null,
3283
3319
  JSON.stringify(input.candidateEventIds || []),
3284
3320
  JSON.stringify(input.selectedEventIds || []),
@@ -3304,6 +3340,8 @@ var SQLiteEventStore = class {
3304
3340
  sessionId: row.session_id || void 0,
3305
3341
  projectHash: row.project_hash || void 0,
3306
3342
  queryText: row.query_text,
3343
+ rawQueryText: row.raw_query_text || void 0,
3344
+ queryRewriteKind: normalizeQueryRewriteKind(row.query_rewrite_kind),
3307
3345
  strategy: row.strategy || void 0,
3308
3346
  candidateEventIds: row.candidate_event_ids ? JSON.parse(row.candidate_event_ids) : [],
3309
3347
  selectedEventIds: row.selected_event_ids ? JSON.parse(row.selected_event_ids) : [],
@@ -3330,6 +3368,11 @@ var SQLiteEventStore = class {
3330
3368
  COUNT(*) as total_queries,
3331
3369
  AVG(candidate_count) as avg_candidate_count,
3332
3370
  AVG(selected_count) as avg_selected_count,
3371
+ SUM(CASE WHEN ${REWRITTEN_QUERY_REWRITE_KIND_SQL} THEN 1 ELSE 0 END) as rewritten_queries,
3372
+ SUM(CASE WHEN ${REWRITTEN_QUERY_REWRITE_KIND_SQL} AND selected_count > 0 THEN 1 ELSE 0 END) as rewritten_queries_with_selection,
3373
+ SUM(CASE WHEN NOT (${REWRITTEN_QUERY_REWRITE_KIND_SQL}) AND selected_count > 0 THEN 1 ELSE 0 END) as raw_queries_with_selection,
3374
+ AVG(CASE WHEN ${REWRITTEN_QUERY_REWRITE_KIND_SQL} THEN selected_count END) as avg_selected_count_for_rewritten_queries,
3375
+ AVG(CASE WHEN NOT (${REWRITTEN_QUERY_REWRITE_KIND_SQL}) THEN selected_count END) as avg_selected_count_for_raw_queries,
3333
3376
  CASE
3334
3377
  WHEN SUM(candidate_count) > 0 THEN (SUM(selected_count) * 1.0 / SUM(candidate_count))
3335
3378
  ELSE 0
@@ -3337,15 +3380,41 @@ var SQLiteEventStore = class {
3337
3380
  FROM retrieval_traces`,
3338
3381
  []
3339
3382
  );
3383
+ const totalQueries = Number(row?.total_queries || 0);
3384
+ const rewrittenQueries = Number(row?.rewritten_queries || 0);
3385
+ const rawQueries = Math.max(0, totalQueries - rewrittenQueries);
3386
+ const rewrittenQueriesWithSelection = Number(row?.rewritten_queries_with_selection || 0);
3387
+ const rawQueriesWithSelection = Number(row?.raw_queries_with_selection || 0);
3340
3388
  return {
3341
- totalQueries: Number(row?.total_queries || 0),
3389
+ totalQueries,
3342
3390
  avgCandidateCount: Number(row?.avg_candidate_count || 0),
3343
3391
  avgSelectedCount: Number(row?.avg_selected_count || 0),
3344
- selectionRate: Number(row?.selection_rate || 0)
3392
+ selectionRate: Number(row?.selection_rate || 0),
3393
+ rewrittenQueries,
3394
+ rewriteRate: totalQueries > 0 ? rewrittenQueries / totalQueries : 0,
3395
+ rewrittenQueriesWithSelection,
3396
+ rawQueriesWithSelection,
3397
+ rewrittenSelectionRate: rewrittenQueries > 0 ? rewrittenQueriesWithSelection / rewrittenQueries : 0,
3398
+ rawSelectionRate: rawQueries > 0 ? rawQueriesWithSelection / rawQueries : 0,
3399
+ avgSelectedCountForRewrittenQueries: Number(row?.avg_selected_count_for_rewritten_queries || 0),
3400
+ avgSelectedCountForRawQueries: Number(row?.avg_selected_count_for_raw_queries || 0)
3345
3401
  };
3346
3402
  } catch (err) {
3347
3403
  if (err?.message?.includes("no such table")) {
3348
- return { totalQueries: 0, avgCandidateCount: 0, avgSelectedCount: 0, selectionRate: 0 };
3404
+ return {
3405
+ totalQueries: 0,
3406
+ avgCandidateCount: 0,
3407
+ avgSelectedCount: 0,
3408
+ selectionRate: 0,
3409
+ rewrittenQueries: 0,
3410
+ rewriteRate: 0,
3411
+ rewrittenQueriesWithSelection: 0,
3412
+ rawQueriesWithSelection: 0,
3413
+ rewrittenSelectionRate: 0,
3414
+ rawSelectionRate: 0,
3415
+ avgSelectedCountForRewrittenQueries: 0,
3416
+ avgSelectedCountForRawQueries: 0
3417
+ };
3349
3418
  }
3350
3419
  throw err;
3351
3420
  }
@@ -3697,6 +3766,57 @@ var COMMAND_ARTIFACT_PATTERNS = [
3697
3766
  /<local-command-stdout>[\s\S]*?<\/local-command-stdout>/i,
3698
3767
  /<local-command-stderr>[\s\S]*?<\/local-command-stderr>/i
3699
3768
  ];
3769
+ var CONTINUATION_QUERY_PATTERNS = [
3770
+ /^\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,
3771
+ /^\s*(?:응\s*)?(?:이어서(?:\s*진행(?:해줘)?)?|계속(?:\s*해줘)?|다음\s*(?:단계|작업|추천\s*작업|추천|할\s*일)?(?:은|는)?(?:\s*(?:뭐야|진행(?:해줘)?))?\??|남은\s*(?:추가(?:로)?\s*)?(?:(?:할\s*만한\s*)?(?:작업|일)|할\s*일)?(?:은|는)?\s*(?:있어|있나|있나요|뭐야)\??|추천\s*작업(?:은|는)?(?:\s*뭐야)?\??|진행해줘)\s*$/i
3772
+ ];
3773
+ var SHORT_REPAIR_FOLLOW_UP_PATTERNS = [
3774
+ /^\s*(?:fix\s+(?:it|that)|repair\s+(?:it|that)|resolve\s+(?:it|that)|that\s+bug|same\s+issue)\s*$/i,
3775
+ /^\s*(?:그거|그것|이거|이것)?\s*(?:고쳐줘|수정해줘|해결해줘|처리해줘)\s*$/i
3776
+ ];
3777
+ var CURRENT_STATE_QUERY_PATTERNS = [
3778
+ /\bcurrent\b.*\b(?:state|status|deployment|blocker|pr|pull request)\b/i,
3779
+ /\b(?:still|as current|current)\b.*\b(?:unresolved|open|pending|not completed)\b/i,
3780
+ /\b(?:old|obsolete|stale|resolved|already resolved)\b.*\b(?:current|still|unresolved|open|state|status)\b/i,
3781
+ /(?:현재|아직|이전|오래된|해결된).*(?:상태|미해결|열린|블로커|PR|풀리퀘스트)/i
3782
+ ];
3783
+ var STALE_CONTENT_PATTERNS = [
3784
+ /\b(?:obsolete|superseded|outdated)\b/i,
3785
+ /\bstale\s+(?:operational\s+)?state\b/i,
3786
+ /\bstale\s+after\b/i,
3787
+ /\bno\s+longer\s+(?:valid|current|applies?)\b/i,
3788
+ /\bearlier\s+(?:pull request|pr)\b[\s\S]{0,160}\b(?:open|not completed|had not completed)\b/i,
3789
+ /\bshould\s+not\s+be\s+injected\s+as\s+current\s+context\b/i,
3790
+ /(?:오래된|더 이상 유효하지|현재 상태가 아님)/i
3791
+ ];
3792
+ var CONTINUATION_EXPANSION = "current next step plan roadmap status validation replay rerank memory usefulness continuation";
3793
+ var REPAIR_FOLLOW_UP_EXPANSION = "review blocker fix pattern dashboard error state metrics bucket validation sanitize rerun unresolved";
3794
+ 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";
3795
+ var DECISION_RECALL_TERMS = /* @__PURE__ */ new Set([
3796
+ "decide",
3797
+ "decided",
3798
+ "decision",
3799
+ "agreed",
3800
+ "policy",
3801
+ "constraint"
3802
+ ]);
3803
+ var RETRIEVAL_PRIVACY_SURFACE_TERMS = /* @__PURE__ */ new Set([
3804
+ "retrieval",
3805
+ "dashboard",
3806
+ "telemetry",
3807
+ "trace"
3808
+ ]);
3809
+ var DECISION_TOPIC_WEAK_TERMS = /* @__PURE__ */ new Set([
3810
+ "api",
3811
+ "dashboard",
3812
+ "retrieval",
3813
+ "trace",
3814
+ "telemetry",
3815
+ "query",
3816
+ "raw",
3817
+ "count",
3818
+ "counts"
3819
+ ]);
3700
3820
  var GENERIC_TECHNICAL_TERMS = /* @__PURE__ */ new Set([
3701
3821
  "api",
3702
3822
  "cli",
@@ -3714,6 +3834,87 @@ var GENERIC_TECHNICAL_TERMS = /* @__PURE__ */ new Set([
3714
3834
  "db",
3715
3835
  "sql"
3716
3836
  ]);
3837
+ var LOW_INFORMATION_QUERY_TERMS = /* @__PURE__ */ new Set([
3838
+ "the",
3839
+ "and",
3840
+ "or",
3841
+ "for",
3842
+ "from",
3843
+ "with",
3844
+ "without",
3845
+ "about",
3846
+ "what",
3847
+ "when",
3848
+ "where",
3849
+ "which",
3850
+ "who",
3851
+ "why",
3852
+ "how",
3853
+ "did",
3854
+ "does",
3855
+ "do",
3856
+ "we",
3857
+ "i",
3858
+ "in",
3859
+ "to",
3860
+ "of",
3861
+ "on",
3862
+ "as",
3863
+ "be",
3864
+ "was",
3865
+ "were",
3866
+ "decide",
3867
+ "decided",
3868
+ "decision",
3869
+ "agreed",
3870
+ "policy",
3871
+ "constraint",
3872
+ "showing",
3873
+ "can",
3874
+ "you",
3875
+ "me",
3876
+ "show",
3877
+ "tell",
3878
+ "please",
3879
+ "should",
3880
+ "would",
3881
+ "could",
3882
+ "this",
3883
+ "that",
3884
+ "these",
3885
+ "those",
3886
+ "use",
3887
+ "using",
3888
+ "treat",
3889
+ "continue",
3890
+ "resume",
3891
+ "next",
3892
+ "step",
3893
+ "task",
3894
+ "action",
3895
+ "current",
3896
+ "state",
3897
+ "status",
3898
+ "old",
3899
+ "already",
3900
+ "still",
3901
+ "near",
3902
+ "today",
3903
+ "\uC751",
3904
+ "\uADF8\uAC70",
3905
+ "\uADF8\uAC83",
3906
+ "\uC774\uAC70",
3907
+ "\uC774\uAC83",
3908
+ "\uB2E4\uC74C",
3909
+ "\uB2E8\uACC4",
3910
+ "\uC9C4\uD589",
3911
+ "\uC9C4\uD589\uD574\uC918",
3912
+ "\uACC4\uC18D",
3913
+ "\uC774\uC5B4\uC11C",
3914
+ "\uACE0\uCCD0\uC918",
3915
+ "\uC218\uC815\uD574\uC918",
3916
+ "\uD574\uACB0\uD574\uC918"
3917
+ ]);
3717
3918
  function isCommandArtifactQuery(query) {
3718
3919
  const trimmed = query.trim();
3719
3920
  if (!trimmed)
@@ -3725,6 +3926,73 @@ function isCommandArtifactQuery(query) {
3725
3926
  return true;
3726
3927
  return COMMAND_ARTIFACT_PATTERNS.some((pattern) => pattern.test(trimmed));
3727
3928
  }
3929
+ function isGenericContinuationQuery(query) {
3930
+ const trimmed = query.trim();
3931
+ if (!trimmed)
3932
+ return false;
3933
+ if (!CONTINUATION_QUERY_PATTERNS.some((pattern) => pattern.test(trimmed)))
3934
+ return false;
3935
+ if (extractTechnicalQueryTerms(trimmed).length > 0)
3936
+ return false;
3937
+ const tokens = trimmed.match(/[A-Za-z0-9가-힣#._/-]+/g) ?? [];
3938
+ if (tokens.length > 10)
3939
+ return false;
3940
+ 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);
3941
+ }
3942
+ function isShortRepairFollowUpQuery(query) {
3943
+ const trimmed = query.trim();
3944
+ if (!trimmed)
3945
+ return false;
3946
+ if (extractTechnicalQueryTerms(trimmed).length > 0)
3947
+ return false;
3948
+ const tokens = trimmed.match(/[A-Za-z0-9가-힣#._/-]+/g) ?? [];
3949
+ if (tokens.length > 8)
3950
+ return false;
3951
+ return SHORT_REPAIR_FOLLOW_UP_PATTERNS.some((pattern) => pattern.test(trimmed));
3952
+ }
3953
+ function isCurrentStateQuery(query) {
3954
+ const trimmed = query.trim();
3955
+ if (!trimmed)
3956
+ return false;
3957
+ return CURRENT_STATE_QUERY_PATTERNS.some((pattern) => pattern.test(trimmed));
3958
+ }
3959
+ function isStaleOrSupersededContent(content) {
3960
+ const trimmed = content.trim();
3961
+ if (!trimmed)
3962
+ return false;
3963
+ return STALE_CONTENT_PATTERNS.some((pattern) => pattern.test(trimmed));
3964
+ }
3965
+ function buildRetrievalQualityQuery(query) {
3966
+ const trimmed = query.trim();
3967
+ if (!trimmed)
3968
+ return query;
3969
+ if (isRetrievalPrivacyDecisionQuery(trimmed)) {
3970
+ return `${trimmed} ${RETRIEVAL_PRIVACY_DECISION_EXPANSION}`;
3971
+ }
3972
+ if (isGenericContinuationQuery(trimmed)) {
3973
+ return `${trimmed} ${CONTINUATION_EXPANSION}`;
3974
+ }
3975
+ if (isShortRepairFollowUpQuery(trimmed)) {
3976
+ return `${trimmed} ${REPAIR_FOLLOW_UP_EXPANSION}`;
3977
+ }
3978
+ return query;
3979
+ }
3980
+ function isRetrievalPrivacyDecisionQuery(query) {
3981
+ const trimmed = query.trim();
3982
+ if (!trimmed)
3983
+ return false;
3984
+ const terms = new Set(tokenizeQualityText(trimmed));
3985
+ const hasDecisionSignal = hasAnyTerm(terms, DECISION_RECALL_TERMS) || /(?:결정|정책|원칙)/i.test(trimmed);
3986
+ if (!hasDecisionSignal)
3987
+ return false;
3988
+ const hasRawQuerySignal = terms.has("raw") && terms.has("query");
3989
+ const hasPrivacySignal = terms.has("privacy") || terms.has("expose") || terms.has("redacted");
3990
+ const hasRetrievalSurface = hasAnyTerm(terms, RETRIEVAL_PRIVACY_SURFACE_TERMS) || terms.has("api") && terms.has("query");
3991
+ const hasQuerySurface = terms.has("query") && (terms.has("dashboard") || terms.has("trace") || terms.has("telemetry") || terms.has("api"));
3992
+ const hasKoreanRetrievalSurface = /(?:검색|리트리벌|retrieval|대시보드|트레이스|텔레메트리|telemetry)/i.test(trimmed);
3993
+ const hasKoreanPrivacySurface = /(?:원문|쿼리|프라이버시|개인정보|노출|트레이스|메타데이터)/i.test(trimmed);
3994
+ return (hasRetrievalSurface || hasKoreanRetrievalSurface && hasKoreanPrivacySurface) && (hasRawQuerySignal || hasPrivacySignal || hasQuerySurface || hasKoreanPrivacySurface);
3995
+ }
3728
3996
  function extractTechnicalQueryTerms(query) {
3729
3997
  const matches = query.match(/[A-Za-z][A-Za-z0-9_.:-]{2,}/g) ?? [];
3730
3998
  const terms = matches.filter((term) => {
@@ -3742,9 +4010,87 @@ function hasTechnicalTermOverlap(query, content) {
3742
4010
  const normalizedContent = content.toLowerCase();
3743
4011
  return terms.some((term) => normalizedContent.includes(term));
3744
4012
  }
4013
+ function hasDiscriminativeTermOverlap(query, content) {
4014
+ const queryTerms = extractDiscriminativeQueryTerms(query);
4015
+ const contentTerms = new Set(tokenizeQualityText(content));
4016
+ if (isRetrievalPrivacyDecisionQuery(query) && hasRetrievalPrivacyDecisionContent(contentTerms)) {
4017
+ return true;
4018
+ }
4019
+ if (shouldRequireDecisionTopicOverlap(query)) {
4020
+ const topicTerms = queryTerms.filter((term) => !DECISION_TOPIC_WEAK_TERMS.has(term));
4021
+ if (topicTerms.length > 0) {
4022
+ return topicTerms.some((term) => contentTerms.has(term));
4023
+ }
4024
+ }
4025
+ if (queryTerms.length < 3)
4026
+ return true;
4027
+ const requiredHits = queryTerms.length >= 3 ? 2 : 1;
4028
+ let hits = 0;
4029
+ for (const term of queryTerms) {
4030
+ if (contentTerms.has(term))
4031
+ hits += 1;
4032
+ if (hits >= requiredHits)
4033
+ return true;
4034
+ }
4035
+ return false;
4036
+ }
3745
4037
  function shouldApplyTechnicalGuard(query) {
3746
4038
  return extractTechnicalQueryTerms(query).length > 0;
3747
4039
  }
4040
+ function hasAnyTerm(terms, expectedTerms) {
4041
+ let found = false;
4042
+ expectedTerms.forEach((term) => {
4043
+ if (terms.has(term))
4044
+ found = true;
4045
+ });
4046
+ return found;
4047
+ }
4048
+ function shouldRequireDecisionTopicOverlap(query) {
4049
+ if (isRetrievalPrivacyDecisionQuery(query))
4050
+ return false;
4051
+ const trimmed = query.trim();
4052
+ if (!trimmed)
4053
+ return false;
4054
+ const terms = new Set(tokenizeQualityText(trimmed));
4055
+ return hasAnyTerm(terms, DECISION_RECALL_TERMS) || /(?:결정|정책|원칙)/i.test(trimmed);
4056
+ }
4057
+ function extractDiscriminativeQueryTerms(query) {
4058
+ const seen = /* @__PURE__ */ new Set();
4059
+ const terms = [];
4060
+ for (const token of tokenizeQualityText(query)) {
4061
+ if (LOW_INFORMATION_QUERY_TERMS.has(token))
4062
+ continue;
4063
+ if (GENERIC_TECHNICAL_TERMS.has(token))
4064
+ continue;
4065
+ if (seen.has(token))
4066
+ continue;
4067
+ seen.add(token);
4068
+ terms.push(token);
4069
+ }
4070
+ return terms;
4071
+ }
4072
+ function hasRetrievalPrivacyDecisionContent(contentTerms) {
4073
+ 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"));
4074
+ 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")));
4075
+ return hasDashboardTraceMetadata || hasRawQueryPrivacyPolicy;
4076
+ }
4077
+ function tokenizeQualityText(text) {
4078
+ 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);
4079
+ }
4080
+ function normalizeQualityToken(token) {
4081
+ if (token === "apis")
4082
+ return "api";
4083
+ if (token === "ids")
4084
+ return "id";
4085
+ if (LOW_INFORMATION_QUERY_TERMS.has(token) || GENERIC_TECHNICAL_TERMS.has(token))
4086
+ return token;
4087
+ if (token.length > 4 && token.endsWith("ies"))
4088
+ return `${token.slice(0, -3)}y`;
4089
+ if (token.length > 3 && token.endsWith("s") && !token.endsWith("ss") && !token.endsWith("us") && !token.endsWith("is")) {
4090
+ return token.slice(0, -1);
4091
+ }
4092
+ return token;
4093
+ }
3748
4094
 
3749
4095
  // src/core/retriever.ts
3750
4096
  var DEFAULT_OPTIONS = {
@@ -3797,6 +4143,7 @@ var Retriever = class {
3797
4143
  const opts = { ...DEFAULT_OPTIONS, ...options };
3798
4144
  const sessionFilter = opts.scope?.sessionId ?? opts.sessionId;
3799
4145
  const fallbackTrace = [];
4146
+ const qualityQuery = buildRetrievalQualityQuery(query);
3800
4147
  if (isCommandArtifactQuery(query)) {
3801
4148
  fallbackTrace.push("guard:command-artifact-query");
3802
4149
  const emptyMatch = this.matcher.matchSearchResults([], () => 0);
@@ -3813,6 +4160,7 @@ var Retriever = class {
3813
4160
  const fallbackEnabled = (opts.strategy ?? "auto") === "auto";
3814
4161
  const primaryStrategy = opts.strategy === "auto" ? "fast" : opts.strategy || "fast";
3815
4162
  let current = await this.runStage(query, {
4163
+ qualityQuery,
3816
4164
  strategy: primaryStrategy,
3817
4165
  topK: opts.topK,
3818
4166
  minScore: opts.minScore,
@@ -3830,6 +4178,7 @@ var Retriever = class {
3830
4178
  fallbackTrace.push(`stage:primary:${primaryStrategy}`);
3831
4179
  if (fallbackEnabled && this.shouldFallback(current.matchResult, current.results) && primaryStrategy !== "deep") {
3832
4180
  current = await this.runStage(query, {
4181
+ qualityQuery,
3833
4182
  strategy: "deep",
3834
4183
  topK: opts.topK,
3835
4184
  minScore: opts.minScore,
@@ -3847,6 +4196,7 @@ var Retriever = class {
3847
4196
  }
3848
4197
  if (fallbackEnabled && this.shouldFallback(current.matchResult, current.results)) {
3849
4198
  current = await this.runStage(query, {
4199
+ qualityQuery,
3850
4200
  strategy: "deep",
3851
4201
  topK: opts.topK,
3852
4202
  minScore: Math.max(0.5, opts.minScore - 0.15),
@@ -3863,11 +4213,21 @@ var Retriever = class {
3863
4213
  fallbackTrace.push("fallback:scope-expanded");
3864
4214
  }
3865
4215
  if (fallbackEnabled && this.shouldFallback(current.matchResult, current.results)) {
3866
- const summary = await this.buildSummaryFallback(query, opts.topK);
4216
+ const summary = await this.buildSummaryFallback(qualityQuery, opts.topK);
4217
+ const scopedSummary = await this.applyScopeFilters(summary, {
4218
+ scope: opts.scope,
4219
+ projectScopeMode: opts.projectScopeMode,
4220
+ projectHash: opts.projectHash,
4221
+ allowedProjectHashes: opts.allowedProjectHashes
4222
+ });
4223
+ const filteredSummary = this.applyQualityFilters(scopedSummary, {
4224
+ query,
4225
+ minScore: opts.minScore
4226
+ });
3867
4227
  current = {
3868
- results: summary,
3869
- candidateResults: summary,
3870
- matchResult: this.matcher.matchSearchResults(summary, () => 0)
4228
+ results: filteredSummary,
4229
+ candidateResults: filteredSummary,
4230
+ matchResult: this.matcher.matchSearchResults(filteredSummary, () => 0)
3871
4231
  };
3872
4232
  fallbackTrace.push("fallback:summary");
3873
4233
  }
@@ -3892,7 +4252,10 @@ var Retriever = class {
3892
4252
  semanticScore: r.semanticScore,
3893
4253
  lexicalScore: r.lexicalScore,
3894
4254
  recencyScore: r.recencyScore
3895
- }))
4255
+ })),
4256
+ rawQueryText: current.queryRewriteKind ? query : void 0,
4257
+ effectiveQueryText: current.effectiveQueryText,
4258
+ queryRewriteKind: current.queryRewriteKind
3896
4259
  };
3897
4260
  }
3898
4261
  async retrieveUnified(query, options = {}) {
@@ -3930,8 +4293,11 @@ var Retriever = class {
3930
4293
  }
3931
4294
  }
3932
4295
  async runStage(query, input) {
3933
- let rerankQuery = query;
3934
- let initialResults = await this.searchByStrategy(query, {
4296
+ const searchQuery = input.qualityQuery ?? query;
4297
+ let rerankQuery = searchQuery;
4298
+ let effectiveQueryText;
4299
+ let queryRewriteKind;
4300
+ let initialResults = await this.searchByStrategy(searchQuery, {
3935
4301
  strategy: input.strategy,
3936
4302
  topK: input.topK,
3937
4303
  minScore: input.minScore,
@@ -3939,9 +4305,12 @@ var Retriever = class {
3939
4305
  });
3940
4306
  if (input.intentRewrite && input.strategy === "deep" && this.queryRewriter) {
3941
4307
  const rewritten = (await this.queryRewriter(query))?.trim();
3942
- if (rewritten && rewritten !== query) {
3943
- rerankQuery = `${query} ${rewritten}`;
3944
- const rewrittenResults = await this.searchByStrategy(rewritten, {
4308
+ const normalizedQuery = query.trim();
4309
+ if (rewritten && rewritten !== normalizedQuery) {
4310
+ effectiveQueryText = `${normalizedQuery} ${rewritten}`.trim();
4311
+ queryRewriteKind = "intent-rewrite";
4312
+ rerankQuery = buildRetrievalQualityQuery(effectiveQueryText);
4313
+ const rewrittenResults = await this.searchByStrategy(buildRetrievalQualityQuery(rewritten), {
3945
4314
  strategy: "deep",
3946
4315
  topK: input.topK,
3947
4316
  minScore: Math.max(0.5, input.minScore - 0.1),
@@ -3968,10 +4337,14 @@ var Retriever = class {
3968
4337
  });
3969
4338
  const top = qualityFiltered.slice(0, input.topK);
3970
4339
  const matchResult = this.matcher.matchSearchResults(top, () => 0);
3971
- return { results: top, candidateResults: qualityFiltered, matchResult };
4340
+ return { results: top, candidateResults: qualityFiltered, matchResult, effectiveQueryText, queryRewriteKind };
3972
4341
  }
3973
4342
  applyQualityFilters(results, options) {
3974
4343
  let filtered = [...results];
4344
+ if (isCurrentStateQuery(options.query)) {
4345
+ filtered = filtered.filter((result) => !isStaleOrSupersededContent(result.content));
4346
+ }
4347
+ filtered = filtered.filter((result) => hasDiscriminativeTermOverlap(options.query, result.content));
3975
4348
  if (shouldApplyTechnicalGuard(options.query)) {
3976
4349
  filtered = filtered.filter((result) => hasTechnicalTermOverlap(options.query, result.content));
3977
4350
  }
@@ -4279,7 +4652,21 @@ _Context:_ ${sessionContext}`;
4279
4652
  });
4280
4653
  }
4281
4654
  tokenize(text) {
4282
- return text.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, " ").split(/\s+/).filter((t) => t.length >= 2).slice(0, 64);
4655
+ 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);
4656
+ }
4657
+ normalizeToken(token) {
4658
+ if (token === "apis")
4659
+ return "api";
4660
+ if (token === "ids")
4661
+ return "id";
4662
+ if (token === "does")
4663
+ return token;
4664
+ if (token.length > 4 && token.endsWith("ies"))
4665
+ return `${token.slice(0, -3)}y`;
4666
+ if (token.length > 3 && token.endsWith("s") && !token.endsWith("ss") && !token.endsWith("us") && !token.endsWith("is") && !token.endsWith("ps")) {
4667
+ return token.slice(0, -1);
4668
+ }
4669
+ return token;
4283
4670
  }
4284
4671
  keywordOverlap(a, b) {
4285
4672
  if (a.length === 0 || b.length === 0)
@@ -4342,9 +4729,9 @@ var RetrievalAnalyticsService = class {
4342
4729
  await this.deps.initialize();
4343
4730
  return this.deps.retrievalStore.getHelpfulMemories(limit);
4344
4731
  }
4345
- async getHelpfulnessStats() {
4732
+ async getHelpfulnessStats(since) {
4346
4733
  await this.deps.initialize();
4347
- return this.deps.retrievalStore.getHelpfulnessStats();
4734
+ return this.deps.retrievalStore.getHelpfulnessStats(since);
4348
4735
  }
4349
4736
  /**
4350
4737
  * Extract topic keywords from event content (markdown headings and key terms).
@@ -4769,7 +5156,9 @@ var RetrievalOrchestrator = class {
4769
5156
  await this.deps.traceStore.recordRetrievalTrace({
4770
5157
  sessionId: options?.sessionId,
4771
5158
  projectHash: projectHash || void 0,
4772
- queryText: query,
5159
+ queryText: result.effectiveQueryText || query,
5160
+ rawQueryText: result.rawQueryText || (result.queryRewriteKind ? query : void 0),
5161
+ queryRewriteKind: result.queryRewriteKind || "none",
4773
5162
  strategy: options?.strategy || "auto",
4774
5163
  candidateEventIds,
4775
5164
  selectedEventIds,