@@ -187,6 +187,8 @@ pub fn runCellCommand(allocator: Allocator, args: []const []const u8) !void {
187187 if (std .mem .eql (u8 , sub , "version" )) return runVersion (allocator , rest );
188188 if (std .mem .eql (u8 , sub , "outdated" )) return runOutdated (allocator , rest );
189189 if (std .mem .eql (u8 , sub , "regenerate" )) return runRegenerate (allocator , rest );
190+ if (std .mem .eql (u8 , sub , "search" )) return runSearch (allocator , rest );
191+ if (std .mem .eql (u8 , sub , "find" )) return runFind (allocator , rest );
190192
191193 printHelp ();
192194}
@@ -250,6 +252,9 @@ fn printHelp() void {
250252 std .debug .print (" {s}version{s} Show cell versions and content hashes\n " , .{ GREEN , RESET });
251253 std .debug .print (" {s}outdated{s} List cells with modified content (needs regen)\n " , .{ GREEN , RESET });
252254 std .debug .print (" {s}regenerate --outdated{s} Regenerate all outdated cells\n " , .{ GREEN , RESET });
255+ std .debug .print (" {s}search <query>{s} Fuzzy search by name/id/description\n " , .{ GREEN , RESET });
256+ std .debug .print (" {s}find --capability X{s} Find cells with specific capability\n " , .{ GREEN , RESET });
257+ std .debug .print (" {s}list --tag X:Y{s} Filter by tags (scope:brain, type:library)\n " , .{ GREEN , RESET });
253258}
254259
255260// ═══════════════════════════════════════════════════════════════════════════════
@@ -277,6 +282,7 @@ fn runList(allocator: Allocator, args: []const []const u8) !void {
277282 var owner_filter : ? []const u8 = null ;
278283 var scope_filter : ? []const u8 = null ;
279284 var type_filter : ? []const u8 = null ;
285+ var tag_filter : ? []const u8 = null ;
280286 var show_commands = false ;
281287 var show_health = false ;
282288 var show_group = false ;
@@ -291,6 +297,9 @@ fn runList(allocator: Allocator, args: []const []const u8) !void {
291297 } else if (std .mem .eql (u8 , args [i ], "--type" ) and i + 1 < args .len ) {
292298 type_filter = args [i + 1 ];
293299 i += 1 ;
300+ } else if (std .mem .eql (u8 , args [i ], "--tag" ) and i + 1 < args .len ) {
301+ tag_filter = args [i + 1 ];
302+ i += 1 ;
294303 } else if (std .mem .eql (u8 , args [i ], "--commands" )) {
295304 show_commands = true ;
296305 } else if (std .mem .eql (u8 , args [i ], "--health" )) {
@@ -300,6 +309,20 @@ fn runList(allocator: Allocator, args: []const []const u8) !void {
300309 }
301310 }
302311
312+ // Process --tag filter (format: "key:value" or "*:value")
313+ if (tag_filter ) | tf | {
314+ if (std .mem .indexOf (u8 , tf , ":" )) | colon_idx | {
315+ const key = tf [0.. colon_idx ];
316+ const value = tf [colon_idx + 1 .. ];
317+ if (std .mem .eql (u8 , key , "scope" )) {
318+ scope_filter = value ;
319+ } else if (std .mem .eql (u8 , key , "type" )) {
320+ type_filter = value ;
321+ }
322+ // Could support more tag keys in future
323+ }
324+ }
325+
303326 std .debug .print ("\n {s}🐝 TRINITY HONEYCOMB — {d} cells{s}\n " , .{ GOLDEN , items .len , RESET });
304327 std .debug .print ("{s}Core version: {s}{s}\n\n " , .{ GRAY , CORE_VERSION , RESET });
305328
@@ -474,6 +497,237 @@ fn runList(allocator: Allocator, args: []const []const u8) !void {
474497 std .debug .print (" | Files: {d} | Tests: {d}\n\n " , .{ total_files , total_tests });
475498}
476499
500+ // ═══════════════════════════════════════════════════════════════════════════════
501+ // SEARCH — fuzzy search by name/id/description
502+ // ═══════════════════════════════════════════════════════════════════════════════
503+
504+ fn runSearch (allocator : Allocator , args : []const []const u8 ) ! void {
505+ if (args .len == 0 ) {
506+ std .debug .print ("{s}Usage:{s} tri cell search <query>\n " , .{ YELLOW , RESET });
507+ std .debug .print (" Example: tri cell search faculty\n " , .{});
508+ std .debug .print (" Searches: cell ID, name, description\n\n " , .{});
509+ return ;
510+ }
511+
512+ const query = args [0 ];
513+ const query_lower = try allocator .dupe (u8 , query );
514+ defer allocator .free (query_lower );
515+
516+ // Convert to lowercase for case-insensitive search
517+ for (query_lower , 0.. ) | c , i | {
518+ query_lower [i ] = toLower (c );
519+ }
520+
521+ const registry = try loadRegistry (allocator );
522+ defer allocator .free (registry );
523+
524+ const parsed = std .json .parseFromSlice (std .json .Value , allocator , registry , .{}) catch {
525+ std .debug .print ("{s}ERROR{s}: Failed to parse registry\n " , .{ RED , RESET });
526+ return ;
527+ };
528+ defer parsed .deinit ();
529+
530+ const cells = (parsed .value .object .get ("cells" ) orelse return ).array .items ;
531+
532+ std .debug .print ("\n {s}🔍 SEARCH: \" {s}\" {s}\n\n " , .{ CYAN , query , RESET });
533+
534+ var match_count : usize = 0 ;
535+
536+ for (cells ) | item | {
537+ const obj = item .object ;
538+ const id = jsonStr (obj , "id" );
539+ const path = jsonStr (obj , "path" );
540+
541+ // Load cell.tri for name/description
542+ const cell_tri_path = std .fmt .allocPrint (allocator , "{s}/cell.tri" , .{path }) catch continue ;
543+ defer allocator .free (cell_tri_path );
544+
545+ const cell_content = std .fs .cwd ().readFileAlloc (allocator , cell_tri_path , 65536 ) catch continue ;
546+ defer allocator .free (cell_content );
547+
548+ const cell = parseCellTri (cell_content );
549+
550+ // Check fuzzy match in id, name, description
551+ const id_lower = try allocLower (allocator , id );
552+ defer allocator .free (id_lower );
553+ const name_lower = try allocLower (allocator , cell .name );
554+ defer allocator .free (name_lower );
555+ const desc_lower = try allocLower (allocator , cell .description );
556+ defer allocator .free (desc_lower );
557+
558+ const matches_id = std .mem .indexOf (u8 , id_lower , query_lower ) != null ;
559+ const matches_name = std .mem .indexOf (u8 , name_lower , query_lower ) != null ;
560+ const matches_desc = std .mem .indexOf (u8 , desc_lower , query_lower ) != null ;
561+
562+ if (! matches_id and ! matches_name and ! matches_desc ) continue ;
563+
564+ match_count += 1 ;
565+ const health = computeHealthScore (obj );
566+ const health_color = if (health >= 80 ) GREEN else if (health >= 50 ) YELLOW else RED ;
567+ const status = jsonStr (obj , "status" );
568+ const status_color = if (std .mem .eql (u8 , status , "stable" )) GREEN else YELLOW ;
569+
570+ // Match indicator
571+ std .debug .print (" {s}{s}{s} " , .{ WHITE , cell .id , RESET });
572+ if (matches_id ) std .debug .print ("{s}[id]{s} " , .{ GREEN , RESET });
573+ if (matches_name ) std .debug .print ("{s}[name]{s} " , .{ CYAN , RESET });
574+ if (matches_desc ) std .debug .print ("{s}[desc]{s} " , .{ GRAY , RESET });
575+ std .debug .print ("\n " , .{});
576+
577+ std .debug .print (" Name: {s}{s}{s}\n " , .{ WHITE , cell .name , RESET });
578+ if (cell .description .len > 0 ) {
579+ std .debug .print (" Desc: {s}{s}{s}\n " , .{ GRAY , cell .description , RESET });
580+ }
581+ std .debug .print (" Health: {s}{d}%{s} | Status: {s}{s}{s}\n " , .{
582+ health_color , health , RESET , status_color , status , RESET ,
583+ });
584+ std .debug .print ("\n " , .{});
585+ }
586+
587+ if (match_count == 0 ) {
588+ std .debug .print (" {s}No matches found for \" {s}\" {s}\n\n " , .{ GRAY , query , RESET });
589+ } else {
590+ std .debug .print (" {s}Found {d} cell(s){s}\n\n " , .{ GREEN , match_count , RESET });
591+ }
592+ }
593+
594+ // ═══════════════════════════════════════════════════════════════════════════════
595+ // FIND — filter by capability (commands, exports, etc.)
596+ // ═══════════════════════════════════════════════════════════════════════════════
597+
598+ fn runFind (allocator : Allocator , args : []const []const u8 ) ! void {
599+ // Parse flags
600+ var capability_filter : ? []const u8 = null ;
601+ var export_filter : ? []const u8 = null ;
602+ var command_filter : ? []const u8 = null ;
603+ var i : usize = 0 ;
604+ while (i < args .len ) : (i += 1 ) {
605+ if (std .mem .eql (u8 , args [i ], "--capability" ) and i + 1 < args .len ) {
606+ capability_filter = args [i + 1 ];
607+ i += 1 ;
608+ } else if (std .mem .eql (u8 , args [i ], "--export" ) and i + 1 < args .len ) {
609+ export_filter = args [i + 1 ];
610+ i += 1 ;
611+ } else if (std .mem .eql (u8 , args [i ], "--command" ) and i + 1 < args .len ) {
612+ command_filter = args [i + 1 ];
613+ i += 1 ;
614+ }
615+ }
616+
617+ if (capability_filter == null and export_filter == null and command_filter == null ) {
618+ std .debug .print ("{s}Usage:{s} tri cell find --capability <name>\n " , .{ YELLOW , RESET });
619+ std .debug .print (" tri cell find --command <name>\n " , .{});
620+ std .debug .print (" tri cell find --export <name>\n\n " , .{});
621+ std .debug .print (" Examples:\n " , .{});
622+ std .debug .print (" tri cell find --capability pipeline\n " , .{});
623+ std .debug .print (" tri cell find --command build\n " , .{});
624+ std .debug .print (" tri cell find --export runIdempotencyCommand\n\n " , .{});
625+ return ;
626+ }
627+
628+ const registry = try loadRegistry (allocator );
629+ defer allocator .free (registry );
630+
631+ const parsed = std .json .parseFromSlice (std .json .Value , allocator , registry , .{}) catch {
632+ std .debug .print ("{s}ERROR{s}: Failed to parse registry\n " , .{ RED , RESET });
633+ return ;
634+ };
635+ defer parsed .deinit ();
636+
637+ const cells = (parsed .value .object .get ("cells" ) orelse return ).array .items ;
638+
639+ std .debug .print ("\n {s}🎯 FIND CELLS BY CAPABILITY{s}\n\n " , .{ CYAN , RESET });
640+
641+ var match_count : usize = 0 ;
642+
643+ for (cells ) | item | {
644+ const obj = item .object ;
645+ const path = jsonStr (obj , "path" );
646+
647+ // Load cell.tri for contributes
648+ const cell_tri_path = std .fmt .allocPrint (allocator , "{s}/cell.tri" , .{path }) catch continue ;
649+ defer allocator .free (cell_tri_path );
650+
651+ const cell_content = std .fs .cwd ().readFileAlloc (allocator , cell_tri_path , 65536 ) catch continue ;
652+ defer allocator .free (cell_content );
653+
654+ const cell = parseCellTri (cell_content );
655+
656+ var matches = false ;
657+ var match_details : []const u8 = "" ;
658+
659+ // Check --capability filter (searches in capabilities array)
660+ if (capability_filter != null ) {
661+ const cap = capability_filter .? ;
662+ const cap_lower = try allocLower (allocator , cap );
663+ defer allocator .free (cap_lower );
664+ const caps_lower = try allocLower (allocator , cell .capabilities );
665+ defer allocator .free (caps_lower );
666+
667+ if (std .mem .indexOf (u8 , caps_lower , cap_lower ) != null ) {
668+ matches = true ;
669+ match_details = "capability" ;
670+ }
671+ }
672+
673+ // Check --command filter (searches in contributes.commands)
674+ if (! matches and command_filter != null ) {
675+ const cmd = command_filter .? ;
676+ var cmd_iter = cell_parser .ArrayIterator .init (cell .contributes_commands );
677+ while (cmd_iter .next ()) | command | {
678+ if (std .mem .indexOf (u8 , command , cmd ) != null ) {
679+ matches = true ;
680+ match_details = "command" ;
681+ break ;
682+ }
683+ }
684+ }
685+
686+ // Check --export filter (searches in contributes.exports)
687+ if (! matches and export_filter != null ) {
688+ const exp = export_filter .? ;
689+ var exp_iter = cell_parser .ArrayIterator .init (cell .contributes_exports );
690+ while (exp_iter .next ()) | export_name | {
691+ if (std .mem .indexOf (u8 , export_name , exp ) != null ) {
692+ matches = true ;
693+ match_details = "export" ;
694+ break ;
695+ }
696+ }
697+ }
698+
699+ if (! matches ) continue ;
700+
701+ match_count += 1 ;
702+ const health = computeHealthScore (obj );
703+ const health_color = if (health >= 80 ) GREEN else if (health >= 50 ) YELLOW else RED ;
704+
705+ std .debug .print (" {s}{s}{s} " , .{ WHITE , cell .id , RESET });
706+ std .debug .print ("{s}({s}){s}\n " , .{ GRAY , match_details , RESET });
707+ std .debug .print (" Name: {s}\n " , .{cell .name });
708+
709+ // Show matching details
710+ if (command_filter != null and cell .contributes_commands .len > 0 ) {
711+ std .debug .print (" Commands: {s}\n " , .{cell .contributes_commands });
712+ }
713+ if (export_filter != null and cell .contributes_exports .len > 0 ) {
714+ std .debug .print (" Exports: {s}\n " , .{cell .contributes_exports });
715+ }
716+ if (capability_filter != null and cell .capabilities .len > 0 ) {
717+ std .debug .print (" Capabilities: {s}\n " , .{cell .capabilities });
718+ }
719+
720+ std .debug .print (" Health: {s}{d}%{s}\n " , .{ health_color , health , RESET });
721+ std .debug .print ("\n " , .{});
722+ }
723+
724+ if (match_count == 0 ) {
725+ std .debug .print (" {s}No cells found with specified capability{s}\n\n " , .{ GRAY , RESET });
726+ } else {
727+ std .debug .print (" {s}Found {d} cell(s){s}\n\n " , .{ GREEN , match_count , RESET });
728+ }
729+ }
730+
477731// ═══════════════════════════════════════════════════════════════════════════════
478732// INFO — detailed view of a single cell
479733// ═══════════════════════════════════════════════════════════════════════════════
@@ -7672,6 +7926,22 @@ fn findCellVersion(cells: []const std.json.Value, cell_id: []const u8) ?Version
76727926 return null ;
76737927}
76747928
7929+ // ═══════════════════════════════════════════════════════════════════════════════
7930+ // CASE-INSENSITIVE SEARCH HELPERS
7931+ // ═══════════════════════════════════════════════════════════════════════════════
7932+
7933+ fn toLower (c : u8 ) u8 {
7934+ return if (c >= 'A' and c <= 'Z' ) c + 32 else c ;
7935+ }
7936+
7937+ fn allocLower (allocator : Allocator , s : []const u8 ) ! []u8 {
7938+ const result = try allocator .alloc (u8 , s .len );
7939+ for (s , 0.. ) | c , i | {
7940+ result [i ] = toLower (c );
7941+ }
7942+ return result ;
7943+ }
7944+
76757945// ═══════════════════════════════════════════════════════════════════════════════
76767946// FILESYSTEM DISCOVERY — walk directories for cell.tri
76777947// ═══════════════════════════════════════════════════════════════════════════════
0 commit comments