memorix 1.2.2 → 1.2.3

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.
Files changed (139) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/TEAM.md +86 -86
  3. package/dist/cli/index.js +34 -18
  4. package/dist/cli/index.js.map +1 -1
  5. package/dist/index.js +17 -8
  6. package/dist/index.js.map +1 -1
  7. package/dist/maintenance-runner.js.map +1 -1
  8. package/dist/memcode-runtime/CHANGELOG.md +7 -0
  9. package/dist/sdk.js +17 -8
  10. package/dist/sdk.js.map +1 -1
  11. package/docs/DESIGN_DECISIONS.md +357 -357
  12. package/docs/dev-log/progress.txt +18 -8
  13. package/package.json +1 -1
  14. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  15. package/src/audit/index.ts +156 -156
  16. package/src/cli/commands/audit-list.ts +89 -89
  17. package/src/cli/commands/background.ts +659 -659
  18. package/src/cli/commands/formation.ts +48 -48
  19. package/src/cli/commands/git-hook-install.ts +111 -111
  20. package/src/cli/commands/handoff.ts +54 -54
  21. package/src/cli/commands/hooks-status.ts +63 -63
  22. package/src/cli/commands/ingest-commit.ts +153 -153
  23. package/src/cli/commands/ingest-image.ts +66 -66
  24. package/src/cli/commands/ingest-log.ts +180 -180
  25. package/src/cli/commands/ingest.ts +44 -44
  26. package/src/cli/commands/integrate-shared.ts +15 -15
  27. package/src/cli/commands/lock.ts +82 -82
  28. package/src/cli/commands/message.ts +104 -104
  29. package/src/cli/commands/poll.ts +58 -58
  30. package/src/cli/commands/purge-all-memory.ts +85 -85
  31. package/src/cli/commands/purge-project-memory.ts +83 -83
  32. package/src/cli/commands/reasoning.ts +118 -118
  33. package/src/cli/commands/serve-shared.ts +118 -118
  34. package/src/cli/commands/session.ts +15 -7
  35. package/src/cli/commands/skills.ts +114 -114
  36. package/src/cli/commands/task.ts +167 -167
  37. package/src/cli/commands/transfer.ts +47 -47
  38. package/src/cli/commands/uninstall-project-artifacts.ts +85 -85
  39. package/src/cli/tui/ChatView.tsx +234 -234
  40. package/src/cli/tui/CommandBar.tsx +312 -312
  41. package/src/cli/tui/ContextRail.tsx +118 -118
  42. package/src/cli/tui/HeaderBar.tsx +72 -72
  43. package/src/cli/tui/LogoBanner.tsx +51 -51
  44. package/src/cli/tui/Sidebar.tsx +179 -179
  45. package/src/cli/tui/index.ts +41 -41
  46. package/src/cli/tui/markdown-render.tsx +371 -371
  47. package/src/cli/tui/session-service.ts +3 -2
  48. package/src/cli/tui/use-mouse.ts +157 -157
  49. package/src/cli/tui/useNavigation.ts +56 -56
  50. package/src/cli/update-checker.ts +211 -211
  51. package/src/cli/version.ts +7 -7
  52. package/src/cli/workbench.ts +1 -1
  53. package/src/compact/token-budget.ts +74 -74
  54. package/src/dashboard/project-classification.ts +64 -64
  55. package/src/embedding/fastembed-provider.ts +142 -142
  56. package/src/embedding/transformers-provider.ts +111 -111
  57. package/src/git/extractor.ts +209 -209
  58. package/src/git/hooks-path.ts +85 -85
  59. package/src/hooks/pattern-detector.ts +173 -173
  60. package/src/hooks/significance-filter.ts +250 -250
  61. package/src/llm/memory-manager.ts +328 -328
  62. package/src/llm/provider.ts +885 -885
  63. package/src/llm/quality.ts +248 -248
  64. package/src/memory/attribution-guard.ts +249 -249
  65. package/src/memory/disclosure-policy.ts +135 -135
  66. package/src/memory/entity-extractor.ts +197 -197
  67. package/src/memory/formation/evaluate.ts +217 -217
  68. package/src/memory/formation/extract.ts +361 -361
  69. package/src/memory/formation/index.ts +417 -417
  70. package/src/memory/formation/resolve.ts +344 -344
  71. package/src/memory/formation/types.ts +315 -315
  72. package/src/memory/freshness.ts +122 -122
  73. package/src/memory/graph.ts +197 -197
  74. package/src/memory/refs.ts +94 -94
  75. package/src/memory/secret-filter.ts +79 -79
  76. package/src/memory/session.ts +24 -9
  77. package/src/multimodal/image-loader.ts +143 -143
  78. package/src/orchestrate/adapters/claude-stream.ts +192 -192
  79. package/src/orchestrate/adapters/claude.ts +111 -111
  80. package/src/orchestrate/adapters/codex-stream.ts +134 -134
  81. package/src/orchestrate/adapters/codex.ts +41 -41
  82. package/src/orchestrate/adapters/gemini-stream.ts +166 -166
  83. package/src/orchestrate/adapters/gemini.ts +42 -42
  84. package/src/orchestrate/adapters/index.ts +73 -73
  85. package/src/orchestrate/adapters/opencode-stream.ts +143 -143
  86. package/src/orchestrate/adapters/opencode.ts +47 -47
  87. package/src/orchestrate/adapters/spawn-helper.ts +286 -286
  88. package/src/orchestrate/adapters/types.ts +77 -77
  89. package/src/orchestrate/capability-router.ts +284 -284
  90. package/src/orchestrate/context-compact.ts +188 -188
  91. package/src/orchestrate/cost-tracker.ts +219 -219
  92. package/src/orchestrate/error-recovery.ts +191 -191
  93. package/src/orchestrate/evidence.ts +140 -140
  94. package/src/orchestrate/ledger.ts +110 -110
  95. package/src/orchestrate/memorix-bridge.ts +343 -343
  96. package/src/orchestrate/output-budget.ts +80 -80
  97. package/src/orchestrate/permission.ts +152 -152
  98. package/src/orchestrate/pipeline-trace.ts +131 -131
  99. package/src/orchestrate/prompt-builder.ts +155 -155
  100. package/src/orchestrate/ring-buffer.ts +37 -37
  101. package/src/orchestrate/task-graph.ts +389 -389
  102. package/src/orchestrate/worktree.ts +232 -232
  103. package/src/project/aliases.ts +374 -374
  104. package/src/project/detector.ts +268 -268
  105. package/src/rules/adapters/claude-code.ts +99 -99
  106. package/src/rules/adapters/codex.ts +97 -97
  107. package/src/rules/adapters/copilot.ts +124 -124
  108. package/src/rules/adapters/cursor.ts +114 -114
  109. package/src/rules/adapters/kiro.ts +126 -126
  110. package/src/rules/adapters/trae.ts +56 -56
  111. package/src/rules/adapters/windsurf.ts +83 -83
  112. package/src/rules/syncer.ts +235 -235
  113. package/src/sdk.ts +299 -299
  114. package/src/search/intent-detector.ts +289 -289
  115. package/src/search/query-expansion.ts +52 -52
  116. package/src/server/formation-timeout.ts +27 -27
  117. package/src/server.ts +7 -2
  118. package/src/skills/mini-skills.ts +386 -386
  119. package/src/store/chat-store.ts +119 -119
  120. package/src/store/graph-store.ts +249 -249
  121. package/src/store/mini-skill-store.ts +349 -349
  122. package/src/store/persistence-json.ts +212 -212
  123. package/src/store/persistence.ts +291 -291
  124. package/src/store/project-affinity.ts +195 -195
  125. package/src/team/event-bus.ts +76 -76
  126. package/src/team/file-locks.ts +173 -173
  127. package/src/team/handoff.ts +161 -161
  128. package/src/team/messages.ts +203 -203
  129. package/src/team/poll.ts +132 -132
  130. package/src/team/tasks.ts +211 -211
  131. package/src/workspace/mcp-adapters/codex.ts +191 -191
  132. package/src/workspace/mcp-adapters/copilot.ts +105 -105
  133. package/src/workspace/mcp-adapters/cursor.ts +53 -53
  134. package/src/workspace/mcp-adapters/kiro.ts +64 -64
  135. package/src/workspace/mcp-adapters/opencode.ts +123 -123
  136. package/src/workspace/mcp-adapters/trae.ts +134 -134
  137. package/src/workspace/mcp-adapters/windsurf.ts +91 -91
  138. package/src/workspace/sanitizer.ts +60 -60
  139. package/src/workspace/workflow-sync.ts +131 -131
@@ -1,289 +1,289 @@
1
- /**
2
- * Intent-Aware Recall — Query Intent Detection
3
- *
4
- * Detects the underlying intent of a search query (why/when/how/what)
5
- * and returns type-specific boosting factors to improve recall precision.
6
- *
7
- * Inspired by MemCP's intent routing architecture.
8
- * Uses fast keyword/pattern matching (no LLM needed).
9
- */
10
-
11
- import type { ObservationType } from '../types.js';
12
-
13
- // ─── Types ───
14
-
15
- export type QueryIntent = 'why' | 'when' | 'how' | 'what_changed' | 'problem' | 'general';
16
-
17
- export interface IntentResult {
18
- /** Detected intent category */
19
- intent: QueryIntent;
20
- /** Confidence score 0-1 */
21
- confidence: number;
22
- /** Observation type → boost multiplier (applied to search scores) */
23
- typeBoosts: Partial<Record<ObservationType, number>>;
24
- /** Field weight overrides for Orama search (optional) */
25
- fieldBoosts?: Record<string, number>;
26
- /** Whether to prefer chronological ordering over relevance */
27
- preferChronological: boolean;
28
- /** Source → boost multiplier for source-aware retrieval */
29
- sourceBoosts?: Partial<Record<'agent' | 'git' | 'manual', number>>;
30
- }
31
-
32
- // ─── Intent Patterns ───
33
-
34
- interface IntentPattern {
35
- intent: QueryIntent;
36
- patterns: RegExp[];
37
- /** Higher weight = stronger match when multiple intents match */
38
- weight: number;
39
- }
40
-
41
- const INTENT_PATTERNS: IntentPattern[] = [
42
- {
43
- intent: 'why',
44
- patterns: [
45
- /\bwhy\b/i,
46
- /\breason(?:s|ing)?\b/i,
47
- /\brationale\b/i,
48
- /\bmotivat(?:ion|ed)\b/i,
49
- /\bjustif(?:y|ication)\b/i,
50
- /\bchose|chosen|picked\b/i,
51
- /\bdecid(?:e[ds]?|ing)\b/i,
52
- /\btrade-?off\b/i,
53
- /为什么/,
54
- /原因/,
55
- /理由/,
56
- /为何/,
57
- ],
58
- weight: 1.0,
59
- },
60
- {
61
- intent: 'when',
62
- patterns: [
63
- /\bwhen\b/i,
64
- /\btimeline\b/i,
65
- /\bhistory\b/i,
66
- /\blast\s+(week|month|time|session)\b/i,
67
- /\brecent(?:ly)?\b/i,
68
- /\byesterday\b/i,
69
- /\btoday\b/i,
70
- /\bchronolog/i,
71
- /什么时候/,
72
- /何时/,
73
- /最近/,
74
- /上次/,
75
- ],
76
- weight: 0.9,
77
- },
78
- {
79
- intent: 'how',
80
- patterns: [
81
- /\bhow\s+(does|do|to|is|can|did|should|would)\b/i,
82
- /\barchitecture\b/i,
83
- /\bmechanism\b/i,
84
- /\bimplement(?:ation|ed|ing)?\b/i,
85
- /\bwork(?:s|ing|ed)?\b/i,
86
- /\bexplain\b/i,
87
- /\bunderstand\b/i,
88
- /怎么/,
89
- /如何/,
90
- /机制/,
91
- /原理/,
92
- /架构/,
93
- ],
94
- weight: 0.85,
95
- },
96
- {
97
- intent: 'what_changed',
98
- patterns: [
99
- /\bwhat\s+changed\b/i,
100
- /\bwhat\s+was\s+(modified|updated|changed)\b/i,
101
- /\bdiff(?:erence)?\b/i,
102
- /\bchangelog\b/i,
103
- /\bmodifi(?:ed|cation)\b/i,
104
- /\bupdat(?:e[ds]?|ing)\b/i,
105
- /\brefactor(?:ed|ing)?\b/i,
106
- /改了/,
107
- /修改/,
108
- /变更/,
109
- /变化/,
110
- ],
111
- weight: 0.8,
112
- },
113
- {
114
- intent: 'problem',
115
- patterns: [
116
- /\bbug(?:s|gy)?\b/i,
117
- /\berror(?:s)?\b/i,
118
- /\bfix(?:e[ds]|ing)?\b/i,
119
- /\bissue(?:s)?\b/i,
120
- /\bproblem(?:s)?\b/i,
121
- /\bcrash(?:e[ds]|ing)?\b/i,
122
- /\bfail(?:e[ds]|ure|ing)?\b/i,
123
- /\bbroken\b/i,
124
- /\bgotcha\b/i,
125
- /\bpitfall\b/i,
126
- /\bworkaround\b/i,
127
- /\btroubleshoot/i,
128
- /\bdebug(?:ging)?\b/i,
129
- /报错/,
130
- /问题/,
131
- /修复/,
132
- /故障/,
133
- /异常/,
134
- ],
135
- weight: 0.9,
136
- },
137
- ];
138
-
139
- // ─── Type Boost Maps ───
140
-
141
- const INTENT_TYPE_BOOSTS: Record<QueryIntent, Partial<Record<ObservationType, number>>> = {
142
- why: {
143
- 'decision': 3.0,
144
- 'why-it-exists': 3.0,
145
- 'trade-off': 2.5,
146
- 'how-it-works': 1.2,
147
- },
148
- when: {
149
- // Temporal queries don't strongly prefer any type — they prefer recency
150
- 'what-changed': 1.5,
151
- 'session-request': 1.3,
152
- },
153
- how: {
154
- 'how-it-works': 3.0,
155
- 'discovery': 2.0,
156
- 'decision': 1.3,
157
- },
158
- what_changed: {
159
- 'what-changed': 3.0,
160
- 'discovery': 1.5,
161
- 'session-request': 1.2,
162
- },
163
- problem: {
164
- 'problem-solution': 3.0,
165
- 'gotcha': 2.5,
166
- 'discovery': 1.3,
167
- },
168
- general: {
169
- // No special boosting
170
- },
171
- };
172
-
173
- // ─── Source Boost Maps ───
174
- // Maps intent to preferred memory sources for source-aware retrieval.
175
- // git source = ground truth (commits), agent source = reasoning/decisions.
176
-
177
- const INTENT_SOURCE_BOOSTS: Partial<Record<QueryIntent, Partial<Record<'agent' | 'git' | 'manual', number>>>> = {
178
- what_changed: {
179
- git: 2.0, // "what changed" → prefer commit-derived ground truth
180
- agent: 0.8,
181
- },
182
- when: {
183
- git: 1.8, // Temporal queries → git has precise timestamps
184
- agent: 1.0,
185
- },
186
- why: {
187
- agent: 2.0, // "why" → prefer reasoning/decision memories
188
- git: 0.7, // commits rarely explain WHY
189
- },
190
- how: {
191
- agent: 1.5, // "how" → prefer explanations
192
- git: 1.0,
193
- },
194
- problem: {
195
- git: 1.5, // Bug fixes often in commit history
196
- agent: 1.5, // But also in problem-solution memories
197
- },
198
- };
199
-
200
- const INTENT_FIELD_BOOSTS: Partial<Record<QueryIntent, Record<string, number>>> = {
201
- why: {
202
- title: 2,
203
- entityName: 1.5,
204
- narrative: 2.5, // WHY queries need narrative context
205
- facts: 1.5,
206
- concepts: 1,
207
- filesModified: 0.3,
208
- },
209
- problem: {
210
- title: 3,
211
- entityName: 2,
212
- narrative: 2,
213
- facts: 2, // Bug details often in facts
214
- concepts: 1,
215
- filesModified: 1.5, // File paths help find the right bug fix
216
- },
217
- };
218
-
219
- // ─── Detection ───
220
-
221
- /**
222
- * Detect the intent of a search query.
223
- *
224
- * Returns the best matching intent with confidence score and
225
- * type-specific boosting factors to apply during search.
226
- */
227
- export function detectQueryIntent(query: string): IntentResult {
228
- if (!query || query.length < 2) {
229
- return {
230
- intent: 'general',
231
- confidence: 0,
232
- typeBoosts: {},
233
- preferChronological: false,
234
- };
235
- }
236
-
237
- let bestIntent: QueryIntent = 'general';
238
- let bestScore = 0;
239
- let totalMatches = 0;
240
-
241
- for (const { intent, patterns, weight } of INTENT_PATTERNS) {
242
- let matchCount = 0;
243
- for (const pattern of patterns) {
244
- if (pattern.test(query)) matchCount++;
245
- }
246
- if (matchCount > 0) {
247
- const score = matchCount * weight;
248
- totalMatches += matchCount;
249
- if (score > bestScore) {
250
- bestScore = score;
251
- bestIntent = intent;
252
- }
253
- }
254
- }
255
-
256
- // Confidence: how strongly did we match vs. random noise
257
- const confidence = totalMatches === 0
258
- ? 0
259
- : Math.min(1, bestScore / 2); // 2+ pattern matches → high confidence
260
-
261
- return {
262
- intent: bestIntent,
263
- confidence,
264
- typeBoosts: INTENT_TYPE_BOOSTS[bestIntent],
265
- fieldBoosts: INTENT_FIELD_BOOSTS[bestIntent],
266
- sourceBoosts: INTENT_SOURCE_BOOSTS[bestIntent],
267
- preferChronological: bestIntent === 'when',
268
- };
269
- }
270
-
271
- /**
272
- * Apply intent-based type boosting to a search result's score.
273
- *
274
- * @param score Original search score
275
- * @param type Observation type of the result
276
- * @param intentResult Detected intent from detectQueryIntent()
277
- * @returns Boosted score
278
- */
279
- export function applyIntentBoost(
280
- score: number,
281
- type: string,
282
- intentResult: IntentResult,
283
- ): number {
284
- if (intentResult.confidence < 0.3) return score; // Low confidence → no boost
285
- const boost = intentResult.typeBoosts[type as ObservationType] ?? 1.0;
286
- // Scale boost by confidence: full boost at confidence=1, partial at lower
287
- const effectiveBoost = 1 + (boost - 1) * intentResult.confidence;
288
- return score * effectiveBoost;
289
- }
1
+ /**
2
+ * Intent-Aware Recall — Query Intent Detection
3
+ *
4
+ * Detects the underlying intent of a search query (why/when/how/what)
5
+ * and returns type-specific boosting factors to improve recall precision.
6
+ *
7
+ * Inspired by MemCP's intent routing architecture.
8
+ * Uses fast keyword/pattern matching (no LLM needed).
9
+ */
10
+
11
+ import type { ObservationType } from '../types.js';
12
+
13
+ // ─── Types ───
14
+
15
+ export type QueryIntent = 'why' | 'when' | 'how' | 'what_changed' | 'problem' | 'general';
16
+
17
+ export interface IntentResult {
18
+ /** Detected intent category */
19
+ intent: QueryIntent;
20
+ /** Confidence score 0-1 */
21
+ confidence: number;
22
+ /** Observation type → boost multiplier (applied to search scores) */
23
+ typeBoosts: Partial<Record<ObservationType, number>>;
24
+ /** Field weight overrides for Orama search (optional) */
25
+ fieldBoosts?: Record<string, number>;
26
+ /** Whether to prefer chronological ordering over relevance */
27
+ preferChronological: boolean;
28
+ /** Source → boost multiplier for source-aware retrieval */
29
+ sourceBoosts?: Partial<Record<'agent' | 'git' | 'manual', number>>;
30
+ }
31
+
32
+ // ─── Intent Patterns ───
33
+
34
+ interface IntentPattern {
35
+ intent: QueryIntent;
36
+ patterns: RegExp[];
37
+ /** Higher weight = stronger match when multiple intents match */
38
+ weight: number;
39
+ }
40
+
41
+ const INTENT_PATTERNS: IntentPattern[] = [
42
+ {
43
+ intent: 'why',
44
+ patterns: [
45
+ /\bwhy\b/i,
46
+ /\breason(?:s|ing)?\b/i,
47
+ /\brationale\b/i,
48
+ /\bmotivat(?:ion|ed)\b/i,
49
+ /\bjustif(?:y|ication)\b/i,
50
+ /\bchose|chosen|picked\b/i,
51
+ /\bdecid(?:e[ds]?|ing)\b/i,
52
+ /\btrade-?off\b/i,
53
+ /为什么/,
54
+ /原因/,
55
+ /理由/,
56
+ /为何/,
57
+ ],
58
+ weight: 1.0,
59
+ },
60
+ {
61
+ intent: 'when',
62
+ patterns: [
63
+ /\bwhen\b/i,
64
+ /\btimeline\b/i,
65
+ /\bhistory\b/i,
66
+ /\blast\s+(week|month|time|session)\b/i,
67
+ /\brecent(?:ly)?\b/i,
68
+ /\byesterday\b/i,
69
+ /\btoday\b/i,
70
+ /\bchronolog/i,
71
+ /什么时候/,
72
+ /何时/,
73
+ /最近/,
74
+ /上次/,
75
+ ],
76
+ weight: 0.9,
77
+ },
78
+ {
79
+ intent: 'how',
80
+ patterns: [
81
+ /\bhow\s+(does|do|to|is|can|did|should|would)\b/i,
82
+ /\barchitecture\b/i,
83
+ /\bmechanism\b/i,
84
+ /\bimplement(?:ation|ed|ing)?\b/i,
85
+ /\bwork(?:s|ing|ed)?\b/i,
86
+ /\bexplain\b/i,
87
+ /\bunderstand\b/i,
88
+ /怎么/,
89
+ /如何/,
90
+ /机制/,
91
+ /原理/,
92
+ /架构/,
93
+ ],
94
+ weight: 0.85,
95
+ },
96
+ {
97
+ intent: 'what_changed',
98
+ patterns: [
99
+ /\bwhat\s+changed\b/i,
100
+ /\bwhat\s+was\s+(modified|updated|changed)\b/i,
101
+ /\bdiff(?:erence)?\b/i,
102
+ /\bchangelog\b/i,
103
+ /\bmodifi(?:ed|cation)\b/i,
104
+ /\bupdat(?:e[ds]?|ing)\b/i,
105
+ /\brefactor(?:ed|ing)?\b/i,
106
+ /改了/,
107
+ /修改/,
108
+ /变更/,
109
+ /变化/,
110
+ ],
111
+ weight: 0.8,
112
+ },
113
+ {
114
+ intent: 'problem',
115
+ patterns: [
116
+ /\bbug(?:s|gy)?\b/i,
117
+ /\berror(?:s)?\b/i,
118
+ /\bfix(?:e[ds]|ing)?\b/i,
119
+ /\bissue(?:s)?\b/i,
120
+ /\bproblem(?:s)?\b/i,
121
+ /\bcrash(?:e[ds]|ing)?\b/i,
122
+ /\bfail(?:e[ds]|ure|ing)?\b/i,
123
+ /\bbroken\b/i,
124
+ /\bgotcha\b/i,
125
+ /\bpitfall\b/i,
126
+ /\bworkaround\b/i,
127
+ /\btroubleshoot/i,
128
+ /\bdebug(?:ging)?\b/i,
129
+ /报错/,
130
+ /问题/,
131
+ /修复/,
132
+ /故障/,
133
+ /异常/,
134
+ ],
135
+ weight: 0.9,
136
+ },
137
+ ];
138
+
139
+ // ─── Type Boost Maps ───
140
+
141
+ const INTENT_TYPE_BOOSTS: Record<QueryIntent, Partial<Record<ObservationType, number>>> = {
142
+ why: {
143
+ 'decision': 3.0,
144
+ 'why-it-exists': 3.0,
145
+ 'trade-off': 2.5,
146
+ 'how-it-works': 1.2,
147
+ },
148
+ when: {
149
+ // Temporal queries don't strongly prefer any type — they prefer recency
150
+ 'what-changed': 1.5,
151
+ 'session-request': 1.3,
152
+ },
153
+ how: {
154
+ 'how-it-works': 3.0,
155
+ 'discovery': 2.0,
156
+ 'decision': 1.3,
157
+ },
158
+ what_changed: {
159
+ 'what-changed': 3.0,
160
+ 'discovery': 1.5,
161
+ 'session-request': 1.2,
162
+ },
163
+ problem: {
164
+ 'problem-solution': 3.0,
165
+ 'gotcha': 2.5,
166
+ 'discovery': 1.3,
167
+ },
168
+ general: {
169
+ // No special boosting
170
+ },
171
+ };
172
+
173
+ // ─── Source Boost Maps ───
174
+ // Maps intent to preferred memory sources for source-aware retrieval.
175
+ // git source = ground truth (commits), agent source = reasoning/decisions.
176
+
177
+ const INTENT_SOURCE_BOOSTS: Partial<Record<QueryIntent, Partial<Record<'agent' | 'git' | 'manual', number>>>> = {
178
+ what_changed: {
179
+ git: 2.0, // "what changed" → prefer commit-derived ground truth
180
+ agent: 0.8,
181
+ },
182
+ when: {
183
+ git: 1.8, // Temporal queries → git has precise timestamps
184
+ agent: 1.0,
185
+ },
186
+ why: {
187
+ agent: 2.0, // "why" → prefer reasoning/decision memories
188
+ git: 0.7, // commits rarely explain WHY
189
+ },
190
+ how: {
191
+ agent: 1.5, // "how" → prefer explanations
192
+ git: 1.0,
193
+ },
194
+ problem: {
195
+ git: 1.5, // Bug fixes often in commit history
196
+ agent: 1.5, // But also in problem-solution memories
197
+ },
198
+ };
199
+
200
+ const INTENT_FIELD_BOOSTS: Partial<Record<QueryIntent, Record<string, number>>> = {
201
+ why: {
202
+ title: 2,
203
+ entityName: 1.5,
204
+ narrative: 2.5, // WHY queries need narrative context
205
+ facts: 1.5,
206
+ concepts: 1,
207
+ filesModified: 0.3,
208
+ },
209
+ problem: {
210
+ title: 3,
211
+ entityName: 2,
212
+ narrative: 2,
213
+ facts: 2, // Bug details often in facts
214
+ concepts: 1,
215
+ filesModified: 1.5, // File paths help find the right bug fix
216
+ },
217
+ };
218
+
219
+ // ─── Detection ───
220
+
221
+ /**
222
+ * Detect the intent of a search query.
223
+ *
224
+ * Returns the best matching intent with confidence score and
225
+ * type-specific boosting factors to apply during search.
226
+ */
227
+ export function detectQueryIntent(query: string): IntentResult {
228
+ if (!query || query.length < 2) {
229
+ return {
230
+ intent: 'general',
231
+ confidence: 0,
232
+ typeBoosts: {},
233
+ preferChronological: false,
234
+ };
235
+ }
236
+
237
+ let bestIntent: QueryIntent = 'general';
238
+ let bestScore = 0;
239
+ let totalMatches = 0;
240
+
241
+ for (const { intent, patterns, weight } of INTENT_PATTERNS) {
242
+ let matchCount = 0;
243
+ for (const pattern of patterns) {
244
+ if (pattern.test(query)) matchCount++;
245
+ }
246
+ if (matchCount > 0) {
247
+ const score = matchCount * weight;
248
+ totalMatches += matchCount;
249
+ if (score > bestScore) {
250
+ bestScore = score;
251
+ bestIntent = intent;
252
+ }
253
+ }
254
+ }
255
+
256
+ // Confidence: how strongly did we match vs. random noise
257
+ const confidence = totalMatches === 0
258
+ ? 0
259
+ : Math.min(1, bestScore / 2); // 2+ pattern matches → high confidence
260
+
261
+ return {
262
+ intent: bestIntent,
263
+ confidence,
264
+ typeBoosts: INTENT_TYPE_BOOSTS[bestIntent],
265
+ fieldBoosts: INTENT_FIELD_BOOSTS[bestIntent],
266
+ sourceBoosts: INTENT_SOURCE_BOOSTS[bestIntent],
267
+ preferChronological: bestIntent === 'when',
268
+ };
269
+ }
270
+
271
+ /**
272
+ * Apply intent-based type boosting to a search result's score.
273
+ *
274
+ * @param score Original search score
275
+ * @param type Observation type of the result
276
+ * @param intentResult Detected intent from detectQueryIntent()
277
+ * @returns Boosted score
278
+ */
279
+ export function applyIntentBoost(
280
+ score: number,
281
+ type: string,
282
+ intentResult: IntentResult,
283
+ ): number {
284
+ if (intentResult.confidence < 0.3) return score; // Low confidence → no boost
285
+ const boost = intentResult.typeBoosts[type as ObservationType] ?? 1.0;
286
+ // Scale boost by confidence: full boost at confidence=1, partial at lower
287
+ const effectiveBoost = 1 + (boost - 1) * intentResult.confidence;
288
+ return score * effectiveBoost;
289
+ }
@@ -1,52 +1,52 @@
1
- import { callLLM, initLLM, isLLMEnabled } from '../llm/provider.js';
2
-
3
- const CJK_PATTERN = /[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/g;
4
- const COMMAND_LIKE_QUERY = /\b(git|npm|npx|pnpm|yarn|node|bash|powershell|curl|memorix)\b/i;
5
-
6
- const QUERY_EXPANSION_PROMPT = `You rewrite coding-memory search queries for retrieval.
7
-
8
- Rules:
9
- - Input may be Chinese, Japanese, or Korean
10
- - Output one short English search phrase only
11
- - Keep the technical meaning
12
- - Prefer wording that would match engineering notes or memory titles
13
- - No bullets, no JSON, no explanation, no quotes
14
- - 4 to 12 words is ideal`;
15
-
16
- function isCjkHeavy(query: string): boolean {
17
- const cjkCount = (query.match(CJK_PATTERN) || []).length;
18
- return query.length > 0 && cjkCount / query.length > 0.3;
19
- }
20
-
21
- function normalizeExpansion(text: string): string {
22
- return text
23
- .trim()
24
- .replace(/^[-*]\s*/, '')
25
- .replace(/^["'`]+|["'`]+$/g, '')
26
- .split(/\r?\n/)[0]
27
- .trim();
28
- }
29
-
30
- export async function maybeExpandSearchQuery(query: string): Promise<string> {
31
- if (!query || !isCjkHeavy(query) || COMMAND_LIKE_QUERY.test(query)) {
32
- return query;
33
- }
34
-
35
- if (!isLLMEnabled()) {
36
- initLLM();
37
- }
38
-
39
- if (!isLLMEnabled()) {
40
- return query;
41
- }
42
-
43
- try {
44
- const response = await callLLM(QUERY_EXPANSION_PROMPT, query);
45
- const expanded = normalizeExpansion(response.content);
46
- if (!expanded) return query;
47
- if (expanded.toLowerCase() === query.toLowerCase()) return query;
48
- return `${query} ${expanded}`.slice(0, 500);
49
- } catch {
50
- return query;
51
- }
52
- }
1
+ import { callLLM, initLLM, isLLMEnabled } from '../llm/provider.js';
2
+
3
+ const CJK_PATTERN = /[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/g;
4
+ const COMMAND_LIKE_QUERY = /\b(git|npm|npx|pnpm|yarn|node|bash|powershell|curl|memorix)\b/i;
5
+
6
+ const QUERY_EXPANSION_PROMPT = `You rewrite coding-memory search queries for retrieval.
7
+
8
+ Rules:
9
+ - Input may be Chinese, Japanese, or Korean
10
+ - Output one short English search phrase only
11
+ - Keep the technical meaning
12
+ - Prefer wording that would match engineering notes or memory titles
13
+ - No bullets, no JSON, no explanation, no quotes
14
+ - 4 to 12 words is ideal`;
15
+
16
+ function isCjkHeavy(query: string): boolean {
17
+ const cjkCount = (query.match(CJK_PATTERN) || []).length;
18
+ return query.length > 0 && cjkCount / query.length > 0.3;
19
+ }
20
+
21
+ function normalizeExpansion(text: string): string {
22
+ return text
23
+ .trim()
24
+ .replace(/^[-*]\s*/, '')
25
+ .replace(/^["'`]+|["'`]+$/g, '')
26
+ .split(/\r?\n/)[0]
27
+ .trim();
28
+ }
29
+
30
+ export async function maybeExpandSearchQuery(query: string): Promise<string> {
31
+ if (!query || !isCjkHeavy(query) || COMMAND_LIKE_QUERY.test(query)) {
32
+ return query;
33
+ }
34
+
35
+ if (!isLLMEnabled()) {
36
+ initLLM();
37
+ }
38
+
39
+ if (!isLLMEnabled()) {
40
+ return query;
41
+ }
42
+
43
+ try {
44
+ const response = await callLLM(QUERY_EXPANSION_PROMPT, query);
45
+ const expanded = normalizeExpansion(response.content);
46
+ if (!expanded) return query;
47
+ if (expanded.toLowerCase() === query.toLowerCase()) return query;
48
+ return `${query} ${expanded}`.slice(0, 500);
49
+ } catch {
50
+ return query;
51
+ }
52
+ }