claude-brain 0.30.2 → 0.30.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 (236) hide show
  1. package/README.md +241 -191
  2. package/VERSION +1 -1
  3. package/assets/CLAUDE-unified.md +11 -11
  4. package/assets/CLAUDE.md +29 -29
  5. package/package.json +7 -3
  6. package/packs/backend/node.json +173 -173
  7. package/packs/core/javascript.json +176 -176
  8. package/packs/core/typescript.json +222 -222
  9. package/packs/frontend/react.json +254 -254
  10. package/packs/meta/testing.json +172 -172
  11. package/scripts/postinstall.mjs +531 -531
  12. package/src/automation/decision-detector.ts +452 -452
  13. package/src/automation/phase12-manager.ts +456 -456
  14. package/src/automation/proactive-recall.ts +373 -373
  15. package/src/automation/project-detector.ts +310 -310
  16. package/src/automation/repo-scanner.ts +210 -205
  17. package/src/cli/auto-setup.ts +75 -75
  18. package/src/cli/auto-start.ts +266 -266
  19. package/src/cli/bin.ts +264 -264
  20. package/src/cli/commands/autostart.ts +90 -90
  21. package/src/cli/commands/chroma.ts +578 -577
  22. package/src/cli/commands/export-training.ts +70 -70
  23. package/src/cli/commands/export.ts +130 -130
  24. package/src/cli/commands/git-hook.ts +183 -183
  25. package/src/cli/commands/hooks.ts +217 -217
  26. package/src/cli/commands/init.ts +123 -123
  27. package/src/cli/commands/install-mcp.ts +122 -111
  28. package/src/cli/commands/models.ts +979 -979
  29. package/src/cli/commands/pack.ts +200 -200
  30. package/src/cli/commands/refresh.ts +344 -339
  31. package/src/cli/commands/reindex.ts +120 -120
  32. package/src/cli/commands/serve.ts +466 -463
  33. package/src/cli/commands/start.ts +44 -44
  34. package/src/cli/commands/status.ts +220 -203
  35. package/src/cli/commands/uninstall-mcp.ts +45 -41
  36. package/src/cli/commands/update.ts +130 -124
  37. package/src/cli/migrate-chroma.ts +106 -106
  38. package/src/cli/ui/animations.ts +80 -80
  39. package/src/cli/ui/components.ts +82 -82
  40. package/src/cli/ui/index.ts +4 -4
  41. package/src/cli/ui/logo.ts +36 -36
  42. package/src/cli/ui/theme.ts +55 -55
  43. package/src/code-intelligence/indexer.ts +352 -352
  44. package/src/code-intelligence/linker.ts +178 -178
  45. package/src/code-intelligence/parser.ts +484 -484
  46. package/src/code-intelligence/query.ts +291 -291
  47. package/src/code-intelligence/schema.ts +83 -83
  48. package/src/code-intelligence/types.ts +95 -95
  49. package/src/config/defaults.ts +52 -52
  50. package/src/config/home.ts +56 -56
  51. package/src/config/index.ts +5 -5
  52. package/src/config/loader.ts +192 -192
  53. package/src/config/schema.ts +446 -415
  54. package/src/config/validator.ts +182 -182
  55. package/src/context/assembler.ts +407 -400
  56. package/src/context/index.ts +79 -79
  57. package/src/context/progress-tracker.ts +174 -174
  58. package/src/context/standards-manager.ts +287 -287
  59. package/src/context/validator.ts +58 -58
  60. package/src/diagnostics/index.ts +122 -121
  61. package/src/health/index.ts +233 -232
  62. package/src/hooks/brain-hook.ts +134 -131
  63. package/src/hooks/capture.ts +168 -168
  64. package/src/hooks/claude-code-mastery.md +112 -112
  65. package/src/hooks/context-hook.ts +260 -245
  66. package/src/hooks/deduplicator.ts +72 -72
  67. package/src/hooks/git-capture.ts +109 -109
  68. package/src/hooks/git-hook-installer.ts +211 -207
  69. package/src/hooks/index.ts +20 -20
  70. package/src/hooks/installer.ts +306 -288
  71. package/src/hooks/interceptor-hook.ts +204 -201
  72. package/src/hooks/passive-classifier.ts +397 -397
  73. package/src/hooks/queue.ts +160 -129
  74. package/src/hooks/session-tracker.ts +312 -312
  75. package/src/hooks/types.ts +52 -52
  76. package/src/index.ts +7 -7
  77. package/src/intelligence/cross-project/generalizer.ts +283 -283
  78. package/src/intelligence/cross-project/index.ts +7 -7
  79. package/src/intelligence/hf-downloader.ts +222 -222
  80. package/src/intelligence/hf-manifest.json +78 -78
  81. package/src/intelligence/index.ts +24 -24
  82. package/src/intelligence/inference-router.ts +762 -762
  83. package/src/intelligence/model-manager.ts +263 -245
  84. package/src/intelligence/optimization/index.ts +10 -10
  85. package/src/intelligence/optimization/precompute.ts +202 -202
  86. package/src/intelligence/optimization/semantic-cache.ts +213 -207
  87. package/src/intelligence/prediction/index.ts +7 -7
  88. package/src/intelligence/prediction/recommender.ts +276 -268
  89. package/src/intelligence/reasoning/chain-retrieval.ts +243 -247
  90. package/src/intelligence/reasoning/index.ts +7 -7
  91. package/src/intelligence/temporal/evolution.ts +193 -197
  92. package/src/intelligence/temporal/index.ts +16 -16
  93. package/src/intelligence/temporal/query-processor.ts +190 -190
  94. package/src/intelligence/temporal/timeline.ts +272 -259
  95. package/src/intelligence/temporal/trends.ts +263 -263
  96. package/src/intelligence/tokenizer.ts +118 -118
  97. package/src/knowledge/entity-extractor.ts +447 -443
  98. package/src/knowledge/graph/builder.ts +185 -185
  99. package/src/knowledge/graph/linker.ts +201 -201
  100. package/src/knowledge/graph/memory-graph.ts +359 -359
  101. package/src/knowledge/graph/schema.ts +99 -99
  102. package/src/knowledge/graph/search.ts +166 -166
  103. package/src/knowledge/relationship-extractor.ts +108 -108
  104. package/src/memory/chroma/client.ts +211 -192
  105. package/src/memory/chroma/collection-manager.ts +92 -92
  106. package/src/memory/chroma/config.ts +57 -57
  107. package/src/memory/chroma/embeddings.ts +177 -175
  108. package/src/memory/chroma/index.ts +82 -82
  109. package/src/memory/chroma/migration.ts +270 -270
  110. package/src/memory/chroma/schemas.ts +69 -69
  111. package/src/memory/chroma/search.ts +319 -315
  112. package/src/memory/chroma/store.ts +755 -747
  113. package/src/memory/compression.ts +121 -121
  114. package/src/memory/consolidation/archiver.ts +162 -165
  115. package/src/memory/consolidation/merger.ts +182 -186
  116. package/src/memory/consolidation/scorer.ts +136 -136
  117. package/src/memory/database.ts +9 -0
  118. package/src/memory/dual-write.ts +145 -0
  119. package/src/memory/embeddings.ts +226 -226
  120. package/src/memory/episodic/detector.ts +108 -108
  121. package/src/memory/episodic/manager.ts +347 -351
  122. package/src/memory/episodic/summarizer.ts +179 -179
  123. package/src/memory/episodic/types.ts +52 -52
  124. package/src/memory/fts5-search.ts +692 -633
  125. package/src/memory/index.ts +943 -1060
  126. package/src/memory/migrations/add-fts5.ts +118 -108
  127. package/src/memory/patterns.ts +438 -438
  128. package/src/memory/pruning.ts +60 -60
  129. package/src/memory/schema.ts +88 -88
  130. package/src/memory/store.ts +911 -787
  131. package/src/orchestrator/handlers/decision-handler.ts +204 -204
  132. package/src/packs/index.ts +9 -9
  133. package/src/packs/loader.ts +134 -134
  134. package/src/packs/manager.ts +204 -204
  135. package/src/packs/ranker.ts +78 -78
  136. package/src/packs/types.ts +81 -81
  137. package/src/phase12/index.ts +5 -5
  138. package/src/retrieval/bm25/index.ts +300 -297
  139. package/src/retrieval/bm25/tokenizer.ts +184 -184
  140. package/src/retrieval/feedback/adaptive.ts +221 -221
  141. package/src/retrieval/feedback/index.ts +16 -16
  142. package/src/retrieval/feedback/metrics.ts +221 -221
  143. package/src/retrieval/feedback/store.ts +283 -283
  144. package/src/retrieval/fusion/index.ts +194 -194
  145. package/src/retrieval/fusion/rrf.ts +165 -165
  146. package/src/retrieval/index.ts +12 -12
  147. package/src/retrieval/pipeline.ts +375 -375
  148. package/src/retrieval/query/expander.ts +203 -203
  149. package/src/retrieval/query/index.ts +27 -27
  150. package/src/retrieval/query/intent-classifier.ts +252 -252
  151. package/src/retrieval/query/temporal-parser.ts +295 -295
  152. package/src/retrieval/reranker/index.ts +189 -188
  153. package/src/retrieval/reranker/model.ts +99 -95
  154. package/src/retrieval/service.ts +125 -125
  155. package/src/retrieval/types.ts +162 -162
  156. package/src/routing/entity-extractor.ts +454 -454
  157. package/src/routing/handlers/exploration-handler.ts +369 -0
  158. package/src/routing/handlers/index.ts +19 -0
  159. package/src/routing/handlers/memory-handler.ts +273 -0
  160. package/src/routing/handlers/mutation-handler.ts +241 -0
  161. package/src/routing/handlers/recall-handler.ts +642 -0
  162. package/src/routing/handlers/shared.ts +515 -0
  163. package/src/routing/handlers/types.ts +48 -0
  164. package/src/routing/intent-classifier.ts +552 -552
  165. package/src/routing/response-filter.ts +399 -391
  166. package/src/routing/router.ts +245 -2193
  167. package/src/routing/search-engine.ts +521 -514
  168. package/src/routing/types.ts +104 -94
  169. package/src/scripts/health-check.ts +118 -118
  170. package/src/scripts/setup.ts +122 -122
  171. package/src/server/auto-updater.ts +283 -276
  172. package/src/server/handlers/call-tool.ts +159 -159
  173. package/src/server/handlers/list-tools.ts +35 -35
  174. package/src/server/handlers/tools/auto-remember.ts +165 -165
  175. package/src/server/handlers/tools/brain.ts +86 -86
  176. package/src/server/handlers/tools/create-project.ts +135 -135
  177. package/src/server/handlers/tools/get-code-standards.ts +123 -123
  178. package/src/server/handlers/tools/get-corrections.ts +152 -152
  179. package/src/server/handlers/tools/get-patterns.ts +156 -156
  180. package/src/server/handlers/tools/get-project-context.ts +75 -75
  181. package/src/server/handlers/tools/index.ts +30 -30
  182. package/src/server/handlers/tools/init-project.ts +756 -756
  183. package/src/server/handlers/tools/list-projects.ts +126 -126
  184. package/src/server/handlers/tools/recall-similar.ts +87 -87
  185. package/src/server/handlers/tools/recognize-pattern.ts +132 -132
  186. package/src/server/handlers/tools/record-correction.ts +131 -131
  187. package/src/server/handlers/tools/remember-decision.ts +168 -168
  188. package/src/server/handlers/tools/schemas.ts +179 -179
  189. package/src/server/handlers/tools/search-code.ts +122 -122
  190. package/src/server/handlers/tools/smart-context.ts +146 -146
  191. package/src/server/handlers/tools/update-progress.ts +131 -131
  192. package/src/server/http-api.ts +215 -1229
  193. package/src/server/mcp-proxy.ts +85 -84
  194. package/src/server/mcp-server.ts +285 -284
  195. package/src/server/middleware/auth.ts +39 -0
  196. package/src/server/middleware/error-handler.ts +37 -0
  197. package/src/server/middleware/rate-limit.ts +53 -0
  198. package/src/server/middleware/validate.ts +42 -0
  199. package/src/server/pid-manager.ts +137 -136
  200. package/src/server/providers/resources.ts +581 -581
  201. package/src/server/routes/code.ts +228 -0
  202. package/src/server/routes/context.ts +26 -0
  203. package/src/server/routes/health.ts +19 -0
  204. package/src/server/routes/helpers.ts +100 -0
  205. package/src/server/routes/hooks.ts +197 -0
  206. package/src/server/routes/mcp.ts +47 -0
  207. package/src/server/routes/memory.ts +397 -0
  208. package/src/server/routes/models.ts +96 -0
  209. package/src/server/routes/projects.ts +89 -0
  210. package/src/server/routes/types.ts +21 -0
  211. package/src/server/schemas/api-schemas.ts +202 -0
  212. package/src/server/services.ts +720 -720
  213. package/src/server/utils/memory-indicator.ts +84 -84
  214. package/src/server/utils/response-formatter.ts +129 -129
  215. package/src/server/web-viewer.ts +1145 -1115
  216. package/src/setup/index.ts +38 -38
  217. package/src/tools/registry.ts +115 -115
  218. package/src/tools/schemas.ts +666 -666
  219. package/src/tools/types.ts +412 -412
  220. package/src/training/data-store.ts +320 -298
  221. package/src/training/retrain-pipeline.ts +399 -394
  222. package/src/utils/error-handler.ts +136 -136
  223. package/src/utils/index.ts +58 -58
  224. package/src/utils/kill-port.ts +55 -53
  225. package/src/utils/phase12-helper.ts +56 -56
  226. package/src/utils/safe-path.ts +43 -0
  227. package/src/utils/timing.ts +47 -47
  228. package/src/utils/transaction.ts +63 -63
  229. package/src/vault/index.ts +4 -3
  230. package/src/vault/paths.ts +106 -106
  231. package/src/vault/query.ts +4 -1
  232. package/src/vault/reader.ts +44 -1
  233. package/src/vault/watcher.ts +24 -1
  234. package/src/vault/writer.ts +487 -413
  235. package/skills/persistent-memory/SKILL.md +0 -148
  236. package/skills/persistent-memory/references/tool-reference.md +0 -90
@@ -1,552 +1,552 @@
1
- /**
2
- * Brain Intent Classifier
3
- * Phase 16 + Phase 19: Rule-based intent classification for the unified brain() tool
4
- *
5
- * Priority-ordered — first confident match wins.
6
- * No LLM calls, pure pattern matching.
7
- *
8
- * Phase 19 changes:
9
- * - B1: Question confidence raised (? → 0.95, question word → 0.90)
10
- * - B2: session_start narrowed (must be at start + no progress indicators)
11
- * - B3: Temporal signal detection added to secondary intents
12
- */
13
-
14
- export type Intent =
15
- | 'session_start'
16
- | 'context_needed'
17
- | 'decision_made'
18
- | 'store_this'
19
- | 'pattern_found'
20
- | 'mistake_learned'
21
- | 'progress_update'
22
- | 'question'
23
- | 'comparison'
24
- | 'exploration'
25
- | 'list_all'
26
- | 'update_memory'
27
- | 'delete_memory'
28
- | 'detail_request'
29
- | 'timeline'
30
- | 'no_action'
31
-
32
- export interface ClassificationResult {
33
- primary: Intent
34
- confidence: number
35
- secondary: Intent[]
36
- }
37
-
38
- // Decision-indicating phrases
39
- const DECISION_PHRASES = [
40
- 'i recommend', 'you should use', 'the best approach',
41
- 'i suggest', 'better to use', 'prefer using',
42
- 'go with', 'decided to', "let's use", 'we will use',
43
- 'the solution is', 'implement using', 'going with',
44
- 'switching to', 'adopting', 'we chose', 'the plan is to',
45
- 'i decided that', 'decided that', 'choosing', 'we picked',
46
- 'settled on', 'committing to', 'opting for', 'sticking with',
47
- // BUG-006: "Chose X because Y" — bare "chose" at start
48
- 'chose ',
49
- ]
50
-
51
- // Explicit "store this" phrases — the user wants to persist something
52
- const STORE_PHRASES = [
53
- 'remember:', 'remember this:', 'remember that',
54
- 'note to self:', 'note to self', 'note:', 'save this:',
55
- 'save this', 'store this', 'keep this', 'record this',
56
- 'i prefer', 'my preference is', 'my convention is',
57
- 'i like to', 'i always', 'always use', 'never use this',
58
- 'from now on', 'going forward', 'the rule is',
59
- 'my approach is', 'my standard is', 'the standard is',
60
- 'we should always', 'we should never',
61
- 'i want to remember', 'don\'t forget',
62
- 'for future reference', 'for the record',
63
- 'important:', 'key decision:', 'takeaway:',
64
- // Issue 3: Declarative patterns ("X should be Y")
65
- 'should be', 'must be', 'needs to be', 'has to be',
66
- 'is set to', 'will be set', 'is always', 'are always',
67
- 'is configured to', 'is configured as',
68
- // Issue 6: "I learned that" is a store, not a mistake
69
- 'i learned that',
70
- // Phase 25: Session summary storage
71
- 'session summary:', 'session summary ',
72
- // Explicit prefix signal for preference storage
73
- 'preference:', 'my preference:',
74
- ]
75
-
76
- const REASONING_PHRASES = [
77
- 'because', 'since', 'due to', 'as it', 'which provides',
78
- 'this allows', 'this ensures', 'given that', 'considering',
79
- 'the reason is', 'this way'
80
- ]
81
-
82
- // Mistake/correction indicators
83
- const MISTAKE_PHRASES = [
84
- 'bug was', 'the issue was', 'the problem was', 'mistake was',
85
- 'should have', "shouldn't have", 'should not have',
86
- 'lesson learned', "don't use", 'avoid using',
87
- 'the fix is', 'the fix was', 'fixed by', 'solved by',
88
- 'was wrong', 'was broken', 'was incorrect',
89
- 'gotcha:', 'pitfall:', 'watch out for', 'be careful with',
90
- 'turns out that',
91
- // BUG-006: "Mistake:" prefix (colon form, e.g. "Mistake: I was using...")
92
- 'mistake:',
93
- // Explicit prefix signals for category classification
94
- 'lesson:', 'lesson learned:',
95
- ]
96
-
97
- // Progress indicators
98
- const PROGRESS_PHRASES = [
99
- 'finished', 'completed', 'done with', 'implemented',
100
- 'built', 'created', 'added', 'fixed', 'resolved',
101
- 'shipped', 'deployed', 'merged', 'released',
102
- "i'm working on", 'currently working', 'making progress',
103
- 'just did', 'just finished',
104
- // Phase 23b: present-tense statement patterns
105
- "i'm implementing", "i'm building", "i'm adding",
106
- "i'm fixing", "i'm refactoring", "i'm setting up",
107
- "i'm migrating", "i'm updating", "i'm creating",
108
- 'started implementing', 'started building', 'started adding'
109
- ]
110
-
111
- // Comparison indicators
112
- const COMPARISON_PHRASES = [
113
- ' vs ', ' versus ', 'or should', 'compared to', 'comparing ',
114
- 'which is better', 'should i use', 'should we use',
115
- 'what if we switch', 'what if we change', 'what if we replace',
116
- 'pros and cons', 'tradeoffs', 'trade-offs'
117
- ]
118
-
119
- // Pattern indicators
120
- const PATTERN_PHRASES = [
121
- 'pattern:', 'best practice:', 'anti-pattern:', 'reusable solution',
122
- 'reusable approach', 'common issue:', 'this pattern',
123
- 'recognized pattern', 'document this pattern'
124
- ]
125
-
126
- // Exploration indicators
127
- const EXPLORATION_PHRASES = [
128
- 'show me', 'timeline', 'trends', 'evolution of',
129
- 'history of', 'graph', 'how has', 'what happened',
130
- 'decisions about', 'when did we', 'list episodes'
131
- ]
132
-
133
- // Session start indicators — Phase 19: narrowed to require start-of-message
134
- const SESSION_START_PHRASES = [
135
- 'starting work', 'beginning work', 'resuming',
136
- 'picking up', 'getting started', 'opening',
137
- 'starting on', 'working on', 'beginning on'
138
- ]
139
-
140
- // Progress indicators that disqualify session_start
141
- const PROGRESS_INDICATORS = [
142
- 'finished', 'completed', 'done', 'fixed', 'resolved',
143
- 'shipped', 'deployed', 'merged', 'released', 'just did',
144
- 'making progress', 'currently working'
145
- ]
146
-
147
- // Question indicators
148
- const QUESTION_WORDS = [
149
- 'what', 'how', 'when', 'why', 'where', 'which',
150
- 'who', 'can', 'does', 'is', 'are', 'should', 'could', 'would'
151
- ]
152
-
153
- // No-action indicators
154
- const NO_ACTION_PHRASES = [
155
- 'ok', 'okay', 'thanks', 'thank you', 'got it',
156
- 'sure', 'yes', 'no', 'cool', 'nice', 'great',
157
- 'understood', 'noted', 'alright', 'right'
158
- ]
159
-
160
- // List/browse indicators
161
- const LIST_PHRASES = [
162
- 'list all', 'show all', 'what decisions',
163
- 'what have i decided', 'all my decisions', 'all decisions',
164
- 'everything about', 'what do i know', 'what do we know',
165
- 'show me everything', 'list decisions', 'list memories',
166
- 'browse', 'dump all', 'show my',
167
- // Issue 4: Additional list phrases
168
- 'list everything', "everything i've stored", "everything i know",
169
- "what have i stored", "stored in my brain", "what's in my brain",
170
- "what have i remembered", "what do i have stored"
171
- ]
172
-
173
- // Update/correct existing memory
174
- const UPDATE_PHRASES = [
175
- 'actually,', 'actually ', 'correction:', 'update that',
176
- 'i changed my mind', 'changed my mind', 'change that to', 'instead of what i said',
177
- 'supersedes', 'override', 'revise that', 'amend that',
178
- 'that should be', 'update the decision', 'modify that',
179
- 'replace that with', 'no wait', 'scratch that, use',
180
- // Phase 23b: additional update triggers
181
- 'switch to', 'instead use', 'use instead', 'no longer using',
182
- 'moved to', 'migrated to', 'replaced with'
183
- ]
184
-
185
- // Delete/forget memory
186
- const DELETE_PHRASES = [
187
- 'forget that', 'forget about', 'delete that', 'delete the memory', 'delete the decision',
188
- 'remove that memory', 'remove that decision', 'that was wrong',
189
- 'discard that', 'erase that', 'undo that decision',
190
- 'remove the memory', 'clear that', 'drop that',
191
- // BUG-006: Bulk delete support
192
- 'delete all',
193
- ]
194
-
195
- // Phase 19 B3: Temporal signal phrases
196
- const TEMPORAL_PHRASES = [
197
- 'last week', 'last month', 'last year', 'yesterday', 'today',
198
- 'this week', 'this month', 'this year',
199
- 'since january', 'since february', 'since march', 'since april',
200
- 'since may', 'since june', 'since july', 'since august',
201
- 'since september', 'since october', 'since november', 'since december',
202
- 'in january', 'in february', 'in march', 'in april',
203
- 'in may', 'in june', 'in july', 'in august',
204
- 'in september', 'in october', 'in november', 'in december',
205
- 'ago', 'recently', 'before', 'after', 'during',
206
- 'last few days', 'past week', 'past month',
207
- 'earlier this', 'earlier today', 'over the past'
208
- ]
209
-
210
- // Phase 27: Detail request patterns — "details obs_abc123", "show me obs_abc123"
211
- const DETAIL_PATTERNS = [
212
- /^details?\s+(\S+)/i, // "details obs_abc123"
213
- /^show\s+(?:me\s+)?(\S+)/i, // "show me obs_abc123" (only when followed by an ID-like string)
214
- /^get\s+(?:details?\s+)?(\S+)/i, // "get details obs_abc123"
215
- /^expand\s+(\S+)/i, // "expand obs_abc123"
216
- /^more\s+(?:about\s+)?(\S+)/i, // "more about obs_abc123"
217
- ]
218
-
219
- // Phase 27: Timeline patterns
220
- const TIMELINE_PATTERNS = [
221
- /timeline\s+(?:for\s+)?(.+)/i, // "timeline for expense-tracker"
222
- /what\s+did\s+I\s+do\s+(.+)/i, // "what did I do yesterday"
223
- /show\s+(?:me\s+)?history/i, // "show me history"
224
- /recent\s+(?:activity|changes)/i, // "recent activity"
225
- ]
226
-
227
- /** Check if a string looks like an observation ID (contains underscores or 8+ alphanumeric chars) */
228
- function isIdLike(s: string): boolean {
229
- return /[_-]/.test(s) || /^[a-f0-9]{8,}$/i.test(s)
230
- }
231
-
232
- export class IntentClassifier {
233
- /**
234
- * Classify the intent of a message
235
- */
236
- classify(message: string): ClassificationResult {
237
- const lower = message.toLowerCase().trim()
238
- const secondary: Intent[] = []
239
-
240
- // Phase 19 B3: Detect temporal signals for secondary intent
241
- const hasTemporal = this.hasTemporalSignal(lower)
242
-
243
- // SLM Phase 1A: Log classification for training data collection
244
- const startTime = Date.now()
245
-
246
- // Check in priority order — first confident match wins
247
-
248
- // Helper to log + return in one step
249
- const ret = (result: ClassificationResult): ClassificationResult => {
250
- this._logTraining(message, result, startTime)
251
- return result
252
- }
253
-
254
- // 1. no_action: very short messages, greetings, acknowledgments
255
- if (this.isNoAction(lower)) {
256
- return ret({ primary: 'no_action', confidence: 0.95, secondary: [] })
257
- }
258
-
259
- // 2. delete_memory: "forget that", "delete", "remove"
260
- if (this.isDeleteMemory(lower)) {
261
- return ret({ primary: 'delete_memory', confidence: 0.90, secondary })
262
- }
263
-
264
- // 3. update_memory: "actually", "correction:", "change that to"
265
- if (this.isUpdateMemory(lower)) {
266
- return ret({ primary: 'update_memory', confidence: 0.85, secondary })
267
- }
268
-
269
- // 4. store_this: explicit "remember:", "save this:", "I prefer" (never for questions)
270
- if (this.isStoreThis(lower, message)) {
271
- if (this.hasDecisionSignal(lower)) secondary.push('decision_made')
272
- return ret({ primary: 'store_this', confidence: 0.90, secondary })
273
- }
274
-
275
- // 5. decision_made: decision phrases + reasoning (never for questions)
276
- if (this.isDecisionMade(lower, message)) {
277
- if (this.hasComparisonSignal(lower)) secondary.push('comparison')
278
- return ret({ primary: 'decision_made', confidence: 0.85, secondary })
279
- }
280
-
281
- // 6. mistake_learned: correction/bug/lesson indicators
282
- if (this.isMistakeLearned(lower)) {
283
- return ret({ primary: 'mistake_learned', confidence: 0.85, secondary })
284
- }
285
-
286
- // 7. list_all: "list all", "what decisions", "show all"
287
- if (this.isListAll(lower, message)) {
288
- return ret({ primary: 'list_all', confidence: 0.85, secondary })
289
- }
290
-
291
- // 8. progress_update: completed task indicators (NOT questions)
292
- if (this.isProgressUpdate(lower, message)) {
293
- if (this.hasSessionSignal(lower)) secondary.push('session_start')
294
- return ret({ primary: 'progress_update', confidence: 0.85, secondary })
295
- }
296
-
297
- // 9. comparison: vs, which is better, etc.
298
- if (this.isComparison(lower)) {
299
- if (this.isQuestion(lower, message)) secondary.push('question')
300
- if (hasTemporal) secondary.push('exploration')
301
- return ret({ primary: 'comparison', confidence: 0.85, secondary })
302
- }
303
-
304
- // 10. pattern_found: explicit pattern documentation
305
- if (this.isPatternFound(lower)) {
306
- return ret({ primary: 'pattern_found', confidence: 0.80, secondary })
307
- }
308
-
309
- // 11. session_start: starting/resuming work (Phase 19: narrowed check)
310
- if (this.isSessionStart(lower)) {
311
- secondary.push('context_needed')
312
- return ret({ primary: 'session_start', confidence: 0.90, secondary })
313
- }
314
-
315
- // 12. detail_request: "details obs_abc123", "show me <id>" (before exploration to avoid misclassification)
316
- if (this.isDetailRequest(lower)) {
317
- return ret({ primary: 'detail_request', confidence: 0.90, secondary })
318
- }
319
-
320
- // 12b. timeline: "timeline for project", "what did I do yesterday", "recent activity" (before exploration)
321
- if (this.isTimeline(lower)) {
322
- if (hasTemporal) secondary.push('exploration')
323
- return ret({ primary: 'timeline', confidence: 0.85, secondary })
324
- }
325
-
326
- // 12c. exploration: trends, graph, evolution, history (general exploration that isn't a specific timeline)
327
- if (this.isExploration(lower)) {
328
- if (this.isQuestion(lower, message)) secondary.push('question')
329
- if (hasTemporal) secondary.push('exploration')
330
- return ret({ primary: 'exploration', confidence: 0.75, secondary })
331
- }
332
-
333
- // 13. question: starts with question word or ends with ?
334
- // Phase 19 B1: Higher confidence for questions
335
- if (this.isQuestion(lower, message)) {
336
- if (this.hasComparisonSignal(lower)) secondary.push('comparison')
337
- if (this.hasExplorationSignal(lower)) secondary.push('exploration')
338
- if (hasTemporal) secondary.push('exploration')
339
-
340
- // Phase 19 B1: ? → 0.95, question word → 0.90
341
- const confidence = message.trim().endsWith('?') ? 0.95 : 0.90
342
- return ret({ primary: 'question', confidence, secondary })
343
- }
344
-
345
- // 14. Default: context_needed
346
- if (hasTemporal) secondary.push('exploration')
347
- const defaultResult = { primary: 'context_needed' as Intent, confidence: 0.60, secondary }
348
- this._logTraining(message, defaultResult, startTime)
349
- return defaultResult
350
- }
351
-
352
- /**
353
- * SLM Phase 1A: Log classification result for training data collection.
354
- * Fire-and-forget, never blocks the main path.
355
- */
356
- private _logTraining(message: string, result: ClassificationResult, startTime: number): void {
357
- try {
358
- const { logTrainingData } = require('@/training/data-store')
359
- logTrainingData({
360
- task: 'intent' as const,
361
- input: message,
362
- output: JSON.stringify({ label: result.primary, secondary: result.secondary }),
363
- metadata: JSON.stringify({ confidence: result.confidence, elapsed_ms: Date.now() - startTime }),
364
- })
365
- } catch {
366
- // Training data logging is non-critical
367
- }
368
- }
369
-
370
- private isNoAction(lower: string): boolean {
371
- if (lower.length > 30) return false
372
- const words = lower.split(/\s+/)
373
- if (words.length > 5) return false
374
- return NO_ACTION_PHRASES.some(p => lower === p || lower === p + '.' || lower === p + '!')
375
- }
376
-
377
- private isStoreThis(lower: string, original?: string): boolean {
378
- // Questions are never storage requests — "Do I prefer X?" is a query, not "I prefer X"
379
- if (original?.trim().endsWith('?')) return false
380
- const firstWord = lower.split(/\s+/)[0] || ''
381
- if (QUESTION_WORDS.includes(firstWord)) return false
382
- // Phase 19 B4: Additional question guards
383
- if (this.startsWithQuestionPattern(lower)) return false
384
- if (STORE_PHRASES.some(p => lower.includes(p))) return true
385
- // BUG-006: "Using X for Y" at start of message is a declarative preference
386
- if (lower.startsWith('using ') && lower.includes(' for ')) return true
387
- return false
388
- }
389
-
390
- private isDecisionMade(lower: string, original?: string): boolean {
391
- // Questions are never decisions — "Did I decide to use X?" is a query
392
- if (original?.trim().endsWith('?')) return false
393
- const firstWord = lower.split(/\s+/)[0] || ''
394
- if (QUESTION_WORDS.includes(firstWord)) return false
395
- // Phase 19 B4: Additional question guards
396
- if (this.startsWithQuestionPattern(lower)) return false
397
-
398
- const hasDecision = DECISION_PHRASES.some(p => lower.includes(p))
399
- if (!hasDecision) return false
400
- // Higher confidence if reasoning is also present
401
- const hasReasoning = REASONING_PHRASES.some(p => lower.includes(p))
402
- return hasReasoning || lower.length > 30
403
- }
404
-
405
- private isMistakeLearned(lower: string): boolean {
406
- return MISTAKE_PHRASES.some(p => lower.includes(p))
407
- }
408
-
409
- private isListAll(lower: string, _original: string): boolean {
410
- // "What do I know about X?" is a filtered question, not a list-all
411
- // Only treat as list_all when there's no specific topic after the phrase
412
- const topicPhrases = ['what do i know about', 'what do we know about', 'everything about']
413
- for (const phrase of topicPhrases) {
414
- if (lower.includes(phrase)) {
415
- const afterPhrase = lower.slice(lower.indexOf(phrase) + phrase.length).trim()
416
- // If there's a specific topic after the phrase, it's a question not list_all
417
- if (afterPhrase.length > 2 && !afterPhrase.startsWith('this project') && afterPhrase !== '?') {
418
- return false
419
- }
420
- }
421
- }
422
- return LIST_PHRASES.some(p => lower.includes(p))
423
- }
424
-
425
- private isProgressUpdate(lower: string, original: string): boolean {
426
- // Must not be a question
427
- if (original.trim().endsWith('?')) return false
428
- const firstWord = lower.split(/\s+/)[0] || ''
429
- if (QUESTION_WORDS.includes(firstWord)) return false
430
-
431
- return PROGRESS_PHRASES.some(p => lower.includes(p))
432
- }
433
-
434
- private isComparison(lower: string): boolean {
435
- return COMPARISON_PHRASES.some(p => lower.includes(p))
436
- }
437
-
438
- private isPatternFound(lower: string): boolean {
439
- const hasPattern = PATTERN_PHRASES.some(p => lower.includes(p))
440
- // Explicit prefix signals (e.g. "Pattern: ...") need shorter minimum length
441
- const hasExplicitPrefix = lower.startsWith('pattern:') || lower.startsWith('best practice:') || lower.startsWith('anti-pattern:')
442
- return hasPattern && (hasExplicitPrefix ? lower.length > 20 : lower.length > 50)
443
- }
444
-
445
- /**
446
- * Phase 19 B2: Narrowed session_start detection.
447
- * Must start at beginning of message AND have no progress indicators.
448
- * "working on X" in the middle of a sentence is not a session start.
449
- */
450
- private isSessionStart(lower: string): boolean {
451
- // Must NOT have progress indicators (e.g. "finished working on X" is progress, not session start)
452
- if (PROGRESS_INDICATORS.some(p => lower.includes(p))) return false
453
-
454
- // Phase 19 B2: Session phrases must match near the start of the message
455
- for (const phrase of SESSION_START_PHRASES) {
456
- const idx = lower.indexOf(phrase)
457
- if (idx !== -1 && idx <= 5) {
458
- return true
459
- }
460
- }
461
- return false
462
- }
463
-
464
- private isExploration(lower: string): boolean {
465
- return EXPLORATION_PHRASES.some(p => lower.includes(p))
466
- }
467
-
468
- private isQuestion(lower: string, original: string): boolean {
469
- if (original.trim().endsWith('?')) return true
470
- const firstWord = lower.split(/\s+/)[0] || ''
471
- if (QUESTION_WORDS.includes(firstWord)) return true
472
- // Phase 19 B4: "do I", "did we", "have we", "can we" patterns
473
- if (this.startsWithQuestionPattern(lower)) return true
474
- return false
475
- }
476
-
477
- private isUpdateMemory(lower: string): boolean {
478
- return UPDATE_PHRASES.some(p => lower.includes(p))
479
- }
480
-
481
- private isDeleteMemory(lower: string): boolean {
482
- if (lower.length > 150) return false
483
- return DELETE_PHRASES.some(p => lower.includes(p))
484
- }
485
-
486
- // Secondary signal checks
487
- private hasDecisionSignal(lower: string): boolean {
488
- return DECISION_PHRASES.some(p => lower.includes(p))
489
- }
490
-
491
- private hasComparisonSignal(lower: string): boolean {
492
- return COMPARISON_PHRASES.some(p => lower.includes(p))
493
- }
494
-
495
- private hasExplorationSignal(lower: string): boolean {
496
- return EXPLORATION_PHRASES.some(p => lower.includes(p))
497
- }
498
-
499
- private hasSessionSignal(lower: string): boolean {
500
- return SESSION_START_PHRASES.some(p => lower.includes(p))
501
- }
502
-
503
- /**
504
- * Phase 27: Detect detail requests — "details <id>", "show me <id>", "expand <id>"
505
- * Only matches when the argument looks like an ID (contains underscores, hyphens, or 8+ hex chars).
506
- */
507
- private isDetailRequest(lower: string): boolean {
508
- for (const pattern of DETAIL_PATTERNS) {
509
- const match = lower.match(pattern)
510
- if (match && match[1] && isIdLike(match[1])) {
511
- return true
512
- }
513
- }
514
- return false
515
- }
516
-
517
- /**
518
- * Phase 27: Detect timeline requests — "timeline for X", "recent activity", "what did I do yesterday"
519
- * Distinct from exploration because it specifically asks for chronological activity view.
520
- */
521
- private isTimeline(lower: string): boolean {
522
- return TIMELINE_PATTERNS.some(p => p.test(lower))
523
- }
524
-
525
- /**
526
- * Phase 19 B3: Detect temporal signals in the message.
527
- * Used to add 'temporal' context to secondary intents.
528
- */
529
- hasTemporalSignal(lower: string): boolean {
530
- return TEMPORAL_PHRASES.some(p => lower.includes(p))
531
- }
532
-
533
- /**
534
- * Phase 19 B4: Extended question guards.
535
- * Catches "do I", "did we", "have we", "can we" etc. patterns
536
- * that start with auxiliary verbs not in the QUESTION_WORDS list.
537
- */
538
- private startsWithQuestionPattern(lower: string): boolean {
539
- const questionStarters = [
540
- 'do i ', 'do we ', 'do you ',
541
- 'did i ', 'did we ', 'did you ',
542
- 'have i ', 'have we ', 'have you ',
543
- 'has it ', 'has the ',
544
- 'can i ', 'can we ', 'can you ',
545
- 'will i ', 'will we ', 'will you ',
546
- 'am i ', 'was i ', 'were we ',
547
- 'shall we ', 'shall i ',
548
- 'tell me '
549
- ]
550
- return questionStarters.some(p => lower.startsWith(p))
551
- }
552
- }
1
+ /**
2
+ * Brain Intent Classifier
3
+ * Phase 16 + Phase 19: Rule-based intent classification for the unified brain() tool
4
+ *
5
+ * Priority-ordered — first confident match wins.
6
+ * No LLM calls, pure pattern matching.
7
+ *
8
+ * Phase 19 changes:
9
+ * - B1: Question confidence raised (? → 0.95, question word → 0.90)
10
+ * - B2: session_start narrowed (must be at start + no progress indicators)
11
+ * - B3: Temporal signal detection added to secondary intents
12
+ */
13
+
14
+ export type Intent =
15
+ | 'session_start'
16
+ | 'context_needed'
17
+ | 'decision_made'
18
+ | 'store_this'
19
+ | 'pattern_found'
20
+ | 'mistake_learned'
21
+ | 'progress_update'
22
+ | 'question'
23
+ | 'comparison'
24
+ | 'exploration'
25
+ | 'list_all'
26
+ | 'update_memory'
27
+ | 'delete_memory'
28
+ | 'detail_request'
29
+ | 'timeline'
30
+ | 'no_action'
31
+
32
+ export interface ClassificationResult {
33
+ primary: Intent
34
+ confidence: number
35
+ secondary: Intent[]
36
+ }
37
+
38
+ // Decision-indicating phrases
39
+ const DECISION_PHRASES = [
40
+ 'i recommend', 'you should use', 'the best approach',
41
+ 'i suggest', 'better to use', 'prefer using',
42
+ 'go with', 'decided to', "let's use", 'we will use',
43
+ 'the solution is', 'implement using', 'going with',
44
+ 'switching to', 'adopting', 'we chose', 'the plan is to',
45
+ 'i decided that', 'decided that', 'choosing', 'we picked',
46
+ 'settled on', 'committing to', 'opting for', 'sticking with',
47
+ // BUG-006: "Chose X because Y" — bare "chose" at start
48
+ 'chose ',
49
+ ]
50
+
51
+ // Explicit "store this" phrases — the user wants to persist something
52
+ const STORE_PHRASES = [
53
+ 'remember:', 'remember this:', 'remember that',
54
+ 'note to self:', 'note to self', 'note:', 'save this:',
55
+ 'save this', 'store this', 'keep this', 'record this',
56
+ 'i prefer', 'my preference is', 'my convention is',
57
+ 'i like to', 'i always', 'always use', 'never use this',
58
+ 'from now on', 'going forward', 'the rule is',
59
+ 'my approach is', 'my standard is', 'the standard is',
60
+ 'we should always', 'we should never',
61
+ 'i want to remember', 'don\'t forget',
62
+ 'for future reference', 'for the record',
63
+ 'important:', 'key decision:', 'takeaway:',
64
+ // Issue 3: Declarative patterns ("X should be Y")
65
+ 'should be', 'must be', 'needs to be', 'has to be',
66
+ 'is set to', 'will be set', 'is always', 'are always',
67
+ 'is configured to', 'is configured as',
68
+ // Issue 6: "I learned that" is a store, not a mistake
69
+ 'i learned that',
70
+ // Phase 25: Session summary storage
71
+ 'session summary:', 'session summary ',
72
+ // Explicit prefix signal for preference storage
73
+ 'preference:', 'my preference:',
74
+ ]
75
+
76
+ const REASONING_PHRASES = [
77
+ 'because', 'since', 'due to', 'as it', 'which provides',
78
+ 'this allows', 'this ensures', 'given that', 'considering',
79
+ 'the reason is', 'this way'
80
+ ]
81
+
82
+ // Mistake/correction indicators
83
+ const MISTAKE_PHRASES = [
84
+ 'bug was', 'the issue was', 'the problem was', 'mistake was',
85
+ 'should have', "shouldn't have", 'should not have',
86
+ 'lesson learned', "don't use", 'avoid using',
87
+ 'the fix is', 'the fix was', 'fixed by', 'solved by',
88
+ 'was wrong', 'was broken', 'was incorrect',
89
+ 'gotcha:', 'pitfall:', 'watch out for', 'be careful with',
90
+ 'turns out that',
91
+ // BUG-006: "Mistake:" prefix (colon form, e.g. "Mistake: I was using...")
92
+ 'mistake:',
93
+ // Explicit prefix signals for category classification
94
+ 'lesson:', 'lesson learned:',
95
+ ]
96
+
97
+ // Progress indicators
98
+ const PROGRESS_PHRASES = [
99
+ 'finished', 'completed', 'done with', 'implemented',
100
+ 'built', 'created', 'added', 'fixed', 'resolved',
101
+ 'shipped', 'deployed', 'merged', 'released',
102
+ "i'm working on", 'currently working', 'making progress',
103
+ 'just did', 'just finished',
104
+ // Phase 23b: present-tense statement patterns
105
+ "i'm implementing", "i'm building", "i'm adding",
106
+ "i'm fixing", "i'm refactoring", "i'm setting up",
107
+ "i'm migrating", "i'm updating", "i'm creating",
108
+ 'started implementing', 'started building', 'started adding'
109
+ ]
110
+
111
+ // Comparison indicators
112
+ const COMPARISON_PHRASES = [
113
+ ' vs ', ' versus ', 'or should', 'compared to', 'comparing ',
114
+ 'which is better', 'should i use', 'should we use',
115
+ 'what if we switch', 'what if we change', 'what if we replace',
116
+ 'pros and cons', 'tradeoffs', 'trade-offs'
117
+ ]
118
+
119
+ // Pattern indicators
120
+ const PATTERN_PHRASES = [
121
+ 'pattern:', 'best practice:', 'anti-pattern:', 'reusable solution',
122
+ 'reusable approach', 'common issue:', 'this pattern',
123
+ 'recognized pattern', 'document this pattern'
124
+ ]
125
+
126
+ // Exploration indicators
127
+ const EXPLORATION_PHRASES = [
128
+ 'show me', 'timeline', 'trends', 'evolution of',
129
+ 'history of', 'graph', 'how has', 'what happened',
130
+ 'decisions about', 'when did we', 'list episodes'
131
+ ]
132
+
133
+ // Session start indicators — Phase 19: narrowed to require start-of-message
134
+ const SESSION_START_PHRASES = [
135
+ 'starting work', 'beginning work', 'resuming',
136
+ 'picking up', 'getting started', 'opening',
137
+ 'starting on', 'working on', 'beginning on'
138
+ ]
139
+
140
+ // Progress indicators that disqualify session_start
141
+ const PROGRESS_INDICATORS = [
142
+ 'finished', 'completed', 'done', 'fixed', 'resolved',
143
+ 'shipped', 'deployed', 'merged', 'released', 'just did',
144
+ 'making progress', 'currently working'
145
+ ]
146
+
147
+ // Question indicators
148
+ const QUESTION_WORDS = [
149
+ 'what', 'how', 'when', 'why', 'where', 'which',
150
+ 'who', 'can', 'does', 'is', 'are', 'should', 'could', 'would'
151
+ ]
152
+
153
+ // No-action indicators
154
+ const NO_ACTION_PHRASES = [
155
+ 'ok', 'okay', 'thanks', 'thank you', 'got it',
156
+ 'sure', 'yes', 'no', 'cool', 'nice', 'great',
157
+ 'understood', 'noted', 'alright', 'right'
158
+ ]
159
+
160
+ // List/browse indicators
161
+ const LIST_PHRASES = [
162
+ 'list all', 'show all', 'what decisions',
163
+ 'what have i decided', 'all my decisions', 'all decisions',
164
+ 'everything about', 'what do i know', 'what do we know',
165
+ 'show me everything', 'list decisions', 'list memories',
166
+ 'browse', 'dump all', 'show my',
167
+ // Issue 4: Additional list phrases
168
+ 'list everything', "everything i've stored", "everything i know",
169
+ "what have i stored", "stored in my brain", "what's in my brain",
170
+ "what have i remembered", "what do i have stored"
171
+ ]
172
+
173
+ // Update/correct existing memory
174
+ const UPDATE_PHRASES = [
175
+ 'actually,', 'actually ', 'correction:', 'update that',
176
+ 'i changed my mind', 'changed my mind', 'change that to', 'instead of what i said',
177
+ 'supersedes', 'override', 'revise that', 'amend that',
178
+ 'that should be', 'update the decision', 'modify that',
179
+ 'replace that with', 'no wait', 'scratch that, use',
180
+ // Phase 23b: additional update triggers
181
+ 'switch to', 'instead use', 'use instead', 'no longer using',
182
+ 'moved to', 'migrated to', 'replaced with'
183
+ ]
184
+
185
+ // Delete/forget memory
186
+ const DELETE_PHRASES = [
187
+ 'forget that', 'forget about', 'delete that', 'delete the memory', 'delete the decision',
188
+ 'remove that memory', 'remove that decision', 'that was wrong',
189
+ 'discard that', 'erase that', 'undo that decision',
190
+ 'remove the memory', 'clear that', 'drop that',
191
+ // BUG-006: Bulk delete support
192
+ 'delete all',
193
+ ]
194
+
195
+ // Phase 19 B3: Temporal signal phrases
196
+ const TEMPORAL_PHRASES = [
197
+ 'last week', 'last month', 'last year', 'yesterday', 'today',
198
+ 'this week', 'this month', 'this year',
199
+ 'since january', 'since february', 'since march', 'since april',
200
+ 'since may', 'since june', 'since july', 'since august',
201
+ 'since september', 'since october', 'since november', 'since december',
202
+ 'in january', 'in february', 'in march', 'in april',
203
+ 'in may', 'in june', 'in july', 'in august',
204
+ 'in september', 'in october', 'in november', 'in december',
205
+ 'ago', 'recently', 'before', 'after', 'during',
206
+ 'last few days', 'past week', 'past month',
207
+ 'earlier this', 'earlier today', 'over the past'
208
+ ]
209
+
210
+ // Phase 27: Detail request patterns — "details obs_abc123", "show me obs_abc123"
211
+ const DETAIL_PATTERNS = [
212
+ /^details?\s+(\S+)/i, // "details obs_abc123"
213
+ /^show\s+(?:me\s+)?(\S+)/i, // "show me obs_abc123" (only when followed by an ID-like string)
214
+ /^get\s+(?:details?\s+)?(\S+)/i, // "get details obs_abc123"
215
+ /^expand\s+(\S+)/i, // "expand obs_abc123"
216
+ /^more\s+(?:about\s+)?(\S+)/i, // "more about obs_abc123"
217
+ ]
218
+
219
+ // Phase 27: Timeline patterns
220
+ const TIMELINE_PATTERNS = [
221
+ /timeline\s+(?:for\s+)?(.+)/i, // "timeline for expense-tracker"
222
+ /what\s+did\s+I\s+do\s+(.+)/i, // "what did I do yesterday"
223
+ /show\s+(?:me\s+)?history/i, // "show me history"
224
+ /recent\s+(?:activity|changes)/i, // "recent activity"
225
+ ]
226
+
227
+ /** Check if a string looks like an observation ID (contains underscores or 8+ alphanumeric chars) */
228
+ function isIdLike(s: string): boolean {
229
+ return /[_-]/.test(s) || /^[a-f0-9]{8,}$/i.test(s)
230
+ }
231
+
232
+ export class IntentClassifier {
233
+ /**
234
+ * Classify the intent of a message
235
+ */
236
+ classify(message: string): ClassificationResult {
237
+ const lower = message.toLowerCase().trim()
238
+ const secondary: Intent[] = []
239
+
240
+ // Phase 19 B3: Detect temporal signals for secondary intent
241
+ const hasTemporal = this.hasTemporalSignal(lower)
242
+
243
+ // SLM Phase 1A: Log classification for training data collection
244
+ const startTime = Date.now()
245
+
246
+ // Check in priority order — first confident match wins
247
+
248
+ // Helper to log + return in one step
249
+ const ret = (result: ClassificationResult): ClassificationResult => {
250
+ this._logTraining(message, result, startTime)
251
+ return result
252
+ }
253
+
254
+ // 1. no_action: very short messages, greetings, acknowledgments
255
+ if (this.isNoAction(lower)) {
256
+ return ret({ primary: 'no_action', confidence: 0.95, secondary: [] })
257
+ }
258
+
259
+ // 2. delete_memory: "forget that", "delete", "remove"
260
+ if (this.isDeleteMemory(lower)) {
261
+ return ret({ primary: 'delete_memory', confidence: 0.90, secondary })
262
+ }
263
+
264
+ // 3. update_memory: "actually", "correction:", "change that to"
265
+ if (this.isUpdateMemory(lower)) {
266
+ return ret({ primary: 'update_memory', confidence: 0.85, secondary })
267
+ }
268
+
269
+ // 4. store_this: explicit "remember:", "save this:", "I prefer" (never for questions)
270
+ if (this.isStoreThis(lower, message)) {
271
+ if (this.hasDecisionSignal(lower)) secondary.push('decision_made')
272
+ return ret({ primary: 'store_this', confidence: 0.90, secondary })
273
+ }
274
+
275
+ // 5. decision_made: decision phrases + reasoning (never for questions)
276
+ if (this.isDecisionMade(lower, message)) {
277
+ if (this.hasComparisonSignal(lower)) secondary.push('comparison')
278
+ return ret({ primary: 'decision_made', confidence: 0.85, secondary })
279
+ }
280
+
281
+ // 6. mistake_learned: correction/bug/lesson indicators
282
+ if (this.isMistakeLearned(lower)) {
283
+ return ret({ primary: 'mistake_learned', confidence: 0.85, secondary })
284
+ }
285
+
286
+ // 7. list_all: "list all", "what decisions", "show all"
287
+ if (this.isListAll(lower, message)) {
288
+ return ret({ primary: 'list_all', confidence: 0.85, secondary })
289
+ }
290
+
291
+ // 8. progress_update: completed task indicators (NOT questions)
292
+ if (this.isProgressUpdate(lower, message)) {
293
+ if (this.hasSessionSignal(lower)) secondary.push('session_start')
294
+ return ret({ primary: 'progress_update', confidence: 0.85, secondary })
295
+ }
296
+
297
+ // 9. comparison: vs, which is better, etc.
298
+ if (this.isComparison(lower)) {
299
+ if (this.isQuestion(lower, message)) secondary.push('question')
300
+ if (hasTemporal) secondary.push('exploration')
301
+ return ret({ primary: 'comparison', confidence: 0.85, secondary })
302
+ }
303
+
304
+ // 10. pattern_found: explicit pattern documentation
305
+ if (this.isPatternFound(lower)) {
306
+ return ret({ primary: 'pattern_found', confidence: 0.80, secondary })
307
+ }
308
+
309
+ // 11. session_start: starting/resuming work (Phase 19: narrowed check)
310
+ if (this.isSessionStart(lower)) {
311
+ secondary.push('context_needed')
312
+ return ret({ primary: 'session_start', confidence: 0.90, secondary })
313
+ }
314
+
315
+ // 12. detail_request: "details obs_abc123", "show me <id>" (before exploration to avoid misclassification)
316
+ if (this.isDetailRequest(lower)) {
317
+ return ret({ primary: 'detail_request', confidence: 0.90, secondary })
318
+ }
319
+
320
+ // 12b. timeline: "timeline for project", "what did I do yesterday", "recent activity" (before exploration)
321
+ if (this.isTimeline(lower)) {
322
+ if (hasTemporal) secondary.push('exploration')
323
+ return ret({ primary: 'timeline', confidence: 0.85, secondary })
324
+ }
325
+
326
+ // 12c. exploration: trends, graph, evolution, history (general exploration that isn't a specific timeline)
327
+ if (this.isExploration(lower)) {
328
+ if (this.isQuestion(lower, message)) secondary.push('question')
329
+ if (hasTemporal) secondary.push('exploration')
330
+ return ret({ primary: 'exploration', confidence: 0.75, secondary })
331
+ }
332
+
333
+ // 13. question: starts with question word or ends with ?
334
+ // Phase 19 B1: Higher confidence for questions
335
+ if (this.isQuestion(lower, message)) {
336
+ if (this.hasComparisonSignal(lower)) secondary.push('comparison')
337
+ if (this.hasExplorationSignal(lower)) secondary.push('exploration')
338
+ if (hasTemporal) secondary.push('exploration')
339
+
340
+ // Phase 19 B1: ? → 0.95, question word → 0.90
341
+ const confidence = message.trim().endsWith('?') ? 0.95 : 0.90
342
+ return ret({ primary: 'question', confidence, secondary })
343
+ }
344
+
345
+ // 14. Default: context_needed
346
+ if (hasTemporal) secondary.push('exploration')
347
+ const defaultResult = { primary: 'context_needed' as Intent, confidence: 0.60, secondary }
348
+ this._logTraining(message, defaultResult, startTime)
349
+ return defaultResult
350
+ }
351
+
352
+ /**
353
+ * SLM Phase 1A: Log classification result for training data collection.
354
+ * Fire-and-forget, never blocks the main path.
355
+ */
356
+ private _logTraining(message: string, result: ClassificationResult, startTime: number): void {
357
+ try {
358
+ const { logTrainingData } = require('@/training/data-store')
359
+ logTrainingData({
360
+ task: 'intent' as const,
361
+ input: message,
362
+ output: JSON.stringify({ label: result.primary, secondary: result.secondary }),
363
+ metadata: JSON.stringify({ confidence: result.confidence, elapsed_ms: Date.now() - startTime }),
364
+ })
365
+ } catch {
366
+ // Training data logging is non-critical
367
+ }
368
+ }
369
+
370
+ private isNoAction(lower: string): boolean {
371
+ if (lower.length > 30) return false
372
+ const words = lower.split(/\s+/)
373
+ if (words.length > 5) return false
374
+ return NO_ACTION_PHRASES.some(p => lower === p || lower === p + '.' || lower === p + '!')
375
+ }
376
+
377
+ private isStoreThis(lower: string, original?: string): boolean {
378
+ // Questions are never storage requests — "Do I prefer X?" is a query, not "I prefer X"
379
+ if (original?.trim().endsWith('?')) return false
380
+ const firstWord = lower.split(/\s+/)[0] || ''
381
+ if (QUESTION_WORDS.includes(firstWord)) return false
382
+ // Phase 19 B4: Additional question guards
383
+ if (this.startsWithQuestionPattern(lower)) return false
384
+ if (STORE_PHRASES.some(p => lower.includes(p))) return true
385
+ // BUG-006: "Using X for Y" at start of message is a declarative preference
386
+ if (lower.startsWith('using ') && lower.includes(' for ')) return true
387
+ return false
388
+ }
389
+
390
+ private isDecisionMade(lower: string, original?: string): boolean {
391
+ // Questions are never decisions — "Did I decide to use X?" is a query
392
+ if (original?.trim().endsWith('?')) return false
393
+ const firstWord = lower.split(/\s+/)[0] || ''
394
+ if (QUESTION_WORDS.includes(firstWord)) return false
395
+ // Phase 19 B4: Additional question guards
396
+ if (this.startsWithQuestionPattern(lower)) return false
397
+
398
+ const hasDecision = DECISION_PHRASES.some(p => lower.includes(p))
399
+ if (!hasDecision) return false
400
+ // Higher confidence if reasoning is also present
401
+ const hasReasoning = REASONING_PHRASES.some(p => lower.includes(p))
402
+ return hasReasoning || lower.length > 30
403
+ }
404
+
405
+ private isMistakeLearned(lower: string): boolean {
406
+ return MISTAKE_PHRASES.some(p => lower.includes(p))
407
+ }
408
+
409
+ private isListAll(lower: string, _original: string): boolean {
410
+ // "What do I know about X?" is a filtered question, not a list-all
411
+ // Only treat as list_all when there's no specific topic after the phrase
412
+ const topicPhrases = ['what do i know about', 'what do we know about', 'everything about']
413
+ for (const phrase of topicPhrases) {
414
+ if (lower.includes(phrase)) {
415
+ const afterPhrase = lower.slice(lower.indexOf(phrase) + phrase.length).trim()
416
+ // If there's a specific topic after the phrase, it's a question not list_all
417
+ if (afterPhrase.length > 2 && !afterPhrase.startsWith('this project') && afterPhrase !== '?') {
418
+ return false
419
+ }
420
+ }
421
+ }
422
+ return LIST_PHRASES.some(p => lower.includes(p))
423
+ }
424
+
425
+ private isProgressUpdate(lower: string, original: string): boolean {
426
+ // Must not be a question
427
+ if (original.trim().endsWith('?')) return false
428
+ const firstWord = lower.split(/\s+/)[0] || ''
429
+ if (QUESTION_WORDS.includes(firstWord)) return false
430
+
431
+ return PROGRESS_PHRASES.some(p => lower.includes(p))
432
+ }
433
+
434
+ private isComparison(lower: string): boolean {
435
+ return COMPARISON_PHRASES.some(p => lower.includes(p))
436
+ }
437
+
438
+ private isPatternFound(lower: string): boolean {
439
+ const hasPattern = PATTERN_PHRASES.some(p => lower.includes(p))
440
+ // Explicit prefix signals (e.g. "Pattern: ...") need shorter minimum length
441
+ const hasExplicitPrefix = lower.startsWith('pattern:') || lower.startsWith('best practice:') || lower.startsWith('anti-pattern:')
442
+ return hasPattern && (hasExplicitPrefix ? lower.length > 20 : lower.length > 50)
443
+ }
444
+
445
+ /**
446
+ * Phase 19 B2: Narrowed session_start detection.
447
+ * Must start at beginning of message AND have no progress indicators.
448
+ * "working on X" in the middle of a sentence is not a session start.
449
+ */
450
+ private isSessionStart(lower: string): boolean {
451
+ // Must NOT have progress indicators (e.g. "finished working on X" is progress, not session start)
452
+ if (PROGRESS_INDICATORS.some(p => lower.includes(p))) return false
453
+
454
+ // Phase 19 B2: Session phrases must match near the start of the message
455
+ for (const phrase of SESSION_START_PHRASES) {
456
+ const idx = lower.indexOf(phrase)
457
+ if (idx !== -1 && idx <= 5) {
458
+ return true
459
+ }
460
+ }
461
+ return false
462
+ }
463
+
464
+ private isExploration(lower: string): boolean {
465
+ return EXPLORATION_PHRASES.some(p => lower.includes(p))
466
+ }
467
+
468
+ private isQuestion(lower: string, original: string): boolean {
469
+ if (original.trim().endsWith('?')) return true
470
+ const firstWord = lower.split(/\s+/)[0] || ''
471
+ if (QUESTION_WORDS.includes(firstWord)) return true
472
+ // Phase 19 B4: "do I", "did we", "have we", "can we" patterns
473
+ if (this.startsWithQuestionPattern(lower)) return true
474
+ return false
475
+ }
476
+
477
+ private isUpdateMemory(lower: string): boolean {
478
+ return UPDATE_PHRASES.some(p => lower.includes(p))
479
+ }
480
+
481
+ private isDeleteMemory(lower: string): boolean {
482
+ if (lower.length > 150) return false
483
+ return DELETE_PHRASES.some(p => lower.includes(p))
484
+ }
485
+
486
+ // Secondary signal checks
487
+ private hasDecisionSignal(lower: string): boolean {
488
+ return DECISION_PHRASES.some(p => lower.includes(p))
489
+ }
490
+
491
+ private hasComparisonSignal(lower: string): boolean {
492
+ return COMPARISON_PHRASES.some(p => lower.includes(p))
493
+ }
494
+
495
+ private hasExplorationSignal(lower: string): boolean {
496
+ return EXPLORATION_PHRASES.some(p => lower.includes(p))
497
+ }
498
+
499
+ private hasSessionSignal(lower: string): boolean {
500
+ return SESSION_START_PHRASES.some(p => lower.includes(p))
501
+ }
502
+
503
+ /**
504
+ * Phase 27: Detect detail requests — "details <id>", "show me <id>", "expand <id>"
505
+ * Only matches when the argument looks like an ID (contains underscores, hyphens, or 8+ hex chars).
506
+ */
507
+ private isDetailRequest(lower: string): boolean {
508
+ for (const pattern of DETAIL_PATTERNS) {
509
+ const match = lower.match(pattern)
510
+ if (match && match[1] && isIdLike(match[1])) {
511
+ return true
512
+ }
513
+ }
514
+ return false
515
+ }
516
+
517
+ /**
518
+ * Phase 27: Detect timeline requests — "timeline for X", "recent activity", "what did I do yesterday"
519
+ * Distinct from exploration because it specifically asks for chronological activity view.
520
+ */
521
+ private isTimeline(lower: string): boolean {
522
+ return TIMELINE_PATTERNS.some(p => p.test(lower))
523
+ }
524
+
525
+ /**
526
+ * Phase 19 B3: Detect temporal signals in the message.
527
+ * Used to add 'temporal' context to secondary intents.
528
+ */
529
+ hasTemporalSignal(lower: string): boolean {
530
+ return TEMPORAL_PHRASES.some(p => lower.includes(p))
531
+ }
532
+
533
+ /**
534
+ * Phase 19 B4: Extended question guards.
535
+ * Catches "do I", "did we", "have we", "can we" etc. patterns
536
+ * that start with auxiliary verbs not in the QUESTION_WORDS list.
537
+ */
538
+ private startsWithQuestionPattern(lower: string): boolean {
539
+ const questionStarters = [
540
+ 'do i ', 'do we ', 'do you ',
541
+ 'did i ', 'did we ', 'did you ',
542
+ 'have i ', 'have we ', 'have you ',
543
+ 'has it ', 'has the ',
544
+ 'can i ', 'can we ', 'can you ',
545
+ 'will i ', 'will we ', 'will you ',
546
+ 'am i ', 'was i ', 'were we ',
547
+ 'shall we ', 'shall i ',
548
+ 'tell me '
549
+ ]
550
+ return questionStarters.some(p => lower.startsWith(p))
551
+ }
552
+ }