@@ -296,6 +296,145 @@ export function buildSpecialFieldsPlain(groups: SpecialFieldGroup[]): string[] {
296296 return lines ;
297297}
298298
299+ // ---------------------------------------------------------------------------
300+ // Search-specific CLI examples for generated docs
301+ // ---------------------------------------------------------------------------
302+
303+ export interface SearchExample {
304+ description : string ;
305+ code : string [ ] ;
306+ }
307+
308+ /**
309+ * Build concrete, field-specific CLI examples for tables with search fields.
310+ * Uses the same field-name derivation logic as buildSearchHandler in
311+ * table-command-generator.ts so the examples match the actual generated code.
312+ *
313+ * Returns an empty array when the table has no search/embedding fields.
314+ */
315+ export function buildSearchExamples (
316+ specialGroups : SpecialFieldGroup [ ] ,
317+ toolName : string ,
318+ cmd : string ,
319+ ) : SearchExample [ ] {
320+ const examples : SearchExample [ ] = [ ] ;
321+ const scoreFields : string [ ] = [ ] ;
322+
323+ for ( const group of specialGroups ) {
324+ for ( const field of group . fields ) {
325+ // tsvector (FullText scalar) — where input uses the column name directly
326+ if ( field . type . gqlType === 'FullText' && ! field . type . isArray ) {
327+ examples . push ( {
328+ description : `Full-text search via tsvector (\`${ field . name } \`)` ,
329+ code : [
330+ `${ toolName } ${ cmd } list --where.${ field . name } "search query" --fields title,tsvRank` ,
331+ ] ,
332+ } ) ;
333+ scoreFields . push ( 'tsvRank' ) ;
334+ }
335+
336+ // BM25 computed score — bodyBm25Score → bm25Body
337+ if ( / B m 2 5 S c o r e $ / . test ( field . name ) ) {
338+ const baseName = field . name . replace ( / B m 2 5 S c o r e $ / , '' ) ;
339+ const inputName = `bm25${ baseName . charAt ( 0 ) . toUpperCase ( ) } ${ baseName . slice ( 1 ) } ` ;
340+ examples . push ( {
341+ description : `BM25 keyword search via \`${ inputName } \`` ,
342+ code : [
343+ `${ toolName } ${ cmd } list --where.${ inputName } .query "search query" --fields title,${ field . name } ` ,
344+ ] ,
345+ } ) ;
346+ scoreFields . push ( field . name ) ;
347+ }
348+
349+ // Trigram similarity — titleTrgmSimilarity → trgmTitle
350+ if ( / T r g m S i m i l a r i t y $ / . test ( field . name ) ) {
351+ const baseName = field . name . replace ( / T r g m S i m i l a r i t y $ / , '' ) ;
352+ const inputName = `trgm${ baseName . charAt ( 0 ) . toUpperCase ( ) } ${ baseName . slice ( 1 ) } ` ;
353+ examples . push ( {
354+ description : `Fuzzy search via trigram similarity (\`${ inputName } \`)` ,
355+ code : [
356+ `${ toolName } ${ cmd } list --where.${ inputName } .value "approximate query" --where.${ inputName } .threshold 0.3 --fields title,${ field . name } ` ,
357+ ] ,
358+ } ) ;
359+ scoreFields . push ( field . name ) ;
360+ }
361+
362+ // pgvector embedding — uses column name, note about CLI limitation
363+ if ( group . category === 'embedding' ) {
364+ examples . push ( {
365+ description : `Vector similarity search via \`${ field . name } \` (requires JSON array)` ,
366+ code : [
367+ `# Note: vector arrays must be passed as JSON strings via dot-notation` ,
368+ `${ toolName } ${ cmd } list --where.${ field . name } .vector '[0.1,0.2,0.3]' --where.${ field . name } .distance 1.0 --fields title,${ field . name } VectorDistance` ,
369+ ] ,
370+ } ) ;
371+ }
372+
373+ // searchScore — composite blend field, useful for ordering
374+ if ( field . name === 'searchScore' ) {
375+ scoreFields . push ( 'searchScore' ) ;
376+ }
377+ }
378+ }
379+
380+ // Composite fullTextSearch example (dispatches to all text adapters)
381+ const hasTextSearch = specialGroups . some (
382+ ( g ) => g . category === 'search' && g . fields . some (
383+ ( f ) => f . type . gqlType === 'FullText' || / T r g m S i m i l a r i t y $ / . test ( f . name ) || / B m 2 5 S c o r e $ / . test ( f . name ) ,
384+ ) ,
385+ ) ;
386+ if ( hasTextSearch ) {
387+ const fieldsArg = scoreFields . length > 0
388+ ? `title,${ [ ...new Set ( scoreFields ) ] . join ( ',' ) } `
389+ : 'title' ;
390+ examples . push ( {
391+ description : 'Composite search (fullTextSearch dispatches to all text adapters)' ,
392+ code : [
393+ `${ toolName } ${ cmd } list --where.fullTextSearch "search query" --fields ${ fieldsArg } ` ,
394+ ] ,
395+ } ) ;
396+ }
397+
398+ // Combined search + pagination + ordering example
399+ if ( examples . length > 0 ) {
400+ examples . push ( {
401+ description : 'Search with pagination and field projection' ,
402+ code : [
403+ `${ toolName } ${ cmd } list --where.fullTextSearch "query" --limit 10 --fields id,title,searchScore` ,
404+ `${ toolName } ${ cmd } search "query" --limit 10 --fields id,title,searchScore` ,
405+ ] ,
406+ } ) ;
407+ }
408+
409+ return examples ;
410+ }
411+
412+ /**
413+ * Build markdown lines for search-specific examples in README-style docs.
414+ * Returns empty array when there are no search examples.
415+ */
416+ export function buildSearchExamplesMarkdown (
417+ specialGroups : SpecialFieldGroup [ ] ,
418+ toolName : string ,
419+ cmd : string ,
420+ ) : string [ ] {
421+ const examples = buildSearchExamples ( specialGroups , toolName , cmd ) ;
422+ if ( examples . length === 0 ) return [ ] ;
423+ const lines : string [ ] = [ ] ;
424+ lines . push ( '**Search Examples:**' ) ;
425+ lines . push ( '' ) ;
426+ for ( const ex of examples ) {
427+ lines . push ( `*${ ex . description } :*` ) ;
428+ lines . push ( '```bash' ) ;
429+ for ( const c of ex . code ) {
430+ lines . push ( c ) ;
431+ }
432+ lines . push ( '```' ) ;
433+ lines . push ( '' ) ;
434+ }
435+ return lines ;
436+ }
437+
299438/**
300439 * Represents a flattened argument for docs/skills generation.
301440 * INPUT_OBJECT args are expanded to dot-notation fields.
0 commit comments