Skip to content

feat(output): Add tree-only mode for structure-only output#23

Merged
epilande merged 4 commits intomainfrom
tree-only
Dec 31, 2025
Merged

feat(output): Add tree-only mode for structure-only output#23
epilande merged 4 commits intomainfrom
tree-only

Conversation

@epilande
Copy link
Copy Markdown
Owner

@epilande epilande commented Dec 31, 2025

Overview

Adds a new tree-only mode (-T / --tree-only flag) that outputs only the file structure without file contents. This is useful for sharing project layouts with LLMs when you want to provide context about code organization without consuming tokens on file contents.

Motivation

When working with LLMs, there are scenarios where you want to share your project structure to help the model understand the codebase organization, without needing the actual file contents. This saves significant tokens and provides a quick way to communicate project layout.

Resolves #22

Changes

CLI & Configuration

  • Added -T / --tree-only flags to cmd/grab/main.go
  • Extended model.Config with TreeOnly field
  • Updated runNonInteractive() to support tree-only mode

Generator

  • Added TreeOnly field to Generator struct
  • Added SetTreeOnlyMode() method
  • Added FilePaths field to TemplateData for XML format support
  • Modified PrepareTemplateData() to skip file content collection in tree-only mode

Output Formats

  • Markdown: Added {{if .Files}} conditional to hide "Project Files" section
  • Text: Added {{if .Files}} conditional to hide "PROJECT FILES" section
  • XML: Updated to use FilePaths for building directory structure (works in both modes)

TUI

  • Added T key binding to toggle tree-only mode
  • Added 🌳 indicator in footer when tree-only mode is active
  • Updated help text with new keybinding

Documentation

  • Updated README.md with new flag documentation
  • Added example usage: grab -T -n
  • Added keyboard control documentation for T key

Breaking Changes

None

Testing

  • Added TestSetTreeOnlyMode - verifies mode toggling
  • Added TestPrepareTemplateDataTreeOnly - verifies generator produces structure without file contents
  • Added TestMarkdownFormatTreeOnly - verifies markdown output excludes file section
  • Added TestTxtFormatTreeOnly - verifies text output excludes file section
  • Added TestXMLFormatTreeOnly - verifies XML output has filesystem structure but no file contents
  • All existing tests pass
  • Manual testing: ./grab -T -n produces structure-only output

Summary by CodeRabbit

  • New Features

    • Added Tree-Only mode (-T / --tree-only flag) to output file structure without file contents
    • Added keyboard toggle (T key) for Tree-Only mode in interactive mode
    • Added UI footer indicator showing when Tree-Only mode is active
  • Documentation

    • Updated CLI options documentation with the new -T/--tree-only flag
    • Added usage examples demonstrating Tree-Only mode
  • Tests

    • Added test coverage for Tree-Only mode rendering across multiple output formats

✏️ Tip: You can customize this high-level summary in your review settings.

Add -T/--tree-only flag to output just the file structure without
file contents. This is useful for sharing project layout with LLMs
without overwhelming context with full file contents.

- Add CLI flag with short (-T) and long (--tree-only) forms
- Add T keybinding to toggle tree-only mode in interactive TUI
- Update markdown and text templates to conditionally render files
- Update documentation with new option and example usage
- Add TestSetTreeOnlyMode to verify setter method
- Add TestPrepareTemplateDataTreeOnly to verify empty files with structure
- Add tree-only tests for markdown, text, and XML format renderers
Add FilePaths field to TemplateData to provide file paths independently
of file contents. This allows XML format to build the directory structure
correctly in tree-only mode when Files array is empty.

- Add FilePaths to TemplateData struct
- Populate FilePaths in PrepareTemplateData (always, not just tree-only)
- Update XML format to use FilePaths for directory structure
- Update tests to include and verify FilePaths field
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Dec 31, 2025

📝 Walkthrough

Walkthrough

A new tree-only mode feature has been added that allows users to output only file structure without file contents. The feature is implemented via CLI flag (-T/--tree-only), keyboard toggle (T), and propagated through the generator and UI to conditionally skip file content collection and render only directory trees.

Changes

Cohort / File(s) Summary
CLI & Configuration
README.md, cmd/grab/main.go, internal/ui/help.go
Documents and implements the new -T/--tree-only CLI flag with shorthand -T. The flag is threaded through the application config (TreeOnly field added to Config struct) and propagated to the generator via SetTreeOnlyMode. Help text updated to advertise the new option.
Generator Core & Logic
internal/generator/generator.go, internal/generator/generator_test.go
Introduces SetTreeOnlyMode(bool) method and TreeOnly field on Generator. When enabled, file content collection (collectFiles) is conditionally skipped while FilePaths are always collected for tree rendering. Tests verify default state and tree-only behavior.
Template Data Structure
internal/generator/format.go
Adds FilePaths []string field to TemplateData struct to expose file paths for template rendering in tree-only mode.
Format Templates & Tests
internal/generator/formats/markdown.go, internal/generator/formats/text.go, internal/generator/formats/xml.go, internal/generator/formats/formats_test.go
Markdown and Text templates now conditionally render "Project Files" section only when files exist ({{if .Files}}). XML template uses FilePaths for directory tree construction instead of Files. New tests verify tree-only output across all formats omits file content sections and correctly renders tree structures.
Interactive Mode & UI
internal/model/model.go, internal/model/init_update.go, internal/model/view.go
Adds treeOnly field to Model and TreeOnly field to Config. T key handler toggles tree-only mode and refreshes viewport. Footer displays " | 🌳 Tree" indicator when mode is active. Generator is configured during model initialization with SetTreeOnlyMode(config.TreeOnly).

Sequence Diagram

sequenceDiagram
    participant User
    participant CLI
    participant Config
    participant Generator
    participant Template
    participant Output

    User->>CLI: Invoke with -T flag (or press T key)
    CLI->>Config: Set TreeOnly = true
    Config->>Generator: SetTreeOnlyMode(true)
    Generator->>Generator: Prepare TemplateData:<br/>Collect FilePaths<br/>Skip collectFiles()
    Generator->>Template: Pass TemplateData with FilePaths<br/>Files remains empty
    Template->>Template: Conditional rendering:<br/>{{if .Files}} skipped<br/>Directory tree from FilePaths rendered
    Template->>Output: Render structure only<br/>(no file contents)
    Output->>User: Display tree structure
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A tree stands tall without its leaves,
Just branches bare for all to see—
With -T flag, the structure breathes,
No files cluttering, just the tree! 🌳✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding a tree-only mode feature for structure-only output, which is the primary objective of this PR.
Linked Issues check ✅ Passed The PR fully implements the feature requested in issue #22: enabling users to generate/copy only the file structure without file contents through the new -T/--tree-only CLI flag.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the tree-only mode feature. No unrelated modifications were introduced outside the scope of issue #22.
✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
internal/generator/generator.go (1)

205-208: Non-deterministic order of FilePaths due to map iteration.

Iterating over expandedSelection (a map) produces non-deterministic ordering. While this may not cause functional issues, it can lead to inconsistent output between runs, which could be problematic for diffing or reproducibility.

🔎 Suggested fix to ensure deterministic ordering
 	filePaths := make([]string, 0, len(expandedSelection))
 	for path := range expandedSelection {
 		filePaths = append(filePaths, path)
 	}
+	sort.Strings(filePaths)

You'll need to add "sort" to the imports.

cmd/grab/main.go (1)

221-221: Consider extracting parameters into a struct.

The runNonInteractive function now has 10 parameters, which can be difficult to maintain and prone to argument ordering mistakes. Consider grouping these into a configuration struct similar to model.Config.

🔎 Example refactor
type nonInteractiveConfig struct {
    rootPath      string
    filterMgr     *filesystem.FilterManager
    outputPath    string
    useTempFile   bool
    formatName    string
    skipRedaction bool
    resolveDeps   bool
    maxDepth      int
    maxFileSize   int64
    treeOnly      bool
}

func runNonInteractive(cfg nonInteractiveConfig) {
    // ...
}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1bae135 and e549467.

📒 Files selected for processing (13)
  • README.md
  • cmd/grab/main.go
  • internal/generator/format.go
  • internal/generator/formats/formats_test.go
  • internal/generator/formats/markdown.go
  • internal/generator/formats/text.go
  • internal/generator/formats/xml.go
  • internal/generator/generator.go
  • internal/generator/generator_test.go
  • internal/model/init_update.go
  • internal/model/model.go
  • internal/model/view.go
  • internal/ui/help.go
🧰 Additional context used
🧬 Code graph analysis (3)
internal/model/view.go (1)
internal/ui/styles.go (1)
  • GetStyleInfo (92-98)
internal/generator/formats/formats_test.go (4)
internal/generator/format.go (2)
  • TemplateData (14-18)
  • FileData (6-11)
internal/generator/formats/markdown.go (1)
  • MarkdownFormat (13-13)
internal/generator/formats/text.go (1)
  • TxtFormat (14-14)
internal/generator/formats/xml.go (1)
  • XMLFormat (15-15)
internal/generator/generator_test.go (4)
internal/generator/generator.go (1)
  • NewGenerator (34-54)
internal/cache/manager.go (1)
  • ResetGlobalCache (29-31)
internal/filesystem/gitignore.go (1)
  • NewGitIgnoreManager (25-51)
internal/filesystem/filter.go (1)
  • NewFilterManager (12-16)
🔇 Additional comments (33)
internal/generator/formats/xml.go (1)

64-66: LGTM! Clean separation of structure and content.

The switch from iterating over data.Files to data.FilePaths for tree construction is correct. This enables the XML filesystem structure to be built in both standard mode (when Files contains content) and tree-only mode (when Files is empty but FilePaths is populated). The file content list at lines 79-85 correctly continues to use data.Files.

internal/generator/formats/markdown.go (1)

47-55: LGTM! Conditional rendering correctly implemented.

The addition of {{if .Files}} properly guards the "Project Files" section so it only renders when files are present. The double {{end}}{{end}} correctly closes both the {{range .Files}} loop and the {{if .Files}} conditional. This ensures tree-only mode outputs only the structure section.

internal/generator/format.go (1)

17-17: LGTM! Clean extension of template data.

The addition of the FilePaths field enables templates to access the list of file paths for structure-only rendering. This additive change maintains backward compatibility while supporting the new tree-only mode.

internal/model/view.go (1)

356-359: LGTM! Consistent footer indicator pattern.

The tree-only status indicator follows the established pattern used by the dependency and redaction indicators. The styling with ui.GetStyleInfo() is consistent with other informational footer elements.

README.md (4)

30-30: LGTM! Clear feature documentation.

The tree-only mode feature is well-documented with a clear description of its purpose and use case.


112-112: LGTM! Complete flag documentation.

The CLI flag documentation clearly explains the purpose and benefit of the tree-only mode.


182-186: LGTM! Practical usage example.

The example demonstrates the correct combination of flags (-T -n) for non-interactive tree-only output.


222-222: LGTM! Keyboard control documented.

The keyboard shortcut is clearly documented with an accurate description of its behavior.

internal/ui/help.go (2)

24-24: LGTM! Help text consistent with implementation.

The keyboard shortcut description accurately reflects the toggle behavior and is consistent with other mode toggles.


70-70: LGTM! Usage documentation complete.

The CLI flag usage text is clear and aligns with the README documentation.

internal/generator/formats/text.go (1)

60-70: LGTM! Consistent conditional rendering across formats.

The text format implements the same conditional rendering pattern as Markdown. The {{if .Files}} guard ensures the "PROJECT FILES" section only appears when files are present, and the double {{end}}{{end}} correctly closes both the range and if blocks.

internal/model/init_update.go (1)

474-482: LGTM! Clean toggle implementation.

The "T" key handler follows the established pattern used by other mode toggles (dependency resolution at lines 434-442 and secret redaction at lines 464-473). The implementation correctly:

  • Toggles the state
  • Synchronizes with the generator via SetTreeOnlyMode
  • Provides clear user feedback
  • Refreshes the viewport
internal/generator/generator.go (4)

29-29: LGTM!

The TreeOnly field is appropriately added as an exported boolean, consistent with other mode flags like RedactSecrets.


79-82: LGTM!

The SetTreeOnlyMode method follows the same pattern as SetRedactionMode, providing a clean API for toggling tree-only output.


225-227: LGTM!

The conditional skip of collectFiles when TreeOnly is enabled correctly implements the tree-only behavior while still building the structure tree. This preserves the secret scanning loop below (which will simply iterate over an empty slice).


244-248: LGTM!

FilePaths is correctly included in the returned TemplateData, enabling formats (particularly XML) to build directory structures even when file contents are not collected.

internal/generator/generator_test.go (3)

154-158: LGTM!

Good addition to verify that FilePaths is populated alongside Files in the normal (non-tree-only) mode.


222-238: LGTM!

Thorough test coverage for SetTreeOnlyMode verifying the default value and toggle behavior in both directions.


240-294: LGTM!

Comprehensive test for tree-only mode that validates:

  • Structure is generated (non-empty)
  • Files slice is empty (no content collected)
  • FilePaths is still populated for XML format support

The inline comment on line 290 clarifies the design intent.

internal/model/model.go (4)

73-73: LGTM!

The unexported treeOnly field appropriately encapsulates the tree-only state within the Model.


91-91: LGTM!

The exported TreeOnly field in Config allows external configuration of the tree-only mode.


198-198: LGTM!

Correctly propagates the TreeOnly configuration to the generator during model initialization.


231-231: LGTM!

The model's internal treeOnly state is properly initialized from the config, enabling UI features like the footer indicator.

internal/generator/formats/formats_test.go (5)

156-158: LGTM!

Good update to include FilePaths in the standard test data, ensuring existing tests remain valid with the new field.


160-166: LGTM!

Well-structured helper function that creates tree-only test data with:

  • Non-empty Structure for the tree display
  • Empty Files slice (no content)
  • Populated FilePaths for XML directory building

168-189: LGTM!

Good test coverage for Markdown tree-only mode, verifying the structure header is present while the files section is absent.


191-212: LGTM!

Appropriate test for text format tree-only mode, mirroring the Markdown test structure.


214-246: LGTM!

Thorough XML tree-only test that validates:

  • XML header and project tags are present
  • Directory structure is built from FilePaths (e.g., <directory name="src">)
  • File entries appear in the filesystem section
  • No <file path= content elements are rendered
cmd/grab/main.go (5)

53-53: LGTM!

Variable declaration for the new tree-only flag.


96-98: LGTM!

Clear flag definitions with both long (--tree-only) and short (-T) forms, consistent with CLI conventions.


194-194: LGTM!

The treeOnly flag is correctly passed to the non-interactive execution path.


208-208: LGTM!

The TreeOnly configuration is properly set for the interactive (TUI) mode.


309-309: LGTM!

Correctly enables tree-only mode on the generator in the non-interactive path.

@epilande epilande merged commit b44b8f5 into main Dec 31, 2025
1 check passed
@epilande epilande deleted the tree-only branch December 31, 2025 11:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: ability to only copy/generate the file structure

1 participant