osborn 0.5.3 → 0.5.5
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/.claude/settings.local.json +9 -0
- package/.claude/skills/markdown-to-pdf/SKILL.md +29 -0
- package/.claude/skills/pdf-to-markdown/SKILL.md +28 -0
- package/.claude/skills/playwright-browser/SKILL.md +75 -0
- package/.claude/skills/youtube-transcript/SKILL.md +24 -0
- package/dist/claude-llm.d.ts +29 -1
- package/dist/claude-llm.js +334 -78
- package/dist/config.d.ts +5 -1
- package/dist/config.js +4 -1
- package/dist/fast-brain.d.ts +70 -16
- package/dist/fast-brain.js +662 -99
- package/dist/index-3-2-26-legacy.d.ts +1 -0
- package/dist/index-3-2-26-legacy.js +2233 -0
- package/dist/index.js +752 -423
- package/dist/jsonl-search.d.ts +66 -0
- package/dist/jsonl-search.js +274 -0
- package/dist/leagcyprompts2.d.ts +0 -0
- package/dist/leagcyprompts2.js +573 -0
- package/dist/pipeline-direct-llm.d.ts +77 -0
- package/dist/pipeline-direct-llm.js +216 -0
- package/dist/pipeline-fastbrain.d.ts +45 -0
- package/dist/pipeline-fastbrain.js +367 -0
- package/dist/prompts-2-25-26.d.ts +0 -0
- package/dist/prompts-2-25-26.js +518 -0
- package/dist/prompts-3-2-26.d.ts +78 -0
- package/dist/prompts-3-2-26.js +1319 -0
- package/dist/prompts.d.ts +83 -12
- package/dist/prompts.js +1991 -588
- package/dist/session-access.d.ts +24 -0
- package/dist/session-access.js +74 -0
- package/dist/summary-index.d.ts +87 -0
- package/dist/summary-index.js +570 -0
- package/dist/turn-detector-shim.d.ts +24 -0
- package/dist/turn-detector-shim.js +83 -0
- package/dist/voice-io.d.ts +9 -3
- package/dist/voice-io.js +39 -20
- package/package.json +13 -10
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* jsonl-search.ts — Ripgrep search and BM25 search utilities for JSONL session files
|
|
3
|
+
*
|
|
4
|
+
* Provides two search strategies over Claude Agent SDK session data:
|
|
5
|
+
* 1. ripgrepSearch — fast regex search via rg (or grep fallback) across files
|
|
6
|
+
* 2. bm25Search — in-memory full-text search over parsed session messages
|
|
7
|
+
*
|
|
8
|
+
* Also provides resolveJsonlDir() for locating the JSONL project directory
|
|
9
|
+
* and invalidateBM25Cache() for cache management.
|
|
10
|
+
*/
|
|
11
|
+
export interface RipgrepResult {
|
|
12
|
+
lineNumber: number;
|
|
13
|
+
filePath: string;
|
|
14
|
+
content: string;
|
|
15
|
+
}
|
|
16
|
+
export interface BM25Result {
|
|
17
|
+
content: string;
|
|
18
|
+
score: number;
|
|
19
|
+
type?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Search files using ripgrep (rg) with fallback to grep.
|
|
23
|
+
*
|
|
24
|
+
* @param searchDir - Directory to search in
|
|
25
|
+
* @param pattern - Regex pattern to search for (validated for safety)
|
|
26
|
+
* @param opts.maxResults - Maximum number of results (default: 100)
|
|
27
|
+
* @param opts.include - Glob pattern for file inclusion (e.g., "*.jsonl")
|
|
28
|
+
* @returns Array of matches with line number, file path, and content
|
|
29
|
+
*/
|
|
30
|
+
export declare function ripgrepSearch(searchDir: string, pattern: string, opts?: {
|
|
31
|
+
maxResults?: number;
|
|
32
|
+
include?: string;
|
|
33
|
+
contextLines?: number;
|
|
34
|
+
fromEnd?: boolean;
|
|
35
|
+
}): RipgrepResult[];
|
|
36
|
+
/**
|
|
37
|
+
* Full-text search over session messages using BM25 ranking via MiniSearch.
|
|
38
|
+
*
|
|
39
|
+
* Builds an in-memory index from readSessionHistory() and caches it per session.
|
|
40
|
+
* The cache is invalidated when the sessionId changes or invalidateBM25Cache() is called.
|
|
41
|
+
*
|
|
42
|
+
* @param sessionId - The session UUID
|
|
43
|
+
* @param workingDir - The project working directory
|
|
44
|
+
* @param query - The search query (natural language or keywords)
|
|
45
|
+
* @param opts.maxResults - Maximum number of results (default: 20)
|
|
46
|
+
* @returns Ranked array of matches with content, score, and optional type
|
|
47
|
+
*/
|
|
48
|
+
export declare function bm25Search(sessionId: string, workingDir: string, query: string, opts?: {
|
|
49
|
+
maxResults?: number;
|
|
50
|
+
}): Promise<BM25Result[]>;
|
|
51
|
+
/**
|
|
52
|
+
* Resolve the JSONL directory for a session.
|
|
53
|
+
* Uses the same slug resolution as session-access.ts.
|
|
54
|
+
*
|
|
55
|
+
* @param sessionId - The session UUID
|
|
56
|
+
* @param workingDir - The project working directory
|
|
57
|
+
* @returns The directory path containing JSONL files, or null if it doesn't exist
|
|
58
|
+
*/
|
|
59
|
+
export declare function resolveJsonlDir(sessionId: string, workingDir: string): string | null;
|
|
60
|
+
/**
|
|
61
|
+
* Clear the BM25 cache. Call this when session data has changed
|
|
62
|
+
* and you want the next bm25Search() call to rebuild the index.
|
|
63
|
+
*
|
|
64
|
+
* @param sessionId - The session ID to invalidate (currently clears any cached session)
|
|
65
|
+
*/
|
|
66
|
+
export declare function invalidateBM25Cache(sessionId: string): void;
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* jsonl-search.ts — Ripgrep search and BM25 search utilities for JSONL session files
|
|
3
|
+
*
|
|
4
|
+
* Provides two search strategies over Claude Agent SDK session data:
|
|
5
|
+
* 1. ripgrepSearch — fast regex search via rg (or grep fallback) across files
|
|
6
|
+
* 2. bm25Search — in-memory full-text search over parsed session messages
|
|
7
|
+
*
|
|
8
|
+
* Also provides resolveJsonlDir() for locating the JSONL project directory
|
|
9
|
+
* and invalidateBM25Cache() for cache management.
|
|
10
|
+
*/
|
|
11
|
+
import { spawnSync } from 'child_process';
|
|
12
|
+
import { existsSync } from 'fs';
|
|
13
|
+
import { homedir } from 'os';
|
|
14
|
+
import { join } from 'path';
|
|
15
|
+
import { readSessionHistory } from './session-access.js';
|
|
16
|
+
// ============================================================
|
|
17
|
+
// BM25 CACHE
|
|
18
|
+
// ============================================================
|
|
19
|
+
let bm25Cache = null;
|
|
20
|
+
// ============================================================
|
|
21
|
+
// INTERNAL HELPERS
|
|
22
|
+
// ============================================================
|
|
23
|
+
/** Resolve the claude config directory (same logic as session-access.ts) */
|
|
24
|
+
function resolveClaudeDir() {
|
|
25
|
+
return process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude');
|
|
26
|
+
}
|
|
27
|
+
/** Convert a project path to a slug (same logic as session-access.ts) */
|
|
28
|
+
function projectPathToSlug(projectPath) {
|
|
29
|
+
return projectPath.replace(/\//g, '-');
|
|
30
|
+
}
|
|
31
|
+
/** Find the rg binary — uses @vscode/ripgrep npm package (bundled binary) */
|
|
32
|
+
function findRgBinary() {
|
|
33
|
+
try {
|
|
34
|
+
// @vscode/ripgrep bundles the rg binary — this is our own dependency
|
|
35
|
+
const { rgPath } = require('@vscode/ripgrep');
|
|
36
|
+
if (rgPath && existsSync(rgPath))
|
|
37
|
+
return rgPath;
|
|
38
|
+
}
|
|
39
|
+
catch { }
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
/** Validate a search pattern — reject empty and shell-injection characters */
|
|
43
|
+
function validatePattern(pattern) {
|
|
44
|
+
if (!pattern || pattern.trim().length === 0)
|
|
45
|
+
return false;
|
|
46
|
+
// Block shell-injection characters
|
|
47
|
+
if (/[\$`|;&]/.test(pattern))
|
|
48
|
+
return false;
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
// ============================================================
|
|
52
|
+
// PUBLIC API
|
|
53
|
+
// ============================================================
|
|
54
|
+
/**
|
|
55
|
+
* Search files using ripgrep (rg) with fallback to grep.
|
|
56
|
+
*
|
|
57
|
+
* @param searchDir - Directory to search in
|
|
58
|
+
* @param pattern - Regex pattern to search for (validated for safety)
|
|
59
|
+
* @param opts.maxResults - Maximum number of results (default: 100)
|
|
60
|
+
* @param opts.include - Glob pattern for file inclusion (e.g., "*.jsonl")
|
|
61
|
+
* @returns Array of matches with line number, file path, and content
|
|
62
|
+
*/
|
|
63
|
+
export function ripgrepSearch(searchDir, pattern, opts) {
|
|
64
|
+
if (!validatePattern(pattern))
|
|
65
|
+
return [];
|
|
66
|
+
if (!existsSync(searchDir))
|
|
67
|
+
return [];
|
|
68
|
+
const maxResults = opts?.maxResults ?? 100;
|
|
69
|
+
const fromEnd = opts?.fromEnd ?? false; // caller decides; pipeline uses true for main, false for sub-agents
|
|
70
|
+
const rgBinary = findRgBinary();
|
|
71
|
+
if (rgBinary) {
|
|
72
|
+
if (fromEnd) {
|
|
73
|
+
// Search ALL matches (no --max-count), take last N — gets most recent
|
|
74
|
+
const allResults = runRipgrep(rgBinary, searchDir, pattern, 0, opts?.include, opts?.contextLines);
|
|
75
|
+
return allResults.slice(-maxResults);
|
|
76
|
+
}
|
|
77
|
+
return runRipgrep(rgBinary, searchDir, pattern, maxResults, opts?.include, opts?.contextLines);
|
|
78
|
+
}
|
|
79
|
+
// Fallback to grep
|
|
80
|
+
return runGrepFallback(searchDir, pattern, maxResults, opts?.include);
|
|
81
|
+
}
|
|
82
|
+
/** Run ripgrep and parse results */
|
|
83
|
+
function runRipgrep(rgPath, searchDir, pattern, maxResults, include, contextLines) {
|
|
84
|
+
const args = [
|
|
85
|
+
'--no-heading',
|
|
86
|
+
'--with-filename',
|
|
87
|
+
'--line-number',
|
|
88
|
+
'-i',
|
|
89
|
+
];
|
|
90
|
+
// maxResults 0 = no cap (search all, caller handles slicing)
|
|
91
|
+
if (maxResults > 0) {
|
|
92
|
+
args.push('--max-count', String(maxResults));
|
|
93
|
+
}
|
|
94
|
+
// Add context lines for surrounding message context (default: 3 lines each side)
|
|
95
|
+
const ctx = contextLines ?? 3;
|
|
96
|
+
if (ctx > 0) {
|
|
97
|
+
args.push('-C', String(ctx));
|
|
98
|
+
}
|
|
99
|
+
if (include) {
|
|
100
|
+
args.push('--glob', include);
|
|
101
|
+
}
|
|
102
|
+
args.push(pattern, searchDir);
|
|
103
|
+
const result = spawnSync(rgPath, args, {
|
|
104
|
+
maxBuffer: 2 * 1024 * 1024, // 2MB
|
|
105
|
+
timeout: 5000,
|
|
106
|
+
encoding: 'utf-8',
|
|
107
|
+
});
|
|
108
|
+
// Exit code 1 = no matches (not an error)
|
|
109
|
+
if (result.status === 1)
|
|
110
|
+
return [];
|
|
111
|
+
// Any other non-zero exit is an error
|
|
112
|
+
if (result.status !== 0 && result.status !== null)
|
|
113
|
+
return [];
|
|
114
|
+
return parseSearchOutput(result.stdout || '');
|
|
115
|
+
}
|
|
116
|
+
/** Fallback to grep if rg is not found */
|
|
117
|
+
function runGrepFallback(searchDir, pattern, maxResults, include) {
|
|
118
|
+
const args = [
|
|
119
|
+
'-r',
|
|
120
|
+
'-n',
|
|
121
|
+
'-i',
|
|
122
|
+
'-m', String(maxResults),
|
|
123
|
+
];
|
|
124
|
+
if (include) {
|
|
125
|
+
args.push('--include', include);
|
|
126
|
+
}
|
|
127
|
+
args.push(pattern, searchDir);
|
|
128
|
+
const result = spawnSync('grep', args, {
|
|
129
|
+
maxBuffer: 2 * 1024 * 1024, // 2MB
|
|
130
|
+
timeout: 5000,
|
|
131
|
+
encoding: 'utf-8',
|
|
132
|
+
});
|
|
133
|
+
// Exit code 1 = no matches
|
|
134
|
+
if (result.status === 1)
|
|
135
|
+
return [];
|
|
136
|
+
if (result.status !== 0 && result.status !== null)
|
|
137
|
+
return [];
|
|
138
|
+
return parseSearchOutput(result.stdout || '');
|
|
139
|
+
}
|
|
140
|
+
/** Parse output lines in the format: filePath:lineNumber:content */
|
|
141
|
+
function parseSearchOutput(stdout) {
|
|
142
|
+
if (!stdout || !stdout.trim())
|
|
143
|
+
return [];
|
|
144
|
+
const results = [];
|
|
145
|
+
const lines = stdout.trim().split('\n');
|
|
146
|
+
for (const line of lines) {
|
|
147
|
+
// Format: filePath:lineNumber:content
|
|
148
|
+
const match = line.match(/^(.+?):(\d+):(.*)$/);
|
|
149
|
+
if (match) {
|
|
150
|
+
results.push({
|
|
151
|
+
filePath: match[1],
|
|
152
|
+
lineNumber: parseInt(match[2], 10),
|
|
153
|
+
content: match[3],
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return results;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Full-text search over session messages using BM25 ranking via MiniSearch.
|
|
161
|
+
*
|
|
162
|
+
* Builds an in-memory index from readSessionHistory() and caches it per session.
|
|
163
|
+
* The cache is invalidated when the sessionId changes or invalidateBM25Cache() is called.
|
|
164
|
+
*
|
|
165
|
+
* @param sessionId - The session UUID
|
|
166
|
+
* @param workingDir - The project working directory
|
|
167
|
+
* @param query - The search query (natural language or keywords)
|
|
168
|
+
* @param opts.maxResults - Maximum number of results (default: 20)
|
|
169
|
+
* @returns Ranked array of matches with content, score, and optional type
|
|
170
|
+
*/
|
|
171
|
+
export async function bm25Search(sessionId, workingDir, query, opts) {
|
|
172
|
+
if (!query || !query.trim())
|
|
173
|
+
return [];
|
|
174
|
+
const maxResults = opts?.maxResults ?? 20;
|
|
175
|
+
// Rebuild index if cache is stale or for a different session
|
|
176
|
+
if (!bm25Cache || bm25Cache.sessionId !== sessionId) {
|
|
177
|
+
const { default: MiniSearch } = await import('minisearch');
|
|
178
|
+
const miniSearch = new MiniSearch({
|
|
179
|
+
fields: ['text'],
|
|
180
|
+
storeFields: ['text', 'type', 'toolName', 'timestamp'],
|
|
181
|
+
searchOptions: {
|
|
182
|
+
fuzzy: 0.2,
|
|
183
|
+
prefix: true,
|
|
184
|
+
boost: { text: 2 },
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
// Load recent session messages only — cap at 500 to keep index build fast
|
|
188
|
+
// For a 4400-message session, loading all would take 10+ seconds
|
|
189
|
+
const messages = readSessionHistory(sessionId, workingDir, { lastN: 500 });
|
|
190
|
+
// Build documents — filter to entries with meaningful text
|
|
191
|
+
const docs = [];
|
|
192
|
+
for (let i = 0; i < messages.length; i++) {
|
|
193
|
+
const msg = messages[i];
|
|
194
|
+
const text = extractMessageText(msg);
|
|
195
|
+
if (text && text.length > 10) {
|
|
196
|
+
docs.push({
|
|
197
|
+
id: i,
|
|
198
|
+
text: text.substring(0, 2000),
|
|
199
|
+
type: msg.type,
|
|
200
|
+
toolName: msg.toolName,
|
|
201
|
+
timestamp: msg.timestamp,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
miniSearch.addAll(docs);
|
|
206
|
+
bm25Cache = {
|
|
207
|
+
index: miniSearch,
|
|
208
|
+
sessionId,
|
|
209
|
+
builtAt: Date.now(),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
// Search the index
|
|
213
|
+
const results = bm25Cache.index.search(query, { limit: maxResults });
|
|
214
|
+
return results.map((r) => ({
|
|
215
|
+
content: r.text || '',
|
|
216
|
+
score: r.score,
|
|
217
|
+
type: r.type,
|
|
218
|
+
}));
|
|
219
|
+
}
|
|
220
|
+
/** Extract searchable text from a SessionMessage */
|
|
221
|
+
function extractMessageText(msg) {
|
|
222
|
+
switch (msg.type) {
|
|
223
|
+
case 'user':
|
|
224
|
+
case 'assistant':
|
|
225
|
+
return msg.text;
|
|
226
|
+
case 'tool_use':
|
|
227
|
+
// Include tool name + any associated text + stringified input
|
|
228
|
+
const parts = [];
|
|
229
|
+
if (msg.toolName)
|
|
230
|
+
parts.push(msg.toolName);
|
|
231
|
+
if (msg.text)
|
|
232
|
+
parts.push(msg.text);
|
|
233
|
+
if (msg.toolInput) {
|
|
234
|
+
try {
|
|
235
|
+
parts.push(JSON.stringify(msg.toolInput));
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
// skip
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return parts.join(' ') || undefined;
|
|
242
|
+
case 'tool_result':
|
|
243
|
+
return msg.toolResultContent;
|
|
244
|
+
default:
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Resolve the JSONL directory for a session.
|
|
250
|
+
* Uses the same slug resolution as session-access.ts.
|
|
251
|
+
*
|
|
252
|
+
* @param sessionId - The session UUID
|
|
253
|
+
* @param workingDir - The project working directory
|
|
254
|
+
* @returns The directory path containing JSONL files, or null if it doesn't exist
|
|
255
|
+
*/
|
|
256
|
+
export function resolveJsonlDir(sessionId, workingDir) {
|
|
257
|
+
const claudeDir = resolveClaudeDir();
|
|
258
|
+
const slug = projectPathToSlug(workingDir);
|
|
259
|
+
const projectsDir = join(claudeDir, 'projects', slug);
|
|
260
|
+
if (!existsSync(projectsDir))
|
|
261
|
+
return null;
|
|
262
|
+
return projectsDir;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Clear the BM25 cache. Call this when session data has changed
|
|
266
|
+
* and you want the next bm25Search() call to rebuild the index.
|
|
267
|
+
*
|
|
268
|
+
* @param sessionId - The session ID to invalidate (currently clears any cached session)
|
|
269
|
+
*/
|
|
270
|
+
export function invalidateBM25Cache(sessionId) {
|
|
271
|
+
if (bm25Cache && bm25Cache.sessionId === sessionId) {
|
|
272
|
+
bm25Cache = null;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
File without changes
|