@@ -159,6 +159,7 @@ func main() {
159159 jsonMode := flag .Bool ("json" , false , "Output JSON (for Python renderer compatibility)" )
160160 debugMode := flag .Bool ("debug" , false , "Show debug info (gitignore loading, paths, etc.)" )
161161 watchMode := flag .Bool ("watch" , false , "Live file watcher daemon (experimental)" )
162+ stdinMode := flag .Bool ("stdin" , false , "Read file manifest from stdin (use with --deps)" )
162163 importersMode := flag .String ("importers" , "" , "Check file impact: who imports it, is it a hub?" )
163164 helpMode := flag .Bool ("help" , false , "Show help" )
164165 // Short flag aliases
@@ -180,6 +181,7 @@ func main() {
180181 fmt .Println (" --depth, -d <n> Limit tree depth (0 = unlimited)" )
181182 fmt .Println (" --only <exts> Only show files with these extensions (e.g., 'swift,go')" )
182183 fmt .Println (" --exclude <patterns> Exclude paths matching patterns (e.g., '.xcassets,Fonts')" )
184+ fmt .Println (" --stdin Read JSON file manifest from stdin (use with --deps)" )
183185 fmt .Println (" --importers <file> Check file impact (who imports it, hub status)" )
184186 fmt .Println ()
185187 fmt .Println ("Examples:" )
@@ -193,6 +195,7 @@ func main() {
193195 fmt .Println (" codemap --only swift . # Just Swift files" )
194196 fmt .Println (" codemap --exclude .xcassets,Fonts,.png # Hide assets" )
195197 fmt .Println (" codemap --importers scanner/types.go # Check file impact" )
198+ fmt .Println (" echo '{...}' | codemap --deps --stdin # Deps from file manifest" )
196199 fmt .Println ()
197200 fmt .Println ("Remote repos (clones temporarily):" )
198201 fmt .Println (" codemap github.com/user/repo # GitHub repo" )
@@ -328,7 +331,7 @@ func main() {
328331 if diffInfo != nil {
329332 changedFiles = diffInfo .Changed
330333 }
331- runDepsMode (absRoot , root , * jsonMode , * diffRef , changedFiles )
334+ runDepsMode (absRoot , root , * jsonMode , * diffRef , changedFiles , * stdinMode )
332335 return
333336 }
334337
@@ -377,17 +380,43 @@ func main() {
377380 }
378381}
379382
380- func runDepsMode (absRoot , root string , jsonMode bool , diffRef string , changedFiles map [string ]bool ) {
381- analyses , err := scanner .ScanForDeps (root )
382- if err != nil {
383- fmt .Fprintf (os .Stderr , "Error: %v\n " , err )
384- fmt .Fprintln (os .Stderr , "" )
385- fmt .Fprintln (os .Stderr , "The --deps feature requires ast-grep. Install it with:" )
386- fmt .Fprintln (os .Stderr , " brew install ast-grep # macOS/Linux (installs as 'sg')" )
387- fmt .Fprintln (os .Stderr , " cargo install ast-grep # via Rust (installs as 'ast-grep')" )
388- fmt .Fprintln (os .Stderr , " pipx install ast-grep # via Python (installs as 'ast-grep')" )
389- fmt .Fprintln (os .Stderr , "" )
390- os .Exit (1 )
383+ // stdinManifest is the JSON format accepted by --stdin.
384+ type stdinManifest struct {
385+ Root string `json:"root"`
386+ Files []struct {
387+ Path string `json:"path"`
388+ Content string `json:"content"`
389+ } `json:"files"`
390+ }
391+
392+ func runDepsMode (absRoot , root string , jsonMode bool , diffRef string , changedFiles map [string ]bool , stdinMode bool ) {
393+ var analyses []FileAnalysis
394+ var externalDeps map [string ][]string
395+ var err error
396+
397+ if stdinMode {
398+ analyses , externalDeps , err = runDepsFromStdin ()
399+ if err != nil {
400+ fmt .Fprintf (os .Stderr , "Error reading stdin manifest: %v\n " , err )
401+ os .Exit (1 )
402+ }
403+ // Use the manifest root as absRoot if provided
404+ if externalDeps == nil {
405+ externalDeps = make (map [string ][]string )
406+ }
407+ } else {
408+ analyses , err = scanForDepsWithHint (root )
409+ if err != nil {
410+ fmt .Fprintf (os .Stderr , "Error: %v\n " , err )
411+ fmt .Fprintln (os .Stderr , "" )
412+ fmt .Fprintln (os .Stderr , "The --deps feature requires ast-grep. Install it with:" )
413+ fmt .Fprintln (os .Stderr , " brew install ast-grep # macOS/Linux (installs as 'sg')" )
414+ fmt .Fprintln (os .Stderr , " cargo install ast-grep # via Rust (installs as 'ast-grep')" )
415+ fmt .Fprintln (os .Stderr , " pipx install ast-grep # via Python (installs as 'ast-grep')" )
416+ fmt .Fprintln (os .Stderr , "" )
417+ os .Exit (1 )
418+ }
419+ externalDeps = scanner .ReadExternalDeps (absRoot )
391420 }
392421
393422 // Filter to changed files if --diff specified
@@ -399,7 +428,7 @@ func runDepsMode(absRoot, root string, jsonMode bool, diffRef string, changedFil
399428 Root : absRoot ,
400429 Mode : "deps" ,
401430 Files : analyses ,
402- ExternalDeps : scanner . ReadExternalDeps ( absRoot ) ,
431+ ExternalDeps : externalDeps ,
403432 DiffRef : diffRef ,
404433 }
405434
@@ -411,6 +440,56 @@ func runDepsMode(absRoot, root string, jsonMode bool, diffRef string, changedFil
411440 }
412441}
413442
443+ // scanForDepsWithHint wraps scanner.ScanForDeps (extracted for testability).
444+ func scanForDepsWithHint (root string ) ([]FileAnalysis , error ) {
445+ return scanner .ScanForDeps (root )
446+ }
447+
448+ // runDepsFromStdin reads a JSON manifest from stdin, writes files to a temp
449+ // directory, runs ast-grep on it, and returns the results with paths matching
450+ // the original manifest.
451+ func runDepsFromStdin () ([]FileAnalysis , map [string ][]string , error ) {
452+ var manifest stdinManifest
453+ if err := json .NewDecoder (os .Stdin ).Decode (& manifest ); err != nil {
454+ return nil , nil , fmt .Errorf ("invalid JSON: %w" , err )
455+ }
456+
457+ if len (manifest .Files ) == 0 {
458+ return nil , nil , nil
459+ }
460+
461+ // Create temp directory and write manifest files
462+ tempDir , err := os .MkdirTemp ("" , "codemap-stdin-*" )
463+ if err != nil {
464+ return nil , nil , fmt .Errorf ("failed to create temp dir: %w" , err )
465+ }
466+ defer os .RemoveAll (tempDir )
467+
468+ for _ , f := range manifest .Files {
469+ dest := filepath .Join (tempDir , f .Path )
470+ if err := os .MkdirAll (filepath .Dir (dest ), 0755 ); err != nil {
471+ return nil , nil , fmt .Errorf ("mkdir %s: %w" , filepath .Dir (dest ), err )
472+ }
473+ if err := os .WriteFile (dest , []byte (f .Content ), 0644 ); err != nil {
474+ return nil , nil , fmt .Errorf ("write %s: %w" , f .Path , err )
475+ }
476+ }
477+
478+ // Run ast-grep on temp directory
479+ analyses , err := scanner .ScanForDeps (tempDir )
480+ if err != nil {
481+ return nil , nil , err
482+ }
483+
484+ // Read external deps from temp directory (manifest may include go.mod etc.)
485+ externalDeps := scanner .ReadExternalDeps (tempDir )
486+
487+ return analyses , externalDeps , nil
488+ }
489+
490+ // FileAnalysis is a type alias for use in main package.
491+ type FileAnalysis = scanner.FileAnalysis
492+
414493func runWatchMode (root string , verbose bool ) {
415494 fmt .Println ("codemap watch - Live code graph daemon" )
416495 fmt .Println ()
0 commit comments