Metadata-Version: 2.4
Name: lastmod
Version: 0.2.0
Summary: Produces a directory tree of non-generated files by last updated
Keywords: tree,last-modified,filesystem,cli
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: System :: Filesystems
Classifier: Topic :: Utilities
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# lastmod

Show which parts of a project were touched most recently, as a tree.

`lastmod` scans a directory tree and prints it like `tree`, but each directory
shows a single file — the **latest-modified file** anywhere in that directory's
branch. Great for answering "where has the work been happening?" at a glance.

```
.
├── docs
├── src
│   └── lastmod
│       ├── cache
│       │   └── default.py  [2026-07-03 23:00:01]
│       └── lastmod.py      [2026-07-03 18:00:01]
├── tests
│   └── test_cache.py       [2026-07-05 00:00:01]  (3)
└── uv.lock                 [2026-07-10 00:00:01]
```

## Installation

```sh
pip install lastmod
# or
uv tool install lastmod
# or
pipx install lastmod
```

Requires Python 3.10 or later.

## Usage

```sh
lastmod [directory] [options]
```

If no directory is given, the current directory is scanned.

| Option | Description |
| --- | --- |
| `-i, --include PATTERN` | Include only files matching the patterns (extensions, globs). Comma or space separated; repeatable. Defaults to `.md .py .ts .js`. |
| `-e, --exclude PATTERN` | Exclude files/folders matching the patterns. Comma or space separated; repeatable. `node_modules`, `.git`, `.venv`, `__pycache__`, hidden files and similar are always excluded. |
| `-a, --include-all` | Skip the default include set and show every file type (exclusions still apply). |
| `-L, --level N` | Limit display depth like `tree -L`. `0` = root only, `1` = first-level dirs, etc. Default: unlimited. |
| `--by-time` | Sort tree entries by last-modified time, newest first (default: alphabetical). |
| `-f, --force` | Ignore the cache and rescan. |
| `--cache-info` | Print cache status for the target directory and exit. |
| `--ttl HOURS` | Cache time-to-live in hours (default: 24, or `LASTMOD_CACHE_TTL_HOURS`). |

### Examples

```sh
# Show the whole project, latest file per directory
lastmod

# First two directory levels only
lastmod -L 2

# Docs only
lastmod -i ".md .txt" -e notes

# Everything, newest first
lastmod -a --by-time

# Force a fresh scan
lastmod -f
```

## How the display works

- Directories are shown like `tree`, in alphabetical order, down to the `-L`
  limit. Empty directories appear too.
- Each directory shows **one file**: the latest-modified file among all files
  whose deepest *displayed* directory is that directory. When subdirectories
  are within the `-L` limit they claim their own files, so a file only "bubbles
  up" past a collapsed branch.
- If the winning file sits deeper than the displayed depth, its location is
  shown as a hint: `[lastmod/cache/]` (truncated to three path components).
- When several files share the winning mtime, the alphabetically first name
  wins, and the `(n)` count shows how many files share that exact mtime
  (only when more than one).
- Ties are broken alphabetically, so output is deterministic.

With `-L 1` on the example above:

```
.
├── docs
├── src
│   └── default.py  [2026-07-03 23:00:01]  [lastmod/cache/]
├── tests
│   └── test_cache.py  [2026-07-05 00:00:01]  (3)
└── uv.lock  [2026-07-10 00:00:01]
```

`src/` shows `default.py` — the newest file in the whole `src` branch — with a
hint pointing at where it actually lives.

## Caching

- The first run performs a full scan and stores it in
  `~/.cache/lastmod/<hash-of-directory>.json`.
- Runs within the TTL reuse the cache, so changing `--include`, `--exclude`,
  `--level`, or `--by-time` never forces a rescan — filters apply at query
  time.
- `--force` always rescans. Note that files deleted since the scan remain in
  the cache until the TTL expires; use `--force` for a fresh view.
- Set `LASTMOD_CACHE_TTL_HOURS` to change the default TTL.

## Development

```sh
uv sync                 # install dev dependencies
uv run pytest           # run the tests
uv run pytest --cov     # run tests with coverage report (80% floor)
uv run mypy             # strict type-check
uv build                # build sdist + wheel
```
