killpy 0.25.1__tar.gz → 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. {killpy-0.25.1 → killpy-1.0.0}/PKG-INFO +9 -10
  2. {killpy-0.25.1 → killpy-1.0.0}/README.md +8 -9
  3. {killpy-0.25.1 → killpy-1.0.0}/killpy/cli.py +5 -9
  4. {killpy-0.25.1 → killpy-1.0.0}/killpy/commands/_utils.py +2 -2
  5. {killpy-0.25.1 → killpy-1.0.0}/killpy/commands/delete.py +1 -1
  6. {killpy-0.25.1 → killpy-1.0.0}/killpy/commands/doctor.py +2 -2
  7. {killpy-0.25.1 → killpy-1.0.0}/killpy/commands/list.py +3 -3
  8. {killpy-0.25.1 → killpy-1.0.0}/killpy/detectors/artifacts.py +1 -1
  9. {killpy-0.25.1 → killpy-1.0.0}/killpy/detectors/cache.py +1 -1
  10. {killpy-0.25.1 → killpy-1.0.0}/killpy/detectors/conda.py +1 -1
  11. {killpy-0.25.1 → killpy-1.0.0}/killpy/detectors/hatch.py +1 -1
  12. {killpy-0.25.1 → killpy-1.0.0}/killpy/detectors/pipenv.py +1 -1
  13. {killpy-0.25.1 → killpy-1.0.0}/killpy/detectors/pipx.py +1 -1
  14. {killpy-0.25.1 → killpy-1.0.0}/killpy/detectors/poetry.py +1 -1
  15. {killpy-0.25.1 → killpy-1.0.0}/killpy/detectors/pyenv.py +1 -1
  16. {killpy-0.25.1 → killpy-1.0.0}/killpy/detectors/tox.py +1 -1
  17. {killpy-0.25.1 → killpy-1.0.0}/killpy/detectors/uv.py +1 -1
  18. {killpy-0.25.1 → killpy-1.0.0}/killpy/detectors/venv.py +1 -1
  19. {killpy-0.25.1 → killpy-1.0.0}/killpy/intelligence/scoring.py +4 -4
  20. {killpy-0.25.1 → killpy-1.0.0}/killpy/intelligence/suggestions.py +21 -25
  21. {killpy-0.25.1 → killpy-1.0.0}/killpy/models.py +7 -6
  22. {killpy-0.25.1 → killpy-1.0.0}/killpy.egg-info/PKG-INFO +9 -10
  23. {killpy-0.25.1 → killpy-1.0.0}/pyproject.toml +1 -1
  24. {killpy-0.25.1 → killpy-1.0.0}/killpy/__init__.py +0 -0
  25. {killpy-0.25.1 → killpy-1.0.0}/killpy/__main__.py +0 -0
  26. {killpy-0.25.1 → killpy-1.0.0}/killpy/cleaner.py +0 -0
  27. {killpy-0.25.1 → killpy-1.0.0}/killpy/cleaners/__init__.py +0 -0
  28. {killpy-0.25.1 → killpy-1.0.0}/killpy/commands/__init__.py +0 -0
  29. {killpy-0.25.1 → killpy-1.0.0}/killpy/commands/clean.py +0 -0
  30. {killpy-0.25.1 → killpy-1.0.0}/killpy/commands/find.py +0 -0
  31. {killpy-0.25.1 → killpy-1.0.0}/killpy/commands/stats.py +0 -0
  32. {killpy-0.25.1 → killpy-1.0.0}/killpy/detectors/__init__.py +0 -0
  33. {killpy-0.25.1 → killpy-1.0.0}/killpy/detectors/base.py +0 -0
  34. {killpy-0.25.1 → killpy-1.0.0}/killpy/files/__init__.py +0 -0
  35. {killpy-0.25.1 → killpy-1.0.0}/killpy/intelligence/__init__.py +0 -0
  36. {killpy-0.25.1 → killpy-1.0.0}/killpy/intelligence/git_analyzer.py +0 -0
  37. {killpy-0.25.1 → killpy-1.0.0}/killpy/intelligence/tracker.py +0 -0
  38. {killpy-0.25.1 → killpy-1.0.0}/killpy/scanner.py +0 -0
  39. {killpy-0.25.1 → killpy-1.0.0}/killpy.egg-info/SOURCES.txt +0 -0
  40. {killpy-0.25.1 → killpy-1.0.0}/killpy.egg-info/dependency_links.txt +0 -0
  41. {killpy-0.25.1 → killpy-1.0.0}/killpy.egg-info/entry_points.txt +0 -0
  42. {killpy-0.25.1 → killpy-1.0.0}/killpy.egg-info/requires.txt +0 -0
  43. {killpy-0.25.1 → killpy-1.0.0}/killpy.egg-info/top_level.txt +0 -0
  44. {killpy-0.25.1 → killpy-1.0.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: killpy
3
- Version: 0.25.1
3
+ Version: 1.0.0
4
4
  Summary: Find and delete unused Python environments (.venv, conda, poetry, pipenv, pipx, pyenv, uv) and caches to free disk space — from a fast terminal UI or a single CLI command.
5
5
  Project-URL: Homepage, https://github.com/Tlaloc-Es/killpy
6
6
  Project-URL: Documentation, https://tlaloc-es.github.io/killpy/
@@ -196,7 +196,7 @@ Environments flagged with `⚠️` are actively in use (the environment killpy r
196
196
  | Key | Action |
197
197
  |-----|--------|
198
198
  | `↑` / `↓` or `k` / `j` | Move cursor up / down (vim-style) |
199
- | `/` | Open live search/filter bar (regex supported) |
199
+ | `/` | Open live search/filter bar (substring match) |
200
200
  | `Escape` | Close search bar and clear filter |
201
201
  | `T` | Toggle multi-select mode on / off |
202
202
  | `Space` | *(Multi-select)* Toggle current row selected |
@@ -211,7 +211,7 @@ Environments flagged with `⚠️` are actively in use (the environment killpy r
211
211
 
212
212
  ### Search / filter
213
213
 
214
- Press `/` to open the filter bar at the bottom of the screen. Type any string or a Python regex — the venv table updates live as you type. Press `Escape` or submit an empty value to clear the filter and return to the full list.
214
+ Press `/` to open the filter bar at the bottom of the screen. Type any text — the venv table filters by path substring (case-insensitive) and updates live as you type. Press `Escape` or submit an empty value to clear the filter and return to the full list.
215
215
 
216
216
  ### Multi-select mode
217
217
 
@@ -265,7 +265,7 @@ ______________________________________________________________________
265
265
  killpy list # list all detected environments
266
266
  killpy list --path ~/projects # scan a specific path
267
267
  killpy list --type venv --type conda # filter by type (repeatable)
268
- killpy list --older-than 90 # not accessed in the last 90 days
268
+ killpy list --older-than 90 # not modified in the last 90 days
269
269
  killpy list --json # output as a JSON array
270
270
  killpy list --json-stream # stream as NDJSON — one line per env
271
271
  killpy list --quiet # suppress progress output (scripts/CI)
@@ -284,7 +284,7 @@ The **Path** column mirrors the form of `--path`: a relative `--path` produces r
284
284
  "absolute_path": "/home/user/projects/my-app/.venv",
285
285
  "name": "my-app/.venv",
286
286
  "type": "venv",
287
- "last_accessed": "2025-11-02T14:23:01+00:00",
287
+ "last_modified": "2025-11-02T14:23:01+00:00",
288
288
  "size_bytes": 54393984,
289
289
  "size_human": "51.88 MB",
290
290
  "managed_by": null,
@@ -417,8 +417,7 @@ Category is assigned deterministically by applying the following rules in order:
417
417
  |----------|------|----------|
418
418
  | 1 | Orphan (`is_orphan=True`) **and** `age ≥ 180 days` | `HIGH` |
419
419
  | 2 | Active git repository **or** `age < 120 days` | `LOW` |
420
- | 3 | `age ≥ 120 days` | `MEDIUM` |
421
- | 4 | Fallback | `LOW` |
420
+ | 3 | `age ≥ 120 days` *(exhaustive fallback)* | `MEDIUM` |
422
421
 
423
422
  Age and orphan status are the dominant signals. Size does not affect the category.
424
423
 
@@ -498,7 +497,7 @@ Yes. Run `killpy --path ~` to scan your home folder. The `stats` command gives a
498
497
  killpy delete --type venv --older-than 90 --yes
499
498
  ```
500
499
 
501
- Deletes every `.venv` / `pyvenv.cfg` env not accessed in the last 90 days, without prompting.
500
+ Deletes every `.venv` / `pyvenv.cfg` env not modified in the last 90 days, without prompting.
502
501
 
503
502
  **How do I use killpy in a CI pipeline or script?**
504
503
 
@@ -604,7 +603,7 @@ ______________________________________________________________________
604
603
 
605
604
  ```yaml
606
605
  - repo: https://github.com/Tlaloc-Es/KillPy
607
- rev: 0.25.1
606
+ rev: 1.0.0
608
607
  hooks:
609
608
  - id: killpy # remove __pycache__ on every commit
610
609
  - id: killpy-clean-caches # also removes .mypy_cache, .pytest_cache, .ruff_cache
@@ -629,7 +628,7 @@ Typical minimal setup (safe for daily use):
629
628
 
630
629
  ```yaml
631
630
  - repo: https://github.com/Tlaloc-Es/KillPy
632
- rev: 0.25.1
631
+ rev: 1.0.0
633
632
  hooks:
634
633
  - id: killpy
635
634
  ```
@@ -167,7 +167,7 @@ Environments flagged with `⚠️` are actively in use (the environment killpy r
167
167
  | Key | Action |
168
168
  |-----|--------|
169
169
  | `↑` / `↓` or `k` / `j` | Move cursor up / down (vim-style) |
170
- | `/` | Open live search/filter bar (regex supported) |
170
+ | `/` | Open live search/filter bar (substring match) |
171
171
  | `Escape` | Close search bar and clear filter |
172
172
  | `T` | Toggle multi-select mode on / off |
173
173
  | `Space` | *(Multi-select)* Toggle current row selected |
@@ -182,7 +182,7 @@ Environments flagged with `⚠️` are actively in use (the environment killpy r
182
182
 
183
183
  ### Search / filter
184
184
 
185
- Press `/` to open the filter bar at the bottom of the screen. Type any string or a Python regex — the venv table updates live as you type. Press `Escape` or submit an empty value to clear the filter and return to the full list.
185
+ Press `/` to open the filter bar at the bottom of the screen. Type any text — the venv table filters by path substring (case-insensitive) and updates live as you type. Press `Escape` or submit an empty value to clear the filter and return to the full list.
186
186
 
187
187
  ### Multi-select mode
188
188
 
@@ -236,7 +236,7 @@ ______________________________________________________________________
236
236
  killpy list # list all detected environments
237
237
  killpy list --path ~/projects # scan a specific path
238
238
  killpy list --type venv --type conda # filter by type (repeatable)
239
- killpy list --older-than 90 # not accessed in the last 90 days
239
+ killpy list --older-than 90 # not modified in the last 90 days
240
240
  killpy list --json # output as a JSON array
241
241
  killpy list --json-stream # stream as NDJSON — one line per env
242
242
  killpy list --quiet # suppress progress output (scripts/CI)
@@ -255,7 +255,7 @@ The **Path** column mirrors the form of `--path`: a relative `--path` produces r
255
255
  "absolute_path": "/home/user/projects/my-app/.venv",
256
256
  "name": "my-app/.venv",
257
257
  "type": "venv",
258
- "last_accessed": "2025-11-02T14:23:01+00:00",
258
+ "last_modified": "2025-11-02T14:23:01+00:00",
259
259
  "size_bytes": 54393984,
260
260
  "size_human": "51.88 MB",
261
261
  "managed_by": null,
@@ -388,8 +388,7 @@ Category is assigned deterministically by applying the following rules in order:
388
388
  |----------|------|----------|
389
389
  | 1 | Orphan (`is_orphan=True`) **and** `age ≥ 180 days` | `HIGH` |
390
390
  | 2 | Active git repository **or** `age < 120 days` | `LOW` |
391
- | 3 | `age ≥ 120 days` | `MEDIUM` |
392
- | 4 | Fallback | `LOW` |
391
+ | 3 | `age ≥ 120 days` *(exhaustive fallback)* | `MEDIUM` |
393
392
 
394
393
  Age and orphan status are the dominant signals. Size does not affect the category.
395
394
 
@@ -469,7 +468,7 @@ Yes. Run `killpy --path ~` to scan your home folder. The `stats` command gives a
469
468
  killpy delete --type venv --older-than 90 --yes
470
469
  ```
471
470
 
472
- Deletes every `.venv` / `pyvenv.cfg` env not accessed in the last 90 days, without prompting.
471
+ Deletes every `.venv` / `pyvenv.cfg` env not modified in the last 90 days, without prompting.
473
472
 
474
473
  **How do I use killpy in a CI pipeline or script?**
475
474
 
@@ -575,7 +574,7 @@ ______________________________________________________________________
575
574
 
576
575
  ```yaml
577
576
  - repo: https://github.com/Tlaloc-Es/KillPy
578
- rev: 0.25.1
577
+ rev: 1.0.0
579
578
  hooks:
580
579
  - id: killpy # remove __pycache__ on every commit
581
580
  - id: killpy-clean-caches # also removes .mypy_cache, .pytest_cache, .ruff_cache
@@ -600,7 +599,7 @@ Typical minimal setup (safe for daily use):
600
599
 
601
600
  ```yaml
602
601
  - repo: https://github.com/Tlaloc-Es/KillPy
603
- rev: 0.25.1
602
+ rev: 1.0.0
604
603
  hooks:
605
604
  - id: killpy
606
605
  ```
@@ -3,7 +3,6 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import asyncio
6
- import re
7
6
  import subprocess
8
7
  import sys
9
8
  from datetime import datetime
@@ -297,7 +296,7 @@ class TableApp(App):
297
296
  yield Label("", id="status-label")
298
297
  yield Label("", id="selected-path-label")
299
298
  yield Input(
300
- placeholder="Filter by path (regex)… Esc to clear",
299
+ placeholder="Filter by path… Esc to clear",
301
300
  id="search-input",
302
301
  )
303
302
  yield Label("", id="multi-select-label")
@@ -337,7 +336,7 @@ class TableApp(App):
337
336
  {
338
337
  "path": str(environment.path),
339
338
  "type": environment.type,
340
- "last_modified": environment.last_accessed_str,
339
+ "last_modified": environment.last_modified_str,
341
340
  "size": environment.size_bytes,
342
341
  "size_human": environment.size_human,
343
342
  "health": self._health_by_path.get(str(environment.path), ""),
@@ -382,13 +381,10 @@ class TableApp(App):
382
381
  table.clear(columns=True)
383
382
  table.add_columns(*self.get_headers_for_table("venv-table"))
384
383
  self._venv_display_indices = []
384
+ query = self._filter_query.lower()
385
385
  for i, row in enumerate(self.venv_rows):
386
- if self._filter_query:
387
- try:
388
- if not re.search(self._filter_query, row["path"], re.IGNORECASE):
389
- continue
390
- except re.error:
391
- pass # invalid regex — show all
386
+ if query and query not in row["path"].lower():
387
+ continue
392
388
  self._venv_display_indices.append(i)
393
389
  env = row["environment"]
394
390
  type_label = ("\u26a0\ufe0f " if env.is_system_critical else "") + row[
@@ -68,7 +68,7 @@ def filter_envs(
68
68
  names such as ``"venv"`` and ``"cache"`` are automatically expanded to
69
69
  their concrete sub-type values via :data:`_TYPE_ALIASES`.
70
70
  older_than:
71
- If provided, only environments not accessed in the last *older_than* days
71
+ If provided, only environments not modified in the last *older_than* days
72
72
  are kept.
73
73
  """
74
74
  now = datetime.now(tz=timezone.utc)
@@ -84,6 +84,6 @@ def filter_envs(
84
84
 
85
85
  if older_than is not None:
86
86
  cutoff = now - timedelta(days=older_than)
87
- result = [e for e in result if e.last_accessed < cutoff]
87
+ result = [e for e in result if e.last_modified < cutoff]
88
88
 
89
89
  return result
@@ -37,7 +37,7 @@ from killpy.scanner import Scanner
37
37
  type=int,
38
38
  default=None,
39
39
  metavar="DAYS",
40
- help="Only delete environments not accessed in the last N days.",
40
+ help="Only delete environments not modified in the last N days.",
41
41
  )
42
42
  @click.option(
43
43
  "--dry-run",
@@ -195,7 +195,7 @@ def _output_rich(
195
195
 
196
196
  now = datetime.now(tz=timezone.utc)
197
197
  for se in top:
198
- la = se.env.last_accessed
198
+ la = se.env.last_modified
199
199
  if la.tzinfo is None:
200
200
  la = la.replace(tzinfo=timezone.utc)
201
201
  age_days = (now - la).days
@@ -264,7 +264,7 @@ def _print_category_table(
264
264
 
265
265
  now = datetime.now(tz=timezone.utc)
266
266
  for se in scored_envs:
267
- la = se.env.last_accessed
267
+ la = se.env.last_modified
268
268
  if la.tzinfo is None:
269
269
  la = la.replace(tzinfo=timezone.utc)
270
270
  age_days = (now - la).days
@@ -58,7 +58,7 @@ def _print_table(envs: list, console: Console) -> None:
58
58
  table = Table(show_header=True, header_style="bold cyan")
59
59
  table.add_column("Type", style="dim", min_width=10)
60
60
  table.add_column("Name", min_width=20)
61
- table.add_column("Last accessed", min_width=12)
61
+ table.add_column("Last modified", min_width=12)
62
62
  table.add_column("Size", justify="right", min_width=9)
63
63
  table.add_column("Path")
64
64
 
@@ -66,7 +66,7 @@ def _print_table(envs: list, console: Console) -> None:
66
66
  table.add_row(
67
67
  env.type,
68
68
  env.name,
69
- env.last_accessed_str,
69
+ env.last_modified_str,
70
70
  env.size_human,
71
71
  str(env.path),
72
72
  )
@@ -101,7 +101,7 @@ def _print_table(envs: list, console: Console) -> None:
101
101
  type=int,
102
102
  default=None,
103
103
  metavar="DAYS",
104
- help="Only show environments not accessed in the last N days.",
104
+ help="Only show environments not modified in the last N days.",
105
105
  )
106
106
  @click.option(
107
107
  "--json-stream",
@@ -69,7 +69,7 @@ class ArtifactsDetector(AbstractDetector):
69
69
  path=artifact_path,
70
70
  name=str(artifact_path),
71
71
  type="artifacts",
72
- last_accessed=mtime,
72
+ last_modified=mtime,
73
73
  size_bytes=size,
74
74
  )
75
75
  )
@@ -147,6 +147,6 @@ def _make_cache_env(cache_path: Path, tag: str) -> Environment:
147
147
  path=cache_path,
148
148
  name=str(cache_path),
149
149
  type=tag,
150
- last_accessed=mtime,
150
+ last_modified=mtime,
151
151
  size_bytes=size,
152
152
  )
@@ -97,7 +97,7 @@ class CondaDetector(AbstractDetector):
97
97
  path=env_path,
98
98
  name=env_name,
99
99
  type="conda",
100
- last_accessed=mtime,
100
+ last_modified=mtime,
101
101
  size_bytes=size,
102
102
  managed_by="conda",
103
103
  )
@@ -73,7 +73,7 @@ class HatchDetector(AbstractDetector):
73
73
  path=env_dir,
74
74
  name=f"{project_dir.name}/{env_dir.name}",
75
75
  type="hatch",
76
- last_accessed=mtime,
76
+ last_modified=mtime,
77
77
  size_bytes=size,
78
78
  )
79
79
  )
@@ -62,7 +62,7 @@ class PipenvDetector(AbstractDetector):
62
62
  path=venv_path,
63
63
  name=venv_path.name,
64
64
  type="pipenv",
65
- last_accessed=mtime,
65
+ last_modified=mtime,
66
66
  size_bytes=size,
67
67
  )
68
68
  )
@@ -126,7 +126,7 @@ class PipxDetector(AbstractDetector):
126
126
  path=candidate,
127
127
  name=package_name,
128
128
  type="pipx",
129
- last_accessed=mtime,
129
+ last_modified=mtime,
130
130
  size_bytes=size,
131
131
  managed_by="pipx",
132
132
  )
@@ -72,7 +72,7 @@ class PoetryDetector(AbstractDetector):
72
72
  path=venv_path,
73
73
  name=venv_path.name,
74
74
  type="poetry",
75
- last_accessed=mtime,
75
+ last_modified=mtime,
76
76
  size_bytes=size,
77
77
  )
78
78
  )
@@ -68,7 +68,7 @@ class PyenvDetector(AbstractDetector):
68
68
  path=version_dir,
69
69
  name=version_dir.name,
70
70
  type="pyenv",
71
- last_accessed=mtime,
71
+ last_modified=mtime,
72
72
  size_bytes=size,
73
73
  )
74
74
  )
@@ -42,7 +42,7 @@ class ToxDetector(AbstractDetector):
42
42
  path=tox_path,
43
43
  name=str(tox_path),
44
44
  type="tox",
45
- last_accessed=mtime,
45
+ last_modified=mtime,
46
46
  size_bytes=size,
47
47
  )
48
48
  )
@@ -81,7 +81,7 @@ class UvDetector(AbstractDetector):
81
81
  path=env_dir,
82
82
  name=env_dir.name,
83
83
  type="uv",
84
- last_accessed=mtime,
84
+ last_modified=mtime,
85
85
  size_bytes=size,
86
86
  managed_by=managed_by,
87
87
  )
@@ -105,6 +105,6 @@ def _make_env(dir_path: Path, tag: str) -> Environment:
105
105
  path=dir_path,
106
106
  name=str(dir_path),
107
107
  type=tag,
108
- last_accessed=mtime,
108
+ last_modified=mtime,
109
109
  size_bytes=size,
110
110
  )
@@ -69,8 +69,8 @@ class ScoringService:
69
69
  size_score = self._normalize_size(env.size_bytes)
70
70
  explanation.append(f"Size: {env.size_human}")
71
71
 
72
- age_score, age_days = self._normalize_age(env.last_accessed)
73
- explanation.append(f"Last accessed {age_days} days ago")
72
+ age_score, age_days = self._normalize_age(env.last_modified)
73
+ explanation.append(f"Last modified {age_days} days ago")
74
74
 
75
75
  is_orphan, orphan_score = self._orphan_score(env.path)
76
76
  has_project_files = not is_orphan
@@ -132,11 +132,11 @@ class ScoringService:
132
132
  return 1.0 / (1.0 + math.exp(-k * (size_bytes - x0)))
133
133
 
134
134
  @staticmethod
135
- def _normalize_age(last_accessed: datetime) -> tuple[float, int]:
135
+ def _normalize_age(last_modified: datetime) -> tuple[float, int]:
136
136
  """Return (age_score, age_days) where age_score is linear [0, 1]."""
137
137
  now = datetime.now(tz=timezone.utc)
138
138
  try:
139
- la = last_accessed
139
+ la = last_modified
140
140
  if la.tzinfo is None:
141
141
  la = la.replace(tzinfo=timezone.utc)
142
142
  age_days = max(0, (now - la).days)
@@ -9,8 +9,9 @@ from killpy.models import ScoredEnvironment, Suggestion
9
9
 
10
10
  # Age thresholds in days.
11
11
  _HIGH_AGE_ORPHAN_THRESHOLD = 180 # orphan + age >= this → HIGH
12
- _MEDIUM_AGE_THRESHOLD = 120 # age >= this → MEDIUM
13
- _LOW_AGE_THRESHOLD = 120 # age < this LOW
12
+ # Single LOW/MEDIUM cutoff: age < this → LOW (recently modified), age >= this →
13
+ # MEDIUM (stale). Active git always forces LOW regardless of age.
14
+ _STALE_AGE_THRESHOLD = 120
14
15
 
15
16
  _ACTION_HIGH = "Delete — unused and orphaned"
16
17
  _ACTION_MEDIUM = "Review — possibly unused"
@@ -59,8 +60,8 @@ class SuggestionEngine:
59
60
 
60
61
  @staticmethod
61
62
  def _age_days(scored: ScoredEnvironment) -> int:
62
- """Return age in days based on env.last_accessed."""
63
- la = scored.env.last_accessed
63
+ """Return age in days based on env.last_modified."""
64
+ la = scored.env.last_modified
64
65
  if la.tzinfo is None:
65
66
  la = la.replace(tzinfo=timezone.utc)
66
67
  return max(0, (datetime.now(tz=timezone.utc) - la).days)
@@ -81,32 +82,27 @@ class SuggestionEngine:
81
82
  # ── Rule 1: HIGH — orphan and stale ────────────────────────────────────
82
83
  if is_orphan and age_days >= _HIGH_AGE_ORPHAN_THRESHOLD:
83
84
  reasons.append("Orphan — no project files found nearby")
84
- reasons.append(f"Not used in {age_days} days")
85
+ reasons.append(f"Not modified in {age_days} days")
85
86
  return "HIGH", _ACTION_HIGH
86
87
 
87
88
  # ── Rule 2: LOW — active git or very recent ─────────────────────────────
88
- if is_active_git or age_days < _LOW_AGE_THRESHOLD:
89
+ if is_active_git or age_days < _STALE_AGE_THRESHOLD:
89
90
  if is_active_git:
90
91
  reasons.append("Active git repository")
91
- if age_days < _LOW_AGE_THRESHOLD:
92
- reasons.append(f"Recently used ({age_days} days ago)")
92
+ if age_days < _STALE_AGE_THRESHOLD:
93
+ reasons.append(f"Recently modified ({age_days} days ago)")
93
94
  action = _ACTION_LOW_ACTIVE if is_active_git else _ACTION_LOW
94
95
  return "LOW", action
95
96
 
96
- # ── Rule 3: MEDIUM — moderately old ────────────────────────────────────
97
- if age_days >= _MEDIUM_AGE_THRESHOLD:
98
- if is_orphan:
99
- reasons.append("Orphan — no project files found nearby")
100
- reasons.append(f"Not used in {age_days} days")
101
- if (
102
- scored.git_info is not None
103
- and scored.git_info.is_git_repo
104
- and not scored.git_info.is_active
105
- ):
106
- reasons.append("Associated git repo has no recent commits")
107
- return "MEDIUM", _ACTION_MEDIUM
108
-
109
- # ── Rule 4: FALLBACK → LOW ──────────────────────────────────────────────
110
- if is_active_git:
111
- reasons.append("Active git repository")
112
- return "LOW", _ACTION_LOW
97
+ # ── Rule 3: MEDIUM — stale (not active, age >= _STALE_AGE_THRESHOLD) ────
98
+ # Exhaustive fallback: everything not caught by Rules 1–2 is stale.
99
+ if is_orphan:
100
+ reasons.append("Orphan — no project files found nearby")
101
+ reasons.append(f"Not modified in {age_days} days")
102
+ if (
103
+ scored.git_info is not None
104
+ and scored.git_info.is_git_repo
105
+ and not scored.git_info.is_active
106
+ ):
107
+ reasons.append("Associated git repo has no recent commits")
108
+ return "MEDIUM", _ACTION_MEDIUM
@@ -35,8 +35,9 @@ class Environment:
35
35
  type:
36
36
  Short detector tag, e.g. ``"venv"``, ``"pyvenv.cfg"``, ``"poetry"``,
37
37
  ``"conda"``, ``"pipx"``, ``"cache"``, ``"uv"``, ``"artifacts"``…
38
- last_accessed:
39
- Timestamp of the last modification reported by the filesystem.
38
+ last_modified:
39
+ Last modification time (``st_mtime``) of the environment root, as
40
+ reported by the filesystem — not an access time.
40
41
  size_bytes:
41
42
  Total size in bytes (recursive directory sum).
42
43
  managed_by:
@@ -48,7 +49,7 @@ class Environment:
48
49
  path: Path
49
50
  name: str
50
51
  type: str
51
- last_accessed: datetime
52
+ last_modified: datetime
52
53
  size_bytes: int
53
54
  managed_by: str | None = None
54
55
  is_system_critical: bool = False
@@ -63,9 +64,9 @@ class Environment:
63
64
  return format_size(self.size_bytes)
64
65
 
65
66
  @property
66
- def last_accessed_str(self) -> str:
67
+ def last_modified_str(self) -> str:
67
68
  """Formatted date string ``DD/MM/YYYY`` for display."""
68
- return self.last_accessed.strftime("%d/%m/%Y")
69
+ return self.last_modified.strftime("%d/%m/%Y")
69
70
 
70
71
  # ------------------------------------------------------------------ #
71
72
  # Serialisation #
@@ -83,7 +84,7 @@ class Environment:
83
84
  "absolute_path": str(abs_path),
84
85
  "name": self.name,
85
86
  "type": self.type,
86
- "last_accessed": self.last_accessed.isoformat(),
87
+ "last_modified": self.last_modified.isoformat(),
87
88
  "size_bytes": self.size_bytes,
88
89
  "size_human": self.size_human,
89
90
  "managed_by": self.managed_by,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: killpy
3
- Version: 0.25.1
3
+ Version: 1.0.0
4
4
  Summary: Find and delete unused Python environments (.venv, conda, poetry, pipenv, pipx, pyenv, uv) and caches to free disk space — from a fast terminal UI or a single CLI command.
5
5
  Project-URL: Homepage, https://github.com/Tlaloc-Es/killpy
6
6
  Project-URL: Documentation, https://tlaloc-es.github.io/killpy/
@@ -196,7 +196,7 @@ Environments flagged with `⚠️` are actively in use (the environment killpy r
196
196
  | Key | Action |
197
197
  |-----|--------|
198
198
  | `↑` / `↓` or `k` / `j` | Move cursor up / down (vim-style) |
199
- | `/` | Open live search/filter bar (regex supported) |
199
+ | `/` | Open live search/filter bar (substring match) |
200
200
  | `Escape` | Close search bar and clear filter |
201
201
  | `T` | Toggle multi-select mode on / off |
202
202
  | `Space` | *(Multi-select)* Toggle current row selected |
@@ -211,7 +211,7 @@ Environments flagged with `⚠️` are actively in use (the environment killpy r
211
211
 
212
212
  ### Search / filter
213
213
 
214
- Press `/` to open the filter bar at the bottom of the screen. Type any string or a Python regex — the venv table updates live as you type. Press `Escape` or submit an empty value to clear the filter and return to the full list.
214
+ Press `/` to open the filter bar at the bottom of the screen. Type any text — the venv table filters by path substring (case-insensitive) and updates live as you type. Press `Escape` or submit an empty value to clear the filter and return to the full list.
215
215
 
216
216
  ### Multi-select mode
217
217
 
@@ -265,7 +265,7 @@ ______________________________________________________________________
265
265
  killpy list # list all detected environments
266
266
  killpy list --path ~/projects # scan a specific path
267
267
  killpy list --type venv --type conda # filter by type (repeatable)
268
- killpy list --older-than 90 # not accessed in the last 90 days
268
+ killpy list --older-than 90 # not modified in the last 90 days
269
269
  killpy list --json # output as a JSON array
270
270
  killpy list --json-stream # stream as NDJSON — one line per env
271
271
  killpy list --quiet # suppress progress output (scripts/CI)
@@ -284,7 +284,7 @@ The **Path** column mirrors the form of `--path`: a relative `--path` produces r
284
284
  "absolute_path": "/home/user/projects/my-app/.venv",
285
285
  "name": "my-app/.venv",
286
286
  "type": "venv",
287
- "last_accessed": "2025-11-02T14:23:01+00:00",
287
+ "last_modified": "2025-11-02T14:23:01+00:00",
288
288
  "size_bytes": 54393984,
289
289
  "size_human": "51.88 MB",
290
290
  "managed_by": null,
@@ -417,8 +417,7 @@ Category is assigned deterministically by applying the following rules in order:
417
417
  |----------|------|----------|
418
418
  | 1 | Orphan (`is_orphan=True`) **and** `age ≥ 180 days` | `HIGH` |
419
419
  | 2 | Active git repository **or** `age < 120 days` | `LOW` |
420
- | 3 | `age ≥ 120 days` | `MEDIUM` |
421
- | 4 | Fallback | `LOW` |
420
+ | 3 | `age ≥ 120 days` *(exhaustive fallback)* | `MEDIUM` |
422
421
 
423
422
  Age and orphan status are the dominant signals. Size does not affect the category.
424
423
 
@@ -498,7 +497,7 @@ Yes. Run `killpy --path ~` to scan your home folder. The `stats` command gives a
498
497
  killpy delete --type venv --older-than 90 --yes
499
498
  ```
500
499
 
501
- Deletes every `.venv` / `pyvenv.cfg` env not accessed in the last 90 days, without prompting.
500
+ Deletes every `.venv` / `pyvenv.cfg` env not modified in the last 90 days, without prompting.
502
501
 
503
502
  **How do I use killpy in a CI pipeline or script?**
504
503
 
@@ -604,7 +603,7 @@ ______________________________________________________________________
604
603
 
605
604
  ```yaml
606
605
  - repo: https://github.com/Tlaloc-Es/KillPy
607
- rev: 0.25.1
606
+ rev: 1.0.0
608
607
  hooks:
609
608
  - id: killpy # remove __pycache__ on every commit
610
609
  - id: killpy-clean-caches # also removes .mypy_cache, .pytest_cache, .ruff_cache
@@ -629,7 +628,7 @@ Typical minimal setup (safe for daily use):
629
628
 
630
629
  ```yaml
631
630
  - repo: https://github.com/Tlaloc-Es/KillPy
632
- rev: 0.25.1
631
+ rev: 1.0.0
633
632
  hooks:
634
633
  - id: killpy
635
634
  ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "killpy"
3
- version = "0.25.1"
3
+ version = "1.0.0"
4
4
  description = "Find and delete unused Python environments (.venv, conda, poetry, pipenv, pipx, pyenv, uv) and caches to free disk space — from a fast terminal UI or a single CLI command."
5
5
  readme = "README.md"
6
6
  keywords = [
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes