Skip to content

Commit 6f104fb

Browse files
committed
v1.3.2: unified search, session pagination, preview fix, Cursor docs
1 parent e841741 commit 6f104fb

7 files changed

Lines changed: 131 additions & 60 deletions

File tree

docs/SETUP.md

Lines changed: 61 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -234,32 +234,69 @@ Combine with `AGENTS.md` instructions (see below).
234234

235235
### Cursor and Windsurf
236236

237-
Cursor and Windsurf don't support lifecycle hooks. Use the client's system prompt / instructions file:
237+
Cursor and Windsurf don't support lifecycle hooks. Use rules files instead:
238238

239-
| Client | Instructions file |
240-
| -------- | ----------------- |
241-
| Cursor | `.cursorrules` |
242-
| Windsurf | `.windsurfrules` |
239+
| Client | Rules location |
240+
| -------- | --------------------- |
241+
| Cursor | `.cursor/rules/*.mdc` |
242+
| Windsurf | `.windsurfrules` |
243243

244-
Add these instructions:
244+
#### Cursor Rules Setup
245245

246+
Create `.cursor/rules/agent-mcp-stack.mdc` with "Always Apply" mode:
247+
248+
```yaml
249+
---
250+
description: Agent MCP stack integration
251+
globs: *
252+
alwaysApply: true
253+
---
254+
255+
# Agent MCP Stack
256+
257+
## Default behavior in every chat
258+
259+
Treat `agent-comm`, `agent-knowledge`, and `agent-tasks` as default infrastructure
260+
for this repository. Do not wait for the user to restate this.
261+
262+
At the start of each new chat/session:
263+
264+
1. Bootstrap `agent-comm` (`comm_register`, join `general`, send one-line intent, then `comm_inbox`).
265+
2. Use `agent-knowledge` first for recall/session context when it can help
266+
(especially prior decisions, errors, tool usage, and related sessions).
267+
3. Check `agent-tasks` before creating work (`task_list`), and keep task
268+
state/artifacts/comments current when work spans more than one step.
269+
270+
If an MCP server is unavailable, report it briefly and continue with local tools;
271+
retry MCP usage after restoring connectivity.
246272
```
247-
You have access to agent-knowledge MCP tools — a shared knowledge base synced via git.
248273

249-
Available tools: knowledge_list, knowledge_read, knowledge_write, knowledge_search,
250-
knowledge_recall, knowledge_sessions, knowledge_config, knowledge_sync,
251-
knowledge_link, knowledge_unlink, knowledge_links, knowledge_graph
274+
Optionally create `.cursor/rules/session-wrapup-knowledge.mdc` for session wrap-up:
275+
276+
```yaml
277+
---
278+
description: Save session learnings to knowledge base
279+
globs: *
280+
alwaysApply: false
281+
---
252282

253-
Categories: projects, people, decisions, workflows, notes
283+
# Session Wrap-up
254284

255-
Use knowledge_search for semantic + keyword search across all entries.
256-
Use knowledge_recall for scoped search (errors, plans, configs, tools, files, decisions).
257-
Use knowledge_link/knowledge_unlink to manage relationships between entries.
258-
Use knowledge_graph to explore connections from any entry via BFS traversal.
285+
Before ending a session, save non-obvious learnings to agent-knowledge:
259286

260-
Dashboard: http://localhost:3423
287+
1. `knowledge_search` for existing entries that overlap with this session's work.
288+
2. `knowledge_write` new entries for decisions, architecture changes, or error patterns
289+
that aren't already captured.
290+
3. `knowledge_link` related entries together.
291+
292+
Categories: projects (project context), decisions (trade-offs made), workflows (processes),
293+
notes (everything else).
261294
```
262295

296+
#### Windsurf
297+
298+
Add equivalent instructions to `.windsurfrules` in your project root.
299+
263300
---
264301

265302
## Knowledge Base Setup
@@ -457,11 +494,11 @@ If your session data is in a non-standard location, use `EXTRA_SESSION_ROOTS` to
457494

458495
## Client Comparison
459496

460-
| Feature | Claude Code | Cursor | OpenCode | Cline | Continue.dev | Aider | Windsurf |
461-
| -------------------- | ----------- | ------------ | ------------- | ---------- | ------------ | -------------- | -------------- |
462-
| MCP stdio transport | Yes | Yes | Yes | Yes | Yes | -- | Yes |
463-
| Session reading | Yes (JSONL) | Yes (JSONL) | Yes (SQLite) | Yes (JSON) | Yes (JSON) | Yes (MD/JSONL) | -- |
464-
| Lifecycle hooks | Yes (JSON) | No | Yes (plugins) | No | No | -- | No |
465-
| Session auto-distill | Yes | Yes | Yes | Yes | Yes | Yes | -- |
466-
| System prompt file | CLAUDE.md | .cursorrules | AGENTS.md | -- | -- | -- | .windsurfrules |
467-
| REST API fallback | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
497+
| Feature | Claude Code | Cursor | OpenCode | Cline | Continue.dev | Aider | Windsurf |
498+
| -------------------- | ----------- | -------------------- | ------------- | ---------- | ------------ | -------------- | -------------- |
499+
| MCP stdio transport | Yes | Yes | Yes | Yes | Yes | -- | Yes |
500+
| Session reading | Yes (JSONL) | Yes (JSONL) | Yes (SQLite) | Yes (JSON) | Yes (JSON) | Yes (MD/JSONL) | -- |
501+
| Lifecycle hooks | Yes (JSON) | No | Yes (plugins) | No | No | -- | No |
502+
| Session auto-distill | Yes | Yes | Yes | Yes | Yes | Yes | -- |
503+
| System prompt file | CLAUDE.md | .cursor/rules/\*.mdc | AGENTS.md | -- | -- | -- | .windsurfrules |
504+
| REST API fallback | Yes | Yes | Yes | Yes | Yes | Yes | Yes |

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "agent-knowledge",
3-
"version": "1.3.1",
3+
"version": "1.3.2",
44
"description": "Cross-session memory and recall for AI agents — git-synced knowledge base, knowledge graph, confidence scoring, hybrid semantic+TF-IDF search, auto-distillation with secrets scrubbing",
55
"type": "module",
66
"main": "dist/index.js",

src/server.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,9 @@ export function createServer(options?: ServerOptions): Server {
6868
{
6969
name: 'knowledge_search',
7070
description:
71-
'Hybrid semantic + TF-IDF search across session conversations. ' +
72-
'Results ranked by blended relevance (semantic similarity + keyword match) x recency. ' +
73-
'When "scope" is provided, searches within a specific domain (errors, plans, configs, tools, files, decisions). ' +
74-
'Without scope, performs general search across all sessions.',
71+
'Search across sessions AND knowledge entries. ' +
72+
'Returns both session matches (semantic + TF-IDF) and knowledge base matches. ' +
73+
'When "scope" is provided, searches within a specific domain (errors, plans, configs, tools, files, decisions) in sessions only.',
7574
inputSchema: {
7675
type: 'object' as const,
7776
properties: {
@@ -144,6 +143,14 @@ export function createServer(options?: ServerOptions): Server {
144143
type: 'number',
145144
description: 'Only return the last N messages (action=get)',
146145
},
146+
limit: {
147+
type: 'number',
148+
description: 'Max sessions to return (action=list, default: 20, max: 500)',
149+
},
150+
offset: {
151+
type: 'number',
152+
description: 'Skip first N sessions (action=list, default: 0)',
153+
},
147154
},
148155
required: ['action'],
149156
},

src/sessions/parser.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import fs from 'fs';
2+
import os from 'os';
23
import path from 'path';
34
import { getConfig } from '../types.js';
45
import { getAvailableAdapters } from './adapters/index.js';
@@ -191,21 +192,21 @@ export function getSessionMeta(entries: SessionEntry[]): SessionMeta {
191192
* Falls back to the raw name if it doesn't match the expected pattern.
192193
*/
193194
function decodeProjectDirName(dirName: string): string {
194-
const parts = dirName.split('--');
195-
if (parts.length < 2) return dirName;
196-
197-
if (!/^[A-Z]$/i.test(parts[0])) return dirName;
195+
const homeParts = os.homedir().replace(/\\/g, '/').split('/');
196+
const homeEncoded = homeParts[0].replace(':', '') + '--' + homeParts.slice(1).join('-');
198197

199-
const homeIdx = parts.findIndex((p) => /^(users|home)-/i.test(p));
198+
if (dirName === homeEncoded) return '~';
200199

201-
if (homeIdx >= 0 && homeIdx < parts.length - 1) {
202-
return parts.slice(homeIdx + 1).join('/');
200+
if (dirName.startsWith(homeEncoded + '--')) {
201+
return dirName.slice(homeEncoded.length + 2).replace(/--/g, '/');
203202
}
204203

205-
if (homeIdx >= 0 && homeIdx === parts.length - 1) {
206-
return '~';
204+
if (dirName.startsWith(homeEncoded + '-')) {
205+
return dirName.slice(homeEncoded.length + 1).replace(/--/g, '/');
207206
}
208207

208+
const parts = dirName.split('--');
209+
if (parts.length < 2) return dirName;
209210
return parts[parts.length - 1];
210211
}
211212

src/sessions/summary.ts

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -207,24 +207,31 @@ function fastMeta(filePath: string): SessionMeta | null {
207207
}
208208
fs.closeSync(fd);
209209

210+
const extractText = (content: unknown): string => {
211+
if (typeof content === 'string') return content;
212+
if (Array.isArray(content)) {
213+
const parts = content
214+
.filter(
215+
(p) =>
216+
typeof p === 'string' || (typeof p === 'object' && p !== null && p.type === 'text'),
217+
)
218+
.map((p) => (typeof p === 'string' ? p : p.text));
219+
return parts.length > 0 ? parts.join('\n') : '';
220+
}
221+
return '';
222+
};
223+
210224
let preview = '';
225+
let userMessageCount = 0;
211226
for (const line of headLines) {
212227
try {
213228
const entry = JSON.parse(line);
214-
if (entry.type === 'user' && entry.message?.role === 'user') {
215-
const content = entry.message.content;
216-
if (typeof content === 'string') {
217-
preview = content.substring(0, 200);
218-
break;
219-
}
220-
if (Array.isArray(content)) {
221-
const textBlock = content.find(
222-
(b: { type: string; text?: string }) => b.type === 'text' && b.text,
223-
);
224-
if (textBlock) {
225-
preview = textBlock.text.substring(0, 200);
226-
break;
227-
}
229+
const role = entry.type ?? entry.role;
230+
if (role === 'user') {
231+
userMessageCount++;
232+
if (!preview && entry.message?.content !== null && entry.message?.content !== undefined) {
233+
const text = extractText(entry.message.content);
234+
if (text) preview = text.substring(0, 200);
228235
}
229236
}
230237
} catch {
@@ -238,7 +245,7 @@ function fastMeta(filePath: string): SessionMeta | null {
238245
cwd: (first.cwd as string) || '',
239246
branch: (first.gitBranch as string) || '',
240247
messageCount: Math.max(1, Math.round(stat.size / 500)),
241-
userMessageCount: 0,
248+
userMessageCount,
242249
preview,
243250
};
244251
} catch {

src/tool-handlers.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { consolidate } from './knowledge/consolidate.js';
1414
import { reflect } from './knowledge/reflect.js';
1515
import { indexKnowledgeEntry } from './sessions/indexer.js';
1616
import { searchSessions } from './sessions/search.js';
17+
import { searchKnowledge } from './knowledge/search.js';
1718
import { listSessions, getSessionSummary } from './sessions/summary.js';
1819
import { scopedSearch, type SearchScope } from './sessions/scopes.js';
1920
import { VectorStore } from './vectorstore/index.js';
@@ -253,8 +254,12 @@ export function handleKnowledgeSession(
253254
switch (action) {
254255
case 'list': {
255256
const project = optionalString(a, 'project');
256-
const sessions = listSessions(project);
257-
return ok(sessions);
257+
const limit = optionalNumber(a, 'limit', 1, 500) ?? 20;
258+
const offset = optionalNumber(a, 'offset', 0, 100000) ?? 0;
259+
let sessions = listSessions(project);
260+
const total = sessions.length;
261+
sessions = sessions.slice(offset, offset + limit);
262+
return ok({ total, offset, limit, sessions });
258263
}
259264
case 'get': {
260265
const sessionId = requireString(a, 'session_id');
@@ -323,18 +328,32 @@ export async function handleKnowledgeSearch(args: Record<string, unknown>): Prom
323328
return ok(results);
324329
}
325330

326-
// General search
331+
// General search — sessions + knowledge entries
327332
const role =
328333
validateEnum(optionalString(a, 'role'), ['user', 'assistant', 'all'] as const, 'role') ?? 'all';
329334
const ranked = optionalBoolean(a, 'ranked') ?? true;
330-
const results = await searchSessions(query, {
335+
const sessionResults = await searchSessions(query, {
331336
project,
332337
role,
333338
maxResults,
334339
ranked,
335340
semantic,
336341
});
337-
return ok(results);
342+
343+
const config = getConfig();
344+
const knowledgeResults = searchKnowledge(config.memoryDir, query, { maxResults: 10 }).map(
345+
(r) => ({
346+
source: 'knowledge' as const,
347+
id: r.entry.path,
348+
path: r.entry.path,
349+
category: r.entry.category,
350+
title: r.entry.title || r.entry.path,
351+
excerpt: r.excerpt,
352+
score: r.score,
353+
}),
354+
);
355+
356+
return ok({ sessions: sessionResults, knowledge: knowledgeResults });
338357
}
339358

340359
export function handleKnowledgeAdmin(args: Record<string, unknown>): ToolResult {

0 commit comments

Comments
 (0)