@@ -6,6 +6,17 @@ import { getBundledRgPath } from '../native/ripgrep'
66
77import type { CodebuffToolOutput } from '../../../common/src/tools/list'
88
9+ // Hidden directories to include in code search by default.
10+ // These are searched in addition to '.' to ensure important config/workflow files are discoverable.
11+ const INCLUDED_HIDDEN_DIRS = [
12+ '.agents', // Codebuff agent definitions
13+ '.claude', // Claude settings
14+ '.github', // GitHub Actions, workflows, issue templates
15+ '.gitlab', // GitLab CI configuration
16+ '.circleci', // CircleCI configuration
17+ '.husky', // Git hooks
18+ ]
19+
920export function codeSearch({
1021 projectPath,
1122 pattern,
@@ -31,9 +42,12 @@ export function codeSearch({
3142 // Guard paths robustly
3243 const projectRoot = path.resolve(projectPath)
3344 const searchCwd = cwd ? path.resolve(projectRoot, cwd) : projectRoot
34-
45+
3546 // Ensure the resolved path is within the project directory
36- if (!searchCwd.startsWith(projectRoot + path.sep) && searchCwd !== projectRoot) {
47+ if (
48+ !searchCwd.startsWith(projectRoot + path.sep) &&
49+ searchCwd !== projectRoot
50+ ) {
3751 return resolve([
3852 {
3953 type: 'json',
@@ -53,7 +67,17 @@ export function codeSearch({
5367 // -n shows line numbers
5468 // --json outputs in JSON format, which streams in and allows us to cut off the output if it grows too long
5569 // "--"" prevents pattern from being misparsed as a flag (e.g., pattern starting with '-')
56- const args = ['--no-config', '-n', '--json', ...flagsArray, '--', pattern, '.']
70+ // Search paths: '.' plus blessed hidden directories (ripgrep ignores non-existent paths)
71+ const searchPaths = ['.', ...INCLUDED_HIDDEN_DIRS]
72+ const args = [
73+ '--no-config',
74+ '-n',
75+ '--json',
76+ ...flagsArray,
77+ '--',
78+ pattern,
79+ ...searchPaths,
80+ ]
5781
5882 const rgPath = getBundledRgPath(import.meta.url)
5983 const childProcess = spawn(rgPath, args, {
@@ -126,7 +150,8 @@ export function codeSearch({
126150 // Parse ripgrep JSON for early stopping
127151 childProcess.stdout.on('data', (chunk: Buffer | string) => {
128152 if (isResolved) return
129- const chunkStr = typeof chunk === 'string' ? chunk : chunk.toString('utf8')
153+ const chunkStr =
154+ typeof chunk === 'string' ? chunk : chunk.toString('utf8')
130155 jsonRemainder += chunkStr
131156
132157 // Split by lines; last line might be partial
@@ -181,7 +206,10 @@ export function codeSearch({
181206 matchesGlobal++
182207
183208 // Check global limit or output size limit
184- if (matchesGlobal >= globalMaxResults || estimatedOutputLen >= maxOutputStringLength) {
209+ if (
210+ matchesGlobal >= globalMaxResults ||
211+ estimatedOutputLen >= maxOutputStringLength
212+ ) {
185213 killedForLimit = true
186214 hardKill()
187215
@@ -199,9 +227,10 @@ export function codeSearch({
199227 '\n\n[Output truncated]'
200228 : formattedOutput
201229
202- const limitReason = matchesGlobal >= globalMaxResults
203- ? `[Global limit of ${globalMaxResults} results reached.]`
204- : '[Output size limit reached.]'
230+ const limitReason =
231+ matchesGlobal >= globalMaxResults
232+ ? `[Global limit of ${globalMaxResults} results reached.]`
233+ : '[Output size limit reached.]'
205234
206235 return settle({
207236 stdout: finalOutput + '\n\n' + limitReason,
@@ -216,7 +245,8 @@ export function codeSearch({
216245
217246 childProcess.stderr.on('data', (chunk: Buffer | string) => {
218247 if (isResolved) return
219- const chunkStr = typeof chunk === 'string' ? chunk : chunk.toString('utf8')
248+ const chunkStr =
249+ typeof chunk === 'string' ? chunk : chunk.toString('utf8')
220250 // Keep stderr bounded during streaming
221251 const limit = Math.floor(maxOutputStringLength / 5)
222252 if (stderrBuf.length < limit) {
@@ -232,13 +262,16 @@ export function codeSearch({
232262 try {
233263 if (jsonRemainder) {
234264 // Ensure we have a trailing newline for split to work correctly
235- const maybeMany = jsonRemainder.endsWith('\n') ? jsonRemainder : jsonRemainder + '\n'
265+ const maybeMany = jsonRemainder.endsWith('\n')
266+ ? jsonRemainder
267+ : jsonRemainder + '\n'
236268 for (const ln of maybeMany.split('\n')) {
237269 if (!ln) continue
238270 try {
239271 const evt = JSON.parse(ln)
240272 if (evt?.type === 'match' || evt?.type === 'context') {
241- const filePath = evt.data.path?.text ?? evt.data.path?.bytes ?? ''
273+ const filePath =
274+ evt.data.path?.text ?? evt.data.path?.bytes ?? ''
242275 const lineNumber = evt.data.line_number ?? 0
243276 const rawText = evt.data.lines?.text ?? ''
244277 const lineText = rawText.replace(/\r?\n$/, '')
@@ -253,7 +286,10 @@ export function codeSearch({
253286 const isMatch = evt.type === 'match'
254287
255288 // Check if we should include this line
256- const shouldInclude = !isMatch || (fileMatchCount < maxResults && matchesGlobal < globalMaxResults)
289+ const shouldInclude =
290+ !isMatch ||
291+ (fileMatchCount < maxResults &&
292+ matchesGlobal < globalMaxResults)
257293
258294 if (shouldInclude) {
259295 fileLines.push(formattedLine)
0 commit comments