Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ src/tree2guide/renderers/__pycache__
*.egg-info/
*_tree.md
.venv-test
dist
dist
.ignoredfiles
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,9 +216,13 @@ tree2guide <target_folder> [options]
| `--files-only` | Only show files (directories shown as scaffolding only) |
| `--no-hidden` | Skip dotfiles/dotfolders without needing `.tree2ignore` entries |
| `--sort {dirs-first,files-first,alpha}` | Sort order within each folder (default: `dirs-first`) |
| `--no-progress` | Suppress scan progress and completion telemetry (stderr only) on large directories |
| `--version` | Print the installed version and exit |

Symlinks are rendered as `name -> target` and never followed — avoids infinite loops.

On large directories, tree2guide prints periodic progress (`Scanning... N files, N dirs`) and a final `Scan complete.` summary to **stderr** — never stdout — so piping (`--stdout | your-tool`) is unaffected. Fast scans print nothing extra. Use `--no-progress` to suppress this entirely.

### Default output filenames

| Format | Output file |
Expand Down Expand Up @@ -328,7 +332,7 @@ print(summary.notable_flags)

| Name | Description |
|---|---|
| `build_node_tree(root, matcher, options=None)` | Scan filesystem → `TreeNode` |
| `build_node_tree(root, matcher, options=None, on_progress=None)` | Scan filesystem → `TreeNode`. `on_progress(files, dirs)`, if given, is called at most once per second during the walk — pure instrumentation, `None` by default with zero effect |
| `build_tree(root, matcher, options=None)` | Backward-compatible wrapper → `list[str]` |
| `TreeNode` | Internal tree model (dataclass) |
| `TreeOptions` | Scanner options (max_depth, dirs_only, sort, etc.) |
Expand Down
17 changes: 15 additions & 2 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import tree2guide

## Scanner

### `build_node_tree(root, matcher, options=None) -> TreeNode`
### `build_node_tree(root, matcher, options=None, on_progress=None) -> TreeNode`

The primary scanner function. Walks the filesystem once and returns a `TreeNode` tree. All renderers consume this.

Expand All @@ -26,6 +26,19 @@ options = tree2guide.TreeOptions(max_depth=3, no_hidden=True)
node = tree2guide.build_node_tree(root, matcher, options)
```

`on_progress`, if given, is a `Callable[[int, int], None]` called with
`(files_visited, dirs_visited)` at most once per second of wall-clock time
during the walk. It is pure instrumentation — the scanner does not know or
care who's listening — and `None` (the default) has zero effect on the
returned tree or on any existing caller:

```python
def report(files: int, dirs: int) -> None:
print(f"...{files} files, {dirs} dirs so far", file=sys.stderr)

node = tree2guide.build_node_tree(root, matcher, options, on_progress=report)
```

### `build_tree(root, matcher, options=None) -> list[str]`

Backward-compatible wrapper. Returns the same connector-prefixed display lines the old API produced. Internally calls `build_node_tree()` and `render_lines()`.
Expand Down Expand Up @@ -177,5 +190,5 @@ class LlmSummary:
## Version

```python
tree2guide.__version__ # "1.1.0"
tree2guide.__version__ # "1.2.0"
```
48 changes: 47 additions & 1 deletion docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,50 @@ Versioning follows [Semantic Versioning](https://semver.org/).

### Fixed
- PHP/CakePHP projects no longer misidentified as Python when a
stray requirements.txt exists deep in the codebase
stray requirements.txt exists deep in the codebase

---

## [1.2.0] — 2026-07-01

### Added
- `--version` flag — `tree2guide --version` now prints the version instead
of crashing with a missing-argument error
- AI tooling notable flags — detects `.claude/`, `.cursor/`, `.windsurf/`,
`.roo/`, `.specify/`, `.roomodes`, `.maestro/`, `.githooks/` in addition
to the previously-existing `CLAUDE.md`, `AGENTS.md`, `.cursorrules`
- Generic `PHP MVC structure` stack signal — `Controller/`, `Model/`,
`View/` are recognized as a weak, generic MVC-framework signal without
being misattributed to CakePHP specifically, since these directory names
are shared by CodeIgniter, Yii, and other PHP MVC frameworks
- Scan progress and completion telemetry for large directories — periodic
`Scanning... N files, N dirs` and a final `Scan complete.` summary,
printed to stderr only (never stdout, so `--stdout` piping is
unaffected). Gated on elapsed wall-clock time, not entry count, so fast
scans of small projects print nothing extra. `--no-progress` suppresses
this entirely. `build_node_tree()` gained an optional `on_progress`
callback parameter (`None` by default, zero effect on existing callers)
- Number formatting — file and directory counts in `--llm` output now use
comma separators for readability on large projects (`227886` → `227,886`)

### Fixed
- `_STACK_SIGNALS` patterns containing a path separator (e.g. `bin/cake`,
`config/app.php`, `bootstrap/app.php`, `bin/console`, `config/routes.rb`,
`src/main/java`, and 9 others) could never match anything, because stack
detection only ever compared against bare file/directory names, never
relative paths. These are now matched against each entry's actual
relative path, restoring the CakePHP, Laravel, Symfony, Ruby on Rails,
Java/Kotlin, and Flutter signals that depend on them — several of which
are the highest-confidence (weight 5) signal for their framework
- `cli.py` used `str | None` / `list[str] | None` type-hint syntax without
`from __future__ import annotations`, which would raise `TypeError` on
Python 3.9 the moment the module was imported. No test previously
imported `tree2guide.cli`, so this went undetected by CI despite the 3.9
job reporting green; new CLI tests now cover this module directly
- Scan progress counts (added in this release) included symlinks, while
the final completion summary and `--llm`'s SIZE section both excluded
them — the same long-standing convention used since v1.0.0. On a
project with many symlinks (e.g. Python venvs, `node_modules/.bin`),
the live progress numbers could run noticeably ahead of the final
count. Progress now excludes symlinks, matching every other counting
function in the codebase
25 changes: 25 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,31 @@ Sort modes:
| `files-first` | Files before directories, each group alphabetical |
| `alpha` | Strict alphabetical — ignores whether an entry is a file or directory |

### Other

| Flag | Default | Description |
|---|---|---|
| `--no-progress` | off | Suppress scan progress and completion telemetry on large directories |
| `--version` | — | Print the installed version and exit |

---

## Scan awareness

On large directories, `tree2guide` prints periodic progress and a completion
summary — always to **stderr**, never stdout, so `--stdout | your-tool`
piping is never affected:

```
Scanning... 12,430 files, 2,105 dirs
Scanning... 91,202 files, 8,340 dirs
Scan complete. Files: 227,886, Directories: 13,415, Elapsed: 18.2s
```

Progress is gated on elapsed wall-clock time (roughly once per second), not
entry count — a fast scan of a small project prints nothing extra at all.
Pass `--no-progress` to suppress this entirely, including the final summary.

---

## Formats
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "tree2guide"
version = "1.1.0"
version = "1.2.0"
description = "See your project structure clearly — a tree generator with gitignore-compatible exclusion, multi-format output, and AI-ready summaries."
readme = "README.md"
license = { file = "LICENSE" }
Expand Down
Loading
Loading