@adverant/nexus-memory-skill 1.3.0 → 2.1.0

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.
@@ -1,9 +1,15 @@
1
1
  #!/bin/bash
2
2
  #
3
- # Nexus Memory - Enhanced Recall Memory Hook (GraphRAG v2)
3
+ # Nexus Memory - Optimized Recall Memory Hook (GraphRAG v2)
4
4
  # Recalls relevant context from Nexus GraphRAG with entity extraction,
5
5
  # knowledge graph traversal, and episodic memory support.
6
6
  #
7
+ # PERFORMANCE OPTIMIZED:
8
+ # - Query caching with configurable TTL
9
+ # - Fast dependency checks
10
+ # - Connection keep-alive for faster requests
11
+ # - Optimized JSON parsing
12
+ #
7
13
  # Usage:
8
14
  # echo '{"query": "..."}' | recall-memory.sh
9
15
  #
@@ -16,12 +22,13 @@
16
22
  #
17
23
  # GraphRAG Enhancement Options:
18
24
  # NEXUS_INCLUDE_EPISODIC - Include episodic memory context (default: true)
19
- # NEXUS_INCLUDE_DOCUMENTS - Include document context (default: true)
25
+ # NEXUS_INCLUDE_DOCUMENTS - Include document context (default: false for speed)
20
26
  # NEXUS_INCLUDE_ENTITIES - Include entity information (default: true)
21
27
  # NEXUS_INCLUDE_FACTS - Include extracted facts (default: true)
22
- # NEXUS_INCLUDE_FOLLOWUPS - Include suggested follow-ups (default: true)
23
- # NEXUS_GRAPH_DEPTH - Multi-hop graph traversal depth (default: 2)
24
- # NEXUS_MAX_TOKENS - Token budget for recall (default: 4000)
28
+ # NEXUS_INCLUDE_FOLLOWUPS - Include suggested follow-ups (default: false)
29
+ # NEXUS_GRAPH_DEPTH - Multi-hop graph traversal depth (default: 1)
30
+ # NEXUS_MAX_TOKENS - Token budget for recall (default: 3000)
31
+ # NEXUS_CACHE_TTL - Cache TTL in seconds (default: 600)
25
32
  #
26
33
  # Output:
27
34
  # Rich JSON object with memories, documents, entities, facts, and suggestions
@@ -36,14 +43,19 @@ COMPANY_ID="${NEXUS_COMPANY_ID:-adverant}"
36
43
  APP_ID="${NEXUS_APP_ID:-claude-code}"
37
44
  VERBOSE="${NEXUS_VERBOSE:-0}"
38
45
 
39
- # GraphRAG Enhancement Configuration
46
+ # GraphRAG Enhancement Configuration (optimized defaults)
40
47
  INCLUDE_EPISODIC="${NEXUS_INCLUDE_EPISODIC:-true}"
41
- INCLUDE_DOCUMENTS="${NEXUS_INCLUDE_DOCUMENTS:-true}"
48
+ INCLUDE_DOCUMENTS="${NEXUS_INCLUDE_DOCUMENTS:-false}" # Disabled for speed
42
49
  INCLUDE_ENTITIES="${NEXUS_INCLUDE_ENTITIES:-true}"
43
50
  INCLUDE_FACTS="${NEXUS_INCLUDE_FACTS:-true}"
44
- INCLUDE_FOLLOWUPS="${NEXUS_INCLUDE_FOLLOWUPS:-true}"
45
- GRAPH_DEPTH="${NEXUS_GRAPH_DEPTH:-2}"
46
- MAX_TOKENS="${NEXUS_MAX_TOKENS:-4000}"
51
+ INCLUDE_FOLLOWUPS="${NEXUS_INCLUDE_FOLLOWUPS:-false}" # Disabled for speed
52
+ GRAPH_DEPTH="${NEXUS_GRAPH_DEPTH:-1}" # Reduced for speed
53
+ MAX_TOKENS="${NEXUS_MAX_TOKENS:-3000}"
54
+
55
+ # Cache configuration
56
+ CACHE_DIR="${HOME}/.claude/session-env/recall-cache"
57
+ CACHE_TTL="${NEXUS_CACHE_TTL:-600}" # 10 minutes
58
+ SKIP_CACHE="${NEXUS_SKIP_CACHE:-0}"
47
59
 
48
60
  # Logging function
49
61
  log() {
@@ -66,29 +78,23 @@ EMPTY_RESPONSE='{
66
78
  "suggested_followups": []
67
79
  }'
68
80
 
81
+ # =========================================================
82
+ # FAST PATH: Early exit checks
83
+ # =========================================================
84
+
69
85
  # Check for API key (REQUIRED)
70
86
  if [[ -z "$NEXUS_API_KEY" ]]; then
71
- log_error "NEXUS_API_KEY environment variable is required but not set."
72
- log_error "Get your API key from: https://dashboard.adverant.ai/dashboard/api-keys"
73
- log_error ""
74
- log_error "Set it in your shell profile or Claude Code settings:"
75
- log_error " export NEXUS_API_KEY='your-api-key-here'"
87
+ log_error "NEXUS_API_KEY not set"
76
88
  echo "$EMPTY_RESPONSE"
77
89
  exit 1
78
90
  fi
79
91
 
80
- # Check dependencies
81
- if ! command -v jq &> /dev/null; then
82
- log_error "jq is required but not installed. Install with: brew install jq"
83
- echo "$EMPTY_RESPONSE"
84
- exit 1
85
- fi
92
+ # Fast dependency check
93
+ type jq &>/dev/null || { log_error "jq not available"; echo "$EMPTY_RESPONSE"; exit 1; }
94
+ type curl &>/dev/null || { log_error "curl not available"; echo "$EMPTY_RESPONSE"; exit 1; }
86
95
 
87
- if ! command -v curl &> /dev/null; then
88
- log_error "curl is required but not installed."
89
- echo "$EMPTY_RESPONSE"
90
- exit 1
91
- fi
96
+ # Ensure cache directory exists
97
+ mkdir -p "$CACHE_DIR" 2>/dev/null
92
98
 
93
99
  # Read input from stdin
94
100
  INPUT=$(cat)
@@ -101,18 +107,24 @@ fi
101
107
 
102
108
  log "Received input: ${INPUT:0:100}..."
103
109
 
104
- # Extract query from input
105
- QUERY=$(echo "$INPUT" | jq -r '.query // .prompt // .tool_input.command // "recent context"' 2>/dev/null)
110
+ # =========================================================
111
+ # INPUT PARSING
112
+ # =========================================================
106
113
 
107
- # Get limit if provided
108
- LIMIT=$(echo "$INPUT" | jq -r '.limit // 10' 2>/dev/null)
114
+ # Extract query and limit with single jq call
115
+ read -r QUERY LIMIT < <(echo "$INPUT" | jq -r '[
116
+ (.query // .prompt // .tool_input.command // "recent context"),
117
+ (.limit // 10)
118
+ ] | @tsv' 2>/dev/null || echo "recent context 10")
119
+
120
+ # Validate limit
109
121
  if ! [[ "$LIMIT" =~ ^[0-9]+$ ]]; then
110
122
  LIMIT=10
111
123
  fi
112
124
 
113
125
  # Get optional filters from input
114
126
  FILTERS=$(echo "$INPUT" | jq -c '.filters // {}' 2>/dev/null)
115
- if [[ "$FILTERS" == "null" ]]; then
127
+ if [[ "$FILTERS" == "null" ]] || [[ -z "$FILTERS" ]]; then
116
128
  FILTERS="{}"
117
129
  fi
118
130
 
@@ -123,7 +135,6 @@ PROJECT_DIR=$(pwd)
123
135
  log "Query: $QUERY"
124
136
  log "Limit: $LIMIT"
125
137
  log "Project: $PROJECT_NAME"
126
- log "Filters: $FILTERS"
127
138
 
128
139
  # Skip if no query
129
140
  if [[ -z "$QUERY" ]] || [[ "$QUERY" == "null" ]]; then
@@ -132,7 +143,34 @@ if [[ -z "$QUERY" ]] || [[ "$QUERY" == "null" ]]; then
132
143
  exit 0
133
144
  fi
134
145
 
135
- # Build the enhanced payload for GraphRAG retrieval
146
+ # =========================================================
147
+ # CACHE CHECK
148
+ # =========================================================
149
+
150
+ # Generate query hash
151
+ QUERY_KEY="${PROJECT_NAME}:${QUERY:0:150}:$LIMIT"
152
+ QUERY_HASH=$(echo -n "$QUERY_KEY" | md5 2>/dev/null || echo -n "$QUERY_KEY" | md5sum 2>/dev/null | cut -d' ' -f1)
153
+ CACHE_FILE="${CACHE_DIR}/recall-${QUERY_HASH}"
154
+
155
+ # Check cache
156
+ if [[ "$SKIP_CACHE" != "1" ]] && [[ -f "$CACHE_FILE" ]]; then
157
+ CACHE_AGE=$(( $(date +%s) - $(stat -f %m "$CACHE_FILE" 2>/dev/null || stat -c %Y "$CACHE_FILE" 2>/dev/null || echo 0) ))
158
+
159
+ if [[ "$CACHE_AGE" -lt "$CACHE_TTL" ]]; then
160
+ log "Cache hit (age: ${CACHE_AGE}s)"
161
+ cat "$CACHE_FILE"
162
+ exit 0
163
+ else
164
+ log "Cache expired"
165
+ rm -f "$CACHE_FILE" 2>/dev/null
166
+ fi
167
+ fi
168
+
169
+ # =========================================================
170
+ # API REQUEST
171
+ # =========================================================
172
+
173
+ # Build the optimized payload for GraphRAG retrieval
136
174
  PAYLOAD=$(jq -n \
137
175
  --arg query "$QUERY" \
138
176
  --argjson limit "$LIMIT" \
@@ -159,42 +197,40 @@ PAYLOAD=$(jq -n \
159
197
  include_entities: $includeEntities,
160
198
  include_facts: $includeFacts,
161
199
  include_followups: $includeFollowups,
162
- enable_graph_traversal: true,
200
+ enable_graph_traversal: ($graphDepth > 0),
163
201
  graph_depth: $graphDepth,
164
202
  max_tokens: $maxTokens,
165
203
  hybrid_search: true,
166
- rerank: true
204
+ rerank: true,
205
+ fast_mode: true
167
206
  }')
168
207
 
169
- # Use enhanced retrieval endpoint for full GraphRAG features
170
- # The Gateway exposes this at /api/retrieve/enhanced (routes through smartRouter)
208
+ # Use enhanced retrieval endpoint
171
209
  ENDPOINT="$NEXUS_API_URL/api/retrieve/enhanced"
172
210
  log "Recalling from $ENDPOINT"
173
211
 
174
- # Search GraphRAG for relevant memories via enhanced retrieval endpoint
175
- # Authorization: Bearer token for API authentication
176
- # Headers: X-Company-ID, X-App-ID, X-User-ID (required for tenant context)
212
+ # Make request with optimized settings
177
213
  RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$ENDPOINT" \
178
214
  -H "Content-Type: application/json" \
179
215
  -H "Authorization: Bearer $NEXUS_API_KEY" \
180
216
  -H "X-Company-ID: $COMPANY_ID" \
181
217
  -H "X-App-ID: $APP_ID" \
182
218
  -H "X-User-ID: ${USER:-unknown}" \
219
+ -H "Connection: keep-alive" \
183
220
  -d "$PAYLOAD" \
184
- --max-time 15 2>&1)
221
+ --connect-timeout 3 \
222
+ --max-time 10 2>&1)
185
223
 
186
224
  # Parse response
187
225
  HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
188
226
  BODY=$(echo "$RESPONSE" | sed '$d')
189
227
 
190
228
  log "Response code: $HTTP_CODE"
191
- log "Response body: ${BODY:0:500}..."
192
229
 
193
- # Check for errors - fallback to basic endpoint if enhanced fails
230
+ # Fallback to basic endpoint if enhanced fails
194
231
  if [[ "$HTTP_CODE" != "200" ]]; then
195
232
  log "Enhanced retrieval failed (HTTP $HTTP_CODE), trying basic endpoint..."
196
233
 
197
- # Fallback to basic recall endpoint
198
234
  BASIC_PAYLOAD=$(jq -n \
199
235
  --arg query "$QUERY" \
200
236
  --argjson limit "$LIMIT" \
@@ -209,8 +245,10 @@ if [[ "$HTTP_CODE" != "200" ]]; then
209
245
  -H "X-Company-ID: $COMPANY_ID" \
210
246
  -H "X-App-ID: $APP_ID" \
211
247
  -H "X-User-ID: ${USER:-unknown}" \
248
+ -H "Connection: keep-alive" \
212
249
  -d "$BASIC_PAYLOAD" \
213
- --max-time 10 2>&1)
250
+ --connect-timeout 2 \
251
+ --max-time 8 2>&1)
214
252
 
215
253
  HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
216
254
  BODY=$(echo "$RESPONSE" | sed '$d')
@@ -225,16 +263,19 @@ if [[ "$HTTP_CODE" != "200" ]]; then
225
263
  fi
226
264
 
227
265
  # Validate JSON response
228
- if ! echo "$BODY" | jq . &>/dev/null; then
266
+ if ! echo "$BODY" | jq -e . &>/dev/null; then
229
267
  log_error "Invalid JSON response"
230
268
  echo "$EMPTY_RESPONSE"
231
269
  exit 0
232
270
  fi
233
271
 
234
- # Normalize response to expected structure
235
- # Handle both direct response and Gateway-wrapped response (.data.results)
272
+ # =========================================================
273
+ # RESPONSE NORMALIZATION
274
+ # =========================================================
275
+
276
+ # Normalize response to expected structure - single jq pass
236
277
  NORMALIZED=$(echo "$BODY" | jq '
237
- # Extract the actual result object (handle Gateway wrapper)
278
+ # Handle Gateway wrapper
238
279
  (if .data.results then .data.results else . end) as $result |
239
280
  {
240
281
  memories: ($result.unified_memories // $result.memories // []),
@@ -244,13 +285,25 @@ NORMALIZED=$(echo "$BODY" | jq '
244
285
  episodic_context: ($result.episodic_context // []),
245
286
  suggested_followups: ($result.suggested_followups // []),
246
287
  query_understanding: ($result.query_understanding // null),
247
- confidence_score: ($result.confidence_score // null)
288
+ confidence_score: ($result.confidence_score // null),
289
+ context: {
290
+ project: ($result.context.project // "unknown"),
291
+ query: ($result.query // "")
292
+ }
248
293
  }
249
294
  ')
250
295
 
251
- # Output the normalized response
296
+ # Cache the result
297
+ if [[ "$SKIP_CACHE" != "1" ]]; then
298
+ echo "$NORMALIZED" > "$CACHE_FILE" 2>/dev/null
299
+ log "Cached result for ${CACHE_TTL}s"
300
+
301
+ # Cleanup old cache files (async)
302
+ (find "$CACHE_DIR" -type f -name "recall-*" -mmin +$((CACHE_TTL / 60 + 1)) -delete 2>/dev/null &)
303
+ fi
304
+
305
+ # Output verbose info if enabled
252
306
  if [[ "$VERBOSE" == "1" ]]; then
253
- # In verbose mode, include additional metadata
254
307
  MEMORY_COUNT=$(echo "$NORMALIZED" | jq '.memories | length')
255
308
  ENTITY_COUNT=$(echo "$NORMALIZED" | jq '.entities | length')
256
309
  FACT_COUNT=$(echo "$NORMALIZED" | jq '.facts | length')
@@ -258,4 +311,4 @@ if [[ "$VERBOSE" == "1" ]]; then
258
311
  log "Retrieved: $MEMORY_COUNT memories, $ENTITY_COUNT entities, $FACT_COUNT facts"
259
312
  fi
260
313
 
261
- echo "$NORMALIZED"
314
+ echo "$NORMALIZED"
@@ -27,6 +27,7 @@
27
27
  # pattern - Reusable code patterns
28
28
  # preference - User/project preferences
29
29
  # context - Project-specific context
30
+ # bead - Git-backed issue/task tracking (synced via bead-sync.sh)
30
31
  #
31
32
 
32
33
  set -o pipefail
@@ -84,6 +85,85 @@ detect_domain() {
84
85
  fi
85
86
  }
86
87
 
88
+ # Classify content to determine if it should be stored
89
+ # Returns: bead | decision | learning | error_fix | code_change | routine | noise
90
+ classify_content() {
91
+ local content="$1"
92
+ local event_type="$2"
93
+ local content_lower=$(echo "$content" | tr '[:upper:]' '[:lower:]')
94
+
95
+ # Bead operations (HIGH priority - always store + trigger sync)
96
+ # Matches: bd create, bd close, bd work, bd dep, bd list, bd ready, bd show, bd init
97
+ if echo "$content" | grep -qE '\bbd (create|close|work|dep|list|ready|show|init|update|add|remove|link|unlink|graph|molecule)\b'; then
98
+ echo "bead"
99
+ return 0
100
+ fi
101
+
102
+ # Also detect bead-related event type
103
+ if [[ "$event_type" == "bead" ]]; then
104
+ echo "bead"
105
+ return 0
106
+ fi
107
+
108
+ # High-value patterns - ALWAYS store
109
+ # Decisions: chose X over Y, decided to, instead of, went with
110
+ if echo "$content_lower" | grep -qE '(decided to|chose|instead of|went with|opted for|prefer|selected|picked)'; then
111
+ echo "decision"
112
+ return 0
113
+ fi
114
+
115
+ # Learnings: learned, realized, discovered, found out, turns out
116
+ if echo "$content_lower" | grep -qE '(learned|realized|discovered|found out|turns out|figured out|now i know|aha|insight)'; then
117
+ echo "learning"
118
+ return 0
119
+ fi
120
+
121
+ # Error fixes: fixed, resolved, solved, bug, error, issue
122
+ if echo "$content_lower" | grep -qE '(fixed|resolved|solved|the (bug|error|issue)|fix for|solution)'; then
123
+ echo "error_fix"
124
+ return 0
125
+ fi
126
+
127
+ # Event type overrides for explicit categorization
128
+ if [[ "$event_type" == "decision" ]] || [[ "$event_type" == "preference" ]]; then
129
+ echo "decision"
130
+ return 0
131
+ fi
132
+
133
+ if [[ "$event_type" == "learning" ]]; then
134
+ echo "learning"
135
+ return 0
136
+ fi
137
+
138
+ if [[ "$event_type" == "fix" ]] || [[ "$event_type" == "pattern" ]]; then
139
+ echo "error_fix"
140
+ return 0
141
+ fi
142
+
143
+ # Medium-value: code changes with explanation
144
+ if echo "$content_lower" | grep -qE '(modified|updated|changed|added|implemented|created|refactored)'; then
145
+ echo "code_change"
146
+ return 0
147
+ fi
148
+
149
+ # Low-value: routine commands - SKIP storage
150
+ # Common shell commands, git status checks, npm installs
151
+ if echo "$content" | grep -qE '^(ls |cd |pwd|cat |head |tail |grep |find |git status|git diff|git log|npm install|npm run|yarn |pnpm )'; then
152
+ echo "routine"
153
+ return 0
154
+ fi
155
+
156
+ # Noise: very short content, single words, numbers only
157
+ if [[ ${#content} -lt 30 ]]; then
158
+ echo "noise"
159
+ return 0
160
+ fi
161
+
162
+ # Default: store as context
163
+ echo "context"
164
+ return 0
165
+ }
166
+
87
167
  # Get previous memory ID for causal chaining
88
168
  get_previous_memory_id() {
89
169
  if [[ -f "$LAST_MEMORY_FILE" ]]; then
@@ -142,7 +222,7 @@ SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"' 2>/dev/null)
142
222
  EVENT_TYPE=$(echo "$INPUT" | jq -r '.event_type // "conversation"' 2>/dev/null)
143
223
 
144
224
  # Validate event type
145
- VALID_TYPES=("fix" "decision" "learning" "pattern" "preference" "context" "conversation")
225
+ VALID_TYPES=("fix" "decision" "learning" "pattern" "preference" "context" "conversation" "bead")
146
226
  TYPE_VALID=0
147
227
  for type in "${VALID_TYPES[@]}"; do
148
228
  if [[ "$EVENT_TYPE" == "$type" ]]; then
@@ -169,6 +249,16 @@ if [[ -z "$CONTENT" ]] || [[ "$CONTENT" == "null" ]]; then
169
249
  exit 0
170
250
  fi
171
251
 
252
+ # Classify content to determine storage value
253
+ CONTENT_CLASS=$(classify_content "$CONTENT" "$EVENT_TYPE")
254
+ log "Content classification: $CONTENT_CLASS"
255
+
256
+ # Skip low-value content (routine commands, noise)
257
+ if [[ "$CONTENT_CLASS" == "routine" ]] || [[ "$CONTENT_CLASS" == "noise" ]]; then
258
+ log "Skipping $CONTENT_CLASS content (low value)"
259
+ exit 0
260
+ fi
261
+
172
262
  # Truncate very long content (max 10000 chars)
173
263
  if [[ ${#CONTENT} -gt 10000 ]]; then
174
264
  CONTENT="${CONTENT:0:10000}... [truncated]"
@@ -196,6 +286,7 @@ PAYLOAD=$(jq -n \
196
286
  --arg content "$CONTENT" \
197
287
  --arg session "$SESSION_ID" \
198
288
  --arg event "$EVENT_TYPE" \
289
+ --arg contentClass "$CONTENT_CLASS" \
199
290
  --arg project "$PROJECT_NAME" \
200
291
  --arg projectDir "$PROJECT_DIR" \
201
292
  --arg timestamp "$TIMESTAMP" \
@@ -207,10 +298,11 @@ PAYLOAD=$(jq -n \
207
298
  --argjson entityTypes "$ENTITY_TYPES_JSON" \
208
299
  '{
209
300
  content: $content,
210
- tags: ["claude-code", "session:\($session)", "type:\($event)", "project:\($project)", "domain:\($domain)"],
301
+ tags: ["claude-code", "session:\($session)", "type:\($event)", "class:\($contentClass)", "project:\($project)", "domain:\($domain)"],
211
302
  metadata: {
212
303
  sessionId: $session,
213
304
  eventType: $event,
305
+ contentClass: $contentClass,
214
306
  projectDir: $projectDir,
215
307
  projectName: $project,
216
308
  timestamp: $timestamp,
@@ -288,4 +380,15 @@ else
288
380
  ) &
289
381
  fi
290
382
 
383
+ # Trigger bead-sync if this was a bead operation
384
+ # This syncs the local beads to GraphRAG in the background
385
+ if [[ "$CONTENT_CLASS" == "bead" ]]; then
386
+ BEAD_SYNC_HOOK="${HOME}/.claude/hooks/bead-sync.sh"
387
+ if [[ -x "$BEAD_SYNC_HOOK" ]]; then
388
+ log "Triggering bead-sync for bead operation"
389
+ # Run sync-latest in background to not block the hook
390
+ ("$BEAD_SYNC_HOOK" sync-latest 2>/dev/null) &
391
+ fi
392
+ fi
393
+
291
394
  exit 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adverant/nexus-memory-skill",
3
- "version": "1.3.0",
3
+ "version": "2.1.0",
4
4
  "description": "Claude Code skill for persistent memory via Nexus GraphRAG - store and recall memories across all sessions and projects",
5
5
  "main": "SKILL.md",
6
6
  "type": "module",