@ottocode/sdk 0.1.301 → 0.1.303

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ottocode/sdk",
3
- "version": "0.1.301",
3
+ "version": "0.1.303",
4
4
  "description": "AI agent SDK for building intelligent assistants - tree-shakable and comprehensive",
5
5
  "author": "nitishxyz",
6
6
  "license": "MIT",
@@ -61,9 +61,9 @@
61
61
  "import": "./src/core/src/tools/builtin/progress.ts",
62
62
  "types": "./src/core/src/tools/builtin/progress.ts"
63
63
  },
64
- "./tools/builtin/ripgrep": {
65
- "import": "./src/core/src/tools/builtin/ripgrep.ts",
66
- "types": "./src/core/src/tools/builtin/ripgrep.ts"
64
+ "./tools/builtin/search": {
65
+ "import": "./src/core/src/tools/builtin/search.ts",
66
+ "types": "./src/core/src/tools/builtin/search.ts"
67
67
  },
68
68
  "./tools/builtin/websearch": {
69
69
  "import": "./src/core/src/tools/builtin/websearch.ts",
@@ -81,6 +81,10 @@
81
81
  "import": "./src/core/src/tools/bin-manager.ts",
82
82
  "types": "./src/core/src/tools/bin-manager.ts"
83
83
  },
84
+ "./search/fff": {
85
+ "import": "./src/core/src/search/fff.ts",
86
+ "types": "./src/core/src/search/fff.ts"
87
+ },
84
88
  "./prompts/*": "./src/prompts/src/*"
85
89
  },
86
90
  "files": [
@@ -94,33 +98,36 @@
94
98
  "typecheck": "tsc --noEmit"
95
99
  },
96
100
  "dependencies": {
97
- "@ai-sdk/anthropic": "^3.0.72",
98
- "@ai-sdk/google": "^3.0.0",
99
- "@ai-sdk/openai": "^3.0.0",
100
- "@ai-sdk/openai-compatible": "^2.0.0",
101
- "@ai-sdk/xai": "^3.0.0",
102
- "@modelcontextprotocol/sdk": "^1.12",
103
- "@openauthjs/openauth": "^0.4.3",
104
- "@openrouter/ai-sdk-provider": "^1.2.0",
101
+ "@ai-sdk/anthropic": "3.0.82",
102
+ "@ai-sdk/google": "3.0.80",
103
+ "@ai-sdk/openai": "3.0.69",
104
+ "@ai-sdk/openai-compatible": "2.0.46",
105
+ "@ai-sdk/provider": "3.0.10",
106
+ "@ai-sdk/provider-utils": "4.0.27",
107
+ "@ai-sdk/xai": "3.0.93",
108
+ "@ff-labs/fff-bun": "0.9.3",
109
+ "@modelcontextprotocol/sdk": "1.27.1",
110
+ "@openauthjs/openauth": "0.4.3",
111
+ "@openrouter/ai-sdk-provider": "1.5.4",
105
112
  "@ottorouter/ai-sdk": "0.2.6",
106
- "@solana/web3.js": "^1.98.0",
107
- "ai": "^6.0.170",
108
- "ai-sdk-ollama": "^3.8.3",
109
- "bs58": "^6.0.0",
110
- "bun-pty": "^0.3.2",
111
- "diff": "^8.0.2",
112
- "fast-glob": "^3.3.2",
113
- "hono": "^4.9.9",
114
- "opencode-anthropic-auth": "^0.0.2",
115
- "qrcode": "^1.5.4",
116
- "qrcode-terminal": "^0.12.0",
117
- "tweetnacl": "^1.0.3",
118
- "x402": "^1.1.0",
119
- "zod": "^4.3.6"
113
+ "@solana/web3.js": "1.98.4",
114
+ "ai": "6.0.199",
115
+ "ai-sdk-ollama": "3.8.3",
116
+ "bs58": "6.0.0",
117
+ "bun-pty": "0.3.2",
118
+ "diff": "8.0.3",
119
+ "fast-glob": "3.3.3",
120
+ "hono": "4.12.0",
121
+ "opencode-anthropic-auth": "0.0.2",
122
+ "qrcode": "1.5.4",
123
+ "qrcode-terminal": "0.12.0",
124
+ "tweetnacl": "1.0.3",
125
+ "x402": "1.1.0",
126
+ "zod": "4.3.6"
120
127
  },
121
128
  "devDependencies": {
122
- "@types/bun": "latest",
123
- "typescript": "~5.9.3"
129
+ "@types/bun": "1.3.14",
130
+ "typescript": "5.9.3"
124
131
  },
125
132
  "keywords": [
126
133
  "ai",
@@ -0,0 +1,215 @@
1
+ import { dirname, isAbsolute, relative, resolve } from 'node:path';
2
+ import { stat } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { FileFinder, type FileFinderApi } from '@ff-labs/fff-bun';
5
+
6
+ export type FffFileSearchResult = {
7
+ files: string[];
8
+ truncated: boolean;
9
+ };
10
+
11
+ type FinderEntry = {
12
+ finder: FileFinderApi;
13
+ ready: Promise<void>;
14
+ lastScanMs: number;
15
+ };
16
+
17
+ const finderCache = new Map<string, Promise<FinderEntry>>();
18
+
19
+ function normalizeRoot(path: string): string {
20
+ return resolve(path);
21
+ }
22
+
23
+ function isHomeRoot(path: string): boolean {
24
+ return normalizeRoot(path) === normalizeRoot(homedir());
25
+ }
26
+
27
+ async function createFinderEntry(basePath: string): Promise<FinderEntry> {
28
+ const result = FileFinder.create({
29
+ basePath,
30
+ aiMode: true,
31
+ disableWatch: true,
32
+ enableHomeDirScanning: isHomeRoot(basePath),
33
+ enableFsRootScanning: false,
34
+ });
35
+ if (!result.ok) throw new Error(result.error);
36
+
37
+ const finder = result.value;
38
+ const entry: FinderEntry = {
39
+ finder,
40
+ ready: Promise.resolve(),
41
+ lastScanMs: 0,
42
+ };
43
+ const ready = (async () => {
44
+ const scan = await finder.waitForIndexReady(10_000);
45
+ if (!scan.ok) throw new Error(scan.error);
46
+ entry.lastScanMs = Date.now();
47
+ })();
48
+ entry.ready = ready;
49
+
50
+ return entry;
51
+ }
52
+
53
+ async function getFffEntry(basePath: string): Promise<FinderEntry> {
54
+ const root = normalizeRoot(basePath);
55
+ let entryPromise = finderCache.get(root);
56
+ if (!entryPromise) {
57
+ entryPromise = createFinderEntry(root).catch((error) => {
58
+ finderCache.delete(root);
59
+ throw error;
60
+ });
61
+ finderCache.set(root, entryPromise);
62
+ }
63
+
64
+ const entry = await entryPromise;
65
+ await entry.ready;
66
+ return entry;
67
+ }
68
+
69
+ /**
70
+ * Return a cached FFF finder for a root directory.
71
+ */
72
+ export async function getFffFinder(basePath: string): Promise<FileFinderApi> {
73
+ const entry = await getFffEntry(basePath);
74
+ return entry.finder;
75
+ }
76
+
77
+ /**
78
+ * Force FFF to rescan a root and wait briefly for fresh results.
79
+ */
80
+ export async function refreshFffIndex(
81
+ basePath: string,
82
+ timeoutMs = 3_000,
83
+ maxAgeMs = 0,
84
+ ): Promise<void> {
85
+ const entry = await getFffEntry(basePath);
86
+ if (maxAgeMs > 0 && Date.now() - entry.lastScanMs < maxAgeMs) return;
87
+ const finder = entry.finder;
88
+ const scan = finder.scanFiles();
89
+ if (!scan.ok) throw new Error(scan.error);
90
+ const done = await finder.waitForScan(timeoutMs);
91
+ if (!done.ok) throw new Error(done.error);
92
+ entry.lastScanMs = Date.now();
93
+ }
94
+
95
+ export function clearFffFinderCache(): void {
96
+ for (const entryPromise of finderCache.values()) {
97
+ entryPromise.then((entry) => entry.finder.destroy()).catch(() => undefined);
98
+ }
99
+ finderCache.clear();
100
+ }
101
+
102
+ function normalizeRelativePath(root: string, path: string): string {
103
+ const rel = relative(root, path).replace(/\\/g, '/');
104
+ return rel === '' ? '.' : rel;
105
+ }
106
+
107
+ function fileDepth(relativePath: string): number {
108
+ if (relativePath === '.') return 0;
109
+ return Math.max(0, relativePath.split(/[\\/]/).length - 1);
110
+ }
111
+
112
+ function isWithinRoot(root: string, target: string): boolean {
113
+ const rel = relative(root, target);
114
+ return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));
115
+ }
116
+
117
+ async function getRootAndConstraint(projectRoot: string, inputPath = '.') {
118
+ const trimmed = inputPath.trim() || '.';
119
+ const expanded =
120
+ trimmed === '~'
121
+ ? homedir()
122
+ : trimmed.startsWith('~/')
123
+ ? `${homedir()}/${trimmed.slice(2)}`
124
+ : trimmed;
125
+ const target = isAbsolute(expanded)
126
+ ? resolve(expanded)
127
+ : resolve(projectRoot, expanded);
128
+ const root = normalizeRoot(projectRoot);
129
+
130
+ try {
131
+ const targetStat = await stat(target);
132
+ if (isWithinRoot(root, target)) {
133
+ const rel = normalizeRelativePath(root, target);
134
+ if (rel === '.') return { basePath: root, constraint: '' };
135
+ return {
136
+ basePath: root,
137
+ constraint: targetStat.isDirectory() ? `${rel}/` : rel,
138
+ };
139
+ }
140
+
141
+ if (targetStat.isDirectory()) return { basePath: target, constraint: '' };
142
+ return {
143
+ basePath: dirname(target),
144
+ constraint: target.split(/[\\/]/).pop() ?? '',
145
+ };
146
+ } catch {
147
+ if (isWithinRoot(root, target)) {
148
+ const rel = normalizeRelativePath(root, target);
149
+ return { basePath: root, constraint: rel === '.' ? '' : rel };
150
+ }
151
+ return { basePath: target, constraint: '' };
152
+ }
153
+ }
154
+
155
+ export async function resolveFffSearchScope(projectRoot: string, path = '.') {
156
+ return getRootAndConstraint(projectRoot, path);
157
+ }
158
+
159
+ export async function searchFffFiles(args: {
160
+ projectRoot: string;
161
+ query?: string;
162
+ path?: string;
163
+ exclude?: string[];
164
+ maxDepth: number;
165
+ limit: number;
166
+ }): Promise<FffFileSearchResult> {
167
+ const { basePath, constraint } = await getRootAndConstraint(
168
+ args.projectRoot,
169
+ args.path ?? '.',
170
+ );
171
+ await refreshFffIndex(basePath, 3_000, 2_000);
172
+ const finder = await getFffFinder(basePath);
173
+ const query = args.query?.trim() ?? '';
174
+ const constraints = [
175
+ constraint,
176
+ ...(args.exclude ?? []).map((pattern) => `!${pattern.replace(/^!/, '')}`),
177
+ ]
178
+ .map((part) => part.trim())
179
+ .filter(Boolean)
180
+ .join(' ');
181
+ const pageSize = Math.min(Math.max(args.limit * 2, 100), 1_000);
182
+ const files: string[] = [];
183
+ const seen = new Set<string>();
184
+ let truncated = false;
185
+
186
+ for (
187
+ let pageIndex = 0;
188
+ pageIndex < 100 && files.length < args.limit;
189
+ pageIndex++
190
+ ) {
191
+ const result =
192
+ query || constraints
193
+ ? finder.fileSearch(`${constraints ? `${constraints} ` : ''}${query}`, {
194
+ pageIndex,
195
+ pageSize,
196
+ })
197
+ : finder.glob(constraint || '**/*', { pageIndex, pageSize });
198
+ if (!result.ok) throw new Error(result.error);
199
+
200
+ for (const item of result.value.items) {
201
+ const relativePath = item.relativePath;
202
+ if (seen.has(relativePath)) continue;
203
+ if (fileDepth(relativePath) >= args.maxDepth) continue;
204
+ seen.add(relativePath);
205
+ files.push(relativePath);
206
+ if (files.length >= args.limit) break;
207
+ }
208
+
209
+ const consumed = (pageIndex + 1) * pageSize;
210
+ if (consumed >= result.value.totalMatched) break;
211
+ if (files.length >= args.limit) truncated = true;
212
+ }
213
+
214
+ return { files: files.slice(0, args.limit), truncated };
215
+ }
@@ -5,6 +5,6 @@
5
5
 
6
6
  Usage tips:
7
7
  - Prefer relative project paths when possible (more portable)
8
- - For large files or searches, use the Grep or Ripgrep tool
8
+ - For large files or searches, use the `search` tool
9
9
  - Use startLine/endLine or startLine/maxLines for targeted reads of large files
10
10
  - If startLine is provided without endLine or maxLines, only that one line is read
@@ -8,6 +8,6 @@
8
8
 
9
9
  ## Usage tips
10
10
 
11
- - Use `glob` for filename patterns; use `ripgrep` for file contents.
11
+ - Use `glob` for filename patterns; use `search` for file contents.
12
12
  - Combine with `path` to restrict the search to a subdirectory.
13
13
  - Prefer reading a known file directly over globbing to "find" it (check the `<project>` listing in the system prompt first).
@@ -0,0 +1,157 @@
1
+ import { tool, type Tool } from 'ai';
2
+ import { z } from 'zod/v3';
3
+ import DESCRIPTION from './search.txt' with { type: 'text' };
4
+ import { createToolError, type ToolResponse } from '../error.ts';
5
+ import {
6
+ getFffFinder,
7
+ refreshFffIndex,
8
+ resolveFffSearchScope,
9
+ } from '../../search/fff.ts';
10
+
11
+ const TEXT_MAX = 200;
12
+
13
+ type SearchMatch = { file: string; line: number; text: string };
14
+
15
+ type SearchToolResult = {
16
+ count: number;
17
+ matches: SearchMatch[];
18
+ truncated?: boolean;
19
+ shownMatches?: number;
20
+ files?: Array<{ file: string; matches: number }>;
21
+ };
22
+
23
+ function truncateText(text: string): string {
24
+ return text.length > TEXT_MAX ? `${text.slice(0, TEXT_MAX)}…` : text;
25
+ }
26
+
27
+ function buildFffQuery(args: {
28
+ query: string;
29
+ pathConstraint: string;
30
+ glob?: string;
31
+ ignoreCase?: boolean;
32
+ }): string {
33
+ const constraints = [args.pathConstraint, args.glob]
34
+ .map((part) => part?.trim() ?? '')
35
+ .filter(Boolean);
36
+ const query = args.ignoreCase ? `(?i)${args.query}` : args.query;
37
+ return [...constraints, query].join(' ');
38
+ }
39
+
40
+ function summarizeMatches(matches: SearchMatch[]) {
41
+ const fileCounts = new Map<string, number>();
42
+ for (const match of matches) {
43
+ fileCounts.set(match.file, (fileCounts.get(match.file) ?? 0) + 1);
44
+ }
45
+ return Array.from(fileCounts.entries()).map(([file, count]) => ({
46
+ file,
47
+ matches: count,
48
+ }));
49
+ }
50
+
51
+ export function buildSearchTool(projectRoot: string): {
52
+ name: string;
53
+ tool: Tool;
54
+ } {
55
+ const search = tool({
56
+ description: DESCRIPTION,
57
+ inputSchema: z.object({
58
+ query: z.string().min(1).describe('Search pattern (regex by default)'),
59
+ path: z
60
+ .string()
61
+ .optional()
62
+ .default('.')
63
+ .describe('Relative path to search in'),
64
+ ignoreCase: z.boolean().optional().default(false),
65
+ glob: z
66
+ .array(z.string())
67
+ .optional()
68
+ .describe('One or more glob patterns to include'),
69
+ maxResults: z.number().int().min(1).max(5000).optional().default(100),
70
+ }),
71
+ async execute({
72
+ query,
73
+ path = '.',
74
+ ignoreCase,
75
+ glob,
76
+ maxResults = 100,
77
+ }: {
78
+ query: string;
79
+ path?: string;
80
+ ignoreCase?: boolean;
81
+ glob?: string[];
82
+ maxResults?: number;
83
+ }): Promise<ToolResponse<SearchToolResult>> {
84
+ try {
85
+ const { basePath, constraint } = await resolveFffSearchScope(
86
+ projectRoot,
87
+ path,
88
+ );
89
+ await refreshFffIndex(basePath);
90
+ const finder = await getFffFinder(basePath);
91
+ const includeGlobs =
92
+ Array.isArray(glob) && glob.length > 0 ? glob : [undefined];
93
+ const matches: SearchMatch[] = [];
94
+ const seen = new Set<string>();
95
+ let truncated = false;
96
+
97
+ for (const includeGlob of includeGlobs) {
98
+ if (matches.length >= maxResults) {
99
+ truncated = true;
100
+ break;
101
+ }
102
+
103
+ const fffQuery = buildFffQuery({
104
+ query,
105
+ pathConstraint: constraint,
106
+ glob: includeGlob,
107
+ ignoreCase,
108
+ });
109
+ const result = finder.grep(fffQuery, {
110
+ mode: 'regex',
111
+ smartCase: false,
112
+ pageSize: maxResults - matches.length,
113
+ maxMatchesPerFile: maxResults,
114
+ });
115
+
116
+ if (!result.ok) {
117
+ return createToolError(result.error, 'execution', {
118
+ suggestion: 'Check if the search query is valid',
119
+ });
120
+ }
121
+
122
+ for (const item of result.value.items) {
123
+ const match = {
124
+ file: item.relativePath,
125
+ line: item.lineNumber,
126
+ text: truncateText(item.lineContent),
127
+ };
128
+ const key = `${match.file}:${match.line}:${match.text}`;
129
+ if (seen.has(key)) continue;
130
+ seen.add(key);
131
+ matches.push(match);
132
+ if (matches.length >= maxResults) break;
133
+ }
134
+
135
+ truncated = truncated || Boolean(result.value.nextCursor);
136
+ }
137
+
138
+ const files = summarizeMatches(matches);
139
+
140
+ return {
141
+ ok: true,
142
+ count: matches.length,
143
+ matches,
144
+ ...(truncated
145
+ ? { truncated: true, shownMatches: matches.length }
146
+ : {}),
147
+ ...(files.length ? { files } : {}),
148
+ };
149
+ } catch (err) {
150
+ return createToolError(String(err), 'execution', {
151
+ suggestion: 'Check if FFF is available and the query is valid',
152
+ });
153
+ }
154
+ },
155
+ });
156
+ return { name: 'search', tool: search };
157
+ }
@@ -0,0 +1,14 @@
1
+ - Search file contents using the FFF indexed search engine
2
+ - Returns a flat list of matches with `file`, `line`, and `text`
3
+ - Supports regex patterns, include globs, path constraints, and case-insensitive search
4
+ - Respects `.gitignore` by default
5
+
6
+ Use this for text/code search across the codebase. It is the primary tool for repository content discovery.
7
+
8
+ ## Usage tips
9
+
10
+ - Narrow broad searches with `path` and `glob` values.
11
+ - Keep `maxResults` low for broad searches; the tool returns at most that many matches.
12
+ - Batch independent searches (e.g. multiple function names) in a single turn for parallel execution.
13
+ - Use `ignoreCase: true` for case-insensitive matching; pass `glob` patterns (e.g. `["*.ts", "*.tsx"]`) to limit file types.
14
+ - For filename/path discovery, use `glob` first when you already know the file pattern.
@@ -47,6 +47,17 @@ export type ShellOutputMode = 'auto' | 'full' | 'tail';
47
47
  const DEFAULT_TAIL_LINES = 100;
48
48
  const DEFAULT_MAX_OUTPUT_BYTES = 128_000;
49
49
 
50
+ function looksLikeRepositorySearchCommand(cmd: string): boolean {
51
+ const normalized = cmd.replace(/\s+/g, ' ').trim();
52
+ return (
53
+ /(^|[;&|()]\s*)(rg|ripgrep)(\s|$)/.test(normalized) ||
54
+ /(^|[;&|()]\s*)grep\s+.*\s(-R|-r|--recursive)(\s|$)/.test(normalized) ||
55
+ /(^|[;&|()]\s*)find\s+(\.|\.\/|\$PWD|\S*\/).*(-name|-iname|-path|-type)/.test(
56
+ normalized,
57
+ )
58
+ );
59
+ }
60
+
50
61
  type CompactTextResult = {
51
62
  text: string;
52
63
  truncated: boolean;
@@ -220,6 +231,18 @@ export function buildShellTool(projectRoot: string): {
220
231
  });
221
232
  }
222
233
 
234
+ if (looksLikeRepositorySearchCommand(cmd)) {
235
+ return createToolError(
236
+ 'This looks like repository discovery. Use the search tool for content/code search or glob for filename/path discovery.',
237
+ 'validation',
238
+ {
239
+ cmd,
240
+ suggestion:
241
+ 'Use search for file contents, or glob for file and path discovery.',
242
+ },
243
+ );
244
+ }
245
+
223
246
  const absCwd = resolveSafePath(projectRoot, cwd || '.');
224
247
  const finalCmd = injectCoAuthorIntoGitCommit(cmd);
225
248
  const shellExecutor = shellExecutorContext.getStore();
@@ -4,13 +4,15 @@
4
4
 
5
5
  **Use `shell` for one-off, non-interactive commands.** These may be short-lived checks or long-running commands that finish on their own and do not require stdin. For commands that need interactive input, a TTY, or persistence across turns, use the `terminal` tool instead.
6
6
 
7
+ For repository discovery, use `search` for content/code search and `glob` for filename/path discovery. Reserve `shell` for execution, builds, tests, diagnostics, and other command-line tasks.
8
+
7
9
  ## Usage tips
8
10
 
9
11
  - Chain commands with `&&` to fail-fast.
10
12
  - For long outputs, redirect to a file, inspect `wc -c`, then read a small range or tail.
11
13
  - `outputMode: "auto"` is the default and keeps bounded tail output. Use `outputMode: "tail"` with `tailLines` for verbose builds/tests, or `outputMode: "full"` only when complete output is truly needed.
12
14
  - Final `stdout`/`stderr` are capped by `maxOutputBytes` per stream to avoid huge tool results. Set `maxOutputBytes: 0` only when you intentionally need uncapped output.
13
- - For binary, minified, or `strings` searches, cap line width explicitly, e.g. `rg -o '.{0,80}needle.{0,120}' | head -50`.
15
+ - For binary/minified inspection that cannot be handled by `search`, cap line width and output explicitly.
14
16
  - For long-running non-interactive commands, set an appropriate `timeout` and ensure the command exits on its own.
15
17
  - Batch independent checks (e.g. `git status && git diff`) in parallel tool calls rather than sequential shell chains when you need results separately.
16
18
  - Never use `shell` with `sed`/`awk` for programmatic file editing — use the dedicated file-editing tools instead.
@@ -5,7 +5,7 @@ import { buildFsTools } from './builtin/fs/index.ts';
5
5
  import { buildGitTools } from './builtin/git.ts';
6
6
  import { progressUpdateTool } from './builtin/progress.ts';
7
7
  import { buildShellTool } from './builtin/shell.ts';
8
- import { buildRipgrepTool } from './builtin/ripgrep.ts';
8
+ import { buildSearchTool } from './builtin/search.ts';
9
9
  import { buildGlobTool } from './builtin/glob.ts';
10
10
  import { buildApplyPatchTool } from './builtin/patch.ts';
11
11
  import { updateTodosTool } from './builtin/todos.ts';
@@ -153,8 +153,8 @@ async function discoverStaticProjectTools(
153
153
  const shell = buildShellTool(projectRoot);
154
154
  tools.set(shell.name, shell.tool);
155
155
  // Search
156
- const rg = buildRipgrepTool(projectRoot);
157
- tools.set(rg.name, rg.tool);
156
+ const search = buildSearchTool(projectRoot);
157
+ tools.set(search.name, search.tool);
158
158
  const glob = buildGlobTool(projectRoot);
159
159
  tools.set(glob.name, glob.tool);
160
160
  // Patch/apply
@@ -31,5 +31,6 @@ After making changes:
31
31
 
32
32
  ## Searching & discovery
33
33
 
34
- - `ripgrep` for content search, `glob` for filename patterns, `tree` for hierarchical structure.
34
+ - Use the `search` tool for content/code search and `glob` for filename patterns.
35
+ - Reserve `shell` for execution, builds, tests, and other command-line tasks.
35
36
  - Batch independent reads/searches in a single turn for performance.
@@ -5,6 +5,7 @@ You may ONLY observe, analyze, and plan.
5
5
  </system-reminder>
6
6
 
7
7
  Your job: produce an actionable, minimal plan.
8
- - Use only read/inspect tools (read, ls, tree, ripgrep, git_diff).
8
+ - Use only read/inspect tools (read, ls, tree, search, glob, git_diff).
9
+ - Use `search` for content/code search and `glob` for filename/path discovery.
9
10
  - Identify concrete steps with just enough detail to execute later.
10
11
  - No changes. No write operations. No refactors.
@@ -20,7 +20,7 @@ Help users find information from past sessions, session history, and the codebas
20
20
 
21
21
  ## Codebase Tools
22
22
 
23
- - `read`, `ripgrep`, `tree`, `ls`
23
+ - `read`, `search`, `glob`, `tree`, `ls`
24
24
 
25
25
  ## Research Strategy
26
26
 
@@ -97,7 +97,7 @@ assistant: Clients are marked as failed in the `connectToServer` function in src
97
97
  For software engineering requests (bugs, features, refactors, explanations):
98
98
 
99
99
  1. Use `update_todos` to plan multi-step work.
100
- 2. Explore the codebase with the search tools (`glob`, `ripgrep`, `tree`, `read`). Batch independent searches in parallel.
100
+ 2. Explore the codebase with the search tools (`search`, `glob`, `tree`, `read`). Batch independent searches in parallel.
101
101
  3. Implement the solution.
102
102
  4. Verify — run the project's build/lint/test commands with `shell`. Check `README.md` / `AGENTS.md` to find the right command.
103
103
  5. Review diffs with `git_status` / `git_diff`.
@@ -109,7 +109,7 @@ When the user mentions a specific file (e.g. `@publish.config`, `src/app.ts`, `p
109
109
 
110
110
  - Check the `<project>` listing in the system prompt first — if the file is there, read it directly.
111
111
  - Do NOT waste tool calls searching for a file whose path is already known.
112
- - Fall back to `glob` / `ripgrep` only when the path is genuinely ambiguous.
112
+ - Fall back to `glob` / `search` only when the path is genuinely ambiguous.
113
113
 
114
114
  # Batching tool calls
115
115
 
@@ -24,7 +24,7 @@ You are a coding agent running in otto, a terminal-based coding assistant. Preci
24
24
 
25
25
  # Working on tasks
26
26
 
27
- 1. Understand — use `glob`, `ripgrep`, `tree`, `read` to map the code. Batch independent searches.
27
+ 1. Understand — use `search`, `glob`, `tree`, `read` to map the code. Batch independent searches.
28
28
  2. Plan — use `update_todos` for multi-step work. Mark one step `in_progress` at a time.
29
29
  3. Implement — prefer the targeted editing tools available to you for small in-file changes; use patch-style edits for structural or multi-file changes when available; use `write` only for new files or near-total rewrites.
30
30
  4. Verify — run project-specific build/lint/test commands via `shell`. Check `README.md` / `AGENTS.md` for the right command.
@@ -36,7 +36,7 @@ When the user names a specific file:
36
36
 
37
37
  - Check the `<project>` listing in the system prompt first — read directly if listed.
38
38
  - Don't waste tool calls searching for a known path.
39
- - Fall back to `glob` / `ripgrep` only when the path is genuinely ambiguous.
39
+ - Fall back to `glob` / `search` only when the path is genuinely ambiguous.
40
40
 
41
41
  # Batching and parallelism
42
42
 
@@ -36,7 +36,7 @@ Your reasoning is powerful, but it can override what you actually read. Guard ag
36
36
 
37
37
  # Working on tasks
38
38
 
39
- 1. Understand — use `glob`, `ripgrep`, `tree`, `read` to map the code. Batch independent searches.
39
+ 1. Understand — use `search`, `glob`, `tree`, `read` to map the code. Batch independent searches.
40
40
  2. Plan — use `update_todos` for multi-step work. Mark one step `in_progress` at a time.
41
41
  3. Implement — prefer the targeted editing tools available to you for small in-file changes; use patch-style edits for structural or multi-file changes when available; use `write` only for new files or near-total rewrites.
42
42
  4. Verify — run project-specific build/lint/test commands via `shell`. Check `README.md` / `AGENTS.md` for the right command.
@@ -48,7 +48,7 @@ When the user names a specific file:
48
48
 
49
49
  - Check the `<project>` listing in the system prompt first — read directly if listed.
50
50
  - Don't waste tool calls searching for a known path.
51
- - Fall back to `glob` / `ripgrep` only when the path is genuinely ambiguous.
51
+ - Fall back to `glob` / `search` only when the path is genuinely ambiguous.
52
52
 
53
53
  # Batching and parallelism
54
54
 
@@ -19,7 +19,7 @@ You are Gemini, operating as otto — an interactive CLI coding agent specializi
19
19
 
20
20
  For bug fixes, features, refactors, or explanations:
21
21
 
22
- 1. **Understand.** Use `glob` / `ripgrep` / `tree` / `read` extensively (in parallel when independent) to understand structure, patterns, and conventions.
22
+ 1. **Understand.** Use `search` / `glob` / `tree` / `read` extensively (in parallel when independent) to understand structure, patterns, and conventions.
23
23
  2. **Plan.** Build a grounded plan. Share an extremely concise plan with the user if it helps. Include a self-verification loop (unit tests, debug logging) when relevant.
24
24
  3. **Implement.** Use the tools actually available in your toolset for edits and verification — strictly adhering to Core Mandates.
25
25
  4. **Verify (tests).** Identify test commands from `README`, `package.json`, or existing test patterns. NEVER assume standard test commands.