@@ -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