claude-memory-layer 1.0.32 → 1.0.34
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/cli/index.js +1114 -74
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +414 -25
- package/dist/core/index.js.map +2 -2
- package/dist/hooks/post-tool-use.js +416 -27
- package/dist/hooks/post-tool-use.js.map +2 -2
- package/dist/hooks/semantic-daemon.js +416 -27
- package/dist/hooks/semantic-daemon.js.map +2 -2
- package/dist/hooks/session-end.js +416 -27
- package/dist/hooks/session-end.js.map +2 -2
- package/dist/hooks/session-start.js +416 -27
- package/dist/hooks/session-start.js.map +2 -2
- package/dist/hooks/stop.js +416 -27
- package/dist/hooks/stop.js.map +2 -2
- package/dist/hooks/user-prompt-submit.js +504 -34
- package/dist/hooks/user-prompt-submit.js.map +2 -2
- package/dist/index.js +416 -27
- package/dist/index.js.map +2 -2
- package/dist/mcp/index.js +567 -51
- package/dist/mcp/index.js.map +2 -2
- package/dist/server/api/index.js +850 -44
- package/dist/server/api/index.js.map +3 -3
- package/dist/server/index.js +1073 -64
- package/dist/server/index.js.map +3 -3
- package/dist/services/memory-service.js +416 -27
- package/dist/services/memory-service.js.map +2 -2
- package/dist/ui/assets/js/bootstrap.js +2 -0
- package/dist/ui/assets/js/overview.js +166 -3
- package/dist/ui/assets/js/state.js +3 -0
- package/dist/ui/index.html +20 -0
- package/dist/ui/style.css +193 -0
- package/package.json +5 -1
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: "
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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
|
|
14941
|
+
/^\s*(?:응\s*)?(?:이어서(?:\s*진행(?:해줘)?)?|계속(?:\s*해줘)?|다음\s*(?:단계|작업|추천\s*작업|추천|할\s*일)?(?:은|는)?(?:\s*(?:뭐야|진행(?:해줘)?))?\??|남은\s*(?:추가(?:로)?\s*)?(?:(?:할\s*만한\s*)?(?:작업|일)|할\s*일)?(?:은|는)?\s*(?:있어|있나|있나요|뭐야)\??|추천\s*작업(?:은|는)?(?:\s*뭐야)?\??|진행해줘)\s*$/i
|
|
14873
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
|
|
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(
|
|
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:
|
|
15077
|
-
candidateResults:
|
|
15078
|
-
matchResult: this.matcher.matchSearchResults(
|
|
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
|
-
|
|
15142
|
-
let
|
|
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
|
-
|
|
15151
|
-
|
|
15152
|
-
|
|
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
|
|
@@ -19487,7 +19859,8 @@ var HermesSessionHistoryImporter = class {
|
|
|
19487
19859
|
source: "hermes",
|
|
19488
19860
|
hermesSource: session.source,
|
|
19489
19861
|
sourceSessionId: session.id,
|
|
19490
|
-
sourceSessionHash: hashLabel(session.id)
|
|
19862
|
+
sourceSessionHash: hashLabel(session.id),
|
|
19863
|
+
projectPath: effectiveProjectPath
|
|
19491
19864
|
}
|
|
19492
19865
|
);
|
|
19493
19866
|
if (appendResult.success && appendResult.isDuplicate) {
|
|
@@ -19524,7 +19897,8 @@ var HermesSessionHistoryImporter = class {
|
|
|
19524
19897
|
source: "hermes",
|
|
19525
19898
|
hermesSource: session.source,
|
|
19526
19899
|
sourceSessionId: session.id,
|
|
19527
|
-
sourceSessionHash: hashLabel(session.id)
|
|
19900
|
+
sourceSessionHash: hashLabel(session.id),
|
|
19901
|
+
projectPath: effectiveProjectPath
|
|
19528
19902
|
}
|
|
19529
19903
|
);
|
|
19530
19904
|
if (appendResult.success && appendResult.isDuplicate) {
|
|
@@ -20073,7 +20447,7 @@ async function handleToolCall(name, args) {
|
|
|
20073
20447
|
case "mem-details":
|
|
20074
20448
|
return await handleMemDetails(memoryService, args);
|
|
20075
20449
|
case "mem-stats":
|
|
20076
|
-
return await handleMemStats(memoryService);
|
|
20450
|
+
return await handleMemStats(memoryService, args);
|
|
20077
20451
|
case "mem-context-pack":
|
|
20078
20452
|
return await handleMemContextPack(memoryService, args);
|
|
20079
20453
|
case "mem-import-latest":
|
|
@@ -20109,19 +20483,19 @@ async function handleExternalMarketContext(args) {
|
|
|
20109
20483
|
async function handleMemSearch(memoryService, args) {
|
|
20110
20484
|
const query = args.query;
|
|
20111
20485
|
const topK = Math.min(args.topK || 5, 20);
|
|
20112
|
-
const
|
|
20113
|
-
|
|
20114
|
-
sessionId: args.sessionId,
|
|
20115
|
-
recordTrace: false
|
|
20116
|
-
});
|
|
20486
|
+
const sessionId = args.sessionId;
|
|
20487
|
+
const search = await retrieveMcpMemories(memoryService, query, { topK, sessionId });
|
|
20117
20488
|
const lines = [
|
|
20118
20489
|
"## Memory Search Results",
|
|
20119
20490
|
"",
|
|
20120
|
-
`Found ${
|
|
20491
|
+
`Found ${search.memories.length} relevant memories:`,
|
|
20121
20492
|
""
|
|
20122
20493
|
];
|
|
20123
|
-
|
|
20124
|
-
|
|
20494
|
+
if (search.warning) {
|
|
20495
|
+
lines.push(search.warning, "");
|
|
20496
|
+
}
|
|
20497
|
+
for (let i = 0; i < search.memories.length; i++) {
|
|
20498
|
+
const m = search.memories[i];
|
|
20125
20499
|
const citationId = generateCitationId(m.event.id);
|
|
20126
20500
|
const date = m.event.timestamp.toISOString().split("T")[0];
|
|
20127
20501
|
const preview = m.event.content.slice(0, 100) + (m.event.content.length > 100 ? "..." : "");
|
|
@@ -20136,6 +20510,52 @@ async function handleMemSearch(memoryService, args) {
|
|
|
20136
20510
|
content: [{ type: "text", text: lines.join("\n") }]
|
|
20137
20511
|
};
|
|
20138
20512
|
}
|
|
20513
|
+
var SEMANTIC_VECTOR_FALLBACK_WARNING = "Warning: semantic/vector retrieval unavailable; used keyword fallback.";
|
|
20514
|
+
var SEMANTIC_VECTOR_FALLBACK_FAILED_WARNING = "Warning: semantic/vector retrieval unavailable; keyword fallback failed.";
|
|
20515
|
+
async function retrieveMcpMemories(memoryService, query, options) {
|
|
20516
|
+
try {
|
|
20517
|
+
const result = await memoryService.retrieveMemories(query, {
|
|
20518
|
+
topK: options.topK,
|
|
20519
|
+
sessionId: options.sessionId,
|
|
20520
|
+
recordTrace: false
|
|
20521
|
+
});
|
|
20522
|
+
return { memories: result.memories };
|
|
20523
|
+
} catch (error) {
|
|
20524
|
+
if (!isVectorSchemaMismatchError(error)) {
|
|
20525
|
+
throw error;
|
|
20526
|
+
}
|
|
20527
|
+
try {
|
|
20528
|
+
const memories = options.sessionId ? rankSessionKeywordMatches(
|
|
20529
|
+
query,
|
|
20530
|
+
await memoryService.getSessionHistory(options.sessionId),
|
|
20531
|
+
options.topK
|
|
20532
|
+
) : await memoryService.keywordSearch(query, { topK: options.topK });
|
|
20533
|
+
return { memories, warning: SEMANTIC_VECTOR_FALLBACK_WARNING };
|
|
20534
|
+
} catch {
|
|
20535
|
+
return { memories: [], warning: SEMANTIC_VECTOR_FALLBACK_FAILED_WARNING };
|
|
20536
|
+
}
|
|
20537
|
+
}
|
|
20538
|
+
}
|
|
20539
|
+
function isVectorSchemaMismatchError(error) {
|
|
20540
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20541
|
+
return /no vector column/i.test(message) || /query vector dimension/i.test(message) || /vector[^\n]{0,80}dimension/i.test(message) || /dimension[^\n]{0,80}vector/i.test(message) || /lancedb[^\n]{0,120}schema/i.test(message);
|
|
20542
|
+
}
|
|
20543
|
+
function rankSessionKeywordMatches(query, events, topK) {
|
|
20544
|
+
const queryTokens = tokenizeKeywordQuery(query);
|
|
20545
|
+
if (queryTokens.length === 0)
|
|
20546
|
+
return [];
|
|
20547
|
+
return events.map((event) => ({ event, score: scoreKeywordMatch(event.content, queryTokens) })).filter((match) => match.score > 0).sort((a, b) => b.score - a.score || b.event.timestamp.getTime() - a.event.timestamp.getTime()).slice(0, topK);
|
|
20548
|
+
}
|
|
20549
|
+
function tokenizeKeywordQuery(value) {
|
|
20550
|
+
return Array.from(new Set(
|
|
20551
|
+
value.toLowerCase().split(/[^a-z0-9가-힣_]+/).map((token) => token.trim()).filter((token) => token.length > 1)
|
|
20552
|
+
));
|
|
20553
|
+
}
|
|
20554
|
+
function scoreKeywordMatch(content, queryTokens) {
|
|
20555
|
+
const haystack = content.toLowerCase();
|
|
20556
|
+
const hits = queryTokens.filter((token) => haystack.includes(token)).length;
|
|
20557
|
+
return hits / queryTokens.length;
|
|
20558
|
+
}
|
|
20139
20559
|
async function handleMemTimeline(memoryService, args) {
|
|
20140
20560
|
const ids = args.ids;
|
|
20141
20561
|
const windowSize = args.windowSize || 3;
|
|
@@ -20214,8 +20634,10 @@ async function handleMemContextPack(memoryService, args) {
|
|
|
20214
20634
|
const sessionId = optionalString(args.sessionId);
|
|
20215
20635
|
const projectPath = optionalString(args.projectPath);
|
|
20216
20636
|
const genericContinuationQuery = isGenericContinuationQuery(query);
|
|
20637
|
+
const explicitFreshnessRefresh = args.refreshLatest === true;
|
|
20638
|
+
const autoFreshnessRefresh = args.refreshLatest !== false && !explicitFreshnessRefresh && genericContinuationQuery && sessionId === void 0 && projectPath !== void 0 && path14.isAbsolute(projectPath);
|
|
20217
20639
|
const retrievalTopK = Math.min(topK * 3, 12);
|
|
20218
|
-
const freshnessRun =
|
|
20640
|
+
const freshnessRun = explicitFreshnessRefresh || autoFreshnessRefresh ? await runLatestImport(memoryService, {
|
|
20219
20641
|
projectPath: projectPath || "",
|
|
20220
20642
|
sources: sourceListArg(args.refreshSources),
|
|
20221
20643
|
sessionLimit: numberArg(args.refreshSessionLimit, 1, 1, 10),
|
|
@@ -20225,10 +20647,8 @@ async function handleMemContextPack(memoryService, args) {
|
|
|
20225
20647
|
sessionsDir: optionalString(args.sessionsDir),
|
|
20226
20648
|
stateDb: optionalString(args.stateDb)
|
|
20227
20649
|
}) : void 0;
|
|
20228
|
-
const
|
|
20229
|
-
|
|
20230
|
-
memoryService.getRecentEvents(recentLimit)
|
|
20231
|
-
]);
|
|
20650
|
+
const search = await retrieveMcpMemories(memoryService, query, { topK: retrievalTopK, sessionId });
|
|
20651
|
+
const recentEvents = await memoryService.getRecentEvents(recentLimit);
|
|
20232
20652
|
const timelineEvents = selectContextPackTimelineEvents(
|
|
20233
20653
|
recentEvents,
|
|
20234
20654
|
projectPath,
|
|
@@ -20236,7 +20656,7 @@ async function handleMemContextPack(memoryService, args) {
|
|
|
20236
20656
|
);
|
|
20237
20657
|
const sessions = summarizeSessions(timelineEvents, sessionLimit);
|
|
20238
20658
|
const recentSessionIds = new Set(sessions.map((session) => session.sessionId));
|
|
20239
|
-
const relevantMemories = selectContextPackMemories(
|
|
20659
|
+
const relevantMemories = selectContextPackMemories(search.memories, {
|
|
20240
20660
|
genericContinuationQuery,
|
|
20241
20661
|
topK,
|
|
20242
20662
|
recentSessionIds,
|
|
@@ -20251,11 +20671,15 @@ async function handleMemContextPack(memoryService, args) {
|
|
|
20251
20671
|
`- Recent sessions shown: ${Math.min(sessionLimit, sessions.length)}`
|
|
20252
20672
|
];
|
|
20253
20673
|
if (freshnessRun) {
|
|
20674
|
+
const refreshMode = autoFreshnessRefresh ? "auto" : "attempted";
|
|
20254
20675
|
lines.push(
|
|
20255
|
-
`- Freshness refresh:
|
|
20676
|
+
`- Freshness refresh: ${refreshMode} before retrieval (${freshnessRun.sources.join(", ")})`,
|
|
20256
20677
|
`- Refresh limits: sessions=${freshnessRun.sessionLimit} messages=${freshnessRun.messageLimit} force=${freshnessRun.force ? "yes" : "no"} embeddings=${freshnessRun.processEmbeddings ? `processed ${freshnessRun.embeddingsProcessed ?? 0}` : "skipped"}`
|
|
20257
20678
|
);
|
|
20258
20679
|
}
|
|
20680
|
+
if (search.warning) {
|
|
20681
|
+
lines.push(`- ${search.warning}`);
|
|
20682
|
+
}
|
|
20259
20683
|
if (genericContinuationQuery) {
|
|
20260
20684
|
lines.push("- Generic continuation query: recent project timeline prioritized.");
|
|
20261
20685
|
}
|
|
@@ -20495,7 +20919,7 @@ function shouldShowContextPackTimelineEvent(event, projectPath, genericContinuat
|
|
|
20495
20919
|
return false;
|
|
20496
20920
|
if (event.eventType === "tool_observation")
|
|
20497
20921
|
return false;
|
|
20498
|
-
if (
|
|
20922
|
+
if (eventBelongsToDifferentProject(event, projectPath))
|
|
20499
20923
|
return false;
|
|
20500
20924
|
if (genericContinuationQuery && isGenericContinuationQuery(content))
|
|
20501
20925
|
return false;
|
|
@@ -20507,7 +20931,7 @@ function shouldShowContextPackMemory(memory, options) {
|
|
|
20507
20931
|
return false;
|
|
20508
20932
|
if (memory.event.eventType === "tool_observation")
|
|
20509
20933
|
return false;
|
|
20510
|
-
if (
|
|
20934
|
+
if (eventBelongsToDifferentProject(memory.event, options.projectPath))
|
|
20511
20935
|
return false;
|
|
20512
20936
|
if (!options.genericContinuationQuery)
|
|
20513
20937
|
return true;
|
|
@@ -20519,6 +20943,54 @@ function shouldShowContextPackMemory(memory, options) {
|
|
|
20519
20943
|
return memory.score >= GENERIC_RECENT_MEMORY_MIN_SCORE;
|
|
20520
20944
|
return memory.score >= GENERIC_STALE_MEMORY_MIN_SCORE;
|
|
20521
20945
|
}
|
|
20946
|
+
function eventBelongsToDifferentProject(event, projectPath) {
|
|
20947
|
+
if (!projectPath)
|
|
20948
|
+
return false;
|
|
20949
|
+
const metadata = event.metadata || {};
|
|
20950
|
+
const metadataProjectRefs = metadataProjectReferenceValues(metadata);
|
|
20951
|
+
if (metadataProjectRefs.length > 0) {
|
|
20952
|
+
return !metadataProjectRefs.some((value) => projectReferenceMatches(value, projectPath));
|
|
20953
|
+
}
|
|
20954
|
+
if (isUnscopedImportedHistory(metadata))
|
|
20955
|
+
return true;
|
|
20956
|
+
return mentionsDifferentWorkspaceProject(event.content || "", projectPath);
|
|
20957
|
+
}
|
|
20958
|
+
function isUnscopedImportedHistory(metadata) {
|
|
20959
|
+
return typeof metadata.importedFrom === "string" || typeof metadata.sourceSessionId === "string" || typeof metadata.sourceSessionHash === "string" || typeof metadata.transcriptPath === "string";
|
|
20960
|
+
}
|
|
20961
|
+
var PROJECT_METADATA_KEYS = /* @__PURE__ */ new Set([
|
|
20962
|
+
"projectPath",
|
|
20963
|
+
"sourceProjectPath",
|
|
20964
|
+
"workspacePath",
|
|
20965
|
+
"sourceWorkspacePath",
|
|
20966
|
+
"cwd",
|
|
20967
|
+
"sourceCwd",
|
|
20968
|
+
"currentWorkingDirectory",
|
|
20969
|
+
"projectRoot",
|
|
20970
|
+
"repoPath",
|
|
20971
|
+
"repositoryPath"
|
|
20972
|
+
]);
|
|
20973
|
+
function metadataProjectReferenceValues(metadata) {
|
|
20974
|
+
const values = [];
|
|
20975
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
20976
|
+
if (!PROJECT_METADATA_KEYS.has(key))
|
|
20977
|
+
continue;
|
|
20978
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
20979
|
+
values.push(value.trim());
|
|
20980
|
+
}
|
|
20981
|
+
}
|
|
20982
|
+
return values;
|
|
20983
|
+
}
|
|
20984
|
+
function projectReferenceMatches(reference, projectPath) {
|
|
20985
|
+
const normalizedReference = normalizeProjectReference(reference);
|
|
20986
|
+
const normalizedProjectPath = normalizeProjectReference(projectPath);
|
|
20987
|
+
if (normalizedReference === normalizedProjectPath)
|
|
20988
|
+
return true;
|
|
20989
|
+
return normalizedReference.startsWith(`${normalizedProjectPath}/`);
|
|
20990
|
+
}
|
|
20991
|
+
function normalizeProjectReference(value) {
|
|
20992
|
+
return value.trim().replace(/\\/g, "/").replace(/\/+$/g, "").toLowerCase();
|
|
20993
|
+
}
|
|
20522
20994
|
function mentionsDifferentWorkspaceProject(content, projectPath) {
|
|
20523
20995
|
const currentProject = basenameOfPath(projectPath);
|
|
20524
20996
|
if (!currentProject)
|
|
@@ -20749,9 +21221,11 @@ function formatMetadataValue(value) {
|
|
|
20749
21221
|
function textResult(text) {
|
|
20750
21222
|
return { content: [{ type: "text", text }] };
|
|
20751
21223
|
}
|
|
20752
|
-
async function handleMemStats(memoryService) {
|
|
21224
|
+
async function handleMemStats(memoryService, args) {
|
|
20753
21225
|
const stats = await memoryService.getStats();
|
|
20754
21226
|
const recentEvents = await memoryService.getRecentEvents(1e4);
|
|
21227
|
+
const outboxStats = await readMcpOutboxStats(memoryService);
|
|
21228
|
+
const storageView = buildMcpStatsStorageView(optionalString(args.projectPath));
|
|
20755
21229
|
const uniqueSessions = new Set(recentEvents.map((e) => e.sessionId));
|
|
20756
21230
|
const lines = [
|
|
20757
21231
|
"## Memory Statistics",
|
|
@@ -20760,6 +21234,19 @@ async function handleMemStats(memoryService) {
|
|
|
20760
21234
|
`- **Total Vectors**: ${stats.vectorCount}`,
|
|
20761
21235
|
`- **Sessions**: ${uniqueSessions.size}`,
|
|
20762
21236
|
"",
|
|
21237
|
+
"### Storage View / Freshness",
|
|
21238
|
+
"",
|
|
21239
|
+
`- Storage View: ${storageView.storageView}`,
|
|
21240
|
+
`- Storage Path Label: ${storageView.storagePathLabel}`,
|
|
21241
|
+
`- Embedder Model: ${storageView.embedderModel}`,
|
|
21242
|
+
`- Vector Table Dimension: ${storageView.vectorTableDimension}`,
|
|
21243
|
+
`- Pending Embeddings: ${outboxStats.embedding.pending}`,
|
|
21244
|
+
`- Embedding Outbox: pending=${outboxStats.embedding.pending}, processing=${outboxStats.embedding.processing}, failed=${outboxStats.embedding.failed}, total=${outboxStats.embedding.total}`,
|
|
21245
|
+
`- Vector Outbox Pending: ${outboxStats.vector.pending}`,
|
|
21246
|
+
`- Vector Outbox: pending=${outboxStats.vector.pending}, processing=${outboxStats.vector.processing}, failed=${outboxStats.vector.failed}, total=${outboxStats.vector.total}`,
|
|
21247
|
+
"- MCP/CLI parity: CLI `stats -p <project>` and MCP `mem-stats(projectPath=...)` should use this same storage view label.",
|
|
21248
|
+
"- Restart guidance: if CLI and MCP counts differ for this storage view after import/build, restart the long-lived MCP/Hermes gateway process.",
|
|
21249
|
+
"",
|
|
20763
21250
|
"### Events by Type",
|
|
20764
21251
|
""
|
|
20765
21252
|
];
|
|
@@ -20774,6 +21261,35 @@ async function handleMemStats(memoryService) {
|
|
|
20774
21261
|
content: [{ type: "text", text: lines.join("\n") }]
|
|
20775
21262
|
};
|
|
20776
21263
|
}
|
|
21264
|
+
async function readMcpOutboxStats(memoryService) {
|
|
21265
|
+
try {
|
|
21266
|
+
return await memoryService.getOutboxStats();
|
|
21267
|
+
} catch {
|
|
21268
|
+
return {
|
|
21269
|
+
embedding: { pending: 0, processing: 0, failed: 0, total: 0 },
|
|
21270
|
+
vector: { pending: 0, processing: 0, failed: 0, total: 0 }
|
|
21271
|
+
};
|
|
21272
|
+
}
|
|
21273
|
+
}
|
|
21274
|
+
function buildMcpStatsStorageView(projectPath) {
|
|
21275
|
+
const embedderModel = process.env.CLAUDE_MEMORY_EMBEDDING_MODEL || DEFAULT_EMBEDDING_MODEL;
|
|
21276
|
+
const requestedProjectPath = projectPath?.trim();
|
|
21277
|
+
if (requestedProjectPath) {
|
|
21278
|
+
const projectHash = hashProjectPath(requestedProjectPath);
|
|
21279
|
+
return {
|
|
21280
|
+
storageView: `project:${projectHash}`,
|
|
21281
|
+
storagePathLabel: `~/.claude-code/memory/projects/${projectHash}`,
|
|
21282
|
+
embedderModel,
|
|
21283
|
+
vectorTableDimension: "unknown (not recorded in current vector metadata)"
|
|
21284
|
+
};
|
|
21285
|
+
}
|
|
21286
|
+
return {
|
|
21287
|
+
storageView: "global",
|
|
21288
|
+
storagePathLabel: "~/.claude-code/memory",
|
|
21289
|
+
embedderModel,
|
|
21290
|
+
vectorTableDimension: "unknown (not recorded in current vector metadata)"
|
|
21291
|
+
};
|
|
21292
|
+
}
|
|
20777
21293
|
|
|
20778
21294
|
// src/extensions/mcp/index.ts
|
|
20779
21295
|
var server = new Server(
|