@pcircle/memesh 2.9.0 ā 2.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mcp/ToolDefinitions.d.ts.map +1 -1
- package/dist/mcp/ToolDefinitions.js +0 -104
- package/dist/mcp/ToolDefinitions.js.map +1 -1
- package/package.json +2 -1
- package/plugin.json +1 -1
- package/scripts/hooks/README.md +230 -0
- package/scripts/hooks/__tests__/hook-test-harness.js +218 -0
- package/scripts/hooks/__tests__/hooks.test.js +267 -0
- package/scripts/hooks/hook-utils.js +899 -0
- package/scripts/hooks/post-commit.js +307 -0
- package/scripts/hooks/post-tool-use.js +812 -0
- package/scripts/hooks/pre-tool-use.js +462 -0
- package/scripts/hooks/session-start.js +544 -0
- package/scripts/hooks/stop.js +673 -0
- package/scripts/hooks/subagent-stop.js +184 -0
- package/scripts/postinstall-lib.js +8 -4
- package/scripts/postinstall-new.js +15 -7
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SessionStart Hook - Claude Code Event-Driven Hooks
|
|
5
|
+
*
|
|
6
|
+
* Triggered at the start of each Claude Code session.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Checks MeMesh MCP server availability
|
|
10
|
+
* - Auto-recalls last session key points from MeMesh
|
|
11
|
+
* - Reads recommendations from last session
|
|
12
|
+
* - Displays suggested skills to load
|
|
13
|
+
* - Shows warnings (quota, slow tools, etc.)
|
|
14
|
+
* - Initializes current session state
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
HOME_DIR,
|
|
19
|
+
STATE_DIR,
|
|
20
|
+
MEMESH_DB_PATH,
|
|
21
|
+
THRESHOLDS,
|
|
22
|
+
readJSONFile,
|
|
23
|
+
writeJSONFile,
|
|
24
|
+
sqliteQueryJSON,
|
|
25
|
+
getTimeAgo,
|
|
26
|
+
logError,
|
|
27
|
+
queryActivePlans,
|
|
28
|
+
renderTimelineCompact,
|
|
29
|
+
} from './hook-utils.js';
|
|
30
|
+
import fs from 'fs';
|
|
31
|
+
import path from 'path';
|
|
32
|
+
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// File Paths
|
|
35
|
+
// ============================================================================
|
|
36
|
+
|
|
37
|
+
const CCB_HEARTBEAT_FILE = path.join(STATE_DIR, 'ccb-heartbeat.json');
|
|
38
|
+
const MCP_SETTINGS_FILE = path.join(HOME_DIR, '.claude', 'mcp_settings.json');
|
|
39
|
+
const RECOMMENDATIONS_FILE = path.join(STATE_DIR, 'recommendations.json');
|
|
40
|
+
const SESSION_CONTEXT_FILE = path.join(STATE_DIR, 'session-context.json');
|
|
41
|
+
const CURRENT_SESSION_FILE = path.join(STATE_DIR, 'current-session.json');
|
|
42
|
+
const LAST_SESSION_CACHE_FILE = path.join(STATE_DIR, 'last-session-summary.json');
|
|
43
|
+
|
|
44
|
+
/** Maximum cache age: 7 days */
|
|
45
|
+
const CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
46
|
+
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// MeMesh Status Check
|
|
49
|
+
// ============================================================================
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check MeMesh MCP Server availability
|
|
53
|
+
* @returns {{ configured: boolean, running: boolean, lastHeartbeat: string|null, serverPath: string|null }}
|
|
54
|
+
*/
|
|
55
|
+
function checkCCBAvailability() {
|
|
56
|
+
const result = {
|
|
57
|
+
configured: false,
|
|
58
|
+
running: false,
|
|
59
|
+
lastHeartbeat: null,
|
|
60
|
+
serverPath: null,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Check if MeMesh is configured in MCP settings
|
|
64
|
+
try {
|
|
65
|
+
if (fs.existsSync(MCP_SETTINGS_FILE)) {
|
|
66
|
+
const mcpSettings = JSON.parse(fs.readFileSync(MCP_SETTINGS_FILE, 'utf-8'));
|
|
67
|
+
|
|
68
|
+
// Check for MeMesh and legacy names (backward compatibility)
|
|
69
|
+
const ccbNames = [
|
|
70
|
+
'memesh',
|
|
71
|
+
'@pcircle/memesh',
|
|
72
|
+
'@pcircle/claude-code-buddy-mcp',
|
|
73
|
+
'claude-code-buddy',
|
|
74
|
+
'ccb',
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
for (const name of ccbNames) {
|
|
78
|
+
if (mcpSettings.mcpServers && mcpSettings.mcpServers[name]) {
|
|
79
|
+
result.configured = true;
|
|
80
|
+
result.serverPath = mcpSettings.mcpServers[name].args?.[0] || 'configured';
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
// Ignore parse errors
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Check heartbeat file (MeMesh writes this when running)
|
|
90
|
+
try {
|
|
91
|
+
if (fs.existsSync(CCB_HEARTBEAT_FILE)) {
|
|
92
|
+
const heartbeat = JSON.parse(fs.readFileSync(CCB_HEARTBEAT_FILE, 'utf-8'));
|
|
93
|
+
result.lastHeartbeat = heartbeat.timestamp;
|
|
94
|
+
|
|
95
|
+
const heartbeatTime = new Date(heartbeat.timestamp).getTime();
|
|
96
|
+
const now = Date.now();
|
|
97
|
+
|
|
98
|
+
if (now - heartbeatTime < THRESHOLDS.HEARTBEAT_VALIDITY) {
|
|
99
|
+
result.running = true;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} catch {
|
|
103
|
+
// Ignore errors
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Display MeMesh status and reminder
|
|
111
|
+
*/
|
|
112
|
+
function displayCCBStatus(ccbStatus) {
|
|
113
|
+
console.log('ā'.repeat(60));
|
|
114
|
+
console.log(' š¤ MeMesh Status');
|
|
115
|
+
console.log('ā'.repeat(60));
|
|
116
|
+
|
|
117
|
+
if (!ccbStatus.configured) {
|
|
118
|
+
console.log('');
|
|
119
|
+
console.log(' ā ļø MeMesh MCP Server is NOT configured!');
|
|
120
|
+
console.log('');
|
|
121
|
+
console.log(' MeMesh provides memory management and knowledge graph tools.');
|
|
122
|
+
console.log(' To configure MeMesh, add it to ~/.claude/mcp_settings.json');
|
|
123
|
+
console.log('');
|
|
124
|
+
console.log(' Available MeMesh tools when connected:');
|
|
125
|
+
console.log(' ⢠buddy-remember: Query past knowledge');
|
|
126
|
+
console.log(' ⢠buddy-do: Execute common operations');
|
|
127
|
+
console.log(' ⢠memesh-create-entities: Store new knowledge to graph');
|
|
128
|
+
console.log('');
|
|
129
|
+
} else if (!ccbStatus.running) {
|
|
130
|
+
console.log('');
|
|
131
|
+
console.log(' ā¹ļø MeMesh is configured but status unknown');
|
|
132
|
+
console.log(` Path: ${ccbStatus.serverPath}`);
|
|
133
|
+
console.log('');
|
|
134
|
+
console.log(' š REMINDER: Use MeMesh tools for memory management:');
|
|
135
|
+
console.log('');
|
|
136
|
+
console.log(' Before starting work:');
|
|
137
|
+
console.log(' buddy-remember "relevant topic" - Query past experiences');
|
|
138
|
+
console.log('');
|
|
139
|
+
console.log(' After completing work:');
|
|
140
|
+
console.log(' memesh-create-entities - Store new learnings');
|
|
141
|
+
console.log(' memesh-record-mistake - Record errors for future reference');
|
|
142
|
+
console.log('');
|
|
143
|
+
console.log(' š” If MeMesh tools fail, check MCP server status.');
|
|
144
|
+
console.log('');
|
|
145
|
+
} else {
|
|
146
|
+
console.log('');
|
|
147
|
+
console.log(' ā
MeMesh MCP Server is running');
|
|
148
|
+
console.log(` Last heartbeat: ${ccbStatus.lastHeartbeat}`);
|
|
149
|
+
console.log('');
|
|
150
|
+
console.log(' š Session Start Checklist:');
|
|
151
|
+
console.log(' ā buddy-remember - Query relevant past knowledge');
|
|
152
|
+
console.log('');
|
|
153
|
+
console.log(' š Session End Checklist:');
|
|
154
|
+
console.log(' ā memesh-create-entities - Store new learnings');
|
|
155
|
+
console.log(' ā memesh-record-mistake - Record any errors');
|
|
156
|
+
console.log('');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
console.log('ā'.repeat(60));
|
|
160
|
+
console.log('');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ============================================================================
|
|
164
|
+
// Memory Recall
|
|
165
|
+
// ============================================================================
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Try to read session summary from cache file (fast path).
|
|
169
|
+
* Cache is written by stop.js on session end.
|
|
170
|
+
* @returns {{ entityName: string, createdAt: string, metadata: object, keyPoints: string[] } | null}
|
|
171
|
+
*/
|
|
172
|
+
function recallFromCache() {
|
|
173
|
+
try {
|
|
174
|
+
if (!fs.existsSync(LAST_SESSION_CACHE_FILE)) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const cache = readJSONFile(LAST_SESSION_CACHE_FILE, null);
|
|
179
|
+
if (!cache || !cache.savedAt || !cache.keyPoints) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Check staleness
|
|
184
|
+
const cacheAge = Date.now() - new Date(cache.savedAt).getTime();
|
|
185
|
+
if (cacheAge > CACHE_MAX_AGE_MS) {
|
|
186
|
+
// Stale cache ā delete it
|
|
187
|
+
try { fs.unlinkSync(LAST_SESSION_CACHE_FILE); } catch { /* ignore */ }
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
entityName: 'session_cache',
|
|
193
|
+
createdAt: cache.savedAt,
|
|
194
|
+
metadata: {
|
|
195
|
+
duration: cache.duration,
|
|
196
|
+
toolCount: cache.toolCount,
|
|
197
|
+
},
|
|
198
|
+
keyPoints: cache.keyPoints,
|
|
199
|
+
};
|
|
200
|
+
} catch (error) {
|
|
201
|
+
logError('recallFromCache', error);
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Recall recent session key points from MeMesh (slow path ā SQLite query).
|
|
208
|
+
* Used as fallback when cache is not available.
|
|
209
|
+
* @returns {{ entityName: string, createdAt: string, metadata: object, keyPoints: string[] } | null}
|
|
210
|
+
*/
|
|
211
|
+
function recallFromSQLite() {
|
|
212
|
+
try {
|
|
213
|
+
if (!fs.existsSync(MEMESH_DB_PATH)) {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const cutoffDate = new Date();
|
|
218
|
+
cutoffDate.setDate(cutoffDate.getDate() - THRESHOLDS.RECALL_DAYS);
|
|
219
|
+
const cutoffISO = cutoffDate.toISOString();
|
|
220
|
+
|
|
221
|
+
const query = `
|
|
222
|
+
SELECT id, name, metadata, created_at
|
|
223
|
+
FROM entities
|
|
224
|
+
WHERE type = ? AND created_at > ?
|
|
225
|
+
ORDER BY created_at DESC
|
|
226
|
+
LIMIT 1
|
|
227
|
+
`.replace(/\n/g, ' ');
|
|
228
|
+
|
|
229
|
+
// Use JSON mode to avoid pipe-split issues with | in metadata
|
|
230
|
+
const entityRows = sqliteQueryJSON(
|
|
231
|
+
MEMESH_DB_PATH,
|
|
232
|
+
query,
|
|
233
|
+
['session_keypoint', cutoffISO]
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
if (!entityRows || entityRows.length === 0) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const row = entityRows[0];
|
|
241
|
+
const entityId = row.id;
|
|
242
|
+
const entityName = row.name;
|
|
243
|
+
const createdAt = row.created_at;
|
|
244
|
+
|
|
245
|
+
// Observations also use JSON mode for safety
|
|
246
|
+
const obsRows = sqliteQueryJSON(
|
|
247
|
+
MEMESH_DB_PATH,
|
|
248
|
+
'SELECT content FROM observations WHERE entity_id = ? ORDER BY created_at ASC',
|
|
249
|
+
[entityId]
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
const keyPoints = (obsRows || []).map(r => r.content).filter(Boolean);
|
|
253
|
+
|
|
254
|
+
let parsedMetadata = {};
|
|
255
|
+
try {
|
|
256
|
+
parsedMetadata = JSON.parse(row.metadata || '{}');
|
|
257
|
+
} catch {
|
|
258
|
+
// Ignore parse errors
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
entityName,
|
|
263
|
+
createdAt,
|
|
264
|
+
metadata: parsedMetadata,
|
|
265
|
+
keyPoints,
|
|
266
|
+
};
|
|
267
|
+
} catch (error) {
|
|
268
|
+
logError('recallFromSQLite', error);
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Recall recent session key points ā cache-first, SQLite fallback.
|
|
275
|
+
* @returns {{ entityName: string, createdAt: string, metadata: object, keyPoints: string[] } | null}
|
|
276
|
+
*/
|
|
277
|
+
function recallRecentKeyPoints() {
|
|
278
|
+
// Fast path: read from cache file (no sqlite3 spawn)
|
|
279
|
+
const cached = recallFromCache();
|
|
280
|
+
if (cached) {
|
|
281
|
+
return cached;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Slow path: query SQLite
|
|
285
|
+
return recallFromSQLite();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Display recalled key points from last session
|
|
290
|
+
*/
|
|
291
|
+
function displayRecalledMemory(recalledData) {
|
|
292
|
+
console.log('ā'.repeat(60));
|
|
293
|
+
console.log(' š§ MeMesh Memory Recall');
|
|
294
|
+
console.log('ā'.repeat(60));
|
|
295
|
+
|
|
296
|
+
if (!recalledData || !recalledData.keyPoints || recalledData.keyPoints.length === 0) {
|
|
297
|
+
console.log('');
|
|
298
|
+
console.log(' ā¹ļø No recent memories found (last 30 days)');
|
|
299
|
+
console.log(' š” Memories will be auto-saved when this session ends');
|
|
300
|
+
console.log('');
|
|
301
|
+
console.log('ā'.repeat(60));
|
|
302
|
+
console.log('');
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
console.log('');
|
|
307
|
+
|
|
308
|
+
// Display timestamp
|
|
309
|
+
const savedTime = new Date(recalledData.createdAt);
|
|
310
|
+
const timeAgo = getTimeAgo(savedTime);
|
|
311
|
+
console.log(` š Saved: ${timeAgo}`);
|
|
312
|
+
|
|
313
|
+
// Display metadata if available
|
|
314
|
+
if (recalledData.metadata) {
|
|
315
|
+
const meta = recalledData.metadata;
|
|
316
|
+
if (meta.duration) {
|
|
317
|
+
console.log(` ā±ļø Last session duration: ${meta.duration}`);
|
|
318
|
+
}
|
|
319
|
+
if (meta.toolCount) {
|
|
320
|
+
console.log(` š ļø Tools used: ${meta.toolCount}`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
console.log('');
|
|
325
|
+
console.log(' š Key Points:');
|
|
326
|
+
|
|
327
|
+
// Display key points with formatting
|
|
328
|
+
recalledData.keyPoints.forEach(point => {
|
|
329
|
+
if (point.startsWith('[SESSION]')) {
|
|
330
|
+
console.log(` š ${point.replace('[SESSION] ', '')}`);
|
|
331
|
+
} else if (point.startsWith('[WORK]')) {
|
|
332
|
+
console.log(` š ${point.replace('[WORK] ', '')}`);
|
|
333
|
+
} else if (point.startsWith('[COMMIT]')) {
|
|
334
|
+
console.log(` ā
${point.replace('[COMMIT] ', '')}`);
|
|
335
|
+
} else if (point.startsWith('[ISSUE]') || point.startsWith('[PROBLEM]')) {
|
|
336
|
+
console.log(` ā ļø ${point.replace(/\[(ISSUE|PROBLEM)\] /, '')}`);
|
|
337
|
+
} else if (point.startsWith('[LEARN]')) {
|
|
338
|
+
console.log(` š” ${point.replace('[LEARN] ', '')}`);
|
|
339
|
+
} else if (point.startsWith('[TASK]')) {
|
|
340
|
+
console.log(` š ${point.replace('[TASK] ', '')}`);
|
|
341
|
+
} else if (point.startsWith('[DECISION]')) {
|
|
342
|
+
console.log(` šÆ ${point.replace('[DECISION] ', '')}`);
|
|
343
|
+
} else if (point.startsWith('[PATTERN]')) {
|
|
344
|
+
console.log(` š ${point.replace('[PATTERN] ', '')}`);
|
|
345
|
+
} else if (point.startsWith('[SCOPE]') || point.startsWith('[FOCUS]')) {
|
|
346
|
+
console.log(` šÆ ${point.replace(/\[(SCOPE|FOCUS)\] /, '')}`);
|
|
347
|
+
} else if (point.startsWith('[NOTE]')) {
|
|
348
|
+
console.log(` š ${point.replace('[NOTE] ', '')}`);
|
|
349
|
+
} else {
|
|
350
|
+
console.log(` ⢠${point}`);
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
console.log('');
|
|
355
|
+
console.log('ā'.repeat(60));
|
|
356
|
+
console.log('');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// ============================================================================
|
|
360
|
+
// CLAUDE.md Reload
|
|
361
|
+
// ============================================================================
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Find and display project CLAUDE.md content on session start.
|
|
365
|
+
* This ensures instructions are fresh in context even after compaction.
|
|
366
|
+
* Searches: CWD/.claude/CLAUDE.md, CWD/CLAUDE.md
|
|
367
|
+
*/
|
|
368
|
+
function reloadClaudeMd() {
|
|
369
|
+
const cwd = process.cwd();
|
|
370
|
+
const candidates = [
|
|
371
|
+
path.join(cwd, '.claude', 'CLAUDE.md'),
|
|
372
|
+
path.join(cwd, 'CLAUDE.md'),
|
|
373
|
+
];
|
|
374
|
+
|
|
375
|
+
for (const candidate of candidates) {
|
|
376
|
+
try {
|
|
377
|
+
if (fs.existsSync(candidate)) {
|
|
378
|
+
const content = fs.readFileSync(candidate, 'utf-8');
|
|
379
|
+
const lineCount = content.split('\n').length;
|
|
380
|
+
const relativePath = path.relative(cwd, candidate);
|
|
381
|
+
|
|
382
|
+
console.log('ā'.repeat(60));
|
|
383
|
+
console.log(' š CLAUDE.md Reloaded');
|
|
384
|
+
console.log('ā'.repeat(60));
|
|
385
|
+
console.log('');
|
|
386
|
+
console.log(` Source: ${relativePath} (${lineCount} lines)`);
|
|
387
|
+
console.log('');
|
|
388
|
+
console.log(content);
|
|
389
|
+
console.log('');
|
|
390
|
+
console.log('ā'.repeat(60));
|
|
391
|
+
console.log('');
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
} catch {
|
|
395
|
+
// Skip unreadable files
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// ============================================================================
|
|
401
|
+
// Main Session Start Logic
|
|
402
|
+
// ============================================================================
|
|
403
|
+
|
|
404
|
+
// ============================================================================
|
|
405
|
+
// Active Plans Display (Beta)
|
|
406
|
+
// ============================================================================
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Display active plans with compact timeline.
|
|
410
|
+
* Also warns about stale plans (no progress for 7+ days).
|
|
411
|
+
*/
|
|
412
|
+
function displayActivePlans() {
|
|
413
|
+
try {
|
|
414
|
+
if (!fs.existsSync(MEMESH_DB_PATH)) return;
|
|
415
|
+
|
|
416
|
+
const activePlans = queryActivePlans(MEMESH_DB_PATH);
|
|
417
|
+
if (activePlans.length === 0) return;
|
|
418
|
+
|
|
419
|
+
console.log('ā'.repeat(60));
|
|
420
|
+
console.log(' š Active Plans');
|
|
421
|
+
console.log('ā'.repeat(60));
|
|
422
|
+
console.log('');
|
|
423
|
+
|
|
424
|
+
for (const plan of activePlans) {
|
|
425
|
+
console.log(renderTimelineCompact(plan));
|
|
426
|
+
|
|
427
|
+
// Stale plan warning (7+ days since last progress)
|
|
428
|
+
const lastStep = plan.metadata.stepsDetail
|
|
429
|
+
?.filter(s => s.completed && s.date)
|
|
430
|
+
.sort((a, b) => b.date.localeCompare(a.date))[0];
|
|
431
|
+
if (lastStep && lastStep.date) {
|
|
432
|
+
const daysSince = Math.floor((Date.now() - new Date(lastStep.date).getTime()) / (1000 * 60 * 60 * 24));
|
|
433
|
+
if (daysSince >= 7) {
|
|
434
|
+
console.log(` ā ļø No progress for ${daysSince} days`);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
console.log('');
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
console.log('ā'.repeat(60));
|
|
441
|
+
console.log('');
|
|
442
|
+
} catch (error) {
|
|
443
|
+
logError('displayActivePlans', error);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function sessionStart() {
|
|
448
|
+
console.log('\nš Smart-Agents Session Started\n');
|
|
449
|
+
|
|
450
|
+
// Reload project CLAUDE.md into context
|
|
451
|
+
reloadClaudeMd();
|
|
452
|
+
|
|
453
|
+
// Check MeMesh availability
|
|
454
|
+
const ccbStatus = checkCCBAvailability();
|
|
455
|
+
displayCCBStatus(ccbStatus);
|
|
456
|
+
|
|
457
|
+
// Auto-recall last session's key points from MeMesh
|
|
458
|
+
const recalledMemory = recallRecentKeyPoints();
|
|
459
|
+
displayRecalledMemory(recalledMemory);
|
|
460
|
+
|
|
461
|
+
// Display active plans (beta)
|
|
462
|
+
displayActivePlans();
|
|
463
|
+
|
|
464
|
+
// Read recommendations from last session
|
|
465
|
+
const recommendations = readJSONFile(RECOMMENDATIONS_FILE, {
|
|
466
|
+
recommendedSkills: [],
|
|
467
|
+
detectedPatterns: [],
|
|
468
|
+
warnings: [],
|
|
469
|
+
lastUpdated: null,
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
// Read session context
|
|
473
|
+
const sessionContext = readJSONFile(SESSION_CONTEXT_FILE, {
|
|
474
|
+
tokenQuota: { used: 0, limit: 200000 },
|
|
475
|
+
learnedPatterns: [],
|
|
476
|
+
lastSessionDate: null,
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
// Display recommendations
|
|
480
|
+
if (recommendations.recommendedSkills?.length > 0) {
|
|
481
|
+
console.log('š Recommended skills based on last session:');
|
|
482
|
+
recommendations.recommendedSkills.forEach(skill => {
|
|
483
|
+
const priority = skill.priority === 'high' ? 'š“' : skill.priority === 'medium' ? 'š”' : 'š¢';
|
|
484
|
+
console.log(` ${priority} ${skill.name} - ${skill.reason}`);
|
|
485
|
+
});
|
|
486
|
+
console.log('');
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Display detected patterns
|
|
490
|
+
if (recommendations.detectedPatterns?.length > 0) {
|
|
491
|
+
console.log('⨠Detected patterns:');
|
|
492
|
+
recommendations.detectedPatterns.slice(0, 3).forEach(pattern => {
|
|
493
|
+
console.log(` ⢠${pattern.description}`);
|
|
494
|
+
if (pattern.suggestion) {
|
|
495
|
+
console.log(` š” ${pattern.suggestion}`);
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
console.log('');
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Display warnings
|
|
502
|
+
if (recommendations.warnings?.length > 0) {
|
|
503
|
+
console.log('ā ļø Warnings:');
|
|
504
|
+
recommendations.warnings.forEach(warning => {
|
|
505
|
+
console.log(` ⢠${warning}`);
|
|
506
|
+
});
|
|
507
|
+
console.log('');
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Display quota info (guard against division by zero)
|
|
511
|
+
const quotaLimit = sessionContext.tokenQuota?.limit || 1;
|
|
512
|
+
const quotaUsed = sessionContext.tokenQuota?.used || 0;
|
|
513
|
+
const quotaPercentNum = (quotaUsed / quotaLimit) * 100;
|
|
514
|
+
if (quotaPercentNum > 80) {
|
|
515
|
+
console.log(`š“ Quota usage: ${quotaPercentNum.toFixed(1)}% (please monitor usage)\n`);
|
|
516
|
+
} else if (quotaPercentNum > 50) {
|
|
517
|
+
console.log(`š” Quota usage: ${quotaPercentNum.toFixed(1)}%\n`);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Initialize current session
|
|
521
|
+
const currentSession = {
|
|
522
|
+
startTime: new Date().toISOString(),
|
|
523
|
+
toolCalls: [],
|
|
524
|
+
patterns: [],
|
|
525
|
+
ccbStatus: ccbStatus,
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
if (writeJSONFile(CURRENT_SESSION_FILE, currentSession)) {
|
|
529
|
+
console.log('ā
Session initialized, ready to work!\n');
|
|
530
|
+
} else {
|
|
531
|
+
console.log('ā ļø Session initialization failed, but you can continue working\n');
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// ============================================================================
|
|
536
|
+
// Execute
|
|
537
|
+
// ============================================================================
|
|
538
|
+
|
|
539
|
+
try {
|
|
540
|
+
sessionStart();
|
|
541
|
+
} catch (error) {
|
|
542
|
+
console.error('ā SessionStart hook error:', error.message);
|
|
543
|
+
process.exit(0); // Never block Claude Code on hook errors
|
|
544
|
+
}
|