@everstateai/mcp 1.3.12 → 1.3.14
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 +26 -3
- package/dist/index.js +470 -15
- package/dist/index.js.map +1 -1
- package/dist/setup/auto-update.d.ts.map +1 -1
- package/dist/setup/auto-update.js +223 -0
- package/dist/setup/auto-update.js.map +1 -1
- package/dist/setup/hooks/templates.d.ts +18 -1
- package/dist/setup/hooks/templates.d.ts.map +1 -1
- package/dist/setup/hooks/templates.js +381 -41
- package/dist/setup/hooks/templates.js.map +1 -1
- package/dist/setup/types.d.ts +2 -0
- package/dist/setup/types.d.ts.map +1 -1
- package/dist/setup/types.js +3 -1
- package/dist/setup/types.js.map +1 -1
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +98 -8
- package/dist/setup.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +514 -17
- package/src/setup/auto-update.ts +263 -0
- package/src/setup/hooks/templates.ts +398 -41
- package/src/setup/types.ts +3 -1
- package/src/setup.ts +113 -8
|
@@ -21,9 +21,13 @@ export function getSyncTodosHook(config?: Partial<HookConfig>): string {
|
|
|
21
21
|
* VERSION: ${HOOK_VERSIONS.syncTodos}
|
|
22
22
|
* GENERATED: ${generatedAt}
|
|
23
23
|
*
|
|
24
|
-
*
|
|
24
|
+
* Two-way sync between Claude's TodoWrite and Everstate dashboard.
|
|
25
25
|
* Runs after every TodoWrite tool call.
|
|
26
26
|
*
|
|
27
|
+
* Push: Sends Claude's todos to Everstate for matching and storage.
|
|
28
|
+
* Pull: Reads dashboard changes from API response and outputs instructions
|
|
29
|
+
* to stdout so Claude can update its TodoWrite list.
|
|
30
|
+
*
|
|
27
31
|
* Claude Code passes hook data via STDIN as JSON:
|
|
28
32
|
* { "tool_name": "TodoWrite", "tool_input": { "todos": [...] }, ... }
|
|
29
33
|
*/
|
|
@@ -88,6 +92,19 @@ async function main() {
|
|
|
88
92
|
process.exit(0);
|
|
89
93
|
}
|
|
90
94
|
|
|
95
|
+
// Read last sync timestamp for two-way sync
|
|
96
|
+
const syncStatePath = path.join(
|
|
97
|
+
process.env.CLAUDE_PROJECT_DIR || process.cwd(),
|
|
98
|
+
'.claude', '.sync-state.json'
|
|
99
|
+
);
|
|
100
|
+
let lastSyncedAt = null;
|
|
101
|
+
try {
|
|
102
|
+
if (fs.existsSync(syncStatePath)) {
|
|
103
|
+
const state = JSON.parse(fs.readFileSync(syncStatePath, 'utf8'));
|
|
104
|
+
lastSyncedAt = state.lastSyncedAt || null;
|
|
105
|
+
}
|
|
106
|
+
} catch {}
|
|
107
|
+
|
|
91
108
|
// Send to API for matching and storage
|
|
92
109
|
const response = await fetch(\`\${API_URL}/api/session/sync-todos\`, {
|
|
93
110
|
method: 'POST',
|
|
@@ -99,12 +116,47 @@ async function main() {
|
|
|
99
116
|
projectId,
|
|
100
117
|
todos,
|
|
101
118
|
sessionId: process.env.SESSION_ID || null,
|
|
119
|
+
lastSyncedAt,
|
|
102
120
|
}),
|
|
103
121
|
});
|
|
104
122
|
|
|
105
123
|
if (!response.ok) {
|
|
106
124
|
const text = await response.text();
|
|
107
125
|
console.error('[everstate] Sync failed:', response.status, text);
|
|
126
|
+
process.exit(0);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Parse response for two-way sync
|
|
130
|
+
const data = await response.json();
|
|
131
|
+
|
|
132
|
+
// Save sync timestamp for next run
|
|
133
|
+
try {
|
|
134
|
+
fs.mkdirSync(path.dirname(syncStatePath), { recursive: true });
|
|
135
|
+
fs.writeFileSync(syncStatePath, JSON.stringify({
|
|
136
|
+
lastSyncedAt: data.syncedAt || new Date().toISOString(),
|
|
137
|
+
}));
|
|
138
|
+
} catch {}
|
|
139
|
+
|
|
140
|
+
// Output dashboard changes to stdout (Claude sees this)
|
|
141
|
+
const changes = data.dashboardChanges || [];
|
|
142
|
+
if (changes.length > 0) {
|
|
143
|
+
const lines = [];
|
|
144
|
+
for (const c of changes) {
|
|
145
|
+
if (c.type === 'status_changed') {
|
|
146
|
+
lines.push('- "' + c.taskTitle + '" was changed to ' + c.newValue + ' on the dashboard');
|
|
147
|
+
} else if (c.type === 'deleted') {
|
|
148
|
+
lines.push('- "' + c.taskTitle + '" was deleted on the dashboard');
|
|
149
|
+
} else if (c.type === 'title_changed') {
|
|
150
|
+
lines.push('- "' + c.oldValue + '" was renamed to "' + c.newValue + '" on the dashboard');
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (lines.length > 0) {
|
|
154
|
+
console.log('');
|
|
155
|
+
console.log('[Everstate] Dashboard changes detected since last sync:');
|
|
156
|
+
console.log(lines.join('\\n'));
|
|
157
|
+
console.log('');
|
|
158
|
+
console.log('Please update your TodoWrite list to reflect these changes.');
|
|
159
|
+
}
|
|
108
160
|
}
|
|
109
161
|
} catch (error) {
|
|
110
162
|
console.error('[everstate] Hook error:', error.message);
|
|
@@ -153,7 +205,8 @@ export function getSessionEndHook(config?: Partial<HookConfig>): string {
|
|
|
153
205
|
# VERSION: ${HOOK_VERSIONS.sessionEnd}
|
|
154
206
|
# GENERATED: ${generatedAt}
|
|
155
207
|
#
|
|
156
|
-
#
|
|
208
|
+
# Auto-saves session when Claude session ends.
|
|
209
|
+
# Calls auto-done which generates summary from tracked activity.
|
|
157
210
|
|
|
158
211
|
API_KEY_PATH="${apiKeyPath}"
|
|
159
212
|
API_URL="${apiUrl}"
|
|
@@ -182,8 +235,8 @@ if [ -z "$PROJECT_ID" ]; then
|
|
|
182
235
|
exit 0
|
|
183
236
|
fi
|
|
184
237
|
|
|
185
|
-
#
|
|
186
|
-
curl -s -X POST "$API_URL/api/session/
|
|
238
|
+
# Call auto-done (generates summary from activity, skips if done() was already called)
|
|
239
|
+
curl -s -X POST "$API_URL/api/session/auto-done" \\
|
|
187
240
|
-H "Content-Type: application/json" \\
|
|
188
241
|
-H "Authorization: Bearer $API_KEY" \\
|
|
189
242
|
-d "{\\"projectId\\": \\"$PROJECT_ID\\"}" > /dev/null 2>&1
|
|
@@ -193,57 +246,358 @@ exit 0
|
|
|
193
246
|
}
|
|
194
247
|
|
|
195
248
|
/**
|
|
196
|
-
* Get the pre-compact.
|
|
249
|
+
* Get the pre-compact.js hook content
|
|
250
|
+
* Prints critical Everstate memory to stdout so it survives compaction.
|
|
197
251
|
*/
|
|
198
252
|
export function getPreCompactHook(config?: Partial<HookConfig>): string {
|
|
199
253
|
const apiKeyPath = config?.apiKeyPath || `${getEverstateDir()}/api-key`;
|
|
200
254
|
const apiUrl = config?.apiUrl || EVERSTATE_API_URL;
|
|
201
255
|
const generatedAt = config?.generatedAt || new Date().toISOString();
|
|
202
256
|
|
|
203
|
-
return
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
257
|
+
return getContextInjectionHook({
|
|
258
|
+
hookName: 'Pre-Compact',
|
|
259
|
+
version: HOOK_VERSIONS.preCompact,
|
|
260
|
+
generatedAt,
|
|
261
|
+
apiKeyPath,
|
|
262
|
+
apiUrl,
|
|
263
|
+
preamble: 'Critical context preserved through compaction',
|
|
264
|
+
timeout: 8000,
|
|
265
|
+
includeBlockers: true,
|
|
266
|
+
includeTasks: true,
|
|
267
|
+
maxGotchas: 5,
|
|
268
|
+
maxDecisions: 5,
|
|
269
|
+
postAction: `
|
|
270
|
+
fetch(\\\`\\\${API_URL}/api/session/compact-warning\\\`, {
|
|
271
|
+
method: 'POST',
|
|
272
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': \\\`Bearer \\\${apiKey}\\\` },
|
|
273
|
+
body: JSON.stringify({ projectId, message: 'Context compacted — critical context re-injected' }),
|
|
274
|
+
}).catch(() => {});`,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
209
277
|
|
|
210
|
-
|
|
211
|
-
|
|
278
|
+
/**
|
|
279
|
+
* Get the subagent-context.js hook content
|
|
280
|
+
* Injects critical context when subagents spawn.
|
|
281
|
+
*/
|
|
282
|
+
export function getSubagentContextHook(config?: Partial<HookConfig>): string {
|
|
283
|
+
const apiKeyPath = config?.apiKeyPath || `${getEverstateDir()}/api-key`;
|
|
284
|
+
const apiUrl = config?.apiUrl || EVERSTATE_API_URL;
|
|
285
|
+
const generatedAt = config?.generatedAt || new Date().toISOString();
|
|
212
286
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
287
|
+
return getContextInjectionHook({
|
|
288
|
+
hookName: 'SubagentStart',
|
|
289
|
+
version: HOOK_VERSIONS.subagentContext,
|
|
290
|
+
generatedAt,
|
|
291
|
+
apiKeyPath,
|
|
292
|
+
apiUrl,
|
|
293
|
+
preamble: 'Project context for this subagent',
|
|
294
|
+
timeout: 5000,
|
|
295
|
+
includeBlockers: false,
|
|
296
|
+
includeTasks: false,
|
|
297
|
+
maxGotchas: 5,
|
|
298
|
+
maxDecisions: 3,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
218
301
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
302
|
+
/**
|
|
303
|
+
* Shared template for hooks that inject Everstate context via stdout.
|
|
304
|
+
* Used by both PreCompact and SubagentStart hooks.
|
|
305
|
+
*/
|
|
306
|
+
function getContextInjectionHook(opts: {
|
|
307
|
+
hookName: string;
|
|
308
|
+
version: string;
|
|
309
|
+
generatedAt: string;
|
|
310
|
+
apiKeyPath: string;
|
|
311
|
+
apiUrl: string;
|
|
312
|
+
preamble: string;
|
|
313
|
+
timeout: number;
|
|
314
|
+
includeBlockers: boolean;
|
|
315
|
+
includeTasks: boolean;
|
|
316
|
+
maxGotchas: number;
|
|
317
|
+
maxDecisions: number;
|
|
318
|
+
postAction?: string;
|
|
319
|
+
}): string {
|
|
320
|
+
return `#!/usr/bin/env node
|
|
321
|
+
/**
|
|
322
|
+
* Everstate ${opts.hookName} Hook v${opts.version}
|
|
323
|
+
* VERSION: ${opts.version}
|
|
324
|
+
* GENERATED: ${opts.generatedAt}
|
|
325
|
+
*
|
|
326
|
+
* Prints critical Everstate memory to stdout.
|
|
327
|
+
* Claude adds hook stdout to the agent's context.
|
|
328
|
+
*/
|
|
329
|
+
|
|
330
|
+
const fs = require('fs');
|
|
331
|
+
const path = require('path');
|
|
332
|
+
|
|
333
|
+
const API_KEY_PATH = '${opts.apiKeyPath}';
|
|
334
|
+
const API_URL = '${opts.apiUrl}';
|
|
335
|
+
|
|
336
|
+
async function main() {
|
|
337
|
+
if (!fs.existsSync(API_KEY_PATH)) process.exit(0);
|
|
338
|
+
const apiKey = fs.readFileSync(API_KEY_PATH, 'utf8').trim();
|
|
339
|
+
|
|
340
|
+
let projectId = null;
|
|
341
|
+
let searchDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
342
|
+
for (let i = 0; i < 10; i++) {
|
|
343
|
+
for (const cn of ['.everstate.json', '.codekeep.json']) {
|
|
344
|
+
const cp = path.join(searchDir, cn);
|
|
345
|
+
if (fs.existsSync(cp)) {
|
|
346
|
+
try { projectId = JSON.parse(fs.readFileSync(cp, 'utf8')).projectId; } catch {}
|
|
347
|
+
}
|
|
348
|
+
if (projectId) break;
|
|
349
|
+
}
|
|
350
|
+
if (projectId) break;
|
|
351
|
+
const parent = path.dirname(searchDir);
|
|
352
|
+
if (parent === searchDir) break;
|
|
353
|
+
searchDir = parent;
|
|
354
|
+
}
|
|
355
|
+
if (!projectId) process.exit(0);
|
|
356
|
+
|
|
357
|
+
try {
|
|
358
|
+
const response = await fetch(\\\`\\\${API_URL}/api/context/handoff?projectId=\\\${projectId}\\\`, {
|
|
359
|
+
headers: { 'Authorization': \\\`Bearer \\\${apiKey}\\\` },
|
|
360
|
+
signal: AbortSignal.timeout(${opts.timeout}),
|
|
361
|
+
});
|
|
362
|
+
if (!response.ok) process.exit(0);
|
|
363
|
+
const ctx = await response.json();
|
|
364
|
+
|
|
365
|
+
const lines = ['[Everstate] ${opts.preamble}:', ''];
|
|
366
|
+
|
|
367
|
+
if (ctx.activeDirective) {
|
|
368
|
+
lines.push(\\\`## Current Directive: \\\${ctx.activeDirective.title}\\\`);
|
|
369
|
+
if (ctx.activeDirective.currentFocus) lines.push(\\\`> \\\${ctx.activeDirective.currentFocus}\\\`);
|
|
370
|
+
if (ctx.activeDirective.branch) lines.push(\\\`Branch: \\\${ctx.activeDirective.branch}\\\`);
|
|
371
|
+
lines.push('');
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (ctx.gotchas && ctx.gotchas.length > 0) {
|
|
375
|
+
lines.push('## Gotchas');
|
|
376
|
+
for (const g of ctx.gotchas.slice(0, ${opts.maxGotchas})) {
|
|
377
|
+
lines.push(\\\`- \\\${g.title}: \\\${g.description.substring(0, 150)}\\\`);
|
|
378
|
+
}
|
|
379
|
+
lines.push('');
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (ctx.recentDecisions && ctx.recentDecisions.length > 0) {
|
|
383
|
+
lines.push('## Decisions');
|
|
384
|
+
for (const d of ctx.recentDecisions.slice(0, ${opts.maxDecisions})) {
|
|
385
|
+
lines.push(\\\`- \\\${d.summary}\\\${d.rationale ? ' (' + d.rationale.substring(0, 100) + ')' : ''}\\\`);
|
|
386
|
+
}
|
|
387
|
+
lines.push('');
|
|
388
|
+
}
|
|
389
|
+
${opts.includeTasks ? `
|
|
390
|
+
if (ctx.tasks) {
|
|
391
|
+
const inProgress = ctx.tasks.inProgress || [];
|
|
392
|
+
const pending = ctx.tasks.pending || [];
|
|
393
|
+
if (inProgress.length > 0 || pending.length > 0) {
|
|
394
|
+
lines.push('## Tasks');
|
|
395
|
+
for (const t of inProgress) lines.push(\\\`- [in_progress] \\\${t.title}\\\`);
|
|
396
|
+
for (const t of pending.slice(0, 5)) lines.push(\\\`- [pending] \\\${t.title}\\\`);
|
|
397
|
+
lines.push('');
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
` : ''}${opts.includeBlockers ? `
|
|
401
|
+
if (ctx.blockers && ctx.blockers.length > 0) {
|
|
402
|
+
lines.push('## Blockers');
|
|
403
|
+
for (const b of ctx.blockers) {
|
|
404
|
+
lines.push(\\\`- \\\${b.title} (\\\${b.severity}): \\\${b.description.substring(0, 100)}\\\`);
|
|
405
|
+
}
|
|
406
|
+
lines.push('');
|
|
407
|
+
}
|
|
408
|
+
` : ''}
|
|
409
|
+
if (ctx.doNotModify && ctx.doNotModify.length > 0) {
|
|
410
|
+
lines.push('Do not modify:');
|
|
411
|
+
for (const f of ctx.doNotModify) lines.push(\\\`- \\\${f.filePath}: \\\${f.reason}\\\`);
|
|
412
|
+
lines.push('');
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (lines.length > 2) console.log(lines.join('\\n'));
|
|
416
|
+
${opts.postAction || ''}
|
|
417
|
+
} catch {
|
|
418
|
+
try {
|
|
419
|
+
const cachePath = path.join(process.env.CLAUDE_PROJECT_DIR || process.cwd(), '.claude', '.gotcha-cache.json');
|
|
420
|
+
if (fs.existsSync(cachePath)) {
|
|
421
|
+
const gotchas = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
|
|
422
|
+
if (Array.isArray(gotchas) && gotchas.length > 0) {
|
|
423
|
+
console.log('[Everstate] Known gotchas:');
|
|
424
|
+
for (const g of gotchas.slice(0, ${opts.maxGotchas})) console.log(\\\`- \\\${g.title}: \\\${g.description.substring(0, 150)}\\\`);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
} catch {}
|
|
428
|
+
}
|
|
230
429
|
}
|
|
231
430
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
431
|
+
main().catch(() => process.exit(0));
|
|
432
|
+
`;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Get the implicit-progress.js hook content (PostToolUse for Edit/Write/Bash)
|
|
437
|
+
*/
|
|
438
|
+
export function getImplicitProgressHook(config?: Partial<HookConfig>): string {
|
|
439
|
+
const apiKeyPath = config?.apiKeyPath || `${getEverstateDir()}/api-key`;
|
|
440
|
+
const apiUrl = config?.apiUrl || EVERSTATE_API_URL;
|
|
441
|
+
const generatedAt = config?.generatedAt || new Date().toISOString();
|
|
442
|
+
|
|
443
|
+
return `#!/usr/bin/env node
|
|
444
|
+
/**
|
|
445
|
+
* Everstate Implicit Progress Hook v${HOOK_VERSIONS.syncTodos}
|
|
446
|
+
* VERSION: ${HOOK_VERSIONS.syncTodos}
|
|
447
|
+
* GENERATED: ${generatedAt}
|
|
448
|
+
*
|
|
449
|
+
* Automatically logs progress when files are edited or commands run.
|
|
450
|
+
* Runs as PostToolUse hook for Edit, Write, and Bash tools.
|
|
451
|
+
*/
|
|
236
452
|
|
|
237
|
-
|
|
238
|
-
|
|
453
|
+
const fs = require('fs');
|
|
454
|
+
const path = require('path');
|
|
239
455
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
-H "Content-Type: application/json" \\
|
|
243
|
-
-H "Authorization: Bearer $API_KEY" \\
|
|
244
|
-
-d "{\\"projectId\\": \\"$PROJECT_ID\\", \\"transcript\\": \\"\$TRANSCRIPT\\"}" > /dev/null 2>&1
|
|
456
|
+
const API_KEY_PATH = '${apiKeyPath}';
|
|
457
|
+
const API_URL = '${apiUrl}';
|
|
245
458
|
|
|
246
|
-
|
|
459
|
+
let stdinData = '';
|
|
460
|
+
process.stdin.setEncoding('utf8');
|
|
461
|
+
process.stdin.on('data', chunk => stdinData += chunk);
|
|
462
|
+
process.stdin.on('end', () => main());
|
|
463
|
+
|
|
464
|
+
async function main() {
|
|
465
|
+
try {
|
|
466
|
+
if (!fs.existsSync(API_KEY_PATH)) process.exit(0);
|
|
467
|
+
const apiKey = fs.readFileSync(API_KEY_PATH, 'utf8').trim();
|
|
468
|
+
|
|
469
|
+
const hookData = JSON.parse(stdinData || '{}');
|
|
470
|
+
const toolName = hookData.tool_name;
|
|
471
|
+
const toolInput = hookData.tool_input || {};
|
|
472
|
+
|
|
473
|
+
let activity = null;
|
|
474
|
+
if (toolName === 'Edit' || toolName === 'Write') {
|
|
475
|
+
const filePath = toolInput.file_path || toolInput.filePath || '';
|
|
476
|
+
if (filePath) activity = { tool: toolName.toLowerCase(), file: path.basename(filePath), path: filePath };
|
|
477
|
+
} else if (toolName === 'Bash') {
|
|
478
|
+
const cmd = toolInput.command || '';
|
|
479
|
+
if (cmd && !cmd.startsWith('git status') && !cmd.startsWith('ls '))
|
|
480
|
+
activity = { tool: 'bash', command: cmd.substring(0, 100) };
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (!activity) process.exit(0);
|
|
484
|
+
|
|
485
|
+
// Find project
|
|
486
|
+
let projectId = null;
|
|
487
|
+
let searchDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
488
|
+
for (let i = 0; i < 10; i++) {
|
|
489
|
+
for (const cn of ['.everstate.json', '.codekeep.json']) {
|
|
490
|
+
const cp = path.join(searchDir, cn);
|
|
491
|
+
if (fs.existsSync(cp)) {
|
|
492
|
+
try { projectId = JSON.parse(fs.readFileSync(cp, 'utf8')).projectId; } catch {}
|
|
493
|
+
}
|
|
494
|
+
if (projectId) break;
|
|
495
|
+
}
|
|
496
|
+
if (projectId) break;
|
|
497
|
+
const parent = path.dirname(searchDir);
|
|
498
|
+
if (parent === searchDir) break;
|
|
499
|
+
searchDir = parent;
|
|
500
|
+
}
|
|
501
|
+
if (!projectId) process.exit(0);
|
|
502
|
+
|
|
503
|
+
// Debounce: 1 per file per 60s
|
|
504
|
+
const df = path.join(process.env.CLAUDE_PROJECT_DIR || process.cwd(), '.claude', '.progress-debounce.json');
|
|
505
|
+
let db = {};
|
|
506
|
+
try { if (fs.existsSync(df)) db = JSON.parse(fs.readFileSync(df, 'utf8')); } catch {}
|
|
507
|
+
const key = activity.file || activity.command || 'x';
|
|
508
|
+
const now = Date.now();
|
|
509
|
+
if (db[key] && (now - db[key]) < 60000) process.exit(0);
|
|
510
|
+
db[key] = now;
|
|
511
|
+
for (const [k, v] of Object.entries(db)) { if ((now - v) > 300000) delete db[k]; }
|
|
512
|
+
try { fs.mkdirSync(path.dirname(df), { recursive: true }); fs.writeFileSync(df, JSON.stringify(db)); } catch {}
|
|
513
|
+
|
|
514
|
+
await fetch(\`\${API_URL}/api/session/implicit-progress\`, {
|
|
515
|
+
method: 'POST',
|
|
516
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': \`Bearer \${apiKey}\` },
|
|
517
|
+
body: JSON.stringify({ projectId, activity }),
|
|
518
|
+
}).catch(() => {});
|
|
519
|
+
} catch {}
|
|
520
|
+
process.exit(0);
|
|
521
|
+
}
|
|
522
|
+
`;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Get the gotcha-check.js hook content (PreToolUse for Edit/Write)
|
|
527
|
+
*/
|
|
528
|
+
export function getGotchaCheckHook(config?: Partial<HookConfig>): string {
|
|
529
|
+
const generatedAt = config?.generatedAt || new Date().toISOString();
|
|
530
|
+
|
|
531
|
+
return `#!/usr/bin/env node
|
|
532
|
+
/**
|
|
533
|
+
* Everstate Gotcha Check Hook v${HOOK_VERSIONS.syncTodos}
|
|
534
|
+
* VERSION: ${HOOK_VERSIONS.syncTodos}
|
|
535
|
+
* GENERATED: ${generatedAt}
|
|
536
|
+
*
|
|
537
|
+
* Surfaces relevant gotchas before file edits.
|
|
538
|
+
* Runs as PreToolUse hook for Edit and Write tools.
|
|
539
|
+
*
|
|
540
|
+
* Claude Code passes hook data via STDIN as JSON:
|
|
541
|
+
* { "tool_name": "Edit", "tool_input": { "file_path": "..." }, ... }
|
|
542
|
+
*/
|
|
543
|
+
|
|
544
|
+
const fs = require('fs');
|
|
545
|
+
const path = require('path');
|
|
546
|
+
|
|
547
|
+
let stdinData = '';
|
|
548
|
+
process.stdin.setEncoding('utf8');
|
|
549
|
+
process.stdin.on('data', chunk => stdinData += chunk);
|
|
550
|
+
process.stdin.on('end', () => main());
|
|
551
|
+
|
|
552
|
+
function main() {
|
|
553
|
+
try {
|
|
554
|
+
const hookData = JSON.parse(stdinData || '{}');
|
|
555
|
+
const filePath = hookData.tool_input?.file_path || hookData.tool_input?.filePath || '';
|
|
556
|
+
if (!filePath) {
|
|
557
|
+
process.exit(0);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Read gotcha cache
|
|
561
|
+
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
562
|
+
const cachePath = path.join(projectDir, '.claude', '.gotcha-cache.json');
|
|
563
|
+
if (!fs.existsSync(cachePath)) {
|
|
564
|
+
process.exit(0);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const gotchas = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
|
|
568
|
+
if (!Array.isArray(gotchas) || gotchas.length === 0) {
|
|
569
|
+
process.exit(0);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Match gotchas against file path
|
|
573
|
+
const fileLC = filePath.toLowerCase();
|
|
574
|
+
const fileName = path.basename(filePath).toLowerCase();
|
|
575
|
+
const matched = [];
|
|
576
|
+
|
|
577
|
+
for (const gotcha of gotchas) {
|
|
578
|
+
if (!gotcha.keywords || !Array.isArray(gotcha.keywords)) continue;
|
|
579
|
+
for (const keyword of gotcha.keywords) {
|
|
580
|
+
if (fileLC.includes(keyword) || fileName.includes(keyword)) {
|
|
581
|
+
matched.push(gotcha);
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (matched.length > 0) {
|
|
588
|
+
const sevIcon = { critical: '!!!', warning: '>>', info: '>' };
|
|
589
|
+
console.log('');
|
|
590
|
+
console.log('[Everstate] Gotcha warning for ' + path.basename(filePath) + ':');
|
|
591
|
+
for (const g of matched.slice(0, 3)) {
|
|
592
|
+
console.log((sevIcon[g.severity] || '>>') + ' ' + g.title + ': ' + g.description.substring(0, 150));
|
|
593
|
+
}
|
|
594
|
+
console.log('');
|
|
595
|
+
}
|
|
596
|
+
} catch {
|
|
597
|
+
// Silent failure - don't block tool execution
|
|
598
|
+
}
|
|
599
|
+
process.exit(0);
|
|
600
|
+
}
|
|
247
601
|
`;
|
|
248
602
|
}
|
|
249
603
|
|
|
@@ -252,4 +606,7 @@ export const hookTemplates = {
|
|
|
252
606
|
sessionStart: getSessionStartHook,
|
|
253
607
|
sessionEnd: getSessionEndHook,
|
|
254
608
|
preCompact: getPreCompactHook,
|
|
609
|
+
subagentContext: getSubagentContextHook,
|
|
610
|
+
gotchaCheck: getGotchaCheckHook,
|
|
611
|
+
implicitProgress: getImplicitProgressHook,
|
|
255
612
|
};
|
package/src/setup/types.ts
CHANGED
|
@@ -177,7 +177,9 @@ export function c(color: keyof Colors, text: string): string {
|
|
|
177
177
|
export const HOOK_VERSIONS = {
|
|
178
178
|
sessionStart: '1.1.0',
|
|
179
179
|
sessionEnd: '1.1.0',
|
|
180
|
-
syncTodos: '
|
|
180
|
+
syncTodos: '6.0.0', // Two-way sync: dashboard changes reported back to Claude via stdout
|
|
181
|
+
preCompact: '2.0.0', // Re-injects critical context (gotchas, decisions, tasks) before compaction
|
|
182
|
+
subagentContext: '1.0.0', // Injects project context when subagents spawn
|
|
181
183
|
};
|
|
182
184
|
|
|
183
185
|
export const EVERSTATE_API_URL = 'https://www.everstate.ai';
|