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.
- package/README.md +241 -191
- package/VERSION +1 -1
- package/assets/CLAUDE-unified.md +11 -11
- package/assets/CLAUDE.md +29 -29
- package/package.json +7 -3
- package/packs/backend/node.json +173 -173
- package/packs/core/javascript.json +176 -176
- package/packs/core/typescript.json +222 -222
- package/packs/frontend/react.json +254 -254
- package/packs/meta/testing.json +172 -172
- package/scripts/postinstall.mjs +531 -531
- package/src/automation/decision-detector.ts +452 -452
- package/src/automation/phase12-manager.ts +456 -456
- package/src/automation/proactive-recall.ts +373 -373
- package/src/automation/project-detector.ts +310 -310
- package/src/automation/repo-scanner.ts +210 -205
- package/src/cli/auto-setup.ts +75 -75
- package/src/cli/auto-start.ts +266 -266
- package/src/cli/bin.ts +264 -264
- package/src/cli/commands/autostart.ts +90 -90
- package/src/cli/commands/chroma.ts +578 -577
- package/src/cli/commands/export-training.ts +70 -70
- package/src/cli/commands/export.ts +130 -130
- package/src/cli/commands/git-hook.ts +183 -183
- package/src/cli/commands/hooks.ts +217 -217
- package/src/cli/commands/init.ts +123 -123
- package/src/cli/commands/install-mcp.ts +122 -111
- package/src/cli/commands/models.ts +979 -979
- package/src/cli/commands/pack.ts +200 -200
- package/src/cli/commands/refresh.ts +344 -339
- package/src/cli/commands/reindex.ts +120 -120
- package/src/cli/commands/serve.ts +466 -463
- package/src/cli/commands/start.ts +44 -44
- package/src/cli/commands/status.ts +220 -203
- package/src/cli/commands/uninstall-mcp.ts +45 -41
- package/src/cli/commands/update.ts +130 -124
- package/src/cli/migrate-chroma.ts +106 -106
- package/src/cli/ui/animations.ts +80 -80
- package/src/cli/ui/components.ts +82 -82
- package/src/cli/ui/index.ts +4 -4
- package/src/cli/ui/logo.ts +36 -36
- package/src/cli/ui/theme.ts +55 -55
- package/src/code-intelligence/indexer.ts +352 -352
- package/src/code-intelligence/linker.ts +178 -178
- package/src/code-intelligence/parser.ts +484 -484
- package/src/code-intelligence/query.ts +291 -291
- package/src/code-intelligence/schema.ts +83 -83
- package/src/code-intelligence/types.ts +95 -95
- package/src/config/defaults.ts +52 -52
- package/src/config/home.ts +56 -56
- package/src/config/index.ts +5 -5
- package/src/config/loader.ts +192 -192
- package/src/config/schema.ts +446 -415
- package/src/config/validator.ts +182 -182
- package/src/context/assembler.ts +407 -400
- package/src/context/index.ts +79 -79
- package/src/context/progress-tracker.ts +174 -174
- package/src/context/standards-manager.ts +287 -287
- package/src/context/validator.ts +58 -58
- package/src/diagnostics/index.ts +122 -121
- package/src/health/index.ts +233 -232
- package/src/hooks/brain-hook.ts +134 -131
- package/src/hooks/capture.ts +168 -168
- package/src/hooks/claude-code-mastery.md +112 -112
- package/src/hooks/context-hook.ts +260 -245
- package/src/hooks/deduplicator.ts +72 -72
- package/src/hooks/git-capture.ts +109 -109
- package/src/hooks/git-hook-installer.ts +211 -207
- package/src/hooks/index.ts +20 -20
- package/src/hooks/installer.ts +306 -288
- package/src/hooks/interceptor-hook.ts +204 -201
- package/src/hooks/passive-classifier.ts +397 -397
- package/src/hooks/queue.ts +160 -129
- package/src/hooks/session-tracker.ts +312 -312
- package/src/hooks/types.ts +52 -52
- package/src/index.ts +7 -7
- package/src/intelligence/cross-project/generalizer.ts +283 -283
- package/src/intelligence/cross-project/index.ts +7 -7
- package/src/intelligence/hf-downloader.ts +222 -222
- package/src/intelligence/hf-manifest.json +78 -78
- package/src/intelligence/index.ts +24 -24
- package/src/intelligence/inference-router.ts +762 -762
- package/src/intelligence/model-manager.ts +263 -245
- package/src/intelligence/optimization/index.ts +10 -10
- package/src/intelligence/optimization/precompute.ts +202 -202
- package/src/intelligence/optimization/semantic-cache.ts +213 -207
- package/src/intelligence/prediction/index.ts +7 -7
- package/src/intelligence/prediction/recommender.ts +276 -268
- package/src/intelligence/reasoning/chain-retrieval.ts +243 -247
- package/src/intelligence/reasoning/index.ts +7 -7
- package/src/intelligence/temporal/evolution.ts +193 -197
- package/src/intelligence/temporal/index.ts +16 -16
- package/src/intelligence/temporal/query-processor.ts +190 -190
- package/src/intelligence/temporal/timeline.ts +272 -259
- package/src/intelligence/temporal/trends.ts +263 -263
- package/src/intelligence/tokenizer.ts +118 -118
- package/src/knowledge/entity-extractor.ts +447 -443
- package/src/knowledge/graph/builder.ts +185 -185
- package/src/knowledge/graph/linker.ts +201 -201
- package/src/knowledge/graph/memory-graph.ts +359 -359
- package/src/knowledge/graph/schema.ts +99 -99
- package/src/knowledge/graph/search.ts +166 -166
- package/src/knowledge/relationship-extractor.ts +108 -108
- package/src/memory/chroma/client.ts +211 -192
- package/src/memory/chroma/collection-manager.ts +92 -92
- package/src/memory/chroma/config.ts +57 -57
- package/src/memory/chroma/embeddings.ts +177 -175
- package/src/memory/chroma/index.ts +82 -82
- package/src/memory/chroma/migration.ts +270 -270
- package/src/memory/chroma/schemas.ts +69 -69
- package/src/memory/chroma/search.ts +319 -315
- package/src/memory/chroma/store.ts +755 -747
- package/src/memory/compression.ts +121 -121
- package/src/memory/consolidation/archiver.ts +162 -165
- package/src/memory/consolidation/merger.ts +182 -186
- package/src/memory/consolidation/scorer.ts +136 -136
- package/src/memory/database.ts +9 -0
- package/src/memory/dual-write.ts +145 -0
- package/src/memory/embeddings.ts +226 -226
- package/src/memory/episodic/detector.ts +108 -108
- package/src/memory/episodic/manager.ts +347 -351
- package/src/memory/episodic/summarizer.ts +179 -179
- package/src/memory/episodic/types.ts +52 -52
- package/src/memory/fts5-search.ts +692 -633
- package/src/memory/index.ts +943 -1060
- package/src/memory/migrations/add-fts5.ts +118 -108
- package/src/memory/patterns.ts +438 -438
- package/src/memory/pruning.ts +60 -60
- package/src/memory/schema.ts +88 -88
- package/src/memory/store.ts +911 -787
- package/src/orchestrator/handlers/decision-handler.ts +204 -204
- package/src/packs/index.ts +9 -9
- package/src/packs/loader.ts +134 -134
- package/src/packs/manager.ts +204 -204
- package/src/packs/ranker.ts +78 -78
- package/src/packs/types.ts +81 -81
- package/src/phase12/index.ts +5 -5
- package/src/retrieval/bm25/index.ts +300 -297
- package/src/retrieval/bm25/tokenizer.ts +184 -184
- package/src/retrieval/feedback/adaptive.ts +221 -221
- package/src/retrieval/feedback/index.ts +16 -16
- package/src/retrieval/feedback/metrics.ts +221 -221
- package/src/retrieval/feedback/store.ts +283 -283
- package/src/retrieval/fusion/index.ts +194 -194
- package/src/retrieval/fusion/rrf.ts +165 -165
- package/src/retrieval/index.ts +12 -12
- package/src/retrieval/pipeline.ts +375 -375
- package/src/retrieval/query/expander.ts +203 -203
- package/src/retrieval/query/index.ts +27 -27
- package/src/retrieval/query/intent-classifier.ts +252 -252
- package/src/retrieval/query/temporal-parser.ts +295 -295
- package/src/retrieval/reranker/index.ts +189 -188
- package/src/retrieval/reranker/model.ts +99 -95
- package/src/retrieval/service.ts +125 -125
- package/src/retrieval/types.ts +162 -162
- package/src/routing/entity-extractor.ts +454 -454
- package/src/routing/handlers/exploration-handler.ts +369 -0
- package/src/routing/handlers/index.ts +19 -0
- package/src/routing/handlers/memory-handler.ts +273 -0
- package/src/routing/handlers/mutation-handler.ts +241 -0
- package/src/routing/handlers/recall-handler.ts +642 -0
- package/src/routing/handlers/shared.ts +515 -0
- package/src/routing/handlers/types.ts +48 -0
- package/src/routing/intent-classifier.ts +552 -552
- package/src/routing/response-filter.ts +399 -391
- package/src/routing/router.ts +245 -2193
- package/src/routing/search-engine.ts +521 -514
- package/src/routing/types.ts +104 -94
- package/src/scripts/health-check.ts +118 -118
- package/src/scripts/setup.ts +122 -122
- package/src/server/auto-updater.ts +283 -276
- package/src/server/handlers/call-tool.ts +159 -159
- package/src/server/handlers/list-tools.ts +35 -35
- package/src/server/handlers/tools/auto-remember.ts +165 -165
- package/src/server/handlers/tools/brain.ts +86 -86
- package/src/server/handlers/tools/create-project.ts +135 -135
- package/src/server/handlers/tools/get-code-standards.ts +123 -123
- package/src/server/handlers/tools/get-corrections.ts +152 -152
- package/src/server/handlers/tools/get-patterns.ts +156 -156
- package/src/server/handlers/tools/get-project-context.ts +75 -75
- package/src/server/handlers/tools/index.ts +30 -30
- package/src/server/handlers/tools/init-project.ts +756 -756
- package/src/server/handlers/tools/list-projects.ts +126 -126
- package/src/server/handlers/tools/recall-similar.ts +87 -87
- package/src/server/handlers/tools/recognize-pattern.ts +132 -132
- package/src/server/handlers/tools/record-correction.ts +131 -131
- package/src/server/handlers/tools/remember-decision.ts +168 -168
- package/src/server/handlers/tools/schemas.ts +179 -179
- package/src/server/handlers/tools/search-code.ts +122 -122
- package/src/server/handlers/tools/smart-context.ts +146 -146
- package/src/server/handlers/tools/update-progress.ts +131 -131
- package/src/server/http-api.ts +215 -1229
- package/src/server/mcp-proxy.ts +85 -84
- package/src/server/mcp-server.ts +285 -284
- package/src/server/middleware/auth.ts +39 -0
- package/src/server/middleware/error-handler.ts +37 -0
- package/src/server/middleware/rate-limit.ts +53 -0
- package/src/server/middleware/validate.ts +42 -0
- package/src/server/pid-manager.ts +137 -136
- package/src/server/providers/resources.ts +581 -581
- package/src/server/routes/code.ts +228 -0
- package/src/server/routes/context.ts +26 -0
- package/src/server/routes/health.ts +19 -0
- package/src/server/routes/helpers.ts +100 -0
- package/src/server/routes/hooks.ts +197 -0
- package/src/server/routes/mcp.ts +47 -0
- package/src/server/routes/memory.ts +397 -0
- package/src/server/routes/models.ts +96 -0
- package/src/server/routes/projects.ts +89 -0
- package/src/server/routes/types.ts +21 -0
- package/src/server/schemas/api-schemas.ts +202 -0
- package/src/server/services.ts +720 -720
- package/src/server/utils/memory-indicator.ts +84 -84
- package/src/server/utils/response-formatter.ts +129 -129
- package/src/server/web-viewer.ts +1145 -1115
- package/src/setup/index.ts +38 -38
- package/src/tools/registry.ts +115 -115
- package/src/tools/schemas.ts +666 -666
- package/src/tools/types.ts +412 -412
- package/src/training/data-store.ts +320 -298
- package/src/training/retrain-pipeline.ts +399 -394
- package/src/utils/error-handler.ts +136 -136
- package/src/utils/index.ts +58 -58
- package/src/utils/kill-port.ts +55 -53
- package/src/utils/phase12-helper.ts +56 -56
- package/src/utils/safe-path.ts +43 -0
- package/src/utils/timing.ts +47 -47
- package/src/utils/transaction.ts +63 -63
- package/src/vault/index.ts +4 -3
- package/src/vault/paths.ts +106 -106
- package/src/vault/query.ts +4 -1
- package/src/vault/reader.ts +44 -1
- package/src/vault/watcher.ts +24 -1
- package/src/vault/writer.ts +487 -413
- package/skills/persistent-memory/SKILL.md +0 -148
- 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
|
+
}
|